包详细信息

@pothos/plugin-errors

hayes68.6kISC4.4.2

A Pothos plugin for adding typed errors into your schema

pothos, graphql, schema, typescript

自述文件

Errors Plugin

A plugin for easily including error types in your GraphQL schema and hooking up error types to resolvers

Usage

Install

yarn add @pothos/plugin-errors

Setup

Ensure that the target in your tsconfig.json is set to es6 or higher (default is es3).

Example Usage

import ErrorsPlugin from '@pothos/plugin-errors';
const builder = new SchemaBuilder({
  plugins: [ErrorsPlugin],
  errors: {
    defaultTypes: [],
  },
});

builder.objectType(Error, {
  name: 'Error',
  fields: (t) => ({
    message: t.exposeString('message'),
  }),
});

builder.queryType({
  fields: (t) => ({
    hello: t.string({
      errors: {
        types: [Error],
      },
      args: {
        name: t.arg.string({ required: false }),
      },
      resolve: (parent, { name }) => {
        if (name.slice(0, 1) !== name.slice(0, 1).toUpperCase()) {
          throw new Error('name must be capitalized');
        }

        return `hello, ${name || 'World'}`;
      },
    }),
  }),
});

The above example will produce a GraphQL schema that looks like:

type Error {
  message: String!
}

type Query {
  hello(name: String!): QueryHelloResult
}

union QueryHelloResult = Error | QueryHelloSuccess

type QueryHelloSuccess {
  data: String!
}

This field can be queried using fragments like:

query {
  hello(name: "World") {
    __typename
    ... on Error {
      message
    }
    ... on QueryHelloSuccess {
      data
    }
  }
}

This plugin works by wrapping fields that define error options in a union type. This union consists of an object type for each error type defined for the field, and a Success object type that wraps the returned data. If the fields resolver throws an instance of one of the defined errors, the errors plugin will automatically resolve to the corresponding error object type.

Builder options

  • defaultTypes: An array of Error classes to include in every field with error handling.
  • directResult: Sets the default for directResult option on fields (only affects non-list fields)
  • defaultResultOptions: Sets the defaults for result option on fields.
    • name: Function to generate a custom name on the generated result types.
      export const builderWithCustomErrorTypeNames = new SchemaBuilder<{}>({
        plugins: [ErrorPlugin, ValidationPlugin],
        errors: {
          defaultTypes: [Error],
          defaultResultOptions: {
            name: ({ parentTypeName, fieldName }) => `${fieldName}_Custom`,
          },
          defaultUnionOptions: {
            name: ({ parentTypeName, fieldName }) => `${fieldName}_Custom`,
          },
        },
      });
      
  • defaultUnionOptions: Sets the defaults for result option on fields.
    • name: Function to generate a custom name on the generated union types.
      export const builderWithCustomErrorTypeNames = new SchemaBuilder<{}>({
        plugins: [ErrorPlugin, ValidationPlugin],
        errors: {
          defaultTypes: [Error],
          defaultResultOptions: {
            name: ({ parentTypeName, fieldName }) => `${fieldName}_Custom`,
          },
          defaultUnionOptions: {
            name: ({ parentTypeName, fieldName }) => `${fieldName}_Custom`,
          },
        },
      });
      

Options on Fields

  • types: An array of Error classes to catch and handle as error objects in the schema. Will be merged with defaultTypes from builder.
  • union: An options object for the union type. Can include any normal union type options, and name option for setting a custom name for the union type.
  • result: An options object for result object type. Can include any normal object type options, and name option for setting a custom name for the result type.
  • dataField: An options object for the data field on the result object. This field will be named data by default, but can be written by passsing a custom name option.
  • directResult: Boolean, can only be set to true for non-list fields. This will directly include the fields type in the union rather than creating an intermediate Result object type. This will throw at build time if the type is not an object type.

Recommended Usage

  1. Set up an Error interface
  2. Create a BaseError object type
  3. Include the Error interface in any custom Error types you define
  4. Include the BaseError type in the defaultTypes in the builder config

This pattern will allow you to consistently query your schema using a ... on Error { message } fragment since all Error classes extend that interface. If your client want's to query details of more specialized error types, they can just add a fragment for the errors it cares about. This pattern should also make it easier to make future changes without unexpected breaking changes for your clients.

The follow is a small example of this pattern:

import ErrorsPlugin from '@pothos/plugin-errors';
const builder = new SchemaBuilder({
  plugins: [ErrorsPlugin],
  errors: {
    defaultTypes: [Error],
  },
});

const ErrorInterface = builder.interfaceRef<Error>('Error').implement({
  fields: (t) => ({
    message: t.exposeString('message'),
  }),
});

builder.objectType(Error, {
  name: 'BaseError',
  interfaces: [ErrorInterface],
});

class LengthError extends Error {
  minLength: number;

  constructor(minLength: number) {
    super(`string length should be at least ${minLength}`);

    this.minLength = minLength;
    this.name = 'LengthError';
  }
}

builder.objectType(LengthError, {
  name: 'LengthError',
  interfaces: [ErrorInterface],
  fields: (t) => ({
    minLength: t.exposeInt('minLength'),
  }),
});

builder.queryType({
  fields: (t) => ({
    // Simple error handling just using base error class
    hello: t.string({
      errors: {},
      args: {
        name: t.arg.string({ required: true }),
      },
      resolve: (parent, { name }) => {
        if (!name.startsWith(name.slice(0, 1).toUpperCase())) {
          throw new Error('name must be capitalized');
        }

        return `hello, ${name || 'World'}`;
      },
    }),
    // Handling custom errors
    helloWithMinLength: t.string({
      errors: {
        types: [LengthError],
      },
      args: {
        name: t.arg.string({ required: true }),
      },
      resolve: (parent, { name }) => {
        if (name.length < 5) {
          throw new LengthError(5);
        }

        return `hello, ${name || 'World'}`;
      },
    }),
  }),
});

With validation plugin

To use this in combination with the validation plugin, ensure that that errors plugin is listed BEFORE the validation plugin in your plugin list.

Once your plugins are set up, you can define types for a ZodError, the same way you would for any other error type. Below is a simple example of how this can be done, but the specifics of how you structure your error types are left up to you.

// Util for flattening zod errors into something easier to represent in your Schema.
function flattenErrors(
  error: ZodFormattedError<unknown>,
  path: string[],
): { path: string[]; message: string }[] {
    const errors = error._errors.map((message) => ({
    path,
    message,
  }));

  Object.keys(error).forEach((key) => {
    if (key !== '_errors') {
      errors.push(
        ...flattenErrors((error as Record<string, unknown>)[key] as ZodFormattedError<unknown>, [
          ...path,
          key,
        ]),
      );
    }
  });

  return errors;
}

// A type for the individual validation issues
const ZodFieldError = builder
  .objectRef<{
    message: string;
    path: string[];
  }>('ZodFieldError')
  .implement({
    fields: (t) => ({
      message: t.exposeString('message'),
      path: t.exposeStringList('path'),
    }),
  });

// The actual error type
builder.objectType(ZodError, {
  name: 'ZodError',
  interfaces: [ErrorInterface],
  fields: (t) => ({
    fieldErrors: t.field({
      type: [ZodFieldError],
      resolve: (err) => flattenErrors(err.format(), []),
    }),
  }),
});

builder.queryField('fieldWIthValidation', (t) =>
  t.boolean({
    errors: {
      types: [ZodError],
    },
    args: {
      string: t.arg.string({
        validate: {
          type: 'string',
          minLength: 3,
        },
      }),
    },
    resolve: () => true,
  }),
);

Example query:

query {
  validation(string: "a") {
    __typename
    ... on QueryValidationSuccess {
      data
    }
    ... on ZodError {
      fieldErrors {
        message
        path
      }
    }
  }
}

With the dataloader plugin

To use this in combination with the dataloader plugin, ensure that that errors plugin is listed BEFORE the validation plugin in your plugin list.

If a field with errors returns a loadableObject, or loadableNode the errors plugin will now catch errors thrown when loading ids returned by the resolve function.

If the field is a List field, errors that occur when resolving objects from ids will not be handled by the errors plugin. This is because those errors are associated with each item in the list rather than the list field itself. In the future, the dataloader plugin may have an option to throw an error at the field level if any items can not be loaded, which would allow the error plugin to handle these types of errors.

With the prisma plugin

To use this in combination with the prisma plugin, ensure that that errors plugin is listed BEFORE the validation plugin in your plugin list. This will enable errors option to work work correctly with any field builder method from the prisma plugin.

errors can be configured for any field, but if there is an error pre-loading a relation the error will always surfaced at the field that executed the query. Because there are cases that fall back to executing queries for relation fields, these fields may still have errors if the relation was not pre-loaded. Detection of nested relations will continue to work if those relations use the errors plugin

更新日志

Change Log

4.4.2

Patch Changes

  • d4ecd42: Fix directResult default when used with list fields

4.4.1

Patch Changes

  • 1622740: update dependencies

4.4.0

Minor Changes

  • d36d747: fieldNames are no longer capitalized before being passed to naming functions in the errors plugin. This behavior was a bug, but this change may cause some schema name changes who configured custom naming functions in their builder options for the errors plugin
  • 9d6a30c: Add itemErrors option for handling errors in list items

4.3.0

Minor Changes

  • fd5395f: Add proper support for errors in subscriptions

4.2.1

Patch Changes

  • cd7f309: Update dependencies

4.2.0

Minor Changes

  • 27af377: replace eslint and prettier with biome

4.1.0

Minor Changes

  • 307340a: Use a shared directive name for defining indirect resolutions

4.0.2

Patch Changes

  • Updated dependencies [777f6de]
    • @pothos/core@4.0.2

4.0.1

Patch Changes

  • 9bd203e: Fix graphql peer dependency version to match documented minumum version
  • Updated dependencies [9bd203e]
    • @pothos/core@4.0.1

4.0.0

Major Changes

Patch Changes

  • c1e6dcb: update readmes
  • Updated dependencies [c1e6dcb]
  • Updated dependencies [29841a8]
    • @pothos/core@4.0.0

4.0.0-next.1

Patch Changes

  • update readmes
  • Updated dependencies
    • @pothos/core@4.0.0-next.1

4.0.0-next.0

Major Changes

Patch Changes

  • Updated dependencies [29841a8]
    • @pothos/core@4.0.0-next.0

3.11.2

Patch Changes

  • 1ecea46: revert accidental pinning of graphql peer dependency

3.11.1

Patch Changes

  • 4c6bc638: Add provinance to npm releases

3.11.0

Minor Changes

  • bf0385ae: Add new PothosError classes

3.10.1

Patch Changes

  • 372260ec: Fix bug that prevented prisma from correctly including selections when using the directResult option from the errors plugin

3.10.0

Minor Changes

  • ecc1d6f8: Add defaultGetTypeName option to @pothos/plugin-errors, this option allows customizing the generated type names by this plugin.

    An example usage of this:

    export const builderWithCustomErrorTypeNames = new SchemaBuilder<{}>({
      plugins: [ErrorPlugin, ValidationPlugin],
      errorOptions: {
        defaultTypes: [Error],
        defaultResultOptions: {
          name: ({ parentTypeName, fieldName }) => `${fieldName}_CustomResult`,
        },
        defaultUnionOptions: {
          name: ({ parentTypeName, fieldName }) => `${fieldName}_CustomUnion`,
        },
      },
    });
    

3.9.0

Minor Changes

  • cd1c0502: Add support for nested lists

3.8.7

Patch Changes

  • d4d41796: Update dev dependencies

3.8.6

Patch Changes

  • 6f00194c: Fix an issue with esm import transform

3.8.5

Patch Changes

  • b12f9122: Fix issue with esm build script

3.8.4

Patch Changes

  • 9fa27cf7: Transform dynamic type imports in d.ts files

3.8.3

Patch Changes

  • 3a82d645: Apply esm transform to esm d.ts definitions

3.8.2

Patch Changes

  • 218fc68b: Fix script for copying ems d.ts definitions

3.8.1

Patch Changes

  • 67531f1e: Create separate typescript definitions for esm files

3.8.0

Minor Changes

  • 11929311: Update type definitions to work with module: "nodeNext"

3.7.1

Patch Changes

  • aa18acb7: update dev dependencies

3.7.0

Minor Changes

  • d67764b5: Make options objecst on toSchema, queryType, and mutationType optional

3.6.0

Minor Changes

  • 09572175: Add builder options for default union and result type options

3.5.1

Patch Changes

  • 3ead60ae: update dev deps

3.5.0

Minor Changes

  • 3a7ff291: Refactor internal imports to remove import cycles

Patch Changes

  • 3a7ff291: Update dev dependencies

3.4.2

Patch Changes

  • 4e5756ca: Update dev dependencies

3.4.1

Patch Changes

  • 4b24982f: Update dev dependencies

3.4.0

Minor Changes

  • ecb2714c: Add types entry to export map in package.json and update dev dependencies

    This should fix compatibility with typescripts new "moduleResolution": "node12"

3.3.0

Minor Changes

  • 241a385f: Add peer dependency on @pothos/core

3.2.0

Minor Changes

  • 6279235f: Update build process to use swc and move type definitions to dts directory

Patch Changes

  • 21a2454e: update dev dependencies

3.1.1

Patch Changes

  • 03aecf76: update .npmignore

3.1.0

Minor Changes

  • 4ad5f4ff: Normalize resolveType and isTypeOf behavior to match graphql spec behavior and allow both to be optional

Patch Changes

  • 43ca3031: Update dev dependencies

3.0.1

Patch Changes

  • 2d9b21cd: Use workspace:* for dev dependencies on pothos packages

3.0.0

Major Changes

  • 4caad5e4: Rename GiraphQL to Pothos

2.13.0

Minor Changes

  • 9307635a: Migrate build process to use turborepo

2.12.3

Patch Changes

  • 2b08f852: Fix syntax highlighting in docs and update npm README.md files"

2.12.2

Patch Changes

  • c6aa732: graphql@15 type compatibility fix

2.12.1

Patch Changes

  • c85dc33: Add types entry in package.json

2.12.0

Minor Changes

  • aeef5e5: Update dependencies

2.11.0

Minor Changes

  • 9107f29: Update dependencies (includes graphql 16)

2.10.0

Minor Changes

  • 17db3bd: Make type refs extendable by plugins

2.9.0

Minor Changes

  • 73e947b: Add directResult option to remove extra result type wrapper in error unions

2.8.2

Patch Changes

  • c976bfe: Update dependencies

2.8.1

Patch Changes

  • 4150f92: Fixed esm transformer for path-imports from dependencies

2.8.0

Minor Changes

  • dc87e68: update esm build process so extensions are added during build rather than in source

2.7.2

Patch Changes

  • b4b8381: Updrade deps (typescript 4.4)

2.7.1

Patch Changes

  • f04be64: Update dependencies

2.7.0

Minor Changes

  • ea4d456: Add interoperability between prisma and errors plugins

2.6.0

Minor Changes

  • 4f9b886: Add integration between error and dataloader plugins to that errors from dataloaders can be handled via errors plugin

2.5.0

Minor Changes

  • a4c87cf: Use ".js" extensions everywhere and add module and exports to package.json to better support ems in node

2.4.2

Patch Changes

  • f13208c: bump to fix latest tag

2.4.1

Patch Changes

  • 9ab8fbc: re-release previous version due to build-process issue

2.4.0

Minor Changes

  • 3dd3ff14: Updated dev dependencies, switched to pnpm, and added changesets for releases

All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.

2.3.1 - 2021-08-03

Note: Version bump only for package @giraphql/plugin-errors

2.3.1-alpha.0 - 2021-08-02

Note: Version bump only for package @giraphql/plugin-errors

2.3.0 - 2021-07-30

🚀 Updates

Note: Version bump only for package @giraphql/plugin-errors

2.2.2 - 2021-07-23

Note: Version bump only for package @giraphql/plugin-errors

2.2.2-alpha.0 - 2021-07-17

Note: Version bump only for package @giraphql/plugin-errors

2.2.1 - 2021-07-10

Note: Version bump only for package @giraphql/plugin-errors

2.2.0 - 2021-07-04

Note: Version bump only for package @giraphql/plugin-errors

2.2.0-alpha.0 - 2021-07-04

🚀 Updates

  • add early warning for undefined refs to simplify debugging of circular import issues (095b68b)

📦 Dependencies

Note: Version bump only for package @giraphql/plugin-errors

2.1.2 - 2021-07-02

🐞 Fixes

  • only create error types once (60fddd8)

📘 Docs

  • add es6 target requirement to error plugin docs (a218973)

Note: Version bump only for package @giraphql/plugin-errors

2.1.1 - 2021-06-30

📘 Docs

  • update docs to include links to error plugin (46db92d)

Note: Version bump only for package @giraphql/plugin-errors

2.1.0 - 2021-06-28

🐞 Fixes

  • type default empty objects as never to ensure compatibility with plugins that add required options (e457c02)

Note: Version bump only for package @giraphql/plugin-errors

2.1.0-alpha.1 - 2021-06-28

🚀 Updates

  • make error options optional only when options can be empty objects (6791bcb)
  • update docs and deno (4f131b0)

🐞 Fixes

  • fix typos in tests and docs (3b81ba2)

Note: Version bump only for package @giraphql/plugin-errors

2.1.0-alpha.0 - 2021-06-28

🚀 Updates

Note: Version bump only for package @giraphql/plugin-errors