mongo-portable
Version:
Portable Pure JS MongoDB - Based on Monglodb (https://github.com/euforic/monglodb.git) by Christian Sullivan (http://RogueSynaptics.com)
1,261 lines • 59.7 kB
JavaScript
"use strict";
var __values = (this && this.__values) || function (o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
};
Object.defineProperty(exports, "__esModule", { value: true });
var jsw_logger_1 = require("jsw-logger");
var _ = require("lodash");
var Promise = require("promise");
var Cursor_1 = require("./Cursor");
var aggregation_1 = require("../aggregation");
var document_1 = require("../document");
var selector_1 = require("../selector");
/***
* Gets the size of an object.
*
* @method Object#size
*
* @param {Object} obj - The object
*
* @returns {Number} The size of the object
*/
var getObjectSize = function (obj) {
var size = 0;
var key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
size++;
}
}
return size;
};
// module.exports = function(Aggregation, Cursor, Selector, SelectorMatcher, ObjectId, EventEmitter, Logger, _) {
/***
* Collection
*
* @module Collection
* @constructor
* @since 0.0.1
* @author Eduardo Astolfi <eastolfi91@gmail.com>
* @copyright 2016 Eduardo Astolfi <eastolfi91@gmail.com>
* @license MIT Licensed
*
* @classdesc Collection class that maps a MongoDB-like collection
*/
var database = null;
var Collection = /** @class */ (function () {
// var Collection = function(db, collectionName, options) {
/***
* @param {MongoPortable} db - Additional options
* @param {String} collectionName - The name of the collection
* @param {Object} [options] - Database object
*
* @param {Object} [options.pkFactory=null] - Object overriding the basic "ObjectId" primary key generation.
*/
function Collection(db, collectionName /*, options*/) {
// super(options.log || {});
// super();
/***
* Inserts several documents into the collection
*
* @method Collection#bulkInsert
*
* @param {Array} docs - Documents to be inserted
* @param {Object} [options] - Additional options
*
* @param {Function} [callback=null] Callback function to be called at the end with the results
*
* @returns {Promise<Array<Object>>} Returns a promise with the inserted documents
*/
this.bulkInsert = function (docs, options, callback) {
var self = this;
return new Promise(function (resolve, reject) {
var e_1, _a;
if (_.isNil(docs)) {
self.logger.throw("docs parameter required");
}
if (!_.isArray(docs)) {
self.logger.throw("docs must be an array");
}
if (_.isNil(options)) {
options = {};
}
if (_.isFunction(options)) {
callback = options;
options = {};
}
if (!_.isNil(callback) && !_.isFunction(callback)) {
self.logger.throw("callback must be a function");
}
var promises = [];
try {
for (var docs_1 = __values(docs), docs_1_1 = docs_1.next(); !docs_1_1.done; docs_1_1 = docs_1.next()) {
var doc = docs_1_1.value;
promises.push(self.insert(doc, options));
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (docs_1_1 && !docs_1_1.done && (_a = docs_1.return)) _a.call(docs_1);
}
finally { if (e_1) throw e_1.error; }
}
Promise.all(promises)
.then(function (_docs) {
if (callback) {
callback(null, _docs);
}
resolve(docs);
}).catch(function (error) {
if (callback) {
callback(error, null);
}
reject(error);
});
});
};
if (!(this instanceof Collection)) {
return new Collection(db, collectionName /*, options*/);
}
this.logger = jsw_logger_1.JSWLogger.instance;
if (_.isNil(db)) {
this.logger.throw("db parameter required");
}
if (_.isNil(collectionName)) {
this.logger.throw("collectionName parameter required");
}
// if (_.isNil(options) || !_.isPlainObject(options)) options = {};
Collection.checkCollectionName(collectionName);
// this.db = db;
database = db;
this.name = collectionName;
this.databaseName = db._databaseName;
this.fullName = this.databaseName + "." + this.name;
this.docs = [];
this.doc_indexes = {};
this.snapshots = [];
// this.opts = {}; // Default options
// _.merge(this.opts, options);
this.emit = function (name, args) {
return db.emit(name, args);
};
}
// emit(name, args) {
// super.emit(name, args, database._stores);
// }
// TODO enforce rule that field names can't start with '$' or contain '.'
// (real mongodb does in fact enforce this)
// TODO possibly enforce that 'undefined' does not appear (we assume
// this in our handling of null and $exists)
/***
* Inserts a document into the collection
*
* @method Collection#insert
*
* @param {Object} doc - Document to be inserted
* @param {Object} [options] - Additional options
*
* @param {Function} [callback=null] Callback function to be called at the end with the results
*
* @returns {Promise<Object>} Returns a promise with the inserted document
*/
Collection.prototype.insert = function (doc, options, callback) {
var self = this;
return new Promise(function (resolve, reject) {
// REJECT
if (_.isNil(doc)) {
self.logger.throw("doc parameter required");
}
if (!_.isPlainObject(doc)) {
self.logger.throw("doc must be an object");
}
if (_.isNil(options)) {
options = {};
}
if (_.isFunction(options)) {
callback = options;
options = {};
}
if (!_.isNil(callback) && !_.isFunction(callback)) {
self.logger.throw("callback must be a function");
}
// Creating a safe copy of the document
var _doc = _.cloneDeep(doc);
// If the document comes with a number ID, parse it to String
if (_.isNumber(_doc._id)) {
_doc._id = _.toString(_doc._id);
}
if (_.isNil(_doc._id) || (!(_doc._id instanceof document_1.ObjectId) && (!_.isString(_doc._id) || !_doc._id.length))) {
_doc._id = new document_1.ObjectId();
}
// Add options to more dates
_doc.timestamp = new document_1.ObjectId().generationTime;
// Reverse
self.doc_indexes[_.toString(_doc._id)] = self.docs.length;
self.docs.push(_doc);
/***
* "insert" event.
*
* @event MongoPortable~insert
*
* @param {Object} collection - Information about the collection
* @param {Object} doc - Information about the document inserted
*/
self.emit("insert", {
collection: self,
doc: _doc
}).then(function () {
if (callback) {
callback(null, _doc);
}
resolve(_doc);
}).catch(function (error) {
// EXCEPTION UTIL
if (callback) {
callback(error, null);
}
reject(error);
});
});
};
/***
* Finds all matching documents
*
* @method Collection#find
*
* @param {Object|Array|String} [selection={}] - The selection for matching documents
* @param {Object|Array|String} [fields={}] - The fields of the document to show
* @param {Object} [options] - Additional options
*
* @param {Number} [options.skip] - Number of documents to be skipped
* @param {Number} [options.limit] - Max number of documents to display
* @param {Object|Array|String} [options.fields] - Same as "fields" parameter (if both passed, "options.fields" will be ignored)
* @param {Boolean} [options.doNotFetch=false] - If set to'"true" returns the cursor not fetched
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Array<Object>|Cursor>} Returns a promise with the documents (or cursor if "options.forceFetch" set to true)
*/
Collection.prototype.find = function (selection, fields, options, callback) {
var self = this;
return new Promise(function (resolve, reject) {
var params = ensureFindParams({
selection: selection,
fields: fields,
options: options,
callback: callback
});
selection = params.selection;
fields = params.fields;
options = params.options;
callback = params.callback;
/***
* "find" event.
*
* @event MongoPortable~find
*
* @property {Object} collection - Information about the collection
* @property {Object} selector - The selection of the query
* @property {Object} fields - The fields showed in the query
*/
self.emit("find", {
collection: self,
selector: selection,
fields: fields
}).then(function () {
var cursor = new Cursor_1.Cursor(self.docs, selection, fields, options);
// Pass the cursor fetched to the callback
if (options.doNotFecth) {
if (callback) {
callback(null, cursor);
}
resolve(cursor);
}
else {
var docs = cursor.fetch();
if (callback) {
callback(null, docs);
}
resolve(docs);
}
}).catch(function (error) {
if (callback) {
callback(error, null);
}
reject(error);
});
});
};
/***
* Finds the first matching document
*
* @method Collection#findOne
*
* @param {Object|Array|String} [selection={}] - The selection for matching documents
* @param {Object|Array|String} [fields={}] - The fields of the document to show
* @param {Object} [options] - Additional options
*
* @param {Number} [options.skip] - Number of documents to be skipped
* @param {Number} [options.limit] - Max number of documents to display
* @param {Object|Array|String} [options.fields] - Same as "fields" parameter (if both passed, "options.fields" will be ignored)
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Object>} Returns a promise with the first matching document of the collection
*/
Collection.prototype.findOne = function (selection, fields, options, callback) {
var self = this;
return new Promise(function (resolve, reject) {
var params = ensureFindParams({
selection: selection,
fields: fields,
options: options,
callback: callback
});
selection = params.selection;
fields = params.fields;
options = params.options;
callback = params.callback;
/***
* "findOne" event.
*
* @event MongoPortable~findOne
*
* @property {Object} collection - Information about the collection
* @property {Object} selector - The selection of the query
* @property {Object} fields - The fields showed in the query
*/
self.emit("findOne", {
collection: self,
selector: selection,
fields: fields
}).then(function () {
var cursor = new Cursor_1.Cursor(self.docs, selection, fields, options);
var res = null;
if (cursor.hasNext()) {
res = cursor.next();
}
if (callback) {
callback(null, res);
}
resolve(res);
}).catch(function (error) {
if (callback) {
callback(error, null);
}
reject(error);
});
});
};
/***
* Updates one or many documents
*
* @method Collection#update
*
* @param {Object|Array|String} [selection={}] - The selection for matching documents
* @param {Object} [update={}] - The update operation
* @param {Object} [options] - Additional options
*
* @param {Number} [options.updateAsMongo=true] - By default:
* If the [update] object contains update operator modifiers, such as those using the "$set" modifier, then:
* <ul>
* <li>The [update] object must contain only update operator expressions</li>
* <li>The Collection#update method updates only the corresponding fields in the document</li>
* <ul>
* If the [update] object contains only "field: value" expressions, then:
* <ul>
* <li>The Collection#update method replaces the matching document with the [update] object. The Collection#update method does not replace the "_id" value</li>
* <li>Collection#update cannot update multiple documents</li>
* <ul>
*
* @param {Number} [options.override=false] - Replaces the whole document (only apllies when [updateAsMongo=false])
* @param {Number} [options.upsert=false] - Creates a new document when no document matches the query criteria
* @param {Number} [options.multi=false] - Updates multiple documents that meet the criteria
* @param {Object} [options.writeConcern=null] - An object expressing the write concern
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Object>} Returns a promise with the update/insert (if upsert=true) information
*/
Collection.prototype.update = function (selection, update, options, callback) {
var self = this;
return new Promise(function (resolve, reject) {
if (_.isNil(selection)) {
selection = {};
}
if (_.isNil(update)) {
self.logger.throw("You must specify the update operation");
}
if (_.isNil(options)) {
options = {
skip: 0,
limit: 15 // for no limit pass [options.limit = -1]
};
}
if (_.isFunction(selection)) {
self.logger.throw("You must specify the update operation");
}
if (_.isFunction(update)) {
self.logger.throw("You must specify the update operation");
}
if (_.isFunction(options)) {
callback = options;
options = {};
}
// Check special case where we are using an objectId
if (selection instanceof document_1.ObjectId) {
selection = {
_id: selection
};
}
if (!_.isNil(callback) && !_.isFunction(callback)) {
self.logger.throw("callback must be a function");
}
var res = null;
// var docs = null;
if (options.multi) {
// docs = self.find(selection, null, { forceFetch: true });
self.find(selection, null, { forceFetch: true })
.then(onDocsFound)
.catch(doReject);
}
else {
// docs = self.findOne(selection);
self.findOne(selection, null, null, callback)
.then(onDocsFound)
.catch(doReject);
}
function onDocsFound(docs) {
var e_2, _a, e_3, _b, e_4, _c;
if (_.isNil(docs)) {
docs = [];
}
if (!_.isArray(docs)) {
docs = [docs];
}
if (docs.length === 0) {
if (options.upsert) {
self.insert(update, null, callback)
.then(function (inserted) {
doResolve({
updated: {
documents: null,
count: 0
},
inserted: {
documents: [inserted],
count: 1
}
});
}).catch(doReject);
// res = {
// updated: {
// documents: null,
// count: 0
// },
// inserted: {
// documents: [inserted],
// count: 1
// }
// };
}
else {
// No documents found
/*res = */ doResolve({
updated: {
documents: null,
count: 0
},
inserted: {
documents: null,
count: 0
}
});
}
}
else {
var updatedDocs_1 = [];
for (var i = 0; i < docs.length; i++) {
var doc = docs[i];
var override = null;
var hasModifier = false;
try {
for (var _d = __values(Object.keys(update)), _e = _d.next(); !_e.done; _e = _d.next()) {
var key = _e.value;
// IE7 doesn't support indexing into strings (eg, key[0] or key.indexOf('$') ), so use substr.
// Testing over the first letter:
// Bests result with 1e8 loops => key[0](~3s) > substr(~5s) > regexp(~6s) > indexOf(~16s)
var modifier = (key.substr(0, 1) === "$");
if (modifier) {
hasModifier = true;
}
if (options.updateAsMongo) {
if (hasModifier && !modifier) {
self.logger.throw("All update fields must be an update operator");
}
if (!hasModifier && options.multi) {
self.logger.throw("You can not update several documents when no update operators are included");
}
if (hasModifier) {
override = false;
}
if (!hasModifier) {
override = true;
}
}
else {
override = !!options.override;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
}
finally { if (e_2) throw e_2.error; }
}
var _docUpdate = null;
if (override) {
// Overrides the document except for the "_id"
_docUpdate = {
_id: doc._id
};
try {
// Must ignore fields starting with '$', '.'...
for (var _f = __values(Object.keys(update)), _g = _f.next(); !_g.done; _g = _f.next()) {
var key = _g.value;
if (key.substr(0, 1) === "$" || /\./g.test(key)) {
self.logger.warn("The field " + key + " can not begin with '$' or contain '.'");
}
else {
_docUpdate[key] = update[key];
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
}
finally { if (e_3) throw e_3.error; }
}
}
else {
_docUpdate = _.cloneDeep(doc);
try {
for (var _h = __values(Object.keys(update)), _j = _h.next(); !_j.done; _j = _h.next()) {
var key = _j.value;
var val = update[key];
if (key.substr(0, 1) === "$") {
_docUpdate = applyModifier(_docUpdate, key, val);
}
else {
if (!_.isNil(_docUpdate[key])) {
if (key !== "_id") {
_docUpdate[key] = val;
}
else {
self.logger.warn("The field '_id' can not be updated");
}
}
else {
self.logger.warn("The document does not contains the field " + key);
}
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_j && !_j.done && (_c = _h.return)) _c.call(_h);
}
finally { if (e_4) throw e_4.error; }
}
}
updatedDocs_1.push(_docUpdate);
var idx = self.doc_indexes[_docUpdate._id];
self.docs[idx] = _docUpdate;
}
/***
* "update" event.
*
* @event MongoPortable~update
*
* @property {Object} collection - Information about the collection
* @property {Object} selector - The selection of the query
* @property {Object} modifier - The modifier used in the query
* @property {Object} docs - The updated/inserted documents information
*/
self.emit("update", {
collection: self,
selector: selection,
modifier: update,
docs: updatedDocs_1
}).then(function () {
doResolve({
updated: {
documents: updatedDocs_1,
count: updatedDocs_1.length
},
inserted: {
documents: null,
count: 0
}
});
}).catch(function (error) {
doReject(error);
});
// res = {
// updated: {
// documents: updatedDocs,
// count: updatedDocs.length
// },
// inserted: {
// documents: null,
// count: 0
// }
// };
}
// if (callback) callback(null, res);
// return res;
}
function doResolve(result) {
if (callback) {
callback(null, result);
}
resolve(result);
}
function doReject(error) {
if (callback) {
callback(error, null);
}
reject(error);
}
});
};
/***
* Removes one or many documents
*
* @method Collection#remove
*
* @param {Object|Array|String} [selection={}] - The selection for matching documents
* @param {Object} [options] - Additional options
*
* @param {Number} [options.justOne=false] - Deletes the first occurrence of the selection
* @param {Object} [options.writeConcern=null] - An object expressing the write concern
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Array<Obejct>>} Promise with the deleted documents
*/
Collection.prototype.remove = function (selection, options, callback) {
var self = this;
if (_.isNil(selection)) {
selection = {};
}
if (_.isFunction(selection)) {
callback = selection;
selection = {};
}
if (_.isFunction(options)) {
callback = options;
options = {};
}
if (_.isNil(options)) {
options = { justOne: false };
}
// If we are not passing a selection and we are not removing just one, is the same as a drop
if (getObjectSize(selection) === 0 && !options.justOne) {
return self.drop(options, callback);
}
else {
return new Promise(function (resolve, reject) {
// Check special case where we are using an objectId
if (selection instanceof document_1.ObjectId) {
selection = {
_id: selection
};
}
if (!_.isNil(callback) && !_.isFunction(callback)) {
self.logger.throw("callback must be a function");
}
self.find(selection, null, null, callback)
.then(function (documents) {
var e_5, _a;
var docs = [];
try {
for (var documents_1 = __values(documents), documents_1_1 = documents_1.next(); !documents_1_1.done; documents_1_1 = documents_1.next()) {
var doc = documents_1_1.value;
var idx = self.doc_indexes[doc._id];
delete self.doc_indexes[doc._id];
self.docs.splice(idx, 1);
docs.push(doc);
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (documents_1_1 && !documents_1_1.done && (_a = documents_1.return)) _a.call(documents_1);
}
finally { if (e_5) throw e_5.error; }
}
/***
* "remove" event.
*
* @event MongoPortable~remove
*
* @property {Object} collection - Information about the collection
* @property {Object} selector - The selection of the query
* @property {Object} docs - The deleted documents information
*/
self.emit("remove", {
collection: self,
selector: selection,
docs: docs
}).then(function () {
if (callback) {
callback(null, docs);
}
resolve(docs);
}).catch(function (error) {
if (callback) {
callback(error, null);
}
reject(error);
});
}).catch(function (error) {
if (callback) {
callback(error, null);
}
reject(error);
});
});
}
};
/***
* Alias for {@link Collection#remove}
*
* @method Collection#delete
*/
Collection.prototype.delete = function (selection, options, callback) {
return this.remove(selection, options, callback);
};
/***
* Alias for {@link Collection#remove}
*
* @method Collection#destroy
*/
Collection.prototype.destroy = function (selection, options, callback) {
return this.remove(selection, options, callback);
};
/***
* Drops a collection
*
* @method Collection#drop
*
* @param {Object} [ options] - Additional options
*
* @param {Number} [options.dropIndexes=false] - True if we want to drop the indexes too
* @param {Object} [options.writeConcern=null] - An object expressing the write concern
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Object[]>} Promise with the deleted documents
*/
Collection.prototype.drop = function (options, callback) {
var self = this;
return new Promise(function (resolve, reject) {
if (_.isNil(options)) {
options = {};
}
if (_.isFunction(options)) {
callback = options;
options = {};
}
if (!_.isNil(callback) && !_.isFunction(callback)) {
self.logger.throw("callback must be a function");
}
self.find(null, null, { limit: -1 }).then(function (docs) {
self.doc_indexes = {};
self.docs = [];
if (options.dropIndexes) {
// TODO
}
self.emit("dropCollection", {
collection: self,
indexes: !!options.dropIndexes
}).then(function () {
if (callback) {
callback(null, docs);
}
resolve(docs);
}).catch(function (error) {
if (callback) {
callback(error, false);
}
reject();
});
}).catch(function (error) {
if (callback) {
callback(error, false);
}
reject();
});
});
};
/***
* Insert or update a document. If the document has an "_id" is an update (with upsert), if not is an insert.
*
* @method Collection#save
*
* @param {Object} doc - Document to be inserted/updated
*
* @param {Number} [options.dropIndexes=false] - True if we want to drop the indexes too
* @param {Object} [options.writeConcern=null] - An object expressing the write concern
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Object>} Returns a promise with the inserted document or the update information
*/
Collection.prototype.save = function (doc, options, callback) {
if (_.isNil(doc) || _.isFunction(doc)) {
this.logger.throw("You must pass a document");
}
if (_.isFunction(options)) {
callback = options;
options = {};
}
if (_.isNil(options)) {
options = {};
}
if (_.hasIn(doc, "_id")) {
options.upsert = true;
return this.update({ _id: doc._id }, doc, options, callback);
}
else {
return this.insert(doc, options, callback);
}
};
/***
* @ignore
*/
Collection.prototype.ensureIndex = function () {
// TODO Implement EnsureIndex
this.logger.throw("Collection#ensureIndex unimplemented by driver");
};
// TODO document (at some point)
// TODO test
// TODO obviously this particular implementation will not be very efficient
/***
* @ignore
*/
Collection.prototype.backup = function (backupID, callback) {
var self = this;
return new Promise(function (resolve, reject) {
if (_.isFunction(backupID)) {
callback = backupID;
backupID = new document_1.ObjectId().toString();
}
if (!_.isNil(callback) && !_.isFunction(callback)) {
self.logger.throw("callback must be a function");
}
self.snapshots[backupID] = _.cloneDeep(self.docs);
self.emit("snapshot", {
collection: self,
backupID: backupID,
documents: self.snapshots[backupID]
}).then(function () {
var result = {
backupID: backupID,
documents: self.snapshots[backupID]
};
if (callback) {
callback(null, result);
}
resolve(result);
}).catch(function (error) {
if (callback) {
callback(error, null);
}
reject(error);
});
});
};
// Lists available Backups
/***
* @ignore
*/
Collection.prototype.backups = function ( /*callback*/) {
// if (!_.isNil(callback) && !_.isFunction(callback)) this.logger.throw("callback must be a function");
var e_6, _a;
var backups = [];
try {
for (var _b = __values(Object.keys(this.snapshots)), _c = _b.next(); !_c.done; _c = _b.next()) {
var id = _c.value;
backups.push({ id: id, documents: this.snapshots[id] });
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_6) throw e_6.error; }
}
// if (callback) callback(null, backups);
return backups;
};
// Lists available Backups
/***
* @ignore
*/
Collection.prototype.removeBackup = function (backupID /*, callback*/) {
// if (_.isFunction(backupID)) {
// callback = backupID;
// backupID = null;
// }
if (_.isNil(backupID)) {
this.logger.throw("backupID required");
}
// if (!_.isNil(callback) && !_.isFunction(callback)) this.logger.throw("callback must be a function");
var result = null;
if (backupID) {
delete this.snapshots[_.toString(backupID)];
result = backupID;
// } else {
// this.snapshots = {};
// result = true;
}
// if (callback) callback(null, result);
return result;
};
Collection.prototype.clearBackups = function () {
// TODO
};
// Restore the snapshot. If no snapshot exists, raise an exception;
/***
* @ignore
*/
Collection.prototype.restore = function (backupID, callback) {
var self = this;
return new Promise(function (resolve, reject) {
var e_7, _a;
if (_.isFunction(backupID)) {
callback = backupID;
backupID = null;
}
if (!_.isNil(callback) && !_.isFunction(callback)) {
self.logger.throw("callback must be a function");
}
var snapshotCount = getObjectSize(self.snapshots);
var backupData = null;
if (snapshotCount === 0) {
self.logger.throw("There is no snapshots");
}
else {
if (!backupID) {
if (snapshotCount === 1) {
self.logger.info("No backupID passed. Restoring the only snapshot");
try {
// Retrieve the only snapshot
for (var _b = __values(Object.keys(self.snapshots)), _c = _b.next(); !_c.done; _c = _b.next()) {
var key = _c.value;
backupID = key;
}
}
catch (e_7_1) { e_7 = { error: e_7_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_7) throw e_7.error; }
}
}
else {
self.logger.throw("The are several snapshots. Please specify one backupID");
}
}
}
backupData = self.snapshots[backupID];
if (!backupData) {
self.logger.throw("Unknown Backup ID: " + backupID);
}
self.docs = backupData;
self.emit("restore", {
collection: self,
backupID: backupID
}).then(function () {
if (callback) {
callback(null, backupID);
}
resolve(backupID);
}).catch(function (error) {
if (callback) {
callback(error, null);
}
reject(error);
});
});
};
/***
* Calculates aggregate values for the data in a collection
*
* @method Collection#aggregate
*
* @param {Array} pipeline - A sequence of data aggregation operations or stages
* @param {Object} [options] - Additional options
*
* @param {Boolean} [options.forceFetch=false] - If set to'"true" returns the array of documents already fetched
*
* @returns {Array|Cursor} If "options.forceFetch" set to true returns the array of documents, otherwise returns a cursor
*/
Collection.prototype.aggregate = function (pipeline, options) {
if (options === void 0) { options = { forceFetch: false }; }
var e_8, _a, e_9, _b;
if (_.isNil(pipeline) || !_.isArray(pipeline)) {
this.logger.throw('The "pipeline" param must be an array');
}
var aggregation = new aggregation_1.Aggregation(pipeline);
try {
for (var pipeline_1 = __values(pipeline), pipeline_1_1 = pipeline_1.next(); !pipeline_1_1.done; pipeline_1_1 = pipeline_1.next()) {
var stage = pipeline_1_1.value;
try {
for (var _c = __values(Object.keys(stage)), _d = _c.next(); !_d.done; _d = _c.next()) {
var key = _d.value;
if (key.substr(0, 1) !== "$") {
this.logger.throw("The pipeline stages must begin with '$'");
}
if (!aggregation.validStage(key)) {
this.logger.throw("Invalid stage \"" + key + "\"");
}
break;
}
}
catch (e_9_1) { e_9 = { error: e_9_1 }; }
finally {
try {
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
}
finally { if (e_9) throw e_9.error; }
}
}
}
catch (e_8_1) { e_8 = { error: e_8_1 }; }
finally {
try {
if (pipeline_1_1 && !pipeline_1_1.done && (_a = pipeline_1.return)) _a.call(pipeline_1);
}
finally { if (e_8) throw e_8.error; }
}
var result = aggregation.aggregate(this);
return result; // change to cursor
};
/***
* @ignore
*/
Collection.prototype.rename = function (newName) {
if (_.isString(newName)) {
if (this.name !== newName) {
Collection.checkCollectionName(newName);
var dbName = this.name.split(".").length > 1 ? this.name.split(".")[0] : "";
this.name = newName;
this.fullName = dbName + "." + this.name;
return this;
}
}
else {
// Error
return null;
}
};
/***
* @ignore
*/
Collection.checkCollectionName = function (collectionName) {
if (!_.isString(collectionName)) {
jsw_logger_1.JSWLogger.instance.throw("collection name must be a String");
}
if (!collectionName || collectionName.indexOf("..") !== -1) {
jsw_logger_1.JSWLogger.instance.throw("collection names cannot be empty");
}
if (collectionName.indexOf("$") !== -1 && collectionName.match(/((^\$cmd)|(oplog\.\$main))/) === null) {
jsw_logger_1.JSWLogger.instance.throw("collection names must not contain '$'");
}
if (collectionName.match(/^system\./) !== null) {
jsw_logger_1.JSWLogger.instance.throw("collection names must not start with 'system.' (reserved for internal use)");
}
if (collectionName.match(/^\.|\.$/) !== null) {
jsw_logger_1.JSWLogger.instance.throw("collection names must not start or end with '.'");
}
};
/***
* @ignore
*/
Collection._noCreateModifiers = {
$unset: true,
$pop: true,
$rename: true,
$pull: true,
$pullAll: true
};
return Collection;
}());
exports.Collection = Collection;
var applyModifier = function (_docUpdate, key, val) {
var e_10, _a;
var doc = _.cloneDeep(_docUpdate);
// var mod = modifiers[key];
if (!modifiers[key]) {
jsw_logger_1.JSWLogger.instance.throw("Invalid modifier specified: " + key);
}
try {
for (var _b = __values(Object.keys(val)), _c = _b.next(); !_c.done; _c = _b.next()) {
var keypath = _c.value;
var value = val[keypath];
var keyparts = keypath.split(".");
modify(doc, keyparts, value, key);
// var no_create = !!Collection._noCreateModifiers[key];
// var forbid_array = (key === "$rename");
// var target = Collection._findModTarget(_docUpdate, keyparts, no_create, forbid_array);
// var field = keyparts.pop();
// mod(target, field, value, keypath, _docUpdate);
}
}
catch (e_10_1) { e_10 = { error: e_10_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_10) throw e_10.error; }
}
return doc;
};
var modify = function (document, keyparts, value, key, level) {
if (level === void 0) { level = 0; }
for (var i = level; i < keyparts.length; i++) {
var path = keyparts[i];
var isNumeric = /^[0-9]+$/.test(path);
var target = document[path];
var create = _.hasIn(Collection._noCreateModifiers, key) ? false : true;
if (!create && (!_.isObject(document) || _.isNil(target))) {
jsw_logger_1.JSWLogger.instance.throw("The element \"" + path + "\" must exists in \"" + JSON.stringify(document) + "\"");
}
if (_.isArray(document)) {
// Do not allow $rename on arrays
if (key === "$rename") {
return null;
}
// Only let the use of "arrayfield.<numeric_index>.subfield"
if (isNumeric) {
path = _.toNumber(path);
}
else {
jsw_logger_1.JSWLogger.instance.throw("The field \"" + path + "\" can not be appended to an array");
}
// Fill the array to the desired length
while (document.length < path) {
document.push(null);
}
}
if (i < keyparts.length - 1) {
if (_.isNil(target)) {
// If we are accessing with "arrayField.<numeric_index>"
if (_.isFinite(_.toNumber(keyparts[i + 1]))) { // || keyparts[i + 1] === '$' // TODO "arrayField.$"
target = [];
}
else {
target = {};
}
}
document[path] = modify(target, keyparts, value, key, level + 1);
return document;
}
else {
modifiers[key](document, path, value);
return document;
}
}
};
/***
* @ignore
*/
var modifiers = {
$inc: function (target, field, arg) {
if (!_.isNumber(arg)) {
jsw_logger_1.JSWLogger.instance.throw("Modifier $inc allowed for numbers only");
}
if (field in target) {
if (!_.isNumber(target[field])) {
jsw_logger_1.JSWLogger.instance.throw("Cannot apply $inc modifier to non-number");
}
target[field] += arg;
}
else {
target[field] = arg;
}
},
$set: function (target, field, arg) {
target[field] = _.cloneDeep(arg);
},
$unset: function (target, field, arg) {
if (!_.isNil(target)) {
if (_.isArray(target)) {
if (field in target) {
target[field] = null;
}
}
else {
delete target[field];
}
}
},
$push: function (target, field, arg) {
var fieldTarget = target[field];
if (_.isNil(fieldTarget)) {
target[field] = [arg];
}
else if (!_.isArray(fieldTarget)) {
jsw_logger_1.JSWLogger.instance.throw("Cannot apply $push modifier to non-array");
}
else {
fieldTarget.push(_.cloneDeep(arg));
}
},
$pushAll: function (target, field, arg) {
var e_11, _a;
var fieldTarget = target[field];
if (_.isNil(fieldTarget)) {
target[field] = arg;
}
else if (!_.isArray(fieldTarget)) {
jsw_logger_1.JSWLogger.instance.throw("Modifier $pushAll/pullAll allowed for arrays only");
}
else {
try {
for (var arg_1 = __values(arg), arg_1_1 = arg_1.next(); !arg_1_1.done; arg_1_1 = arg_1.next()) {
var argValue = arg_1_1.value;
fieldTarget.push(argValue);
}
}
catch (e_11_1) { e_11 = { error: e_11_1 }; }
finally {
try {
if (arg_1_1 && !arg_1_1.done && (_a = arg_1.return)) _a.call(arg_1);
}
finally { if (e_11) throw e_11.error; }
}
}
},
$addToSet: function (target, field, arg) {
var e_12, _a;
var fieldTarget = target[field];
if (_.isNil(fieldTarget))