@pothos/plugin-errors
Version:
A Pothos plugin for adding typed errors into your schema
295 lines (294 loc) • 14.8 kB
JavaScript
import './global-types.js';
import SchemaBuilder, { BasePlugin, PothosSchemaError, sortClasses, typeBrandKey, unwrapOutputFieldType } from '@pothos/core';
export * from './types.js';
const pluginName = "errors";
export default pluginName;
export function capitalize(s) {
return `${s.slice(0, 1).toUpperCase()}${s.slice(1)}`;
}
export const defaultGetResultName = ({ parentTypeName, fieldName }) => `${parentTypeName}${capitalize(fieldName)}Success`;
export const defaultGetListItemResultName = ({ parentTypeName, fieldName }) => `${parentTypeName}${capitalize(fieldName)}ItemSuccess`;
export const defaultGetUnionName = ({ parentTypeName, fieldName }) => `${parentTypeName}${capitalize(fieldName)}Result`;
export const defaultGetListItemUnionName = ({ parentTypeName, fieldName }) => `${parentTypeName}${capitalize(fieldName)}ItemResult`;
export const unwrapError = Symbol.for("Pothos.unwrapErrors");
function createErrorProxy(target, ref, state) {
return new Proxy(target, {
get(err, val, receiver) {
if (val === unwrapError) {
return () => {
state.wrapped = false;
};
}
if (val === typeBrandKey) {
return ref;
}
return Reflect.get(err, val, receiver);
},
getPrototypeOf(err) {
const proto = Reflect.getPrototypeOf(err);
if (!state.wrapped || !proto) {
return proto;
}
return createErrorProxy(proto, ref, state);
}
});
}
const errorTypeMap = new WeakMap();
export class PothosErrorsPlugin extends BasePlugin {
wrapIsTypeOf(isTypeOf) {
if (isTypeOf) {
return (parent, context, info) => {
if (typeof parent === "object" && parent) {
var _parent_unwrapError;
(_parent_unwrapError = parent[unwrapError]) === null || _parent_unwrapError === void 0 ? void 0 : _parent_unwrapError.call(parent);
}
return isTypeOf(parent, context, info);
};
}
return isTypeOf;
}
onOutputFieldConfig(fieldConfig) {
const errorOptions = fieldConfig.pothosOptions.errors;
const itemErrorOptions = fieldConfig.pothosOptions.itemErrors;
const errorBuilderOptions = this.builder.options.errors;
if (!errorOptions && !itemErrorOptions) {
return fieldConfig;
}
const parentTypeName = this.buildCache.getTypeConfig(fieldConfig.parentType).name;
var _itemErrorOptions_types, _errorBuilderOptions_defaultTypes;
const itemErrorTypes = itemErrorOptions && sortClasses([
...new Set([
...(_itemErrorOptions_types = itemErrorOptions === null || itemErrorOptions === void 0 ? void 0 : itemErrorOptions.types) !== null && _itemErrorOptions_types !== void 0 ? _itemErrorOptions_types : [],
...(_errorBuilderOptions_defaultTypes = errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.defaultTypes) !== null && _errorBuilderOptions_defaultTypes !== void 0 ? _errorBuilderOptions_defaultTypes : []
])
]);
var _errorOptions_types, _errorBuilderOptions_defaultTypes1;
const errorTypes = errorOptions && sortClasses([
...new Set([
...(_errorOptions_types = errorOptions === null || errorOptions === void 0 ? void 0 : errorOptions.types) !== null && _errorOptions_types !== void 0 ? _errorOptions_types : [],
...(_errorBuilderOptions_defaultTypes1 = errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.defaultTypes) !== null && _errorBuilderOptions_defaultTypes1 !== void 0 ? _errorBuilderOptions_defaultTypes1 : []
])
]);
let resultType = fieldConfig.pothosOptions.type;
if (itemErrorOptions) {
if (!Array.isArray(fieldConfig.pothosOptions.type) || fieldConfig.type.kind !== "List") {
throw new PothosSchemaError(`Field ${parentTypeName}.${fieldConfig.name} must return a list when 'itemErrors' is set`);
}
const itemFieldType = fieldConfig.type.type;
const itemType = fieldConfig.pothosOptions.type[0];
resultType = [
this.createResultType(parentTypeName, fieldConfig.name, itemType, itemFieldType, itemErrorOptions, `Field ${parentTypeName}.${fieldConfig.name} list items must be an ObjectType when 'directResult' is set to true`, defaultGetListItemResultName, defaultGetListItemUnionName, errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.defaultItemResultOptions, errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.defaultItemUnionOptions)
];
if (!errorOptions) {
return {
...fieldConfig,
extensions: {
...fieldConfig.extensions,
pothosItemErrors: itemErrorTypes
},
type: {
...fieldConfig.type,
type: {
kind: "Union",
ref: resultType[0],
nullable: fieldConfig.type.type.nullable
}
}
};
}
}
const unionType = this.createResultType(parentTypeName, fieldConfig.name, resultType, fieldConfig.type, errorOptions, `Field ${parentTypeName}.${fieldConfig.name} must return an ObjectType when 'directResult' is set to true`, defaultGetResultName, defaultGetUnionName, errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.defaultResultOptions, errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.defaultUnionOptions);
return {
...fieldConfig,
extensions: {
...fieldConfig.extensions,
pothosErrors: errorTypes,
pothosItemErrors: itemErrorTypes
},
type: {
kind: "Union",
ref: unionType,
nullable: fieldConfig.type.nullable
}
};
}
wrapResolve(resolver, fieldConfig) {
var _fieldConfig_extensions, _fieldConfig_extensions1;
const pothosErrors = (_fieldConfig_extensions = fieldConfig.extensions) === null || _fieldConfig_extensions === void 0 ? void 0 : _fieldConfig_extensions.pothosErrors;
const pothosItemErrors = (_fieldConfig_extensions1 = fieldConfig.extensions) === null || _fieldConfig_extensions1 === void 0 ? void 0 : _fieldConfig_extensions1.pothosItemErrors;
if (!pothosErrors && !pothosItemErrors) {
return resolver;
}
return async (source, args, context, info) => {
if (fieldConfig.kind === "Subscription" && errorTypeMap.has(source)) {
return source;
}
try {
const result = await resolver(source, args, context, info);
if (pothosItemErrors && result && typeof result === "object" && Symbol.iterator in result) {
return yieldErrors(result, pothosItemErrors);
}
if (pothosItemErrors && result && typeof result === "object" && Symbol.asyncIterator in result) {
console.log(result, yieldAsyncErrors);
return yieldAsyncErrors(result, pothosItemErrors);
}
return result;
}
catch (error) {
return wrapOrThrow(error, pothosErrors !== null && pothosErrors !== void 0 ? pothosErrors : []);
}
};
}
wrapSubscribe(subscribe, fieldConfig) {
var _fieldConfig_extensions;
const pothosErrors = (_fieldConfig_extensions = fieldConfig.extensions) === null || _fieldConfig_extensions === void 0 ? void 0 : _fieldConfig_extensions.pothosErrors;
if (!pothosErrors) {
return subscribe;
}
return (...args) => {
async function* yieldSubscribeErrors() {
try {
const iter = await subscribe(...args);
if (!iter) {
return iter;
}
for await (const value of iter) {
yield value;
}
}
catch (error) {
yield wrapOrThrow(error, pothosErrors !== null && pothosErrors !== void 0 ? pothosErrors : []);
}
}
return yieldSubscribeErrors();
};
}
createResultType(parentTypeName, fieldName, type, fieldType, errorOptions, directResultError, defaultResultName, defaultUnionName, builderResultOptions = {}, builderUnionOptions = {}) {
const errorBuilderOptions = this.builder.options.errors;
const { name: getResultName = defaultResultName, ...defaultResultOptions } = builderResultOptions !== null && builderResultOptions !== void 0 ? builderResultOptions : {};
const { name: getUnionName = defaultUnionName, ...defaultUnionOptions } = builderUnionOptions !== null && builderUnionOptions !== void 0 ? builderUnionOptions : {};
const { types = [], result: { name: resultName = getResultName({
parentTypeName,
fieldName
}), fields: resultFieldOptions, ...resultObjectOptions } = {}, union: { name: unionName = getUnionName({
parentTypeName,
fieldName
}), ...unionOptions } = {}, dataField: { name: dataFieldName = "data", ...dataField } = {} } = errorOptions;
var _errorBuilderOptions_defaultTypes;
const errorTypes = sortClasses([
...new Set([
...types,
...(_errorBuilderOptions_defaultTypes = errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.defaultTypes) !== null && _errorBuilderOptions_defaultTypes !== void 0 ? _errorBuilderOptions_defaultTypes : []
])
]);
var _errorOptions_directResult, _ref;
const directResult = (_ref = (_errorOptions_directResult = errorOptions.directResult) !== null && _errorOptions_directResult !== void 0 ? _errorOptions_directResult : errorBuilderOptions === null || errorBuilderOptions === void 0 ? void 0 : errorBuilderOptions.directResult) !== null && _ref !== void 0 ? _ref : false;
const typeRef = unwrapOutputFieldType(fieldType);
const typeName = this.builder.configStore.getTypeConfig(typeRef).name;
return this.runUnique(resultName, () => {
var _this_buildCache_getTypeConfig_extensions;
let resultType;
if (directResult && !Array.isArray(fieldType)) {
resultType = type;
const resultConfig = this.builder.configStore.getTypeConfig(resultType);
if (resultConfig.graphqlKind !== "Object") {
throw new PothosSchemaError(directResultError);
}
}
else {
resultType = this.builder.objectRef(resultName);
resultType.implement({
...defaultResultOptions,
...resultObjectOptions,
fields: (t) => ({
...resultFieldOptions === null || resultFieldOptions === void 0 ? void 0 : resultFieldOptions(t),
[dataFieldName]: t.field({
...dataField,
type,
nullable: fieldType.kind === "List" ? {
items: fieldType.type.nullable,
list: false
} : false,
resolve: (data) => data
})
})
});
}
const getDataloader = (_this_buildCache_getTypeConfig_extensions = this.buildCache.getTypeConfig(unwrapOutputFieldType(fieldType)).extensions) === null || _this_buildCache_getTypeConfig_extensions === void 0 ? void 0 : _this_buildCache_getTypeConfig_extensions.getDataloader;
return this.builder.unionType(unionName, {
types: [
...errorTypes,
resultType
],
resolveType: (obj) => {
var _errorTypeMap_get;
return (_errorTypeMap_get = errorTypeMap.get(obj)) !== null && _errorTypeMap_get !== void 0 ? _errorTypeMap_get : resultType;
},
...defaultUnionOptions,
...unionOptions,
extensions: {
...unionOptions.extensions,
getDataloader,
pothosIndirectInclude: {
getType: () => typeName,
path: directResult ? [] : [
{
type: resultName,
name: dataFieldName
}
]
}
}
});
});
}
}
SchemaBuilder.registerPlugin(pluginName, PothosErrorsPlugin, {
v3: (options) => ({
errorOptions: undefined,
errors: options === null || options === void 0 ? void 0 : options.errorOptions
})
});
function wrapOrThrow(error, pothosErrors) {
for (const errorType of pothosErrors) {
if (error instanceof errorType) {
const result = createErrorProxy(error, errorType, {
wrapped: true
});
errorTypeMap.set(result, errorType);
return result;
}
}
throw error;
}
function* yieldErrors(result, pothosErrors) {
try {
for (const item of result) {
if (item instanceof Error) {
yield wrapOrThrow(item, pothosErrors);
}
else {
yield item;
}
}
}
catch (error) {
yield wrapOrThrow(error, pothosErrors);
}
}
async function* yieldAsyncErrors(result, pothosErrors) {
try {
for await (const item of result) {
if (item instanceof Error) {
yield wrapOrThrow(item, pothosErrors);
}
else {
yield item;
}
}
}
catch (error) {
yield wrapOrThrow(error, pothosErrors);
}
}
//# sourceMappingURL=index.js.map