@fluojs/http 2.0.1 → 3.0.0

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.
Files changed (125) hide show
  1. package/README.ko.md +417 -16
  2. package/README.md +424 -16
  3. package/dist/access-log-observer.d.ts +77 -0
  4. package/dist/access-log-observer.d.ts.map +1 -0
  5. package/dist/access-log-observer.js +171 -0
  6. package/dist/adapter.d.ts +9 -0
  7. package/dist/adapter.d.ts.map +1 -1
  8. package/dist/adapter.js +15 -1
  9. package/dist/adapters/binding.d.ts.map +1 -1
  10. package/dist/adapters/binding.js +18 -16
  11. package/dist/adapters/dto-binding-plan.d.ts +2 -2
  12. package/dist/adapters/dto-binding-plan.d.ts.map +1 -1
  13. package/dist/adapters/dto-binding-plan.js +13 -4
  14. package/dist/adapters/dto-validation-adapter.d.ts +2 -1
  15. package/dist/adapters/dto-validation-adapter.d.ts.map +1 -1
  16. package/dist/adapters/dto-validation-adapter.js +2 -2
  17. package/dist/byte-range-response.d.ts +71 -0
  18. package/dist/byte-range-response.d.ts.map +1 -0
  19. package/dist/byte-range-response.js +347 -0
  20. package/dist/client-identity.d.ts +8 -4
  21. package/dist/client-identity.d.ts.map +1 -1
  22. package/dist/client-identity.js +12 -93
  23. package/dist/compiled-route-identity.d.ts +17 -0
  24. package/dist/compiled-route-identity.d.ts.map +1 -0
  25. package/dist/compiled-route-identity.js +29 -0
  26. package/dist/connection.d.ts +67 -0
  27. package/dist/connection.d.ts.map +1 -0
  28. package/dist/connection.js +480 -0
  29. package/dist/context/request-context-node-store.d.ts +6 -0
  30. package/dist/context/request-context-node-store.d.ts.map +1 -1
  31. package/dist/context/request-context-node-store.js +14 -0
  32. package/dist/context/request-context.d.ts +5 -5
  33. package/dist/context/request-context.d.ts.map +1 -1
  34. package/dist/context/request-context.js +8 -83
  35. package/dist/context/request-id.d.ts +10 -0
  36. package/dist/context/request-id.d.ts.map +1 -0
  37. package/dist/context/request-id.js +27 -0
  38. package/dist/context/sse.d.ts +13 -0
  39. package/dist/context/sse.d.ts.map +1 -1
  40. package/dist/context/sse.js +36 -6
  41. package/dist/cookie-helpers.d.ts +47 -0
  42. package/dist/cookie-helpers.d.ts.map +1 -0
  43. package/dist/cookie-helpers.js +166 -0
  44. package/dist/decorators.d.ts +52 -19
  45. package/dist/decorators.d.ts.map +1 -1
  46. package/dist/decorators.js +57 -10
  47. package/dist/dispatch/conditional-request-policy.d.ts +67 -0
  48. package/dist/dispatch/conditional-request-policy.d.ts.map +1 -0
  49. package/dist/dispatch/conditional-request-policy.js +297 -0
  50. package/dist/dispatch/dispatch-content-negotiation.d.ts +8 -0
  51. package/dist/dispatch/dispatch-content-negotiation.d.ts.map +1 -1
  52. package/dist/dispatch/dispatch-content-negotiation.js +89 -42
  53. package/dist/dispatch/dispatch-error-negotiation.d.ts +26 -0
  54. package/dist/dispatch/dispatch-error-negotiation.d.ts.map +1 -0
  55. package/dist/dispatch/dispatch-error-negotiation.js +110 -0
  56. package/dist/dispatch/dispatch-error-policy.d.ts +1 -10
  57. package/dist/dispatch/dispatch-error-policy.d.ts.map +1 -1
  58. package/dist/dispatch/dispatch-error-policy.js +1 -33
  59. package/dist/dispatch/dispatch-error-representation.d.ts +18 -0
  60. package/dist/dispatch/dispatch-error-representation.d.ts.map +1 -0
  61. package/dist/dispatch/dispatch-error-representation.js +125 -0
  62. package/dist/dispatch/dispatch-response-metadata.d.ts +10 -0
  63. package/dist/dispatch/dispatch-response-metadata.d.ts.map +1 -0
  64. package/dist/dispatch/dispatch-response-metadata.js +12 -0
  65. package/dist/dispatch/dispatch-response-policy.d.ts +30 -3
  66. package/dist/dispatch/dispatch-response-policy.d.ts.map +1 -1
  67. package/dist/dispatch/dispatch-response-policy.js +104 -15
  68. package/dist/dispatch/dispatcher.d.ts +5 -1
  69. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  70. package/dist/dispatch/dispatcher.js +140 -100
  71. package/dist/dispatch/fast-path/debug-visibility.d.ts.map +1 -1
  72. package/dist/dispatch/fast-path/debug-visibility.js +6 -3
  73. package/dist/dispatch/fast-path/dispatcher-state.d.ts +10 -0
  74. package/dist/dispatch/fast-path/dispatcher-state.d.ts.map +1 -0
  75. package/dist/dispatch/fast-path/dispatcher-state.js +52 -0
  76. package/dist/dispatch/fast-path/eligibility-checker.d.ts.map +1 -1
  77. package/dist/dispatch/fast-path/eligibility-checker.js +27 -15
  78. package/dist/dispatch/fast-path/eligibility.d.ts +16 -10
  79. package/dist/dispatch/fast-path/eligibility.d.ts.map +1 -1
  80. package/dist/dispatch/fast-path/eligibility.js +10 -4
  81. package/dist/dispatch/fast-path/fast-path-executor.d.ts +1 -1
  82. package/dist/dispatch/fast-path/fast-path-executor.d.ts.map +1 -1
  83. package/dist/dispatch/fast-path/fast-path-executor.js +8 -5
  84. package/dist/dispatch/request-abort.d.ts +22 -0
  85. package/dist/dispatch/request-abort.d.ts.map +1 -0
  86. package/dist/dispatch/request-abort.js +30 -0
  87. package/dist/dispatch/response-integration.d.ts +55 -0
  88. package/dist/dispatch/response-integration.d.ts.map +1 -0
  89. package/dist/dispatch/response-integration.js +57 -0
  90. package/dist/errors.d.ts +10 -0
  91. package/dist/errors.d.ts.map +1 -1
  92. package/dist/errors.js +19 -0
  93. package/dist/guards.d.ts +1 -1
  94. package/dist/guards.d.ts.map +1 -1
  95. package/dist/header-helpers.d.ts +59 -0
  96. package/dist/header-helpers.d.ts.map +1 -0
  97. package/dist/header-helpers.js +185 -0
  98. package/dist/index.d.ts +1 -16
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +4 -15
  101. package/dist/index.portable.d.ts +25 -0
  102. package/dist/index.portable.d.ts.map +1 -0
  103. package/dist/index.portable.js +21 -0
  104. package/dist/internal.d.ts +3 -1
  105. package/dist/internal.d.ts.map +1 -1
  106. package/dist/internal.js +2 -0
  107. package/dist/mapping.d.ts.map +1 -1
  108. package/dist/mapping.js +100 -15
  109. package/dist/middleware/correlation.d.ts +9 -1
  110. package/dist/middleware/correlation.d.ts.map +1 -1
  111. package/dist/middleware/correlation.js +25 -14
  112. package/dist/middleware/cors.d.ts.map +1 -1
  113. package/dist/middleware/cors.js +5 -8
  114. package/dist/middleware/middleware.d.ts +3 -3
  115. package/dist/middleware/middleware.d.ts.map +1 -1
  116. package/dist/middleware/rate-limit.d.ts +13 -2
  117. package/dist/middleware/rate-limit.d.ts.map +1 -1
  118. package/dist/middleware/rate-limit.js +1 -0
  119. package/dist/static-assets.d.ts +77 -0
  120. package/dist/static-assets.d.ts.map +1 -0
  121. package/dist/static-assets.js +270 -0
  122. package/dist/types.d.ts +209 -16
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/types.js +69 -1
  125. package/package.json +19 -9
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Function that decides whether one proxy hop is trusted.
3
+ *
4
+ * @param address Proxy address being considered, starting with the direct transport peer.
5
+ * @param index Zero-based distance from the direct transport peer.
6
+ * @returns Whether the proxy hop may supply forwarding metadata.
7
+ */
8
+
9
+ /**
10
+ * Explicit policy that permits forwarding metadata from known proxy hops.
11
+ *
12
+ * `false` disables proxy trust, a number trusts that many nearest hops, an
13
+ * address/CIDR list matches nearest hops, and a predicate receives each hop
14
+ * from nearest to furthest.
15
+ */
16
+
17
+ /**
18
+ * Options used to derive a trusted connection snapshot.
19
+ */
20
+
21
+ /**
22
+ * Immutable, runtime-neutral view of the connection identity selected for a request.
23
+ */
24
+
25
+ function readHeader(headers, name) {
26
+ const values = [];
27
+ let present = false;
28
+ for (const [key, value] of Object.entries(headers)) {
29
+ if (key.toLowerCase() !== name) {
30
+ continue;
31
+ }
32
+ present = true;
33
+ if (typeof value === 'string') {
34
+ values.push(value);
35
+ } else if (Array.isArray(value)) {
36
+ values.push(...value);
37
+ }
38
+ }
39
+ const joined = values.join(',').trim();
40
+ return {
41
+ present,
42
+ value: joined || undefined
43
+ };
44
+ }
45
+ function normalizeAddress(value) {
46
+ if (!value) {
47
+ return undefined;
48
+ }
49
+ const trimmed = value.trim();
50
+ if (!trimmed || trimmed.toLowerCase() === 'unknown' || trimmed.startsWith('_')) {
51
+ return undefined;
52
+ }
53
+ let address = trimmed;
54
+ if (trimmed.startsWith('[')) {
55
+ const bracketed = trimmed.match(/^\[([^\]]+)\](?::(\d+))?$/);
56
+ if (!bracketed) {
57
+ return undefined;
58
+ }
59
+ if (bracketed[2] !== undefined && !isValidPort(bracketed[2])) {
60
+ return undefined;
61
+ }
62
+ address = bracketed[1];
63
+ } else {
64
+ const ipv4WithPort = trimmed.match(/^((?:\d{1,3}\.){3}\d{1,3}):(\d+)$/);
65
+ if (ipv4WithPort) {
66
+ if (!isValidPort(ipv4WithPort[2])) {
67
+ return undefined;
68
+ }
69
+ address = ipv4WithPort[1];
70
+ }
71
+ }
72
+ const parsed = parseAddress(address);
73
+ if (!parsed) {
74
+ return undefined;
75
+ }
76
+ if (parsed.kind === 'ipv4') {
77
+ return formatIpv4(parsed.value);
78
+ }
79
+ const mappedIpv4 = ipv4FromMappedIpv6(parsed.value);
80
+ return mappedIpv4 === undefined ? address.toLowerCase() : formatIpv4(mappedIpv4);
81
+ }
82
+ function splitOutsideQuotes(value, separator) {
83
+ const parts = [];
84
+ let current = '';
85
+ let quoted = false;
86
+ let escaped = false;
87
+ for (const character of value) {
88
+ if (escaped) {
89
+ current += character;
90
+ escaped = false;
91
+ continue;
92
+ }
93
+ if (quoted && character === '\\') {
94
+ current += character;
95
+ escaped = true;
96
+ continue;
97
+ }
98
+ if (character === '"') {
99
+ quoted = !quoted;
100
+ current += character;
101
+ continue;
102
+ }
103
+ if (!quoted && character === separator) {
104
+ parts.push(current.trim());
105
+ current = '';
106
+ continue;
107
+ }
108
+ current += character;
109
+ }
110
+ if (quoted || escaped) {
111
+ return undefined;
112
+ }
113
+ parts.push(current.trim());
114
+ return parts;
115
+ }
116
+ function unquoteForwardedValue(value) {
117
+ const trimmed = value.trim();
118
+ if (!trimmed) {
119
+ return undefined;
120
+ }
121
+ if (!trimmed.startsWith('"')) {
122
+ return trimmed.includes('"') ? undefined : trimmed;
123
+ }
124
+ if (!trimmed.endsWith('"') || trimmed.length < 2) {
125
+ return undefined;
126
+ }
127
+ let unquoted = '';
128
+ let escaped = false;
129
+ for (const character of trimmed.slice(1, -1)) {
130
+ if (escaped) {
131
+ unquoted += character;
132
+ escaped = false;
133
+ } else if (character === '\\') {
134
+ escaped = true;
135
+ } else {
136
+ unquoted += character;
137
+ }
138
+ }
139
+ return escaped ? undefined : unquoted;
140
+ }
141
+ function parseForwarded(value) {
142
+ if (!value) {
143
+ return undefined;
144
+ }
145
+ const elements = splitOutsideQuotes(value, ',');
146
+ if (!elements || elements.some(element => !element)) {
147
+ return undefined;
148
+ }
149
+ const parsed = [];
150
+ for (const element of elements) {
151
+ const parameters = splitOutsideQuotes(element, ';');
152
+ if (!parameters || parameters.some(parameter => !parameter)) {
153
+ return undefined;
154
+ }
155
+ let address;
156
+ let host;
157
+ let protocol;
158
+ const keys = new Set();
159
+ for (const parameter of parameters) {
160
+ const separator = parameter.indexOf('=');
161
+ if (separator <= 0) {
162
+ return undefined;
163
+ }
164
+ const key = parameter.slice(0, separator).trim().toLowerCase();
165
+ const rawParameterValue = parameter.slice(separator + 1);
166
+ const parameterValue = unquoteForwardedValue(rawParameterValue);
167
+ if (!key || !parameterValue || keys.has(key)) {
168
+ return undefined;
169
+ }
170
+ keys.add(key);
171
+ if (key === 'for') {
172
+ const isUnquotedIpv4WithPort = /^(?:\d{1,3}\.){3}\d{1,3}:\d+$/.test(parameterValue);
173
+ if (!rawParameterValue.trim().startsWith('"') && parameterValue.includes(':') && !isUnquotedIpv4WithPort) {
174
+ return undefined;
175
+ }
176
+ address = normalizeAddress(parameterValue);
177
+ } else if (key === 'host') {
178
+ host = parameterValue.trim() || undefined;
179
+ } else if (key === 'proto') {
180
+ protocol = parameterValue.trim().toLowerCase();
181
+ }
182
+ }
183
+ if (!address) {
184
+ return undefined;
185
+ }
186
+ parsed.push({
187
+ address,
188
+ host,
189
+ protocol
190
+ });
191
+ }
192
+ return parsed;
193
+ }
194
+ function parseForwardedFor(value) {
195
+ if (!value) {
196
+ return undefined;
197
+ }
198
+ const values = value.split(',');
199
+ if (values.length === 0) {
200
+ return undefined;
201
+ }
202
+ const addresses = [];
203
+ for (const value of values) {
204
+ const address = normalizeAddress(value);
205
+ if (!address) {
206
+ return undefined;
207
+ }
208
+ addresses.push(address);
209
+ }
210
+ return addresses;
211
+ }
212
+ function parseIpv4(value) {
213
+ const parts = value.split('.');
214
+ if (parts.length !== 4) {
215
+ return undefined;
216
+ }
217
+ let result = 0n;
218
+ for (const part of parts) {
219
+ if (!/^\d{1,3}$/.test(part)) {
220
+ return undefined;
221
+ }
222
+ const octet = Number(part);
223
+ if (octet > 255) {
224
+ return undefined;
225
+ }
226
+ result = (result << 8n) + BigInt(octet);
227
+ }
228
+ return result;
229
+ }
230
+ function parseIpv6(value) {
231
+ const normalized = value.toLowerCase();
232
+ const ipv4Separator = normalized.lastIndexOf(':');
233
+ const ipv4Candidate = ipv4Separator === -1 ? undefined : normalized.slice(ipv4Separator + 1);
234
+ const ipv4 = ipv4Candidate?.includes('.') ? parseIpv4(ipv4Candidate) : undefined;
235
+ const ipv6Value = ipv4 !== undefined ? `${normalized.slice(0, ipv4Separator)}:${(ipv4 >> 16n & 0xffffn).toString(16)}:${(ipv4 & 0xffffn).toString(16)}` : normalized;
236
+ const doubleColon = ipv6Value.indexOf('::');
237
+ if (doubleColon !== -1 && doubleColon !== ipv6Value.lastIndexOf('::')) {
238
+ return undefined;
239
+ }
240
+ const [head, tail] = doubleColon === -1 ? [ipv6Value, ''] : [ipv6Value.slice(0, doubleColon), ipv6Value.slice(doubleColon + 2)];
241
+ const headParts = head ? head.split(':') : [];
242
+ const tailParts = tail ? tail.split(':') : [];
243
+ if (headParts.some(part => !/^[\da-f]{1,4}$/.test(part)) || tailParts.some(part => !/^[\da-f]{1,4}$/.test(part))) {
244
+ return undefined;
245
+ }
246
+ const missingParts = 8 - headParts.length - tailParts.length;
247
+ if (doubleColon === -1 && missingParts !== 0 || doubleColon !== -1 && missingParts < 1) {
248
+ return undefined;
249
+ }
250
+ const parts = [...headParts, ...Array.from({
251
+ length: missingParts
252
+ }, () => '0'), ...tailParts];
253
+ let result = 0n;
254
+ for (const part of parts) {
255
+ result = (result << 16n) + BigInt(`0x${part}`);
256
+ }
257
+ return result;
258
+ }
259
+ function parseAddress(value) {
260
+ const ipv4 = parseIpv4(value);
261
+ if (ipv4 !== undefined) {
262
+ return {
263
+ kind: 'ipv4',
264
+ value: ipv4
265
+ };
266
+ }
267
+ const ipv6 = parseIpv6(value);
268
+ return ipv6 === undefined ? undefined : {
269
+ kind: 'ipv6',
270
+ value: ipv6
271
+ };
272
+ }
273
+ function formatIpv4(value) {
274
+ return [Number(value >> 24n & 0xffn), Number(value >> 16n & 0xffn), Number(value >> 8n & 0xffn), Number(value & 0xffn)].join('.');
275
+ }
276
+ function ipv4FromMappedIpv6(value) {
277
+ return value >> 32n === 0xffffn ? value & 0xffffffffn : undefined;
278
+ }
279
+ function isValidPort(value) {
280
+ if (!/^\d+$/.test(value)) {
281
+ return false;
282
+ }
283
+ const port = Number(value);
284
+ return Number.isInteger(port) && port >= 0 && port <= 65_535;
285
+ }
286
+ function matchesAddressOrCidr(address, rule) {
287
+ const [network, prefixText, ...extra] = rule.trim().split('/');
288
+ if (!network || extra.length > 0) {
289
+ return false;
290
+ }
291
+ const candidate = parseAddress(address);
292
+ let target = parseAddress(network);
293
+ if (!candidate || !target) {
294
+ return false;
295
+ }
296
+ if (prefixText !== undefined && !/^\d+$/.test(prefixText)) {
297
+ return false;
298
+ }
299
+ let prefix = prefixText === undefined ? undefined : Number(prefixText);
300
+ const targetBits = target.kind === 'ipv4' ? 32 : 128;
301
+ if (prefix !== undefined && (prefix < 0 || prefix > targetBits)) {
302
+ return false;
303
+ }
304
+ if (target.kind === 'ipv6') {
305
+ const mappedIpv4 = ipv4FromMappedIpv6(target.value);
306
+ if (mappedIpv4 !== undefined) {
307
+ if (prefix !== undefined && prefix < 96) {
308
+ return false;
309
+ }
310
+ target = {
311
+ kind: 'ipv4',
312
+ value: mappedIpv4
313
+ };
314
+ prefix = prefix === undefined ? undefined : prefix - 96;
315
+ }
316
+ }
317
+ if (candidate.kind !== target.kind) {
318
+ return false;
319
+ }
320
+ if (prefix === undefined) {
321
+ return candidate.value === target.value;
322
+ }
323
+ const bits = candidate.kind === 'ipv4' ? 32 : 128;
324
+ if (prefix === 0) {
325
+ return true;
326
+ }
327
+ const shift = BigInt(bits - prefix);
328
+ return candidate.value >> shift === target.value >> shift;
329
+ }
330
+ function isTrustedProxy(policy, address, index) {
331
+ if (policy === false) {
332
+ return false;
333
+ }
334
+ if (typeof policy === 'number') {
335
+ return Number.isInteger(policy) && policy > index;
336
+ }
337
+ if (typeof policy === 'function') {
338
+ return policy(address, index);
339
+ }
340
+ return policy.some(rule => matchesAddressOrCidr(address, rule));
341
+ }
342
+ function resolveRemoteAddress(request) {
343
+ if (request.connection?.remoteAddress) {
344
+ return normalizeAddress(request.connection.remoteAddress);
345
+ }
346
+ if (!request.raw || typeof request.raw !== 'object') {
347
+ return undefined;
348
+ }
349
+ const socket = request.raw.socket;
350
+ return typeof socket?.remoteAddress === 'string' ? normalizeAddress(socket.remoteAddress) : undefined;
351
+ }
352
+ function resolveTransportProtocol(request) {
353
+ if (request.connection?.protocol) {
354
+ return request.connection.protocol;
355
+ }
356
+ if (!request.raw || typeof request.raw !== 'object') {
357
+ return 'http';
358
+ }
359
+ const socket = request.raw.socket;
360
+ return socket?.encrypted === true ? 'https' : 'http';
361
+ }
362
+ function parseHost(value) {
363
+ const host = value?.trim() || undefined;
364
+ if (!host || host.includes(',')) {
365
+ return {
366
+ host: undefined,
367
+ hostname: undefined,
368
+ port: undefined
369
+ };
370
+ }
371
+ if (host.startsWith('[')) {
372
+ const bracketed = host.match(/^\[([^\]]+)\](?::(\d+))?$/);
373
+ if (!bracketed || parseAddress(bracketed[1])?.kind !== 'ipv6' || bracketed[2] !== undefined && !isValidPort(bracketed[2])) {
374
+ return {
375
+ host: undefined,
376
+ hostname: undefined,
377
+ port: undefined
378
+ };
379
+ }
380
+ const hostname = bracketed[1].toLowerCase();
381
+ const port = bracketed[2] === undefined ? undefined : Number(bracketed[2]);
382
+ return {
383
+ host,
384
+ hostname,
385
+ port
386
+ };
387
+ }
388
+ const authority = host.match(/^([A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?)(?::(\d+))?$/);
389
+ if (!authority || authority[2] !== undefined && !isValidPort(authority[2])) {
390
+ return {
391
+ host: undefined,
392
+ hostname: undefined,
393
+ port: undefined
394
+ };
395
+ }
396
+ return {
397
+ host,
398
+ hostname: authority[1].toLowerCase(),
399
+ port: authority[2] === undefined ? undefined : Number(authority[2])
400
+ };
401
+ }
402
+ function resolveForwardingSource(headers) {
403
+ const forwardedHeader = readHeader(headers, 'forwarded');
404
+ if (forwardedHeader.present) {
405
+ const elements = parseForwarded(forwardedHeader.value);
406
+ return elements ? {
407
+ addresses: elements.map(element => element.address),
408
+ elements,
409
+ kind: 'forwarded'
410
+ } : undefined;
411
+ }
412
+ const xForwardedForHeader = readHeader(headers, 'x-forwarded-for');
413
+ if (xForwardedForHeader.present) {
414
+ const addresses = parseForwardedFor(xForwardedForHeader.value);
415
+ return addresses ? {
416
+ addresses,
417
+ kind: 'x-forwarded-for'
418
+ } : undefined;
419
+ }
420
+ const xRealIpHeader = readHeader(headers, 'x-real-ip');
421
+ if (!xRealIpHeader.present) {
422
+ return undefined;
423
+ }
424
+ const address = normalizeAddress(xRealIpHeader.value);
425
+ return address ? {
426
+ addresses: [address],
427
+ kind: 'x-real-ip'
428
+ } : undefined;
429
+ }
430
+ function readIndexedForwardingMetadata(headers, name, index, expectedLength) {
431
+ const header = readHeader(headers, name);
432
+ if (!header.value) {
433
+ return undefined;
434
+ }
435
+ const values = header.value.split(',').map(value => value.trim());
436
+ return values.length === expectedLength && !values.some(value => !value) ? values[index] : undefined;
437
+ }
438
+
439
+ /**
440
+ * Resolve an immutable connection snapshot using a direct transport identity
441
+ * and only proxy metadata allowed by the explicit trust policy.
442
+ *
443
+ * Malformed forwarding input is ignored as a whole, preserving the direct
444
+ * transport identity. Adapters without a socket address produce an undefined
445
+ * identity rather than trusting a forwarding header by default.
446
+ *
447
+ * @param request Adapter-normalized request whose headers and raw transport are inspected.
448
+ * @param options Explicit proxy trust policy for the current deployment.
449
+ * @returns Immutable connection identity, protocol, host, and trusted proxy chain.
450
+ */
451
+ export function resolveHttpConnection(request, options = {}) {
452
+ const remoteAddress = resolveRemoteAddress(request);
453
+ const trustProxy = options.trustProxy ?? false;
454
+ const forwarding = resolveForwardingSource(request.headers);
455
+ const headerAddresses = forwarding?.addresses;
456
+ const chain = remoteAddress && headerAddresses && trustProxy !== false ? [...headerAddresses, remoteAddress] : !remoteAddress && options.allowForwardedWithoutPeer && headerAddresses && trustProxy !== false ? [...headerAddresses] : remoteAddress ? [remoteAddress] : [];
457
+ let clientIndex = chain.length - 1;
458
+ while (clientIndex > 0 && isTrustedProxy(trustProxy, chain[clientIndex], chain.length - 1 - clientIndex)) {
459
+ clientIndex--;
460
+ }
461
+ const proxyChain = Object.freeze(chain.slice(clientIndex + 1));
462
+ const usesTrustedForwarding = forwarding !== undefined && clientIndex < forwarding.addresses.length && (proxyChain.length > 0 || !remoteAddress && options.allowForwardedWithoutPeer === true);
463
+ const forwardedElement = usesTrustedForwarding && forwarding?.kind === 'forwarded' ? forwarding.elements[clientIndex] : undefined;
464
+ const legacyHost = usesTrustedForwarding && forwarding?.kind !== 'forwarded' ? readIndexedForwardingMetadata(request.headers, 'x-forwarded-host', clientIndex, forwarding.addresses.length) : undefined;
465
+ const legacyProtocol = usesTrustedForwarding && forwarding?.kind !== 'forwarded' ? readIndexedForwardingMetadata(request.headers, 'x-forwarded-proto', clientIndex, forwarding.addresses.length) : undefined;
466
+ const protocolCandidate = forwardedElement?.protocol ?? legacyProtocol?.toLowerCase();
467
+ const protocol = protocolCandidate === 'https' ? 'https' : protocolCandidate === 'http' ? 'http' : resolveTransportProtocol(request);
468
+ const forwardingHost = parseHost(forwardedElement?.host ?? legacyHost);
469
+ const host = forwardingHost.host ? forwardingHost : parseHost(readHeader(request.headers, 'host').value);
470
+ return Object.freeze({
471
+ clientAddress: chain[clientIndex],
472
+ host: host.host,
473
+ hostname: host.hostname,
474
+ port: host.port,
475
+ protocol,
476
+ proxyChain,
477
+ remoteAddress,
478
+ secure: protocol === 'https'
479
+ });
480
+ }
@@ -13,6 +13,12 @@ type AsyncLocalStorageResolutionHost = {
13
13
  };
14
14
  };
15
15
  type NodeAsyncHooksLoader = () => Promise<NodeAsyncHooksModule>;
16
+ /**
17
+ * Registers a host-provided constructor that can be used without an asynchronous module lookup.
18
+ *
19
+ * @param AsyncLocalStorage Constructor supplied by a runtime-specific package entrypoint.
20
+ */
21
+ export declare function registerImmediateAsyncLocalStorageConstructor(AsyncLocalStorage: AsyncLocalStorageConstructor): void;
16
22
  /**
17
23
  * Resolves host-provided `AsyncLocalStorage` without async imports or throwing host probes.
18
24
  *
@@ -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;;;;;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
+ {"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;AAIhE;;;;GAIG;AACH,wBAAgB,6CAA6C,CAC3D,iBAAiB,EAAE,4BAA4B,GAC9C,IAAI,CAEN;AAED;;;;;GAKG;AACH,wBAAgB,4CAA4C,CAC1D,IAAI,GAAE,+BAA4C,GACjD,4BAA4B,GAAG,SAAS,CA0B1C;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,14 @@
1
+ let registeredAsyncLocalStorageConstructor;
2
+
3
+ /**
4
+ * Registers a host-provided constructor that can be used without an asynchronous module lookup.
5
+ *
6
+ * @param AsyncLocalStorage Constructor supplied by a runtime-specific package entrypoint.
7
+ */
8
+ export function registerImmediateAsyncLocalStorageConstructor(AsyncLocalStorage) {
9
+ registeredAsyncLocalStorageConstructor = AsyncLocalStorage;
10
+ }
11
+
1
12
  /**
2
13
  * Resolves host-provided `AsyncLocalStorage` without async imports or throwing host probes.
3
14
  *
@@ -5,6 +16,9 @@
5
16
  * @returns The resolved `AsyncLocalStorage` constructor, or `undefined` when unavailable.
6
17
  */
7
18
  export function resolveImmediateAsyncLocalStorageConstructor(host = globalThis) {
19
+ if (registeredAsyncLocalStorageConstructor) {
20
+ return registeredAsyncLocalStorageConstructor;
21
+ }
8
22
  if (typeof host.AsyncLocalStorage === 'function') {
9
23
  return host.AsyncLocalStorage;
10
24
  }
@@ -2,11 +2,11 @@ import type { ContextKey, RequestContext } from '../types.js';
2
2
  /**
3
3
  * Runs a callback inside the request-scoped async context.
4
4
  *
5
- * Hosts with `AsyncLocalStorage` preserve the context across awaited work. During lazy
6
- * `AsyncLocalStorage` resolution, promise continuations registered before the store resolves keep
7
- * their request context until the returned promise settles. Hosts without an async context primitive
8
- * use a stack fallback that keeps the context only for the synchronous callback frame and clears it
9
- * before awaited continuations resume.
5
+ * Hosts with `AsyncLocalStorage` preserve the context across awaited work. Runtime-specific package
6
+ * entrypoints register an immediately available constructor before exposing this helper, so
7
+ * non-async callbacks retain synchronous return and throw behavior while returned promise
8
+ * continuations remain request-local. Hosts without an async context primitive use a synchronous-only
9
+ * fallback that clears the context before awaited work resumes.
10
10
  *
11
11
  * @param context Request context snapshot to bind to the current async execution chain.
12
12
  * @param callback Callback executed with `context` available through request-context helpers.
@@ -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;AAoB9D;;;;;;;;;;;;GAYG;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
+ {"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;AAa9D;;;;;;;;;;;;GAYG;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"}
@@ -4,17 +4,15 @@ import { createStackRequestContextStore } from './request-context-stack-store.js
4
4
  let requestContextStore;
5
5
  let requestContextStoreResolution;
6
6
  let fallbackRequestContextStore;
7
- let originalPromiseThen;
8
- let promiseThenPatchDepth = 0;
9
- const dynamicResolutionFallbackStack = [];
7
+
10
8
  /**
11
9
  * Runs a callback inside the request-scoped async context.
12
10
  *
13
- * Hosts with `AsyncLocalStorage` preserve the context across awaited work. During lazy
14
- * `AsyncLocalStorage` resolution, promise continuations registered before the store resolves keep
15
- * their request context until the returned promise settles. Hosts without an async context primitive
16
- * use a stack fallback that keeps the context only for the synchronous callback frame and clears it
17
- * before awaited continuations resume.
11
+ * Hosts with `AsyncLocalStorage` preserve the context across awaited work. Runtime-specific package
12
+ * entrypoints register an immediately available constructor before exposing this helper, so
13
+ * non-async callbacks retain synchronous return and throw behavior while returned promise
14
+ * continuations remain request-local. Hosts without an async context primitive use a synchronous-only
15
+ * fallback that clears the context before awaited work resumes.
18
16
  *
19
17
  * @param context Request context snapshot to bind to the current async execution chain.
20
18
  * @param callback Callback executed with `context` available through request-context helpers.
@@ -29,7 +27,7 @@ export function runWithRequestContext(context, callback) {
29
27
  return getFallbackRequestContextStore().run(context, callback);
30
28
  }
31
29
  if (!isAsyncCallback(callback)) {
32
- return runWithDynamicResolutionFallbackContext(context, callback);
30
+ return getFallbackRequestContextStore().run(context, callback);
33
31
  }
34
32
  return runWithResolvedRequestContextStore(context, callback);
35
33
  }
@@ -40,7 +38,7 @@ export function runWithRequestContext(context, callback) {
40
38
  * @returns The active request context, or `undefined` when no request scope is bound.
41
39
  */
42
40
  export function getCurrentRequestContext() {
43
- return getRequestContextStore().getStore() ?? getDynamicResolutionFallbackContext();
41
+ return getRequestContextStore().getStore();
44
42
  }
45
43
 
46
44
  /**
@@ -144,79 +142,6 @@ function getFallbackRequestContextStore() {
144
142
  fallbackRequestContextStore ??= createStackRequestContextStore();
145
143
  return fallbackRequestContextStore;
146
144
  }
147
- function runWithDynamicResolutionFallbackContext(context, callback) {
148
- dynamicResolutionFallbackStack.push(context);
149
- installPromiseThenContextBridge();
150
- let cleanedUp = false;
151
- const cleanup = () => {
152
- if (cleanedUp) {
153
- return;
154
- }
155
- cleanedUp = true;
156
- removeDynamicResolutionFallbackContext(context);
157
- restorePromiseThenContextBridge();
158
- void resolveRequestContextStore();
159
- };
160
- try {
161
- const result = callback();
162
- if (isPromise(result)) {
163
- const then = originalPromiseThen ?? Promise.prototype.then;
164
- void then.call(result, cleanup, cleanup);
165
- return result;
166
- }
167
- cleanup();
168
- return result;
169
- } catch (error) {
170
- cleanup();
171
- throw error;
172
- }
173
- }
174
- function getDynamicResolutionFallbackContext() {
175
- return dynamicResolutionFallbackStack.at(-1);
176
- }
177
- function removeDynamicResolutionFallbackContext(context) {
178
- const index = dynamicResolutionFallbackStack.lastIndexOf(context);
179
- if (index >= 0) {
180
- dynamicResolutionFallbackStack.splice(index, 1);
181
- }
182
- }
183
- function installPromiseThenContextBridge() {
184
- if (promiseThenPatchDepth === 0) {
185
- originalPromiseThen = Promise.prototype.then;
186
- Object.defineProperty(Promise.prototype, 'then', {
187
- configurable: true,
188
- value: createContextBridgePromiseThen(originalPromiseThen),
189
- writable: true
190
- });
191
- }
192
- promiseThenPatchDepth += 1;
193
- }
194
- function restorePromiseThenContextBridge() {
195
- promiseThenPatchDepth -= 1;
196
- if (promiseThenPatchDepth === 0 && originalPromiseThen) {
197
- Object.defineProperty(Promise.prototype, 'then', {
198
- configurable: true,
199
- value: originalPromiseThen,
200
- writable: true
201
- });
202
- originalPromiseThen = undefined;
203
- }
204
- }
205
- function createContextBridgePromiseThen(originalThen) {
206
- return function contextBridgePromiseThen(onfulfilled, onrejected) {
207
- const context = getDynamicResolutionFallbackContext();
208
- return originalThen.call(this, wrapPromiseCallback(context, onfulfilled), wrapPromiseCallback(context, onrejected));
209
- };
210
- }
211
- function wrapPromiseCallback(context, callback) {
212
- if (!context || typeof callback !== 'function') {
213
- return callback;
214
- }
215
- return value => runWithDynamicResolutionFallbackContext(context, () => callback(value));
216
- }
217
- function isPromise(value) {
218
- return value instanceof Promise;
219
- }
220
145
  function isAsyncCallback(callback) {
221
146
  return callback.constructor.name === 'AsyncFunction';
222
147
  }
@@ -0,0 +1,10 @@
1
+ import type { FrameworkRequest } from '../types.js';
2
+ /**
3
+ * Resolves the request identity before dispatcher observers begin.
4
+ *
5
+ * @param request Adapter-normalized request carrying inbound correlation headers.
6
+ * @param generate Whether to generate an ID when neither supported inbound header is present.
7
+ * @returns The adopted request or legacy correlation ID, or a newly generated ID.
8
+ */
9
+ export declare function resolveRequestId(request: FrameworkRequest, generate?: boolean): string | undefined;
10
+ //# sourceMappingURL=request-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-id.d.ts","sourceRoot":"","sources":["../../src/context/request-id.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAuBpD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,UAAO,GAAG,MAAM,GAAG,SAAS,CAI/F"}