npm package: @shiftcode/shift.storage
Description: A object oriented way to store data locally, optionally syncing any changed data with your server in background.
Author: Shift Code
Latest version: 0.1.3
Deprecated: this package has been deprecated

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](https://github.com/localForage/localForage) lib and some of it's extensions, as well as it includes the [localForage's cordova SQLite driver](localForage-cordovaSQLiteDriver) and makes it the primary option of use, when [cordova sqlite plugin](https://github.com/litehelpers/Cordova-sqlite-storage) 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

```js
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

```js
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.

```js
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.

```js
userStorage.list().then(users => {
    // all users retrieved [{}, {}, {}, {}, {}]
});
```

Passing an Array of ids will retrieve only the objects for those ids.

```js
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.

```js
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.

```js
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.

```js
// 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.

```js
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.

```js
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)](#syncWithServer().onPut) 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.

```js
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.

```js
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)](#syncWithServer().onDelete) 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.

```js
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.

```js
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.

```js
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.

```js
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.

```js
userStorage.isFetching(); // => returns either true or false
```

## -> syncWithServer().fetch(fetchFunction)

## -> syncWithServer().onPut(putFunction)

## -> syncWithServer().onDelete(deleteFunction)

## -> reloadFetch()

## -> cancelFetch()