@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 +2 -2
- package/src/index.d.ts +72 -7
- package/src/index.js +72 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/async-context",
|
|
3
|
-
"version": "0.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.
|
|
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
|
|
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
|
-
*
|
|
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
|
|
101
|
+
export declare class AsyncSnapshot {
|
|
102
|
+
#private;
|
|
103
|
+
constructor();
|
|
73
104
|
/**
|
|
74
|
-
*
|
|
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
|
-
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
};
|