UNPKG

nexus

Version:

Scalable, strongly typed GraphQL schema development

132 lines 5.71 kB
import { isEnumType, isInterfaceType, isListType, isNonNullType, isObjectType, isScalarType, isUnionType, isWrappingType, } from 'graphql'; import { forEach } from 'iterall'; import { plugin } from '../plugin'; import { isPromiseLike, printedGenTyping } from '../utils'; const fieldDefTypes = printedGenTyping({ name: 'skipNullGuard', optional: true, type: 'boolean', description: ` The nullability guard can be helpful, but is also a potentially expensive operation for lists. We need to iterate the entire list to check for null items to guard against. Set this to true to skip the null guard on a specific field if you know there's no potential for unsafe types. `, }); export const nullabilityGuardPlugin = (pluginConfig) => { const { shouldGuard = process.env.NODE_ENV === 'production', fallbackValues = {}, onGuarded = (obj) => { console.warn(`Nullability guard called for ${obj.info.parentType.name}.${obj.info.fieldName}`); }, } = pluginConfig; const finalPluginConfig = { shouldGuard, onGuarded, fallbackValues, }; return plugin({ name: 'NullabilityGuard', description: 'If we have a nullable field, we want to guard against this being an issue in production.', fieldDefTypes, onCreateFieldResolver(config) { var _a, _b; if ((_b = (_a = config.fieldConfig.extensions) === null || _a === void 0 ? void 0 : _a.nexus) === null || _b === void 0 ? void 0 : _b.config.skipNullGuard) { return; } const { type } = config.fieldConfig; const { outerNonNull, hasListNonNull } = nonNullInfo(type); if (outerNonNull || hasListNonNull) { return (root, args, ctx, info, next) => { return plugin.completeValue(next(root, args, ctx, info), nonNullGuard(ctx, info, isNonNullType(type) ? type.ofType : type, config, finalPluginConfig, outerNonNull)); }; } }, onAfterBuild(schema) { Object.keys(schema.getTypeMap()).forEach((typeName) => { const type = schema.getType(typeName); if (isScalarType(type)) { if (fallbackValues[type.name]) { return; } console.error(`No nullability guard was provided for Scalar ${type.name}. ` + `Provide one in the nullabilityGuard config to remove this warning.`); } }); if (pluginConfig.fallbackValues) { Object.keys(pluginConfig.fallbackValues).forEach((name) => { const type = schema.getType(name); if (!type) { return console.error(`Unknown type ${name} provided in nullabilityGuard fallbackValues config.`); } }); } }, }); }; const isNullish = (val) => val === null || val === undefined || val !== val; const nonNullGuard = (ctx, info, type, config, pluginConfig, outerNonNull) => { const { onGuarded, fallbackValues, shouldGuard } = pluginConfig; const guardResult = (fallback) => { onGuarded({ ctx, info, type, fallback }); return shouldGuard ? fallback : null; }; return (val) => { // If it's a list type, return [] if the value is null, // otherwise recurse into resolving the individual type. if (isListType(type)) { if (isNullish(val)) { return outerNonNull ? guardResult([]) : null; } let hasPromise = false; const listMembers = []; const listCompleter = nonNullGuard(ctx, info, isNonNullType(type.ofType) ? type.ofType.ofType : type.ofType, config, pluginConfig, isNonNullType(type.ofType)); forEach(val, (item) => { if (!hasPromise && isPromiseLike(item)) { hasPromise = true; } listMembers.push(plugin.completeValue(item, listCompleter)); }); return hasPromise ? Promise.all(listMembers) : listMembers; } if (!isNullish(val) || outerNonNull === false) { return val; } const typeName = type.name; const fallbackFn = fallbackValues[typeName]; const fallbackValue = typeof fallbackFn === 'function' ? fallbackFn({ type, info, ctx }) : null; if (!isNullish(fallbackValue)) { return guardResult(fallbackValue); } // If it's an object, just return an empty object and let the scalar fallbacks take care of the rest if (isObjectType(type)) { return guardResult({}); } // If it's an enum, return the first member if (isEnumType(type)) { return guardResult(type.getValues()[0].value); } // If It's a union or interface, return the first type if (isUnionType(type) || isInterfaceType(type)) { const possibleTypes = info.schema.getPossibleTypes(type); return guardResult({ __typename: possibleTypes[0].name }); } // Otherwise, fail? return val; }; }; const nonNullInfo = (type) => { let outerNonNull = false; let hasListNonNull = false; if (isNonNullType(type)) { outerNonNull = true; type = type.ofType; } while (isWrappingType(type)) { type = type.ofType; if (isNonNullType(type)) { hasListNonNull = true; } } return { outerNonNull, hasListNonNull, }; }; //# sourceMappingURL=nullabilityGuardPlugin.js.map