UNPKG

hydrate-mongodb

Version:
741 lines (740 loc) 27.3 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var async = require("async"); var events_1 = require("events"); var mappingModel_1 = require("./mapping/mappingModel"); var taskQueue_1 = require("./taskQueue"); var batch_1 = require("./batch"); var reference_1 = require("./reference"); var queryBuilder_1 = require("./query/queryBuilder"); var persistenceError_1 = require("./persistenceError"); var writeContext_1 = require("./mapping/writeContext"); var timerUtil_1 = require("./core/timerUtil"); var cachedDetachedLinks = { state: 1, flags: 0 }; var SessionImpl = (function (_super) { __extends(SessionImpl, _super); function SessionImpl(factory) { var _this = _super.call(this) || this; _this.factory = factory; _this._persisters = []; _this._objectLinksById = new Map(); _this._objectLinks = []; _this._createTaskQueue(); _this._traceEnabled = _this.factory.logger != null; return _this; } SessionImpl.prototype.save = function (obj, callback) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } this._queue.add(1, 4095 & ~(1 | 384), obj, callback); }; SessionImpl.prototype.remove = function (obj, callback) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } this._queue.add(2, 4095 & ~2, obj, callback); }; SessionImpl.prototype.refresh = function (obj, callback) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } this._queue.add(32, 4095 & ~32, obj, callback); }; SessionImpl.prototype.detach = function (obj, callback) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } this._queue.add(4, 4095 & ~4, obj, callback); }; SessionImpl.prototype.clear = function (callback) { if (this._queue.invalid) { this._createTaskQueue(); } this._queue.add(16, 4095, undefined, callback); }; SessionImpl.prototype.flush = function (callback) { this._queue.add(8, 4095, undefined, callback); }; SessionImpl.prototype.wait = function (callback) { this._queue.add(1024, 4095, undefined, callback); }; SessionImpl.prototype.find = function (ctr, id, callback) { return this.query(ctr).findOneById(id, callback); }; SessionImpl.prototype.exists = function (ctr, id, callback) { var obj = this.getObject(id); if (obj) { process.nextTick(function () { return callback(null, true); }); return; } if (obj === null) { process.nextTick(function () { return callback(null, false); }); return; } this.query(ctr).count({ _id: id }, function (err, count) { if (err) return callback(err); callback(null, count >= 1); }); }; SessionImpl.prototype.fetch = function (obj, pathsOrCallback, callback) { var paths; if (typeof pathsOrCallback === "function") { callback = pathsOrCallback; } else if (typeof pathsOrCallback === "string") { paths = [pathsOrCallback]; } else { paths = pathsOrCallback; } this._queue.add(128, 4095 & ~384, [obj, paths], callback); }; SessionImpl.prototype.close = function (callback) { this._queue.add(2048, 4095, undefined, callback); }; SessionImpl.prototype.executeQuery = function (query, callback) { if (query.readOnly) { this._queue.add(256, 4095 & ~384, query, callback); } else { this._queue.add(512, 4095, query, callback); } }; SessionImpl.prototype.contains = function (obj) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } var id = obj["id"]; if (id) { var links = this._objectLinksById.get(id); if (links && links.state == 0) { return true; } } return false; }; SessionImpl.prototype.getEntities = function () { var ret = new Array(this._objectLinksById.size), i = 0; this._objectLinksById.forEach(function (value) { ret[i++] = value.object; }); return ret; }; SessionImpl.prototype.getReference = function (ctr, id) { var mapping = this.factory.getMappingForConstructor(ctr); if (mapping) { var identityGenerator = mapping.inheritanceRoot.identity; if (typeof id === "string") { id = identityGenerator.fromString(id); } else { if (!identityGenerator.validate(id)) { id = null; } } } return this.getReferenceInternal(mapping, id); }; SessionImpl.prototype.toDocument = function (obj, callback) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } var mapping = this.factory.getMappingForObject(obj); if (!mapping) { if (callback) { callback(new persistenceError_1.PersistenceError("Object type is not mapped as an entity.")); } return null; } var context = new writeContext_1.WriteContext(); var document = mapping.write(context, obj); if (context.hasErrors) { if (callback) { callback(new persistenceError_1.PersistenceError("Error serializing document:\n" + context.getErrorMessage())); } return null; } if (callback) { callback(null, document); } return document; }; SessionImpl.prototype.isDirty = function (obj, callback) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } var links = this._getObjectLinks(obj); if (!links || links.state == 1) { if (callback) { callback(new persistenceError_1.PersistenceError("Object is not managed.")); } return null; } if (!links.originalDocument) { if (callback) { process.nextTick(function () { return callback(null, false); }); } return false; } var context = {}, areEqual = links.persister.areDocumentsEqual(context, obj, links.originalDocument); if (context.error) { if (callback) { callback(context.error); } return null; } if (callback) { process.nextTick(function () { return callback(null, !areEqual); }); } return !areEqual; }; SessionImpl.prototype.getVersion = function (obj) { if (typeof obj !== "object") { throw new Error("'obj' argument should be an object."); } var links = this._getObjectLinks(obj); if (!links || links.state == 1) { return null; } if (!links.originalDocument) { return 0; } var mapping = this.factory.getMappingForObject(obj); if (!mapping) { return null; } return mapping.getDocumentVersion(links.originalDocument); }; SessionImpl.prototype.getReferenceInternal = function (mapping, id) { return this.getObject(id) || new reference_1.Reference(mapping, id); }; SessionImpl.prototype.getObject = function (id) { if (id) { var links = this._objectLinksById.get(id.toString()); if (links) { switch (links.state) { case 2: return null; case 0: return links.object; } } } }; SessionImpl.prototype.registerManaged = function (persister, entity, document) { var links = this._linkObject(entity, persister); links.originalDocument = document; this._trackChanges(links); }; SessionImpl.prototype._trackChanges = function (links) { if ((links.flags & 2) || links.persister.changeTracking == 1) { this._makeDirty(links); return; } }; SessionImpl.prototype._stopWatching = function (links) { if (links.observer) { links.observer.destroy(); links.observer = undefined; } }; SessionImpl.prototype.getPersister = function (mapping) { return this._persisters[mapping.id] || (this._persisters[mapping.id] = this.factory.createPersister(this, mapping)); }; SessionImpl.prototype.query = function (ctr) { return new queryBuilder_1.QueryBuilderImpl(this, ctr); }; SessionImpl.prototype._execute = function (action, arg, callback) { switch (action) { case 1: this._save(arg, callback); break; case 2: this._remove(arg, callback); break; case 4: this._detach(arg, callback); break; case 32: this._refresh(arg, callback); break; case 16: this._clear(callback); break; case 8: this._flush(callback); break; case 1024: if (callback) { process.nextTick(function () { return callback(null); }); } break; case 128: this.fetchInternal(arg[0], arg[1], callback); break; case 256: case 512: arg.executeInternal(callback); break; case 2048: this._close(callback); break; } }; SessionImpl.prototype._save = function (obj, callback) { var _this = this; if (reference_1.Reference.isReference(obj)) { return callback(new persistenceError_1.PersistenceError("Reference passed to save")); } this._findReferencedEntities(obj, 2, function (err, entities) { if (err) return callback(err); _this._saveEntities(entities, callback); }); }; SessionImpl.prototype._saveEntities = function (entities, callback) { for (var i = 0, l = entities.length; i < l; i++) { var obj = entities[i]; var links = this._getObjectLinks(obj); if (!links) { var mapping = this.factory.getMappingForObject(obj); if (!mapping) { callback(new persistenceError_1.PersistenceError("Object type is not mapped as an entity.")); return; } var persister = this.getPersister(mapping); var id = obj["_id"] = persister.identity.generate(); obj["id"] = id.toString(); links = this._linkObject(obj, persister); this._scheduleOperation(links, 1); } else { switch (links.state) { case 0: if (links.persister.changeTracking == 2) { this._makeDirty(links); } break; case 1: callback(new persistenceError_1.PersistenceError("Cannot save a detached object.")); return; case 2: if (links.scheduledOperation == 3) { this._clearScheduledOperation(links); this._trackChanges(links); } else { this._scheduleOperation(links, 1); } links.state = 0; break; } } } callback(); }; SessionImpl.prototype._makeDirty = function (links) { links.flags |= 2; if (!links.scheduledOperation) { this._scheduleOperation(links, 4); } }; SessionImpl.prototype._clearDirty = function (links) { links.flags &= ~2; if (links.scheduledOperation == 4) { this._clearScheduledOperation(links); } }; SessionImpl.prototype.notifyRemoved = function (entity) { if (!this._removeEntity(entity, false)) { throw new persistenceError_1.PersistenceError("Cannot remove a detached object."); } }; SessionImpl.prototype._remove = function (obj, callback) { var _this = this; reference_1.Reference.fetch(this, obj, function (err, entity) { if (err) return callback(err); _this._findReferencedEntities(entity, 4 | 16384, function (err, entities) { if (err) return callback(err); _this._removeEntities(entities, callback); }); }); }; SessionImpl.prototype._removeEntities = function (entities, callback) { for (var i = entities.length - 1; i >= 0; i--) { var obj = entities[i]; if (!this._removeEntity(obj, true)) { callback(new persistenceError_1.PersistenceError("Cannot remove a detached object.")); return; } } callback(); }; SessionImpl.prototype._removeEntity = function (obj, scheduleDelete) { var links = this._getObjectLinks(obj); if (links) { switch (links.state) { case 0: links.state = 2; if (links.scheduledOperation == 1) { this._unlinkObject(links); this._clearScheduledOperation(links); } else { if (scheduleDelete) { this._scheduleOperation(links, 3); } } break; case 1: return false; } } return true; }; SessionImpl.prototype._detach = function (obj, callback) { var _this = this; if (reference_1.Reference.isReference(obj)) { return callback(new persistenceError_1.PersistenceError("Reference passed to detach")); } this._findReferencedEntities(obj, 8, function (err, entities) { if (err) return callback(err); _this._detachEntities(entities, callback); }); }; SessionImpl.prototype._detachEntities = function (entities, callback) { for (var i = 0, l = entities.length; i < l; i++) { var links = this._getObjectLinks(entities[i]); if (links && links.state == 0) { this._unlinkObject(links); this._clearScheduledOperation(links); } } callback(); }; SessionImpl.prototype._refresh = function (obj, callback) { var _this = this; if (reference_1.Reference.isReference(obj)) { return callback(new persistenceError_1.PersistenceError("Reference passed to refresh")); } this._findReferencedEntities(obj, 16, function (err, entities) { if (err) return callback(err); _this._refreshEntities(entities, callback); }); }; SessionImpl.prototype._refreshEntities = function (entities, callback) { var _this = this; async.each(entities, function (entity, done) { var links = _this._getObjectLinks(entity); if (!links || links.state != 0) { return done(new persistenceError_1.PersistenceError("Object is not managed.")); } _this._stopWatching(links); links.persister.refresh(links.object, function (err, document) { if (err) return done(err); links.originalDocument = document; _this._clearDirty(links); _this._trackChanges(links); done(); }); }, callback); }; SessionImpl.prototype._flush = function (callback) { var _this = this; setImmediate(function () { var head = _this._scheduleHead; _this._scheduleHead = _this._scheduleTail = null; if (_this._traceEnabled) { var start = process.hrtime(); } var batch = new batch_1.Batch(); _this._buildBatch(batch, head, function (err) { if (err) return callback(err); batch.execute(function (err) { if (err) return callback(err); if (start) { var duration = timerUtil_1.getDuration(start); } _this._batchCompleted(head, duration, callback); }); }); }); }; SessionImpl.prototype._buildBatch = function (batch, links, callback) { var _this = this; var count = 0; while (links && count < 1000) { if (links.scheduledOperation == 4) { var document = links.persister.dirtyCheck(batch, links.object, links.originalDocument); if (document) { links.scheduledOperation = 2; links.originalDocument = document; } } else if (links.scheduledOperation == 1) { var document = links.persister.addInsert(batch, links.object); if (document) { links.originalDocument = document; } } else if (links.scheduledOperation == 3) { links.persister.addRemove(batch, links.object); } if (batch.error) { return callback(batch.error); } links = links.next; count++; } if (links) { process.nextTick(function () { return _this._buildBatch(batch, links, callback); }); } else { callback(); } }; SessionImpl.prototype._batchCompleted = function (head, duration, callback) { if (this._traceEnabled) { this._logFlushStats(head, duration); } var links = head; while (links) { var next = links.next; links.scheduledOperation = 0; links.flags = 0; links.prev = links.next = null; switch (links.state) { case 2: this._unlinkObject(links); this._clearIdentifier(links); break; case 0: this._trackChanges(links); break; default: return callback(new persistenceError_1.PersistenceError("Unexpected object state in flush.")); } links = next; } callback(); }; SessionImpl.prototype._logFlushStats = function (head, duration) { var inserted = 0, updated = 0, removed = 0, dirtyChecked = 0; var links = head; while (links) { switch (links.scheduledOperation) { case 2: updated++; break; case 3: inserted++; break; case 1: removed++; break; case 4: dirtyChecked++; break; } links = links.next; } this.factory.logger.trace({ duration: duration, operations: { inserted: inserted, updated: updated, removed: removed, dirtyChecked: dirtyChecked + updated } }, "[Hydrate] Completed flush."); }; SessionImpl.prototype._close = function (callback) { var _this = this; this._queue.close(); this._flush(function (err) { if (err) return callback(err); _this._clear(callback); }); }; SessionImpl.prototype.fetchInternal = function (obj, paths, callback) { var _this = this; if (obj == null) { return callback(null, obj); } if (Array.isArray(obj)) { async.map(obj, function (item, done) { return _this.fetchInternal(item, paths, done); }, callback); return; } reference_1.Reference.fetch(this, obj, function (err, entity) { if (err) return callback(err); _this._fetchPaths(entity, paths, callback); }); }; SessionImpl.prototype._fetchPaths = function (obj, paths, callback) { var links = this._getObjectLinks(obj); if (!links || links.state == 1) { return callback(new persistenceError_1.PersistenceError("Object is not associated with the session.")); } if (!paths || paths.length === 0) { process.nextTick(function () { return callback(null, obj); }); } else { var persister = links.persister; async.each(paths, function (path, done) { persister.fetch(obj, path, done); }, function (err) { if (err) return callback(err); callback(null, obj); }); } }; SessionImpl.prototype._clear = function (callback) { for (var i = 0; i < this._objectLinks.length; i++) { var links = this._objectLinks[i]; if (links) { this._cleanupUnlinkedObject(links); } } this._objectLinksById = new Map(); this._objectLinks = []; this._scheduleHead = this._scheduleTail = null; process.nextTick(callback); }; SessionImpl.prototype._getObjectLinks = function (obj) { var id = obj["id"]; if (id) { var links = this._objectLinksById.get(id); if (!links || links.object !== obj) { return cachedDetachedLinks; } return links; } }; SessionImpl.prototype._linkObject = function (obj, persister) { var id = obj["id"]; if (id == null) { throw new persistenceError_1.PersistenceError("Object is missing identifier."); } if (this._objectLinksById.has(id)) { throw new persistenceError_1.PersistenceError("Session already contains an entity with identifier '" + id + "'."); } var links = { state: 0, flags: 0, object: obj, persister: persister, index: this._objectLinks.length }; this._objectLinksById.set(id, links); this._objectLinks.push(links); return links; }; SessionImpl.prototype._unlinkObject = function (links) { this._objectLinksById.delete(links.object["id"]); this._objectLinks[links.index] = undefined; this._cleanupUnlinkedObject(links); }; SessionImpl.prototype._cleanupUnlinkedObject = function (links) { this._stopWatching(links); if (links.scheduledOperation == 1) { this._clearIdentifier(links); } }; SessionImpl.prototype._scheduleOperation = function (links, operation) { if (!links.scheduledOperation) { if (this._scheduleHead) { this._scheduleTail.next = links; links.prev = this._scheduleTail; this._scheduleTail = links; } else { this._scheduleHead = this._scheduleTail = links; } } links.scheduledOperation = operation; }; SessionImpl.prototype._clearScheduledOperation = function (links) { if (!links.scheduledOperation) { return; } if (links.prev) { links.prev.next = links.next; } else { this._scheduleHead = links.next; } if (links.next) { links.next.prev = links.prev; } else { this._scheduleTail = links.prev; } links.scheduledOperation = 0; }; SessionImpl.prototype._clearIdentifier = function (links) { links.object["_id"] = links.object["id"] = undefined; }; SessionImpl.prototype._findReferencedEntities = function (obj, flags, callback) { var mapping = this.factory.getMappingForObject(obj); if (!mapping) { callback(new persistenceError_1.PersistenceError("Object type is not mapped as an entity.")); return; } var entities = [], embedded = []; this._walk(mapping, obj, flags, entities, embedded, function (err) { if (err) return callback(err); process.nextTick(function () { return callback(null, entities); }); }); }; SessionImpl.prototype._walk = function (mapping, entity, flags, entities, embedded, callback) { var _this = this; var references = []; mapping.walk(this, entity, flags | 8192, entities, embedded, references); async.each(references, function (reference, done) { reference.fetch(_this, function (err, entity) { if (err) return done(err); _this._walk(reference.mapping, entity, flags, entities, embedded, done); }); }, callback); }; SessionImpl.prototype._createTaskQueue = function () { var _this = this; if (this._queue) { this._queue.removeAllListeners(); } this._queue = new taskQueue_1.TaskQueue(function (action, args, callback) { return _this._execute(action, args, callback); }); this._queue.on('error', function (err) { _this.emit('error', err); }); }; return SessionImpl; }(events_1.EventEmitter)); exports.SessionImpl = SessionImpl;