Initial working store, model, connection, and collection
This commit is contained in:
parent
cd8d679df0
commit
7c2e73f53a
|
@ -2,6 +2,9 @@ var asteroid = require('../../');
|
|||
var app = asteroid();
|
||||
|
||||
app.use(asteroid.configure());
|
||||
app.use(asteroid.bodyParser());
|
||||
app.use(asteroid.resources());
|
||||
|
||||
app.listen(3000);
|
||||
|
||||
process.memory_store_cache = {};
|
|
@ -1,11 +1,3 @@
|
|||
{
|
||||
"module": "data-store",
|
||||
"options": {
|
||||
"database": "asteroid-examples-todos",
|
||||
"port": 27017,
|
||||
"host": "127.0.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"db": "memory"
|
||||
}
|
||||
"module": "store"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
throw new Error('foo');
|
|
@ -17,6 +17,6 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"data-store": "db"
|
||||
"store": "db"
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ todos.on('before:validate', function (todo, ctx) {
|
|||
}
|
||||
});
|
||||
|
||||
todos.on('before:create', function (todo, ctx, done) {
|
||||
todos.on('create', function (todo, ctx, done) {
|
||||
ctx.errorUnless(ctx.isEmail(todos.creator));
|
||||
|
||||
todos.model.count({owner: todo.owner}, function (err) {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"module": "users-collection",
|
||||
"options": {
|
||||
"root": "/users",
|
||||
"name": "users",
|
||||
"properties": [
|
||||
{
|
||||
"name": "email",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"type": "password"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"store": "store"
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@ module.exports = configure;
|
|||
function configure(root) {
|
||||
var moduleLoader = ModuleLoader.create(root || '.');
|
||||
|
||||
process.__asteroidCache = {};
|
||||
|
||||
return function configureMiddleware(req, res, next) {
|
||||
req.modules = res.modules = moduleLoader;
|
||||
moduleLoader.load(next);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../mocha/bin/_mocha
|
|
@ -0,0 +1 @@
|
|||
../mocha/bin/mocha
|
|
@ -1,13 +1,28 @@
|
|||
# asteroid-module
|
||||
v0.0.1
|
||||
|
||||
## Install
|
||||
## About
|
||||
|
||||
slnode install asteroid-module
|
||||
An `AsteroidModule` is an abstract class that provides a base for all asteroid modules. Its constructor takes an `options` argument provided by a `config.json`. It is also supplied with dependencies it lists on its constructor based on information in the `config.json` file.
|
||||
|
||||
## Example
|
||||
|
||||
var AsteroidModule = require('asteroid-module');
|
||||
var asteroidModule = AsteroidModule.create();
|
||||
See [resource](../resource) for an example asteroid module.
|
||||
|
||||
asteroidModule.myMethod();
|
||||
## AsteroidModule.dependencies
|
||||
|
||||
An asteroid module may define dependencies on other modules that can be configured in `config.json`. Eg. the [collection](../collection/lib/collection.js) module defines a [model](../model) dependency.
|
||||
|
||||
Collection.dependencies = {
|
||||
model: 'model'
|
||||
}
|
||||
|
||||
A configuration then must define:
|
||||
|
||||
{
|
||||
"dependencies": {
|
||||
"model": "some-model-module"
|
||||
}
|
||||
}
|
||||
|
||||
Where `some-model-module` is an existing `model` instance.
|
|
@ -1,13 +1,21 @@
|
|||
# collection
|
||||
v0.0.1
|
||||
|
||||
## Install
|
||||
## About
|
||||
|
||||
slnode install collection
|
||||
A `Collection` inherits from the [resource](../resource) class. It provides HTTP access to a supplied [model](../model) instance.
|
||||
|
||||
## Example
|
||||
## Config
|
||||
|
||||
var Collection = require('collection');
|
||||
var collection = Collection.create();
|
||||
Supports all configuration of a [resource](../resource).
|
||||
|
||||
collection.myMethod();
|
||||
### Options
|
||||
|
||||
#### name (optional)
|
||||
|
||||
If defined, overrides the default name (based on the path).
|
||||
|
||||
### Dependencies
|
||||
|
||||
#### model
|
||||
|
||||
Requires a path to an existing model instance.
|
||||
|
|
|
@ -8,7 +8,7 @@ module.exports = Collection;
|
|||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Resource = require('resource').Resource
|
||||
var Resource = require('resource')
|
||||
, debug = require('debug')('collection')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
|
@ -26,13 +26,11 @@ function Collection(options) {
|
|||
|
||||
this.options = options;
|
||||
|
||||
// collection middleware
|
||||
this.app.use(function (req, res, next) {
|
||||
req.asyncEmit('request', next);
|
||||
});
|
||||
// model
|
||||
this.model = this.dependencies.model.schema;
|
||||
|
||||
// setup http routes
|
||||
// this.setupRoutes(this.app);
|
||||
this.setupRoutes(this.app);
|
||||
|
||||
debug('created with options', options);
|
||||
}
|
||||
|
@ -48,22 +46,33 @@ inherits(Collection, Resource);
|
|||
*/
|
||||
|
||||
Collection.dependencies = {
|
||||
'store': 'store'
|
||||
'model': 'model'
|
||||
}
|
||||
|
||||
Collection.prototype.setupRoutes = function (app) {
|
||||
var store = this.store;
|
||||
var done = this.done;
|
||||
var Model = this.store;
|
||||
var emit = this.asyncEmit;
|
||||
var Model = this.model;
|
||||
var ctx;
|
||||
var collection = this;
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
ctx = collection.createContext(req, res, next);
|
||||
|
||||
ctx.emit('request', function () {
|
||||
ctx.emit(req.method.toLowerCase(), function () {
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// create
|
||||
app.post('/', create);
|
||||
app.post('/new', create);
|
||||
|
||||
function create(req, res, next) {
|
||||
req.asyncEmit('create', function () {
|
||||
var o = store.save(req.body, res.done);
|
||||
console.log(req.body);
|
||||
|
||||
ctx.emit('create', function (done) {
|
||||
Model.create(req.body, done);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,24 +91,44 @@ Collection.prototype.setupRoutes = function (app) {
|
|||
query.limit = 1;
|
||||
}
|
||||
|
||||
req.asyncEmit('query', query, function () {
|
||||
store.all(query, res.done);
|
||||
if(query.limit) query.limit = Number(query.limit);
|
||||
if(query.skip) query.skip = Number(query.skip);
|
||||
|
||||
ctx.emit('query', query, function (done) {
|
||||
Model.all(query, done);
|
||||
});
|
||||
}
|
||||
|
||||
// update
|
||||
app.put('/:id', function (req, res) {
|
||||
req.asyncEmit('update', req.body, function () {
|
||||
store.updateAttributes(req.body, res.done);
|
||||
var body = req.body || {};
|
||||
var id = req.param('id');
|
||||
|
||||
if(Number(id) == id) {
|
||||
id = Number(id);
|
||||
}
|
||||
|
||||
ctx.emit('find', function () {
|
||||
Model.find(id, function (err, m) {
|
||||
if(err) {
|
||||
ctx.done(err);
|
||||
} else {
|
||||
ctx.emit('validate', body, function () {
|
||||
ctx.emit('update', body, function (done) {
|
||||
m.updateAttributes(body, done);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// delete
|
||||
app.destroy('/:id', function () {
|
||||
app.del('/:id', function () {
|
||||
var id = req.param('id');
|
||||
|
||||
req.asyncEmit('delete', id, function () {
|
||||
store.destroy(id, res.done);
|
||||
ctx.emit('delete', id, function (done) {
|
||||
Model.destroy(id, done);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.swp
|
||||
*.swo
|
||||
node_modules/
|
|
@ -0,0 +1,13 @@
|
|||
# connection
|
||||
|
||||
## About
|
||||
|
||||
Provides a base class for implementing access to a persistence layer.
|
||||
|
||||
### Options
|
||||
|
||||
#### hostname
|
||||
#### port
|
||||
#### database
|
||||
#### username
|
||||
#### password
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* A generated `Connection` example...
|
||||
*
|
||||
* Examples should show a working module api
|
||||
* and be used in tests to continously check
|
||||
* they function as expected.
|
||||
*/
|
||||
|
||||
var Connection = require('../');
|
||||
var connection = Connection.create();
|
||||
|
||||
connection.myMethod();
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* connection ~ public api
|
||||
*/
|
||||
|
||||
module.exports = require('./lib/connection');
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Expose `Connection`.
|
||||
*/
|
||||
|
||||
module.exports = Connection;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var AsteroidModule = require('asteroid-module')
|
||||
,
|
||||
, debug = require('debug')('connection')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Create a new `Connection` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Connection}
|
||||
*/
|
||||
|
||||
function Connection(options) {
|
||||
AsteroidModule.apply(this, arguments);
|
||||
this.options = options;
|
||||
|
||||
debug('created with options', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `AsteroidModule`.
|
||||
*/
|
||||
|
||||
inherits(Connection, AsteroidModule);
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "connection",
|
||||
"description": "connection",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "latest"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
var Connection = require('../');
|
||||
|
||||
describe('Connection', function(){
|
||||
var connection;
|
||||
|
||||
beforeEach(function(){
|
||||
connection = new Connection;
|
||||
});
|
||||
|
||||
describe('.myMethod', function(){
|
||||
// example sync test
|
||||
it('should <description of behavior>', function() {
|
||||
connection.myMethod();
|
||||
});
|
||||
|
||||
// example async test
|
||||
it('should <description of behavior>', function(done) {
|
||||
setTimeout(function () {
|
||||
connection.myMethod();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* connection test setup and support.
|
||||
*/
|
||||
|
||||
assert = require('assert');
|
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.swp
|
||||
*.swo
|
||||
node_modules/
|
|
@ -0,0 +1,37 @@
|
|||
# model
|
||||
|
||||
## About
|
||||
|
||||
Provides a schema protected api to a data [store](../store).
|
||||
|
||||
### Options
|
||||
|
||||
#### namespace
|
||||
|
||||
A table, collection, url, or other namespace.
|
||||
|
||||
#### properties
|
||||
|
||||
An array of properties describing the model's schema.
|
||||
|
||||
"properties": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "done",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"name": "order",
|
||||
"type": "number"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
### Dependencies
|
||||
|
||||
#### store
|
||||
|
||||
A [store](../store) module instance.
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* A generated `Model` example...
|
||||
*
|
||||
* Examples should show a working module api
|
||||
* and be used in tests to continously check
|
||||
* they function as expected.
|
||||
*/
|
||||
|
||||
var Model = require('../');
|
||||
var model = Model.create();
|
||||
|
||||
model.myMethod();
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* model ~ public api
|
||||
*/
|
||||
|
||||
module.exports = require('./lib/model');
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Expose `Model`.
|
||||
*/
|
||||
|
||||
module.exports = Model;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var AsteroidModule = require('asteroid-module')
|
||||
, debug = require('debug')('model')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Create a new `Model` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Model}
|
||||
*/
|
||||
|
||||
function Model(options) {
|
||||
AsteroidModule.apply(this, arguments);
|
||||
|
||||
// throw an error if args are not supplied
|
||||
// assert(typeof options === 'object', 'Model requires an options object');
|
||||
|
||||
this.options = options;
|
||||
|
||||
debug('created with options', options);
|
||||
|
||||
var dependencies = this.dependencies;
|
||||
var store = dependencies.store;
|
||||
var schema = this.schema = store.schema;
|
||||
|
||||
assert(Array.isArray(options.properties), 'the ' + options._name + ' model requires an options.properties array');
|
||||
|
||||
// define schema
|
||||
this.schema = schema.define(options.namespace || options._name, this.buildSchemaDefinition(options.properties));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `AsteroidModule`.
|
||||
*/
|
||||
|
||||
inherits(Model, AsteroidModule);
|
||||
|
||||
/**
|
||||
* Dependencies.
|
||||
*/
|
||||
|
||||
Model.dependencies = {
|
||||
'store': 'store'
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a jugglingdb compatibile schema definition from property array.
|
||||
*/
|
||||
|
||||
Model.prototype.buildSchemaDefinition = function (properties) {
|
||||
return properties.reduce(function (prev, cur) {
|
||||
prev[cur.name] = types[cur.type];
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
|
||||
var types = [String, Number, Date, Array, Boolean].reduce(function (prev, cur) {
|
||||
prev[cur.name.toLowerCase()] = cur
|
||||
return prev;
|
||||
}, {});
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "model",
|
||||
"description": "model",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "latest"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
var Model = require('../');
|
||||
|
||||
describe('Model', function(){
|
||||
var model;
|
||||
|
||||
beforeEach(function(){
|
||||
model = new Model;
|
||||
});
|
||||
|
||||
describe('.myMethod', function(){
|
||||
// example sync test
|
||||
it('should <description of behavior>', function() {
|
||||
model.myMethod();
|
||||
});
|
||||
|
||||
// example async test
|
||||
it('should <description of behavior>', function(done) {
|
||||
setTimeout(function () {
|
||||
model.myMethod();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* model test setup and support.
|
||||
*/
|
||||
|
||||
assert = require('assert');
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* Expose `HttpContext`.
|
||||
*/
|
||||
|
||||
module.exports = HttpContext;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, debug = require('debug')('http-context')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Create a new `HttpContext` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {HttpContext}
|
||||
*/
|
||||
|
||||
function HttpContext(resource, req, res, next) {
|
||||
EventEmitter.apply(this, arguments);
|
||||
|
||||
this.resource = resource;
|
||||
this.req = req;
|
||||
this.res = res;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `EventEmitter`.
|
||||
*/
|
||||
|
||||
inherits(HttpContext, EventEmitter);
|
||||
|
||||
/**
|
||||
* Override the default emitter behavior to support async or sync hooks before and after an event.
|
||||
*/
|
||||
|
||||
HttpContext.prototype.emit = function (ev) {
|
||||
var ctx = this;
|
||||
var resource = this.resource;
|
||||
var origArgs = arguments;
|
||||
var args = Array.prototype.slice.call(arguments, 0)
|
||||
var success = arguments[arguments.length - 1];
|
||||
|
||||
assert(typeof success === 'function', 'ctx.emit requires a callback');
|
||||
args.pop();
|
||||
|
||||
var evName = ev;
|
||||
assert(typeof evName === 'string');
|
||||
args.shift();
|
||||
|
||||
var listeners = resource.listeners(evName);
|
||||
var listener;
|
||||
|
||||
// start
|
||||
next();
|
||||
|
||||
function next(err) {
|
||||
if(err) return fail(err);
|
||||
|
||||
try {
|
||||
if(listener = listeners.shift()) {
|
||||
var expectsCallback = listener._expects === args.length + 2;
|
||||
|
||||
// if a listener expects all the `args`
|
||||
// plus ctx, and a callback
|
||||
if(expectsCallback) {
|
||||
// include ctx (this) and pass next to continue
|
||||
listener.apply(resource, args.concat([this, next]));
|
||||
} else {
|
||||
// dont include the callback
|
||||
listener.apply(resource, args.concat([this]));
|
||||
// call next directly
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
success(done);
|
||||
}
|
||||
} catch(e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
function fail(err) {
|
||||
ctx.done(err);
|
||||
}
|
||||
|
||||
function done(err, result) {
|
||||
if(err) {
|
||||
return fail(err);
|
||||
}
|
||||
|
||||
ctx.emit.apply(ctx,
|
||||
['after:' + evName] // after event
|
||||
.concat(args) // include original arguments/data
|
||||
.concat([function () { // success callback
|
||||
ctx.done.call(ctx, err, result);
|
||||
}])
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
HttpContext.prototype.done = function (err, result) {
|
||||
if(err) {
|
||||
this.next(err);
|
||||
} else {
|
||||
this.res.send(result);
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ module.exports = Resource;
|
|||
|
||||
var asteroid = require('asteroid')
|
||||
, AsteroidModule = require('asteroid-module')
|
||||
, HttpContext = require('./http-context')
|
||||
, debug = require('debug')('asteroid:resource')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
|
@ -52,5 +53,31 @@ inherits(Resource, AsteroidModule);
|
|||
*/
|
||||
|
||||
Resource.prototype.mount = function (parent) {
|
||||
this.parent = parent;
|
||||
parent.use(this.options.path, this.app);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an http context bound to the current resource.
|
||||
*/
|
||||
|
||||
Resource.prototype.createContext = function (req, res, next) {
|
||||
return new HttpContext(this, req, res, next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override `on` to determine how many arguments an event handler expects.
|
||||
*/
|
||||
|
||||
Resource.prototype.on = function () {
|
||||
var fn = arguments[arguments.length - 1];
|
||||
|
||||
if(typeof fn === 'function') {
|
||||
// parse expected arguments from function src
|
||||
// fn.listener handles the wrapped function during `.once()`
|
||||
var src = (fn.listener || fn).toString();
|
||||
fn._expects = src.split('{')[0].split(',').length;
|
||||
}
|
||||
|
||||
AsteroidModule.prototype.on.apply(this, arguments);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
var HttpContext = require('../lib/http-context.js');
|
||||
var Resource = require('../lib/resource.js');
|
||||
|
||||
describe('HttpContext', function(){
|
||||
var ctx;
|
||||
var resource;
|
||||
|
||||
function createRequest() {
|
||||
return {};
|
||||
}
|
||||
|
||||
function createResponse() {
|
||||
return {};
|
||||
}
|
||||
|
||||
beforeEach(function(){
|
||||
resource = new Resource({path: '/foo'});
|
||||
ctx = new HttpContext(resource, createRequest(), createResponse());
|
||||
});
|
||||
|
||||
describe('.emit(ev, arg, done)', function(){
|
||||
it('should emit events on a resource', function(done) {
|
||||
var emitted, data;
|
||||
|
||||
resource.once('foo', function (arg, ctx, fn) {
|
||||
emitted = true;
|
||||
data = arg;
|
||||
fn();
|
||||
});
|
||||
|
||||
ctx.emit('foo', {bar: true}, function () {
|
||||
assert(emitted, 'event should be emitted');
|
||||
assert(data, 'arg should be supplied');
|
||||
assert(data.bar, 'arg should be the correct object');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple args', function(done) {
|
||||
var emitted, data;
|
||||
|
||||
resource.once('foo', function (arg1, arg2, arg3, arg4, ctx, fn) {
|
||||
emitted = true;
|
||||
assert(arg1 === 1, 'arg1 should equal 1');
|
||||
assert(arg2 === 2, 'arg2 should equal 2');
|
||||
assert(arg3 === 3, 'arg3 should equal 3');
|
||||
assert(arg4 === 4, 'arg4 should equal 4');
|
||||
fn();
|
||||
});
|
||||
|
||||
ctx.emit('foo', 1, 2, 3, 4, function (fn) {
|
||||
assert(emitted, 'event should be emitted');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have an after event', function(done) {
|
||||
var emitted, emittedAfter;
|
||||
|
||||
ctx.done = done;
|
||||
|
||||
resource.once('foo', function (arg1, arg2, arg3, arg4, ctx, fn) {
|
||||
emitted = true;
|
||||
fn();
|
||||
});
|
||||
|
||||
resource.once('after:foo', function (arg1, arg2, arg3, arg4, ctx, fn) {
|
||||
emittedAfter = true;
|
||||
fn();
|
||||
});
|
||||
|
||||
ctx.emit('foo', 1, 2, 3, 4, function (fn) {
|
||||
assert(emitted, 'event should be emitted');
|
||||
fn();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to emit synchronously', function(done) {
|
||||
var emitted, data;
|
||||
|
||||
resource.once('foo', function (arg1, arg2, arg3, arg4, ctx) {
|
||||
emitted = true;
|
||||
assert(arg1 === 1, 'arg1 should equal 1');
|
||||
assert(arg2 === 2, 'arg2 should equal 2');
|
||||
assert(arg3 === 3, 'arg3 should equal 3');
|
||||
assert(arg4 === 4, 'arg4 should equal 4');
|
||||
});
|
||||
|
||||
ctx.emit('foo', 1, 2, 3, 4, function () {
|
||||
assert(emitted);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.swp
|
||||
*.swo
|
||||
node_modules/
|
|
@ -0,0 +1,15 @@
|
|||
# store
|
||||
|
||||
## About
|
||||
|
||||
Provides an in memory data store. The backing persistence can be swapped out by providing a connection.
|
||||
|
||||
### Options
|
||||
|
||||
tbd
|
||||
|
||||
### Dependencies
|
||||
|
||||
#### connection (optional)
|
||||
|
||||
By default data will be persisted in memory. You can swap out the persistence layer by providing a connection.
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* A generated `Store` example...
|
||||
*
|
||||
* Examples should show a working module api
|
||||
* and be used in tests to continously check
|
||||
* they function as expected.
|
||||
*/
|
||||
|
||||
var Store = require('../');
|
||||
var store = Store.create();
|
||||
|
||||
store.myMethod();
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* store ~ public api
|
||||
*/
|
||||
|
||||
module.exports = require('./lib/store');
|
|
@ -0,0 +1,260 @@
|
|||
exports.initialize = function initializeSchema(schema, callback) {
|
||||
schema.adapter = new Memory();
|
||||
schema.adapter.connect(callback);
|
||||
};
|
||||
|
||||
function Memory(m) {
|
||||
if (m) {
|
||||
this.isTransaction = true;
|
||||
this.cache = m.cache;
|
||||
this.ids = m.ids;
|
||||
this._models = m._models;
|
||||
} else {
|
||||
this.isTransaction = false;
|
||||
// use asteroid cache, otherwise state will be reset during configuration
|
||||
this.cache = process.__asteroidCache.memoryStore || (process.__asteroidCache.memoryStore = {});
|
||||
this.ids = {};
|
||||
this._models = {};
|
||||
}
|
||||
}
|
||||
|
||||
Memory.prototype.connect = function(callback) {
|
||||
if (this.isTransaction) {
|
||||
this.onTransactionExec = callback;
|
||||
} else {
|
||||
process.nextTick(callback);
|
||||
}
|
||||
};
|
||||
|
||||
Memory.prototype.define = function defineModel(descr) {
|
||||
var m = descr.model.modelName;
|
||||
this._models[m] = descr;
|
||||
// allow reuse of data
|
||||
this.cache[m] = this.cache[m] || {};
|
||||
this.ids[m] = 1;
|
||||
};
|
||||
|
||||
Memory.prototype.create = function create(model, data, callback) {
|
||||
var id = data.id || this.ids[model]++;
|
||||
data.id = id;
|
||||
this.cache[model][id] = JSON.stringify(data);
|
||||
process.nextTick(function() {
|
||||
callback(null, id);
|
||||
});
|
||||
};
|
||||
|
||||
Memory.prototype.updateOrCreate = function (model, data, callback) {
|
||||
var mem = this;
|
||||
this.exists(model, data.id, function (err, exists) {
|
||||
if (exists) {
|
||||
mem.save(model, data, callback);
|
||||
} else {
|
||||
mem.create(model, data, function (err, id) {
|
||||
data.id = id;
|
||||
callback(err, data);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Memory.prototype.save = function save(model, data, callback) {
|
||||
this.cache[model][data.id] = JSON.stringify(data);
|
||||
process.nextTick(function () {
|
||||
callback(null, data);
|
||||
});
|
||||
};
|
||||
|
||||
Memory.prototype.exists = function exists(model, id, callback) {
|
||||
process.nextTick(function () {
|
||||
callback(null, this.cache[model].hasOwnProperty(id));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Memory.prototype.find = function find(model, id, callback) {
|
||||
process.nextTick(function () {
|
||||
callback(null, id in this.cache[model] && this.fromDb(model, this.cache[model][id]));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Memory.prototype.destroy = function destroy(model, id, callback) {
|
||||
delete this.cache[model][id];
|
||||
process.nextTick(callback);
|
||||
};
|
||||
|
||||
Memory.prototype.fromDb = function(model, data) {
|
||||
if (!data) return null;
|
||||
data = JSON.parse(data);
|
||||
var props = this._models[model].properties;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
var val = data[key];
|
||||
if (typeof val === 'undefined' || val === null) {
|
||||
return;
|
||||
}
|
||||
if (props[key]) {
|
||||
switch(props[key].type.name) {
|
||||
case 'Date':
|
||||
val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));
|
||||
break;
|
||||
case 'Boolean':
|
||||
val = new Boolean(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
data[key] = val;
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
Memory.prototype.all = function all(model, filter, callback) {
|
||||
var self = this;
|
||||
var nodes = [];
|
||||
var data = this.cache[model];
|
||||
var keys = Object.keys(data);
|
||||
var scanned = 0;
|
||||
|
||||
while(scanned < keys.length) {
|
||||
nodes.push(this.fromDb(model, data[keys[scanned]]));
|
||||
scanned++;
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
|
||||
// do we need some sorting?
|
||||
if (filter.order) {
|
||||
var props = this._models[model].properties;
|
||||
var orders = filter.order;
|
||||
if (typeof filter.order === "string") {
|
||||
orders = [filter.order];
|
||||
}
|
||||
orders.forEach(function (key, i) {
|
||||
var reverse = 1;
|
||||
var m = key.match(/\s+(A|DE)SC$/i);
|
||||
if (m) {
|
||||
key = key.replace(/\s+(A|DE)SC/i, '');
|
||||
if (m[1].toLowerCase() === 'de') reverse = -1;
|
||||
}
|
||||
orders[i] = {"key": key, "reverse": reverse};
|
||||
});
|
||||
nodes = nodes.sort(sorting.bind(orders));
|
||||
}
|
||||
|
||||
// do we need some filtration?
|
||||
if (filter.where) {
|
||||
nodes = nodes ? nodes.filter(applyFilter(filter)) : nodes;
|
||||
}
|
||||
|
||||
// skip
|
||||
if(filter.skip) {
|
||||
nodes = nodes.slice(filter.skip, nodes.length);
|
||||
}
|
||||
|
||||
if(filter.limit) {
|
||||
nodes = nodes.slice(0, filter.limit);
|
||||
}
|
||||
}
|
||||
|
||||
process.nextTick(function () {
|
||||
if (filter && filter.include) {
|
||||
self._models[model].model.include(nodes, filter.include, callback);
|
||||
} else {
|
||||
callback(null, nodes);
|
||||
}
|
||||
});
|
||||
|
||||
function sorting(a, b) {
|
||||
for (var i=0, l=this.length; i<l; i++) {
|
||||
if (a[this[i].key] > b[this[i].key]) {
|
||||
return 1*this[i].reverse;
|
||||
} else if (a[this[i].key] < b[this[i].key]) {
|
||||
return -1*this[i].reverse;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
function applyFilter(filter) {
|
||||
if (typeof filter.where === 'function') {
|
||||
return filter.where;
|
||||
}
|
||||
var keys = Object.keys(filter.where);
|
||||
return function (obj) {
|
||||
var pass = true;
|
||||
keys.forEach(function (key) {
|
||||
if (!test(filter.where[key], obj[key])) {
|
||||
pass = false;
|
||||
}
|
||||
});
|
||||
return pass;
|
||||
}
|
||||
|
||||
function test(example, value) {
|
||||
if (typeof value === 'string' && example && example.constructor.name === 'RegExp') {
|
||||
return value.match(example);
|
||||
}
|
||||
if (typeof example === 'undefined') return undefined;
|
||||
if (typeof value === 'undefined') return undefined;
|
||||
if (typeof example === 'object') {
|
||||
if (example.inq) {
|
||||
if (!value) return false;
|
||||
for (var i = 0; i < example.inq.length; i += 1) {
|
||||
if (example.inq[i] == value) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// not strict equality
|
||||
return (example !== null ? example.toString() : example) == (value !== null ? value.toString() : value);
|
||||
}
|
||||
}
|
||||
|
||||
Memory.prototype.destroyAll = function destroyAll(model, callback) {
|
||||
Object.keys(this.cache[model]).forEach(function (id) {
|
||||
delete this.cache[model][id];
|
||||
}.bind(this));
|
||||
this.cache[model] = {};
|
||||
process.nextTick(callback);
|
||||
};
|
||||
|
||||
Memory.prototype.count = function count(model, callback, where) {
|
||||
var cache = this.cache[model];
|
||||
var data = Object.keys(cache)
|
||||
if (where) {
|
||||
data = data.filter(function (id) {
|
||||
var ok = true;
|
||||
Object.keys(where).forEach(function (key) {
|
||||
if (JSON.parse(cache[id])[key] != where[key]) {
|
||||
ok = false;
|
||||
}
|
||||
});
|
||||
return ok;
|
||||
});
|
||||
}
|
||||
process.nextTick(function () {
|
||||
callback(null, data.length);
|
||||
});
|
||||
};
|
||||
|
||||
Memory.prototype.updateAttributes = function updateAttributes(model, id, data, cb) {
|
||||
data.id = id;
|
||||
|
||||
var base = JSON.parse(this.cache[model][id]);
|
||||
this.save(model, merge(base, data), cb);
|
||||
};
|
||||
|
||||
Memory.prototype.transaction = function () {
|
||||
return new Memory(this);
|
||||
};
|
||||
|
||||
Memory.prototype.exec = function(callback) {
|
||||
this.onTransactionExec();
|
||||
setTimeout(callback, 50);
|
||||
};
|
||||
|
||||
function merge(base, update) {
|
||||
if (!base) return update;
|
||||
Object.keys(update).forEach(function (key) {
|
||||
base[key] = update[key];
|
||||
});
|
||||
return base;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Expose `Store`.
|
||||
*/
|
||||
|
||||
module.exports = Store;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var AsteroidModule = require('asteroid-module')
|
||||
, Schema = require('jugglingdb').Schema
|
||||
, debug = require('debug')('store')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Create a new `Store` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Store}
|
||||
*/
|
||||
|
||||
function Store(options) {
|
||||
AsteroidModule.apply(this, arguments);
|
||||
|
||||
// throw an error if args are not supplied
|
||||
// assert(typeof options === 'object', 'Store requires an options object');
|
||||
|
||||
this.options = options;
|
||||
|
||||
debug('created with options', options);
|
||||
|
||||
var dependencies = this.dependencies;
|
||||
var connection = (dependencies && dependencies.connection) || {};
|
||||
var adapter = this.adapter = (connection && connection.adapter) || require('./memory');
|
||||
|
||||
this.schema = new Schema(adapter, connection.options);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "store",
|
||||
"description": "store",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "latest"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
var Store = require('../');
|
||||
|
||||
describe('Store', function(){
|
||||
var store;
|
||||
|
||||
beforeEach(function(){
|
||||
store = new Store;
|
||||
});
|
||||
|
||||
describe('.myMethod', function(){
|
||||
// example sync test
|
||||
it('should <description of behavior>', function() {
|
||||
store.myMethod();
|
||||
});
|
||||
|
||||
// example async test
|
||||
it('should <description of behavior>', function(done) {
|
||||
setTimeout(function () {
|
||||
store.myMethod();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* store test setup and support.
|
||||
*/
|
||||
|
||||
assert = require('assert');
|
Loading…
Reference in New Issue