Permalink
Browse files

added model.index

  • Loading branch information...
1 parent 4a7ed1c commit 2a845ee4c6642d94106b5765fe752e596c3000b8 @pgte committed Jan 31, 2011
View
3 docs/web/app.js
@@ -29,6 +29,9 @@ app.get('/', function(req, res) {
app.get('/api', function(req, res) {
res.render('api/index');
});
+app.get('/api/odm', function(req, res) {
+ res.render('api/odm');
+});
app.get('/api/database', function(req, res) {
res.render('api/database');
});
View
9 docs/web/static/stylesheets/benchmarks.css
@@ -31,11 +31,20 @@ div#header {
div#header h1 {
background-color: #000;
margin-bottom: 0;
+ position: relative;
}
div#header h1 a {
color: #f0f0f0;
text-decoration: none;
}
+div#header h1 .js {
+ position: absolute;
+ font-size: 35%;
+ left: 275px;
+ bottom: 10px;
+ color: #ffa126;
+ text-shadow: rgba(255, 255, 255, 0.4) 1px 1px 0px;
+}
div#header p {
background-color: #000;
margin-bottom: 0;
View
1 docs/web/views/api/index.jade
@@ -3,6 +3,7 @@ p.note Everything here is experimental, and the API is no exception, so expect t
#api
h2 Table of Contents
ul
+ li <a href="/api/odm">Object Document Model (ODM)</a> - documents as high-level objects with validations
li <a href="/api/database">Database</a> - open a database and manupulate key maps
li <a href="/api/key_map">KeyMap</a> - put, get and find records. Index manipulation. Streams.
li <a href="/api/find">Find</a> - Use KeyMap finder chainable API to find records
View
117 docs/web/views/api/odm.jade
@@ -0,0 +1,117 @@
+p.note Everything here is experimental, and the API is no exception, so expect things to change in future releases.
+
+#api
+ h1 Database
+ h2 Alfred.open (path_to_database, [options, ] callback)
+ p Opens a new or an existing database that resides in <i>path_to_database</i>.
+ p
+ ul
+ li <b>path_to_database</b>: directory where the database files (will) reside. Must already exist.
+ li <b>callback</b> (err, db): invoked when there is an error or the database is open. Use db to access / create / remove key maps.
+ li <b>options:</b>
+ ul
+ li <b>meta_compact_interval</b>: default average compact interval for the metadata key map, in miliseconds. Defaults to 1 hour (1000 * 60 * 60).
+ li <b>replication_master</b>: true if this database is going to act as a replication master. Defaults to false.
+ li <b>replication_port</b>: replication server port for this database. Defaults to 5293.
+ li <b>replication_max_file_size_kb</b>: maximum file size for replication temporary files on master. Defaults to 10000 KBytes.
+ li <b>replication_max_keep_ms</b>: maximum time a replication temporary file should be kept around. Defaults to 15 days.
+ h3 Example:
+ pre
+ code
+ | var Alfred = require('alfred');
+ | Alfred.open('path/to/database/dir', function(err, db) {
+ | if (err) { throw err; }
+ | // now I have the db object to do stuff
+ | });
+
+ h1 Model
+
+ h2 database.define (modelName, schema, options)
+ p Define a new model.
+ p
+ ul
+ li <b>modelName</b>: the model name. Ex.: "User".
+ li <b>schema</b>: See <a href="/api/dom/schema">Schema</a>.
+ li <b>options</b>: Optional. The same options as db.attach:
+ ul
+ li <b>buffered</b>: if <i>true</i>, writes are buffered (flushes are scheduled every second after the last one by default). If <i>false</i>, key_map.put only callsback when data is written to disk. Defaults to <i>true</i>.
+ li <b>flush_interval</b>: This determines the frequency of flushes. A flush is scheduled <i>flush_interval</i> miliseconds after the last one finished. In miliseconds. Defaults to 1000 (1 second).
+ li <b>type</b>: can be 'cached_key_map' or 'indexed_key_map'. Defaults to 'cached_key_map'. ('cached_key_map' is also indexed)
+ li <b>compact_interval</b>: average compact interval, in miliseconds. Defaults to 1 hour (1000 * 60 * 60).
+ li <b>cache_slots</b>: Maximum number of objects to cache (only valid if type is 'cached_key_map'). Defaults to 1000.
+ p <b>Returns</b>: a model instance
+
+ h2 Model.get(id, callback)
+ p Get a model by ID
+ p Example:
+ pre
+ code
+ | var User = db.define('User', schema);
+ | User.get(id, function(user) {
+ | console.log(user.inspect());
+ | });
+
+ h2 Model.new([properties])
+ p Create a new (unsaved) document.
+ p Example:
+ pre
+ code
+ | var User = db.define('User', schema);
+ | var user = User.new({name: 'Pedro Teixeira', role: 'admin'});
+
+ h2 Model.destroy(id, callback)
+ p Destroy the model by id.
+ p
+ ul
+ li <b>callback</b>: called when the model is destroyed.
+ p Example:
+ pre
+ code
+ | var User = db.define('User', schema);
+ | var id = '123abc';
+ | var user = User.destroy(id);
+
+ h2 Model.find(spec)
+ p Starts a finder. Implements the same chainable API as the <a href="/api/find">KeyMap finder</a>, but with documents instead of raw objects.
+ p Example:
+ p
+ pre
+ code
+ | var User = db.define('User', schema);
+ | User.find({age: {$gt: 18, $lte: 40}}).all(function(users) {
+ | console.log('Found ' + users.length + ' users');
+ | }).or({sex: {$eq: 'f'}});
+
+ h1 Document
+
+ h2 document.save(callback)
+ p Saves the document
+ p
+ ul
+ li <b>callback</b> ([validationErrors]): called if validation errors detected or when saved.
+ p Example:
+ pre
+ code
+ | var User = db.define('User', schema);
+ | var user = User.new({name: 'Pedro Teixeira', role: 'admin'});
+ | user.save(function(calidationErrors) {
+ | if (errors) {
+ | throw new Exception('validation errors found.');
+ | }
+ | console.log('User saved');
+ | });
+
+ h2 document.destroy(callback)
+ p Destroys the document
+ p
+ ul
+ li <b>callback</b>: called when the model is destroyed.
+ p Example:
+ pre
+ code
+ | var User = db.define('User', schema);
+ | var id = 'abc123';
+ | var user = User.get(id);
+ | user.destroy(function() {
+ | console.log('user with id ' + id + ' destroyed');
+ | });
View
42 docs/web/views/content/index.jade
@@ -1,11 +1,12 @@
#intro
- p Alfred is a fast <b>in-process key-value store</b> for <b><a href="http://nodejs.org">node.js</a></b>.
- p.note Note that Alfred is still very young, so you shouldn't use it on live systems just yet.
+ p Alfred.js is a fast <b>in-process key-value store</b> for <b><a href="http://nodejs.org">node.js</a></b>.
+ p.note Note that Alfred.js is still very young, so you shouldn't use it on live systems just yet.
div.part
h2 What does it do?
section.explanation
p Alfred supports:
ul
+ li ODM (Object Document Model)
li multiple key-value maps in one database
li atomic operations on one record
li finder streams
@@ -21,7 +22,7 @@
a(href='/features') More...
.clearboth
div.part
- h2 How can I install Alfred?
+ h2 How can I install Alfred.js?
section.explanation
p Install via npm:
p.code
@@ -32,6 +33,35 @@
div.part
h2 Examples:
section.explanation
+ p Using the ODM:
+ pre
+ code
+ | var Alfred = require('alfred');
+ | // Open database
+ | Alfred.open('path/to/db', function(err, db) {
+ | if (err) { throw err; }
+ |
+ | // define User model and its properties
+ | var User = db.define('User', {
+ | indexes: [{name: 'age',
+ | fn: function(user) { return user.age; }]
+ | });
+ | User.property('name', 'string', {
+ | maxLength: 100
+ | });
+ | User.property('active', 'boolean');
+ |
+ | // get user by id
+ | User.get(id, function(user) {
+ | console.log(user.inspect());
+ | };
+ |
+ | // find users
+ | User.find({age: {$gt: 18}}).all(function(users) {
+ | console.log('Found ' + users.length + ' users with more than 18 years') ;
+ | });
+ | });
+ p Raw:
pre
code
| var Alfred = require('alfred');
@@ -49,8 +79,8 @@
div.part
h2 How does it work?
section.explanation
- p Alfred stores data on append-only files, which is good for maintaining a consistent state (even when the writes are interrupted).
+ p Alfred.js stores data on append-only files, which is good for maintaining a consistent state (even when the writes are interrupted).
p Writes are fast because many writes can occurr in parallel.
p Reads are fast because indexes into files are kept in memory.
- p Alfred is generally fast also because you don't have any inter-process communication overhead.
- p Alfred lets you (optionally) use the power of javascript to filter your data set.
+ p Alfred.js is generally fast also because you don't have any inter-process communication overhead.
+ p Alfred.js lets you (optionally) use the power of javascript to filter your data set.
View
2 docs/web/views/layout.jade
@@ -9,7 +9,7 @@ html
body
#header
h1
- a(href='/') Alfred
+ a(href='/') Alfred<span class="js">.js</span>
p
i Node is your mansion, Alfred is your butler
#menu
View
1 index.js
@@ -0,0 +1 @@
+module.exports = require('./lib/alfred');
View
185 lib/alfred/meta/model/model.js
@@ -1,5 +1,6 @@
var registrar = require('./registrar'),
promise = require('../../util/promise'),
+ Promises = require('../../util/promises'),
definers = require('./schema').definers,
Finder = require('../finder'),
mixin = require('../../util/mixin'),
@@ -19,32 +20,39 @@ var Model = function(database, name, schema, options) {
throw new Error('No database connection defined');
}
- this.initiatedPromise = promise(function(callback) {
+ this.collPromise = promise(function(cb) {
database.ensure(self.name, options, function(err, coll) {
- if (err) { callback(err); return; }
+ if (err) { cb(err); return; }
self.collection = coll;
- if (self.schema.indexes) {
- var indexesLeft = self.schema.indexes.length;
- if (indexesLeft === 0) {
- callback(null);
- return;
- }
- self.schema.indexes.forEach(function(indexDef) {
- coll.ensureIndex(indexDef.name, indexDef.options || {ordered: true}, indexDef.fn, function(err) {
- if (err) { callback(err); return; }
+ cb(null);
+ });
+ }, function(err) {
+ self.database.emit('error', err);
+ });
+ this.promises = Promises.new();
+ this.promises.add(this.collPromise);
+
+ if (self.schema.indexes && self.schema.indexes.length > 0) {
+ this.collPromise(function() {
+ self.schema.indexes.forEach(function(indexDef) {
+ self.promises.add(promise(function(cb) {
+ self.collection.ensureIndex(indexDef.name, indexDef.options || {ordered: true}, indexDef.fn, function(err) {
+ if (err) { cb(err); return; }
if (-- indexesLeft === 0) {
- callback(null);
+ cb(null);
}
});
- });
- } else {
- callback(null);
- }
-
+ }, function(err) {
+ self.database.emit('error', err);
+ }));
+ });
});
- }, function(err) {
- self.database.emit('error', err);
+ }
+
+ this.promises.done(function() {
+ self._promiseDone();
});
+
};
module.exports.define = function(database, name, schema, options) {
@@ -55,61 +63,105 @@ Model.prototype._error = function(err) {
this.database.emit('error', err);
};
+Model.prototype._swap = function(fa, fb) {
+ var f = this[fb];
+ this[fb] = this[fa];
+ this["_waiting_" + fb] = f;
+};
+
+Model.prototype._promiseDone = function() {
+ if (!this._promiseReallyDone) {
+ this._promiseReallyDone = true;
+ this._swap('_get', 'get');
+ this._swap('_put', 'put');
+ this._swap('_atomic', 'atomic');
+ this._swap('_delete', 'delete');
+ }
+};
+
+Model.prototype._promiseUndone = function() {
+ if (this._promiseReallyDone) {
+ delete this._promiseReallyDone;
+ this['get'] = this['_waiting_get'];
+ this['put'] = this['_waiting_put'];
+ this['atomic'] = this['_waiting_atomic'];
+ this['delete'] = this['_waiting_delete'];
+ }
+};
+
+Model.prototype._get = function(id, callback) {
+ var self = this;
+ self.collection.get(id, function(err, value) {
+ if (err) { self._error(err); return; }
+ if (value === null) {
+ callback(null); return;
+ }
+ var doc = self.instantiate(value);
+ doc.id = id;
+ doc.doc.id = id;
+ callback(doc);
+ });
+};
+
Model.prototype.get = function(id, callback) {
var self = this;
- this.initiatedPromise(function() {
- self.collection.get(id, function(err, value) {
- if (err) { self._error(err); return; }
- if (value === null) {
- callback(null); return;
- }
- var doc = self.instantiate(value);
- doc.id = id;
- doc.doc.id = id;
- callback(doc);
- });
+ this.promises.done(function() {
+ self._get(id, callback);
});
};
+Model.prototype._put = function(id, value, callback) {
+ var self = this;
+ delete value.id;
+ self.collection.put(id, value, function(err) {
+ if (err) { self._error(err); return; }
+ callback();
+ })
+};
+
Model.prototype.put = function(id, value, callback) {
var self = this;
- this.initiatedPromise(function() {
- delete value.id;
- self.collection.put(id, value, function(err) {
- if (err) { self._error(err); return; }
- callback();
- })
+ this.promises.done(function() {
+ self._put(id, value, callback);
+ });
+};
+
+Model.prototype._atomic = function(id, doc_callback, final_callback) {
+ var self = this;
+ self.collection.atomic(id, function(err, value) {
+ if (err) { self._error(err); return; }
+ var doc = self.instantiate(value);
+ var retDoc = doc_callback(doc);
+ if (retDoc.isValid()) {
+ return retDoc.doc;
+ } else {
+ final_callback(retDoc.errors);
+ return null;
+ }
+ }, function(err) {
+ if (err) { self._error(err); return; }
+ final_callback();
});
};
Model.prototype.atomic = function(id, doc_callback, final_callback) {
var self = this;
- this.initiatedPromise(function() {
- self.collection.atomic(id, function(err, value) {
- if (err) { self._error(err); return; }
- var doc = self.instantiate(value);
- var retDoc = doc_callback(doc);
- if (retDoc.isValid()) {
- return retDoc.doc;
- } else {
- final_callback(retDoc.errors);
- return null;
- }
- }, function(err) {
- if (err) { self._error(err); return; }
- final_callback();
- });
+ this.promises.done(function() {
+ self._atomic(id, doc_callback, final_callback);
+ });
+};
+
+Model.prototype._delete = function(id, callback) {
+ this.collection.destroy(id, function(err) {
+ if (err) { self._error(err); return; }
+ callback();
});
-
};
Model.prototype.delete = function(id, callback) {
var self = this;
- this.initiatedPromise(function() {
- self.collection.destroy(id, function(err) {
- if (err) { self._error(err); return; }
- callback();
- });
+ this.promises.done(function() {
+ self.delete(id, callback);
});
};
@@ -121,7 +173,7 @@ Model.prototype.find = function(query) {
old_all = finder.all;
finder.execute = function(callback) {
- self.initiatedPromise(function() {
+ self.promises.done(function() {
finder.setKeyMap(self.collection);
old_execute.call(finder, function(err, key, value) {
if (err) { self._error(err); return; }
@@ -134,7 +186,7 @@ Model.prototype.find = function(query) {
};
finder.all = function(callback) {
- self.initiatedPromise(function() {
+ self.promises.done(function() {
finder.setKeyMap(self.collection);
old_all.call(finder, function(err, records) {
if (err) { self._error(err); return; }
@@ -151,7 +203,7 @@ Model.prototype.find = function(query) {
};
finder.stream = function(callback) {
- self.initiatedPromise(function() {
+ self.promises.done(function() {
finder.setKeyMap(self.collection);
old_stream.call(finder, function(stream) {
@@ -211,4 +263,17 @@ Model.prototype.property = function(name, typeOrSchema, schema) {
});
return definer;
+};
+
+Model.prototype.index = function(name, fn, options) {
+ var self = this;
+
+ self._promiseUndone();
+ self.promises.done(function() {
+ self.promises.add(promise(function(cb) {
+ self.collection.ensureIndex(name, options || {ordered: true}, fn, cb);
+ }, function(err) {
+ self.database.emit('error', err);
+ }));
+ });
};
View
7 lib/alfred/util/promise.js
@@ -1,14 +1,15 @@
module.exports = function(action, error_callback) {
- var done = false;
+ var done = false, cb;
var queue = [];
action(function(err) {
if (err) { error_callback(err); return; }
done = true;
- queue.forEach(function(cb) {
+ while (queue.length > 0) {
+ cb = queue.splice(0, 1)[0];
cb();
- });
+ }
delete queue;
});
View
30 lib/alfred/util/promises.js
@@ -0,0 +1,30 @@
+var Promises = function() {
+ this.pendingCount = 0;
+ this.doneCallbacks = [];
+};
+
+module.exports.new = function() {
+ return new Promises();
+};
+
+Promises.prototype.add = function(promise) {
+ var self = this, cb;
+ self.pendingCount ++;
+ promise(function() {
+ self.pendingCount --;
+ if (self.pendingCount === 0) {
+ while (self.doneCallbacks.length > 0) {
+ cb = self.doneCallbacks.splice(0, 1)[0];
+ cb();
+ }
+ }
+ });
+};
+
+Promises.prototype.done = function(callback) {
+ if (this.pendingCount === 0) {
+ callback();
+ } else {
+ this.doneCallbacks.push(callback);
+ }
+};
View
14 test/model/test_find.js
@@ -39,21 +39,15 @@ module.exports.run = function(next) {
next(err);
});
- var User = db.define('User', {
- indexes: [
- {
- name: 'age',
- fn: function(user) {
- return user.age;
- }
- }
- ]
- });
+ var User = db.define('User');
User.property('name');
User.property('address');
User.property('age', Number);
User.property('married', 'boolean');
User.property('email', 'string');
+ User.index('age', function(user) {
+ return user.age;
+ });
var user = User.new(USER);
user.save(function(errors) {

0 comments on commit 2a845ee

Please sign in to comment.