diff --git a/.changeset/smart-years-cry.md b/.changeset/smart-years-cry.md new file mode 100644 index 00000000..6f2ad525 --- /dev/null +++ b/.changeset/smart-years-cry.md @@ -0,0 +1,5 @@ +--- +"inngest": patch +--- + +Allow passing `error` when transforming outputs in middleware diff --git a/packages/inngest/src/components/InngestMiddleware.test.ts b/packages/inngest/src/components/InngestMiddleware.test.ts index ed906893..6f0d0017 100644 --- a/packages/inngest/src/components/InngestMiddleware.test.ts +++ b/packages/inngest/src/components/InngestMiddleware.test.ts @@ -3,6 +3,7 @@ import { Inngest } from "@local/components/Inngest"; import { referenceFunction } from "@local/components/InngestFunctionReference"; import { InngestMiddleware } from "@local/components/InngestMiddleware"; +import { NonRetriableError } from "@local/components/NonRetriableError"; import { ExecutionVersion } from "@local/components/execution/InngestExecution"; import { type IsEqual, type IsUnknown } from "@local/helpers/types"; import { StepOpCode } from "@local/types"; @@ -440,6 +441,130 @@ describe("stacking and inference", () => { }); }); }); + + describe("transformOutput", () => { + test("can see an error in output context", async () => { + let error: Error | undefined; + + const fn = new Inngest({ + id: "test", + middleware: [ + new InngestMiddleware({ + name: "mw", + init() { + return { + onFunctionRun() { + return { + transformOutput({ result }) { + error = result.error as Error; + }, + }; + }, + }; + }, + }), + ], + }).createFunction({ id: "" }, { event: "" }, ({ step }) => { + throw new Error("test error"); + }); + + await runFnWithStack(fn, {}, { executionVersion: ExecutionVersion.V1 }); + + expect(error).toBeInstanceOf(Error); + }); + + test("can overwrite an existing error in output context", async () => { + const fn = new Inngest({ + id: "test", + middleware: [ + new InngestMiddleware({ + name: "mw1", + init() { + return { + onFunctionRun() { + return { + transformOutput() { + return { + result: { error: new Error("foo") }, + }; + }, + }; + }, + }; + }, + }), + new InngestMiddleware({ + name: "mw2", + init() { + return { + onFunctionRun() { + return { + transformOutput() { + return { + result: { error: new Error("bar") }, + }; + }, + }; + }, + }; + }, + }), + ], + }).createFunction({ id: "" }, { event: "" }, () => { + throw new Error("test error"); + }); + + const res = await runFnWithStack( + fn, + {}, + { executionVersion: ExecutionVersion.V1 } + ); + + expect(res).toMatchObject({ + type: "function-rejected", + error: { message: "bar" }, + retriable: true, + }); + }); + + test("can set a NonRetriableError", async () => { + const fn = new Inngest({ + id: "test", + middleware: [ + new InngestMiddleware({ + name: "mw1", + init() { + return { + onFunctionRun() { + return { + transformOutput() { + return { + result: { error: new NonRetriableError("foo") }, + }; + }, + }; + }, + }; + }, + }), + ], + }).createFunction({ id: "" }, { event: "" }, () => { + throw new Error("test error"); + }); + + const res = await runFnWithStack( + fn, + {}, + { executionVersion: ExecutionVersion.V1 } + ); + + expect(res).toMatchObject({ + type: "function-rejected", + error: { message: "foo" }, + retriable: false, + }); + }); + }); }); describe("onSendEvent", () => { diff --git a/packages/inngest/src/components/InngestMiddleware.ts b/packages/inngest/src/components/InngestMiddleware.ts index 927712d8..9fe494e5 100644 --- a/packages/inngest/src/components/InngestMiddleware.ts +++ b/packages/inngest/src/components/InngestMiddleware.ts @@ -499,7 +499,9 @@ type MiddlewareSendEventOutput = ( type MiddlewareRunOutput = (ctx: { result: Readonly>; step?: Readonly>; -}) => MaybePromise<{ result?: Partial> } | void>; +}) => MaybePromise<{ + result?: Partial>; +} | void>; type MiddlewareRunFinished = (ctx: { result: Readonly>;