@apollo/server
Version:
Core engine for Apollo GraphQL server
133 lines (118 loc) • 4.19 kB
text/typescript
import {
type GraphQLSchema,
type GraphQLField,
getNamedType,
GraphQLObjectType,
type GraphQLFieldResolver,
defaultFieldResolver,
} from 'graphql';
import type {
BaseContext,
GraphQLRequestExecutionListener,
} from '../externalTypes/index.js';
export const symbolExecutionDispatcherWillResolveField = Symbol(
'apolloServerExecutionDispatcherWillResolveField',
);
export const symbolUserFieldResolver = Symbol('apolloServerUserFieldResolver');
const symbolPluginsEnabled = Symbol('apolloServerPluginsEnabled');
export function enablePluginsForSchemaResolvers<TContext extends BaseContext>(
schema: GraphQLSchema & { [symbolPluginsEnabled]?: boolean },
) {
if (pluginsEnabledForSchemaResolvers(schema)) {
return schema;
}
Object.defineProperty(schema, symbolPluginsEnabled, {
value: true,
});
const typeMap = schema.getTypeMap();
Object.values(typeMap).forEach((type) => {
if (
!getNamedType(type).name.startsWith('__') &&
type instanceof GraphQLObjectType
) {
const fields = type.getFields();
Object.values(fields).forEach((field) => {
wrapField<TContext>(field);
});
}
});
return schema;
}
export function pluginsEnabledForSchemaResolvers(
schema: GraphQLSchema & { [symbolPluginsEnabled]?: boolean },
): boolean {
return !!schema[symbolPluginsEnabled];
}
function wrapField<TContext extends BaseContext>(
field: GraphQLField<any, any>,
): void {
const originalFieldResolve = field.resolve;
field.resolve = (source, args, contextValue, info) => {
const willResolveField = contextValue?.[
symbolExecutionDispatcherWillResolveField
] as
| GraphQLRequestExecutionListener<TContext>['willResolveField']
| undefined;
const userFieldResolver = contextValue?.[symbolUserFieldResolver] as
| GraphQLFieldResolver<any, any>
| undefined;
// The technique for implementing a "did resolve field" is accomplished by
// returning a function from the `willResolveField` handler. While there
// may be several callbacks, depending on the number of plugins which have
// implemented a `willResolveField` hook, this hook will call them all
// as dictated by the dispatcher. We will call this when object
// resolution is complete.
const didResolveField =
typeof willResolveField === 'function' &&
willResolveField({ source, args, contextValue, info });
const fieldResolver =
originalFieldResolve || userFieldResolver || defaultFieldResolver;
try {
const result = fieldResolver(source, args, contextValue, info);
// Call the stack's handlers either immediately (if result is not a
// Promise) or once the Promise is done. Then return that same
// maybe-Promise value.
if (typeof didResolveField === 'function') {
whenResultIsFinished(result, didResolveField);
}
return result;
} catch (error) {
// Normally it's a bad sign to see an error both handled and
// re-thrown. But it is useful to allow extensions to track errors while
// still handling them in the normal GraphQL way.
if (typeof didResolveField === 'function') {
didResolveField(error as Error);
}
throw error;
}
};
}
function isPromise(x: any): boolean {
return x && typeof x.then === 'function';
}
// Given result (which may be a Promise or an array some of whose elements are
// promises) Promises, set up 'callback' to be invoked when result is fully
// resolved. (Unfortunately, this does not perfectly handle every possible
// return value shape, such as arrays of arrays of Promises.)
export function whenResultIsFinished(
result: any,
callback: (err: Error | null, result?: any) => void,
) {
if (isPromise(result)) {
result.then(
(r: any) => whenResultIsFinished(r, callback),
(err: Error) => callback(err),
);
} else if (Array.isArray(result)) {
if (result.some(isPromise)) {
Promise.all(result).then(
(r: any) => callback(null, r),
(err: Error) => callback(err),
);
} else {
callback(null, result);
}
} else {
callback(null, result);
}
}