@b9g/async-context 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/async-context",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Lightweight AsyncContext polyfill for JavaScript runtimes. Implements TC39 AsyncContext proposal using AsyncLocalStorage.",
5
5
  "keywords": [
6
6
  "asynccontext",
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "dependencies": {},
18
18
  "devDependencies": {
19
- "@b9g/libuild": "^0.1.11",
19
+ "@b9g/libuild": "^0.1.18",
20
20
  "@types/node": "^20.0.0",
21
21
  "bun-types": "latest"
22
22
  },
package/src/index.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * This implementation uses Node.js AsyncLocalStorage under the hood
8
8
  * to provide async context propagation across promise chains and async callbacks.
9
9
  */
10
+ import { AsyncLocalStorage } from "node:async_hooks";
10
11
  /**
11
12
  * Options for creating an AsyncContext.Variable
12
13
  */
@@ -27,7 +28,7 @@ export interface AsyncVariableOptions<T> {
27
28
  *
28
29
  * @example
29
30
  * ```ts
30
- * const userContext = new AsyncVariable<User>();
31
+ * const userContext = new AsyncContext.Variable<User>();
31
32
  *
32
33
  * userContext.run(currentUser, async () => {
33
34
  * await someAsyncOperation();
@@ -44,9 +45,10 @@ export declare class AsyncVariable<T> {
44
45
  *
45
46
  * @param value - The context value to set
46
47
  * @param fn - The function to execute with the context
48
+ * @param args - Additional arguments to pass to fn
47
49
  * @returns The return value of fn
48
50
  */
49
- run<R>(value: T, fn: () => R): R;
51
+ run<R, Args extends unknown[]>(value: T, fn: (...args: Args) => R, ...args: Args): R;
50
52
  /**
51
53
  * Get the current context value
52
54
  * Returns the default value if no context is set
@@ -65,15 +67,78 @@ export declare class AsyncVariable<T> {
65
67
  * Get the name of this variable (for debugging)
66
68
  */
67
69
  get name(): string | undefined;
70
+ /**
71
+ * Internal: Get the underlying storage (used by Snapshot)
72
+ * @internal
73
+ */
74
+ _getStorage(): AsyncLocalStorage<T>;
68
75
  }
69
76
  /**
70
- * Namespace matching the TC39 AsyncContext proposal
77
+ * AsyncContext.Snapshot - captures the current values of all Variables
78
+ *
79
+ * A Snapshot captures the state of all AsyncContext.Variable instances at the
80
+ * time of construction. Later, calling `run()` restores that state for the
81
+ * duration of the callback.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const userVar = new AsyncContext.Variable<string>();
86
+ *
87
+ * userVar.run("alice", () => {
88
+ * const snapshot = new AsyncContext.Snapshot();
89
+ *
90
+ * // Later, in a different context...
91
+ * userVar.run("bob", () => {
92
+ * console.log(userVar.get()); // "bob"
93
+ *
94
+ * snapshot.run(() => {
95
+ * console.log(userVar.get()); // "alice"
96
+ * });
97
+ * });
98
+ * });
99
+ * ```
71
100
  */
72
- export declare namespace AsyncContext {
101
+ export declare class AsyncSnapshot {
102
+ #private;
103
+ constructor();
73
104
  /**
74
- * AsyncContext.Variable - stores a value that propagates through async operations
105
+ * Execute a function with the captured context values
106
+ *
107
+ * @param fn - The function to execute
108
+ * @param args - Additional arguments to pass to fn
109
+ * @returns The return value of fn
75
110
  */
76
- class Variable<T> extends AsyncVariable<T> {
77
- }
111
+ run<R, Args extends unknown[]>(fn: (...args: Args) => R, ...args: Args): R;
112
+ /**
113
+ * Wrap a function to capture the current context
114
+ *
115
+ * Creates a new function that, when called, will execute with the
116
+ * context values that were active when wrap() was called.
117
+ *
118
+ * @param fn - The function to wrap
119
+ * @returns A wrapped function that preserves context
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const userVar = new AsyncContext.Variable<string>();
124
+ *
125
+ * const wrappedFn = userVar.run("alice", () => {
126
+ * return AsyncContext.Snapshot.wrap(() => {
127
+ * return userVar.get();
128
+ * });
129
+ * });
130
+ *
131
+ * // Later, even outside the run() context:
132
+ * wrappedFn(); // returns "alice"
133
+ * ```
134
+ */
135
+ static wrap<T, A extends unknown[], R>(fn: (this: T, ...args: A) => R): (this: T, ...args: A) => R;
78
136
  }
137
+ /**
138
+ * AsyncContext object matching the TC39 AsyncContext proposal
139
+ */
140
+ export declare const AsyncContext: {
141
+ Variable: typeof AsyncVariable;
142
+ Snapshot: typeof AsyncSnapshot;
143
+ };
79
144
  export default AsyncContext;
package/src/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  /// <reference types="./index.d.ts" />
2
2
  // src/index.ts
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
+ var variableRegistry = /* @__PURE__ */ new Set();
5
+ var NO_VALUE = Symbol("NO_VALUE");
4
6
  var AsyncVariable = class {
5
7
  #storage;
6
8
  #defaultValue;
@@ -9,6 +11,7 @@ var AsyncVariable = class {
9
11
  this.#storage = new AsyncLocalStorage();
10
12
  this.#defaultValue = options?.defaultValue;
11
13
  this.#name = options?.name;
14
+ variableRegistry.add(this);
12
15
  }
13
16
  /**
14
17
  * Execute a function with a context value
@@ -16,10 +19,11 @@ var AsyncVariable = class {
16
19
  *
17
20
  * @param value - The context value to set
18
21
  * @param fn - The function to execute with the context
22
+ * @param args - Additional arguments to pass to fn
19
23
  * @returns The return value of fn
20
24
  */
21
- run(value, fn) {
22
- return this.#storage.run(value, fn);
25
+ run(value, fn, ...args) {
26
+ return this.#storage.run(value, fn, ...args);
23
27
  }
24
28
  /**
25
29
  * Get the current context value
@@ -46,16 +50,77 @@ var AsyncVariable = class {
46
50
  get name() {
47
51
  return this.#name;
48
52
  }
53
+ /**
54
+ * Internal: Get the underlying storage (used by Snapshot)
55
+ * @internal
56
+ */
57
+ _getStorage() {
58
+ return this.#storage;
59
+ }
49
60
  };
50
- var AsyncContext;
51
- ((AsyncContext2) => {
52
- class Variable extends AsyncVariable {
61
+ var AsyncSnapshot = class _AsyncSnapshot {
62
+ #captured;
63
+ constructor() {
64
+ this.#captured = /* @__PURE__ */ new Map();
65
+ for (const variable of variableRegistry) {
66
+ const value = variable.getStore();
67
+ this.#captured.set(variable, value !== void 0 ? value : NO_VALUE);
68
+ }
69
+ }
70
+ /**
71
+ * Execute a function with the captured context values
72
+ *
73
+ * @param fn - The function to execute
74
+ * @param args - Additional arguments to pass to fn
75
+ * @returns The return value of fn
76
+ */
77
+ run(fn, ...args) {
78
+ let result = () => fn(...args);
79
+ for (const [variable, value] of this.#captured) {
80
+ const prev = result;
81
+ const actualValue = value === NO_VALUE ? void 0 : value;
82
+ result = () => variable._getStorage().run(actualValue, prev);
83
+ }
84
+ return result();
85
+ }
86
+ /**
87
+ * Wrap a function to capture the current context
88
+ *
89
+ * Creates a new function that, when called, will execute with the
90
+ * context values that were active when wrap() was called.
91
+ *
92
+ * @param fn - The function to wrap
93
+ * @returns A wrapped function that preserves context
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * const userVar = new AsyncContext.Variable<string>();
98
+ *
99
+ * const wrappedFn = userVar.run("alice", () => {
100
+ * return AsyncContext.Snapshot.wrap(() => {
101
+ * return userVar.get();
102
+ * });
103
+ * });
104
+ *
105
+ * // Later, even outside the run() context:
106
+ * wrappedFn(); // returns "alice"
107
+ * ```
108
+ */
109
+ static wrap(fn) {
110
+ const snapshot = new _AsyncSnapshot();
111
+ return function(...args) {
112
+ return snapshot.run(() => fn.apply(this, args));
113
+ };
53
114
  }
54
- AsyncContext2.Variable = Variable;
55
- })(AsyncContext || (AsyncContext = {}));
115
+ };
116
+ var AsyncContext = {
117
+ Variable: AsyncVariable,
118
+ Snapshot: AsyncSnapshot
119
+ };
56
120
  var src_default = AsyncContext;
57
121
  export {
58
122
  AsyncContext,
123
+ AsyncSnapshot,
59
124
  AsyncVariable,
60
125
  src_default as default
61
126
  };