hydrate-mongodb
Version:
An Object Document Mapper (ODM) for MongoDB.
741 lines (740 loc) • 27.3 kB
JavaScript
"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;