UNPKG

monogram

Version:

TAO (aspect-oriented) modeling for MongoDB

175 lines (157 loc) 4.44 kB
const { ObjectId } = require('mongodb'); const { Observable, Subject } = require('rxjs'); const aggregate = require('./aggregate'); const co = require('co'); const debug = require('debug')('monogram:collection'); const find = require('./find'); class Collection { constructor(collection, archetype, options) { this._collection = collection; this.collection = collection.s.name; this.options = options || {}; if (archetype) { this._archetype = archetype; } else { this._archetype = x => x; } this._actionSubject = new Subject(); this.action$ = this._actionSubject.asObservable(); this.pres = []; this._customActions = {}; [ // Read // 'aggregate', is a custom action 'count', 'distinct', // 'find', is a custom action 'findOne', // Write 'deleteOne', 'deleteMany', 'findOneAndDelete', 'findOneAndUpdate', 'insertOne', 'insertMany', 'replaceOne', 'updateOne', 'updateMany' ].forEach(f => { this.action(f); }); aggregate(this); find(this); this.pre('insertOne', action => { action.params[0] = this._archetype(action.params[0]); }); this.pre('insertMany', action => { action.params[0] = action.params[0].map(doc => this._archetype(doc)); }); } pre(filter, fn) { if (arguments.length <= 1) { fn = filter; filter = null; } const _filter = filter; if (typeof _filter === 'string') { filter = action => action.name === _filter; } else if (_filter instanceof RegExp) { filter = action => _filter.test(action.name); } else if (_filter == null) { filter = () => true; } this.pres.push({ filter, fn }); return this; } action(fn) { const name = typeof fn === 'string' ? fn : fn.name; if (typeof fn === 'function') { this._customActions[name] = fn; } this[name] = function() { const chained = []; const args = Array.prototype.slice.call(arguments); const actionPromise = this.$baseAction(fn, args, chained); const error = new Error(); const wrappedPromise = actionPromise.then( res => { if (res.promise != null && typeof res.promise.then === 'function') { return res.promise.then(null, decorate(error)); } return res.promise; }, decorate(error) ); return chainable(wrappedPromise, this[name].chainable, chained); }; this[name].chainable = []; return this; } $baseAction(name, params, chained) { const _this = this; return co(function * () { yield Promise.resolve(); const _id = new ObjectId(); let fn; if (typeof name === 'function') { fn = name; name = name.name; } let actionObj = { _id, params, collection: _this.collection, name, chained }; for (const pre of _this.pres) { if (!pre.filter(actionObj)) { continue; } let _res = pre.fn(actionObj); if (_res != null) { if (typeof _res.then === 'function') { _res = yield _res; } actionObj = _res || actionObj; } } name = actionObj.name; params = actionObj.params; if (_this._customActions[name] != null) { fn = _this._customActions[name]; } actionObj.promise = fn ? fn.apply(actionObj, params) : _this._collection[name].apply(_this._collection, params); _this._actionSubject.next(actionObj); return actionObj; }); } } module.exports = Collection; function convertToPreFilter(filter) { if (typeof filter === 'string') { return action => action.name === filter; } else if (filter instanceof RegExp) { return action => filter.test(action.name); } else if (filter == null) { return () => true; } else if (typeof filter === 'function') { return filter; } throw new Error('Filter must be string, regexp, function, or nullish'); } function chainable(promise, fns, arr) { fns.forEach(function(name) { promise[name] = function() { arr.push({ name, params: Array.prototype.slice.call(arguments) }); return promise; }; }); return promise; } function decorate(error) { return function(err) { err.originalStack = error.stack; throw err; }; }