UNPKG

graphql-anywhere-mongodb

Version:
148 lines 5.49 kB
"use strict"; 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