Live data events for KeystoneJS
Attach events to Lists and create simple restful routes.
npm install keystone-live
For Keystone v0.4.x apiRoutes
must be added in routes as the onMount
event is fired too late.
For keystone-live 0.2.0 you must include a keystone instance with init
.
var keystone = ;var Live = ; // Add keystone.init()// Add keystone models // ... Live; keystone; keystonestart { Live ; };
For Keystone 0.3.x and below you can add apiRoutes
in the onMount
event.
var Live = ; // optionally add the keystone instance// Live.init(keystone) keystonestart { Live; } { Live ; };
API
Demo
A complete demo with live testbed is included in the git repo (not npm).
View the README for installation.
Method Reference
The following is a list of methods available.
.init ( keystone )
@param keystone {Instance} - Keystone instance
@return this
In order to use keystone-live 2.0+ you must include a keystone instance with .init(keystone)
. Keystone-live <2.0 .init
is optional.
.apiRoutes ( [ list ], [ options ] )
@param list {String} - Optional Keystone List key
@param options {Object} - Optional Options
@return this
Set list
= false
to attach routes to all lists. Call multiple times to attach to chosen Lists.
For Keystone v0.4.x apiRoutes
must be added in routes as the onMount
event is fired too late. For Keystone 0.3.x and below you can add apiRoutes
in the onMount
event.
keystone;
options
is an object that may contain:
skip - {String} - Comma seperated string of default Routes to skip.
exclude - {String} - Comma seperated string of Fields to exclude from requests (takes precedence over include)
include - {String} - Comma seperated string of Fields to include in requests
auth - {...Boolean|Function} - Global auth.true
sets check ofreq.user
middleware - {...Array|Function} - Global middleware routes
route - {String} - Root path without pre and trailing slash eg: api paths - {Object} rename the default action uri pathscreate - {String}
get - {String}
list - {String}
remove - {String}
update - {String}
updateField - {String}
routes - {Object} override the default routes
create - {...Object|Function}
get - {...Object|Function}
list - {...Object|Function}
remove - {...Object|Function}
update - {...Object|Function}
updateField - {...Object|Function}
additionalCustomRoute - {...Object|Function} - add your own routesEach route can be a single route function or an object that contains:
route - {Function} - your route function
auth - {...Boolean|Function} - auth for the route. usetrue
for the built inreq.user
check.
middleware - {...Array|Function} - middleware for the route.
excludeFields - {String} - comma seperated string of fields to exclude.'_id, __v'
(takes precedence over include)
includeFields - {String} - comma seperated string of fields to exclude.'name, address, city'
NOTE: include
and exclude
can be set for each list individually, before applying to all other lists with Live.apiRoute(null, options)
. exclude
takes precedent over include
and only one is used per request. You can override the global setting per request.
var opts = route: 'api/v2' exclude: '_id, __v' auth: false paths: remove: 'delete' skip: 'create, remove' routes: get: auth: false middleware: { return { console; listmodel; } } create: { if !requser return res; else ; } { return { console; listmodel; } } // add rest routes to all lists Live; // add rest routes to Post // Live.apiRoutes('Post', opts);
Created Routes
Each registered list gets a set of routes created and attached to the schema. You can control the uri of the routes with the options
object as explained above.
action | route |
---|---|
list | /api/posts/list |
create | /api/posts/create |
get | /api/posts/:id |
update | /api/posts/:id/update |
updateField | /api/posts/:id/updateField |
remove | /api/posts/:id/remove |
yourRoute | /api/posts/:id/yourRoute |
yourRoute | /api/posts/yourRoute |
Modifiers: each request can have relevant modifiers added to filter the results.
include: 'name, slug' - fields to include in result
exclude: '__v' - fields to exclude from result
populate: 'createdBy updatedBy' - fields to populate
populate: 0 - do not populate - createdBy and updatedBy are defaults
limit: 10 - limit results
skip: 10 - skip results
sort: {} - sort results
route requests look like
/api/posts/55dbe981a0699a5f76354707/?list=Post&path=posts&emit=get&id=55dbe981a0699a5f76354707&exclude=__v&populate=0
.apiSockets ( [ options ], callback )
alias
.live
@param options {Object} - Options for creating events
@return callback {Function}
Create the socket server and attach to events
Returns this
if no callback
provided.
options
is an object that may contain:
exclude - {String} - Comma seperated Fields to exclude from requests (takes precedence over include)
include - {String} - Fields to include in requests
auth - {...Boolean|Function} - require user
middleware - {...Array|Function} - global middleware function stackfunction(socket, data, next)
listConfig - {Object} - configuration for listsonly - {String} - comma seperated string of Lists allowed (takes first precedence)
skip - {String} - comma seperated string of Lists not to allow
lists - {Object} - individual List config
KEY - {Object} - Each Key should be a valid List with an object consisting of:
exclude - {String} - comma seperated string of routes to exclude.
'create, update, remove, updateField'
...get|find|list - {...Object|Function} - all of the routes
auth - {...Boolean|Function} - global auth funtion ortrue
for default auth for all paths
middleware - {...Array|Function} - global middleware function stack for all pathsfunction(socket, data, next)
routes - {Object} - override the default routes
create - {...Object|Function}
get - {...Object|Function} returnsObject
find - {...Object|Function} alias of list
list - {...Object|Function} returnsArray
ofObjects
remove - {...Object|Function}
update - {...Object|Function}
updateField - {...Object|Function}
...customRoutes - {...Object|Function} - create your own routes
Each route can be a Function or an object consisting of:route - {Function} - route to run
auth - {...Boolean|Function} - auth funtion ortrue
for default auth
middleware - {...Array|Function} - middleware stack - function(socket, data, next)
excludeFields - {String} - comma seperated string of fields to exclude.'_id, __v'
(takes precedence over include)
includeFields - {String} - comma seperated string of fields to exclude.'name, address, city'
var opts = include: 'name,slug,_id,createdAt' { if sockethandshakesession console var session = sockethandshakesession; if!sessionuserId console; return ; else var User = keystone; Usermodel; else console; ; } routes: // all functions except create and update follow this argument structure { console; if!_ { console; }; var list = datalist; var id = dataid; if!list return ; if!id return ; listmodel; } { // req contains a user field with the session id console; if!_ { console; }; var list = datalist; var id = dataid; if!list return ; if!id return ; listmodel; } // create and update follow the same argument structure { if!_ { console; }; var list = datalist; var id = dataid; var doc = datadoc; if!list return ; if!id return ; if!_ return ; if!_ req = {}; listmodel; } // start live events and add emitters to Post Live; // alternate new configuration Live;
Modifiers: each request can have relevant modifiers added to filter the results.
include: 'name, slug' - fields to include in result
exclude: '__v' - fields to exclude from result
populate: 'createdBy updatedBy' - fields to populate
populate: 0 - do not populate - createdBy and updatedBy are defaults
limit: 10 - limit results
skip: 10 - skip results
sort: {} - sort results
socket requests look like - see socket requests and client
var data = {
list: 'Post',
limit: 10,
skip: 10,
sort: {}
}
live.io.emit('list', data);
Listens to emitter events
/* add Live doc events */Live; /* add Live doc pre events */Live; /* add Live doc post events */Live; /* add Live list event */Live;
Socket emitters
user emitters sent to individual sockets
// list emitsocket; // document pre eventssocket;socket; // document post eventssocket;socket; // document eventsocket;socket;
global emitters
global events sent to rooms on change events only.
// room unique identifier sent by user - emit docifeventiden emitter; emitter; emitter;// the doc id - doc:_idifeventid emitter; emitter; emitter;// the doc slug - doc:slugifeventdata && eventdataslug emitter; emitter; emitter;// the list path - doc:pathifeventpath emitter; emitter; emitter;// individual field listening - ifeventfield && eventid // room event.id:event.field emit doc emitter; emitter; emitter; emitter; // room path emit field:event.id:event.field emitter; emitter;
.init ( [ keystone ] )
@param keystone {Instance} - Pass keystone in as a dependency
@return this
Useful for development if you want to pass Keystone in
.listEvents ( [ list ] )
alias
.list
@param list {String} - Keystone List Key
@return this
Leave blank to attach live events to all Lists.
Should be called after Live.apiSockets()
Learn about attached events
List Broadcast Events
Websocket Broadcast Events
keystonestart { Live ; };
.router ()
@return this
thisMockRes = ; thisMockReq = ;
Events
Overview
Live uses the event system to broadcast changes made to registered lists.
Each registered list will broadcast change events.
Socket based events have a finer grain of control and you can listen for specific change events.
List Broadcast Events
A registered list has events attached to the pre and post routines. These are global events that fire anytime a change request happens. You can also listen to the global doc:pre
and doc:post
on the user broadcast (explained below). For greater interactivity and control use the websocket broadcast events.
pre | post | *post |
---|---|---|
init:pre | init:post | |
validate:pre | validate:post | |
save:pre | save:post | save |
remove:pre | remove:post |
*Note that post save has an extra event save:post
and save
.
listschema;
Each method will trigger a local event and a broadcast event.
Each event will send a data object similiar to:
type:'save:pre' path:listpath id:doc_id data:doc success: true
The broadcast event is sent when each action occurs.
;
changeEvent will send a broadcast to any of the following rooms that are available for listening:
doc._id
list.path
doc.slugEach room emits
doc
anddoc:event.type
// the doc id - event.idifeventid emitter; emitter;// the doc slug - event.data.slugifeventdata && eventdataslug emitter; emitter;// the list path - event.pathifeventpath emitter; emitter;
The following are valid event.type
values for List global broadcasts:
init:pre
init:post
validate:pre
validate:post
save:pre
save:post
save
remove:pre
remove:post
The local event will emit doc:Pre
or doc:Post
for the appropriate events
// preLive;// postLive;
We use Live.on
in app to respond and broadcast to the current user.
/* add live doc pre events */Live; /* add live doc post events */Live; { // send update info to global log Live; /* send the users change events */ socket; socket;} { Live; /* send the users change events */ socket; socket;}
Websocket Broadcast Events
Live uses socket.io v~1.3.2 to handle live event transportation. A set of CRUD routes are available and there are several rooms you can subscribe to that emit results.
io is exposed via Live.io
. Our list namespace is Live.io.of('/lists')
.
You will connect to the /lists
namespace in the client to listen to emit events.
CRUD Listeners
There is a generic set of CRUD listeners available to control the database. You do not receive callback results with Websocket CRUD listeners. You will need to pick the best strategy to use to listen for result events from the rooms available. Each listener emits its result to Live.on
. Live.on
will catch each submission and decide who should be notified. View the changeEvent()
behaviour below.
create
socket; socket;
custom
socket; socket;
get
socket; socket;
list
socket; socket;
remove
socket; socket;
update
socket; socket;
updateField
socket;// Hellosocket;
Broadcast Results
Instead of returning a http response, each listener emits a local event that the app is waiting for. This event is processed and the correct rooms are chosen to broadcast the result.
There are two emitter namespaces
doc
emitter.to(event.path).emit('doc', event);
emitter.to(event.path).emit('doc:TYPE', event);
list
socket.emit('list', event);
list is only sent to the requesting user
The following are valid event.type
values:
created
get
save
updated
updatedField
custom
Each broadcast is sent to the global doc as well as a computed doc:event.type channel.
changeEvent will send the broadcast to the following rooms that are available for listening:
path
emitter;emitter;
id
the doc._id
value if available
emitter;emitter;
slug
document slug if available
emitter;emitter;
id:field
field broadcasts to the list.path room and a doc._id:fieldName room
// room event.id:event.field emit docemitter;emitter; // room path emit field:event.id:event.fieldemitter;emitter;
iden
Dynamic room. Send a unique iden
with each request and the app emits back to a room named after iden
emitter;emitter;
To use iden
make sure to kill your event listeners. Here is a simple response trap function:
var { var unique = keystoneutils; var { socket; ; } socket; return unique;} var { // do someting} socket;
Additional io namespaces
The socket instance is exposed at Live.io
.
The /lists
and /
namespaces are reserved. You can create any others of your own.
var sharedsession = ; /* create namespace */var myNamespace = Liveio; /* session management */myNamespace; /* add auth middleware */myNamespace; /* list events */myNamespace;
Client
Your client should match up with our server version. Make sure you are using 1.x.x and not 0.x.x versions.
var socketLists = ; socketLists; socketLists; socketLists; socketLists; socketLists;