UNPKG

koa-mongo-router

Version:
430 lines (429 loc) 15.4 kB
Object.defineProperty(exports, "__esModule", { value: true }); exports.mongoDatabaseFunctions = void 0; const mongodb_1 = require("mongodb"); const stream_1 = require("stream"); const mongo_1 = require("./mongo"); const query_string_1 = require("./query-string"); const JSONStream = require('JSONStream'); // tslint:disable-line exports.mongoDatabaseFunctions = { getDatabases, getDatabaseCollections, deleteDatabase, getCollectionItemsExplain, getCollectionItemsStream, getCollectionItems, putCollectionItems, putCollectionItemsStream, postCollectionItems, patchCollectionItems, deleteCollectionItems, getCollectionItem, putCollectionItem, putCollectionItemOnlyIfAlreadyExists, putCollectionItemOnlyIfDoesNotAlreadyExist, patchCollectionItem, deleteCollectionItem, getCollectionSchema, putCollectionSchema, deleteCollectionSchema, getCollectionIndices, postCollectionIndex, deleteCollectionIndex, }; async function getDatabases() { const db = await mongo_1.getDatabase('admin'); return db.admin().listDatabases(); } async function getDatabaseCollections(databaseName) { const db = await mongo_1.getDatabase(databaseName); const collections = await db.collections(); return Promise.all(collections.map(async (collection) => { return Object.assign({ name: collection.collectionName }, (await collection.stats())); })); } async function deleteDatabase(databaseName) { const db = await mongo_1.getDatabase(databaseName); return db.dropDatabase(); } async function getCollectionItemsCursor(databaseName, collectionName, query) { convertFilter(query.filter); const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); // if query includes "invalid" or "valid" get collection schema to only return valid or invalid items let schema; if (query.invalid === true || query.valid === true) { const db = await mongo_1.getDatabase(databaseName); const collectionInfos = await db.listCollections({ name: collectionName }).toArray(); if (collectionInfos.length === 1) { const collectionInfo = collectionInfos[0]; if (collectionInfo.options != undefined && collectionInfo.options.validator != undefined) { schema = collectionInfo.options.validator.$jsonSchema; } } if (schema != undefined) { if (query.invalid === true) { query.filter = { $and: [query.filter, { $nor: [{ $jsonSchema: schema }] }], }; } else { query.filter = { $and: [query.filter, { $jsonSchema: schema }], }; } } } const cursor = collection.find(query.filter); if (query.sort != undefined) { cursor.sort(query.sort); } if (query.skip != undefined) { cursor.skip(query.skip); } if (query.limit != undefined) { cursor.limit(query.limit); } if (query.fields != undefined) { cursor.project(query.fields); } return cursor; } async function getCollectionItemsExplain(databaseName, collectionName, collectionQuery) { const cursor = await getCollectionItemsCursor(databaseName, collectionName, collectionQuery); return cursor.explain(); } async function getCollectionItemsStream(databaseName, collectionName, collectionQuery) { const cursor = await getCollectionItemsCursor(databaseName, collectionName, collectionQuery); let count; if (collectionQuery != undefined && collectionQuery.count === true) { count = await cursor.count(); } const pipe = (destination, options) => cursor.pipe(destination, options); return { count, pipe, }; } async function getCollectionItems(databaseName, collectionName, collectionQuery) { const cursor = await getCollectionItemsCursor(databaseName, collectionName, collectionQuery); let count; if (collectionQuery != undefined && collectionQuery.count === true) { count = await cursor.count(); } const items = await cursor.toArray(); return { count, items }; } async function putCollectionItems(databaseName, collectionName, querystring, items) { const stream = new stream_1.PassThrough({}); stream.write(JSON.stringify(items)); stream.end(); return putCollectionItemsStream(databaseName, collectionName, querystring, stream); } async function putCollectionItemsStream(databaseName, collectionName, querystring, inputStream) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); const query = query_string_1.parseQueryString(querystring); const objectIDs = []; const modifiedIDs = []; const insertedIDs = []; const unchangedIDs = []; const failedIDs = []; const promises = []; const maxAsyncCalls = 100; // TODO allow this to be passed in ... maybe query.concurrency? let activeCount = 0; let paused = false; try { await new Promise((resolve, reject) => { const jsonStream = JSONStream.parse('*') .on('data', function (item) { if (typeof item === 'string') { reject(new Error('Bad Request')); return; } if (item._id != undefined) { item._id = new mongodb_1.ObjectID(item._id); objectIDs.push(item._id); promises.push(collection .replaceOne({ _id: item._id }, item, { upsert: true, }) .then((result) => { if (result.upsertedCount === 1) { insertedIDs.push(item._id); } else if (result.modifiedCount === 1) { modifiedIDs.push(item._id); } else { unchangedIDs.push(item._id); } }) .catch(() => { failedIDs.push(item._id); }) .finally(() => { activeCount--; if (paused) { paused = false; jsonStream.resume(); } })); } else { promises.push(collection .insertOne(item) .then((result) => { insertedIDs.push(result.insertedId); objectIDs.push(result.insertedId); }) .catch(() => { failedIDs.push(item._id); }) .finally(() => { activeCount--; if (paused) { paused = false; jsonStream.resume(); } })); } activeCount++; if (activeCount >= maxAsyncCalls) { paused = true; jsonStream.pause(); } }) .on('error', /* istanbul ignore next */ function (err) { reject(err); }) .on('end', function () { resolve(); }); inputStream.pipe(jsonStream); }); } catch (err) { return { status: 400, }; } await Promise.all(promises); let deleteFilter = { _id: { $nin: objectIDs } }; if (query.filter !== undefined && Object.keys(query.filter).length > 0) { deleteFilter = { $and: [query.filter, deleteFilter], }; } const deleteIDs = (await collection.find(deleteFilter).project({ _id: 1 }).toArray()).map((item) => item._id); await collection.deleteMany({ _id: { $in: deleteIDs } }); const response = { inserted: insertedIDs.map((id) => id.toHexString()), modified: modifiedIDs.map((id) => id.toHexString()), unchanged: unchangedIDs.map((id) => id.toHexString()), deleted: deleteIDs.map((id) => id.toHexString()), failed: failedIDs.map((id) => id.toHexString()), }; return { status: 200, response, }; } async function postCollectionItems(databaseName, collectionName, item) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); const result = await collection.insertOne(item); return { status: 201, _id: result.insertedId.toHexString(), }; } async function patchCollectionItems(databaseName, collectionName, update, querystring) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); const query = query_string_1.parseQueryString(querystring); const result = await collection.updateMany(query.filter, update); return { status: 200, matchedCount: result.matchedCount, modifiedCount: result.modifiedCount, }; } async function deleteCollectionItems(databaseName, collectionName, querystring) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); const query = query_string_1.parseQueryString(querystring); const result = await collection.deleteMany(query.filter); return { status: 200, deletedCount: result.deletedCount, }; } async function getCollectionItem(databaseName, collectionName, id) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); const item = await collection.findOne({ _id: new mongodb_1.ObjectID(id) }); if (item == undefined) { return { status: 404, }; } else { return { status: 200, item, }; } } async function putCollectionItem(databaseName, collectionName, id, item) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); item._id = new mongodb_1.ObjectID(id); const result = await collection.replaceOne({ _id: item._id }, item, { upsert: true }); if (result.upsertedCount === 1) { return 201; } else if (result.modifiedCount === 1) { return 200; } else { return 204; } } async function putCollectionItemOnlyIfAlreadyExists(databaseName, collectionName, id, item) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); item._id = new mongodb_1.ObjectID(id); const result = await collection.replaceOne({ _id: item._id }, item, { upsert: false, }); if (result.modifiedCount === 1) { return 200; } else if (result.matchedCount === 1) { return 204; } else { return 412; } } async function putCollectionItemOnlyIfDoesNotAlreadyExist(databaseName, collectionName, id, item) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); item._id = new mongodb_1.ObjectID(id); const result = await collection.updateOne({ _id: new mongodb_1.ObjectID(id) }, { $setOnInsert: item }, { upsert: true }); if (result.upsertedCount === 1) { return 201; } else { return 412; } } async function patchCollectionItem(databaseName, collectionName, id, patch) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); const result = await collection.updateOne({ _id: new mongodb_1.ObjectID(id) }, patch); if (result.modifiedCount === 1) { return 200; } else if (result.matchedCount === 1) { return 204; } else { return 404; } } async function deleteCollectionItem(databaseName, collectionName, id) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); const result = await collection.deleteOne({ _id: new mongodb_1.ObjectID(id) }); if (result.deletedCount === 1) { return 200; } else { return 404; } } async function getCollectionSchema(databaseName, collectionName) { const db = await mongo_1.getDatabase(databaseName); const collectionInfos = await db.listCollections({ name: collectionName }).toArray(); if (collectionInfos.length === 1 && collectionInfos[0].options != undefined && collectionInfos[0].options.validator != undefined) { return { status: 200, schema: collectionInfos[0].options.validator.$jsonSchema, }; } else { return { status: 404, }; } } async function putCollectionSchema(databaseName, collectionName, schema) { const db = await mongo_1.getDatabase(databaseName); await db.createCollection(collectionName); try { const result = await db.command({ collMod: collectionName, validator: { $jsonSchema: schema, }, validationLevel: 'strict', validationAction: 'error', }); return { status: 200, result, }; } catch (err) { return { status: 400, error: err.message, }; } } async function deleteCollectionSchema(databaseName, collectionName) { const db = await mongo_1.getDatabase(databaseName); try { await db.command({ collMod: collectionName, validator: {}, validationLevel: 'off', validationAction: 'warn', }); return 200; } catch (_a) { return 404; } } async function getCollectionIndices(databaseName, collectionName) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); return collection.indexes(); } async function postCollectionIndex(databaseName, collectionName, index) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); return collection.createIndex(index); } async function deleteCollectionIndex(databaseName, collectionName, id) { const collection = await mongo_1.getDatabaseCollection(databaseName, collectionName); return collection.dropIndex(id); } function convertFilter(filter) { for (const key in filter) { if (key.startsWith('$')) { convertFilter(filter[key]); } else if (key === '_id') { const value = filter[key]; if (typeof value === 'string') { filter[key] = new mongodb_1.ObjectID(value); } else { for (const valueKey of Object.keys(value)) { switch (valueKey) { case '$nin': value[valueKey] = value[valueKey].map((id) => new mongodb_1.ObjectID(id)); break; case '$eq': value[valueKey] = new mongodb_1.ObjectID(value[valueKey]); break; } } } } } }