UNPKG

@warp-works/warpjs-mongo-persistence

Version:

Mongo implementation for persistence layer of WarpWorks

180 lines (150 loc) 6.58 kB
const debug = require('debug')('W2:persistence:mongo'); const Promise = require('bluebird'); const mongodb = require('mongodb'); const path = require('path'); const shelljs = require('shelljs'); const urlTemplate = require('url-template'); const WarpJSPersistence = require('@warp-works/warpjs-persistence'); const convertObjectIdToString = require('./convert-objectid-to-string'); const convertStringToObjectId = require('./convert-string-to-objectid'); const WarpJSMongoPersistenceError = require('./error'); const connectionTemplate = urlTemplate.parse('mongodb://{host}/{dbName}'); const rethrow = WarpJSMongoPersistenceError.rethrow.bind(null, WarpJSMongoPersistenceError); class WarpJSMongoPersistence extends WarpJSPersistence { constructor(host, dbName) { super(host, dbName); // TODO: Instead of fake private, look at WeakMap(). this._db = null; this._client = null; } get _connection() { if (!this._db) { const url = connectionTemplate.expand({ host: this._host, dbName: this._dbName }); return Promise.resolve() .then(() => mongodb.MongoClient.connect(url, { useUnifiedTopology: true })) .then((client) => { this._db = client.db(this._dbName); this._client = client; return this._db; }) .catch(rethrow.bind(null, "Connection error.")); } return Promise.resolve(this._db); } async close(forceClose) { if (this._client) { return this._client.close(forceClose) .then(() => { this._db = null; this._client = null; }) .catch(rethrow.bind(null, "Cannot close connection.")) ; } return Promise.resolve(); } get databases() { return this._connection .then((db) => db.admin().listDatabases()) .then((results) => { if (results.ok) { return results.databases.map((db) => db.name); } // TODO: Check what error we can rethrow? throw new WarpJSMongoPersistenceError("Cannot listDatabases()"); }) .catch(rethrow.bind(null, "Cannot listDatabases().")); } get collections() { return this._connection .then((db) => db.listCollections().toArray()) .catch(rethrow.bind(null, "Cannot get collections.")); } async collection(collectionName) { return this._connection .then((db) => db.collection(collectionName)); } async documentsLimit(collectionName, limit) { return this.collection(collectionName) .then((collection) => collection.find().sort({ lastUpdated: -1 }).limit(limit).toArray()) .then((documents) => documents.map(convertObjectIdToString)) .catch(rethrow.bind(null, "Cannot get documents.")); } async documents(collectionName, query, convertId) { if (convertId && query && query._id) { query._id = mongodb.ObjectID(query._id); } if (convertId && query && query.parentID) { query.parentID = mongodb.ObjectID(query.parentID); } return this.collection(collectionName) .then((collection) => collection.find(query).toArray()) .then((documents) => documents.map(convertObjectIdToString)) .catch(rethrow.bind(null, "Cannot get documents.")); } async findOne(collectionName, query) { const converted = convertStringToObjectId(query); return this.collection(collectionName) .then((collection) => collection.findOne(converted)) .then((doc) => convertObjectIdToString(doc)) .catch(rethrow.bind(null, `Cannot find '${collectionName}'='${converted.id}'.`)); } async save(collectionName, doc) { const converted = convertStringToObjectId(doc); return this.collection(collectionName) .then((collection) => collection.insertOne(converted)) .then((res) => res.ops[0]) .then((instance) => convertObjectIdToString(instance)) .catch(rethrow.bind(null, "Failed to save document.")); } async aggregate(collectionName, collectionJoinKey, foreignCollection, foreignKey, aggregateOutputName, matchQuery) { const lookup = { from: foreignCollection, foreignField: foreignKey, localField: collectionJoinKey, as: aggregateOutputName }; const aggrObj = [{ $lookup: lookup }, { $match: matchQuery }]; return this.collection(collectionName) .then((collection) => collection.aggregate(aggrObj).toArray()) .catch(rethrow.bind(null, "Cannot find user.")); } async remove(collectionName, id) { const converted = convertStringToObjectId(id); return this.collection(collectionName) .then((collection) => collection.deleteOne(converted)) .catch(rethrow.bind(null, "Failed to delete document.")); } async update(collectionName, doc) { const converted = convertStringToObjectId(doc); return this.collection(collectionName) .then((collection) => collection.update({ _id: converted._id }, converted)) .catch(rethrow.bind(null, "Failed to update document.")); } async makeBackup(outputFolder, config) { const dbFolder = path.join(outputFolder, 'dbdump'); const options = { silent: true }; const command = `mongodump --host "${config.host}" -d "${this._dbName}" -o "${dbFolder}"`; return new Promise((resolve, reject) => { if (!shelljs.which('mongodump')) { reject(new WarpJSPersistence(`Cannot find 'mongodump' required to backup mongo data.`)); } else { debug(`makeBackup(): exec: ${command}`); shelljs.exec(command, options, (code, stdout, stderr) => { if (code !== 0) { reject(new WarpJSMongoPersistenceError(`Error executing mongodump: ${stderr}`)); } else { resolve(stdout); } }); } }); } } WarpJSMongoPersistence.Error = WarpJSMongoPersistenceError; module.exports = WarpJSMongoPersistence;