From 3f31e1861ad6f0697f0f815ecc113eccf89c93ea Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Tue, 10 Sep 2024 17:39:47 -0400 Subject: [PATCH] chore(docs): add return execution errors example (#1079) --- examples/$/helpers.ts | 7 +- examples/$/schemas/pokemon/schema.ts | 8 +- .../output|output_envelope.output.txt | 10 +- ..._envelope-error__envelope-error.output.txt | 16 +-- ...ror-throw__envelope-error-throw.output.txt | 16 +-- .../output|output_return-error.output.txt | 17 +++ ...ecution__return-error-execution.output.txt | 55 +++++++++ ...tension_headers__dynamicHeaders.output.txt | 2 +- ...envelope_envelope-error__envelope-error.ts | 26 ++-- examples/output|output_return-error.ts | 28 +++++ ...error-execution__return-error-execution.ts | 45 +++++++ package.json | 7 +- pnpm-lock.yaml | 22 +++- .../generate-docs.ts | 15 +-- .../generate-outputs.ts | 12 +- .../generate-examples-derivatives/generate.ts | 9 +- .../generate-examples-derivatives/helpers.ts | 1 + src/layers/6_client/RootTypeMethods.ts | 8 +- src/layers/6_client/handleOutput.ts | 5 + src/lib/prelude.ts | 10 +- .../output|output_return-error.test.ts | 19 +++ ...-execution__return-error-execution.test.ts | 21 ++++ website/.vitepress/config.ts | 1 + website/.vitepress/configExamples.ts | 8 ++ .../content/examples/generated-arguments.md | 1 - .../examples/other-transport-memory.md | 1 - website/content/examples/output-default.md | 1 - .../examples/output-envelope-error-throw.md | 16 +-- .../content/examples/output-envelope-error.md | 43 +++---- website/content/examples/output-envelope.md | 11 +- .../examples/output-return-error-execution.md | 112 ++++++++++++++++++ .../content/examples/output-return-error.md | 57 +++++++++ .../content/examples/raw-raw-string-typed.md | 1 - website/content/examples/raw-raw-string.md | 1 - website/content/examples/raw-raw.md | 1 - .../content/examples/transport-http-abort.md | 1 - .../examples/transport-http-custom-fetch.md | 1 - .../transport-http-dynamic-headers.md | 2 +- .../content/guides/_example_links/output.md | 2 +- .../output_return-error-execution.md | 1 + .../_example_links/output_return-error.md | 1 + ...put_return-error_return-error-execution.md | 1 + .../_example_links/return-error-execution.md | 1 + .../guides/_example_links/return-error.md | 1 + .../return-error_return-error-execution.md | 1 + 45 files changed, 515 insertions(+), 110 deletions(-) create mode 100644 examples/__outputs__/output|output_return-error.output.txt create mode 100644 examples/__outputs__/output|output_return-error_return-error-execution__return-error-execution.output.txt create mode 100644 examples/output|output_return-error.ts create mode 100644 examples/output|output_return-error_return-error-execution__return-error-execution.ts create mode 100644 tests/examples/output|output_return-error.test.ts create mode 100644 tests/examples/output|output_return-error_return-error-execution__return-error-execution.test.ts create mode 100644 website/content/examples/output-return-error-execution.md create mode 100644 website/content/examples/output-return-error.md create mode 100644 website/content/guides/_example_links/output_return-error-execution.md create mode 100644 website/content/guides/_example_links/output_return-error.md create mode 100644 website/content/guides/_example_links/output_return-error_return-error-execution.md create mode 100644 website/content/guides/_example_links/return-error-execution.md create mode 100644 website/content/guides/_example_links/return-error.md create mode 100644 website/content/guides/_example_links/return-error_return-error-execution.md diff --git a/examples/$/helpers.ts b/examples/$/helpers.ts index e53715523..4e57ee041 100644 --- a/examples/$/helpers.ts +++ b/examples/$/helpers.ts @@ -26,10 +26,11 @@ export const showJson = (value: unknown) => { export const serveSchema = async (input: { schema: GraphQLSchema }) => { const { schema } = input - const yoga = createYoga({ schema }) + const yoga = createYoga({ schema, logging: false, maskedErrors: false }) const server = createServer(yoga) // eslint-disable-line const port = await getPort({ port: [3000, 3001, 3002, 3003, 3004] }) const url = new URL(`http://localhost:${String(port)}/graphql`) + let runState = true server.listen(port) await new Promise((resolve) => server.once(`listening`, () => { @@ -37,6 +38,8 @@ export const serveSchema = async (input: { schema: GraphQLSchema }) => { }) ) const stop = async () => { + if (!runState) return + runState = false await new Promise((resolve) => { server.close(resolve) setImmediate(() => { @@ -45,6 +48,8 @@ export const serveSchema = async (input: { schema: GraphQLSchema }) => { }) } + process.once('beforeExit', stop) + return { yoga, server, diff --git a/examples/$/schemas/pokemon/schema.ts b/examples/$/schemas/pokemon/schema.ts index bbe173208..bb5a76f34 100644 --- a/examples/$/schemas/pokemon/schema.ts +++ b/examples/$/schemas/pokemon/schema.ts @@ -1,5 +1,6 @@ import SchemaBuilder from '@pothos/core' import SimpleObjectsPlugin from '@pothos/plugin-simple-objects' +import ZodPlugin from '@pothos/plugin-zod' type Trainer = { id: number @@ -23,7 +24,7 @@ const builder = new SchemaBuilder<{ } } }>({ - plugins: [SimpleObjectsPlugin], + plugins: [SimpleObjectsPlugin, ZodPlugin], }) type Database = { @@ -97,7 +98,10 @@ builder.mutationField(`addPokemon`, (t) => t.field({ type: Pokemon, args: { - name: t.arg.string({ required: true }), + name: t.arg.string({ + required: true, + validate: { minLength: [1, { message: 'Pokemon name cannot be empty.' }] }, + }), hp: t.arg.int({ required: true }), attack: t.arg.int({ required: true }), defense: t.arg.int({ required: true }), diff --git a/examples/__outputs__/output|output_envelope.output.txt b/examples/__outputs__/output|output_envelope.output.txt index f14b68a41..3b1c6af3c 100644 --- a/examples/__outputs__/output|output_envelope.output.txt +++ b/examples/__outputs__/output|output_envelope.output.txt @@ -19,7 +19,7 @@ headers: Headers { connection: 'keep-alive', 'content-length': '119', - 'x-served-by': 'cache-yul1970035-YUL', + 'x-served-by': 'cache-yul1970023-YUL', 'accept-ranges': 'bytes', date: 'Sun, 08 Sep 2024 18:13:26 GMT', 'content-type': 'application/graphql-response+json; charset=utf-8', @@ -32,13 +32,13 @@ 'alt-svc': 'h3=":443"; ma=86400', 'access-control-allow-origin': '*', 'x-powered-by': 'Stellate', - age: '176284', + age: '185021', 'cache-control': 'public, s-maxage=2628000, stale-while-revalidate=2628000', 'x-cache': 'HIT', - 'x-cache-hits': '13', + 'x-cache-hits': '38', 'gcdn-cache': 'HIT', - 'stellate-rate-limit-budget-remaining': '49', - 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=49;refill=60', + 'stellate-rate-limit-budget-remaining': '36', + 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=36;refill=6', 'stellate-rate-limit-decision': 'pass', 'stellate-rate-limit-budget-required': '5', 'content-encoding': 'br' diff --git a/examples/__outputs__/output|output_envelope_envelope-error__envelope-error.output.txt b/examples/__outputs__/output|output_envelope_envelope-error__envelope-error.output.txt index 0dd2a03f9..92466eb2d 100644 --- a/examples/__outputs__/output|output_envelope_envelope-error__envelope-error.output.txt +++ b/examples/__outputs__/output|output_envelope_envelope-error__envelope-error.output.txt @@ -2,20 +2,20 @@ { errors: [ ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". - at runPipeline (/some/path/to/runPipeline.ts:76:18) - at async Object.run (/some/path/to/main.ts:286:22) - at async run (/some/path/to/client.ts:256:20) - at async executeRootType (/some/path/to/client.ts:185:12) - at async executeRootTypeField (/some/path/to/client.ts:216:20) - at async (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:24:16) { + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:XX:XX) { context: { hookName: 'encode', source: 'extension', extensionName: 'anonymous' }, cause: Error: Something went wrong. - at (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:20:9) - at applyBody (/some/path/to/main.ts:310:28) + at (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) } ] } \ No newline at end of file diff --git a/examples/__outputs__/output|output_envelope_envelope_error-throw__envelope-error-throw.output.txt b/examples/__outputs__/output|output_envelope_envelope_error-throw__envelope-error-throw.output.txt index 39243cefb..470e09334 100644 --- a/examples/__outputs__/output|output_envelope_envelope_error-throw__envelope-error-throw.output.txt +++ b/examples/__outputs__/output|output_envelope_envelope_error-throw__envelope-error-throw.output.txt @@ -4,20 +4,20 @@ ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". - at runPipeline (/some/path/to/runPipeline.ts:76:18) - at async Object.run (/some/path/to/main.ts:286:22) - at async run (/some/path/to/client.ts:256:20) - at async executeRootType (/some/path/to/client.ts:185:12) - at async executeRootTypeField (/some/path/to/client.ts:216:20) - at async (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:22:1) { + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) { context: { hookName: 'encode', source: 'extension', extensionName: 'anonymous' }, cause: Error: Something went wrong. - at (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:18:9) - at applyBody (/some/path/to/main.ts:310:28) + at (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) } Node.js vXX.XX.XX \ No newline at end of file diff --git a/examples/__outputs__/output|output_return-error.output.txt b/examples/__outputs__/output|output_return-error.output.txt new file mode 100644 index 000000000..c3a8661c8 --- /dev/null +++ b/examples/__outputs__/output|output_return-error.output.txt @@ -0,0 +1,17 @@ +---------------------------------------- SHOW ---------------------------------------- +ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_return-error.ts:XX:XX) { + context: { + hookName: 'encode', + source: 'extension', + extensionName: 'anonymous' + }, + cause: Error: Something went wrong. + at (/some/path/to/output|output_return-error.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) +} \ No newline at end of file diff --git a/examples/__outputs__/output|output_return-error_return-error-execution__return-error-execution.output.txt b/examples/__outputs__/output|output_return-error_return-error-execution__return-error-execution.output.txt new file mode 100644 index 000000000..2498b99be --- /dev/null +++ b/examples/__outputs__/output|output_return-error_return-error-execution__return-error-execution.output.txt @@ -0,0 +1,55 @@ +---------------------------------------- SHOW ---------------------------------------- +ContextualAggregateError: One or more errors in the execution result. + at handleOutput (/some/path/to/handleOutput.ts:XX:XX) + at run (/some/path/to/client.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_return-error_return-error-execution__return-error-execution.ts:XX:XX) { + context: {}, + cause: undefined, + errors: [ + GraphQLError: [ + { + "code": "too_small", + "minimum": 1, + "type": "string", + "inclusive": true, + "exact": false, + "message": "Pokemon name cannot be empty.", + "path": [ + "name" + ] + } + ] + at (/some/path/to/graphqlHTTP.ts:XX:XX) + at Array.map () + at parseExecutionResult (/some/path/to/graphqlHTTP.ts:XX:XX) + at Object.unpack (/some/path/to/core.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) + at async runHook (/some/path/to/runHook.ts:XX:XX) { + path: [ 'addPokemon' ], + locations: undefined, + extensions: [Object: null prototype] {} + } + ] +} +---------------------------------------- SHOW ---------------------------------------- +ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_return-error_return-error-execution__return-error-execution.ts:XX:XX) { + context: { + hookName: 'encode', + source: 'extension', + extensionName: 'anonymous' + }, + cause: Error: Something went wrong. + at (/some/path/to/output|output_return-error_return-error-execution__return-error-execution.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) +} \ No newline at end of file diff --git a/examples/__outputs__/transport-http|transport-http_extension_headers__dynamicHeaders.output.txt b/examples/__outputs__/transport-http|transport-http_extension_headers__dynamicHeaders.output.txt index b42fd66ad..e45dca79e 100644 --- a/examples/__outputs__/transport-http|transport-http_extension_headers__dynamicHeaders.output.txt +++ b/examples/__outputs__/transport-http|transport-http_extension_headers__dynamicHeaders.output.txt @@ -4,7 +4,7 @@ headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', - 'x-sent-at-time': '1725995489078' + 'x-sent-at-time': '1726004226733' }, signal: undefined, method: 'post', diff --git a/examples/output|output_envelope_envelope-error__envelope-error.ts b/examples/output|output_envelope_envelope-error__envelope-error.ts index 8fb733200..9e9abb0bb 100644 --- a/examples/output|output_envelope_envelope-error__envelope-error.ts +++ b/examples/output|output_envelope_envelope-error__envelope-error.ts @@ -6,20 +6,22 @@ import { Atlas } from './$/generated-clients/atlas/__.js' import { show } from './$/helpers.js' // dprint-ignore -const atlas = Atlas.create({ - output: { - envelope: { - errors: { -// ^^^^^^ - execution: true, // default - other: true, +const atlas = Atlas + .create({ + output: { + envelope: { + errors: { + // ^^^^^^ + execution: true, // default + other: true, + }, }, }, - }, -}).use(({ encode: _ }) => { - throw new Error(`Something went wrong.`) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -}) + }) + .use(({ encode: _ }) => { + throw new Error(`Something went wrong.`) + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + }) const result = await atlas.query.continents({ name: true }) diff --git a/examples/output|output_return-error.ts b/examples/output|output_return-error.ts new file mode 100644 index 000000000..1320437ae --- /dev/null +++ b/examples/output|output_return-error.ts @@ -0,0 +1,28 @@ +/** + * This example shows how to configure output to have errors returned instead of e.g. thrown. + */ + +import { Atlas } from './$/generated-clients/atlas/__.js' +import { show } from './$/helpers.js' + +// dprint-ignore +const atlas = Atlas + .create({ + output: { + envelope: false, + defaults: { + errorChannel: `return`, + }, + }, + }) + // dprint-ignore + .use(({ encode: _ }) => { + throw new Error(`Something went wrong.`) +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + }) + +const result = await atlas.query.continents({ name: true }) +type _result = typeof result +// ^? + +show(result) diff --git a/examples/output|output_return-error_return-error-execution__return-error-execution.ts b/examples/output|output_return-error_return-error-execution__return-error-execution.ts new file mode 100644 index 000000000..6ac039f04 --- /dev/null +++ b/examples/output|output_return-error_return-error-execution__return-error-execution.ts @@ -0,0 +1,45 @@ +/** + * This example shows how to configure output to have only certain kinds of errors returned while others thrown. + */ + +import { Pokemon } from './$/generated-clients/pokemon/__.js' +import { serveSchema, show } from './$/helpers.js' +import { schema } from './$/schemas/pokemon/schema.js' + +const server = await serveSchema({ schema: schema }) + +const pokemon = Pokemon + .create({ + schema: server.url, + output: { + envelope: false, + errors: { + execution: `return`, + other: `throw`, + }, + }, + }) + +// 1. The __execution__ error of an empty Pokemon name will be ***returned***. + +type _result = typeof result +const result = await pokemon.mutation.addPokemon({ + $: { name: ``, hp: 1, defense: 0, attack: 0 }, + name: true, +}) +show(result) + +// 2. The __other__ error, in this case from the inline extension, will be ***thrown***. + +try { + await pokemon + .use(({ encode: _ }) => { + throw new Error(`Something went wrong.`) + }) + .query + .pokemon({ name: true }) +} catch (error) { + show(error) +} + +await server.stop() diff --git a/package.json b/package.json index 59745ec45..b6d46ebbf 100644 --- a/package.json +++ b/package.json @@ -86,8 +86,7 @@ "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "@molt/command": "^0.9.0", - "is-plain-obj": "^4.1.0", - "zod": "^3.23.8" + "is-plain-obj": "^4.1.0" }, "peerDependencies": { "@dprint/formatter": "^0.3.0 || ^0.4.0", @@ -110,6 +109,7 @@ "@arethetypeswrong/cli": "^0.16.2", "@pothos/core": "^4.2.0", "@pothos/plugin-simple-objects": "^4.1.0", + "@pothos/plugin-zod": "^4.1.0", "@tsconfig/node20": "^20.1.4", "@tsconfig/strictest": "^2.0.5", "@types/body-parser": "^1.19.5", @@ -146,6 +146,7 @@ "typescript": "^5.6.2", "typescript-eslint": "^8.5.0", "vitepress": "^1.3.4", - "vitest": "^2.0.5" + "vitest": "^2.0.5", + "zod": "^3.23.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12f99313c..cebd0a909 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,9 +26,6 @@ importers: is-plain-obj: specifier: ^4.1.0 version: 4.1.0 - zod: - specifier: ^3.23.8 - version: 3.23.8 devDependencies: '@arethetypeswrong/cli': specifier: ^0.16.2 @@ -39,6 +36,9 @@ importers: '@pothos/plugin-simple-objects': specifier: ^4.1.0 version: 4.1.0(@pothos/core@4.2.0(graphql@16.9.0))(graphql@16.9.0) + '@pothos/plugin-zod': + specifier: ^4.1.0 + version: 4.1.0(@pothos/core@4.2.0(graphql@16.9.0))(graphql@16.9.0)(zod@3.23.8) '@tsconfig/node20': specifier: ^20.1.4 version: 20.1.4 @@ -150,6 +150,9 @@ importers: vitest: specifier: ^2.0.5 version: 2.0.5(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@25.0.0) + zod: + specifier: ^3.23.8 + version: 3.23.8 packages: @@ -861,6 +864,13 @@ packages: '@pothos/core': '*' graphql: '>=16.6.0' + '@pothos/plugin-zod@4.1.0': + resolution: {integrity: sha512-KNAFllvYTZimY+rjELNRRUNVyN2zhZ0jIGsTDb9QVYZ8c5EBT3tHjuImu7t12GHnOAkuTimCEk/VngyMUQpEyw==} + peerDependencies: + '@pothos/core': '*' + graphql: '>=16.6.0' + zod: '*' + '@repeaterjs/repeater@3.0.6': resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} @@ -4488,6 +4498,12 @@ snapshots: '@pothos/core': 4.2.0(graphql@16.9.0) graphql: 16.9.0 + '@pothos/plugin-zod@4.1.0(@pothos/core@4.2.0(graphql@16.9.0))(graphql@16.9.0)(zod@3.23.8)': + dependencies: + '@pothos/core': 4.2.0(graphql@16.9.0) + graphql: 16.9.0 + zod: 3.23.8 + '@repeaterjs/repeater@3.0.6': {} '@rollup/rollup-android-arm-eabi@4.21.2': diff --git a/scripts/generate-examples-derivatives/generate-docs.ts b/scripts/generate-examples-derivatives/generate-docs.ts index 67faf769c..7b601e821 100644 --- a/scripts/generate-examples-derivatives/generate-docs.ts +++ b/scripts/generate-examples-derivatives/generate-docs.ts @@ -158,17 +158,10 @@ const transformRewriteHelperImports = (example: Example) => { const newContent = example.file.content .replaceAll(/^import.*\$\/helpers.*$\n/gm, ``) .replaceAll(`documentQueryContinents`, `{ document: \`${documentQueryContinents.document}\` }`) - .replaceAll( - `publicGraphQLSchemaEndpoints.Atlas`, - `\`${publicGraphQLSchemaEndpoints.Atlas}\``, - ) - .replaceAll(`showJson`, consoleLog) - .replaceAll(`show`, consoleLog) - .replaceAll( - /(^console.log.*$)/gm, - `$1 -//${` `.repeat(consoleLog.length - 1)}^?`, - ) + .replaceAll(`publicGraphQLSchemaEndpoints.Atlas`, `\`${publicGraphQLSchemaEndpoints.Atlas}\``) + .replaceAll(/showJson|show/g, consoleLog) + // We disabled this because the popover gets in the way of output below often. + // .replaceAll(/(^console.log.*$)/gm, `$1\n//${` `.repeat(consoleLog.length - 1)}^?`) return { ...example, diff --git a/scripts/generate-examples-derivatives/generate-outputs.ts b/scripts/generate-examples-derivatives/generate-outputs.ts index 9ee15282e..b315fb8d1 100644 --- a/scripts/generate-examples-derivatives/generate-outputs.ts +++ b/scripts/generate-examples-derivatives/generate-outputs.ts @@ -1,18 +1,20 @@ import * as FS from 'node:fs/promises' import { deleteFiles } from '../lib/deleteFiles.js' -import { directories, type Example, runExample } from './helpers.js' +import { directories, readExampleFiles, runExample } from './helpers.js' + +export const generateOutputs = async () => { + const exampleFiles = await readExampleFiles() -export const generateOutputs = async (examples: Example[]) => { // Handle case of renaming or deleting examples. await Promise.all([ deleteFiles({ pattern: `./examples/*.output.txt` }), deleteFiles({ pattern: `${directories.outputs}/*.output.txt` }), ]) - await Promise.all(examples.map(async (example) => { - const filePath = `./examples/${example.file.name}.ts` + await Promise.all(exampleFiles.map(async (file) => { + const filePath = `./examples/${file.name}.ts` const exampleResult = await runExample(filePath) - await FS.writeFile(`${directories.outputs}/${example.file.name}.output.txt`, exampleResult) + await FS.writeFile(`${directories.outputs}/${file.name}.output.txt`, exampleResult) })) console.log(`Generated an output for each example.`) diff --git a/scripts/generate-examples-derivatives/generate.ts b/scripts/generate-examples-derivatives/generate.ts index 0d8c03194..bb689cb44 100644 --- a/scripts/generate-examples-derivatives/generate.ts +++ b/scripts/generate-examples-derivatives/generate.ts @@ -3,6 +3,11 @@ import { generateOutputs } from './generate-outputs.js' import { generateTests } from './generate-tests.js' import { readExamples } from './helpers.js' +await generateOutputs() + const examples = await readExamples() -await Promise.all([generateOutputs(examples), generateTests(examples)]) -await generateDocs(examples) + +await Promise.all([ + generateTests(examples), + generateDocs(examples), +]) diff --git a/scripts/generate-examples-derivatives/helpers.ts b/scripts/generate-examples-derivatives/helpers.ts index 9fc545666..5502e4144 100644 --- a/scripts/generate-examples-derivatives/helpers.ts +++ b/scripts/generate-examples-derivatives/helpers.ts @@ -166,4 +166,5 @@ export const rewriteDynamicError = (value: string) => { .replaceAll(/\/.*\/(.+)\.ts/g, `/some/path/to/$1.ts`) // When Node.js process exits via an uncaught thrown error, version is printed at bottom. .replaceAll(/Node\.js v.+/g, `Node.js vXX.XX.XX`) + .replaceAll(/(.+):\d+:\d+\)/g, `$1:XX:XX)`) } diff --git a/src/layers/6_client/RootTypeMethods.ts b/src/layers/6_client/RootTypeMethods.ts index 925d05445..38b62d04d 100644 --- a/src/layers/6_client/RootTypeMethods.ts +++ b/src/layers/6_client/RootTypeMethods.ts @@ -4,7 +4,7 @@ import type { TSError } from '../../lib/TSError.js' import type { InputFieldsAllNullable, Schema } from '../1_Schema/__.js' import type { SelectionSet } from '../3_SelectionSet/__.js' import type { ResultSet } from '../4_ResultSet/__.js' -import type { ResolveOutputReturnRootField, ResolveOutputReturnRootType } from './handleOutput.js' +import type { ResolveOutputReturnRootField, ResolveOutputReturnRootType, SimplifyOutput } from './handleOutput.js' import type { AugmentRootTypeSelectionWithTypename, Config, @@ -79,10 +79,14 @@ type RootTypeFieldMethod_<$Context extends RootTypeFieldContext, $Type extends S $Type extends Schema.__typename ? ScalarFieldMethod<$Context> : ObjectLikeFieldMethod<$Context> +// If this type is not locally defined, apparently it affects the computed TS type. +// This exact same utility type used via import leads to different results....?! +type SimplifyOutputUnionLocal = T extends any ? SimplifyOutput : never + // dprint-ignore type ObjectLikeFieldMethod<$Context extends RootTypeFieldContext> = <$SelectionSet>(selectionSet: Exact<$SelectionSet, SelectionSet.Field<$Context['Field'], $Context['Index'], { hideDirectives: true }>>) => - Promise, $Context['Field'], $Context['Index']>>> + Promise, $Context['Field'], $Context['Index']>>>> // dprint-ignore type ScalarFieldMethod<$Context extends RootTypeFieldContext> = diff --git a/src/layers/6_client/handleOutput.ts b/src/layers/6_client/handleOutput.ts index b96cbd247..ee1b623f1 100644 --- a/src/layers/6_client/handleOutput.ts +++ b/src/layers/6_client/handleOutput.ts @@ -1,5 +1,6 @@ import type { GraphQLError } from 'graphql' import type { Simplify } from 'type-fest' +import type { ConditionalSimplify } from 'type-fest/source/conditional-simplify.js' import { Errors } from '../../lib/errors/__.js' import type { GraphQLExecutionResultError } from '../../lib/graphql.js' import { isPlainObject, type SimplifyExceptError, type Values } from '../../lib/prelude.js' @@ -228,3 +229,7 @@ type IsEnvelopeWithoutErrors<$Config extends Config> = ? true : false : false + +export type SimplifyOutput = ConditionalSimplify + +export type SimplifyOutputUnion = T extends any ? SimplifyOutput : never diff --git a/src/lib/prelude.ts b/src/lib/prelude.ts index ce6a247e1..ca8e6757f 100644 --- a/src/lib/prelude.ts +++ b/src/lib/prelude.ts @@ -183,8 +183,6 @@ export type Values = T[keyof T] export type GetKeyOr = Key extends keyof T ? T[Key] : Or -export type SimplifyDeep = ConditionalSimplifyDeep | Date, object> - export type As = U extends T ? U : never export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never @@ -398,6 +396,14 @@ export type Negate = T extends true ? false : true export type SimplifyExceptError = ConditionalSimplify +export type SimplifyExceptErrorUnion = T extends any ? SimplifyExceptError : never + +export type SimplifyUnion = T extends any ? Simplify : never + +export type SimplifyDeep = ConditionalSimplifyDeep | Date, object> + +export type SimplifyDeepUnion = T extends any ? SimplifyDeep : never + export type RequireProperties = Simplify export const throwNull = (value: V): Exclude => { diff --git a/tests/examples/output|output_return-error.test.ts b/tests/examples/output|output_return-error.test.ts new file mode 100644 index 000000000..6909795fa --- /dev/null +++ b/tests/examples/output|output_return-error.test.ts @@ -0,0 +1,19 @@ +// @vitest-environment node + +// WARNING: +// This test is generated by scripts/generate-example-derivatives/generate.ts +// Do not modify this file directly. + +import { expect, test } from 'vitest' +import { runExample } from '../../scripts/generate-examples-derivatives/helpers.js' + +test(`output|output_return-error`, async () => { + const exampleResult = await runExample(`./examples/output|output_return-error.ts`) + // Examples should output their data results. + const exampleResultMaybeEncoded = exampleResult + // If ever outputs vary by Node version, you can use this to snapshot by Node version. + // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` + await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( + `../.././examples/__outputs__/output|output_return-error.output.txt`, + ) +}) diff --git a/tests/examples/output|output_return-error_return-error-execution__return-error-execution.test.ts b/tests/examples/output|output_return-error_return-error-execution__return-error-execution.test.ts new file mode 100644 index 000000000..833fde0da --- /dev/null +++ b/tests/examples/output|output_return-error_return-error-execution__return-error-execution.test.ts @@ -0,0 +1,21 @@ +// @vitest-environment node + +// WARNING: +// This test is generated by scripts/generate-example-derivatives/generate.ts +// Do not modify this file directly. + +import { expect, test } from 'vitest' +import { runExample } from '../../scripts/generate-examples-derivatives/helpers.js' + +test(`output|output_return-error_return-error-execution__return-error-execution`, async () => { + const exampleResult = await runExample( + `./examples/output|output_return-error_return-error-execution__return-error-execution.ts`, + ) + // Examples should output their data results. + const exampleResultMaybeEncoded = exampleResult + // If ever outputs vary by Node version, you can use this to snapshot by Node version. + // const nodeMajor = process.version.match(/v(\d+)/)?.[1] ?? `unknown` + await expect(exampleResultMaybeEncoded).toMatchFileSnapshot( + `../.././examples/__outputs__/output|output_return-error_return-error-execution__return-error-execution.output.txt`, + ) +}) diff --git a/website/.vitepress/config.ts b/website/.vitepress/config.ts index 8e1ef01e8..86e7c822b 100644 --- a/website/.vitepress/config.ts +++ b/website/.vitepress/config.ts @@ -27,6 +27,7 @@ export default defineConfig({ compilerOptions: { moduleResolution: ModuleResolutionKind.Bundler, module: ModuleKind.ESNext, + noErrorTruncation: true, }, extraFiles: { diff --git a/website/.vitepress/configExamples.ts b/website/.vitepress/configExamples.ts index f7f3342b4..661a5e312 100644 --- a/website/.vitepress/configExamples.ts +++ b/website/.vitepress/configExamples.ts @@ -38,6 +38,14 @@ { "text": "Envelope Error Throw", "link": "/examples/output-envelope-error-throw" + }, + { + "text": "Return Error", + "link": "/examples/output-return-error" + }, + { + "text": "Return Error Execution", + "link": "/examples/output-return-error-execution" } ] }, diff --git a/website/content/examples/generated-arguments.md b/website/content/examples/generated-arguments.md index 0824098b1..b54ba3bb5 100644 --- a/website/content/examples/generated-arguments.md +++ b/website/content/examples/generated-arguments.md @@ -20,7 +20,6 @@ const countries = await atlas.query.countries({ }) console.log(countries) -// ^? ``` diff --git a/website/content/examples/other-transport-memory.md b/website/content/examples/other-transport-memory.md index 191d4831e..45d408b39 100644 --- a/website/content/examples/other-transport-memory.md +++ b/website/content/examples/other-transport-memory.md @@ -28,7 +28,6 @@ const graffle = Graffle.create({ schema }) const result = await graffle.rawString({ document: `{ foo }` }) console.log(result) -// ^? ``` diff --git a/website/content/examples/output-default.md b/website/content/examples/output-default.md index b8bbb1662..c6673e7aa 100644 --- a/website/content/examples/output-default.md +++ b/website/content/examples/output-default.md @@ -16,7 +16,6 @@ const atlas = Atlas.create() const result = await atlas.query.continents({ name: true }) console.log(result) -// ^? ``` diff --git a/website/content/examples/output-envelope-error-throw.md b/website/content/examples/output-envelope-error-throw.md index ccf24ca98..5c874cdfa 100644 --- a/website/content/examples/output-envelope-error-throw.md +++ b/website/content/examples/output-envelope-error-throw.md @@ -39,20 +39,20 @@ await atlas.query.continents({ name: true }) ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". - at runPipeline (/some/path/to/runPipeline.ts:76:18) - at async Object.run (/some/path/to/main.ts:286:22) - at async run (/some/path/to/client.ts:256:20) - at async executeRootType (/some/path/to/client.ts:185:12) - at async executeRootTypeField (/some/path/to/client.ts:216:20) - at async (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:22:1) { + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) { context: { hookName: 'encode', source: 'extension', extensionName: 'anonymous' }, cause: Error: Something went wrong. - at (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:18:9) - at applyBody (/some/path/to/main.ts:310:28) + at (/some/path/to/output|output_envelope_envelope_error-throw__envelope-error-throw.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) } Node.js vXX.XX.XX diff --git a/website/content/examples/output-envelope-error.md b/website/content/examples/output-envelope-error.md index f34633634..f744c3dee 100644 --- a/website/content/examples/output-envelope-error.md +++ b/website/content/examples/output-envelope-error.md @@ -11,25 +11,26 @@ This example shows how to configure output to embed errors into the envelope. // ---cut--- import { Graffle as Atlas } from './graffle/__.js' -const atlas = Atlas.create({ - output: { - envelope: { - errors: { -// ^^^^^^ - execution: true, // default - other: true, +const atlas = Atlas + .create({ + output: { + envelope: { + errors: { + // ^^^^^^ + execution: true, // default + other: true, + }, }, }, - }, -}).use(({ encode: _ }) => { - throw new Error(`Something went wrong.`) -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -}) + }) + .use(({ encode: _ }) => { + throw new Error(`Something went wrong.`) + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + }) const result = await atlas.query.continents({ name: true }) console.log(result) -// ^? ``` @@ -40,20 +41,20 @@ console.log(result) { errors: [ ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". - at runPipeline (/some/path/to/runPipeline.ts:76:18) - at async Object.run (/some/path/to/main.ts:286:22) - at async run (/some/path/to/client.ts:256:20) - at async executeRootType (/some/path/to/client.ts:185:12) - at async executeRootTypeField (/some/path/to/client.ts:216:20) - at async (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:24:16) { + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:XX:XX) { context: { hookName: 'encode', source: 'extension', extensionName: 'anonymous' }, cause: Error: Something went wrong. - at (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:20:9) - at applyBody (/some/path/to/main.ts:310:28) + at (/some/path/to/output|output_envelope_envelope-error__envelope-error.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) } ] } diff --git a/website/content/examples/output-envelope.md b/website/content/examples/output-envelope.md index aa98d1ffa..6267d69df 100644 --- a/website/content/examples/output-envelope.md +++ b/website/content/examples/output-envelope.md @@ -20,7 +20,6 @@ const atlas = Atlas.create({ const result = await atlas.query.continents({ name: true }) console.log(result) -// ^? ``` @@ -48,7 +47,7 @@ console.log(result) headers: Headers { connection: 'keep-alive', 'content-length': '119', - 'x-served-by': 'cache-yul1970024-YUL', + 'x-served-by': 'cache-yul1970023-YUL', 'accept-ranges': 'bytes', date: 'Sun, 08 Sep 2024 18:13:26 GMT', 'content-type': 'application/graphql-response+json; charset=utf-8', @@ -61,13 +60,13 @@ console.log(result) 'alt-svc': 'h3=":443"; ma=86400', 'access-control-allow-origin': '*', 'x-powered-by': 'Stellate', - age: '175934', + age: '185021', 'cache-control': 'public, s-maxage=2628000, stale-while-revalidate=2628000', 'x-cache': 'HIT', - 'x-cache-hits': '32', + 'x-cache-hits': '38', 'gcdn-cache': 'HIT', - 'stellate-rate-limit-budget-remaining': '43', - 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=43;refill=59', + 'stellate-rate-limit-budget-remaining': '36', + 'stellate-rate-limit-rules': '"IP limit";type="RequestCount";budget=50;limited=?0;remaining=36;refill=6', 'stellate-rate-limit-decision': 'pass', 'stellate-rate-limit-budget-required': '5', 'content-encoding': 'br' diff --git a/website/content/examples/output-return-error-execution.md b/website/content/examples/output-return-error-execution.md new file mode 100644 index 000000000..ae2ccd582 --- /dev/null +++ b/website/content/examples/output-return-error-execution.md @@ -0,0 +1,112 @@ +--- +aside: false +--- + +# Return Error Execution + +This example shows how to configure output to have only certain kinds of errors returned while others thrown. + + +```ts twoslash +// ---cut--- +import { Pokemon } from './pokemon/__.js' + +const pokemon = Pokemon + .create({ + schema: `http://localhost:3000/graphql`, + output: { + envelope: false, + errors: { + execution: `return`, + other: `throw`, + }, + }, + }) + +// 1. The __execution__ error of an empty Pokemon name will be ***returned***. + +type _result = typeof result +const result = await pokemon.mutation.addPokemon({ + $: { name: ``, hp: 1, defense: 0, attack: 0 }, + name: true, +}) +console.log(result) + +// 2. The __other__ error, in this case from the inline extension, will be ***thrown***. + +try { + await pokemon + .use(({ encode: _ }) => { + throw new Error(`Something went wrong.`) + }) + .query + .pokemon({ name: true }) +} catch (error) { + console.log(error) +} +``` + + +#### Outputs + + +```txt +ContextualAggregateError: One or more errors in the execution result. + at handleOutput (/some/path/to/handleOutput.ts:XX:XX) + at run (/some/path/to/client.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_return-error_return-error-execution__return-error-execution.ts:XX:XX) { + context: {}, + cause: undefined, + errors: [ + GraphQLError: [ + { + "code": "too_small", + "minimum": 1, + "type": "string", + "inclusive": true, + "exact": false, + "message": "Pokemon name cannot be empty.", + "path": [ + "name" + ] + } + ] + at (/some/path/to/graphqlHTTP.ts:XX:XX) + at Array.map () + at parseExecutionResult (/some/path/to/graphqlHTTP.ts:XX:XX) + at Object.unpack (/some/path/to/core.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) + at async runHook (/some/path/to/runHook.ts:XX:XX) { + path: [ 'addPokemon' ], + locations: undefined, + extensions: [Object: null prototype] {} + } + ] +} +``` + + +```txt +ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_return-error_return-error-execution__return-error-execution.ts:XX:XX) { + context: { + hookName: 'encode', + source: 'extension', + extensionName: 'anonymous' + }, + cause: Error: Something went wrong. + at (/some/path/to/output|output_return-error_return-error-execution__return-error-execution.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) + at process.processTicksAndRejections (node:internal/process/task_queues:XX:XX) +} +``` + diff --git a/website/content/examples/output-return-error.md b/website/content/examples/output-return-error.md new file mode 100644 index 000000000..5943b9f2f --- /dev/null +++ b/website/content/examples/output-return-error.md @@ -0,0 +1,57 @@ +--- +aside: false +--- + +# Return Error + +This example shows how to configure output to have errors returned instead of e.g. thrown. + + +```ts twoslash +// ---cut--- +import { Graffle as Atlas } from './graffle/__.js' + +const atlas = Atlas + .create({ + output: { + envelope: false, + defaults: { + errorChannel: `return`, + }, + }, + }) + .use(({ encode: _ }) => { + throw new Error(`Something went wrong.`) +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + }) + +const result = await atlas.query.continents({ name: true }) +type _result = typeof result +// ^? + +console.log(result) +``` + + +#### Outputs + + +```txt +ContextualError: There was an error in the extension "anonymous" (use named functions to improve this error message) while running hook "encode". + at runPipeline (/some/path/to/runPipeline.ts:XX:XX) + at async Object.run (/some/path/to/main.ts:XX:XX) + at async run (/some/path/to/client.ts:XX:XX) + at async executeRootType (/some/path/to/client.ts:XX:XX) + at async executeRootTypeField (/some/path/to/client.ts:XX:XX) + at async (/some/path/to/output|output_return-error.ts:XX:XX) { + context: { + hookName: 'encode', + source: 'extension', + extensionName: 'anonymous' + }, + cause: Error: Something went wrong. + at (/some/path/to/output|output_return-error.ts:XX:XX) + at applyBody (/some/path/to/main.ts:XX:XX) +} +``` + diff --git a/website/content/examples/raw-raw-string-typed.md b/website/content/examples/raw-raw-string-typed.md index 86bb5649f..6c4cf9cc6 100644 --- a/website/content/examples/raw-raw-string-typed.md +++ b/website/content/examples/raw-raw-string-typed.md @@ -44,7 +44,6 @@ const result = await graffle.rawString({ }) console.log(result.data?.countries) -// ^? ``` diff --git a/website/content/examples/raw-raw-string.md b/website/content/examples/raw-raw-string.md index 39b0518c4..b5bc1cd2d 100644 --- a/website/content/examples/raw-raw-string.md +++ b/website/content/examples/raw-raw-string.md @@ -27,7 +27,6 @@ const result = await graffle.rawString({ }) console.log(result.data) -// ^? ``` diff --git a/website/content/examples/raw-raw.md b/website/content/examples/raw-raw.md index 9aaa2450f..51a5d1cc2 100644 --- a/website/content/examples/raw-raw.md +++ b/website/content/examples/raw-raw.md @@ -29,7 +29,6 @@ const result = await graffle.raw({ }) console.log(result.data) -// ^? ``` diff --git a/website/content/examples/transport-http-abort.md b/website/content/examples/transport-http-abort.md index 1d2c37a0d..3da481ba2 100644 --- a/website/content/examples/transport-http-abort.md +++ b/website/content/examples/transport-http-abort.md @@ -36,7 +36,6 @@ abortController.abort() const result = await resultPromise.catch((error: unknown) => (error as Error).message) console.log(result) -// ^? // todo .with(...) variant ``` diff --git a/website/content/examples/transport-http-custom-fetch.md b/website/content/examples/transport-http-custom-fetch.md index e1ff19a58..8301c86f7 100644 --- a/website/content/examples/transport-http-custom-fetch.md +++ b/website/content/examples/transport-http-custom-fetch.md @@ -28,7 +28,6 @@ const graffle = Graffle const countries = await graffle.rawString({ document: `{ countries { name } }` }) console.log(countries.data) -// ^? ``` diff --git a/website/content/examples/transport-http-dynamic-headers.md b/website/content/examples/transport-http-dynamic-headers.md index 6252f1c55..66136990b 100644 --- a/website/content/examples/transport-http-dynamic-headers.md +++ b/website/content/examples/transport-http-dynamic-headers.md @@ -43,7 +43,7 @@ await graffle.rawString({ document: `{ languages { code } }` }) headers: Headers { accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8', 'content-type': 'application/json', - 'x-sent-at-time': '1725995139086' + 'x-sent-at-time': '1726004226733' }, signal: undefined, method: 'post', diff --git a/website/content/guides/_example_links/output.md b/website/content/guides/_example_links/output.md index 3ea9def67..c14e046e0 100644 --- a/website/content/guides/_example_links/output.md +++ b/website/content/guides/_example_links/output.md @@ -1 +1 @@ -###### Examples -> [Default](../../examples/output-default.md) / [Envelope](../../examples/output-envelope.md) / [Envelope Error](../../examples/output-envelope-error.md) / [Envelope Error Throw](../../examples/output-envelope-error-throw.md) +###### Examples -> [Default](../../examples/output-default.md) / [Envelope](../../examples/output-envelope.md) / [Envelope Error](../../examples/output-envelope-error.md) / [Envelope Error Throw](../../examples/output-envelope-error-throw.md) / [Return Error](../../examples/output-return-error.md) / [Return Error Execution](../../examples/output-return-error-execution.md) diff --git a/website/content/guides/_example_links/output_return-error-execution.md b/website/content/guides/_example_links/output_return-error-execution.md new file mode 100644 index 000000000..0cec1fd3a --- /dev/null +++ b/website/content/guides/_example_links/output_return-error-execution.md @@ -0,0 +1 @@ +###### Examples -> [Return Error Execution](../../examples/output-return-error-execution.md) diff --git a/website/content/guides/_example_links/output_return-error.md b/website/content/guides/_example_links/output_return-error.md new file mode 100644 index 000000000..99300c64f --- /dev/null +++ b/website/content/guides/_example_links/output_return-error.md @@ -0,0 +1 @@ +###### Examples -> [Return Error](../../examples/output-return-error.md) / [Return Error Execution](../../examples/output-return-error-execution.md) diff --git a/website/content/guides/_example_links/output_return-error_return-error-execution.md b/website/content/guides/_example_links/output_return-error_return-error-execution.md new file mode 100644 index 000000000..0cec1fd3a --- /dev/null +++ b/website/content/guides/_example_links/output_return-error_return-error-execution.md @@ -0,0 +1 @@ +###### Examples -> [Return Error Execution](../../examples/output-return-error-execution.md) diff --git a/website/content/guides/_example_links/return-error-execution.md b/website/content/guides/_example_links/return-error-execution.md new file mode 100644 index 000000000..0cec1fd3a --- /dev/null +++ b/website/content/guides/_example_links/return-error-execution.md @@ -0,0 +1 @@ +###### Examples -> [Return Error Execution](../../examples/output-return-error-execution.md) diff --git a/website/content/guides/_example_links/return-error.md b/website/content/guides/_example_links/return-error.md new file mode 100644 index 000000000..99300c64f --- /dev/null +++ b/website/content/guides/_example_links/return-error.md @@ -0,0 +1 @@ +###### Examples -> [Return Error](../../examples/output-return-error.md) / [Return Error Execution](../../examples/output-return-error-execution.md) diff --git a/website/content/guides/_example_links/return-error_return-error-execution.md b/website/content/guides/_example_links/return-error_return-error-execution.md new file mode 100644 index 000000000..0cec1fd3a --- /dev/null +++ b/website/content/guides/_example_links/return-error_return-error-execution.md @@ -0,0 +1 @@ +###### Examples -> [Return Error Execution](../../examples/output-return-error-execution.md)