cyclejs-community/cycle-idb

Name: cycle-idb

Owner: Cycle.js Community

Description: A cycle driver for IndexedDB.

Created: 2017-07-11 21:05:04.0

Updated: 2018-04-26 07:59:07.0

Pushed: 2018-04-26 07:59:04.0

Homepage:

Size: 204

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

cycle-idb

A cyclejs driver for IndexedDB. It uses idb to interface with IndexedDB. If you need more details about how to work with IndexedDB, the Google Developers Guide is a good place to start. If you are looking for more resources for cyclejs, check out awesome-cyclejs.

Warning: this library is in early development and it doesn't cover (yet) all the features available in IndexedDB (check Planned features). Any feedback, feature requests, bug reports or contributions are welcome, but should you decide to use cycle-idb in a production project, please be aware that you might find some issues.

Installation

cycle-idb is available through npm packages.

m i cycle-idb
Usage

Take a look at the examples folder for complete samples.

Create IDB driver

The function makeIdbDriver accepts three arguments.

rt makeIdbDriver from 'cycle-idb'

t drivers = {
IDB: makeIdbDriver('pony-db', 1, upgradeDb => {
    // Contains the current version of the database before upgrading
    upgradeDb.oldVersion
    // Creates a new store in the database
    upgradeDb.createObjectStore('ponies', { keyPath: 'name' })
})

Query data

Cycle-idb is designed around subscribing to the data and receiving updates when that data changes.

tion main(sources) {
// This returns a stream that will emit an event every time the data in the 'ponies' store changes.
const allPonies$ = sources.IDB.store('ponies').getAll()

// This returns a stream that will emit an event every time the data in the 'ponies' store
// with the primary key 'Twilight Sparkle' changes.
const twilight$ = sources.IDB.store('ponies').get('Twilight Sparkle')

// This returs a stream that will emit an event every time the count of objects in the 'ponies' store changes.
const ponyCount$ = sources.IDB.store('ponies').count()

// This returns a stream that will emit an event with all the keys in the store everytime an object is added or removed.
const allKeys$ = sources.IDB.store('ponies').getAllKeys()

Key ranges

Cycle-idb provides a fluent api to use key ranges. For more information on IndexedDB key ranges, check IDBKeyRange.

The object returned by the store selector contains four selectors that map to the factories for IDBKeyRange:

tion main(sources) {
const ponyStore = sources.IDB.store('ponies')

const twilight$ = ponyStore.only('Twilight Sparkle').get()
const firstPonies$ = ponyStore.upperBound('R').getAll()
const lastPoniesKeys$ = ponyStore.lowerBound('R').getAllKeys()
const poniesFSCount$ = ponyStore.bound('F', 'S').count()

Custom queries

Cycle-idb allows to query based on a filtering function for use cases that cannot be covered with the provided selectors or indexes. The selector query is exposed for that purpose. The selector query returns a stream that will send an event with the objects in the store that fulfill the filtering function, and will send additional events every time an object that fulfills the filtering function is added, removed or modified.

tion main(sources) {
const tPonies$ = sources.IDB.store('ponies')
    .query(pony => pony.name.indexOf('t') !== -1)

Custom queries can also be used on indexes.

tion main(sources) {
const tUnicorns$ = sources.IDB.store('ponies')
    .index('type')
    .only('unicorn')
    .query(pony => pony.name.indexOf('t') !== -1)

Update data

The cycle-idb driver receives a stream that accepts a few database operations: add, put, update, delete and clear. Factories for these operations can be imported from the cycle-idb package.

rt fromDiagram from 'xstream/extra/fromDiagram'
rt { $add, $put, $update, $delete, $clear } from 'cycle-idb'

tion main(sources) {
const updateDb$ = fromDiagram('-a-b-c-d-e-|', {
    values: {
        // Will add the entry 'Twilight Sparkle' to the store 'ponies'
        a: $put('ponies', { name: 'Twilight Sparkle', type: 'unicorn' }),
        // Will update the entry 'Twilight Sparkle', keeping the previous property 'type'
        b: $update('ponies', { name: 'Twilight Sparkle', element: 'magic' }),
        // Will remove 'Twilight Sparkle' from the store 'ponies'
        c: $delete('ponies', 'Twilight Sparkle'),
        // Will add the entry 'Rainbow Dash' to the store 'ponies'
        d: $add('ponies', { name: 'Rainbow Dash', type: 'pegasus' }),
        // Will remove everypony from the store 'ponies'
        e: $clear('ponies'),
    }
})

return {
    IDB: updateDb$,
}

Indexes

Indexes allow to sort the data in a store according to a particular property or to query only a subset of the data.

Create indexes

Indexes are created in the upgrade function passed to makeIdbDriver. The created store objects exposes the method createIndex(indexName, keyPath, options), which is used for that purpose.

You can check the documentation in IDBObjectStore.createIndex for additional details.

IdbDriver('pony-db', 1, upgradeDb => {
const ponyStore = upgradeDb.createObjectStore('ponies', { keyPath: 'name' })
ponyStore.createIndex('type', 'type')

Subscribe to indexes

The store method in the IDBDriver exposes the index selector, which can be used to select a specific index in the selected store.

t ponyTypeIndex = sources.IDB.store('ponies').index('type')

The index selector returns an object with the following methods:

You can check the IDBIndex documentation for more details.

t ponyTypeIndex = sources.IDB.store('ponies').index('type')
t poniesByType$ = ponyTypeIndex.getAll() // This returns a stream subscribed to all ponies, sorted by 'type'
t unicorns$ = ponyTypeIndex.getAll('unicorn') // This returns a stream subscribed only to the ponies of type 'unicorn'

t ponyKeys$ = ponyTypeIndex.getAllKeys() // This returns a stream subscribed to all the keys of the ponies that have the 'type' property
t unicornKeys$ = ponyTypeIndex.getAllKeys('unicorn') // This returns a stream subscribed to all the keys of the ponies where the 'type' property has the value 'unicorn'

t ponyNameIndex$ = sources.IDB.store('ponies').index('name')
t twilight$ = ponyNameIndex.get('Twilight Sparkle') // This returns a stream subscribed to the pony 'Twilight Sparkle'

t unicornCount$ = ponyTypeIndex.count('unicorn') // This returns a stream subscribed to the count of unicorns
Key ranges

All key range selectors available in a store are also available for indexes.

t ponyNameIndex$ = sources.IDB.store('ponies').index('name')

t twilight$ = ponyNameIndex$.only('Twilight Sparkle').get()
t firstPonies$ = ponyNameIndex$.upperBound('R').getAll()
t lastPoniesKeys$ = ponyNameIndex$.lowerBound('R').getAllKeys()
t poniesFSCount$ = ponyNameIndex$.bound('F', 'S').count()
Custom queries

The query selector is also supported in indexes for custom filtering over an index.

t tUnicorns$ = sources.IDB.store('ponies')
.index('type')
.only('unicorn')
.query(pony => pony.name.indexOf('t') !== -1)
Error handling

The cycle-idb driver exposes an error$ stream that broadcasts all errors that occur during any database writing operation. The error event is the error thrown by IndexedDB with the following data added:

tion main(sources) {
sources.IDB.error$
    .addListener({
        error: e => console.log(`Operation ${e.query.operation}(${e.query.data}) on store ${e.query.store} failed.`)
    })

Unfortunately, the exposed error$ doesn't broadcast errors occurred during reading operations, like the error thrown when querying a store object that doesn't exist.

To catch these errors, you need to add an error listener to the streams returned by the methods get(), getAll() and count() returned by IDB.store(...).

tion main(sources) {
sources.IDB.store('not-found')
    .getAll()
    .addListener({
        error: e => console.log(e)
    })

These listeners will also catch the errors raised by writing operations that affect the result of the query created by that method. This means that the stream returned by store('ponies').get('Twilight Sparkle') will also receive any error raised when updating the entry with the key 'Twilight Sparkle', and the streams returned by store('ponies').getAll() and store('ponies').count() will receive any error raised when updating the 'ponies' store.

Planned features

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.