npm package: @offlinely/idb
Description: offline first package
Author: Marson J. LAZA
Latest version: 3.1.15

# Welcome to Offlinely!
This package will help you to keep your app working **offline!**

# Installation

```yarn add @offlinely/idb```

For server integration, take a look at [@offlinely/server](https://www.npmjs.com/package/@offlinely/server)


# How to use  *iDB*
* Creating an instance of IDB
```ts
import  IDB  from  '@offlinely/idb';

const  gstDB = new  IDB({
  appName:  'app-ko',
  token:  '$2b$10$m2iQrFp0BYqtF.YFmyNTHekQV7uj', // server app token
  url:  'localhost:9000', // server url
  models: {
    User: {
      name: String,
      keywords: [String],
      phone: {$type: String, $required: false},
      role: ['ADMIN', 'SELLER', 'CUSTOMER', 'OTHER'] as const // enum like variable type
    },
    Car: {
      owner: {$ref: 'User'},
      oldOwners: {
        $type: [{
          user: {$ref: 'User'},
          lastUse: Date
        }],
        $required: false
      },
      weirdMix: [
        name: String,
        structure: {
          keys: [{value: String, active: Boolean}],
          user: [{
            user: {$ref: 'User', $required: false},
            age: Number,
            gender: ['M', 'F', 'O'] as const
          }]
        }
      ]
    }
  }
})
```

### About "models" 
The "models" property will generate types and will design the structure of the DB.
* No need to add `id`, it will be added later
* Don't use these keywords as property in your models, unless you're using them with the intended utilities: `$ref`, `$required` and `$type`.


# Documentation
### IDB: `new IDB(IDBOptions)`
```ts
new IDB({
  appName:  'app-nakah',
  token:  'ekQ2iHY1qtF.YFm$2b$yQrFp0BTNV0$m7uj',
  url:  'localhost:9000',
  models: {...}
})
```


`IDBOptions.appName`: Name of the App this will be used for th locale and remote database name

`IDBOptions.token`: Token generated on the remote backend, to access this backend (see [@offlinely/server](https://www.npmjs.com/package/@offlinely/server))

`IDBOptions.url`: URL of the backend, dont add `https`, `http` or anything else, the url is just about the **hostname**

* **Constructor** : IDBOptions

| **Name** | **Type** | **Required** | **Example** | **Description** |
|---:|---|--|---|---|
| url | string | Yes | localhost:9000 | Backend socket backend URL |
| appName | string | Yes | GStock | Application Name |
| delay | number | No | 3000 | Milliseconds before sending data to Server |
| token | string | No | - | Token sent to authenticate to a server,<br>(Not needed for Front only app - see getTokens in [@offlinely/server](https://www.npmjs.com/package/@offlinely/server)) |
| type | DB | No | see [Other DBs](/#other-db) | Type of the offline Database used by IDB |
| models | StoreModel | Yes | - | Structure of the model |

### React Hooks 
  These hooks are very usefull if you use React
  ```ts
  import createHooks from '@offlinely/idb/react'

  const db = new IDB({
    // ...
    models: {
      User: { ... }
    }
  })
  const hooks  = createHooks(gstDB)

  export const useStore = hooks.useStore
  export const useGet = hooks.useGet
  export const useWhere = hooks.useWhere
  export const useList = hooks.useList
  ```

#### How to use React hooks
##### `useGet(id)`
| **Name** | **Type** | **Required** | **default** | **Description** |
|---|---|--|---|---|
| id | string | Yes | - | Id of the item you want |
  ```ts
  import {userGet} from 'path/to/db'

  const Component = () => {
    const [jimId, setJimId] = useState("User", "iamastringid")
    const user = useGet(id) // will return the user with the id
    // user will change everytime jimId will change
    // and when the user with the given id will be updated
  }
  ```

  

#####   `useList(modelName, pageCount, order, 3)`
  | **Name** | **Type** | **Required** | **default** | **Description** |
|---|---|--|---|---|
| modelName | string | Yes | - | Key in the models |
| pageCount | number | No | 0 (all) | Application Name |
| order | 'ASC' or 'DESC' | No | 'DESC' | order of the list by updatedAt |
| page | number | No | 1 | page of the list |
  ```ts
  import {useList} from 'path/to/db'

  const Component = () => {
    const users = useList('User') // will return a list of users
    // the list will be updated anytime an user will be updated
    const users = useList('User', 40, 'ASC', 3)
  }
  ```

### Typing
  Typing will return the types used by your IDB if you need them.
  ```ts
  import IDB, { Typing } from '@offlinely/idb'
  ...
  const db = new IDB({
    Model01: { ... },
    User: { ... }
  })

  ...
  type MyDB = typeof db
  export type Model<K extends Typing.Keys<MyDB>> = Typing.Model<MyDB, K>
  export type PModel<K extends Typing.Keys<MyDB>> = Typing.Parameter<MyDB, K>
  
  // When you want to use the types
  const myThing: Model<"User"> = {...}
  ...
  const myOtherThing: PModel<"Model01"> = {...}
  ```

### Store

  * `get(id: string): Promise<Modeled<T>>` get an entry by id.
      ```ts
      User.get('2ab4a07e-88fb-4233-811e-17bac5935b62')
      ```

  * `getAll(): Promise<Modeled<T>[]>` get all entries of the Store

      Example: 
    ```ts
    User.getAll()
    ```

  * `save(entry: T): Promise<Modeled<T>>` Create or Update an entry, 

      Example: 
    ```ts
    const jacques = {
      username: 'jach60'
    } 
    User.save(jacques)
    ```

  * `remove(entryOrId: T | string): Promise<Modeled<T>>` Remove an entry, 

      Example: 
    ```ts
    User.remove(jacques)
    // or
    User.remove(jacques.id)
    ```

### Item methods

  * `remove(): Modeled<T>` Same as `Store.remove`
    ```ts
    harry.remove()
    // will remove this item from 
    ```

  * `update(SimplifiedItem<T>): Item<T>` Same as `Store.save` - create or update an entry in the store
    ```ts
    harry.update({
      username: "harrypott",
      age: 23
    })
    ``` 

##  StoreModel

###  Primitive types
  ```ts
  new IDB({
    ...,
    models: {
      Foo: {
        name: String,
        age: Number,
        active: Boolean,
        genre: ['M', 'F', 'Other'] as const, // String but one of these values
        birth: Date
      }
    }
  })
  ```
###  References & Optionnal References
  ```ts
  ...
  Foo: { ... },
  Bar: {
    foo: {$ref: 'Foo'},
    optionalFoo: {$ref: 'Foo', $required: false},
  }
  ```
  * If `$required` isn't given, its default value is *`true`*

### Using $type to make other properties optional
  ```ts
  ...
  Foo: {
    age: {$type: Number, $required: false},
    active: {$type: Boolean, $required: false},
    genre: {$type: ['M', 'F', 'Other'] as const}
  }
  ```

### Arrays
  ```ts
  ...
  Foo: {
    nicknames: {$type: [String], $required: false},
    chosenNumbers: [Boolean],
    likedGenres: [['M', 'F', 'Other'] as const]
  }
  ```

### Nested Object
  ```ts
  ...
  Foo: {
    like: {
      bar: {$ref: 'Bar'},
      reaction: ['like', 'love', 'sad', 'grrrr'] as const,
      date: Date,

    },
    comments: [{
      content: String,
      images: [String]
      links: [{$ref: 'Link'}],
    }],
    likedGenres: [['M', 'F', 'Other'] as const]
  }
  ```


## <div id='other-db'>Other DBs</div>
For mobile use, you don't have indexedDB, for example with **react-native**

### Realm
I added Realm as an alternative, just install these dependancies to use it

`yarn add realm react-native-get-random-values`

Then write your code with specifying a `DB type` like this

```ts
import Realm from '@offlinely/idb/realm'

...

new IDB({
  appName:  'app-nakah',
  token:  'ekQ2iHY1qtF.YFm$2b$yQrFp0BTNV0$m7uj',
  url:  'localhost:9000',
  type: Realm, // Here I say the DB type is realm
  models: {...}
})
```