koa-mongo-router
Version:
KOA REST API Router for MongoDB
416 lines (415 loc) • 18.7 kB
JavaScript
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;