mongo-portable
Version:
Portable Pure JS MongoDB - Based on Monglodb (https://github.com/euforic/monglodb.git) by Christian Sullivan (http://RogueSynaptics.com)
616 lines (533 loc) • 20 kB
text/typescript
/***
* @file MongoPortable.js - based on Monglo ({@link https://github.com/Monglo}) by Christian Sullivan <cs@euforic.co> | Copyright (c) 2012
* @version 1.0.0
*
* @author Eduardo Astolfi <eastolfi91@gmail.com>
* @copyright 2016 Eduardo Astolfi <eastolfi91@gmail.com>
* @license MIT Licensed
*/
import { JSWLogger } from "jsw-logger";
import * as _ from "lodash";
import * as Promise from "promise";
import { Options } from "./Options";
import { Collection } from "../collection";
import { ObjectId } from "../document";
import { EventEmitter } from "../emitter";
import { ConnectionHelper, Utils } from "../utils";
/***
* MongoPortable
*
* @module MongoPortable
* @since 0.0.1
*
* @classdesc Portable database with persistence and MongoDB-like API
*
* @param {string} databaseName - Name of the database.
*/
export class MongoPortable extends EventEmitter {
private static _connHelper: ConnectionHelper = new ConnectionHelper();
// static version = "0.0.1";
public pkFactory;
protected logger: JSWLogger;
private _collections: {};
private _stores: Array<object | (() => object)>;
private _databaseName: string;
constructor(databaseName: string, options: any) {
super(options || { log: {} });
this.logger = JSWLogger.instance;
// If we have already this instance, return it
if (MongoPortable._connHelper.hasConnection(databaseName)) {
return MongoPortable._connHelper.getConnection(databaseName).instance;
} else {
this._collections = {};
this._stores = [];
// Check ddbb name format
MongoPortable._connHelper.validateDatabaseName(databaseName);
this._databaseName = databaseName;
MongoPortable._connHelper.addConnection(databaseName, new ObjectId(), this);
}
}
public emit(name: string, args: object): Promise<void> {
return super.emit(name, args, this._stores);
}
/***
* Middleware functions
*
* @param {String} name - Name of the middleware:
* <ul>
* <li>"store": Add a custom store</li>
* </ul>
* @param {Object|Function} fn - Function to implement the middleware
*/
public use(name, obj) {
switch (name) {
case "store":
this._stores.push(obj);
break;
}
}
/***
* Adds a custom stores for remote and local persistence
*
* @param {Object|Function} store - The custom store
*
* @returns {MongoPortable} this - The current Instance
*/
public addStore(store) {
if (_.isNil(store)) { this.logger.throw("missing \"store\" parameter"); }
if (_.isFunction(store)) {
return this.addStoreFromFunction(store);
} else if (_.isObject(store)) {
return this.addStoreFromObject(store);
} else {
this.logger.throw("\"store\" must be a function or object");
}
return this;
}
private addStoreFromFunction(storeClass) {
let store = new storeClass();
return this.addStoreFromObject(store);
}
private addStoreFromObject(store) {
this._stores.push(store);
return this;
}
/***
* Returns a cursor to all the collection information.
*
* @param {String} [collectionName=null] - the collection name we wish to retrieve the information from.
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Array}
*
* @todo Implement
*/
public collectionsInfo(collectionName, callback?) {
this.logger.throw("Not implemented yet");
}
/***
* Alias for {@link MongoPortable#collections}
*
* @method MongoPortable#fetchCollections
*/
public fetchCollections(options, callback?) {
return this.collections(options, callback);
}
/***
* Get the list of all collection for the specified db
*
* @method MongoPortable#collections
*
* @param {Object} [options] - Additional options
*
* @param {Boolean} [options.namesOnly=false] - Return only the collections names
* @param {String|Array} [options.collectionName=null] - The collection name we wish to filter by
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @return {Array}
*/
public collections(options, callback?) {
if (_.isNil(callback) && _.isFunction(options)) {
callback = options;
}
if (_.isNil(options)) { options = {}; }
const self = this;
const collectionList = [];
for (const name in self._collections) {
// Only add the requested collections //TODO Add array type
if (options.collectionName) {
if (name.toLowerCase() === options.collectionName.toLowerCase()) {
if (options.namesOnly) {
collectionList.push(name);
} else {
collectionList.push(self._collections[name]);
}
}
} else {
if (options.namesOnly) {
collectionList.push(name);
} else {
collectionList.push(self._collections[name]);
}
}
}
if (callback) { callback(collectionList); }
return collectionList;
}
/***
* Get the list of all collection names for the specified db,
* by calling MongoPortable#collections with [options.namesOnly = true]
*
* @method MongoPortable#collectionNames
*
* @param {Object} [options] - Additional options.
*
* @param {String|Array} [options.collectionName=null] - The collection name we wish to filter by.
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @return {Array}
*
* {@link MongoPortable#collections}
*/
public collectionNames(options, callback?) {
if (_.isNil(callback) && _.isFunction(options)) {
callback = options;
}
if (_.isNil(options)) { options = {}; }
options.namesOnly = true;
return this.collections(options, callback);
}
/***
* Creates a collection on a server pre-allocating space, need to create f.ex capped collections.
*
* @method MongoPortable#collection
*
* @param {String} collectionName - the collection name we wish to access.
* @param {Object} [options] - returns option results.
*
* @param {Boolean|Object} [options.safe=false] Executes with a getLastError command returning the results of the command on MongoMonglo:
* <ul>
* <li>true</li>
* <li>false</li>
* <li>{ w: {Number}, wtimeout: {Number}}</li>
* <li>{ fsync: true }</li>
* </ul>
* @param {Boolean} [options.serializeFunctions=false] - Serialize functions on the document.
* @param {Boolean} [options.raw=false] - Perform all operations using raw bson objects.
* @param {Object} [options.pkFactory=null] - Object overriding the basic ObjectId primary key generation.
* @param {Boolean} [options.capped=false] - Create a capped collection.
* @param {Number} [options.size=4096] - The size of the capped collection in bytes.
* @param {Number} [options.max=500] - The maximum number of documents in the capped collection.
* @param {Boolean} [options.autoIndexId=false] - Create an index on the _id field of the document, not created automatically on capped collections.
* @param {String} [options.readPreference=ReadPreference.PRIMARY] - Te prefered read preference:
* <ul>
* <li>ReadPreference.PRIMARY</li>
* <li>ReadPreference.PRIMARY_PREFERRED</li>
* <li>ReadPreference.SECONDARY</li>
* <li>ReadPreference.SECONDARY_PREFERRED</li>
* <li>ReadPreference.NEAREST</li>
* </ul>
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @fires {@link MongoStore#createCollection}
*
* @returns {Promise<Collection>}
*/
public collection(collectionName, options, callback?): Promise<Collection> {
return new Promise((resolve, reject) => {
let existing = true;
// var collection;
// var collectionFullName = self.databaseName + "." + collectionName;
if (_.isFunction(options)) {
callback = options;
options = {};
} else {
options = options || {};
}
if (!this._collections[collectionName]) {
this._collections[collectionName] = new Collection(this, collectionName/*, this.pkFactory*//*, options*/);
existing = false;
}
/***
* "createCollection" event.
*
* @event MongoPortable~createCollection
*
* @property {Object} connection - Information about the current database connection
* @property {Object} collection - Information about the collection created
*/
this.emit("createCollection", {
connection: this,
collection: this._collections[collectionName]
}).then(() => {
if (!existing) {
// Letting access the collection by <MongoPortable instance>.<COL_NAME>
Object.defineProperty(MongoPortable.prototype, collectionName, {
enumerable: true,
configurable: true,
writable: false,
value: this._collections[collectionName]
});
}
// return self._collections[collectionName];
if (callback) { callback(null, this._collections[collectionName]); }
resolve(this._collections[collectionName]);
}).catch((error) => {
if (callback) { callback(error, null); }
reject(error);
});
});
}
/***
* Alias for {@link MongoPortable#collection}
*
* @method MongoPortable#createCollection
*/
public createCollection(collectionName, options, callback?): Promise<Collection> {
return this.collection(collectionName, options, callback);
}
/***
* Drop a collection from the database, removing it permanently. New accesses will create a new collection.
*
* @method MongoPortable#dropCollection
*
* @param {String} collectionName - The name of the collection we wish to drop.
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Boolean>} Promise with "true" if dropped successfully
*/
public dropCollection(collectionName, callback?): Promise<boolean> {
return new Promise((resolve, reject) => {
if (this._collections[collectionName]) {
// Drop the collection
this.emit("dropCollection", {
conn: this,
collection: this._collections[collectionName]
}).then(() => {
delete this._collections[collectionName];
if (callback && _.isFunction(callback)) { callback(null, true); }
resolve(true);
}).catch((error) => {
if (callback && _.isFunction(callback)) { callback(error, false); }
reject(error);
});
} else {
const error = new Error("No collection found");
this.logger.throw(error);
if (callback && _.isFunction(callback)) { callback(error, false); }
reject(error);
}
});
}
/***
* Rename a collection.
*
* @method MongoPortable#renameCollection
*
* @param {String} fromCollection - The name of the current collection we wish to rename.
* @param {String} toCollection - The new name of the collection.
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @returns {Promise<Collection>} Promise with the renamed collection
*/
public renameCollection(fromCollection, toCollection, callback?): Promise<Collection> {
return new Promise((resolve, reject) => {
if (!_.isString(fromCollection) || !_.isString(toCollection) || fromCollection === toCollection) {
const error = new Error("You should pass two different string names");
this.logger.throw(error);
if (callback && _.isFunction(callback)) { callback(error, null); }
reject(error);
} else {
Collection.checkCollectionName(toCollection);
if (this._collections[fromCollection]) {
this.emit("renameCollection", {
conn: this,
from: fromCollection,
to: toCollection
}).then(() => {
const renamed = this._collections[fromCollection].rename(toCollection);
if (renamed) {
Utils.renameObjectProperty(this._collections, fromCollection, toCollection);
// this._collections.renameProperty(fromCollection, toCollection);
// this.renameProperty(fromCollection, toCollection);
Utils.renameObjectProperty(this, fromCollection, toCollection);
if (callback && _.isFunction(callback)) { callback(null, renamed); }
resolve(renamed);
} else {
reject(new Error("Could not rename collection"));
}
}).catch((error) => {
this.logger.throw(error);
if (callback && _.isFunction(callback)) { callback(error, null); }
reject(error);
});
} else {
const error = new Error("No collection found");
this.logger.throw(error);
if (callback && _.isFunction(callback)) { callback(error, null); }
reject(error);
}
}
});
}
/***
* Creates an index on the collection.
*
* @method MongoPortable#createIndex
*
* @param {String} collectionName - Name of the collection to create the index on.
* @param {Object} fieldOrSpec - FieldOrSpec that defines the index.
* @param {Object} [options] - Additional options during update.
*
* @param {Boolean|Object} [options.safe=false] Executes with a getLastError command returning the results of the command on MongoMonglo:
* <ul>
* <li>true</li>
* <li>false</li>
* <li>{ w: {Number}, wtimeout: {Number}}</li>
* <li>{ fsync: true }</li>
* </ul>
* @param {Boolean} [options.unique=false] - Creates an unique index
* @param {Boolean} [options.sparse=false] - Creates a sparse index
* @param {Boolean} [options.background=false] - Creates the index in the background, yielding whenever possible
* @param {Boolean} [options.dropDups=false] - A unique index cannot be created on a key that has pre-existing duplicate values.
* If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value
* @param {Number} [options.min=null] - For geospatial indexes set the lower bound for the co-ordinates
* @param {Number} [options.max=null] - For geospatial indexes set the high bound for the co-ordinates
* @param {Number} [options.v=null] - Specify the format version of the indexes
* @param {Number} [options.expireAfterSeconds=null] - Allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher)
* @param {String} [options.name=null] - Override the autogenerated index name (useful if the resulting name is larger than 128 bytes)
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @todo Implement
*/
public createIndex(collectionName, fieldOrSpec, options, callback) {
this.logger.throw("Not implemented yet!");
}
/***
* Ensures that an index exists, if it does not it creates it
*
* @method MongoPortable#ensureIndex
*
* @param {String} collectionName - Name of the collection to create the index on.
* @param {Object} fieldOrSpec - FieldOrSpec that defines the index.
* @param {Object} [options] - Additional options during update.
*
* @param {Boolean|Object} [options.safe=false] - Executes with a getLastError command returning the results of the command on MongoMonglo:
* <ul>
* <li>true</li>
* <li>false</li>
* <li>{ w: {Number}, wtimeout: {Number}}</li>
* <li>{ fsync: true }</li>
* </ul>
* @param {Boolean} [options.unique=false] - Creates an unique index
* @param {Boolean} [options.sparse=false] - Creates a sparse index
* @param {Boolean} [options.background=false] - Creates the index in the background, yielding whenever possible
* @param {Boolean} [options.dropDups=false] - A unique index cannot be created on a key that has pre-existing duplicate values.
* If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value
* @param {Number} [options.min] - For geospatial indexes set the lower bound for the co-ordinates
* @param {Number} [options.max] - For geospatial indexes set the high bound for the co-ordinates
* @param {Number} [options.v] - Specify the format version of the indexes
* @param {Number} [options.expireAfterSeconds] - Allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher)
* @param {String} [options.name] - Override the autogenerated index name (useful if the resulting name is larger than 128 bytes)
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @todo Implement
*/
public ensureIndex(collectionName, fieldOrSpec, options, callback) {
this.logger.throw("Not implemented yet!");
}
/***
* Drop an index on a collection.
*
* @method MongoPortable#dropIndex
*
* @param {String} collectionName - The name of the collection where the command will drop an index.
* @param {String} indexName - Name of the index to drop.
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @todo Implement
*/
public dropIndex(collectionName, indexName, callback) {
this.logger.throw("Not implemented yet!");
}
/***
* Reindex all indexes on the collection
* Warning: "reIndex" is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections.
*
* @method MongoPortable#reIndex
*
* @param {String} collectionName - The name of the collection to reindex
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @todo Implement
**/
public reIndex(collectionName, callback) {
this.logger.throw("Not implemented yet!");
}
/***
* Retrieves this collections index info.
*
* @method MongoPortable#indexInformation
*
* @param {String} collectionName - The name of the collection.
* @param {Object} [options] Additional options during update.
*
* @param {Boolean} [full=false] - Returns the full raw index information.
* @param {String} [readPreference] - The preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST).
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @todo Implement
*/
public indexInformation(collectionName, options, callback) {
this.logger.throw("Not implemented yet!");
}
/***
* Drop the whole database.
*
* @method MongoPortable#dropDatabase
*
* @param {Function} [callback=null] - Callback function to be called at the end with the results
*
* @return {Promise<Boolean>} Promise with "true" if dropped successfully
*/
public dropDatabase(callback?): Promise<boolean> {
return new Promise((resolve, reject) => {
if (MongoPortable._connHelper.hasConnection(this._databaseName)) {
this.emit("dropDatabase", {
conn: this
}).then(() => {
MongoPortable._connHelper.dropConnection(this._databaseName);
this._collections = [];
this._stores = [];
if (callback && _.isFunction(callback)) { callback(null, true); }
resolve(true);
});
} else {
const error = new Error("That database no longer exists");
this.logger.throw(error);
if (callback && _.isFunction(callback)) { callback(error, false); }
reject(error);
}
});
}
/***
* Dereference a dbref, against a db
*
* @param {DBRef} dbRef db reference object we wish to resolve.
* @param {Function} [callback=null] Callback function to be called at the end with the results
*
* @todo Implement
*
* @ignore
*/
public dereference(dbRef, callback) {
// TODO
// var db = this;
// // If we have a db reference then let"s get the db first
// if (dbRef.db !== null) db = this.db(dbRef.db);
// // Fetch the collection and find the reference
// var collection = Monglo.collection(dbRef.namespace);
// collection.findOne({"_id":dbRef.oid}, function(err, result) {
// callback(err, result);
// });
}
/***
* Retrieves the instance of that DDBB name
*
* @param {String} name - The DDBB name
*
* @return {MongoPortable} - The DDBB instance
*/
public static getInstance(name: string) {
if (!_.isNil(name)) {
return MongoPortable._connHelper.getConnection(name);
}
return null;
}
}