graphql-anywhere-mongodb
Version:
Use graphql to query mongodb
148 lines • 5.49 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const graphql_anywhere_1 = require("graphql-anywhere");
const { keys } = Object;
// Arguments that are only valid for the entire collection
exports.ValidCollectionArgs = ['limit', 'skip'];
const validateCollectionArgs = (args) => keys(args)
.filter(arg => !exports.ValidCollectionArgs.includes(arg))
.forEach(arg => {
throw new Error(`Argument '${arg}' is not a valid collection-level argument.`);
});
// Arguments that are only valid for non-leaf nodes
exports.ValidNonLeafArguments = ['include'];
const validateNonLeafArgs = (args) => keys(args)
.filter(arg => !exports.ValidNonLeafArguments.includes(arg))
.forEach(arg => {
throw new Error(`Argument '${arg}' is not a valid non-leaf-level argument.`);
});
// Arguments that are valid for any leaf
exports.ValidLeafArguments = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'exists', 'regex', 'options'];
const validateLeafArguments = (args) => keys(args)
.filter(arg => !exports.ValidLeafArguments.includes(arg))
.forEach(arg => {
throw new Error(`Argument '${arg}' is not a valid field-level argument.`);
});
// Special arguments that should be handled after other operations
const SpecialOperations = ['options'];
const precedenceSort = (a) => SpecialOperations.includes(a) ? 1 : -1;
function graphqlToMongo(query, variables) {
// Use resolver to build an intermediate model of how the mongo query will look
const context = {};
const result = graphql_anywhere_1.default(resolve, query, null, context, variables);
// Build data structure to hold query info
const queries = keys(result)
.map(collection => {
const baseQuery = {
collection,
query: {},
fields: {},
sort: {}
};
// Add on any extra parameters like limit, skip, sort, etc.
const extraParams = context[collection].args || {};
keys(extraParams)
.forEach(key => {
if (typeof extraParams[key] !== 'undefined') {
baseQuery[key] = extraParams[key];
}
});
return baseQuery;
});
// Process each collection subtree to discover how the mongo query should look
queries
.forEach(queryInfo => buildQuery(result[queryInfo.collection], [], queryInfo, context));
return queries;
}
exports.graphqlToMongo = graphqlToMongo;
function resolve(fieldName, rootValue, args, context, info) {
// Calculate path to field
const path = [
...(rootValue && rootValue.path
? rootValue.path
: []),
fieldName
];
const pathKey = path.join('.');
// Attach metadata
context[pathKey] = {
directives: info.directives || {},
args: args || {},
};
// Check for args at the collection level like limit & skip
if (!rootValue && args) {
validateCollectionArgs(args);
}
// Error if applying args to anything other than the collection
// TODO: Support array field types
if (rootValue && !info.isLeaf && args) {
validateNonLeafArgs(args);
}
// Validate leaf args if present
if (info.isLeaf && args) {
validateLeafArguments(args);
}
return {
path,
isQuery: true
};
}
function buildQuery(node, parents, queryInfo, context, ancestorProjected = false) {
if (!node) {
return;
}
const parentPath = parents.join('.');
for (const field of keys(node)) {
const path = [...parents, field];
const fieldPath = path.join('.');
const metaData = context[`${queryInfo.collection}.${fieldPath}`];
const childNode = node[field];
const args = metaData.args || {};
// Apply projection
if (!ancestorProjected && args.include === true) {
queryInfo.fields[fieldPath] = 1;
ancestorProjected = true;
}
// Apply sorting
if ('sort' in metaData.directives) {
queryInfo.sort[fieldPath] = 1;
}
if ('sortDesc' in metaData.directives) {
queryInfo.sort[fieldPath] = -1;
}
// Process leaf queries
if (childNode.isQuery) {
const operations = queryInfo.query[fieldPath] = queryInfo.query[fieldPath] || {};
for (const operation of keys(args).sort(precedenceSort)) {
const value = args[operation];
if (typeof value !== 'undefined') {
applyOperation(operations, operation, value);
}
}
// If results in empty object, blank it out
if (operations && keys(operations).length === 0) {
delete queryInfo.query[fieldPath];
}
// Add leaf fields to projection
if (!ancestorProjected) {
queryInfo.fields[fieldPath] = 1;
}
}
else if (keys(childNode).length > 0) {
// Recursively process children for nested objects
buildQuery(childNode, path, queryInfo, context, ancestorProjected);
}
}
}
function applyOperation(obj, operation, value) {
switch (operation) {
case 'options':
if (typeof obj['$regex'] !== 'undefined') {
obj[`$options`] = value;
}
break;
default:
obj[`$${operation}`] = value;
}
}
//# sourceMappingURL=graphql-to-mongo.js.map