UNPKG

graphql-anywhere-mongodb

Version:
158 lines 6.69 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const parser_1 = require("graphql/language/parser"); const mongo_queries_1 = require("./mongo-queries"); const graphql_to_mongo_1 = require("./graphql-to-mongo"); const log_1 = require("./log"); /** * A mongo client that wraps the standard node MongoDB driver with * an API that allows for making queries using GraphQL. */ class MongoGraphQLClient { /** * Create a new {MongoGraphQLClient}. * @param connection The DB connection. * @param options Options to change the behavior of the client. */ constructor(connection, options) { options = options || {}; if (!connection) { throw new Error(`No mongo connection passed`); } this.connection = connection; this.whitelist = (options.whitelist || []) .map(collection => collection.toLowerCase()); this.includeStack = options.includeStack === true; this.errorFormatter = options.formatError || defaultErrorFormatter; this.defaultLimit = typeof options.defaultLimit === 'number' ? options.defaultLimit : 100; this.maxLimit = typeof options.maxLimit === 'number' ? options.maxLimit : 10000; if (this.defaultLimit > this.maxLimit) { throw new Error('Default limit must be less than or equal to max limit'); } log_1.log('Mongo GraphQL client initialized with options', this.getOptions()); } /** * Gets the options that this client was configured with. */ getOptions() { return { database: this.connection.databaseName, whitelist: this.whitelist, includeStack: this.includeStack, errorFormatter: this.errorFormatter, defaultLimit: this.defaultLimit, maxLimit: this.maxLimit }; } /** * Performs a MongoDB find operation for every collection specified in the passed * GraphQL query and returns the results and any errors as a promise. * * @param query The query to perform. * @param variables Variables to use in the query. * @return {Promise<QueryResult>} The result of the queries. */ find(query, variables) { return tslib_1.__awaiter(this, void 0, void 0, function* () { // Convert graphql to info about how to execute query const document = parseDocument(query); const queryInfos = graphql_to_mongo_1.graphqlToMongo(document, variables); // Default limit on query infos if not passed queryInfos .forEach(info => info.limit = typeof info.limit === 'number' ? info.limit : this.defaultLimit); // Check collections against whitelist if (this.whitelist.length) { queryInfos .map(q => q.collection) .filter(collection => !this.whitelist.includes(collection.toLowerCase())) .forEach(collection => { throw new Error(`Can not query collection '${collection}'`); }); } // Enforce max limit queryInfos .filter(info => info.limit > this.maxLimit) .forEach(info => { throw new Error(`Limit of ${info.limit} on collection '${info.collection}' exceeds the maximum of ${this.maxLimit}`); }); // Execute the query and get back the results const results = yield mongo_queries_1.findMultiple(this.connection, queryInfos); // Check for errors const errors = results .filter(result => !!result.error) .map(result => this.errorFormatter(result, this.includeStack)); // Build a cohesive return value with all results return { data: results.reduce((obj, result) => (Object.assign({}, obj, { [result.collection]: result.results })), {}), errors: !errors.length ? undefined : errors, _meta: queryInfos.reduce((obj, info) => (Object.assign({}, obj, { [info.collection]: { limit: info.limit, skip: info.skip || 0 } })), {}) }; }); } /** * Performs a MongoDB findOne operation for exactly one collection and returns * just a single document for that collection. Will throw an error if multiple * collections are included in the query. * * @param query The query to perform,. * @param variables Variables to use in the query. * @return {Promise<QueryResult>} */ findOne(query, variables) { return tslib_1.__awaiter(this, void 0, void 0, function* () { // Convert graphql to info about how to execute query const document = parseDocument(query); const queryInfos = graphql_to_mongo_1.graphqlToMongo(document, variables); // Ensure we only have one if (!queryInfos || queryInfos.length !== 1) { throw new Error(`Must have exactly one query for a findOne operation`); } // Execute the findOne query and get back the results const result = yield mongo_queries_1.findOne(this.connection, queryInfos[0]); // Build a cohesive return value with the results return { data: result.error ? null : { [result.collection]: result.results }, errors: !result.error ? undefined : [this.errorFormatter(result, this.includeStack)] }; }); } } exports.MongoGraphQLClient = MongoGraphQLClient; /** * Default error formatter * @param result The result of the query. * @param includeStack If true, signifies that stack should be printed. */ function defaultErrorFormatter(result, includeStack) { return { collection: result.collection, message: result.error.message || result.error, stack: includeStack === true ? (result.error.stack || '').split('\n') : undefined }; } exports.defaultErrorFormatter = defaultErrorFormatter; function parseDocument(query) { if (typeof query !== 'string' && !query) { throw new Error('Must pass either document or string'); } // Parse query to document if passed string if (typeof query === 'string') { return parser_1.parse(query); } return query; } //# sourceMappingURL=mongo-graphql-client.js.map