module.exports = function (_opts) { var api = modeler(_opts); if (!api.options.db) throw new Error('must pass a node-mongodb-native db with options.db'); api.options.sort = api.options.sort || '__idx'; var db = api.options.db , collection = db.collection(api.options.name) , counter = db.collection('_counters') , initialized = false; // Make sure we have the indexes we need function initialize (cb) { collection.ensureIndex([{ "__idx": -1 }], { unique: true }, function (err) { if (err) return cb(err); counter.ensureIndex([{ "_id": 1, "seq": -1 }], { unique: true }, cb); }); } function continuable (skip, limit, reverse, cb) { (function next () { var sort = {}; sort[api.options.sort] = reverse ? -1 : 1; var cur = collection.find({}, { _id: 0, __idx: 0 }).skip( skip ? skip : 0); limit && cur.limit(limit); cur.sort(sort); cur.toArray(function (err, results) { if (err) return cb(err); skip += results.length; cb(null, results, next); }); })(); } api._head = function (skip, limit, cb) { continuable(skip, limit, false, cb); }; api._tail = function (skip, limit, cb) { continuable(skip, limit, true, cb); }; api._save = function (entity, cb) { if (!initialized) { return initialize(function(err) { if (err) return cb(err); initialized = true; api._save(entity, cb); }); } function save () { var saveEntity = api.copy(entity); // We need `_id` to be the same as `id` because MongoDB and Modeler // each have their own unique, completely inflexible requirements saveEntity._id = entity.id; // @todo consider performing a findAndModify using both // id and rev in the query part where rev > 1 collection.save(saveEntity, function (err) { cb(err); }); } if (entity.rev > 1 || typeof entity.__idx !== 'undefined') save(); else { // http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/ // compound unique index is ensured in the initialize function, above counter.findAndModify({ _id: api.options.name }, null, { "$inc": { seq: 1 } }, { new: true, upsert: true }, function (err, ct) { if (err) return cb(err); entity.__idx = ct.seq; save(); }); } }; api._load = function (id, cb) { collection.findOne({ _id: id }, { _id: 0 }, function (err, entity) { if (err) return cb(err); cb(err, entity); }); }; api._destroy = function (id, cb) { collection.remove({ _id: id }, cb); }; return api; };
module.exports = function (_opts) { if (!_opts.client) { throw new Error('You must provide an elasticsearch client.'); } var api = modeler(_opts); api.client = api.options.client; api.options.sort = api.options.sort || '_timestamp'; // Refresh the index after all saves, really should only be used in tests. api.options.refresh = api.options.refresh || false; function continuable (offset, limit, dir, cb) { var sort = {}; sort[api.options.sort] = dir; (function next () { api.client.search({_type: api.options.name}, { from: offset, size: limit, fields: ['_id', api.options.sort], sort: [sort] }, function (err, results) { if (err) return cb(err); if (!results.hits || !results.hits.hits.length) return cb(null, [], next); cb(null, results.hits.hits.map(function (hit) { return hit._id; }), next); }); })(); } api._head = function (offset, limit, cb) { continuable(offset, limit, 'asc', cb); }; api._tail = function (offset, limit, cb) { continuable(offset, limit, 'desc', cb); }; api._save = function (entity, cb) { var doc = hydration.dehydrate(entity); api.client.index({_type: api.options.name, _id: ('' + entity.id)}, doc, function (err, result) { if (err) return cb(err); api._finish(cb); }); }; api._load = function (id, cb) { api.client.get({_type: api.options.name, _id: ('' + id)}, function (err, result) { if (err) { if (err.statusCode && err.statusCode === 404) { return cb(null, null); } return cb(err); } cb(null, hydration.hydrate(result._source)); }); }; api._destroy = function (id, cb) { api.client.delete({_type: api.options.name, _id: ('' + id)}, function (err) { if (err) return cb(err); api._finish(cb); }); }; api._finish = function (cb) { if (api.options.refresh) { api.client.indices.refresh(function (e) { cb(e); }); } else { cb(); } }; return api; };
module.exports = function (_opts) { var api = modeler(_opts) , internalProps = [ 'rev', 'created', 'updated' ] , stripe , collection; if (!api.options.secret_key) throw new Error('secret key required'); if (!api.options.name) throw new Error('name required'); if (!api.options.name.match(/^(?:customers)/)) throw new Error('collection not supported: ' + api.options.name); stripe = Stripe(api.options.secret_key); if (api.options.apiVersion) stripe.setApiVersion(api.options.apiVersion); collection = stripe[api.options.name]; api._beforeSave = function (entity) { var e = api.copy(entity) , metadata = {}; e.metadata || (e.metadata = {}); if (e.rev > 1) { delete e.id; } // stash modeler's internal props in metadata // or stripe will barf Object.keys(e).forEach(function (prop) { var idx; if (prop === 'metadata') return; if (~internalProps.indexOf(prop)) { metadata[prop] = e[prop]; delete e[prop]; } // restore Stripe properties with namespace collisions else if ((idx = prop.indexOf('_stripe')) > -1) { e[prop.substr(0, idx)] = e[prop]; delete e[prop]; } }); try { e.metadata.modeler = JSON.stringify(hydration.dehydrate(metadata)); } catch (e) {} // shouldn't happen, but don't throw return e; }; api._afterSave = function (entity) { var e = _.omit(entity, 'id', internalProps); // move Stripe properties with namespace collisions Object.keys(entity).forEach(function (prop) { if (~internalProps.indexOf(prop)) { e[prop + '_stripe'] = e[prop]; } }); return e; }; api._afterLoad = function (entity) { var e = api._afterSave(entity) , metadata; e.id = entity.id; // restore modeler's internal props from metadata if (entity.metadata && entity.metadata.modeler) { try { metadata = JSON.parse(entity.metadata.modeler); metadata = hydration.hydrate(metadata); } catch (e) {} // shouldn't happen, but don't throw internalProps.forEach(function (prop) { if (prop in metadata) { e[prop] = metadata[prop]; } }); } return e; }; api._head = function (offset, count, cb) { return cb(new Error('head() is not supported for Stripe lists - please use tail() instead')); }; api._tail = function (offset, count, cb) { (function fetchNext () { var params = { offset: offset ? offset : 0 }; // Stripe API requires count to be from 1 to 100 if (count && count >= 1 && count <= 100) params.count = count; collection.list(params, function (err, entities) { if (err) return cb(err); offset += entities.data.length; cb(null, entities.data.map(api._afterLoad), fetchNext); }); })(); }; api._save = function (entity, cb) { if (entity.rev > 1) { collection.update(entity.id, api._beforeSave(entity), function (err, savedEntity) { if (err) return cb(err); cb(null, api._afterSave(savedEntity)); }); } else { collection.create(api._beforeSave(entity), function (err, savedEntity) { if (err) return cb(err); cb(null, api._afterSave(savedEntity)); }); } }; api._load = function (id, cb) { collection.retrieve(id, function (err, savedEntity) { if (err && !err.message.match(/No such (?:customer)/)) return cb(err); cb(null, savedEntity ? api._afterLoad(savedEntity) : null); }); }; api._destroy = function (id, cb) { collection.del(id, function (err, confirmation) { if (err) return cb(err); if (!confirmation || !confirmation.deleted) { cb(new Error('Delete failed')); } else cb(); }); }; return api; };