A transactional database API with JSON semantics

This module uses indexedDB with a generic schema that models data in a database as a single arbitrarily deep JSON structure. Records are addressable via paths within the data structure, and can be retrieved in bulk or with cursor functions. Operations are chainable using a Promise-like interface.



Database is backed by indexedDB. An upgrade transaction runs on open if the database version is less than the requested version or does not exist. If upgrade is a json value, the data stores in the first transaction operation on this Database will be populated with this value on an upgrade event. Otherwise, an upgrade will be handled by the given function via UpgradeTransaction.


get, count, put, insert, append, and delete are convenience methods that operate through transaction for a single objectStore and return the corresponding ScopedTransaction. get and count initiate a read-only transaction by default. transaction returns a ScopedTransaction if a single (string) objectStore is specified, and a Transaction if operating on multiple objectStores.


A Transaction acting on multiple data stores must specify a data store as the first argument to every operation. Otherwise, these methods correspond to ScopedTransaction methods.


All methods are chainable and execute on the same transaction in parallel. then assigns a callback for the preceding sequence of operations, or throws an error if no operations are pending. If the transaction is not writable, put, insert, append, and delete throw an error.

By default, cursor buffers all data at the requested path as the result of a get operation. count returns a count of the number of elements in an object or array at path (with optional bounds). insert will splice the given value into the parent array at the specified position, shifting any subsequent elements forward.

When all its pending operations complete, callback is called with the result of each queued operation in order. More operations can be queued onto the same transaction at that time via this.

Results from put, insert, append, and delete are error strings or undefined if successful. get results are json data or undefined if no value exists at the requested path.


Path is either a /-delimited string of encodeURIComponent-encoded keys or an array of unencoded keys and numeric indices to an element within the json data structure; e.g. 'users/123/firstName' or ['users', 123, 'firstName'].


Cursor controls how data is traversed and buffered in a get request. String values 'shallow', 'immediates', and 'deep' control the depth of the returned data object. As a function, Cursor is called for each array or object encountered in the requested json structure. It is called with a path array (of strings and/or numeric indices) relative to the requested path (i.e. [] represents the path as requested in get) and an array boolean that is true if the substructure is an array. It returns an Action callback or object with a range and action, or false to prevent recursion into the structure. lowerBound and upperBound restrict the keys/indices traversed for this object/array, and the Action function is called with each key in the requested range, in order. The Action callback can optionally return either 'skip' or 'stop' to exclude the element at the given key from the structure or to exclude and stop iterating, respectively. If specified, the value function receives the value retrieved from each key and returns a value to insert into the parent object or array, or undefined to skip insertion. value is then also called with null key and the object/array value itself. If Cursor is a LevelCursor object, it applies to the top-level object or array.

For example, the following call uses a cursor to fetch only the immediate members of the object at the requested path (equivalent to 'immediates'). Object and array values will be empty:

db.get('path/to/object', false, function(path) {
  return !path.length;

The following call will get immediate members of the requested object sorted lexicographically (by code unit value) up to and including key value 'c', but excluding key 'abc' (if any):

db.get('path/to/object', false, function(path) {
  return path.length ? false : {
    upperBound: 'c',
    action: function(key) {
      if (key == 'abc') return 'skip';