-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary <!-- Succinctly describe your change, providing context, what you've changed, and why. --> Adds a new `getAsyncCtx()` function exported from `"inngest/experimental"` that will attempt to retrieve the function input arguments. - Uses `@inngest/test@workspace:^` internally, exclusively for the `inngest` package. This can be a bit weird, as `inngest` is a peer dep of `@inngest/test` so typing can be flaky. - Altered exports of `@inngest/test` to have simpler compilation and publishing. - Added the experimental `getAsyncCtx()` ```ts import { getAsyncCtx } from "inngest/experimental"; const ctx = await getAsyncCtx(); ``` ### Questions - [x] Should we remove the store for a particular execution context when the run completes? No. It will still be garbage collected and info may still be useful. ## 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 Experimental for now - [x] Added unit/integration tests - [x] Added changesets if applicable
- Loading branch information
1 parent
3ca3403
commit 0dbcc87
Showing
14 changed files
with
300 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"inngest": patch | ||
--- | ||
|
||
Use `@inngest/test@workspace:^` internally for testing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@inngest/test": patch | ||
--- | ||
|
||
Altered exports to now be namespaced by `./dist/`; if you have directly imported files from `@inngest/test`, you may need to change the imports |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
"inngest": minor | ||
--- | ||
|
||
Add experimental `getAsyncCtx()`, allowing the retrieval of a run's input (`event`, `step`, `runId`, etc) from the relevant async chain. | ||
|
||
```ts | ||
import { getAsyncCtx } from "inngest/experimental"; | ||
|
||
const ctx = await getAsyncCtx(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { InngestTestEngine } from "@inngest/test"; | ||
import { type AsyncContext } from "@local/components/execution/als"; | ||
|
||
describe("getAsyncLocalStorage", () => { | ||
const warningSpy = jest.spyOn(console, "warn"); | ||
|
||
afterEach(() => { | ||
jest.unmock("node:async_hooks"); | ||
jest.resetModules(); | ||
}); | ||
|
||
test("should return an `AsyncLocalStorageIsh`", async () => { | ||
const mod = await import("@local/components/execution/als"); | ||
const als = await mod.getAsyncLocalStorage(); | ||
|
||
expect(als).toBeDefined(); | ||
expect(als.getStore).toBeDefined(); | ||
expect(als.run).toBeDefined(); | ||
}); | ||
|
||
test("should return the same instance of `AsyncLocalStorageIsh`", async () => { | ||
const mod = await import("@local/components/execution/als"); | ||
|
||
const als1p = mod.getAsyncLocalStorage(); | ||
const als2p = mod.getAsyncLocalStorage(); | ||
|
||
const als1 = await als1p; | ||
const als2 = await als2p; | ||
|
||
expect(als1).toBe(als2); | ||
}); | ||
|
||
test("should return `undefined` if node:async_hooks is not supported", async () => { | ||
jest.mock("node:async_hooks", () => { | ||
throw new Error("import failed"); | ||
}); | ||
|
||
const mod = await import("@local/components/execution/als"); | ||
const als = await mod.getAsyncLocalStorage(); | ||
|
||
expect(warningSpy).toHaveBeenCalledWith( | ||
expect.stringContaining( | ||
"node:async_hooks is not supported in this runtime" | ||
) | ||
); | ||
|
||
expect(als).toBeDefined(); | ||
expect(als.getStore()).toBeUndefined(); | ||
expect(als.run).toBeDefined(); | ||
}); | ||
}); | ||
|
||
describe("getAsyncCtx", () => { | ||
const wait = async () => { | ||
await new Promise((resolve) => setTimeout(resolve)); | ||
await new Promise((resolve) => process.nextTick(resolve)); | ||
}; | ||
|
||
afterEach(() => { | ||
jest.unmock("node:async_hooks"); | ||
jest.resetModules(); | ||
}); | ||
|
||
test("should return `undefined` outside of an Inngest async context", async () => { | ||
const mod = await import("@local/components/execution/als"); | ||
const store = await mod.getAsyncCtx(); | ||
|
||
expect(store).toBeUndefined(); | ||
}); | ||
|
||
test("should return the input context during execution", async () => { | ||
const { Inngest } = await import("@local"); | ||
const mod = await import("@local/experimental"); | ||
|
||
const inngest = new Inngest({ id: "test" }); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
let resolve: (value: any) => void | PromiseLike<void>; | ||
const externalP = new Promise<AsyncContext | undefined>((r) => { | ||
resolve = r; | ||
}); | ||
|
||
let internalRunId: string | undefined; | ||
|
||
const fn = inngest.createFunction( | ||
{ id: "test" }, | ||
{ event: "" }, | ||
({ runId }) => { | ||
internalRunId = runId; | ||
|
||
void wait() | ||
.then(() => mod.getAsyncCtx()) | ||
.then(resolve); | ||
|
||
return "done"; | ||
} | ||
); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any | ||
const t = new InngestTestEngine({ function: fn as any }); | ||
|
||
const { result } = await t.execute(); | ||
|
||
expect(result).toBe("done"); | ||
expect(internalRunId).toBeTruthy(); | ||
|
||
const store = await externalP; | ||
expect(store).toBeDefined(); | ||
expect(store?.ctx.runId).toBe(internalRunId); | ||
}); | ||
|
||
test("should return `undefined` if node:async_hooks is not supported", async () => { | ||
jest.mock("node:async_hooks", () => { | ||
throw new Error("import failed"); | ||
}); | ||
|
||
const { Inngest } = await import("@local"); | ||
const mod = await import("@local/experimental"); | ||
|
||
const inngest = new Inngest({ id: "test" }); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
let resolve: (value: any) => void | PromiseLike<void>; | ||
const externalP = new Promise<AsyncContext | undefined>((r) => { | ||
resolve = r; | ||
}); | ||
|
||
let internalRunId: string | undefined; | ||
|
||
const fn = inngest.createFunction( | ||
{ id: "test" }, | ||
{ event: "" }, | ||
({ runId }) => { | ||
internalRunId = runId; | ||
|
||
void wait() | ||
.then(() => mod.getAsyncCtx()) | ||
.then(resolve); | ||
|
||
return "done"; | ||
} | ||
); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any | ||
const t = new InngestTestEngine({ function: fn as any }); | ||
|
||
const { result } = await t.execute(); | ||
|
||
expect(result).toBe("done"); | ||
expect(internalRunId).toBeTruthy(); | ||
|
||
const store = await externalP; | ||
expect(store).toBeUndefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { type Context } from "../../types.js"; | ||
|
||
export interface AsyncContext { | ||
ctx: Context.Any; | ||
} | ||
|
||
/** | ||
* A type that represents a partial, runtime-agnostic interface of | ||
* `AsyncLocalStorage`. | ||
*/ | ||
type AsyncLocalStorageIsh = { | ||
getStore: () => AsyncContext | undefined; | ||
run: <R>(store: AsyncContext, fn: () => R) => R; | ||
}; | ||
|
||
/** | ||
* A local-only variable to store the async local storage instance. | ||
*/ | ||
let als: Promise<AsyncLocalStorageIsh> | undefined; | ||
|
||
/** | ||
* Retrieve the async context for the current execution. | ||
*/ | ||
export const getAsyncCtx = async (): Promise<AsyncContext | undefined> => { | ||
return getAsyncLocalStorage().then((als) => als.getStore()); | ||
}; | ||
|
||
/** | ||
* Get a singleton instance of `AsyncLocalStorage` used to store and retrieve | ||
* async context for the current execution. | ||
*/ | ||
export const getAsyncLocalStorage = async (): Promise<AsyncLocalStorageIsh> => { | ||
// eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor | ||
als ??= new Promise<AsyncLocalStorageIsh>(async (resolve) => { | ||
try { | ||
const { AsyncLocalStorage } = await import("node:async_hooks"); | ||
|
||
resolve(new AsyncLocalStorage<AsyncContext>()); | ||
} catch (err) { | ||
console.warn( | ||
"node:async_hooks is not supported in this runtime. Experimental async context is disabled." | ||
); | ||
|
||
resolve({ | ||
getStore: () => undefined, | ||
run: (_, fn) => fn(), | ||
}); | ||
} | ||
}); | ||
|
||
return als; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { getAsyncCtx } from "./components/execution/als.js"; | ||
export type { AsyncContext } from "./components/execution/als.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.