Skip to content

Commit 7a5f4a5

Browse files
authored
Set inngest's ALS in global state to access it across versions (#819)
## Summary <!-- Succinctly describe your change, providing context, what you've changed, and why. --> Moves `inngest`'s [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html) usage to global state, ensuring we can access `inngest` context while using multiple versions and across boundaries. ## 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 KTLO - [ ] ~Added unit/integration tests~ N/A Included; multi-process tests not included - testing with `@inngest/agent-kit` - [x] Added changesets if applicable
1 parent f42ab05 commit 7a5f4a5

File tree

3 files changed

+45
-24
lines changed

3 files changed

+45
-24
lines changed

Diff for: .changeset/shiny-pigs-smoke.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"inngest": patch
3+
---
4+
5+
Set `inngest`'s ALS in global state to be able access it across versions and package boundaries

Diff for: packages/inngest/src/components/execution/als.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ describe("getAsyncLocalStorage", () => {
77
afterEach(() => {
88
jest.unmock("node:async_hooks");
99
jest.resetModules();
10+
11+
// kill the global used for storing ALS state
12+
delete (globalThis as Record<string | symbol | number, unknown>)[
13+
Symbol.for("inngest:als")
14+
];
1015
});
1116

1217
test("should return an `AsyncLocalStorageIsh`", async () => {
@@ -59,6 +64,11 @@ describe("getAsyncCtx", () => {
5964
afterEach(() => {
6065
jest.unmock("node:async_hooks");
6166
jest.resetModules();
67+
68+
// kill the global used for storing ALS state
69+
delete (globalThis as Record<string | symbol | number, unknown>)[
70+
Symbol.for("inngest:als")
71+
];
6272
});
6373

6474
test("should return `undefined` outside of an Inngest async context", async () => {

Diff for: packages/inngest/src/components/execution/als.ts

+30-24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ export interface AsyncContext {
44
ctx: Context.Any;
55
}
66

7+
/**
8+
* A local-only symbol used as a key in global state to store the async local
9+
* storage instance.
10+
*/
11+
const alsSymbol = Symbol.for("inngest:als");
12+
713
/**
814
* A type that represents a partial, runtime-agnostic interface of
915
* `AsyncLocalStorage`.
@@ -13,11 +19,6 @@ type AsyncLocalStorageIsh = {
1319
run: <R>(store: AsyncContext, fn: () => R) => R;
1420
};
1521

16-
/**
17-
* A local-only variable to store the async local storage instance.
18-
*/
19-
let als: Promise<AsyncLocalStorageIsh> | undefined;
20-
2122
/**
2223
* Retrieve the async context for the current execution.
2324
*/
@@ -30,23 +31,28 @@ export const getAsyncCtx = async (): Promise<AsyncContext | undefined> => {
3031
* async context for the current execution.
3132
*/
3233
export const getAsyncLocalStorage = async (): Promise<AsyncLocalStorageIsh> => {
33-
// eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor
34-
als ??= new Promise<AsyncLocalStorageIsh>(async (resolve) => {
35-
try {
36-
const { AsyncLocalStorage } = await import("node:async_hooks");
37-
38-
resolve(new AsyncLocalStorage<AsyncContext>());
39-
} catch (err) {
40-
console.warn(
41-
"node:async_hooks is not supported in this runtime. Experimental async context is disabled."
42-
);
43-
44-
resolve({
45-
getStore: () => undefined,
46-
run: (_, fn) => fn(),
47-
});
48-
}
49-
});
50-
51-
return als;
34+
(globalThis as Record<string | symbol | number, unknown>)[alsSymbol] ??=
35+
new Promise<AsyncLocalStorageIsh>(
36+
// eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor
37+
async (resolve) => {
38+
try {
39+
const { AsyncLocalStorage } = await import("node:async_hooks");
40+
41+
resolve(new AsyncLocalStorage<AsyncContext>());
42+
} catch (err) {
43+
console.warn(
44+
"node:async_hooks is not supported in this runtime. Experimental async context is disabled."
45+
);
46+
47+
resolve({
48+
getStore: () => undefined,
49+
run: (_, fn) => fn(),
50+
});
51+
}
52+
}
53+
);
54+
55+
return (globalThis as Record<string | symbol | number, unknown>)[
56+
alsSymbol
57+
] as Promise<AsyncLocalStorageIsh>;
5258
};

0 commit comments

Comments
 (0)