resourceful
Version:
an isomorphic Resource engine for JavaScript
291 lines (244 loc) • 7.15 kB
JavaScript
var events = require('events'),
common = require('./common'),
init = require('./init');
var resourceful = exports;
resourceful.env = 'development';
resourceful.autoMigrate = true;
resourceful.resources = {};
resourceful.Resource = require('./resource').Resource;
resourceful.engines = require('./engines');
resourceful.connection = new resourceful.engines.Memory();
resourceful.deferredRelationships = {};
//
// Select a storage engine
//
resourceful.use = function (engine, options) {
if (typeof(engine) === "string") {
engine = common.capitalize(engine);
if (resourceful.engines[engine]) {
this.engine = resourceful.engines[engine];
}
else {
throw new Error("unrecognised engine: " + engine);
}
}
else if (typeof engine === 'function') {
this.engine = engine;
}
else {
throw new Error("invalid engine ");
}
this.key = this.engine.key || 'id';
this.connect(options || {});
return this;
};
//
// Connect to the resource's storage engine, or one specified by the URI protocol
//
resourceful.connect = function (/* [uri], [port], [options] */) {
var args = Array.prototype.slice.call(arguments),
options = {},
protocol,
engine,
m;
args.forEach(function (a) {
switch (typeof(a)) {
case 'number': options.port = parseInt(a, 10); break;
case 'string': options.uri = a; break;
case 'object': options = a; break;
}
});
// Extract the optional 'protocol' if we haven't already selected an engine
// ex: "couchdb://127.0.0.1" would have "couchdb" as it's protocol.
if (!this.engine) {
if (m = options.uri && options.uri.match(/^([a-z]+):\/\//)) {
protocol = common.capitalize(m[1]);
if (resourceful.engines[protocol]) {
engine = resourceful.engines[protocol];
}
else {
throw new Error("unrecognised engine: " + engine);
}
}
}
else {
engine = this.engine || resourceful.engine;
}
this.connection = new(engine)(options);
return this;
};
//
// Default Factory for creating new resources.
//
resourceful.define = function (name, definition) {
if ((typeof name === "function" || typeof name === 'object') && !definition) {
definition = name;
name = definition.name;
}
if (name) {
name = common.capitalize(name);
}
else {
// Use the next available resource name
for (var i = 0; !name || (name in resourceful.resources); i++) {
name = 'Resource' + i;
}
}
var Factory = function Factory (attrs) {
var self = this;
resourceful.Resource.call(this);
Object.defineProperty(this, '_properties', {
value: {},
enumerable: false
});
Object.keys(Factory.properties).forEach(function (k) {
self._properties[k] = Factory.properties[k]['default'];
});
this._properties.resource = name;
if (attrs) {
Object.keys(attrs).forEach(function (k) {
self._properties[k] = attrs[k];
});
}
//
// Resource.method definitions
//
// TODO: add ID lookup here for instance methods
for (var m in Factory) {
if(typeof Factory[m] !== 'undefined' && Factory[m].type === "method") {
if(typeof this[m] !== 'undefined') {
throw new Error(m + ' is a reserved word on the Resource instance');
}
this[m] = Factory[m];
this[m].type = "method" ;
this[m].required = false;
}
}
Object.keys(this._properties).forEach(function (k) {
resourceful.defineProperty(self, k, Factory.schema.properties[k]);
});
};
//
// Setup inheritance
//
Factory.__proto__ = resourceful.Resource;
Factory.prototype.__proto__ = resourceful.Resource.prototype;
//
// Setup intelligent defaults for various properties
// in the Resource Factory.
//
Factory.resource = name;
Factory.lowerResource = common.lowerize(name);
Factory.views = {};
Factory._children = [];
Factory._parents = [];
Factory.schema = {
name: name,
properties: {
id: {
type: 'any',
required: false
}
},
links: []
};
Factory.hooks = {
before: {},
after: {}
};
['get', 'save', 'update', 'create', 'destroy'].forEach(function (m) {
Factory.hooks.before[m] = [];
Factory.hooks.after[m] = [];
});
Factory.emitter = new events.EventEmitter();
Object.keys(events.EventEmitter.prototype).forEach(function (k) {
Factory[k] = function () {
return Factory.emitter[k].apply(Factory.emitter, arguments);
};
});
Factory.on('error', function () {
//
// TODO: Logging
//
});
if (typeof definition === 'object') {
// Use definition as schema
Factory.define(definition);
} else {
(definition || function () {}).call(Factory);
}
Factory.init();
// Add this resource to the set of resources resourceful knows about
resourceful.register(name, Factory);
//
// Check to see if any defferred relationships from previously loaded resources relate to this resource
//
if(typeof resourceful.deferredRelationships[Factory.resource] !== 'undefined') {
//
// For every deferredRelationship we find to our current resource, call it!
//
resourceful.deferredRelationships[Factory.resource].forEach(function(r){
resourceful.resources[r].parent(Factory.resource);
});
}
return Factory;
};
resourceful.defineProperty = function (obj, property, schema) {
schema = schema || {};
// Call setter if needed
if (schema.set) {
obj.writeProperty(property, obj.readProperty(property), schema.set);
}
// Sanitize defaults and per-creation properties
if (schema.sanitize) {
var val = obj.readProperty(property);
if (val !== undefined) {
obj.writeProperty(property, schema.sanitize(val));
}
}
Object.defineProperty(obj, property, {
get: function () {
return this.readProperty(property, schema.get);
},
set: schema.sanitize ? function (val) {
return this.writeProperty(property, schema.sanitize(val), schema.set);
} : function (val) {
return this.writeProperty(property, val, schema.set);
},
enumerable: true
});
if (typeof obj[property] === 'undefined') {
obj[property] = init(obj, property, schema);
}
};
//
// Adds the Factory to the set of known resources
//
resourceful.register = function (name, Factory) {
return this.resources[name] = Factory;
};
//
// Removes the name from the set of known resources;
//
resourceful.unregister = function (name) {
delete this.resources[name];
};
resourceful.instantiate = function (obj) {
var instance, Factory, id;
Factory = resourceful.resources[this.resource];
id = obj[this.key];
if (id && this.connection.cache.has(id)) {
obj = this.connection.cache.get(id);
}
if (Factory) {
// Don't instantiate an already instantiated object
if (obj instanceof Factory) { return obj; }
else {
instance = new Factory(obj);
instance.isNewRecord = false;
return instance;
}
} else {
throw new Error("unrecognised resource '" + obj.resource + "'");
}
};