Collection Resource Type #

Collections are the most common Resource Type in Deployd. They allow the user to store and load data from their app's Store. Behind the scenes, they validate incoming requests and execute event scripts for get, post, put, delete, and validate. If all event scripts execute without error (or cancel()ing), the request is proxied to the collection's Store.

Class: Collection #

A Collection inherits from Resource. Any constructor that inherits from Collection must include its own Collection.external prototype object.

Example inheriting from Collection:

var Collection = require('deployd/lib/resources/collection');
var util = require('util');

function MyCollection(name, options) {
  Collection.apply(this, arguments);
}
MyCollection.external = Collection.external;

util.inherits(MyCollection, Collection);

collection.store #

  • {Store}

The backing persistence abstraction layer. Supports saving and reading data from a database. See Store for more info.

collection.validate(body, create) #

Validate the request body against the Collection's properties and return an object containing any errors.

  • body {Object}

The object to validate

  • create {Boolean}

Should validate a new object being created

  • return errors {Object}

collection.sanitize(body) #

Sanitize the request body against the Collection's properties object and return an object containing only properties that exist in the collection.config.properties object.

  • body {Object}
  • return sanitized {Object}

collection.sanitizeQuery(query) #

Sanitize the request query against the collection.properties and return an object containing only properties that exist in the collection.properties object.

  • query {Object}
  • return sanitizedQuery {Object}

collection.parseId(ctx) #

Parse the ctx.url for an id. Override this to change how an object's id is parsed out of a url.

  • ctx {Context}

collection.find(ctx, fn) #

Find all the objects in a collection that match the given ctx.query. Then execute a get event script, if one exists, using each object found. Finally call fn(err) passing an error if one occurred.

  • ctx {Context}
  • fn(err)

collection.remove(ctx, fn) #

Execute a delete event script, if one exists, using each object found. Then remove a single object that matches the ctx.query.id. Finally call fn(err) passing an error if one occurred.

  • ctx {Context}
  • fn(err)

collection.save(ctx, fn) #

First execute a validate event script if one exists. If the event does not error, try to save the ctx.body into the store. If ctx.body.id exists, perform an update and execute the put event script. Otherwise perform an insert and execute the post event script. Finally call fn(err), passing an error if one occurred.

  • ctx {Context}
  • fn(err)

Context #

Contexts are a thin abstraction between http requests, Resources, and Scripts. They provide utility methods to simplify interacting with node's http.ServerRequest and http.ServerResponse objects.

A Context is built from a request and response and passed to a matching Resource by the Router. This might originate from an external http request or a call to an internal client from a Script.

Mock Contexts #

Contexts may be created without a real request and response such as during an internal request using the dpd object. See Internal Client for more info.

Class: Context #

var Context = require('deployd/lib/context');
var ctx = new Context(resource, req, res, server);

ctx.done(err, result) #

Continuous callback sugar for easily calling res.end(). Conforms to the idiomatic callback signature for most node APIs. It can be passed directly to most APIs that require a callback in node.

fs.readFile('bar.txt', ctx.done);
  • err {Error | Object}

An error if one occurred during handling of the ctx. Otherwise it should be null.

  • result {Object}

The result of executing the ctx. This should be an object that is serializable as JSON.

ctx.body #

  • {Object}

The body of the request if sent as application/json or application/x-www-form-urlencoded.

ctx.query #

  • {Object}

The query string of the request serialized as an Object. Supports both ?key=value as well as ?{"key":"value"}.

ctx.method #

  • {Object}

An alias to the request's method.

Event Scripts #

A Script provides a mechanism to run JavaScript source in a sandbox. A Script is executed with a Context and a domain object using the node vm module. Each Script runs independently. They do not share global scope or state with other scripts or modules.

Async Mode #

Scripts can be run in an async mode. This mode is triggered when a Script is run(ctx, domain, fn) with a callback (fn). When run in this mode a Script will try scrub all functions in the domain for operations that require a callback. If a callback is required, the function is re-written to count the callbacks completion and notify the script. When all pending callbacks are complete the script is considered finished.

Async Errors #

If a script is run with a callback (in async mode), any error will emit an internal error event. This will stop the execution of the script and pass the error to the script's callback.

Class: Script #

var Script = require('deployd/lib/script');
var script = new Script('hello()', 'hello.js');

A Script's source is compiled when its constructor is called. It can be run() many times with independent Contexts and domains.

script.run(ctx, domain, [fn]) #

  • ctx {Context}

A Context with a session, query, req and res.

  • domain {Object}

An Object containing functions to be injected into the Scripts sandbox. This will override any existing functions or objects in the Scripts sandbox / global scope.

This example domain provides a log function to a script.

var script = new Script('log("hello world")');
var context = {};
var domain = {};
var msg;

domain.log = function(str) {
  console.log(msg = str);
}

script.run(ctx, domain, function(err) {
  console.log(msg); // 'hello world'
});
  • fn(err) optional

If a callback is provided the script will be run in async mode. The callback will receive any error even if the error occurs asynchronously. Otherwise it will be called without any arguments when the script is finished executing (see: async mode).

var s = new Script('setTimeout(function() { throw "test err" }, 22)');

// give the script access to setTimeout
var domain = {setTimeout: setTimeout};

s.run({}, domain, function (e) {
  console.log(e); // test err
});

Script.load(path, fn) #

  • path {String}

  • fn(err, script)

Load a new script at the given file path. Runs the callback with an error if one occurred, or a new Script loaded from the contents of the file.

Default Domain #

Scripts are executed with a default sandbox and set of domain functions. These are functions that every Script usually needs, and are available to every Script. These can be overridden by passing a value such as {cancel: ...} in a domain. See Event API for Custom Resources for documentation on this default domain.

Internal Client #

The internal-client module is responsible for building a server-side version of dpd.js. It is intended for use in Scripts but can be used by resources to access other resources' REST APIs.

Note: As in dpd.js, the callback for an internal client request receives the arguments (data, err), which is different than the Node convention of (err, data).

internalClient.build(server, [session], [stack]) #

var internalClient = require('deployd/lib/internal-client');

process.server.on('listening', function() {
  var dpd = internalClient.build(process.server);
  dpd.todos.get(function(data, err) {
    // Do something
  });  
});
  • server {Server}

The Deployd server to build a client for.

  • session {Session} (optional)

The Session object on the current request.

  • stack {Array} (optional)

Used internally to prevent recursive calls to resources.

Mock context #

In order to make requests on resources within the Deployd server, internal-client creates mock req and res objects. These objects are not Streams and cannot be treated exactly like the standard http.ServerRequest and http.ServerResponse objects in Node, but they imitate their interfaces with the following properties:

req #

  • url {String}

The URL of the request, i.e. "/hello"

  • method {String}

The method of the request, i.e. "GET", "POST"

  • query {Object}

The query object.

  • body {Object}

The body of the request.

  • session {Session}

The current session, if any.

  • internal {Boolean}

Always equal to true to indicate an internal request and a mock req object.

res #

  • statusCode {Number}

Set this to a standard HTTP response code.

  • setHeader() {Function}

No-op.

  • end(data) {Function}

Returns data to the internal client call. If data is JSON, it will be parsed into an object, otherwise it will simply be passed as a string. If the res.statusCode is not 200 or 204, data will be passed as an error.

  • internal {Boolean}

Always equal to true to indicate an internal request and a mock res object.

Resource Types #

Resources are the building block of a Deployd app. They provide a way to handle http requests at a root url. They must implement a handle(ctx, next) method that either handles a request or calls next() to give the request back to the router.

Resources can also be attributed with meta-data to allow the dashboard to dynamically render an editor gui for configuring a resource instance.

Events / Scripts #

A Resource can execute Scripts during the handling of an http request when certain events occur. This allows users of the resource to inject logic during specific events during an http request without having to extend the resource or create their own.

For example, the Collection resource executes the get.js event script when it retrieves each object from its store. If a get.js file exists in the instance folder of a resource (eg. /my-project/resources/my-collection/get.js), it will be pulled in by the resource and exposed as myResource.scripts.get.

Class: Resource #

A Resource inherits from EventEmitter. The following events are available.

  • changed after a resource config has changed
  • deleted after a resource config has been deleted

Inheriting from Resource:

var Resource = require('deployd/lib/resource')
  , util = require('util');

function MyResource(name, options) {
  // run the parent constructor
  // before using any properties/methods
  Resource.apply(this, arguments);
}
util.inherits(MyResource, Resource);
module.exports = MyResource;
  • name {String}

The name of the resource.

  • options {Object}

    • configPath the project relative path to the resource instance
    • path the base path a resource should handle
    • db (optional) the database a resource will use for persistence
    • config the instance configuration object
    • server the server object

The following resource would respond with a file at the url /my-file.html.

function MyFileResource(name, options) {
  Resource.apply(this, arguments);

  this.on('changed', function(config) {
    console.log('MyFileResource changed', config);
  });
}
util.inherits(MyFileResource, Resource);

MyFileResource.prototype.handle = function (ctx, next) {
  if (ctx.url === '/my-file.html') {
    fs.createReadStream('my-file.html').pipe(ctx.res);
  } else {
    next();
  }
}

Overriding Behavior #

Certain methods on a Resource prototype are called by the runtime. Their default behavior should be overridden to define an inherited Resources behavior.

resource.handle(ctx, next) #

Handle an incoming request. This gets called by the router.

The resource can either handle this context and call ctx.done(err, obj) with an error or result JSON object, or call next() to give the context back to the router. If a resource calls next() the router might find another match for the request, or respond with a 404.

The http context created by the Router. This provides an abstraction between the actual request and response. A Resource should call ctx.done or pipe to ctx.res if it can handle a request. Otherwise it should call next().

Override the handle method to return a string:

function MyResource(settings) {
  Resource.apply(this, arguments);
}
util.inherits(MyResource, Resource);

MyResource.prototype.handle = function (ctx, next) {
  // respond with the file contents (or an error if one occurs)
  fs.readFile('myfile.txt', ctx.done);
}

resource.load(fn) #

Load any dependencies and call fn(err) with any errors that occur. This is automatically called by the runtime to support asynchronous construction of a resource (such as loading files).

Note: If this method is overridden, the super method must be called to support loading of the MyResource.events array.

resource.clientGeneration #

If true, ensures that this resource is included in dpd.js.

MyResource.prototype.clientGeneration = true;

resource.config #

The instance configuration object; used to access the resource's configuration from member functions.

MyResource.prototype.handle = function (ctx, next) {
  fs.readFile(this.config.filePath, ctx.done);
}

External Prototype #

This is a special type of prototype object that is used to build the dpd object. Each function on the Resource.external prototype Object are exposed externally in two places

  1. To the generated dpd.js browser JavaScript client
  2. To the Context.dpd object generated for inter-resource calls

Here is an example of a simple resource that exposes a method on the external prototype.

/my-project/node_modules/example.js

var util = require('util');
var Resource = require('deployd/lib/resource');
function Example(name, options) {
  Resource.apply(this, arguments);
}
util.inherits(Example, Resource);

Example.external = {};

Example.external.hello = function(options, ctx, fn) {
  console.log(options.msg); // 'hello world'
}

When the hello() method is called, a context does not need to be provided as the dpd object is built with a context. A callback may be provided which will be executed with results of fn(err, result).

/my-project/public/hello.js

dpd.example.hello({msg: 'hello world'});

/my-project/resources/other-resource/get.js

dpd.example.hello({msg: 'hello world'});

Resource.events #

  • {Array}

If a Resource constructor includes an array of events, it will try to load the scripts in its instance folder (eg. /my-project/resources/my-resource/get.js) using resource.loadScripts(eventNames, fn).

MyResource.events = ['get'];

This will be available to each instance of this resource as this.events.

/my-project/node_modules/my-resource.js

MyResource.prototype.handle = function(ctx, next) {
  if(this.events && this.events.get) {
    var domain = {
      say: function(msg) {
        console.log(msg); // 'hello world'
      }
    }
    this.events.get.run(ctx, domain, ctx.done);
  }
}

/my-project/resources/my-resource/get.js

say('hello world');

Resource.label #

The resource type's name as it appears in the dashboard. If this is not set, it will appear with its constructor name.

Hello.label = 'Hello World';

Resource.defaultPath #

The default path suggested to users creating a resource. If this is not set, it will use the constructor's name in lowercase.

Hello.defaultPath = '/hello-world'; 

Collection.basicDashboard #

Set this property to an object to create a custom configuration page for your resource type.

  • settings - An array of objects describing which properties to display.
  • name - The name of the property. This is how the value will be passed into the config object, so make sure it's something JavaScript-friendly, e.g. maxItems.
  • type - The type of control to edit this property. Allowed types are text, textarea, number, and checkbox.
  • description (Optional) - Explanatory text to appear below the field.
Hello.basicDashboard = {
  settings: [{
      name: 'propertyName',
      type: 'text',
      description: "This description appears below the text field"
  }, {
      name: 'longTextProperty',
      type: 'textarea'
  }, {
      name: 'numericProperty',
      type: 'number'
  }, {
      name: 'booleanProperty',
      type: 'checkbox'
  }]
};

The above sample will produce the following dashboard page:

Example basic dashboard

Collection.dashboard #

A resource can describe the dependencies of a fully custom dashboard editor UI. This will be passed to the dashboard during rendering to create a custom UI.

This example creates the custom dashboard for the Collection resource. It automatically includes pages and page-specific scripts:

Collection.dashboard = {
    path: path.join(__dirname, 'dashboard')
  , pages: ['Properties', 'Data', 'Events', 'API']
  , scripts: [
      '/js/ui.js'
    , '/js/util.js'
  ]
}
  • path {String}

The absolute path to this resource's dashboard

  • pages {Array} (optional)

An array of pages to appear in the sidebar. If this is not provided, the only page available will be "Config" (and "Events", if MyResource.events is set).

The dashboard will load content from [current-page].html and js/[current-page].js.

Note: The "Config" page will load from index.html and js/index.js.

  • scripts {Array} (optional)

An array of extra JavaScript files to load with the dashboard pages.

Dashboard asset loading #

When you request a page from a custom dashboard, it will load the following files, if they are available, from the dashboard.path:

  • [current-page].html
  • js/[current-page].js
  • style.css

The default page is index; the config page will also redirect to index.

The config or index page will load the basic dashboard if no index.html file is provided. The events page will load the default event editor if no events.html file is provided.

It will also load the JavaScript files in the dashboard.scripts property.

Creating a custom dashboard #

Event editor control #

To embed the event editor in your dashboard, include this empty div:

<div id="event-editor" class="default-editor"></div>
Styling #

For styling, the dashboard uses a re-skinned version of Twitter Bootstrap 2.0.2.

JavaScript #

The dashboard provides several JavaScript libraries by default:

Within the dashboard, a Context object is available:

//Automatically generated by Deployd:
window.Context = {
  resourceId: '/hello', // The id of the current resource
  resourceType: 'Hello', // The type of the current resource
  page: 'properties', // The current page, in multi-page dashboards
  basicDashboard: {} // The configuration of the basic dashboard
};

You can use this to query the current resource:

dpd(Context.resourceId).get(function(result, err) {
  //Do something
});

In the dashboard, you also have access to the special __resources resource, which lets you update your app's configuration files:

// Get the config for the current resource
dpd('__resources').get(Context.resourceId, function(result, err) {
  //Do something
});

// Set a property for the current resource
dpd('__resources').put(Context.resourceId, {someProperty: true}, function(result, err) {
  //Do something
});

// Set all properties for the current resource, deleting any that are not provided
dpd('__resources').put(Context.resourceId, {someProperty: true, $setAll: true}, function(result, err) {
  //Do something
});

// Save another file, which will be loaded by the resource
dpd('__resources').post(Context.resourceId + '/content.md', {value: "# Hello World!"}, function(result, err)) {
  //Do something
});

Server #

Deployd's Server extends node's http.Server. A Server is created with an options object that tells Deployd what port to serve on and which database to connect to.

The Server object is also the main entry point for modules. After it is started, the Server instance is available at process.server.

Class: Server #

Servers are created when calling the Deployd exported function.

var deployd = require('deployd')
  , options = {port: 3000}
  , server = deployd(options);
  • options {Object}

    • port {Number} - the port to listen on
    • db {Object} - the database to connect to
    • db.connectionString {String} - The URI of the mongoDB using standard Connection String. If db.connectionString is set, the other db options are ignored.
    • port {Number} - the port of the database server
    • host {String} - the ip or domain of the database server
    • name {String} - the name of the database
    • credentials {Object} - credentials for the server
      • username {String}
      • password {String}
    • env {String} - the environment to run in.

Note: If options.env is "development", the dashboard will not require authentication and configuration will not be cached. Make sure to change this to "production" or something similar when deploying.

Server.listen([port], [host]) #

Load any configuration and start listening for incoming connections.

var dpd = require('deployd')
  , server = dpd()

dpd.listen();
dpd.on('listening', function() {
  console.log(server.options.port); // 2403
});

Server.createStore(namespace) #

Create a new Store for persisting data using the database info that was passed to the server when it was created.

// Create a new server
var server = new Server({port: 3000, db: {host: 'localhost', port: 27015, name: 'my-db'}});

// Attach a store to the server
var todos = server.createStore('todos');

// Use the store to CRUD data
todos.insert({name: 'go to the store', done: true}, ...); // see `Store` for more info

Server.sockets #

The socket.io sockets Manager object (view source).

Server.sessions #

The server's SessionStore.

Server.router #

The server's Router.

Server.resources #

An Array of the server's Resource instances. These are built from the config and type loaders.

deployd.attach #

deployd.attach can attach Server Class functions into a regular http server. It also provide handleRequest function to act as express middleware.

var deployd = require('deployd')
  , options = {}
  , server = require('http').createServer(app)
  , server = deployd.attach(server, options);
  • options {Object}

    • socketIo {Object} - socket.io instance
    • db {Object} - the database to connect to
    • port {Number} - the port of the database server
    • host {String} - the ip or domain of the database server
    • name {String} - the name of the database
    • credentials {Object} - credentials for the server
      • username {String}
      • password {String}
    • env {String} - the environment to run in.

Note: If options.env is "development", the dashboard will not require authentication and configuration will not be cached. Make sure to change this to "production" or something similar when deploying.

Session #

An in-memory representation of a client or user connection that can be saved to disk. Data will be passed around via a Context to resources.

Class: Session #

A store for persisting sessions in-between connection and disconnection. Automatically creates session IDs on inserted objects.

var session = new Session({id: 'my-sid', new SessionStore('sessions', db)});
session.set({uid: 'my-uid'}).save();
  • data {Object}

The data used to construct the session.

  • store {SessionStore}

The store used to persist the session.

  • sockets {Socket.IO.sockets}

The Socket.IO sockets object used to attach an existing socket.

Session.set(changes) #

  • changes {Object}

An object containing changes to the session's data.

Session.save(fn) #

  • fn(err, data) {Function} optional

Save the in memory representation of a session to its store.

Session.fetch(fn) #

  • fn(err, data) {Function} optional

Reset the session using the data persisted in its store.

Session.remove(fn) #

  • fn(err, data) {Function} optional

Remove the session.

Session.emitToAll(event, data) #

  • event {String}

The event to emit to all session's sockets.

  • data {Object} optional

The data to send to sockets listening to the given event.

Session.emitToUsers(collection, query, event, data) #

  • collection {Collection}

The user-collection instance (eg. dpd.todos) to use to find users.

  • query {Object}

Only emit the event to users that match this query.

  • event {String}

The event to emit to all session's sockets.

  • data {Object} optional

The data to send to sockets listening to the given event.

Session Store #

Sessions are persisted in a modified store. This store has several methods to help create and manage sessions.

Class: SessionStore #

A store for persisting sessions in-between connection and disconnection. Automatically creates session IDs on inserted objects.

var db = process.server.db;
var sockets = process.server.sockets;
var name = 'sessions';
var store = new SessionStore(name, db, sockets);
  • name {String}

The name of the db store.

  • db {Db}

The server db instance

  • sockets {Socket.IO.sockets}

The socket.io sockets object.

SessionStore.createSession(sid, fn) #

  • sid {String} optional

An existing session id.

  • fn(err, session) {Function}

Called once the session has been created.

Store #

An abstraction of a collection of objects in a database. Collections are HTTP wrappers around a Store. You can access or create a store the same way.

var myStore = process.server.createStore('my-store');

Class: Store #

You shouldn't construct Stores directly. Instead use the process.server.createStore() method.

Store.insert(object, fn) #

  • object {Object}

The data to insert into the store.

  • fn(err, result) {Function}

Called once the insert operation is finished.

Store.count(query, fn) #

  • query {Object}

Only count objects that match this query.

  • fn(err, count) {Function}

Called once the count operation is finished. count is a number.

Store.find(query, fn) #

  • query {Object}

Only returns objects that match this query.

  • fn(err, results) {Function}

Called once the find operation is finished.

Store.first(query, fn) #

  • query {Object}

  • fn(err, result) {Function}

Find the first object in the store that match the given query.

Store.update(query, changes, fn) #

  • query {Object}

  • changes {Object}

  • fn(err, updated) {Function}

Update an object or objects in the store that match the given query only modifying the values in the given changes object.

Store.remove(query, fn) #

  • query {Object}

  • fn(err, updated) {Function}

Remove an object or objects in the store that match the given query.

Store.rename(name, fn) #

  • name {String}

  • fn(err) {Function}

Rename the store.

More