@fluojs/http 1.1.0 → 1.1.1
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/README.ko.md +1 -1
- package/README.md +1 -1
- package/dist/context/request-context-node-store.d.ts +14 -0
- package/dist/context/request-context-node-store.d.ts.map +1 -1
- package/dist/context/request-context-node-store.js +38 -6
- package/dist/context/request-context.d.ts.map +1 -1
- package/dist/context/request-context.js +83 -6
- package/package.json +3 -3
package/README.ko.md
CHANGED
|
@@ -96,7 +96,7 @@ function someDeepHelper() {
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
`runWithRequestContext(...)`는 호스트가 `globalThis.AsyncLocalStorage` 또는 Node 내장 `node:async_hooks` 모듈로 `AsyncLocalStorage`를 제공할 때 활성 컨텍스트를 `await` 이후까지 보존합니다.
|
|
99
|
+
`runWithRequestContext(...)`는 호스트가 `globalThis.AsyncLocalStorage` 또는 Node 내장 `node:async_hooks` 모듈로 `AsyncLocalStorage`를 제공할 때 활성 컨텍스트를 `await` 이후까지 보존합니다. 루트 `@fluojs/http` import는 async-context storage를 probe하거나 instantiate하지 않습니다. Helper가 처음 사용될 때 storage를 lazy하게 해석하고, `process.getBuiltinModule(...)` 실패를 guard하며, 동기 probe를 노출하지 않는 Node host에서는 계속 `node:async_hooks`를 동적으로 해석할 수 있습니다. 비동기 컨텍스트 primitive가 없는 비 Node 호스트에서는 동기 stack fallback을 사용하며, 겹치는 비동기 요청이 서로의 컨텍스트를 관찰하지 않도록 awaited continuation이 재개되기 전에 컨텍스트를 비웁니다.
|
|
100
100
|
|
|
101
101
|
### 프록시 뒤의 속도 제한
|
|
102
102
|
|
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ function someDeepHelper() {
|
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
`runWithRequestContext(...)` preserves the active context across awaited work when the host provides `AsyncLocalStorage` through `globalThis.AsyncLocalStorage` or Node's built-in `node:async_hooks` module.
|
|
101
|
+
`runWithRequestContext(...)` preserves the active context across awaited work when the host provides `AsyncLocalStorage` through `globalThis.AsyncLocalStorage` or Node's built-in `node:async_hooks` module. The root `@fluojs/http` import does not probe or instantiate async-context storage; helpers resolve storage lazily on first use, guard `process.getBuiltinModule(...)` failures, and can still resolve `node:async_hooks` dynamically for Node hosts that do not expose the synchronous probe. Non-Node hosts without an async-context primitive use a synchronous stack fallback that clears the context before awaited continuations resume, avoiding cross-request leaks instead of pretending to isolate overlapping async work.
|
|
102
102
|
|
|
103
103
|
### Rate limiting behind proxies
|
|
104
104
|
|
|
@@ -13,6 +13,13 @@ type AsyncLocalStorageResolutionHost = {
|
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
15
|
type NodeAsyncHooksLoader = () => Promise<NodeAsyncHooksModule>;
|
|
16
|
+
/**
|
|
17
|
+
* Resolves host-provided `AsyncLocalStorage` without async imports or throwing host probes.
|
|
18
|
+
*
|
|
19
|
+
* @param host Host global-like object to inspect for synchronous async-context support.
|
|
20
|
+
* @returns The resolved `AsyncLocalStorage` constructor, or `undefined` when unavailable.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveImmediateAsyncLocalStorageConstructor(host?: AsyncLocalStorageResolutionHost): AsyncLocalStorageConstructor | undefined;
|
|
16
23
|
/**
|
|
17
24
|
* Resolves the host `AsyncLocalStorage` constructor without eagerly importing Node built-ins.
|
|
18
25
|
*
|
|
@@ -21,5 +28,12 @@ type NodeAsyncHooksLoader = () => Promise<NodeAsyncHooksModule>;
|
|
|
21
28
|
* @returns The resolved `AsyncLocalStorage` constructor, or `undefined` when unavailable.
|
|
22
29
|
*/
|
|
23
30
|
export declare function resolveAsyncLocalStorageConstructor(host?: AsyncLocalStorageResolutionHost, loadNodeAsyncHooks?: NodeAsyncHooksLoader): Promise<AsyncLocalStorageConstructor | undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* Reports whether the host can still resolve Node async-context storage asynchronously.
|
|
33
|
+
*
|
|
34
|
+
* @param host Host global-like object to inspect for Node runtime markers.
|
|
35
|
+
* @returns `true` when the host is Node.js and can use a lazy `node:async_hooks` import.
|
|
36
|
+
*/
|
|
37
|
+
export declare function canResolveAsyncLocalStorageDynamically(host?: AsyncLocalStorageResolutionHost): boolean;
|
|
24
38
|
export {};
|
|
25
39
|
//# sourceMappingURL=request-context-node-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context-node-store.d.ts","sourceRoot":"","sources":["../../src/context/request-context-node-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,KAAK,4BAA4B,GAAG,UAAU,mBAAmB,CAAC;AAElE,KAAK,oBAAoB,GAAG;IAC1B,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;CAClD,CAAC;AAEF,KAAK,+BAA+B,GAAG;IACrC,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,CAAC,EAAE,EAAE,kBAAkB,GAAG,oBAAoB,CAAC;QAChE,QAAQ,CAAC,EAAE;YACT,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;CACH,CAAC;AAEF,KAAK,oBAAoB,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEhE;;;;;;GAMG;AACH,wBAAsB,mCAAmC,CACvD,IAAI,GAAE,+BAA4C,EAClD,kBAAkB,GAAE,oBAA2C,GAC9D,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"request-context-node-store.d.ts","sourceRoot":"","sources":["../../src/context/request-context-node-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,KAAK,4BAA4B,GAAG,UAAU,mBAAmB,CAAC;AAElE,KAAK,oBAAoB,GAAG;IAC1B,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;CAClD,CAAC;AAEF,KAAK,+BAA+B,GAAG;IACrC,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,CAAC,EAAE,EAAE,kBAAkB,GAAG,oBAAoB,CAAC;QAChE,QAAQ,CAAC,EAAE;YACT,IAAI,CAAC,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;CACH,CAAC;AAEF,KAAK,oBAAoB,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,wBAAgB,4CAA4C,CAC1D,IAAI,GAAE,+BAA4C,GACjD,4BAA4B,GAAG,SAAS,CAsB1C;AAED;;;;;;GAMG;AACH,wBAAsB,mCAAmC,CACvD,IAAI,GAAE,+BAA4C,EAClD,kBAAkB,GAAE,oBAA2C,GAC9D,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC,CAkBnD;AAED;;;;;GAKG;AACH,wBAAgB,sCAAsC,CACpD,IAAI,GAAE,+BAA4C,GACjD,OAAO,CAET"}
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves host-provided `AsyncLocalStorage` without async imports or throwing host probes.
|
|
3
|
+
*
|
|
4
|
+
* @param host Host global-like object to inspect for synchronous async-context support.
|
|
5
|
+
* @returns The resolved `AsyncLocalStorage` constructor, or `undefined` when unavailable.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveImmediateAsyncLocalStorageConstructor(host = globalThis) {
|
|
8
|
+
if (typeof host.AsyncLocalStorage === 'function') {
|
|
9
|
+
return host.AsyncLocalStorage;
|
|
10
|
+
}
|
|
11
|
+
const getBuiltinModule = host.process?.getBuiltinModule;
|
|
12
|
+
if (typeof getBuiltinModule !== 'function') {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const builtinAsyncLocalStorage = getBuiltinModule('node:async_hooks')?.AsyncLocalStorage;
|
|
17
|
+
if (typeof builtinAsyncLocalStorage === 'function') {
|
|
18
|
+
return builtinAsyncLocalStorage;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
1
26
|
/**
|
|
2
27
|
* Resolves the host `AsyncLocalStorage` constructor without eagerly importing Node built-ins.
|
|
3
28
|
*
|
|
@@ -6,12 +31,9 @@
|
|
|
6
31
|
* @returns The resolved `AsyncLocalStorage` constructor, or `undefined` when unavailable.
|
|
7
32
|
*/
|
|
8
33
|
export async function resolveAsyncLocalStorageConstructor(host = globalThis, loadNodeAsyncHooks = importNodeAsyncHooks) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const builtinAsyncLocalStorage = host.process?.getBuiltinModule?.('node:async_hooks').AsyncLocalStorage;
|
|
13
|
-
if (typeof builtinAsyncLocalStorage === 'function') {
|
|
14
|
-
return builtinAsyncLocalStorage;
|
|
34
|
+
const immediateAsyncLocalStorage = resolveImmediateAsyncLocalStorageConstructor(host);
|
|
35
|
+
if (typeof immediateAsyncLocalStorage === 'function') {
|
|
36
|
+
return immediateAsyncLocalStorage;
|
|
15
37
|
}
|
|
16
38
|
if (!isNodeHost(host)) {
|
|
17
39
|
return undefined;
|
|
@@ -23,6 +45,16 @@ export async function resolveAsyncLocalStorageConstructor(host = globalThis, loa
|
|
|
23
45
|
return undefined;
|
|
24
46
|
}
|
|
25
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reports whether the host can still resolve Node async-context storage asynchronously.
|
|
51
|
+
*
|
|
52
|
+
* @param host Host global-like object to inspect for Node runtime markers.
|
|
53
|
+
* @returns `true` when the host is Node.js and can use a lazy `node:async_hooks` import.
|
|
54
|
+
*/
|
|
55
|
+
export function canResolveAsyncLocalStorageDynamically(host = globalThis) {
|
|
56
|
+
return isNodeHost(host);
|
|
57
|
+
}
|
|
26
58
|
function isNodeHost(host) {
|
|
27
59
|
return typeof host.process?.versions?.node === 'string';
|
|
28
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/context/request-context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../src/context/request-context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAc9D;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAgBtF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,cAAc,GAAG,SAAS,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAUrD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAK5E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAKtE;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,CAE7F;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAE9F"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { FluoError } from '@fluojs/core';
|
|
2
|
-
import { resolveAsyncLocalStorageConstructor } from './request-context-node-store.js';
|
|
2
|
+
import { canResolveAsyncLocalStorageDynamically, resolveAsyncLocalStorageConstructor, resolveImmediateAsyncLocalStorageConstructor } from './request-context-node-store.js';
|
|
3
3
|
import { createStackRequestContextStore } from './request-context-stack-store.js';
|
|
4
|
-
|
|
4
|
+
let requestContextStore;
|
|
5
|
+
let requestContextStoreResolution;
|
|
6
|
+
let fallbackRequestContextStore;
|
|
7
|
+
const dynamicResolutionFallbackStack = [];
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Runs a callback inside the request-scoped async context.
|
|
@@ -15,7 +18,17 @@ const requestContextStore = await createRequestContextStore();
|
|
|
15
18
|
* @returns The return value from `callback`.
|
|
16
19
|
*/
|
|
17
20
|
export function runWithRequestContext(context, callback) {
|
|
18
|
-
|
|
21
|
+
const store = getResolvedRequestContextStore();
|
|
22
|
+
if (store) {
|
|
23
|
+
return store.run(context, callback);
|
|
24
|
+
}
|
|
25
|
+
if (!canResolveAsyncLocalStorageDynamically()) {
|
|
26
|
+
return getFallbackRequestContextStore().run(context, callback);
|
|
27
|
+
}
|
|
28
|
+
if (!isAsyncCallback(callback)) {
|
|
29
|
+
return runWithDynamicResolutionFallbackContext(context, callback);
|
|
30
|
+
}
|
|
31
|
+
return runWithResolvedRequestContextStore(context, callback);
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
/**
|
|
@@ -24,7 +37,7 @@ export function runWithRequestContext(context, callback) {
|
|
|
24
37
|
* @returns The active request context, or `undefined` when no request scope is bound.
|
|
25
38
|
*/
|
|
26
39
|
export function getCurrentRequestContext() {
|
|
27
|
-
return
|
|
40
|
+
return getRequestContextStore().getStore() ?? getDynamicResolutionFallbackContext();
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
/**
|
|
@@ -92,10 +105,74 @@ export function getContextValue(context, key) {
|
|
|
92
105
|
export function setContextValue(context, key, value) {
|
|
93
106
|
context.metadata[key.id] = value;
|
|
94
107
|
}
|
|
108
|
+
function getRequestContextStore() {
|
|
109
|
+
return getResolvedRequestContextStore() ?? getFallbackRequestContextStore();
|
|
110
|
+
}
|
|
111
|
+
function getResolvedRequestContextStore() {
|
|
112
|
+
if (requestContextStore) {
|
|
113
|
+
return requestContextStore;
|
|
114
|
+
}
|
|
115
|
+
const AsyncLocalStorage = resolveImmediateAsyncLocalStorageConstructor();
|
|
116
|
+
if (typeof AsyncLocalStorage === 'function') {
|
|
117
|
+
requestContextStore = new AsyncLocalStorage();
|
|
118
|
+
return requestContextStore;
|
|
119
|
+
}
|
|
120
|
+
void resolveRequestContextStore();
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
async function runWithResolvedRequestContextStore(context, callback) {
|
|
124
|
+
const store = await resolveRequestContextStore();
|
|
125
|
+
return store.run(context, callback);
|
|
126
|
+
}
|
|
127
|
+
async function resolveRequestContextStore() {
|
|
128
|
+
requestContextStoreResolution ??= createRequestContextStore();
|
|
129
|
+
return requestContextStoreResolution;
|
|
130
|
+
}
|
|
95
131
|
async function createRequestContextStore() {
|
|
96
132
|
const AsyncLocalStorage = await resolveAsyncLocalStorageConstructor();
|
|
97
133
|
if (typeof AsyncLocalStorage === 'function') {
|
|
98
|
-
|
|
134
|
+
requestContextStore = new AsyncLocalStorage();
|
|
135
|
+
return requestContextStore;
|
|
99
136
|
}
|
|
100
|
-
|
|
137
|
+
requestContextStore = getFallbackRequestContextStore();
|
|
138
|
+
return requestContextStore;
|
|
139
|
+
}
|
|
140
|
+
function getFallbackRequestContextStore() {
|
|
141
|
+
fallbackRequestContextStore ??= createStackRequestContextStore();
|
|
142
|
+
return fallbackRequestContextStore;
|
|
143
|
+
}
|
|
144
|
+
function runWithDynamicResolutionFallbackContext(context, callback) {
|
|
145
|
+
dynamicResolutionFallbackStack.push(context);
|
|
146
|
+
try {
|
|
147
|
+
const result = callback();
|
|
148
|
+
void resolveRequestContextStore();
|
|
149
|
+
if (isPromiseLike(result)) {
|
|
150
|
+
return result.finally(() => {
|
|
151
|
+
removeDynamicResolutionFallbackContext(context);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
removeDynamicResolutionFallbackContext(context);
|
|
155
|
+
return result;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
removeDynamicResolutionFallbackContext(context);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function getDynamicResolutionFallbackContext() {
|
|
162
|
+
if (dynamicResolutionFallbackStack.length !== 1) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
return dynamicResolutionFallbackStack[0];
|
|
166
|
+
}
|
|
167
|
+
function removeDynamicResolutionFallbackContext(context) {
|
|
168
|
+
const index = dynamicResolutionFallbackStack.lastIndexOf(context);
|
|
169
|
+
if (index >= 0) {
|
|
170
|
+
dynamicResolutionFallbackStack.splice(index, 1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function isPromiseLike(value) {
|
|
174
|
+
return typeof value === 'object' && value !== null && 'then' in value && 'finally' in value;
|
|
175
|
+
}
|
|
176
|
+
function isAsyncCallback(callback) {
|
|
177
|
+
return callback.constructor.name === 'AsyncFunction';
|
|
101
178
|
}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"controller",
|
|
11
11
|
"rest"
|
|
12
12
|
],
|
|
13
|
-
"version": "1.1.
|
|
13
|
+
"version": "1.1.1",
|
|
14
14
|
"private": false,
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"repository": {
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@fluojs/core": "^1.0.3",
|
|
45
|
-
"@fluojs/validation": "^1.0.
|
|
46
|
-
"@fluojs/di": "^1.0
|
|
45
|
+
"@fluojs/validation": "^1.0.5",
|
|
46
|
+
"@fluojs/di": "^1.1.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"vitest": "^3.2.4"
|