UNPKG

atomicrecord

Version:

Super lightweight node.js ActiveRecord ORM layer for FoundationDB

349 lines (296 loc) 10.3 kB
var AbstractRecord, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; AbstractRecord = require('./abstractrecord'); /** * Create an AtomicRecord class * @method * @param {object} options AtomicRecord type specific options. * @param {object} [options.fdb=undefined] fdb API instance. * @param {string} options.database Database name. * @param {string} options.dataset Data collection name. * @param {Boolean} options.partition Flag if AtomicRecord type instance storage is partitioned. * @param {string[]|object} options.fields AliasMap initializer. * @param {object} [options.primaryKey] Override methods for keyfrag Primary Key generator. * @return {AtomicRecord} an AtomicRecord class */ module.exports = function(options) { var AtomicIndex, AtomicRecord, Index, SerializerFactory, activeIndexes, database, dataset, db, fdb, fields, index, indexName, indexes, keyFrag, partition, primaryKey, remove, save, transactionalIndex, transactionalRemove, transactionalSave; if (!options) { throw new Error('No AtomicRecord options specified.'); } fdb = options.fdb, database = options.database, dataset = options.dataset, partition = options.partition, fields = options.fields, primaryKey = options.primaryKey, indexes = options.indexes; if (!database) { throw new Error('Database name not specified.'); } if (!dataset) { throw new Error('Dataset name not specified.'); } fdb = require('fdboost')(fdb); db = fdb.open(); AtomicIndex = require('./atomicindex'); SerializerFactory = require('./serializer/factory'); keyFrag = require('./keyfrag')(database, dataset, primaryKey); activeIndexes = { names: [] }; for (indexName in indexes) { index = indexes[indexName]; Index = AtomicIndex(indexName, index); activeIndexes[indexName] = new Index(); activeIndexes.names.push(indexName); } save = function(tr, record, callback) { var cb; cb = function(err, arr) { var kv, _i, _len; if (err) { return callback(err); } else { for (_i = 0, _len = arr.length; _i < _len; _i++) { kv = arr[_i]; tr.set(kv[0], kv[1]); } record.reset(true); return callback(null, record); } }; return record.serialize(cb); }; remove = function(tr, record, callback) { tr.clear(record.key); /* todo: delete indexes keys */ /* callback since if db.clear returns future */ return callback(null); }; index = function(tr, record, callback) { var cb; cb = function(err, directory) { var _i, _len, _ref; if (err) { return callback(err); } else { _ref = activeIndexes.names; for (_i = 0, _len = _ref.length; _i < _len; _i++) { indexName = _ref[_i]; activeIndexes[indexName].execute(tr, directory, record); } return callback(null); } }; return keyFrag.resolveDirectory(record, cb); }; transactionalSave = fdb.transactional(save); transactionalRemove = fdb.transactional(remove); transactionalIndex = fdb.transactional(index); return AtomicRecord = (function(_super) { __extends(AtomicRecord, _super); /** * Creates a new typed AtomicRecord instance * @class * @param {object} [record] Record object initializer. * @return {Record} a typed AtomicRecord instance. */ function AtomicRecord(initializer) { var src, val; AtomicRecord.__super__.constructor.call(this); if (initializer) { switch (typeof initializer) { case 'string': case 'number': this[keyFrag.idName] = keyFrag.serializeId(initializer); break; case 'object': for (src in initializer) { val = initializer[src]; if (src === keyFrag.idName) { this[src] = keyFrag.serializeId(val); } else { this[src] = val; } } break; default: throw new Error('Initializer must be a record, string or number'); } } } /* Initializers */ AtomicRecord.prototype.database = database; AtomicRecord.prototype.dataset = dataset; AtomicRecord.prototype.keySize = 0; AtomicRecord.prototype.valueSize = 0; AtomicRecord.prototype.partition = partition; AtomicRecord.prototype._key = null; AtomicRecord.prototype._keyValueSize = null; /** * Get / Set internal value for property alias. * @virtual * @param {string} dest Destination property alias. * @param {object} val Optional value to set. * @return {object} Value if val undefined. */ AtomicRecord.prototype.data = function(dest, val) { if (dest && typeof val === 'undefined') { val = AtomicRecord.__super__.data.call(this, dest); /* Decode the value if the dest param is not equal to keyFrag.idName and the type of val is Buffer */ if (dest !== keyFrag.idName && val instanceof Buffer) { val = fdb.encoding.decode(val); this.data(dest, val); } return val; } return AtomicRecord.__super__.data.call(this, dest, val); }; /** * The callback format for the save method * @callback saveCallback * @param {Error} err An error instance representing the error during the execution. * @param {AtomicRecord} record The current AtomicRecord instance if the save method was successful. */ /** * Persists record to the database * @method * @param {object} [tr=null] Transaction. * @param {saveCallback} callback Calback. * @return {Future} */ AtomicRecord.prototype.save = function(tr, callback) { if (typeof tr === 'function') { callback = tr; tr = null; } return fdb.future.create((function(_this) { return function(futureCb) { return transactionalSave(tr || db, _this, futureCb); }; })(this), callback); }; /** * Deletes record from the database * @method * @param {object} [tr=null] Transaction. * @param {saveCallback} callback Calback. * @return {Future} */ AtomicRecord.prototype.remove = function(tr, callback) { if (typeof tr === 'function') { callback = tr; tr = null; } return fdb.future.create((function(_this) { return function(futureCb) { return transactionalRemove(tr || db, _this, futureCb); }; })(this), callback); }; AtomicRecord.prototype.index = function(tr, callback) { if (typeof tr === 'function') { callback = tr; tr = null; } return fdb.future.create((function(_this) { return function(futureCb) { return transactionalIndex(tr || db, _this, futureCb); }; })(this), callback); }; AtomicRecord.prototype.serialize = function(callback) { /* generate an Id if none has been set */ var aliasMapHasId, recordHasId; recordHasId = typeof this[keyFrag.idName] !== 'undefined'; aliasMapHasId = typeof this.aliasMap.srcIndex[keyFrag.idName] !== 'undefined'; if (!recordHasId && aliasMapHasId) { this[keyFrag.idName] = keyFrag.generateId(); } return AtomicRecord.serializer.serialize(this, callback); }; /* Define getters and setters */ Object.defineProperties(AtomicRecord.prototype, { key: { get: function() { if (this._key === null) { throw new Error('Record must be loaded or saved to generate key'); } return this._key; }, set: function(val) { return this._key = val; } }, keyValueSize: { get: function() { if (this._keyValueSize === null || this.isChanged) { this._keyValueSize = this.keySize + this.valueSize; } return this._keyValueSize; } } }); /* Static properties and methods */ AtomicRecord.keyFrag = keyFrag; AtomicRecord.serializer = SerializerFactory.create(AtomicRecord); AtomicRecord.fdb = fdb; AtomicRecord.db = db; AtomicRecord.transactional = function(func) { return fdb.transactional(func); }; AtomicRecord.doTransaction = function(transaction, callback) { return db.doTransaction(transaction, callback); }; AtomicRecord.count = function() { throw new Error('not implemented'); }; AtomicRecord.findAll = function(query, options) { if (options == null) { options = {}; } options.nonTransactional = true; options.snapshot = true; return this.find(query, options); }; AtomicRecord.find = require('./finder'); AtomicRecord.findOne = function(tr, query, callback) { if (typeof query === 'function') { callback = query; query = tr; tr = null; } else if (!query) { query = tr; tr = null; } return fdb.future.create((function(_this) { return function(futureCb) { var finder, record; record = null; finder = _this.find(query, { limit: 1 }); finder.on('data', function(data) { record = data[0]; }); finder.on('error', function(err) { futureCb(err); }); finder.on('end', function() { futureCb(null, record); }); return finder.execute(tr, 'array'); }; })(this), callback); }; AtomicRecord.index = index; AtomicRecord.extend = function(constructor) { var ctorProto, k, v; ctorProto = constructor.prototype; __extends(constructor, this); for (k in ctorProto) { v = ctorProto[k]; constructor.prototype[k] = v; } AtomicRecord.serializer.AtomicRecord = constructor; return constructor; }; return AtomicRecord; })(AbstractRecord(fields)); };