Files
30-seconds-of-code/node_modules/graphql-compose/mjs/Resolver.mjs
2019-08-20 15:52:05 +02:00

706 lines
20 KiB
JavaScript

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/* eslint-disable no-use-before-define, no-restricted-syntax */
import objectPath from 'object-path';
import util from 'util';
import { GraphQLInputObjectType, GraphQLNonNull, GraphQLEnumType, GraphQLObjectType, isInputType, getNamedType } from './graphql';
import { ObjectTypeComposer } from './ObjectTypeComposer';
import { InputTypeComposer, isComposeInputType } from './InputTypeComposer';
import { EnumTypeComposer } from './EnumTypeComposer';
import { SchemaComposer } from './SchemaComposer';
import deepmerge from './utils/deepmerge';
import { resolveArgConfigMapAsThunk, resolveOutputConfigAsThunk, resolveArgConfigAsThunk } from './utils/configAsThunk';
import { only, clearName, inspect } from './utils/misc';
import { isFunction, isString } from './utils/is';
import { filterByDotPaths } from './utils/filterByDotPaths';
import { getProjectionFromAST } from './utils/projection';
import { typeByPath } from './utils/typeByPath';
import GraphQLJSON from './type/json';
export class Resolver {
constructor(opts, schemaComposer) {
if (!(schemaComposer instanceof SchemaComposer)) {
throw new Error('You must provide SchemaComposer instance as a second argument for `new Resolver(opts, SchemaComposer)`');
}
this.schemaComposer = schemaComposer;
if (!opts.name) {
throw new Error('For Resolver constructor the `opts.name` is required option.');
}
this.name = opts.name;
this.displayName = opts.displayName;
this.parent = opts.parent;
this.kind = opts.kind;
this.description = opts.description || '';
this.extensions = opts.extensions;
if (opts.type) {
this.setType(opts.type);
}
this.args = opts.args || {};
if (opts.resolve) {
this.resolve = opts.resolve;
} // alive proper Flow type casting in autosuggestions for class with Generics
/* :: return this; */
} // -----------------------------------------------
// Output type methods
// -----------------------------------------------
getType() {
if (!this.type) {
return GraphQLJSON;
}
const fc = resolveOutputConfigAsThunk(this.schemaComposer, this.type, this.name, 'Resolver');
return fc.type;
}
getTypeComposer() {
const outputType = getNamedType(this.getType());
if (!(outputType instanceof GraphQLObjectType)) {
throw new Error(`Resolver ${this.name} cannot return its output type as ObjectTypeComposer instance. ` + `Cause '${this.type.toString()}' does not instance of GraphQLObjectType.`);
}
return new ObjectTypeComposer(outputType, this.schemaComposer);
}
setType(composeType) {
// check that `composeType` has correct data
this.schemaComposer.typeMapper.convertOutputFieldConfig(composeType, 'setType', 'Resolver');
this.type = composeType;
return this;
} // -----------------------------------------------
// Args methods
// -----------------------------------------------
hasArg(argName) {
return !!this.args[argName];
}
getArg(argName) {
if (!this.hasArg(argName)) {
throw new Error(`Cannot get arg '${argName}' for resolver ${this.name}. Argument does not exist.`);
}
let arg = this.args[argName];
if (isFunction(arg)) arg = arg();
if (typeof arg === 'string' || isComposeInputType(arg) || Array.isArray(arg)) {
return {
type: arg
};
}
return arg;
}
getArgConfig(argName) {
const arg = this.getArg(argName);
return resolveArgConfigAsThunk(this.schemaComposer, arg, argName, this.name, 'Resolver');
}
getArgType(argName) {
const ac = this.getArgConfig(argName);
return ac.type;
}
getArgTC(argName) {
const argType = getNamedType(this.getArgType(argName));
if (!(argType instanceof GraphQLInputObjectType)) {
throw new Error(`Cannot get InputTypeComposer for arg '${argName}' in resolver ${this.getNestedName()}. ` + `This argument should be InputObjectType, but it has type '${argType.constructor.name}'`);
}
return new InputTypeComposer(argType, this.schemaComposer);
}
getArgs() {
return this.args;
}
getArgNames() {
return Object.keys(this.args);
}
setArgs(args) {
this.args = args;
return this;
}
setArg(argName, argConfig) {
this.args[argName] = argConfig;
return this;
}
extendArg(argName, partialArgConfig) {
let prevArgConfig;
try {
prevArgConfig = this.getArgConfig(argName);
} catch (e) {
throw new Error(`Cannot extend arg '${argName}' in Resolver '${this.name}'. Argument does not exist.`);
}
this.setArg(argName, _objectSpread({}, prevArgConfig, partialArgConfig));
return this;
}
addArgs(newArgs) {
this.setArgs(_objectSpread({}, this.getArgs(), newArgs));
return this;
}
removeArg(argNameOrArray) {
const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray];
argNames.forEach(argName => {
delete this.args[argName];
});
return this;
}
removeOtherArgs(argNameOrArray) {
const keepArgNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray];
Object.keys(this.args).forEach(argName => {
if (keepArgNames.indexOf(argName) === -1) {
delete this.args[argName];
}
});
return this;
}
reorderArgs(names) {
const orderedArgs = {};
names.forEach(name => {
if (this.args[name]) {
orderedArgs[name] = this.args[name];
delete this.args[name];
}
});
this.args = _objectSpread({}, orderedArgs, this.args);
return this;
}
cloneArg(argName, newTypeName) {
if (!{}.hasOwnProperty.call(this.args, argName)) {
throw new Error(`Can not clone arg ${argName} for resolver ${this.name}. Argument does not exist.`);
}
let originalType = this.getArgType(argName);
let isUnwrapped = false;
if (originalType instanceof GraphQLNonNull) {
originalType = originalType.ofType;
isUnwrapped = true;
}
if (!(originalType instanceof GraphQLInputObjectType)) {
throw new Error(`Can not clone arg ${argName} for resolver ${this.name}.` + 'Argument should be GraphQLInputObjectType (complex input type).');
}
if (!newTypeName || newTypeName !== clearName(newTypeName)) {
throw new Error('You should provide new type name as second argument');
}
if (newTypeName === originalType.name) {
throw new Error('You should provide new type name. It is equal to current name.');
}
let clonedType = InputTypeComposer.createTemp(originalType, this.schemaComposer).clone(newTypeName).getType();
if (isUnwrapped) {
clonedType = new GraphQLNonNull(clonedType);
}
this.extendArg(argName, {
type: clonedType
});
return this;
}
isRequired(argName) {
return this.getArgType(argName) instanceof GraphQLNonNull;
}
makeRequired(argNameOrArray) {
const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray];
argNames.forEach(argName => {
if (this.hasArg(argName)) {
const argType = this.getArgType(argName);
if (!isInputType(argType)) {
throw new Error(`Cannot make argument ${argName} required. It should be InputType: ${JSON.stringify(argType)}`);
}
if (!(argType instanceof GraphQLNonNull)) {
this.extendArg(argName, {
type: new GraphQLNonNull(argType)
});
}
}
});
return this;
}
makeOptional(argNameOrArray) {
const argNames = Array.isArray(argNameOrArray) ? argNameOrArray : [argNameOrArray];
argNames.forEach(argName => {
if (this.hasArg(argName)) {
const argType = this.getArgType(argName);
if (argType instanceof GraphQLNonNull) {
this.extendArg(argName, {
type: argType.ofType
});
}
}
});
return this;
}
addFilterArg(opts) {
if (!opts.name) {
throw new Error('For Resolver.addFilterArg the arg name `opts.name` is required.');
}
if (!opts.type) {
throw new Error('For Resolver.addFilterArg the arg type `opts.type` is required.');
}
const resolver = this.wrap(null, {
name: 'addFilterArg'
}); // get filterTC or create new one argument
const filter = resolver.hasArg('filter') ? resolver.getArgConfig('filter') : undefined;
let filterITC;
if (filter && filter.type instanceof GraphQLInputObjectType) {
filterITC = new InputTypeComposer(filter.type, this.schemaComposer);
} else {
if (!opts.filterTypeNameFallback || !isString(opts.filterTypeNameFallback)) {
throw new Error('For Resolver.addFilterArg needs to provide `opts.filterTypeNameFallback: string`. ' + 'This string will be used as unique name for `filter` type of input argument. ' + 'Eg. FilterXXXXXInput');
}
filterITC = InputTypeComposer.createTemp(opts.filterTypeNameFallback, this.schemaComposer);
}
let defaultValue;
if (filter && filter.defaultValue) {
defaultValue = filter.defaultValue;
}
if (opts.defaultValue) {
if (!defaultValue) {
defaultValue = {};
}
defaultValue[opts.name] = opts.defaultValue;
}
resolver.setArg('filter', {
type: filterITC.getType(),
description: filter && filter.description || undefined,
defaultValue
});
filterITC.setField(opts.name, _objectSpread({}, only(opts, ['name', 'type', 'defaultValue', 'description'])));
const resolveNext = resolver.getResolve();
const query = opts.query;
if (query && isFunction(query)) {
resolver.setResolve(async resolveParams => {
const value = objectPath.get(resolveParams, ['args', 'filter', opts.name]);
if (value !== null && value !== undefined) {
if (!resolveParams.rawQuery) {
resolveParams.rawQuery = {}; // eslint-disable-line
}
await query(resolveParams.rawQuery, value, resolveParams);
}
return resolveNext(resolveParams);
});
}
return resolver;
}
addSortArg(opts) {
if (!opts.name) {
throw new Error('For Resolver.addSortArg the `opts.name` is required.');
}
if (!opts.value) {
throw new Error('For Resolver.addSortArg the `opts.value` is required.');
}
const resolver = this.wrap(null, {
name: 'addSortArg'
}); // get sortETC or create new one
let sortETC;
if (resolver.hasArg('sort')) {
const sortConfig = resolver.getArgConfig('sort');
if (sortConfig.type instanceof GraphQLEnumType) {
sortETC = EnumTypeComposer.createTemp(sortConfig.type, this.schemaComposer);
} else {
throw new Error('Resolver must have `sort` arg with type GraphQLEnumType. ' + `But got: ${util.inspect(sortConfig.type, {
depth: 2
})} `);
}
} else {
if (!opts.sortTypeNameFallback || !isString(opts.sortTypeNameFallback)) {
throw new Error('For Resolver.addSortArg needs to provide `opts.sortTypeNameFallback: string`. ' + 'This string will be used as unique name for `sort` type of input argument. ' + 'Eg. SortXXXXXEnum');
}
sortETC = EnumTypeComposer.createTemp({
name: opts.sortTypeNameFallback,
values: {
[opts.name]: {}
}
}, this.schemaComposer);
resolver.setArg('sort', sortETC);
} // extend sortETC with new sorting value
sortETC.setField(opts.name, {
description: opts.description,
deprecationReason: opts.deprecationReason,
value: isFunction(opts.value) ? opts.name : opts.value
}); // If sort value is evaluable (function), then wrap resolve method
const resolveNext = resolver.getResolve();
if (isFunction(opts.value)) {
const getValue = opts.value;
resolver.setResolve(resolveParams => {
const value = objectPath.get(resolveParams, ['args', 'sort']);
if (value === opts.name) {
const newSortValue = getValue(resolveParams);
resolveParams.args.sort = newSortValue; // eslint-disable-line
}
return resolveNext(resolveParams);
});
}
return resolver;
} // -----------------------------------------------
// Resolve methods
// -----------------------------------------------
/*
* This method should be overriden via constructor
*/
/* eslint-disable */
resolve(resolveParams) {
return Promise.resolve();
}
/* eslint-enable */
getResolve() {
return this.resolve;
}
setResolve(resolve) {
this.resolve = resolve;
return this;
} // -----------------------------------------------
// Wrap methods
// -----------------------------------------------
withMiddlewares(middlewares) {
if (!Array.isArray(middlewares)) {
throw new Error(`You should provide array of middlewares '(resolve, source, args, context, info) => any', but provided ${inspect(middlewares)}.`);
}
let resolver = this;
middlewares.reverse().forEach(mw => {
let name;
if (mw.name) {
name = mw.name;
} else if (mw.constructor && mw.constructor.name) {
name = mw.constructor.name;
} else {
name = 'middleware';
}
const newResolver = this.clone({
name,
parent: resolver
});
const resolve = resolver.getResolve();
newResolver.setResolve(rp => mw((source, args, context, info) => {
return resolve(_objectSpread({}, rp, {
source,
args,
context,
info
}));
}, rp.source, rp.args, rp.context, rp.info));
resolver = newResolver;
});
return resolver;
}
wrap(cb, newResolverOpts = {}) {
const prevResolver = this;
const newResolver = this.clone(_objectSpread({
name: 'wrap',
parent: prevResolver
}, newResolverOpts));
if (isFunction(cb)) {
const resolver = cb(newResolver, prevResolver);
if (resolver) return resolver;
}
return newResolver;
}
wrapResolve(cb, wrapperName = 'wrapResolve') {
return this.wrap((newResolver, prevResolver) => {
const newResolve = cb(prevResolver.getResolve());
newResolver.setResolve(newResolve);
return newResolver;
}, {
name: wrapperName
});
}
wrapArgs(cb, wrapperName = 'wrapArgs') {
return this.wrap((newResolver, prevResolver) => {
// clone prevArgs, to avoid changing args in callback
const prevArgs = _objectSpread({}, prevResolver.getArgs());
const newArgs = cb(prevArgs);
newResolver.setArgs(newArgs);
return newResolver;
}, {
name: wrapperName
});
}
wrapCloneArg(argName, newTypeName) {
return this.wrap(newResolver => newResolver.cloneArg(argName, newTypeName), {
name: 'cloneFilterArg'
});
}
wrapType(cb, wrapperName = 'wrapType') {
return this.wrap((newResolver, prevResolver) => {
const prevType = prevResolver.getType();
const newType = cb(prevType);
newResolver.setType(newType);
return newResolver;
}, {
name: wrapperName
});
} // -----------------------------------------------
// Misc methods
// -----------------------------------------------
getFieldConfig(opts = {}) {
const resolve = this.getResolve();
return {
type: this.getType(),
args: resolveArgConfigMapAsThunk(this.schemaComposer, this.getArgs(), this.name, 'Resolver'),
description: this.description,
resolve: (source, args, context, info) => {
let projection = getProjectionFromAST(info);
if (opts.projection) {
projection = deepmerge(projection, opts.projection);
}
return resolve({
source,
args,
context,
info,
projection
});
}
};
}
getKind() {
return this.kind;
}
setKind(kind) {
if (kind !== 'query' && kind !== 'mutation' && kind !== 'subscription') {
throw new Error(`You provide incorrect value '${kind}' for Resolver.setKind method. ` + 'Valid values are: query | mutation | subscription');
}
this.kind = kind;
return this;
}
getDescription() {
return this.description;
}
setDescription(description) {
this.description = description;
return this;
}
get(path) {
return typeByPath(this, path);
}
clone(opts = {}) {
const oldOpts = {};
const self = this;
for (const key in self) {
if (self.hasOwnProperty(key)) {
// $FlowFixMe
oldOpts[key] = self[key];
}
}
oldOpts.displayName = undefined;
oldOpts.args = _objectSpread({}, this.args);
return new Resolver(_objectSpread({}, oldOpts, opts), this.schemaComposer);
} // -----------------------------------------------
// Debug methods
// -----------------------------------------------
getNestedName() {
const name = this.displayName || this.name;
if (this.parent) {
return `${name}(${this.parent.getNestedName()})`;
}
return name;
}
toString(colors = true) {
return util.inspect(this.toDebugStructure(false), {
depth: 20,
colors
}).replace(/\\n/g, '\n');
}
setDisplayName(name) {
this.displayName = name;
return this;
}
toDebugStructure(colors = true) {
const info = {
name: this.name,
displayName: this.displayName,
type: util.inspect(this.type, {
depth: 2,
colors
}),
args: this.args,
resolve: this.resolve ? this.resolve.toString() : this.resolve
};
if (this.parent) {
info.resolve = [info.resolve, {
'Parent resolver': this.parent.toDebugStructure(colors)
}];
}
return info;
}
debugExecTime() {
/* eslint-disable no-console */
return this.wrapResolve(next => async rp => {
const name = `Execution time for ${this.getNestedName()}`;
console.time(name);
const res = await next(rp);
console.timeEnd(name);
return res;
}, 'debugExecTime');
/* eslint-enable no-console */
}
debugParams(filterPaths, opts = {
colors: true,
depth: 5
}) {
/* eslint-disable no-console */
return this.wrapResolve(next => rp => {
console.log(`ResolveParams for ${this.getNestedName()}:`);
const data = filterByDotPaths(rp, filterPaths, {
// is hidden (use debugParams(["info"])) or debug({ params: ["info"]})
// `is hidden (use debugParams(["context.*"])) or debug({ params: ["context.*"]})`,
hideFields: rp && rp.context && rp.context.res && rp.context.params && rp.context.headers ? {
// looks like context is express request, colapse it
info: '[[hidden]]',
context: '[[hidden]]'
} : {
info: '[[hidden]]',
'context.*': '[[hidden]]'
},
hideFieldsNote: 'Some data was [[hidden]] to display this fields use debugParams("%fieldNames%")'
});
console.dir(data, opts);
return next(rp);
}, 'debugParams');
/* eslint-enable no-console */
}
debugPayload(filterPaths, opts = {
colors: true,
depth: 5
}) {
/* eslint-disable no-console */
return this.wrapResolve(next => async rp => {
try {
const res = await next(rp);
console.log(`Resolved Payload for ${this.getNestedName()}:`);
if (Array.isArray(res) && res.length > 3 && !filterPaths) {
console.dir([filterPaths ? filterByDotPaths(res[0], filterPaths) : res[0], `[debug note]: Other ${res.length - 1} records was [[hidden]]. ` + 'Use debugPayload("0 1 2 3 4") or debug({ payload: "0 1 2 3 4" }) for display this records'], opts);
} else {
console.dir(filterPaths ? filterByDotPaths(res, filterPaths) : res, opts);
}
return res;
} catch (e) {
console.log(`Rejected Payload for ${this.getNestedName()}:`);
console.log(e);
throw e;
}
}, 'debugPayload');
/* eslint-enable no-console */
}
debug(filterDotPaths, opts = {
colors: true,
depth: 2
}) {
return this.debugExecTime().debugParams(filterDotPaths ? filterDotPaths.params : null, opts).debugPayload(filterDotPaths ? filterDotPaths.payload : null, opts);
}
}