UNPKG

@giraphql/plugin-errors

Version:

A GiraphQL plugin for adding typed errors into your schema

173 lines (142 loc) 5.13 kB
import './global-types'; import { GraphQLFieldResolver } from 'graphql'; import SchemaBuilder, { BasePlugin, GiraphQLOutputFieldConfig, ImplementableObjectRef, SchemaTypes, sortClasses, } from '@giraphql/core'; export * from './types'; const pluginName = 'errors'; export default pluginName; export function capitalize(s: string) { return `${s.slice(0, 1).toUpperCase()}${s.slice(1)}`; } function createErrorProxy(target: {}): {} { return new Proxy(target, { getPrototypeOf(err) { const proto = Object.getPrototypeOf(err) as {}; if (!proto) { return proto; } return createErrorProxy(proto); }, }); } const errorTypeMap = new WeakMap<{}, new (...args: any[]) => Error>(); export class GiraphQLErrorsPlugin<Types extends SchemaTypes> extends BasePlugin<Types> { override onOutputFieldConfig( fieldConfig: GiraphQLOutputFieldConfig<Types>, ): GiraphQLOutputFieldConfig<Types> | null { const errorOptions = fieldConfig.giraphqlOptions.errors; const errorBuilderOptions = this.builder.options.errorOptions; if (!errorOptions) { return fieldConfig; } const parentTypeName = this.buildCache.getTypeConfig(fieldConfig.parentType).name; const { types = [], result: { name: resultName = `${parentTypeName}${capitalize(fieldConfig.name)}Success`, fields: resultFieldOptions, ...resultObjectOptions } = {} as never, union: { name: unionName = `${parentTypeName}${capitalize(fieldConfig.name)}Result`, ...unionOptions } = {} as never, dataField: { name: dataFieldName = 'data', ...dataField } = {} as never, } = errorOptions; const errorTypes = sortClasses([ ...new Set([...types, ...(errorBuilderOptions?.defaultTypes ?? [])]), ]); const directResult = (errorOptions as { directResult?: boolean }).directResult ?? errorBuilderOptions?.directResult ?? false; const typeRef = fieldConfig.type.kind === 'List' ? fieldConfig.type.type.ref : fieldConfig.type.ref; const typeName = this.builder.configStore.getTypeConfig(typeRef).name; const unionType = this.runUnique(resultName, () => { let resultType: ImplementableObjectRef<Types, unknown>; if (directResult && !Array.isArray(fieldConfig.giraphqlOptions.type)) { resultType = fieldConfig.giraphqlOptions.type as ImplementableObjectRef<Types, unknown>; const resultConfig = this.builder.configStore.getTypeConfig(resultType); if (resultConfig.graphqlKind !== 'Object') { throw new TypeError( `Field ${parentTypeName}.${fieldConfig.name} must return an ObjectType when 'directResult' is set to true`, ); } } else { resultType = this.builder.objectRef<unknown>(resultName); resultType.implement({ ...resultObjectOptions, fields: (t) => ({ ...resultFieldOptions?.(t), [dataFieldName]: t.field({ ...dataField, type: fieldConfig.giraphqlOptions.type, nullable: fieldConfig.type.kind === 'List' ? { items: fieldConfig.type.type.nullable, list: false } : false, resolve: (data) => data as never, }), }), }); } const type = fieldConfig.type.kind === 'List' ? fieldConfig.type.type : fieldConfig.type; const getDataloader = this.buildCache.getTypeConfig(type.ref).extensions?.getDataloader; return this.builder.unionType(unionName, { types: [...errorTypes, resultType], resolveType: (obj) => errorTypeMap.get(obj as {}) ?? resultType, ...unionOptions, extensions: { ...unionOptions.extensions, getDataloader, giraphQLPrismaIndirectInclude: { getType: () => typeName, path: [{ type: resultName, name: dataFieldName }], }, }, }); }); return { ...fieldConfig, extensions: { ...fieldConfig.extensions, giraphqlErrors: errorTypes, }, type: { kind: 'Union', ref: unionType, nullable: fieldConfig.type.nullable, }, }; } override wrapResolve( resolver: GraphQLFieldResolver<unknown, Types['Context'], object>, fieldConfig: GiraphQLOutputFieldConfig<Types>, ): GraphQLFieldResolver<unknown, Types['Context'], object> { const giraphqlErrors = fieldConfig.extensions?.giraphqlErrors as typeof Error[] | undefined; if (!giraphqlErrors) { return resolver; } return async (...args) => { try { return (await resolver(...args)) as never; } catch (error: unknown) { for (const errorType of giraphqlErrors) { if (error instanceof errorType) { const result = createErrorProxy(error); errorTypeMap.set(result, errorType); return result; } } throw error; } }; } } SchemaBuilder.registerPlugin(pluginName, GiraphQLErrorsPlugin);