pronto-data-service
v0.0.8-somata-peer
Published
A base Data Service with multiple DB backends and a GraphQL layer
Downloads
28
Readme
data-service
A base Data Service with multiple DB backends and a GraphQL layer
Installation
$ npm install git+ssh://[email protected]/prontotype-us/data-service.git
Schema
Create a file schema.sch
TODO: Document schema syntax
Usage
A DataService is used like a regular Somata Service, with a slightly different set of arguments:
new DataService(service_name, db_options, custom_methods)
DataService = require 'data-service'
# Create a MongoDB data service with only generic methods
new DataService 'myproject:data', {
type: 'mongo'
config: db: 'myprojectdb'
}
# Create a LocalDB with a specific method
new DataService 'myproject:data', {}, {doSpecificThing}
Options
type
: Supported types aremongo
for MongoDB, andpg
for Postgres. Leave blank for an in-memory DB.config
:id_key
: Default is alwaysid
(for MongoDB, IDs are coerced from_id
).- Specific DB types might use extra config options:
db
: Required for MongoDB and Postgres.host
: Optional for MongoDB and Postgres.user
: Optional for MongoDB and Postgres.
strict_auth
: Optionally set authorization to default toUnauthorized
without access rules
Database Types
- MongoDb
- PostgresDb
- LocalDb: In memory database for quick testing
- ServiceDb: Forwards methods to a service that follows the data service protocol
- MultiDb: Combines multiple Dbs into one, each Db handling its own set of collections
Methods
The base database exposes the following functions, all requiring a type
argument (the collection name, e.g. "users"):
get(type, query)
→Item
find(type, query, search, options)
→{items: [Item], total: Int, pages: Int}
create(type, new_item, options)
→Item
update(type, id, item_update)
→Item
remove(type, id)
→Bool
GraphQL methods
query(query, context...)
The base query
method takes a GraphQL query that may represent a get, find, create, or update, and one or more objects of arguments to pass to the query.
Get
Get a single item with either id
or query
argument:
query($id: ID) {
user(id: $id) {id, name, email}
}
query($email: String) {
user(query: {email: $email}) {id, name, email}
}
Find
Find multiple items, paginated, with a query
. Note that the items are wrapped in another object with the shape {items, total, pages}
. You can pass an options
with {page}
for pagination:
query($city: String, $page: Int) {
users(query: {city: $city}, options: {page: $page}) {items {id, name, email}, total}
}
Create
Mutation query with argument create
, which returns the created item:
mutation($name: String, $email: String) {
create_user(create: {name: $name, email: $email}) {id, name, email}
}
Update
Mutation query with arguments id
and update
, which returns the updated item:
mutation($id: ID, $name: String) {
update_user(id: $id, update: {name: $name}) {id, name}
}
Subscriptions
By passing a GraphQL query as second subscription argument you can specify the shape of the event to be returned. The query needs an id
argument which will use the created or updated item's id
.
task_query = '''
query($id: ID){
task(id: $id){id, name}
}
'''
client.subscribe 'sconce:data', 'tasks:5:updated', task_query, ({task}) ->
console.log '[specific task updated, graphql]', task
Without a query, you'll get the object as it is returned from the Db class.
client.subscribe 'sconce:data', 'tasks:created', (task) ->
console.log '[any task created, raw]', task
Triggers
Attach extra actions before and after creates and updates using triggers, passed as a config argument in the shape {collection: {postCreate: fn, ...}, ...}
. Available actions are preCreate
, postCreate
, and postUpdate
.
triggers = {
tasks: {
postCreate: (created_task) ->
console.log 'Look at this task', created_task
}
}
Authorization
Optionally configure authorization for each type with three dictionaries. By default users will be authorized to perform all actions.
canUpdate = {
'[type]': (user_id, item_id, cb) ->
authorized = true # Bool
cb err, authorized
}
canCreate = {
'[type]': (user_id, new_item, cb) ->
cb err, true
}
canRead = {
'[type]': (user_id, item, cb) ->
cb err, true
}
You can set the authorization to default to Unauthorized unless a successful access rule is specified with config.strict_auth = true
.
The service will then offer an authorized version of each generic method, with a user_id as an additional first argument.
createAs = (user_id, args...) ->
# optional type-wise canCreate
@create args...
getAs = (user_id, args...) ->
# optional type-wise canRead
@get args...
findAs = (user_id, args...) ->
# optional type-wise canRead
@find args...
updateAs = (user_id, args...) ->
# optional type-wise canUpdate
@update args...
queryAs = (user_id, query, context) ->
# substitute getAs, findAs for get, find...
query query, context
# Create a LocalDB with some triggers and access rules
new DataService 'myproject:data', {
config: {
triggers
canRead:
tasks: (user_id, item, cb) ->
get 'users', {id: user_id}, (err, user) ->
cb err, user.god == true ||
user_id in item.assigned_user_ids
canUpdate:
tasks: (user_id, item_id, cb) ->
get 'users', {id: user_id}, (err, user) ->
cb err, user.god
}
}
Db Subclasses
Implement these methods:
Db = require './db'
class CustomDb extends Db
_get: (type, query, cb) ->
_find: (type, query, search, options, cb) ->
_findWithArray: (type, queries, cb) ->
_create: (type, new_item, cb) ->
_update: (type, id, item_update, cb) ->
_remove: (type, id, cb) ->