falcor-saddle v0.1.2
falcor-saddle
Experimental model route generation for Netflix Falcor – giddyup!
Overview
Netflix Falcor is a library for efficiently querying data across a network. It presents some useful solutions for querying data from multiple datasources, caching, and batching queries for greater performance. Yet, getting Falcor wired up to a back-end model can take a lot of repetitive code.
falcor-saddle
is a module that will generate CRUD-like routes for you if
you give it a set of methods for accessing your model.
Instead of writing this:
import Todo from 'model-todo';
const routes = {
{
route: "todos.length",
get: (pathSet) => {
/* repetitive implementation */
}
},
{
route: "todos[ranges]",
get: (pathSet) => {
/* more repetitive implementation */
}
},
{
route: "todosById[keys]",
get: (pathSet) => {
/* yet more repetitive implementation */
}
},
/* plus routes to set, create, and delete todos... */
}
You can write this instead:
import Todo from 'model-todo';
import { createRoutes } from 'falcor-saddle';
const routes = createRoutes({
routeBasename: 'todos',
acceptedKeys: ['id', 'createdAt', 'content'],
getLength: async () => Todo.count().execute(),
getRange: async (from, to) =>
Todo.orderBy('key').slice(from, to + 1).run(),
getById: async (id) => Todo.get(id).run(),
update: async (oldObj, newObj) => oldObj.merge(newObj).save(),
create: async (params) => (new Todo(params)).save(),
delete: async (id) => Todo.get(id).delete()
}),
...and falcor-saddle
will create the following routes:
todos.length
: get()todos[ranges]
: get()todosById[keys]
: get(), set()todos.create
: call()todosById[keys].delete
: call()
Usage
The usual means of using falcor-saddle
is through its createRoutes()
method and pass these routes to falcor-router
in a manner similar to this:
import FalcorRouter from 'falcor-router';
const routes = createRoutes({ /* options */ });
const MyRouter = FalcorRouter.createClass(routes);
...and then wire that router up to Express:
/* ... other express imports */
import bodyParser from 'body-parser';
import falcorExpress from 'falcor-express';
const app = express();
app.use('/model.json', bodyParser.urlencoded({extended: false}),
falcorExpress.dataSourceRoute( (req, res, next) =>
new MyRouter(req, res, next)
));
/* ... additional app configuration, call to app.listen() ... */
createRoutes
Syntax
createRoutes(options)
Params
options
is an object with the following key parameters:
routeBasename
: String, given a value likefoo
it will create routes likefoo.length
,foo[range]
,fooById[keys]
,foo.create
andfoo.delete
acceptedKeys
: Array of Strings, the keys you'd like your model to expose. A value of['id', 'date', 'content']
will provide access to theid
,date
, andcontent
keys from a model object. Make sure your model actually returns those keys ;)getLength
: Function or Promise of the form() => length
. Must return the number of objects provided by your model.getRange
: Function or Promise of the form(from, to) => [modelObject, ...]
wherefrom
andto
are inclusive (i.e. (from=0, to=0) would return an Array containing the model object at interval 0).getById
: Function or Promise of the form(id) => modelObject
whereid
is the key used to retrieve a single item from your model collection.update
: Function or Promise of the form(oldObj, newParams) => newObj
whereoldObj
will be given as a model instance retrieved by yourgetById
function andnewParams
will be a plain Object containing the key values to update. As a side-effect, you must savenewObj
to your model store.create
: Function or Promise of the form(newParams) => newObj
wherenewParams
will be a plain Object containing the key values used to create the new model instance. As a side-effect, you must savenewObj
to your model store.delete
: Function or Promise of the form(id) => null
whereid
is the key used to retrieve a single item from your model collection. As a side-effect, you must delete the model instance with thisid
from your model collection.modelIdKey
: (optional) String. The primary key used by your model collection. Default:'id'
.modelKeyGetter
: (optional) Function of the form(model, key) => model[key]
. It's the method used byfalcor-saddle
to retrieve keys from your model objects outside of the methods you specify. Rarely used.modelIdGetter
: (optional) Function of the form(model) => model['id']
. It's the method used byfalcor-saddle
to retrieve the primary key value from your model collection. Default:(model) => modelKeyGetter(model, modelIdKey)
. Rarely used.
Client Usage
Given a NodeJS server providing routes created by falcor-saddle
at
http://localhost/api/model.json
, how do you use this thing? Some examples
are given below.
Creating the Client-side Model (HTTP):
const model = new falcor.Model({source: new HttpDataSource('/api/model.json') });
Length
model.
getValue('todos.length')
.then((response) => console.log(`todos.length: ${response}`));
Retrieve Range
model
.get('todos[0..1]["id", "content"]')
.then((response) => {
console.log('todos[0..1]: ' + JSON.stringify(response));
});
Set (Update) First Item
const aTodoResponse = await model.get('todos[0]["id", "content"]');
const updateTodoReq = { json: { todosById: { } } };
updateTodoReq.json.todosById[aTodoResponse.json.todos['0'].id] = {
content: 'this is some updated content!'
};
model.
.set(updateTodoReq)
.then( (response) => {
console.log('todos[0]:' + JSON.stringify(response));
});
Get By Id
// bd8d468d-a330-4a13-b916-9ff46be54f3e is an example primary key value:
const key = 'bd8d468d-a330-4a13-b916-9ff46be54f3e';
model.
.get(['todosById', [key]])
.then( (response) => {
console.log('todosById[key]:' + JSON.stringify(response));
});
Create
model.
call('todos.create', [
// Values for first item to create:
{ content: 'snow day today' }
],
// Fields to retrieve from created items:
['id', 'content', 'createdAt']
)
.then( (response) => {
console.log('todos.create: ' + JSON.stringify(response));
});
Delete
const firstItemId = await model.getValue(['meetings', 0, 'id']);
model
.call('todosById.delete', [firstItemId])
.then((response) => {
console.log('todosById.delete: ', response);
});
Caveats
Maturity
Falcor itself is currently in preview release. This module, and the capability it provides should be considered experimental.
We wrote it for developing our own applications, but heck, we're not sure if
if falcor-saddle
is even a good idea. We do know that it's helped us
sketch out applications – so, at a minimum, consider it useful for that
purpose.
Ranges
The way ranges are handled (e.g. todo[0..n]
) is presently rather lazy.
When a new item is created with todo.create
it is added to the end of the
range (i.e. todo[n+1]
). When an item is deleted with todo.delete
rather than rebuilding, the entire range we merely invalidate the item
from the list.
This may change in the future.
Serialization
Although Falcor provides its own serialization, there are some corner cases where it fails: for example, we've observed returning a Date object causes an exception to be thrown. To avoid this, we've created a simple serializer.
Model serialization is implemented in src/serialization.js
. Your model's
return values are run through this serializer. At present, anything that's
not a:
- Boolean or
- Number or
- String or
null
orundefined
...will be serialized using JSON.stringify()
.
Releases
Semantic Versioning
falcor-saddle
uses Semantic Versioning.
Change Log
About
Team
falcor-saddle
is an experiment from Parabol. A
young company building a human operating-system for teams and organizations.
With other contributions by:
Contributing
We'd love to see this project grow.
Provide Issues, fork to your heart's content, and submit pull requests.