@reckona/mreact-router 0.0.203 → 0.0.205
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.md +53 -9
- package/dist/actions.d.ts +6 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +70 -10
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts +15 -4
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +301 -63
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +2 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +18 -10
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/edge.d.ts +1 -1
- package/dist/adapters/edge.d.ts.map +1 -1
- package/dist/adapters/edge.js.map +1 -1
- package/dist/adapters/node.d.ts +5 -3
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +2 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +24 -2
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +91 -5
- package/dist/build.js.map +1 -1
- package/dist/built-runtime.d.ts +2 -0
- package/dist/built-runtime.d.ts.map +1 -1
- package/dist/built-runtime.js +16 -0
- package/dist/built-runtime.js.map +1 -1
- package/dist/cache.d.ts +3 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +211 -13
- package/dist/cache.js.map +1 -1
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +32 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -2
- package/dist/config.js.map +1 -1
- package/dist/csp.d.ts +1 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +11 -6
- package/dist/csp.js.map +1 -1
- package/dist/dehydrate-policy.d.ts +3 -0
- package/dist/dehydrate-policy.d.ts.map +1 -0
- package/dist/dehydrate-policy.js +16 -0
- package/dist/dehydrate-policy.js.map +1 -0
- package/dist/dev-server.d.ts +7 -2
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +120 -11
- package/dist/dev-server.js.map +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +147 -7
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +22 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +22 -8
- package/dist/logger.js.map +1 -1
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +60 -22
- package/dist/metadata.js.map +1 -1
- package/dist/navigation-marker.d.ts.map +1 -1
- package/dist/navigation-marker.js +72 -9
- package/dist/navigation-marker.js.map +1 -1
- package/dist/navigation.d.ts +1 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +1 -1
- package/dist/navigation.js.map +1 -1
- package/dist/node-server.d.ts +5 -2
- package/dist/node-server.d.ts.map +1 -1
- package/dist/node-server.js +75 -6
- package/dist/node-server.js.map +1 -1
- package/dist/prerender-entry.d.ts +14 -0
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +40 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +9 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +102 -123
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +8 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +22 -20
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -19
- package/dist/session.js.map +1 -1
- package/dist/upgrade.d.ts +59 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +282 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/vite-config.d.ts +2 -0
- package/dist/vite-config.d.ts.map +1 -1
- package/dist/vite-config.js +2 -0
- package/dist/vite-config.js.map +1 -1
- package/dist/vite.d.ts +4 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +117 -6
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +81 -11
- package/src/adapters/aws-lambda.ts +426 -115
- package/src/adapters/cloudflare.ts +29 -13
- package/src/adapters/edge.ts +2 -0
- package/src/adapters/node.ts +8 -10
- package/src/adapters/static.ts +31 -2
- package/src/build.ts +117 -10
- package/src/built-runtime.ts +28 -0
- package/src/cache.ts +262 -15
- package/src/cli.ts +14 -1
- package/src/client.ts +32 -1
- package/src/config.ts +43 -6
- package/src/csp.ts +14 -11
- package/src/dehydrate-policy.ts +23 -0
- package/src/dev-server.ts +188 -15
- package/src/http.ts +172 -6
- package/src/index.ts +14 -1
- package/src/logger.ts +47 -10
- package/src/metadata.ts +125 -60
- package/src/navigation-marker.ts +79 -9
- package/src/navigation.ts +1 -1
- package/src/node-server.ts +128 -22
- package/src/prerender-entry.ts +52 -3
- package/src/render.ts +807 -753
- package/src/serve.ts +49 -56
- package/src/session.ts +56 -25
- package/src/upgrade.ts +402 -3
- package/src/vite-config.ts +6 -0
- package/src/vite.ts +157 -5
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Buffer } from "node:buffer";
|
|
1
|
+
import { Buffer, isUtf8 } from "node:buffer";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { access, lstat, mkdir, readFile, readlink, symlink } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
+
import type { DehydrateOptions } from "@reckona/mreact-query";
|
|
6
7
|
import type { AppRouterServerActionOptions } from "../actions.js";
|
|
7
8
|
import type { AppRouterCache } from "../cache.js";
|
|
8
9
|
import type { AppRouterImportPolicy } from "../import-policy.js";
|
|
@@ -25,10 +26,7 @@ import type { RouterInstrumentation } from "../trace.js";
|
|
|
25
26
|
/** Re-exports cache contracts used by AWS Lambda handlers. */
|
|
26
27
|
export type { AppRouterCache, AppRouterCacheEntry } from "../cache.js";
|
|
27
28
|
/** Re-exports server action contracts used by AWS Lambda handlers. */
|
|
28
|
-
export type {
|
|
29
|
-
AppRouterAllowedServerAction,
|
|
30
|
-
AppRouterServerActionOptions,
|
|
31
|
-
} from "../actions.js";
|
|
29
|
+
export type { AppRouterAllowedServerAction, AppRouterServerActionOptions } from "../actions.js";
|
|
32
30
|
/** Re-exports logger contracts used by AWS Lambda handlers. */
|
|
33
31
|
export type {
|
|
34
32
|
AppRouterCspInlineNonceWarningLogEvent,
|
|
@@ -41,6 +39,8 @@ export type {
|
|
|
41
39
|
AppRouterRequestStartLogEvent,
|
|
42
40
|
AppRouterRequestTimingLogEvent,
|
|
43
41
|
AppRouterRuntime,
|
|
42
|
+
AppRouterUpgradeErrorLogEvent,
|
|
43
|
+
AppRouterUpgradeRejectedLogEvent,
|
|
44
44
|
} from "../logger.js";
|
|
45
45
|
/** Re-exports response hook contracts used by AWS Lambda handlers. */
|
|
46
46
|
export type { AppRouterResponseHook, AppRouterResponseHookContext } from "../render.js";
|
|
@@ -63,18 +63,15 @@ import {
|
|
|
63
63
|
createBuiltRequestRuntime,
|
|
64
64
|
preloadBuiltAppRuntime,
|
|
65
65
|
resolveRequestHost,
|
|
66
|
-
warnIfImplicitHostTrust,
|
|
67
66
|
type AppRouterPrerenderStore,
|
|
68
67
|
type RequestHostPolicy,
|
|
69
68
|
type ResponseSinkStrategy,
|
|
70
69
|
} from "../serve.js";
|
|
71
70
|
|
|
71
|
+
let warnedImplicitAwsLambdaHostPolicy = false;
|
|
72
|
+
|
|
72
73
|
/** Re-exports request and rendering contracts used by AWS Lambda handlers. */
|
|
73
|
-
export type {
|
|
74
|
-
AppRouterPrerenderStore,
|
|
75
|
-
RequestHostPolicy,
|
|
76
|
-
ResponseSinkStrategy,
|
|
77
|
-
} from "../serve.js";
|
|
74
|
+
export type { AppRouterPrerenderStore, RequestHostPolicy, ResponseSinkStrategy } from "../serve.js";
|
|
78
75
|
|
|
79
76
|
/**
|
|
80
77
|
* Represents the AWS API Gateway HTTP API v2 event consumed by buffered handlers.
|
|
@@ -120,9 +117,25 @@ export interface AwsLambdaStreamingResponseMetadata {
|
|
|
120
117
|
*/
|
|
121
118
|
export interface AwsLambdaStreamingResponseStream {
|
|
122
119
|
destroy?: ((error?: unknown) => void) | undefined;
|
|
120
|
+
/** Reports whether the runtime output stream has already been destroyed. */
|
|
121
|
+
destroyed?: boolean | undefined;
|
|
123
122
|
end(): void;
|
|
124
|
-
|
|
123
|
+
/** Removes a lifecycle listener when the runtime stream implements Node EventEmitter semantics. */
|
|
124
|
+
off?:
|
|
125
|
+
| ((event: "close" | "drain" | "error", listener: (error?: unknown) => void) => unknown)
|
|
126
|
+
| undefined;
|
|
127
|
+
/** Registers drain-only or full Node-compatible lifecycle listeners. */
|
|
128
|
+
once?:
|
|
129
|
+
| ((event: "drain", listener: () => void) => unknown)
|
|
130
|
+
| ((event: "close" | "drain" | "error", listener: (error?: unknown) => void) => unknown)
|
|
131
|
+
| undefined;
|
|
132
|
+
/** Removes a lifecycle listener on runtimes that expose the legacy EventEmitter API. */
|
|
133
|
+
removeListener?:
|
|
134
|
+
| ((event: "close" | "drain" | "error", listener: (error?: unknown) => void) => unknown)
|
|
135
|
+
| undefined;
|
|
125
136
|
write(chunk: string | Uint8Array): boolean;
|
|
137
|
+
/** Reports whether the runtime output stream ended normally. */
|
|
138
|
+
writableEnded?: boolean | undefined;
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
/**
|
|
@@ -130,6 +143,7 @@ export interface AwsLambdaStreamingResponseStream {
|
|
|
130
143
|
*/
|
|
131
144
|
export interface AwsLambdaRequestHandlerOptions {
|
|
132
145
|
allowedHosts?: readonly string[] | undefined;
|
|
146
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
133
147
|
errorHandler?:
|
|
134
148
|
| ((error: unknown) => {
|
|
135
149
|
body: string;
|
|
@@ -157,10 +171,7 @@ export interface AwsLambdaRequestHandlerOptions {
|
|
|
157
171
|
/**
|
|
158
172
|
* Selects how an AWS Lambda handler resolves its app-router import policy.
|
|
159
173
|
*/
|
|
160
|
-
export type AwsLambdaImportPolicy =
|
|
161
|
-
| AppRouterImportPolicy
|
|
162
|
-
| "generated"
|
|
163
|
-
| { fromManifest: true };
|
|
174
|
+
export type AwsLambdaImportPolicy = AppRouterImportPolicy | "generated" | { fromManifest: true };
|
|
164
175
|
|
|
165
176
|
/**
|
|
166
177
|
* Configures preload behavior for built app-router modules in AWS Lambda.
|
|
@@ -197,7 +208,7 @@ export type AwsLambdaStreamingRequestHandler<TContext = unknown> = (
|
|
|
197
208
|
) => Promise<void>;
|
|
198
209
|
|
|
199
210
|
const invalidAwsLambdaHttpEventV2Diagnostic =
|
|
200
|
-
|
|
211
|
+
"Expected an AWS Lambda HTTP API payload format 2.0 event with rawPath and requestContext.http.method.";
|
|
201
212
|
|
|
202
213
|
function invalidAwsLambdaHttpEventV2Result(): AwsLambdaHttpResultV2 {
|
|
203
214
|
return {
|
|
@@ -223,7 +234,7 @@ function invalidAwsLambdaHttpEventV2Response(): Response {
|
|
|
223
234
|
export function createAwsLambdaRequestHandler(
|
|
224
235
|
options: AwsLambdaRequestHandlerOptions,
|
|
225
236
|
): AwsLambdaRequestHandler {
|
|
226
|
-
|
|
237
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
227
238
|
let handler: AwsLambdaRequestHandler | undefined;
|
|
228
239
|
|
|
229
240
|
return async (event) => {
|
|
@@ -235,7 +246,11 @@ export function createAwsLambdaRequestHandler(
|
|
|
235
246
|
|
|
236
247
|
handler ??= (() => {
|
|
237
248
|
const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
|
|
238
|
-
const runtimePreloadPromise = startAwsLambdaRuntimePreload(
|
|
249
|
+
const runtimePreloadPromise = startAwsLambdaRuntimePreload(
|
|
250
|
+
options,
|
|
251
|
+
runtimeDirPromise,
|
|
252
|
+
"middleware",
|
|
253
|
+
);
|
|
239
254
|
void runtimePreloadPromise?.catch(() => {});
|
|
240
255
|
return createAwsLambdaRequestHandlerFromRuntime(
|
|
241
256
|
options,
|
|
@@ -257,7 +272,7 @@ export function createAwsLambdaRequestHandler(
|
|
|
257
272
|
export async function createPreloadedAwsLambdaRequestHandler(
|
|
258
273
|
options: AwsLambdaRequestHandlerOptions,
|
|
259
274
|
): Promise<AwsLambdaRequestHandler> {
|
|
260
|
-
|
|
275
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
261
276
|
let handler: AwsLambdaRequestHandler | undefined;
|
|
262
277
|
|
|
263
278
|
return async (event) => {
|
|
@@ -287,10 +302,8 @@ export async function createPreloadedAwsLambdaRequestHandler(
|
|
|
287
302
|
*
|
|
288
303
|
* Call this during controlled Lambda initialization only when the extra startup work is preferable to first-valid-request latency. Request handlers always validate events before they start runtime work.
|
|
289
304
|
*/
|
|
290
|
-
export async function warmAwsLambdaRuntime(
|
|
291
|
-
options
|
|
292
|
-
): Promise<void> {
|
|
293
|
-
warnIfImplicitHostTrust(options);
|
|
305
|
+
export async function warmAwsLambdaRuntime(options: AwsLambdaRequestHandlerOptions): Promise<void> {
|
|
306
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
294
307
|
const runtimeDir = await prepareAwsLambdaRuntimeDir(options);
|
|
295
308
|
await preloadAwsLambdaRuntime(options, runtimeDir);
|
|
296
309
|
}
|
|
@@ -310,16 +323,18 @@ function createAwsLambdaRequestHandlerFromRuntime(
|
|
|
310
323
|
|
|
311
324
|
const startedAt = logNow();
|
|
312
325
|
const phases = createAwsLambdaTimingPhases(options);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
finishPhase(phases, eventToRequestStartedAt, "eventToRequestMs");
|
|
316
|
-
const logFields = requestLogFields(request, "aws-lambda");
|
|
317
|
-
emitRouterLog(options.logger, "info", {
|
|
318
|
-
...logFields,
|
|
319
|
-
type: "router:request:start",
|
|
320
|
-
});
|
|
326
|
+
let request: Request | undefined;
|
|
327
|
+
let logFields = awsLambdaEventLogFields(event);
|
|
321
328
|
|
|
322
329
|
try {
|
|
330
|
+
const eventToRequestStartedAt = phaseStartedAt(phases);
|
|
331
|
+
request = eventToRequest(event, options);
|
|
332
|
+
finishPhase(phases, eventToRequestStartedAt, "eventToRequestMs");
|
|
333
|
+
logFields = requestLogFields(request, "aws-lambda");
|
|
334
|
+
emitRouterLog(options.logger, "info", {
|
|
335
|
+
...logFields,
|
|
336
|
+
type: "router:request:start",
|
|
337
|
+
});
|
|
323
338
|
const runtimeDirStartedAt = phaseStartedAt(phases);
|
|
324
339
|
const runtimeDir = await runtimeDirPromise;
|
|
325
340
|
finishPhase(phases, runtimeDirStartedAt, "runtimeDirMs");
|
|
@@ -337,12 +352,9 @@ function createAwsLambdaRequestHandlerFromRuntime(
|
|
|
337
352
|
outDir: options.outDir,
|
|
338
353
|
runtimeDir,
|
|
339
354
|
});
|
|
340
|
-
const preload = awsLambdaRenderPreload(
|
|
341
|
-
options,
|
|
342
|
-
runtimePreloadPromise,
|
|
343
|
-
defaultPreloadMode,
|
|
344
|
-
);
|
|
355
|
+
const preload = awsLambdaRenderPreload(options, runtimePreloadPromise, defaultPreloadMode);
|
|
345
356
|
const response = await builtRuntime.render(request, {
|
|
357
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
346
358
|
instrumentation: options.instrumentation,
|
|
347
359
|
logger: awsLambdaRenderLogger(options),
|
|
348
360
|
onResponse: options.onResponse,
|
|
@@ -401,7 +413,7 @@ function createAwsLambdaRequestHandlerFromRuntime(
|
|
|
401
413
|
export function createAwsLambdaStreamingRequestHandler<TContext = unknown>(
|
|
402
414
|
options: AwsLambdaRequestHandlerOptions,
|
|
403
415
|
): AwsLambdaStreamingRequestHandler<TContext> {
|
|
404
|
-
|
|
416
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
405
417
|
const runtime = awsLambdaRuntime();
|
|
406
418
|
let handler: AwsLambdaStreamingRequestHandler<TContext> | undefined;
|
|
407
419
|
|
|
@@ -409,17 +421,17 @@ export function createAwsLambdaStreamingRequestHandler<TContext = unknown>(
|
|
|
409
421
|
try {
|
|
410
422
|
validateAwsLambdaHttpEventV2(event);
|
|
411
423
|
} catch {
|
|
412
|
-
await streamResponseToLambda(
|
|
413
|
-
invalidAwsLambdaHttpEventV2Response(),
|
|
414
|
-
responseStream,
|
|
415
|
-
runtime,
|
|
416
|
-
);
|
|
424
|
+
await streamResponseToLambda(invalidAwsLambdaHttpEventV2Response(), responseStream, runtime);
|
|
417
425
|
return;
|
|
418
426
|
}
|
|
419
427
|
|
|
420
428
|
handler ??= (() => {
|
|
421
429
|
const runtimeDirPromise = prepareAwsLambdaRuntimeDir(options);
|
|
422
|
-
const runtimePreloadPromise = startAwsLambdaRuntimePreload(
|
|
430
|
+
const runtimePreloadPromise = startAwsLambdaRuntimePreload(
|
|
431
|
+
options,
|
|
432
|
+
runtimeDirPromise,
|
|
433
|
+
"middleware",
|
|
434
|
+
);
|
|
423
435
|
void runtimePreloadPromise?.catch(() => {});
|
|
424
436
|
return createAwsLambdaStreamingRequestHandlerFromRuntime(
|
|
425
437
|
options,
|
|
@@ -442,7 +454,7 @@ export function createAwsLambdaStreamingRequestHandler<TContext = unknown>(
|
|
|
442
454
|
export async function createPreloadedAwsLambdaStreamingRequestHandler<TContext = unknown>(
|
|
443
455
|
options: AwsLambdaRequestHandlerOptions,
|
|
444
456
|
): Promise<AwsLambdaStreamingRequestHandler<TContext>> {
|
|
445
|
-
|
|
457
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
446
458
|
const runtime = awsLambdaRuntime();
|
|
447
459
|
let handler: AwsLambdaStreamingRequestHandler<TContext> | undefined;
|
|
448
460
|
|
|
@@ -450,11 +462,7 @@ export async function createPreloadedAwsLambdaStreamingRequestHandler<TContext =
|
|
|
450
462
|
try {
|
|
451
463
|
validateAwsLambdaHttpEventV2(event);
|
|
452
464
|
} catch {
|
|
453
|
-
await streamResponseToLambda(
|
|
454
|
-
invalidAwsLambdaHttpEventV2Response(),
|
|
455
|
-
responseStream,
|
|
456
|
-
runtime,
|
|
457
|
-
);
|
|
465
|
+
await streamResponseToLambda(invalidAwsLambdaHttpEventV2Response(), responseStream, runtime);
|
|
458
466
|
return;
|
|
459
467
|
}
|
|
460
468
|
|
|
@@ -596,8 +604,12 @@ async function readGeneratedAwsLambdaImportPolicy(outDir: string): Promise<AppRo
|
|
|
596
604
|
return await promise;
|
|
597
605
|
}
|
|
598
606
|
|
|
599
|
-
async function readGeneratedAwsLambdaImportPolicyInner(
|
|
600
|
-
|
|
607
|
+
async function readGeneratedAwsLambdaImportPolicyInner(
|
|
608
|
+
outDir: string,
|
|
609
|
+
): Promise<AppRouterImportPolicy> {
|
|
610
|
+
const policy = JSON.parse(
|
|
611
|
+
await readFile(join(outDir, "server", "import-policy.json"), "utf8"),
|
|
612
|
+
) as {
|
|
601
613
|
runtimePackages?: readonly string[] | undefined;
|
|
602
614
|
};
|
|
603
615
|
|
|
@@ -624,26 +636,27 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
|
|
|
624
636
|
try {
|
|
625
637
|
validateAwsLambdaHttpEventV2(event);
|
|
626
638
|
} catch {
|
|
627
|
-
await streamResponseToLambda(
|
|
628
|
-
invalidAwsLambdaHttpEventV2Response(),
|
|
629
|
-
responseStream,
|
|
630
|
-
runtime,
|
|
631
|
-
);
|
|
639
|
+
await streamResponseToLambda(invalidAwsLambdaHttpEventV2Response(), responseStream, runtime);
|
|
632
640
|
return;
|
|
633
641
|
}
|
|
634
642
|
|
|
635
643
|
const startedAt = logNow();
|
|
636
644
|
const phases = createAwsLambdaTimingPhases(options);
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
emitRouterLog(options.logger, "info", {
|
|
642
|
-
...logFields,
|
|
643
|
-
type: "router:request:start",
|
|
644
|
-
});
|
|
645
|
+
const requestAbortController = new AbortController();
|
|
646
|
+
let request: Request | undefined;
|
|
647
|
+
let requestLifecycle: ReturnType<typeof observeLambdaStreamFailure> | undefined;
|
|
648
|
+
let logFields = awsLambdaEventLogFields(event);
|
|
645
649
|
|
|
646
650
|
try {
|
|
651
|
+
const eventToRequestStartedAt = phaseStartedAt(phases);
|
|
652
|
+
request = eventToRequest(event, options, requestAbortController.signal);
|
|
653
|
+
requestLifecycle = observeLambdaStreamFailure(responseStream, requestAbortController);
|
|
654
|
+
finishPhase(phases, eventToRequestStartedAt, "eventToRequestMs");
|
|
655
|
+
logFields = requestLogFields(request, "aws-lambda");
|
|
656
|
+
emitRouterLog(options.logger, "info", {
|
|
657
|
+
...logFields,
|
|
658
|
+
type: "router:request:start",
|
|
659
|
+
});
|
|
647
660
|
const runtimeDirStartedAt = phaseStartedAt(phases);
|
|
648
661
|
const runtimeDir = await runtimeDirPromise;
|
|
649
662
|
finishPhase(phases, runtimeDirStartedAt, "runtimeDirMs");
|
|
@@ -661,12 +674,9 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
|
|
|
661
674
|
outDir: options.outDir,
|
|
662
675
|
runtimeDir,
|
|
663
676
|
});
|
|
664
|
-
const preload = awsLambdaRenderPreload(
|
|
665
|
-
options,
|
|
666
|
-
runtimePreloadPromise,
|
|
667
|
-
defaultPreloadMode,
|
|
668
|
-
);
|
|
677
|
+
const preload = awsLambdaRenderPreload(options, runtimePreloadPromise, defaultPreloadMode);
|
|
669
678
|
const response = await builtRuntime.render(request, {
|
|
679
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
670
680
|
instrumentation: options.instrumentation,
|
|
671
681
|
logger: awsLambdaRenderLogger(options),
|
|
672
682
|
onResponse: options.onResponse,
|
|
@@ -678,7 +688,13 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
|
|
|
678
688
|
});
|
|
679
689
|
finishPhase(phases, renderStartedAt, "renderMs");
|
|
680
690
|
const responseStreamingStartedAt = phaseStartedAt(phases);
|
|
681
|
-
await streamResponseToLambda(
|
|
691
|
+
await streamResponseToLambda(
|
|
692
|
+
response,
|
|
693
|
+
responseStream,
|
|
694
|
+
runtime,
|
|
695
|
+
phases,
|
|
696
|
+
requestAbortController,
|
|
697
|
+
);
|
|
682
698
|
finishPhase(phases, responseStreamingStartedAt, "responseStreamingMs");
|
|
683
699
|
emitRouterLog(options.logger, "info", {
|
|
684
700
|
...logFields,
|
|
@@ -695,6 +711,10 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
|
|
|
695
711
|
type: "router:request:error",
|
|
696
712
|
});
|
|
697
713
|
|
|
714
|
+
if (requestAbortController.signal.aborted) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
698
718
|
const payload = options.errorHandler
|
|
699
719
|
? options.errorHandler(error)
|
|
700
720
|
: { body: "Internal Server Error", status: 500 };
|
|
@@ -710,7 +730,15 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
|
|
|
710
730
|
request,
|
|
711
731
|
);
|
|
712
732
|
|
|
713
|
-
await streamResponseToLambda(
|
|
733
|
+
await streamResponseToLambda(
|
|
734
|
+
response,
|
|
735
|
+
responseStream,
|
|
736
|
+
runtime,
|
|
737
|
+
phases,
|
|
738
|
+
requestAbortController,
|
|
739
|
+
);
|
|
740
|
+
} finally {
|
|
741
|
+
requestLifecycle?.dispose();
|
|
714
742
|
}
|
|
715
743
|
});
|
|
716
744
|
}
|
|
@@ -718,8 +746,12 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime<TContext = unknown>(
|
|
|
718
746
|
async function applyAwsLambdaResponseHook(
|
|
719
747
|
response: Response,
|
|
720
748
|
options: Pick<AwsLambdaRequestHandlerOptions, "onResponse">,
|
|
721
|
-
request: Request,
|
|
749
|
+
request: Request | undefined,
|
|
722
750
|
): Promise<Response> {
|
|
751
|
+
if (request === undefined) {
|
|
752
|
+
return response;
|
|
753
|
+
}
|
|
754
|
+
|
|
723
755
|
const hooked = await options.onResponse?.(response, { request });
|
|
724
756
|
|
|
725
757
|
return hooked instanceof Response ? hooked : response;
|
|
@@ -798,6 +830,7 @@ function isNodeErrorCode(error: unknown, code: string): boolean {
|
|
|
798
830
|
function eventToRequest(
|
|
799
831
|
event: AwsLambdaHttpEventV2,
|
|
800
832
|
options: AwsLambdaRequestHandlerOptions,
|
|
833
|
+
signal?: AbortSignal,
|
|
801
834
|
): Request {
|
|
802
835
|
validateAwsLambdaHttpEventV2(event);
|
|
803
836
|
const headers = eventHeaders(event);
|
|
@@ -808,21 +841,67 @@ function eventToRequest(
|
|
|
808
841
|
hostPolicy: lambdaHostPolicy(options),
|
|
809
842
|
rawHost: rawHost ?? undefined,
|
|
810
843
|
});
|
|
811
|
-
const protocol = lambdaRequestProtocol(
|
|
812
|
-
const rawPath = event.rawPath;
|
|
813
|
-
const rawQueryString = event.rawQueryString === "" ? "" : `?${event.rawQueryString}`;
|
|
844
|
+
const protocol = lambdaRequestProtocol(headers, options);
|
|
814
845
|
const method = event.requestContext.http.method;
|
|
815
846
|
const init: RequestInit = {
|
|
816
847
|
headers,
|
|
817
848
|
method,
|
|
849
|
+
...(signal === undefined ? {} : { signal }),
|
|
818
850
|
};
|
|
819
851
|
|
|
820
852
|
if (method !== "GET" && method !== "HEAD" && event.body !== undefined) {
|
|
821
|
-
init.body =
|
|
822
|
-
|
|
853
|
+
init.body = event.isBase64Encoded === true ? Buffer.from(event.body, "base64") : event.body;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return new Request(lambdaRequestUrl(protocol, host, event.rawPath, event.rawQueryString), init);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function lambdaRequestUrl(
|
|
860
|
+
protocol: "http" | "https",
|
|
861
|
+
host: string,
|
|
862
|
+
rawPath: string,
|
|
863
|
+
rawQueryString: string,
|
|
864
|
+
): URL {
|
|
865
|
+
if (hasInvalidLambdaRequestAuthorityCharacter(host)) {
|
|
866
|
+
throw new TypeError("AWS Lambda request Host is not a valid URL authority");
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const url = new URL(`${protocol}://${host}`);
|
|
870
|
+
if (
|
|
871
|
+
url.protocol !== `${protocol}:` ||
|
|
872
|
+
url.host === "" ||
|
|
873
|
+
url.username !== "" ||
|
|
874
|
+
url.password !== "" ||
|
|
875
|
+
url.pathname !== "/" ||
|
|
876
|
+
url.search !== "" ||
|
|
877
|
+
url.hash !== ""
|
|
878
|
+
) {
|
|
879
|
+
throw new TypeError("AWS Lambda request Host is not a valid URL authority");
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
url.pathname = rawPath;
|
|
883
|
+
url.search = rawQueryString;
|
|
884
|
+
return url;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function hasInvalidLambdaRequestAuthorityCharacter(host: string): boolean {
|
|
888
|
+
for (const character of host) {
|
|
889
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
890
|
+
if (
|
|
891
|
+
character === "/" ||
|
|
892
|
+
character === "?" ||
|
|
893
|
+
character === "#" ||
|
|
894
|
+
character === "\\" ||
|
|
895
|
+
character === "@" ||
|
|
896
|
+
character.trim() === "" ||
|
|
897
|
+
codePoint < 0x20 ||
|
|
898
|
+
codePoint === 0x7f
|
|
899
|
+
) {
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
823
902
|
}
|
|
824
903
|
|
|
825
|
-
return
|
|
904
|
+
return false;
|
|
826
905
|
}
|
|
827
906
|
|
|
828
907
|
function validateAwsLambdaHttpEventV2(event: AwsLambdaHttpEventV2): void {
|
|
@@ -837,10 +916,12 @@ function validateAwsLambdaHttpEventV2(event: AwsLambdaHttpEventV2): void {
|
|
|
837
916
|
typeof (candidate as { rawQueryString?: unknown }).rawQueryString !== "string" ||
|
|
838
917
|
typeof (candidate as { requestContext?: unknown }).requestContext !== "object" ||
|
|
839
918
|
(candidate as { requestContext?: unknown }).requestContext === null ||
|
|
840
|
-
typeof (
|
|
919
|
+
typeof (candidate as { requestContext: { http?: unknown } }).requestContext.http !== "object" ||
|
|
841
920
|
(candidate as { requestContext: { http?: unknown } }).requestContext.http === null ||
|
|
842
|
-
typeof (
|
|
843
|
-
|
|
921
|
+
typeof (candidate as { requestContext: { http: { method?: unknown } } }).requestContext.http
|
|
922
|
+
.method !== "string" ||
|
|
923
|
+
(candidate as { requestContext: { http: { method: string } } }).requestContext.http.method ===
|
|
924
|
+
""
|
|
844
925
|
) {
|
|
845
926
|
throw new Error(invalidAwsLambdaHttpEventV2Diagnostic);
|
|
846
927
|
}
|
|
@@ -857,14 +938,27 @@ function lambdaRequestHost(
|
|
|
857
938
|
return headers.get("host");
|
|
858
939
|
}
|
|
859
940
|
|
|
860
|
-
function lambdaHostPolicy(
|
|
861
|
-
options: AwsLambdaRequestHandlerOptions,
|
|
862
|
-
): RequestHostPolicy | undefined {
|
|
941
|
+
function lambdaHostPolicy(options: AwsLambdaRequestHandlerOptions): RequestHostPolicy | undefined {
|
|
863
942
|
return options.hostPolicy ?? (process.env.NODE_ENV === "production" ? "strict" : undefined);
|
|
864
943
|
}
|
|
865
944
|
|
|
945
|
+
function warnIfImplicitAwsLambdaHostPolicy(options: AwsLambdaRequestHandlerOptions): void {
|
|
946
|
+
if (
|
|
947
|
+
process.env.NODE_ENV !== "production" ||
|
|
948
|
+
options.allowedHosts !== undefined ||
|
|
949
|
+
options.hostPolicy !== undefined ||
|
|
950
|
+
warnedImplicitAwsLambdaHostPolicy
|
|
951
|
+
) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
warnedImplicitAwsLambdaHostPolicy = true;
|
|
956
|
+
console.error(
|
|
957
|
+
'[mreact] AWS Lambda Host handling defaults to strict because neither allowedHosts nor hostPolicy is configured. Unlisted Host headers use the configured hostname or lambda.local. Set allowedHosts for public deployments or hostPolicy: "trusted-proxy" when a trusted reverse proxy normalizes Host.',
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
|
|
866
961
|
function lambdaRequestProtocol(
|
|
867
|
-
event: AwsLambdaHttpEventV2,
|
|
868
962
|
headers: Headers,
|
|
869
963
|
options: AwsLambdaRequestHandlerOptions,
|
|
870
964
|
): "http" | "https" {
|
|
@@ -872,7 +966,7 @@ function lambdaRequestProtocol(
|
|
|
872
966
|
return normalizeRequestProtocol(firstForwardedValue(headers.get("x-forwarded-proto")));
|
|
873
967
|
}
|
|
874
968
|
|
|
875
|
-
return
|
|
969
|
+
return "https";
|
|
876
970
|
}
|
|
877
971
|
|
|
878
972
|
function normalizeRequestProtocol(value: string | undefined): "http" | "https" {
|
|
@@ -880,8 +974,15 @@ function normalizeRequestProtocol(value: string | undefined): "http" | "https" {
|
|
|
880
974
|
return "https";
|
|
881
975
|
}
|
|
882
976
|
|
|
883
|
-
|
|
884
|
-
|
|
977
|
+
return value.toLowerCase() === "http" ? "http" : "https";
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function awsLambdaEventLogFields(event: AwsLambdaHttpEventV2): RouterRequestLogFields {
|
|
981
|
+
return {
|
|
982
|
+
method: event.requestContext.http.method,
|
|
983
|
+
path: event.rawPath,
|
|
984
|
+
runtime: "aws-lambda",
|
|
985
|
+
};
|
|
885
986
|
}
|
|
886
987
|
|
|
887
988
|
function eventHeaders(event: AwsLambdaHttpEventV2): Headers {
|
|
@@ -929,7 +1030,7 @@ async function responseToLambdaResult(
|
|
|
929
1030
|
|
|
930
1031
|
const bodyEncodeStartedAt = phaseStartedAt(phases);
|
|
931
1032
|
const contentType = response.headers.get("content-type");
|
|
932
|
-
const text =
|
|
1033
|
+
const text = isUtf8TextContentType(contentType) && isUtf8(bytes);
|
|
933
1034
|
const result = {
|
|
934
1035
|
body: text ? bytes.toString("utf8") : bytes.toString("base64"),
|
|
935
1036
|
...(cookies.length === 0 ? {} : { cookies }),
|
|
@@ -1055,12 +1156,21 @@ async function streamResponseToLambda(
|
|
|
1055
1156
|
responseStream: AwsLambdaStreamingResponseStream,
|
|
1056
1157
|
runtime: AwsLambdaRuntime,
|
|
1057
1158
|
phases?: Record<string, number> | undefined,
|
|
1159
|
+
requestAbortController?: AbortController,
|
|
1058
1160
|
): Promise<void> {
|
|
1161
|
+
if (isLambdaStreamingAborted(requestAbortController)) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1059
1165
|
const stream = runtime.HttpResponseStream.from(
|
|
1060
1166
|
responseStream,
|
|
1061
1167
|
responseStreamingMetadata(response),
|
|
1062
1168
|
);
|
|
1063
1169
|
|
|
1170
|
+
if (isLambdaStreamingAborted(requestAbortController)) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1064
1174
|
try {
|
|
1065
1175
|
if (response.body === null) {
|
|
1066
1176
|
stream.end();
|
|
@@ -1068,22 +1178,46 @@ async function streamResponseToLambda(
|
|
|
1068
1178
|
}
|
|
1069
1179
|
|
|
1070
1180
|
const reader = response.body.getReader();
|
|
1181
|
+
const lifecycle = observeLambdaStreamFailure(stream, requestAbortController);
|
|
1071
1182
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1183
|
+
try {
|
|
1184
|
+
while (true) {
|
|
1185
|
+
const streamWaitStartedAt = phaseStartedAt(phases);
|
|
1186
|
+
const result = await readLambdaBodyWithAbort(reader, lifecycle.signal);
|
|
1187
|
+
addPhaseDuration(phases, streamWaitStartedAt, "streamWaitMs");
|
|
1188
|
+
|
|
1189
|
+
if (lifecycle.signal.aborted) {
|
|
1190
|
+
throw lambdaAbortReason(lifecycle.signal);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (result.done) {
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const streamWriteStartedAt = phaseStartedAt(phases);
|
|
1198
|
+
await writeStreamingChunk(stream, result.value, lifecycle.signal);
|
|
1199
|
+
addPhaseDuration(phases, streamWriteStartedAt, "streamWriteMs");
|
|
1079
1200
|
}
|
|
1080
1201
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1202
|
+
if (lifecycle.signal.aborted) {
|
|
1203
|
+
throw lambdaAbortReason(lifecycle.signal);
|
|
1204
|
+
}
|
|
1205
|
+
stream.end();
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
1208
|
+
requestAbortController?.abort(reason);
|
|
1209
|
+
try {
|
|
1210
|
+
await reader.cancel(reason);
|
|
1211
|
+
} catch {
|
|
1212
|
+
// The response body may already be errored or cancelled.
|
|
1213
|
+
}
|
|
1214
|
+
if (stream.destroyed !== true) {
|
|
1215
|
+
stream.destroy?.(reason);
|
|
1216
|
+
}
|
|
1217
|
+
} finally {
|
|
1218
|
+
lifecycle.dispose();
|
|
1219
|
+
reader.releaseLock();
|
|
1084
1220
|
}
|
|
1085
|
-
|
|
1086
|
-
stream.end();
|
|
1087
1221
|
} catch (error) {
|
|
1088
1222
|
if (typeof stream.destroy === "function") {
|
|
1089
1223
|
stream.destroy(error);
|
|
@@ -1094,9 +1228,11 @@ async function streamResponseToLambda(
|
|
|
1094
1228
|
}
|
|
1095
1229
|
}
|
|
1096
1230
|
|
|
1097
|
-
function
|
|
1098
|
-
|
|
1099
|
-
|
|
1231
|
+
function isLambdaStreamingAborted(controller: AbortController | undefined): boolean {
|
|
1232
|
+
return controller?.signal.aborted === true;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function responseStreamingMetadata(response: Response): AwsLambdaStreamingResponseMetadata {
|
|
1100
1236
|
const headers: Record<string, string> = {};
|
|
1101
1237
|
response.headers.forEach((value, key) => {
|
|
1102
1238
|
if (key !== "set-cookie") {
|
|
@@ -1116,8 +1252,18 @@ function responseStreamingMetadata(
|
|
|
1116
1252
|
async function writeStreamingChunk(
|
|
1117
1253
|
stream: AwsLambdaStreamingResponseStream,
|
|
1118
1254
|
chunk: Uint8Array,
|
|
1255
|
+
signal: AbortSignal,
|
|
1119
1256
|
): Promise<void> {
|
|
1120
|
-
if (
|
|
1257
|
+
if (signal.aborted) {
|
|
1258
|
+
throw lambdaAbortReason(signal);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
const accepted = stream.write(chunk);
|
|
1262
|
+
if (signal.aborted) {
|
|
1263
|
+
throw lambdaAbortReason(signal);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
if (accepted !== false) {
|
|
1121
1267
|
return;
|
|
1122
1268
|
}
|
|
1123
1269
|
|
|
@@ -1125,11 +1271,131 @@ async function writeStreamingChunk(
|
|
|
1125
1271
|
return;
|
|
1126
1272
|
}
|
|
1127
1273
|
|
|
1128
|
-
await new Promise<void>((resolve) => {
|
|
1129
|
-
|
|
1274
|
+
await new Promise<void>((resolve, reject) => {
|
|
1275
|
+
const dispose = (): void => {
|
|
1276
|
+
removeLambdaStreamListener(stream, "drain", onDrain);
|
|
1277
|
+
signal.removeEventListener("abort", onAbort);
|
|
1278
|
+
};
|
|
1279
|
+
const onDrain = (): void => {
|
|
1280
|
+
dispose();
|
|
1281
|
+
resolve();
|
|
1282
|
+
};
|
|
1283
|
+
const onAbort = (): void => {
|
|
1284
|
+
dispose();
|
|
1285
|
+
reject(lambdaAbortReason(signal));
|
|
1286
|
+
};
|
|
1287
|
+
addLambdaStreamListener(stream, "drain", onDrain);
|
|
1288
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function observeLambdaStreamFailure(
|
|
1293
|
+
stream: AwsLambdaStreamingResponseStream,
|
|
1294
|
+
abortController: AbortController = new AbortController(),
|
|
1295
|
+
): {
|
|
1296
|
+
dispose(): void;
|
|
1297
|
+
signal: AbortSignal;
|
|
1298
|
+
} {
|
|
1299
|
+
let disposed = false;
|
|
1300
|
+
|
|
1301
|
+
const dispose = (): void => {
|
|
1302
|
+
if (disposed) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
disposed = true;
|
|
1306
|
+
removeLambdaStreamListener(stream, "close", onClose);
|
|
1307
|
+
removeLambdaStreamListener(stream, "error", onError);
|
|
1308
|
+
};
|
|
1309
|
+
const abort = (reason: Error): void => {
|
|
1310
|
+
abortController.abort(reason);
|
|
1311
|
+
dispose();
|
|
1312
|
+
};
|
|
1313
|
+
const onClose = (): void => {
|
|
1314
|
+
if (stream.writableEnded === true) {
|
|
1315
|
+
dispose();
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
abort(new Error("The AWS Lambda response stream closed during streaming."));
|
|
1319
|
+
};
|
|
1320
|
+
const onError = (error?: unknown): void => {
|
|
1321
|
+
abort(error instanceof Error ? error : new Error(String(error ?? "AWS Lambda stream error")));
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
const canRemoveListeners =
|
|
1325
|
+
typeof stream.off === "function" || typeof stream.removeListener === "function";
|
|
1326
|
+
if (canRemoveListeners) {
|
|
1327
|
+
addLambdaStreamListener(stream, "close", onClose);
|
|
1328
|
+
addLambdaStreamListener(stream, "error", onError);
|
|
1329
|
+
}
|
|
1330
|
+
if (abortController.signal.aborted) {
|
|
1331
|
+
dispose();
|
|
1332
|
+
} else if (stream.destroyed === true && stream.writableEnded !== true) {
|
|
1333
|
+
onClose();
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
return { dispose, signal: abortController.signal };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function readLambdaBodyWithAbort(
|
|
1340
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
1341
|
+
signal: AbortSignal,
|
|
1342
|
+
): Promise<ReadableStreamReadResult<Uint8Array>> {
|
|
1343
|
+
if (signal.aborted) {
|
|
1344
|
+
return Promise.reject(lambdaAbortReason(signal));
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
return new Promise((resolve, reject) => {
|
|
1348
|
+
const onAbort = (): void => {
|
|
1349
|
+
signal.removeEventListener("abort", onAbort);
|
|
1350
|
+
reject(lambdaAbortReason(signal));
|
|
1351
|
+
};
|
|
1352
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1353
|
+
void reader.read().then(
|
|
1354
|
+
(result) => {
|
|
1355
|
+
signal.removeEventListener("abort", onAbort);
|
|
1356
|
+
resolve(result);
|
|
1357
|
+
},
|
|
1358
|
+
(error: unknown) => {
|
|
1359
|
+
signal.removeEventListener("abort", onAbort);
|
|
1360
|
+
reject(error);
|
|
1361
|
+
},
|
|
1362
|
+
);
|
|
1130
1363
|
});
|
|
1131
1364
|
}
|
|
1132
1365
|
|
|
1366
|
+
function removeLambdaStreamListener(
|
|
1367
|
+
stream: AwsLambdaStreamingResponseStream,
|
|
1368
|
+
event: "close" | "drain" | "error",
|
|
1369
|
+
listener: (error?: unknown) => void,
|
|
1370
|
+
): void {
|
|
1371
|
+
if (typeof stream.off === "function") {
|
|
1372
|
+
stream.off(event, listener);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
stream.removeListener?.(event, listener);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function addLambdaStreamListener(
|
|
1379
|
+
stream: AwsLambdaStreamingResponseStream,
|
|
1380
|
+
event: "close" | "drain" | "error",
|
|
1381
|
+
listener: (error?: unknown) => void,
|
|
1382
|
+
): void {
|
|
1383
|
+
const once = stream.once as
|
|
1384
|
+
| ((event: "close" | "drain" | "error", listener: (error?: unknown) => void) => unknown)
|
|
1385
|
+
| undefined;
|
|
1386
|
+
try {
|
|
1387
|
+
once?.call(stream, event, listener);
|
|
1388
|
+
} catch {
|
|
1389
|
+
// A structurally compatible drain-only stream may reject lifecycle event names.
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function lambdaAbortReason(signal: AbortSignal): Error {
|
|
1394
|
+
return signal.reason instanceof Error
|
|
1395
|
+
? signal.reason
|
|
1396
|
+
: new Error("AWS Lambda response streaming was aborted.");
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1133
1399
|
interface AwsLambdaRuntime {
|
|
1134
1400
|
HttpResponseStream: {
|
|
1135
1401
|
from(
|
|
@@ -1143,8 +1409,7 @@ interface AwsLambdaRuntime {
|
|
|
1143
1409
|
}
|
|
1144
1410
|
|
|
1145
1411
|
function awsLambdaRuntime(): AwsLambdaRuntime {
|
|
1146
|
-
const runtime = (globalThis as { awslambda?: Partial<AwsLambdaRuntime> | undefined })
|
|
1147
|
-
.awslambda;
|
|
1412
|
+
const runtime = (globalThis as { awslambda?: Partial<AwsLambdaRuntime> | undefined }).awslambda;
|
|
1148
1413
|
|
|
1149
1414
|
if (
|
|
1150
1415
|
typeof runtime?.streamifyResponse !== "function" ||
|
|
@@ -1202,13 +1467,14 @@ function splitSetCookieHeader(header: string): string[] {
|
|
|
1202
1467
|
return cookies.filter((cookie) => cookie.length > 0);
|
|
1203
1468
|
}
|
|
1204
1469
|
|
|
1205
|
-
function
|
|
1470
|
+
function isUtf8TextContentType(contentType: string | null): boolean {
|
|
1206
1471
|
if (contentType === null) {
|
|
1207
|
-
return
|
|
1472
|
+
return false;
|
|
1208
1473
|
}
|
|
1209
1474
|
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1475
|
+
const [rawType = "", ...parameters] = contentType.split(";");
|
|
1476
|
+
const type = rawType.trim().toLowerCase();
|
|
1477
|
+
const textual =
|
|
1212
1478
|
type.startsWith("text/") ||
|
|
1213
1479
|
type === "application/json" ||
|
|
1214
1480
|
type === "application/javascript" ||
|
|
@@ -1216,6 +1482,51 @@ function isTextContentType(contentType: string | null): boolean {
|
|
|
1216
1482
|
type === "application/xml" ||
|
|
1217
1483
|
type === "application/x-www-form-urlencoded" ||
|
|
1218
1484
|
type.endsWith("+json") ||
|
|
1219
|
-
type.endsWith("+xml")
|
|
1485
|
+
type.endsWith("+xml");
|
|
1486
|
+
if (!textual) {
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
let hasCharset = false;
|
|
1491
|
+
for (const parameter of parameters) {
|
|
1492
|
+
const equals = parameter.indexOf("=");
|
|
1493
|
+
const name = (equals === -1 ? parameter : parameter.slice(0, equals)).trim().toLowerCase();
|
|
1494
|
+
if (name !== "charset") {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
hasCharset = true;
|
|
1499
|
+
if (equals === -1) {
|
|
1500
|
+
return false;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
const value = unquoteContentTypeParameter(parameter.slice(equals + 1).trim()).toLowerCase();
|
|
1504
|
+
if (value !== "utf-8" && value !== "utf8") {
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
if (hasCharset) {
|
|
1510
|
+
return true;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
return (
|
|
1514
|
+
type === "application/json" ||
|
|
1515
|
+
type === "application/javascript" ||
|
|
1516
|
+
type === "application/x-javascript" ||
|
|
1517
|
+
type === "text/javascript" ||
|
|
1518
|
+
type.endsWith("+json")
|
|
1220
1519
|
);
|
|
1221
1520
|
}
|
|
1521
|
+
|
|
1522
|
+
function unquoteContentTypeParameter(value: string): string {
|
|
1523
|
+
if (value.length < 2) {
|
|
1524
|
+
return value;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const first = value[0];
|
|
1528
|
+
const last = value.at(-1);
|
|
1529
|
+
return (first === '"' && last === '"') || (first === "'" && last === "'")
|
|
1530
|
+
? value.slice(1, -1)
|
|
1531
|
+
: value;
|
|
1532
|
+
}
|