@hoodie/store-server-api

API to manage databases, access and replications, backed by PouchDB

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
@hoodie/store-server-api
2.0.07 years ago7 years agoMinified + gzip package size for @hoodie/store-server-api in KB

Readme

hoodie-store-server-api
API to manage databases, access and replications, backed by PouchDB

Build Status Coverage Status Dependency Status devDependency Status
@hoodie/store-server-api is a JavaScript API to manage databases, access as well as persisted continuous replications. All data is persisted using PouchDB and hence compatible with CouchDB as well as other PouchDB adapters.

Example

var PouchDB = require('pouchdb')
var Store = require('@hoodie/store-server-api')(PouchDB)

Store.open('mydb')

.then(function (store) {
  store.findAll().then(function (docs) {})
})

API

Factory

var StoreFactory = require('@hoodie/store-server-api')
var PouchDB = require('pouchdb')
var Store = StoreFactory(PouchDB)

<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>PouchDB</code></th>
<td>PouchDB Constructor</td>
<td>
  PouchDB constructor as returned by <code>require('pouchdb')</code>.
  Note that you can set defaults to a PouchDB constructor using <a href="https://pouchdb.com/api.html#defaults">PouchDB.defaults(options)</a>
</td>
<td>Yes</td>

Returns Store.* API. See below

Store.open()

Store.open(name)

<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>name</code></th>
<td>String</td>
<td>
  Name of database. Based on PouchDB’s configuration, databases will be
  persisted in CouchDB, using LevelDB, in memory or using a custom adapter.
</td>
<td>Yes</td>

Resolves with Store instance.
Rejects with
<th align="left"><code>Missing</code> Error</th>
<td><code>404</code></td>
<td>Store does not exist</td>

Store.open('mydb')
  .then(function (store) {
    // use Hoodie’s store API
  })
  .catch(function (error) {
    if (error.status === 404) {
      console.log('Store "mydb" does not exist')
    } else {
      console.log('Something unexpected happened:', error)
    }
  })

Store.exists()

Store.exists(name)

<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>name</code></th>
<td>String</td>
<td>
  Name of database.
</td>
<td>Yes</td>

Resolves with true / false depending on whether database exists or not.
Example
Store.exists('foo')
  .then(function (doesExist) {
    if (doesExist) {
      console.log('Database "foo" exists');
    } else {
      console.log('Database "foo" does not exists');
    }
  })
  .catch(function (error) {
    console.log('Something unexpected happened:', error)
  })

Store.create()

Creates a database
Store.create(name, options)

<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>name</code></th>
<td>String</td>
<td>
  Name of database.
</td>
<td>Yes</td>
<th align="left"><code>options.access</code></th>
<td>String, Array</td>
<td>
  Can be <code>'read'</code>, <code>'write'</code> or <code>['read', 'write']</code>.
</td>
<td>No</td>
<th align="left"><code>options.role</code></th>
<td>String, Array</td>
<td>
  Give access to one or multiple roles. Can only be passed if <options>options.access</options> is set.
</td>
<td>No</td>

Resolves with name.
Rejects with:
<th align="left"><code>Conflict</code> Error</th>
<td><code>409</code></td>
<td>Store already exist</td>

Example
Store.create('mydb')
  .then(function (dbName) {
    console.log('Database %s created', dbName)
  })
  .catch(function (error) {
    if (error.status === 409) {
      console.log('Database %s already exists', dbName)
    } else {
      console.log('Something unexpected happened:', error)
    }
  })

Store.destroy()

Deletes a database
Store.destroy(name)

<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>name</code></th>
<td>String</td>
<td>
  Name of database.
</td>
<td>Yes</td>

Resolves with name.
Rejects with:
<th align="left"><code>Missing</code> Error</th>
<td><code>404</code></td>
<td>Store does not exist</td>

Example
Store.destroy('mydb')
  .then(function (dbName) {
    console.log('Store "mydb" has been destroyed')
  })
  .catch(function (error) {
    if (error.status === 404) {
      console.log('Store "mydb" does not exist')
    } else {
      console.log('Something unexpected happened:', error)
    }
  })

Store.grant()

Store.grant(dbName, options)

Every store is private by default. Read and write access must be granted explicitly. A store can be set to public read / write, or read / write access can be granted to specific roles only.
Behaviors in detail:
  • Once access was granted to a role it can no longer be public read / write.
  • Once access was granted to 'public', all other roles will be removed
  • Granting access to an existing role has no effect

<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>name</code></th>
<td>String</td>
<td>
  Name of database.
</td>
<td>Yes</td>
<th align="left"><code>options.access</code></th>
<td>String, Array</td>
<td>
  Can be <code>'read'</code>, <code>'write'</code> or <code>['read', 'write']</code>.
</td>
<td>Yes</td>
<th align="left"><code>options.role</code></th>
<td>String, Array</td>
<td>
  Give access to one or multiple roles.
</td>
<td>No</td>

Resolves without arguments. Rejects with
<th align="left"><code>Missing</code> Error</th>
<td><code>404</code></td>
<td>Store does not exist</td>

Example
Store.grant('foo', {
  access: 'read'
})
  .then(function () {
    console.log('"foo" can now be read by everyone');
  })
  .catch(function (error) {
    console.log('Something unexpected happened:', error)
  })

Store.revoke()

Store.revoke(dbName, options)

Revoke read / write access entirely or from one / multiple roles.
<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>name</code></th>
<td>String</td>
<td>
  Name of database.
</td>
<td>Yes</td>
<th align="left"><code>options.access</code></th>
<td>String, Array</td>
<td>
  Can be <code>'read'</code>, <code>'write'</code> or <code>['read', 'write']</code>.
</td>
<td>Yes</td>
<th align="left"><code>options.role</code></th>
<td>String, Array</td>
<td>
  Revoke access from one or multiple roles.
</td>
<td>No</td>

Resolves without arguments. Rejects with
<th align="left"><code>Missing</code> Error</th>
<td><code>404</code></td>
<td>Store does not exist</td>

Example
Store.revoke('foo', {
  access: 'read'
})
  .then(function () {
    console.log('"foo" can no longer be read by anyone');
  })
  .catch(function (error) {
    console.log('Something unexpected happened:', error)
  })

Store.hasAccess()

Store.hasAccess(dbName, options)

Checks if the given role has access to given database.
<tr>
  <th>Argument</th>
  <th>Type</th>
  <th>Description</th>
  <th>Required</th>
</tr>
<th align="left"><code>name</code></th>
<td>String</td>
<td>
  Name of database.
</td>
<td>Yes</td>
<th align="left"><code>options.access</code></th>
<td>String, Array</td>
<td>
  Can be <code>'read'</code>, <code>'write'</code> or <code>['read', 'write']</code>.
  If array passed, checks for access for both.
</td>
<td>Yes</td>
<th align="left"><code>options.role</code></th>
<td>String, Array</td>
<td>
  If Array passed, checks if <em>any</em> of the roles has access
</td>
<td>No</td>

Resolves with true / false depending on whether database exists or not.
Example
Store.hasAccess('foo', {
  access: 'read'
})
  .then(function (hasAccess) {
    if (hasAccess) {
      console.log('"foo" can be read by everyone');
    } else {
      console.log('"foo" cannot be read by everyone');
    }
  })
  .catch(function (error) {
    console.log('Something unexpected happened:', error)
  })

Store.replicate

Important: Make sure you have the pouchdb-replication package installed
Store.replicate(source, target, options)

Options are the same as PouchDB.replication. The only difference is that replications with {live: true} will be persisted and resumed. Because of that, Store.replicate does not return a replication instance, but a Promise which resolves with a replication instance.

Store.cancelReplication

Store.cancelReplication(source, target)

Cancels a live replication removes it from the store so it will no longer be resumed. Returns a promise.

Instance

A Store instance has the Hoodie Store API (as returned by db.hoodieApi()). To get a Store instance call Store.open
// all methods return promises
store.add(object)
store.add([object1, id2])
store.find(id)
store.find(object) // with id property
store.findOrAdd(id, object)
store.findOrAdd(object)
store.findOrAdd([object1, id2])
store.findAll()
store.findAll(filterFunction)
store.update(id, changedProperties)
store.update(id, updateFunction)
store.update(object)
store.update([object1, id2])
store.updateOrAdd(id, object)
store.updateOrAdd(object)
store.updateOrAdd([object1, id2])
store.updateAll(changedProperties)
store.updateAll(updateFunction)
store.remove(id)
store.remove(object)
store.remove([object1, id2])
store.removeAll()
store.removeAll(filterFunction)
store.clear()

// events
store.on('add', function(object, options) {})
store.on('update', function(object, options) {})
store.on('remove', function(object, options) {})
store.on('change', function(eventName, object, options) {})
store.on('clear', function() {})
store.one(eventName, eventHandlerFunction)
store.off(eventName, eventHandlerFunction)

// implicit id prefixes for methods and events
store.withIdPrefix(prefix)

// original PouchDB (http://pouchdb.com/api.html) instance used for the store
store.db

Events

<tr>
  <th align="left">
    Event
  </th>
  <th align="left">
    Description
  </th>
  <th align="left">
    Arguments
  </th>
</tr>
<th align="left"><code>create</code></th>
<td>New store created successfully</td>
<td><code>name</code></td>
<th align="left"><code>destroy</code></th>
<td>Existing store destroyed</td>
<td><code>name</code></td>

How things work

PouchDB only implements APIs on a database level without any kind of access control. In order to keep track of all databases, access settings and replications, we create a meta database hoodie-store. Every database that gets created using Store.create() will also create a document in hoodie-store with _id being db_<db name>. All continuous replications are stored with asreplication_<source db name>_<target db name>.
The database documents have an access property which we use for access control on database level.
The replication documents are stored with source, target and options properties. On server start continuous replications are started for all replication documents.

Note on CouchDB

CouchDB comes with its own REST API, so if it’s accessible at a public URL people can directly access it. For that reason we set /_security on each database created by Hoodie so that it’s only readable by CouchDB admins.

License

Apache 2.0