Skip to content

Commit

Permalink
Allow passing error when transforming outputs in middleware (#684)
Browse files Browse the repository at this point in the history
## Summary
<!-- Succinctly describe your change, providing context, what you've
changed, and why. -->

Allows passing an `error` when using `onFunctionRun.transformOutput` in
middleware.

Runtime code already supports this, allowing for common actions such as
recreating (or creating new) errors based on data returned from
executions. For example:

```ts
new InngestMiddleware({
  name: "My Middleware",
  init() {
    return {
      onFunctionRun({ fn }) {
        return {
          transformOutput({ result }) {
            if (result.error && result.error instanceof ForbiddenError) {
              return {
                result: {
                  error: new NonRetriableError(result.error.message),
                },
              };
            }
          },
        };
      },
    };
  },
});
```

## Checklist
<!-- Tick these items off as you progress. -->
<!-- If an item isn't applicable, ideally please strikeout the item by
wrapping it in "~~"" and suffix it with "N/A My reason for skipping
this." -->
<!-- e.g. "- [ ] ~~Added tests~~ N/A Only touches docs" -->

- [ ] ~Added a [docs PR](https://github.com/inngest/website) that
references this PR~ N/A Bug fix
- [x] Added unit/integration tests
- [x] Added changesets if applicable
  • Loading branch information
jpwilliams authored Aug 21, 2024
1 parent 2019fe2 commit ae7ea5c
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/smart-years-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"inngest": patch
---

Allow passing `error` when transforming outputs in middleware
125 changes: 125 additions & 0 deletions packages/inngest/src/components/InngestMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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", () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/inngest/src/components/InngestMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,9 @@ type MiddlewareSendEventOutput = (
type MiddlewareRunOutput = (ctx: {
result: Readonly<Pick<OutgoingOp, "error" | "data">>;
step?: Readonly<Omit<OutgoingOp, "id">>;
}) => MaybePromise<{ result?: Partial<Pick<OutgoingOp, "data">> } | void>;
}) => MaybePromise<{
result?: Partial<Pick<OutgoingOp, "data" | "error">>;
} | void>;

type MiddlewareRunFinished = (ctx: {
result: Readonly<Pick<OutgoingOp, "error" | "data">>;
Expand Down

0 comments on commit ae7ea5c

Please sign in to comment.