UNPKG

koa-mongo-router

Version:
416 lines (415 loc) 18.7 kB
Object.defineProperty(exports, "__esModule", { value: true }); exports.deleteCollectionIndexRoute = exports.postCollectionIndicesRoute = exports.getCollectionIndicesRoute = exports.deleteCollectionSchemaRoute = exports.putCollectionSchemaRoute = exports.getCollectionSchemaRoute = exports.deleteCollectionItemRoute = exports.patchCollectionItemRoute = exports.putCollectionItemRoute = exports.getCollectionItemRoute = exports.deleteCollectionItemsRoute = exports.patchCollectionItemsRoute = exports.postCollectionItemsRoute = exports.putCollectionItemsRoute = exports.getCollectionItemsRoute = exports.deleteDatabaseRoute = exports.getDatabaseCollectionsRoute = exports.getDatabasesRoute = exports.databaseFunctions = void 0; const database_router_options_1 = require("./database-router-options"); const mongo_functions_1 = require("./mongo-functions"); const query_string_1 = require("./query-string"); const promise_transform_stream_1 = require("./utils/promise-transform-stream"); const JSONStream = require('JSONStream'); // tslint:disable-line const emptyObject = {}; exports.databaseFunctions = mongo_functions_1.mongoDatabaseFunctions; function getDatabasesRoute(options) { return async function getDatabasesRouteHandler(ctx) { ctx.body = await exports.databaseFunctions.getDatabases(); }; } exports.getDatabasesRoute = getDatabasesRoute; function getDatabaseCollectionsRoute(options) { return async function getDatabaseCollectionsRouteHandler(ctx) { const params = ctx.state; ctx.body = await exports.databaseFunctions.getDatabaseCollections(params.database); }; } exports.getDatabaseCollectionsRoute = getDatabaseCollectionsRoute; function deleteDatabaseRoute(options) { return async function deleteDatabaseRouteHandler(ctx) { const params = ctx.state; ctx.body = await exports.databaseFunctions.deleteDatabase(params.database); }; } exports.deleteDatabaseRoute = deleteDatabaseRoute; // TODO queryString support for $explain function getCollectionItemsRoute(options) { return async function getCollectionItemsRouteHandler(ctx) { const params = ctx.state; const collectionQuery = query_string_1.parseQueryString(ctx.request.querystring); if (collectionQuery.explain) { const explanation = await exports.databaseFunctions.getCollectionItemsExplain(params.database, params.collection, collectionQuery); ctx.body = { query: collectionQuery, explanation, }; return; } const result = await exports.databaseFunctions.getCollectionItemsStream(params.database, params.collection, collectionQuery); if (result.count != undefined) { ctx.set('X-Total-Count', result.count.toString()); } const stream = JSONStream.stringify('[', ',', ']'); const transform = database_router_options_1.getItemTransform(options, params.database, params.collection); if (transform != undefined) { let concurrency = 100; if (ctx.query.$concurrency != undefined) { concurrency = Number(ctx.query.$concurrency); ctx.assert(Number.isInteger(concurrency), 400, 'concurrency must be an integer'); if (concurrency === 0 || concurrency === -1) { concurrency = Number.MAX_SAFE_INTEGER; } ctx.assert(concurrency > 0, 400, 'concurrency must be greater than 0'); } const promiseTransformStream = new promise_transform_stream_1.PromiseTransformStream(transform, concurrency); result.pipe(promiseTransformStream).pipe(stream); } else { result.pipe(stream); } ctx.response.type = 'application/json; charset=utf-8'; ctx.set('Trailer', 'X-Count'); let count = 0; stream.on('data', () => { count++; }); ctx.res.on('wantTrailers', () => { ctx.res.addTrailers({ 'X-Count': count.toString() }); }); ctx.body = stream; }; } exports.getCollectionItemsRoute = getCollectionItemsRoute; function putCollectionItemsRoute(options) { return async function putCollectionItemsRouteHandler(ctx) { const params = ctx.state; const objectIDs = []; const modified = []; const inserted = []; const unchanged = []; const failed = []; let promises = []; let activeCount = 0; let paused = false; let concurrency = 100; if (ctx.query.$concurrency != undefined) { concurrency = Number(ctx.query.$concurrency); ctx.assert(Number.isInteger(concurrency), 400, 'concurrency must be an integer'); if (concurrency === 0 || concurrency === -1) { concurrency = Number.MAX_SAFE_INTEGER; } ctx.assert(concurrency > 0, 400, 'concurrency must be greater than 0'); } let transform = database_router_options_1.putItemTransform(options, params.database, params.collection); if (transform == undefined) { transform = (item) => Promise.resolve(item); } 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) { objectIDs.push(item._id); promises.push(transform(item).then((transformedItem) => { return exports.databaseFunctions .putCollectionItem(params.database, params.collection, transformedItem._id, transformedItem) .then((status) => { switch (status) { case 200: modified.push(transformedItem._id); break; case 201: inserted.push(transformedItem._id); break; case 204: unchanged.push(transformedItem._id); break; default: throw new Error(`PUT Unknown Status: ${status}`); } }) .catch(() => { failed.push(transformedItem._id); }) .finally(() => { activeCount--; if (paused) { paused = false; jsonStream.resume(); } }); })); } else { promises.push(transform(item).then((transformedItem) => { return exports.databaseFunctions .postCollectionItems(params.database, params.collection, transformedItem) .then((result) => { switch (result.status) { case 201: inserted.push(result._id); objectIDs.push(result._id); break; default: throw new Error(`POST Unknown Status: ${status}`); } }) .catch(() => { failed.push(item._id); }) .finally(() => { activeCount--; if (paused) { paused = false; jsonStream.resume(); } }); })); } activeCount++; if (activeCount >= concurrency) { paused = true; jsonStream.pause(); } }) .on('error', /* istanbul ignore next */ function (err) { reject(err); }) .on('end', function () { resolve(); }); ctx.req.pipe(jsonStream); }); } catch (err) { return { status: 400, }; } await Promise.all(promises); promises = []; const deleted = []; const deleteQuery = { filter: { _id: { $nin: objectIDs, }, }, fields: { _id: 1, }, }; const deleteItems = await exports.databaseFunctions.getCollectionItems(params.database, params.collection, deleteQuery); for (const deleteItem of deleteItems.items) { promises.push(exports.databaseFunctions .deleteCollectionItem(params.database, params.collection, deleteItem._id) .then((status) => { switch (status) { case 200: deleted.push(deleteItem._id); break; } }) .catch(() => { /* do nothing */ })); } await Promise.all(promises); const response = { inserted, modified, unchanged, deleted, failed, }; ctx.body = response; }; } exports.putCollectionItemsRoute = putCollectionItemsRoute; function postCollectionItemsRoute(options) { return async function postCollectionItemsRouteHandler(ctx) { let body = ctx.request.body; ctx.assert(typeof body !== 'string', 400, 'body must be json object'); ctx.assert(!Array.isArray(body), 400, 'body must be json object'); ctx.assert(body._id === undefined, 400, 'body cannot contain an _id'); const params = ctx.state; const transform = database_router_options_1.putItemTransform(options, params.database, params.collection); if (transform !== undefined) { body = await transform(body); } const result = await exports.databaseFunctions.postCollectionItems(params.database, params.collection, body); ctx.status = result.status; // TODO ctx.body = { _id: result._id, }; }; } exports.postCollectionItemsRoute = postCollectionItemsRoute; function patchCollectionItemsRoute(options) { return async function patchCollectionItemsRouteHandler(ctx) { ctx.assert(!Array.isArray(ctx.request.body), 400, 'request body cannot be an array'); const params = ctx.state; const result = await exports.databaseFunctions.patchCollectionItems(params.database, params.collection, convertPatch(ctx.request.body), ctx.request.querystring); ctx.status = result.status; ctx.body = { matchedCount: result.matchedCount, modifiedCount: result.modifiedCount, }; }; } exports.patchCollectionItemsRoute = patchCollectionItemsRoute; function deleteCollectionItemsRoute(options) { return async function deleteCollectionItemsRouteHandler(ctx) { const params = ctx.state; const result = await exports.databaseFunctions.deleteCollectionItems(params.database, params.collection, ctx.request.querystring); ctx.status = result.status; ctx.body = result; }; } exports.deleteCollectionItemsRoute = deleteCollectionItemsRoute; // TODO - getItem queryString support for $fields function getCollectionItemRoute(options) { return async function getCollectionItemRouteHandler(ctx) { const params = ctx.state; const result = await exports.databaseFunctions.getCollectionItem(params.database, params.collection, params.id); ctx.status = result.status; if (result.status !== 404) { const transform = database_router_options_1.getItemTransform(options, params.database, params.collection); if (transform != undefined) { result.item = await transform(result.item); } ctx.body = result.item; } }; } exports.getCollectionItemRoute = getCollectionItemRoute; function putCollectionItemRoute(options) { return async function putCollectionItemRouteHandler(ctx) { ctx.assert(!Array.isArray(ctx.request.body), 400, 'request body cannot be an array'); const params = ctx.state; let item = ctx.request.body; ctx.assert(item._id == undefined || item._id === params.id, 400, 'body _id does not match id in route'); const transform = database_router_options_1.putItemTransform(options, params.database, params.collection); if (transform !== undefined) { item = await transform(item); } if (ctx.request.get('if-match') === '*') { ctx.status = await exports.databaseFunctions.putCollectionItemOnlyIfAlreadyExists(params.database, params.collection, params.id, item); if (ctx.status !== 404 && ctx.status !== 204) { ctx.body = emptyObject; } } else if (ctx.request.get('if-none-match') === '*') { ctx.status = await exports.databaseFunctions.putCollectionItemOnlyIfDoesNotAlreadyExist(params.database, params.collection, params.id, item); if (ctx.status !== 404 && ctx.status !== 204) { ctx.body = emptyObject; } } else { ctx.status = await exports.databaseFunctions.putCollectionItem(params.database, params.collection, params.id, item); if (ctx.status !== 404 && ctx.status !== 204) { ctx.body = emptyObject; } } }; } exports.putCollectionItemRoute = putCollectionItemRoute; function patchCollectionItemRoute(options) { return async function patchCollectionItemRoutehandler(ctx) { ctx.assert(!Array.isArray(ctx.request.body), 400, 'request body cannot be an array'); const params = ctx.state; ctx.status = await exports.databaseFunctions.patchCollectionItem(params.database, params.collection, params.id, convertPatch(ctx.request.body)); if (ctx.status !== 404 && ctx.status !== 204) { ctx.body = emptyObject; } }; } exports.patchCollectionItemRoute = patchCollectionItemRoute; function convertPatch(patch) { const convertedPatch = {}; for (const key of Object.keys(patch)) { if (key.startsWith('$')) { convertedPatch[key] = patch[key]; } else { if (convertedPatch.$set == undefined) { convertedPatch.$set = {}; } convertedPatch.$set[key] = patch[key]; } } return convertedPatch; } function deleteCollectionItemRoute(options) { return async function deleteCollectionItemRouteHandler(ctx) { const params = ctx.state; ctx.status = await exports.databaseFunctions.deleteCollectionItem(params.database, params.collection, params.id); if (ctx.status !== 404) { ctx.body = emptyObject; } }; } exports.deleteCollectionItemRoute = deleteCollectionItemRoute; function getCollectionSchemaRoute(options) { return async function getCollectionSchemaRouteHandler(ctx) { const params = ctx.state; const result = await exports.databaseFunctions.getCollectionSchema(params.database, params.collection); ctx.status = result.status; if (result.schema != undefined) { ctx.body = result.schema; } }; } exports.getCollectionSchemaRoute = getCollectionSchemaRoute; function putCollectionSchemaRoute(options) { return async function putCollectionSchemaRouteHandler(ctx) { const params = ctx.state; const result = await exports.databaseFunctions.putCollectionSchema(params.database, params.collection, ctx.request.body); ctx.status = result.status; if (ctx.status !== 404 && ctx.status !== 204) { ctx.body = ctx.status; } }; } exports.putCollectionSchemaRoute = putCollectionSchemaRoute; function deleteCollectionSchemaRoute(options) { return async function deleteCollectionSchemaRouteHandler(ctx) { const params = ctx.state; ctx.status = await exports.databaseFunctions.deleteCollectionSchema(params.database, params.collection); if (ctx.status !== 404 && ctx.status !== 204) { ctx.body = {}; } }; } exports.deleteCollectionSchemaRoute = deleteCollectionSchemaRoute; function getCollectionIndicesRoute(options) { return async function getCollectionSchemaRouteHandler(ctx) { const params = ctx.state; const result = await exports.databaseFunctions.getCollectionIndices(params.database, params.collection); ctx.body = result; }; } exports.getCollectionIndicesRoute = getCollectionIndicesRoute; function postCollectionIndicesRoute(options) { return async function getCollectionSchemaRouteHandler(ctx) { const params = ctx.state; const result = await exports.databaseFunctions.postCollectionIndex(params.database, params.collection, ctx.request.body); ctx.body = result; }; } exports.postCollectionIndicesRoute = postCollectionIndicesRoute; function deleteCollectionIndexRoute(options) { return async function getCollectionSchemaRouteHandler(ctx) { const params = ctx.state; try { const result = await exports.databaseFunctions.deleteCollectionIndex(params.database, params.collection, params.id); ctx.body = result; } catch (_a) { ctx.status = 404; } }; } exports.deleteCollectionIndexRoute = deleteCollectionIndexRoute;