graphql-anywhere-mongodb
Version:
Use graphql to query mongodb
158 lines • 6.69 kB
JavaScript
;
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