From 7c2e73f53ab834c4dd0590abf1988be3957d3482 Mon Sep 17 00:00:00 2001 From: Ritchie Martori Date: Wed, 10 Apr 2013 10:55:13 -0700 Subject: [PATCH] Initial working store, model, connection, and collection --- example/todos/app.js | 5 +- example/todos/db/config.json | 10 +- example/todos/index.js | 1 + example/todos/todos/todos-model/config.json | 2 +- example/todos/todos/todos.js | 2 +- example/todos/users/config.json | 20 -- lib/middleware/configure.js | 2 + node_modules/.bin/_mocha | 1 + node_modules/.bin/mocha | 1 + node_modules/asteroid-module/README.md | 27 +- node_modules/collection/README.md | 26 +- node_modules/collection/lib/collection.js | 69 +++-- node_modules/connection/.gitignore | 10 + node_modules/connection/README.md | 13 + node_modules/connection/example/example.js | 12 + node_modules/connection/index.js | 5 + node_modules/connection/lib/connection.js | 36 +++ node_modules/connection/package.json | 14 + .../connection/test/connection.test.js | 24 ++ node_modules/connection/test/support.js | 5 + node_modules/model/.gitignore | 10 + node_modules/model/README.md | 37 +++ node_modules/model/example/example.js | 12 + node_modules/model/index.js | 5 + node_modules/model/lib/model.js | 72 +++++ node_modules/model/package.json | 14 + node_modules/model/test/model.test.js | 24 ++ node_modules/model/test/support.js | 5 + node_modules/resource/lib/http-context.js | 114 ++++++++ node_modules/resource/lib/resource.js | 27 ++ .../resource/test/http-context.test.js | 95 +++++++ node_modules/store/.gitignore | 10 + node_modules/store/README.md | 15 + node_modules/store/example/example.js | 12 + node_modules/store/index.js | 5 + node_modules/store/lib/memory.js | 260 ++++++++++++++++++ node_modules/store/lib/store.js | 40 +++ node_modules/store/package.json | 14 + node_modules/store/test/store.test.js | 24 ++ node_modules/store/test/support.js | 5 + 40 files changed, 1018 insertions(+), 67 deletions(-) create mode 100644 example/todos/index.js delete mode 100644 example/todos/users/config.json create mode 120000 node_modules/.bin/_mocha create mode 120000 node_modules/.bin/mocha create mode 100644 node_modules/connection/.gitignore create mode 100644 node_modules/connection/README.md create mode 100644 node_modules/connection/example/example.js create mode 100644 node_modules/connection/index.js create mode 100644 node_modules/connection/lib/connection.js create mode 100644 node_modules/connection/package.json create mode 100644 node_modules/connection/test/connection.test.js create mode 100644 node_modules/connection/test/support.js create mode 100644 node_modules/model/.gitignore create mode 100644 node_modules/model/README.md create mode 100644 node_modules/model/example/example.js create mode 100644 node_modules/model/index.js create mode 100644 node_modules/model/lib/model.js create mode 100644 node_modules/model/package.json create mode 100644 node_modules/model/test/model.test.js create mode 100644 node_modules/model/test/support.js create mode 100644 node_modules/resource/lib/http-context.js create mode 100644 node_modules/resource/test/http-context.test.js create mode 100644 node_modules/store/.gitignore create mode 100644 node_modules/store/README.md create mode 100644 node_modules/store/example/example.js create mode 100644 node_modules/store/index.js create mode 100644 node_modules/store/lib/memory.js create mode 100644 node_modules/store/lib/store.js create mode 100644 node_modules/store/package.json create mode 100644 node_modules/store/test/store.test.js create mode 100644 node_modules/store/test/support.js diff --git a/example/todos/app.js b/example/todos/app.js index 3bd67aa8..12523938 100644 --- a/example/todos/app.js +++ b/example/todos/app.js @@ -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); \ No newline at end of file +app.listen(3000); + +process.memory_store_cache = {}; \ No newline at end of file diff --git a/example/todos/db/config.json b/example/todos/db/config.json index a6fd9aa3..acf15b1a 100644 --- a/example/todos/db/config.json +++ b/example/todos/db/config.json @@ -1,11 +1,3 @@ { - "module": "data-store", - "options": { - "database": "asteroid-examples-todos", - "port": 27017, - "host": "127.0.0.1" - }, - "dependencies": { - "db": "memory" - } + "module": "store" } \ No newline at end of file diff --git a/example/todos/index.js b/example/todos/index.js new file mode 100644 index 00000000..dba64c03 --- /dev/null +++ b/example/todos/index.js @@ -0,0 +1 @@ +throw new Error('foo'); \ No newline at end of file diff --git a/example/todos/todos/todos-model/config.json b/example/todos/todos/todos-model/config.json index 1ffe0275..e82069f5 100644 --- a/example/todos/todos/todos-model/config.json +++ b/example/todos/todos/todos-model/config.json @@ -17,6 +17,6 @@ ] }, "dependencies": { - "data-store": "db" + "store": "db" } } \ No newline at end of file diff --git a/example/todos/todos/todos.js b/example/todos/todos/todos.js index fcf8582d..0018298e 100644 --- a/example/todos/todos/todos.js +++ b/example/todos/todos/todos.js @@ -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) { diff --git a/example/todos/users/config.json b/example/todos/users/config.json deleted file mode 100644 index 5ba02f38..00000000 --- a/example/todos/users/config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "module": "users-collection", - "options": { - "root": "/users", - "name": "users", - "properties": [ - { - "name": "email", - "type": "string" - }, - { - "name": "password", - "type": "password" - } - ] - }, - "dependencies": { - "store": "store" - } -} \ No newline at end of file diff --git a/lib/middleware/configure.js b/lib/middleware/configure.js index 340ebcf1..2a5fde55 100644 --- a/lib/middleware/configure.js +++ b/lib/middleware/configure.js @@ -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); diff --git a/node_modules/.bin/_mocha b/node_modules/.bin/_mocha new file mode 120000 index 00000000..f2a54ffd --- /dev/null +++ b/node_modules/.bin/_mocha @@ -0,0 +1 @@ +../mocha/bin/_mocha \ No newline at end of file diff --git a/node_modules/.bin/mocha b/node_modules/.bin/mocha new file mode 120000 index 00000000..43c668d9 --- /dev/null +++ b/node_modules/.bin/mocha @@ -0,0 +1 @@ +../mocha/bin/mocha \ No newline at end of file diff --git a/node_modules/asteroid-module/README.md b/node_modules/asteroid-module/README.md index c9830506..9bcc6750 100644 --- a/node_modules/asteroid-module/README.md +++ b/node_modules/asteroid-module/README.md @@ -1,13 +1,28 @@ # asteroid-module v0.0.1 -## Install +## About + +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. - slnode install asteroid-module - ## Example - var AsteroidModule = require('asteroid-module'); - var asteroidModule = AsteroidModule.create(); +See [resource](../resource) for an example asteroid module. - asteroidModule.myMethod(); \ No newline at end of file +## 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. \ No newline at end of file diff --git a/node_modules/collection/README.md b/node_modules/collection/README.md index f5493391..8721c254 100644 --- a/node_modules/collection/README.md +++ b/node_modules/collection/README.md @@ -1,13 +1,21 @@ # collection -v0.0.1 - -## Install - - slnode install collection -## Example +## About - var Collection = require('collection'); - var collection = Collection.create(); +A `Collection` inherits from the [resource](../resource) class. It provides HTTP access to a supplied [model](../model) instance. - collection.myMethod(); \ No newline at end of file +## Config + +Supports all configuration of a [resource](../resource). + +### Options + +#### name (optional) + +If defined, overrides the default name (based on the path). + +### Dependencies + +#### model + +Requires a path to an existing model instance. diff --git a/node_modules/collection/lib/collection.js b/node_modules/collection/lib/collection.js index 65236f00..93ef6466 100644 --- a/node_modules/collection/lib/collection.js +++ b/node_modules/collection/lib/collection.js @@ -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); }); }); } \ No newline at end of file diff --git a/node_modules/connection/.gitignore b/node_modules/connection/.gitignore new file mode 100644 index 00000000..6af74074 --- /dev/null +++ b/node_modules/connection/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.swp +*.swo +node_modules/ diff --git a/node_modules/connection/README.md b/node_modules/connection/README.md new file mode 100644 index 00000000..6978cb7b --- /dev/null +++ b/node_modules/connection/README.md @@ -0,0 +1,13 @@ +# connection + +## About + +Provides a base class for implementing access to a persistence layer. + +### Options + +#### hostname +#### port +#### database +#### username +#### password \ No newline at end of file diff --git a/node_modules/connection/example/example.js b/node_modules/connection/example/example.js new file mode 100644 index 00000000..8fd23202 --- /dev/null +++ b/node_modules/connection/example/example.js @@ -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(); \ No newline at end of file diff --git a/node_modules/connection/index.js b/node_modules/connection/index.js new file mode 100644 index 00000000..64d2c60a --- /dev/null +++ b/node_modules/connection/index.js @@ -0,0 +1,5 @@ +/** + * connection ~ public api + */ + +module.exports = require('./lib/connection'); \ No newline at end of file diff --git a/node_modules/connection/lib/connection.js b/node_modules/connection/lib/connection.js new file mode 100644 index 00000000..56566004 --- /dev/null +++ b/node_modules/connection/lib/connection.js @@ -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); \ No newline at end of file diff --git a/node_modules/connection/package.json b/node_modules/connection/package.json new file mode 100644 index 00000000..73d588a9 --- /dev/null +++ b/node_modules/connection/package.json @@ -0,0 +1,14 @@ +{ + "name": "connection", + "description": "connection", + "version": "0.0.1", + "scripts": { + "test": "mocha" + }, + "dependencies": { + "debug": "latest" + }, + "devDependencies": { + "mocha": "latest" + } +} \ No newline at end of file diff --git a/node_modules/connection/test/connection.test.js b/node_modules/connection/test/connection.test.js new file mode 100644 index 00000000..7bc0aad6 --- /dev/null +++ b/node_modules/connection/test/connection.test.js @@ -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 ', function() { + connection.myMethod(); + }); + + // example async test + it('should ', function(done) { + setTimeout(function () { + connection.myMethod(); + done(); + }, 0); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/connection/test/support.js b/node_modules/connection/test/support.js new file mode 100644 index 00000000..4d8c7d8c --- /dev/null +++ b/node_modules/connection/test/support.js @@ -0,0 +1,5 @@ +/** + * connection test setup and support. + */ + +assert = require('assert'); \ No newline at end of file diff --git a/node_modules/model/.gitignore b/node_modules/model/.gitignore new file mode 100644 index 00000000..6af74074 --- /dev/null +++ b/node_modules/model/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.swp +*.swo +node_modules/ diff --git a/node_modules/model/README.md b/node_modules/model/README.md new file mode 100644 index 00000000..982ad820 --- /dev/null +++ b/node_modules/model/README.md @@ -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. \ No newline at end of file diff --git a/node_modules/model/example/example.js b/node_modules/model/example/example.js new file mode 100644 index 00000000..a3fbffea --- /dev/null +++ b/node_modules/model/example/example.js @@ -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(); \ No newline at end of file diff --git a/node_modules/model/index.js b/node_modules/model/index.js new file mode 100644 index 00000000..61e079e5 --- /dev/null +++ b/node_modules/model/index.js @@ -0,0 +1,5 @@ +/** + * model ~ public api + */ + +module.exports = require('./lib/model'); \ No newline at end of file diff --git a/node_modules/model/lib/model.js b/node_modules/model/lib/model.js new file mode 100644 index 00000000..ce9a3b5a --- /dev/null +++ b/node_modules/model/lib/model.js @@ -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; +}, {}); diff --git a/node_modules/model/package.json b/node_modules/model/package.json new file mode 100644 index 00000000..d21c693a --- /dev/null +++ b/node_modules/model/package.json @@ -0,0 +1,14 @@ +{ + "name": "model", + "description": "model", + "version": "0.0.1", + "scripts": { + "test": "mocha" + }, + "dependencies": { + "debug": "latest" + }, + "devDependencies": { + "mocha": "latest" + } +} \ No newline at end of file diff --git a/node_modules/model/test/model.test.js b/node_modules/model/test/model.test.js new file mode 100644 index 00000000..928953e2 --- /dev/null +++ b/node_modules/model/test/model.test.js @@ -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 ', function() { + model.myMethod(); + }); + + // example async test + it('should ', function(done) { + setTimeout(function () { + model.myMethod(); + done(); + }, 0); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/model/test/support.js b/node_modules/model/test/support.js new file mode 100644 index 00000000..e8db5346 --- /dev/null +++ b/node_modules/model/test/support.js @@ -0,0 +1,5 @@ +/** + * model test setup and support. + */ + +assert = require('assert'); \ No newline at end of file diff --git a/node_modules/resource/lib/http-context.js b/node_modules/resource/lib/http-context.js new file mode 100644 index 00000000..b1c72c7f --- /dev/null +++ b/node_modules/resource/lib/http-context.js @@ -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); + } +} \ No newline at end of file diff --git a/node_modules/resource/lib/resource.js b/node_modules/resource/lib/resource.js index 7d9e3065..23d5b3fe 100644 --- a/node_modules/resource/lib/resource.js +++ b/node_modules/resource/lib/resource.js @@ -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); } \ No newline at end of file diff --git a/node_modules/resource/test/http-context.test.js b/node_modules/resource/test/http-context.test.js new file mode 100644 index 00000000..6cadac3e --- /dev/null +++ b/node_modules/resource/test/http-context.test.js @@ -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(); + }); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/store/.gitignore b/node_modules/store/.gitignore new file mode 100644 index 00000000..6af74074 --- /dev/null +++ b/node_modules/store/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.swp +*.swo +node_modules/ diff --git a/node_modules/store/README.md b/node_modules/store/README.md new file mode 100644 index 00000000..5cebec3b --- /dev/null +++ b/node_modules/store/README.md @@ -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. \ No newline at end of file diff --git a/node_modules/store/example/example.js b/node_modules/store/example/example.js new file mode 100644 index 00000000..c971bb8c --- /dev/null +++ b/node_modules/store/example/example.js @@ -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(); \ No newline at end of file diff --git a/node_modules/store/index.js b/node_modules/store/index.js new file mode 100644 index 00000000..668bf7dc --- /dev/null +++ b/node_modules/store/index.js @@ -0,0 +1,5 @@ +/** + * store ~ public api + */ + +module.exports = require('./lib/store'); \ No newline at end of file diff --git a/node_modules/store/lib/memory.js b/node_modules/store/lib/memory.js new file mode 100644 index 00000000..1061d932 --- /dev/null +++ b/node_modules/store/lib/memory.js @@ -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 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; +} diff --git a/node_modules/store/lib/store.js b/node_modules/store/lib/store.js new file mode 100644 index 00000000..2d9d8501 --- /dev/null +++ b/node_modules/store/lib/store.js @@ -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); +} \ No newline at end of file diff --git a/node_modules/store/package.json b/node_modules/store/package.json new file mode 100644 index 00000000..0e33ec07 --- /dev/null +++ b/node_modules/store/package.json @@ -0,0 +1,14 @@ +{ + "name": "store", + "description": "store", + "version": "0.0.1", + "scripts": { + "test": "mocha" + }, + "dependencies": { + "debug": "latest" + }, + "devDependencies": { + "mocha": "latest" + } +} \ No newline at end of file diff --git a/node_modules/store/test/store.test.js b/node_modules/store/test/store.test.js new file mode 100644 index 00000000..4a379a90 --- /dev/null +++ b/node_modules/store/test/store.test.js @@ -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 ', function() { + store.myMethod(); + }); + + // example async test + it('should ', function(done) { + setTimeout(function () { + store.myMethod(); + done(); + }, 0); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/store/test/support.js b/node_modules/store/test/support.js new file mode 100644 index 00000000..e54878be --- /dev/null +++ b/node_modules/store/test/support.js @@ -0,0 +1,5 @@ +/** + * store test setup and support. + */ + +assert = require('assert'); \ No newline at end of file