README
shift.storage is a object oriented way to store data locally on your device, being that a web or hibrid mobile application and, optionally, syncing in background any changed data with your server. It wraps and uses localForage lib and some of it's extensions, as well as it includes the localForage's cordova SQLite driver and makes it the primary option of use, when cordova sqlite plugin is available.
Install
npm i @shiftcode/shift.storage
Concept
shift.storage treats every data as objects, so you won't be creating tables or schemas, neither performing any sort of querying that doesn't take place in memory.
When saving objects to the database all object's data will replace any existent one for that same object. Objects equality come from having the same id and that's also why the storage needs to know the name of the key that contains the id.
Quick Example
import Storage from 'shift.storage';
const userStorage = Storage('user');
const userA = { name: 'Peter Parker', secretIdentity: 'Spider Man' };
const userB = { name: 'Scott Lang', secretIdentity: 'Ant Man' };
// insert them to the storage
// both will have their id's auto generated as they were not informed
userStorage.put(userA);
userStorage.put(userB);
// retrieve all users
userStorage.list()
.then(users => {
console.log(users);
// logs out
// [
// { _id: '__5968ef6ef06c4254d6238ab5', name: 'Peter Parker', secretIdentity: 'Spider-Man' },
// { _id: '__5968ef77b729de73d8e94437', name: 'Scott Lang', secretIdentity: 'Ant-Man' }
// ]
return userStorage.delete('__5968ef77b729de73d8e94437');
})
.then(() => {
// retrieve them all again to check for deletion
return userStorage.list();
})
.then(users => {
// ant-man was deleted
console.log(users);
// logs out
// [
// { _id: '__5968ef6ef06c4254d6238ab5', name: 'Peter Parker', secretIdentity: 'Spider Man' }
// ]
return userStorage.get('__5968ef6ef06c4254d6238ab5');
})
.then(user => {
// peter parker may also be retrieved by it's id
// and updated by simply putting him again to the storage
user.team = 'Team Iron Man';
return userStorage.put(user);
})
.then(() => {
// retrieve them all again to check the update
return userStorage.list();
})
.then(users => {
// spider-man was updated
console.log(users);
// logs out
// [
// { _id: '__5968ef6ef06c4254d6238ab5', name: 'Peter Parker', secretIdentity: 'Spider Man', team: 'Team Iron Man' }
// ]
});
Server Synchronization
The whole point about this lib being made was about facilitating server side synchronization when working with REST API.
There are two forms of active synchronization: onPut
and onDelete
.
And one form of passive synchronization: fetch
.
The first two are method implementations that are triggered respectively whenever a put
or delete
call happens.
The third one is also a method implementation but it's triggered in a time interval, every 5 minutes by default.
Server side synchronization relies strongly on operation's timestamp for consistency and should have it's API also working with those concepts in order to things work. Check the Storage API description for more information. There's also a full sync example at the end of this doc.
Storage API
-> initialization
Arguments
- name: String
- settings: Object (optional)
Return
- storage referencing a database for the given name
Usage
import Storage from 'shift.storage';
// default settings
const userStorage = Storage('user');
// custom settings (assigning defaults)
const companyStorage = Storage('company', {
id: '_id', // key name that represents the id
fetchInterval: 300000, // ms -> 5 min
fetchReturn: { // syncWithServer().fetch return object description
updated: 'updated',
removed: 'removed'
}
});
-> get(id)
Arguments
- id: String|Number
Return
- promise => object for given id
Usage
Pass in the id and receive the object for that id.
userStorage.get(1).then(user => {
// user object {}
});
-> list(query)
Arguments
- query: Array|Function|Object (optional)
Return
- promise => array of object for given query
Usage
Call it with no arguments for retrieving all objects.
userStorage.list().then(users => {
// all users retrieved [{}, {}, {}, {}, {}]
});
Passing an Array of ids will retrieve only the objects for those ids.
userStorage.list([1, 2, 3]).then(users => {
// only users with ids 1, 2 and 3 retrieved [{}, {}, {}]
});
Passing an Object will filter the response by the values present in each key.
userStorage.list({ team: 'Team Iron Man' }).then(users => {
// only users from Iron Man's team retrieved [{}, {}, {}]
});
Pass in a function to implement a custom filter.
userStorage.list(user => user.movies >= 2).then(users => {
// only users with two or more movies retrieved [{}, {}, {}]
});
-> put(data)
Arguments
- id: Object|Array
Return
- promise => saved object or array with saved objects
Usage
Pass in a single object and receive it after creation/update. If the object already exists in the database it will be entirely replaced.
* Triggers onPut
if implemented.
// auto generated id
let user = { name: 'Peter Parker', secretIdentity: 'Spider Man' };
userStorage.put(user).then(savedUser => {
// user object with a generated id {}
console.log(savedUser._id); // => __5968ef6ef06c4254d6238ab5
});
// pre generated id
let user = { _id: 1, name: 'Peter Parker', secretIdentity: 'Spider Man' };
userStorage.put(user).then(savedUser => {
// user object {}
console.log(savedUser._id); // => 1
});
To update an object just put
it again.
* Triggers onPut
if implemented.
userStorage.get(1).then(user => {
// retrieves peter parker and update his team
user.team = 'Team Iron Man';
return userStorage.put(user);
}).then(savedUser => {
// updated peter parker
console.log(savedUser); // => { _id: 1, name: 'Peter Parker', secretIdentity: 'Spider Man', team: 'Team Iron Man' }
});
It's also possible to put a list of objects at once. However in this case the onPut
is not triggered for any of them.
* Doesn't trigger onPut
even if implemented.
const userA = { name: 'Peter Parker', secretIdentity: 'Spider Man' };
const userB = { name: 'Scott Lang', secretIdentity: 'Ant Man' };
userStorage.put([userA, userB]).then(users => {
console.log(users);
// logs out
// [
// { _id: '__5968ef6ef06c4254d6238ab5', name: 'Peter Parker', secretIdentity: 'Spider-Man' },
// { _id: '__5968ef77b729de73d8e94437', name: 'Scott Lang', secretIdentity: 'Ant-Man' }
// ]
});
Sync with server
If onPut
synchronization method is implemented it will be triggered every time a put
is called. The onPut
implemented function receives the stored user object as parameter and must return a promise that resolves with user's latest data on server side.
Even if an ID was auto generated for putting the object without an id in the storage, the stored object passed in as parameter to the onPut
implemented function won't contain an id if it didn't by the time it was put
in the storage. Meaning "create" functions won't have an id when onPut
is called and "update" functions will.
Check out the syncWithServer().onPut(putFunction) for it's docs.
-> delete(idOrIds)
Arguments
- idOrIds: String|Number|Array
Return
- promise => resolves if object(s) were successful deleted
Usage
Remove the object from database by id.
* Triggers onDelete
if implemented.
userStorage.delete(user._id).then(() => {
// user was deleted
}).catch(() => {
// operation failed
});
Remove multiple objects from database by their id.
* Doesn't trigger onDelete
even if implemented.
userStorage.delete([userA._id, userB._id]).then(() => {
// users were deleted
}).catch(() => {
// operation failed
});
Sync with server
If onDelete
synchronization method is implemented it will be triggered every time a delete
is called. The onDelete
implemented function receives the deleted id as parameter and must return a promise that simply resolves by the time it was confirmedly deleted.
Check out the syncWithServer().onDelete(deleteFunction) for it's docs.
-> count()
Return
- promise => resolves with the number of current stored objects
Usage
Assuming there are 5 stored entities, a call to count
should return the number 5.
userStorage.count().then(size => {
// size === 5
});
-> clear()
Return
- promise => resolves when database has been completed cleared
Usage
Calling the clear
method will empty the database and any synchronization tasks that are still pending as well as stoping the fetch
interval from being called.
userStorage.clear().then(size => {
// userStorage is now empty
});
-> isSyncing(id)
Arguments
- id: String|Number
Return
- boolean
Usage
Check if an object has any pending synchronization task with the server. May be used to show that the object is not fully synced yet.
userStorage.isSyncing(user._id); // => returns either true or false
-> isFetching()
Return
- boolean
Usage
Check if the storage is processing a fetch
call to the server.
userStorage.isFetching(); // => returns either true or false
-> isFetchingForTheFirstTime()
Return
- boolean
Usage
Check if the storage is processing a fetch
call to the server for the first time. Even between app/browser closes and reopens storage data is persistent so this will return true
only on the first time ever. When the app/browser is reopened if fetch
was already called before it will still return false
even if it's the first call for that session. Note that the clear
method does reset this fetching control for the first time.
userStorage.isFetching(); // => returns either true or false