@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,12 +1,13 @@
|
|
|
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
6
|
import { normalizeBuiltAppRuntimePreloadStrategy, } from "../preload-policy.js";
|
|
7
7
|
import { emitRouterLog, logDurationMs, logError, logNow, requestLogFields, } from "../logger.js";
|
|
8
|
-
import { createBuiltRequestRuntime, preloadBuiltAppRuntime, resolveRequestHost,
|
|
9
|
-
|
|
8
|
+
import { createBuiltRequestRuntime, preloadBuiltAppRuntime, resolveRequestHost, } from "../serve.js";
|
|
9
|
+
let warnedImplicitAwsLambdaHostPolicy = false;
|
|
10
|
+
const invalidAwsLambdaHttpEventV2Diagnostic = "Expected an AWS Lambda HTTP API payload format 2.0 event with rawPath and requestContext.http.method.";
|
|
10
11
|
function invalidAwsLambdaHttpEventV2Result() {
|
|
11
12
|
return {
|
|
12
13
|
body: invalidAwsLambdaHttpEventV2Diagnostic,
|
|
@@ -27,7 +28,7 @@ function invalidAwsLambdaHttpEventV2Response() {
|
|
|
27
28
|
* The handler materializes runtime files under `/tmp` by default, starts middleware-focused background preload, applies the generated import policy when requested, and returns API Gateway v2 response objects.
|
|
28
29
|
*/
|
|
29
30
|
export function createAwsLambdaRequestHandler(options) {
|
|
30
|
-
|
|
31
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
31
32
|
let handler;
|
|
32
33
|
return async (event) => {
|
|
33
34
|
try {
|
|
@@ -51,7 +52,7 @@ export function createAwsLambdaRequestHandler(options) {
|
|
|
51
52
|
* Invalid events are rejected before runtime materialization or preload work begins.
|
|
52
53
|
*/
|
|
53
54
|
export async function createPreloadedAwsLambdaRequestHandler(options) {
|
|
54
|
-
|
|
55
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
55
56
|
let handler;
|
|
56
57
|
return async (event) => {
|
|
57
58
|
try {
|
|
@@ -75,7 +76,7 @@ export async function createPreloadedAwsLambdaRequestHandler(options) {
|
|
|
75
76
|
* 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.
|
|
76
77
|
*/
|
|
77
78
|
export async function warmAwsLambdaRuntime(options) {
|
|
78
|
-
|
|
79
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
79
80
|
const runtimeDir = await prepareAwsLambdaRuntimeDir(options);
|
|
80
81
|
await preloadAwsLambdaRuntime(options, runtimeDir);
|
|
81
82
|
}
|
|
@@ -89,15 +90,17 @@ function createAwsLambdaRequestHandlerFromRuntime(options, runtimeDirPromise, ru
|
|
|
89
90
|
}
|
|
90
91
|
const startedAt = logNow();
|
|
91
92
|
const phases = createAwsLambdaTimingPhases(options);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
finishPhase(phases, eventToRequestStartedAt, "eventToRequestMs");
|
|
95
|
-
const logFields = requestLogFields(request, "aws-lambda");
|
|
96
|
-
emitRouterLog(options.logger, "info", {
|
|
97
|
-
...logFields,
|
|
98
|
-
type: "router:request:start",
|
|
99
|
-
});
|
|
93
|
+
let request;
|
|
94
|
+
let logFields = awsLambdaEventLogFields(event);
|
|
100
95
|
try {
|
|
96
|
+
const eventToRequestStartedAt = phaseStartedAt(phases);
|
|
97
|
+
request = eventToRequest(event, options);
|
|
98
|
+
finishPhase(phases, eventToRequestStartedAt, "eventToRequestMs");
|
|
99
|
+
logFields = requestLogFields(request, "aws-lambda");
|
|
100
|
+
emitRouterLog(options.logger, "info", {
|
|
101
|
+
...logFields,
|
|
102
|
+
type: "router:request:start",
|
|
103
|
+
});
|
|
101
104
|
const runtimeDirStartedAt = phaseStartedAt(phases);
|
|
102
105
|
const runtimeDir = await runtimeDirPromise;
|
|
103
106
|
finishPhase(phases, runtimeDirStartedAt, "runtimeDirMs");
|
|
@@ -112,6 +115,7 @@ function createAwsLambdaRequestHandlerFromRuntime(options, runtimeDirPromise, ru
|
|
|
112
115
|
});
|
|
113
116
|
const preload = awsLambdaRenderPreload(options, runtimePreloadPromise, defaultPreloadMode);
|
|
114
117
|
const response = await builtRuntime.render(request, {
|
|
118
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
115
119
|
instrumentation: options.instrumentation,
|
|
116
120
|
logger: awsLambdaRenderLogger(options),
|
|
117
121
|
onResponse: options.onResponse,
|
|
@@ -161,7 +165,7 @@ function createAwsLambdaRequestHandlerFromRuntime(options, runtimeDirPromise, ru
|
|
|
161
165
|
* Use this with Lambda response streaming runtimes when HTML or route responses should be written progressively instead of buffered into an API Gateway result.
|
|
162
166
|
*/
|
|
163
167
|
export function createAwsLambdaStreamingRequestHandler(options) {
|
|
164
|
-
|
|
168
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
165
169
|
const runtime = awsLambdaRuntime();
|
|
166
170
|
let handler;
|
|
167
171
|
return runtime.streamifyResponse(async (event, responseStream, context) => {
|
|
@@ -187,7 +191,7 @@ export function createAwsLambdaStreamingRequestHandler(options) {
|
|
|
187
191
|
* Invalid events are rejected before runtime materialization or preload work begins.
|
|
188
192
|
*/
|
|
189
193
|
export async function createPreloadedAwsLambdaStreamingRequestHandler(options) {
|
|
190
|
-
|
|
194
|
+
warnIfImplicitAwsLambdaHostPolicy(options);
|
|
191
195
|
const runtime = awsLambdaRuntime();
|
|
192
196
|
let handler;
|
|
193
197
|
return runtime.streamifyResponse(async (event, responseStream, context) => {
|
|
@@ -307,15 +311,20 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime(options, runtime, run
|
|
|
307
311
|
}
|
|
308
312
|
const startedAt = logNow();
|
|
309
313
|
const phases = createAwsLambdaTimingPhases(options);
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
emitRouterLog(options.logger, "info", {
|
|
315
|
-
...logFields,
|
|
316
|
-
type: "router:request:start",
|
|
317
|
-
});
|
|
314
|
+
const requestAbortController = new AbortController();
|
|
315
|
+
let request;
|
|
316
|
+
let requestLifecycle;
|
|
317
|
+
let logFields = awsLambdaEventLogFields(event);
|
|
318
318
|
try {
|
|
319
|
+
const eventToRequestStartedAt = phaseStartedAt(phases);
|
|
320
|
+
request = eventToRequest(event, options, requestAbortController.signal);
|
|
321
|
+
requestLifecycle = observeLambdaStreamFailure(responseStream, requestAbortController);
|
|
322
|
+
finishPhase(phases, eventToRequestStartedAt, "eventToRequestMs");
|
|
323
|
+
logFields = requestLogFields(request, "aws-lambda");
|
|
324
|
+
emitRouterLog(options.logger, "info", {
|
|
325
|
+
...logFields,
|
|
326
|
+
type: "router:request:start",
|
|
327
|
+
});
|
|
319
328
|
const runtimeDirStartedAt = phaseStartedAt(phases);
|
|
320
329
|
const runtimeDir = await runtimeDirPromise;
|
|
321
330
|
finishPhase(phases, runtimeDirStartedAt, "runtimeDirMs");
|
|
@@ -330,6 +339,7 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime(options, runtime, run
|
|
|
330
339
|
});
|
|
331
340
|
const preload = awsLambdaRenderPreload(options, runtimePreloadPromise, defaultPreloadMode);
|
|
332
341
|
const response = await builtRuntime.render(request, {
|
|
342
|
+
dehydrateOptions: options.dehydrateOptions,
|
|
333
343
|
instrumentation: options.instrumentation,
|
|
334
344
|
logger: awsLambdaRenderLogger(options),
|
|
335
345
|
onResponse: options.onResponse,
|
|
@@ -341,7 +351,7 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime(options, runtime, run
|
|
|
341
351
|
});
|
|
342
352
|
finishPhase(phases, renderStartedAt, "renderMs");
|
|
343
353
|
const responseStreamingStartedAt = phaseStartedAt(phases);
|
|
344
|
-
await streamResponseToLambda(response, responseStream, runtime, phases);
|
|
354
|
+
await streamResponseToLambda(response, responseStream, runtime, phases, requestAbortController);
|
|
345
355
|
finishPhase(phases, responseStreamingStartedAt, "responseStreamingMs");
|
|
346
356
|
emitRouterLog(options.logger, "info", {
|
|
347
357
|
...logFields,
|
|
@@ -358,6 +368,9 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime(options, runtime, run
|
|
|
358
368
|
error: logError(error),
|
|
359
369
|
type: "router:request:error",
|
|
360
370
|
});
|
|
371
|
+
if (requestAbortController.signal.aborted) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
361
374
|
const payload = options.errorHandler
|
|
362
375
|
? options.errorHandler(error)
|
|
363
376
|
: { body: "Internal Server Error", status: 500 };
|
|
@@ -368,11 +381,17 @@ function createAwsLambdaStreamingRequestHandlerFromRuntime(options, runtime, run
|
|
|
368
381
|
},
|
|
369
382
|
status: payload.status,
|
|
370
383
|
}), options, request);
|
|
371
|
-
await streamResponseToLambda(response, responseStream, runtime, phases);
|
|
384
|
+
await streamResponseToLambda(response, responseStream, runtime, phases, requestAbortController);
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
requestLifecycle?.dispose();
|
|
372
388
|
}
|
|
373
389
|
});
|
|
374
390
|
}
|
|
375
391
|
async function applyAwsLambdaResponseHook(response, options, request) {
|
|
392
|
+
if (request === undefined) {
|
|
393
|
+
return response;
|
|
394
|
+
}
|
|
376
395
|
const hooked = await options.onResponse?.(response, { request });
|
|
377
396
|
return hooked instanceof Response ? hooked : response;
|
|
378
397
|
}
|
|
@@ -428,7 +447,7 @@ function isNodeErrorCode(error, code) {
|
|
|
428
447
|
"code" in error &&
|
|
429
448
|
error.code === code);
|
|
430
449
|
}
|
|
431
|
-
function eventToRequest(event, options) {
|
|
450
|
+
function eventToRequest(event, options, signal) {
|
|
432
451
|
validateAwsLambdaHttpEventV2(event);
|
|
433
452
|
const headers = eventHeaders(event);
|
|
434
453
|
const rawHost = lambdaRequestHost(headers, options);
|
|
@@ -438,19 +457,51 @@ function eventToRequest(event, options) {
|
|
|
438
457
|
hostPolicy: lambdaHostPolicy(options),
|
|
439
458
|
rawHost: rawHost ?? undefined,
|
|
440
459
|
});
|
|
441
|
-
const protocol = lambdaRequestProtocol(
|
|
442
|
-
const rawPath = event.rawPath;
|
|
443
|
-
const rawQueryString = event.rawQueryString === "" ? "" : `?${event.rawQueryString}`;
|
|
460
|
+
const protocol = lambdaRequestProtocol(headers, options);
|
|
444
461
|
const method = event.requestContext.http.method;
|
|
445
462
|
const init = {
|
|
446
463
|
headers,
|
|
447
464
|
method,
|
|
465
|
+
...(signal === undefined ? {} : { signal }),
|
|
448
466
|
};
|
|
449
467
|
if (method !== "GET" && method !== "HEAD" && event.body !== undefined) {
|
|
450
|
-
init.body =
|
|
451
|
-
|
|
468
|
+
init.body = event.isBase64Encoded === true ? Buffer.from(event.body, "base64") : event.body;
|
|
469
|
+
}
|
|
470
|
+
return new Request(lambdaRequestUrl(protocol, host, event.rawPath, event.rawQueryString), init);
|
|
471
|
+
}
|
|
472
|
+
function lambdaRequestUrl(protocol, host, rawPath, rawQueryString) {
|
|
473
|
+
if (hasInvalidLambdaRequestAuthorityCharacter(host)) {
|
|
474
|
+
throw new TypeError("AWS Lambda request Host is not a valid URL authority");
|
|
475
|
+
}
|
|
476
|
+
const url = new URL(`${protocol}://${host}`);
|
|
477
|
+
if (url.protocol !== `${protocol}:` ||
|
|
478
|
+
url.host === "" ||
|
|
479
|
+
url.username !== "" ||
|
|
480
|
+
url.password !== "" ||
|
|
481
|
+
url.pathname !== "/" ||
|
|
482
|
+
url.search !== "" ||
|
|
483
|
+
url.hash !== "") {
|
|
484
|
+
throw new TypeError("AWS Lambda request Host is not a valid URL authority");
|
|
485
|
+
}
|
|
486
|
+
url.pathname = rawPath;
|
|
487
|
+
url.search = rawQueryString;
|
|
488
|
+
return url;
|
|
489
|
+
}
|
|
490
|
+
function hasInvalidLambdaRequestAuthorityCharacter(host) {
|
|
491
|
+
for (const character of host) {
|
|
492
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
493
|
+
if (character === "/" ||
|
|
494
|
+
character === "?" ||
|
|
495
|
+
character === "#" ||
|
|
496
|
+
character === "\\" ||
|
|
497
|
+
character === "@" ||
|
|
498
|
+
character.trim() === "" ||
|
|
499
|
+
codePoint < 0x20 ||
|
|
500
|
+
codePoint === 0x7f) {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
452
503
|
}
|
|
453
|
-
return
|
|
504
|
+
return false;
|
|
454
505
|
}
|
|
455
506
|
function validateAwsLambdaHttpEventV2(event) {
|
|
456
507
|
const candidate = event;
|
|
@@ -462,10 +513,12 @@ function validateAwsLambdaHttpEventV2(event) {
|
|
|
462
513
|
typeof candidate.rawQueryString !== "string" ||
|
|
463
514
|
typeof candidate.requestContext !== "object" ||
|
|
464
515
|
candidate.requestContext === null ||
|
|
465
|
-
typeof
|
|
516
|
+
typeof candidate.requestContext.http !== "object" ||
|
|
466
517
|
candidate.requestContext.http === null ||
|
|
467
|
-
typeof
|
|
468
|
-
|
|
518
|
+
typeof candidate.requestContext.http
|
|
519
|
+
.method !== "string" ||
|
|
520
|
+
candidate.requestContext.http.method ===
|
|
521
|
+
"") {
|
|
469
522
|
throw new Error(invalidAwsLambdaHttpEventV2Diagnostic);
|
|
470
523
|
}
|
|
471
524
|
}
|
|
@@ -478,18 +531,34 @@ function lambdaRequestHost(headers, options) {
|
|
|
478
531
|
function lambdaHostPolicy(options) {
|
|
479
532
|
return options.hostPolicy ?? (process.env.NODE_ENV === "production" ? "strict" : undefined);
|
|
480
533
|
}
|
|
481
|
-
function
|
|
534
|
+
function warnIfImplicitAwsLambdaHostPolicy(options) {
|
|
535
|
+
if (process.env.NODE_ENV !== "production" ||
|
|
536
|
+
options.allowedHosts !== undefined ||
|
|
537
|
+
options.hostPolicy !== undefined ||
|
|
538
|
+
warnedImplicitAwsLambdaHostPolicy) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
warnedImplicitAwsLambdaHostPolicy = true;
|
|
542
|
+
console.error('[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.');
|
|
543
|
+
}
|
|
544
|
+
function lambdaRequestProtocol(headers, options) {
|
|
482
545
|
if (options.trustForwardedProto === true) {
|
|
483
546
|
return normalizeRequestProtocol(firstForwardedValue(headers.get("x-forwarded-proto")));
|
|
484
547
|
}
|
|
485
|
-
return
|
|
548
|
+
return "https";
|
|
486
549
|
}
|
|
487
550
|
function normalizeRequestProtocol(value) {
|
|
488
551
|
if (value === undefined || value === "") {
|
|
489
552
|
return "https";
|
|
490
553
|
}
|
|
491
|
-
|
|
492
|
-
|
|
554
|
+
return value.toLowerCase() === "http" ? "http" : "https";
|
|
555
|
+
}
|
|
556
|
+
function awsLambdaEventLogFields(event) {
|
|
557
|
+
return {
|
|
558
|
+
method: event.requestContext.http.method,
|
|
559
|
+
path: event.rawPath,
|
|
560
|
+
runtime: "aws-lambda",
|
|
561
|
+
};
|
|
493
562
|
}
|
|
494
563
|
function eventHeaders(event) {
|
|
495
564
|
const headers = new Headers();
|
|
@@ -525,7 +594,7 @@ async function responseToLambdaResult(response, phases) {
|
|
|
525
594
|
addPhaseDuration(phases, streamDrainStartedAt, "streamDrainMs");
|
|
526
595
|
const bodyEncodeStartedAt = phaseStartedAt(phases);
|
|
527
596
|
const contentType = response.headers.get("content-type");
|
|
528
|
-
const text =
|
|
597
|
+
const text = isUtf8TextContentType(contentType) && isUtf8(bytes);
|
|
529
598
|
const result = {
|
|
530
599
|
body: text ? bytes.toString("utf8") : bytes.toString("base64"),
|
|
531
600
|
...(cookies.length === 0 ? {} : { cookies }),
|
|
@@ -605,26 +674,58 @@ function emitAwsLambdaTiming(options, logFields, status, startedAt, phases) {
|
|
|
605
674
|
type: "router:request:timing",
|
|
606
675
|
});
|
|
607
676
|
}
|
|
608
|
-
async function streamResponseToLambda(response, responseStream, runtime, phases) {
|
|
677
|
+
async function streamResponseToLambda(response, responseStream, runtime, phases, requestAbortController) {
|
|
678
|
+
if (isLambdaStreamingAborted(requestAbortController)) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
609
681
|
const stream = runtime.HttpResponseStream.from(responseStream, responseStreamingMetadata(response));
|
|
682
|
+
if (isLambdaStreamingAborted(requestAbortController)) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
610
685
|
try {
|
|
611
686
|
if (response.body === null) {
|
|
612
687
|
stream.end();
|
|
613
688
|
return;
|
|
614
689
|
}
|
|
615
690
|
const reader = response.body.getReader();
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
691
|
+
const lifecycle = observeLambdaStreamFailure(stream, requestAbortController);
|
|
692
|
+
try {
|
|
693
|
+
while (true) {
|
|
694
|
+
const streamWaitStartedAt = phaseStartedAt(phases);
|
|
695
|
+
const result = await readLambdaBodyWithAbort(reader, lifecycle.signal);
|
|
696
|
+
addPhaseDuration(phases, streamWaitStartedAt, "streamWaitMs");
|
|
697
|
+
if (lifecycle.signal.aborted) {
|
|
698
|
+
throw lambdaAbortReason(lifecycle.signal);
|
|
699
|
+
}
|
|
700
|
+
if (result.done) {
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
const streamWriteStartedAt = phaseStartedAt(phases);
|
|
704
|
+
await writeStreamingChunk(stream, result.value, lifecycle.signal);
|
|
705
|
+
addPhaseDuration(phases, streamWriteStartedAt, "streamWriteMs");
|
|
706
|
+
}
|
|
707
|
+
if (lifecycle.signal.aborted) {
|
|
708
|
+
throw lambdaAbortReason(lifecycle.signal);
|
|
709
|
+
}
|
|
710
|
+
stream.end();
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
const reason = error instanceof Error ? error : new Error(String(error));
|
|
714
|
+
requestAbortController?.abort(reason);
|
|
715
|
+
try {
|
|
716
|
+
await reader.cancel(reason);
|
|
622
717
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
718
|
+
catch {
|
|
719
|
+
// The response body may already be errored or cancelled.
|
|
720
|
+
}
|
|
721
|
+
if (stream.destroyed !== true) {
|
|
722
|
+
stream.destroy?.(reason);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
finally {
|
|
726
|
+
lifecycle.dispose();
|
|
727
|
+
reader.releaseLock();
|
|
626
728
|
}
|
|
627
|
-
stream.end();
|
|
628
729
|
}
|
|
629
730
|
catch (error) {
|
|
630
731
|
if (typeof stream.destroy === "function") {
|
|
@@ -634,6 +735,9 @@ async function streamResponseToLambda(response, responseStream, runtime, phases)
|
|
|
634
735
|
throw error;
|
|
635
736
|
}
|
|
636
737
|
}
|
|
738
|
+
function isLambdaStreamingAborted(controller) {
|
|
739
|
+
return controller?.signal.aborted === true;
|
|
740
|
+
}
|
|
637
741
|
function responseStreamingMetadata(response) {
|
|
638
742
|
const headers = {};
|
|
639
743
|
response.headers.forEach((value, key) => {
|
|
@@ -648,20 +752,116 @@ function responseStreamingMetadata(response) {
|
|
|
648
752
|
statusCode: response.status,
|
|
649
753
|
};
|
|
650
754
|
}
|
|
651
|
-
async function writeStreamingChunk(stream, chunk) {
|
|
652
|
-
if (
|
|
755
|
+
async function writeStreamingChunk(stream, chunk, signal) {
|
|
756
|
+
if (signal.aborted) {
|
|
757
|
+
throw lambdaAbortReason(signal);
|
|
758
|
+
}
|
|
759
|
+
const accepted = stream.write(chunk);
|
|
760
|
+
if (signal.aborted) {
|
|
761
|
+
throw lambdaAbortReason(signal);
|
|
762
|
+
}
|
|
763
|
+
if (accepted !== false) {
|
|
653
764
|
return;
|
|
654
765
|
}
|
|
655
766
|
if (typeof stream.once !== "function") {
|
|
656
767
|
return;
|
|
657
768
|
}
|
|
658
|
-
await new Promise((resolve) => {
|
|
659
|
-
|
|
769
|
+
await new Promise((resolve, reject) => {
|
|
770
|
+
const dispose = () => {
|
|
771
|
+
removeLambdaStreamListener(stream, "drain", onDrain);
|
|
772
|
+
signal.removeEventListener("abort", onAbort);
|
|
773
|
+
};
|
|
774
|
+
const onDrain = () => {
|
|
775
|
+
dispose();
|
|
776
|
+
resolve();
|
|
777
|
+
};
|
|
778
|
+
const onAbort = () => {
|
|
779
|
+
dispose();
|
|
780
|
+
reject(lambdaAbortReason(signal));
|
|
781
|
+
};
|
|
782
|
+
addLambdaStreamListener(stream, "drain", onDrain);
|
|
783
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
660
784
|
});
|
|
661
785
|
}
|
|
786
|
+
function observeLambdaStreamFailure(stream, abortController = new AbortController()) {
|
|
787
|
+
let disposed = false;
|
|
788
|
+
const dispose = () => {
|
|
789
|
+
if (disposed) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
disposed = true;
|
|
793
|
+
removeLambdaStreamListener(stream, "close", onClose);
|
|
794
|
+
removeLambdaStreamListener(stream, "error", onError);
|
|
795
|
+
};
|
|
796
|
+
const abort = (reason) => {
|
|
797
|
+
abortController.abort(reason);
|
|
798
|
+
dispose();
|
|
799
|
+
};
|
|
800
|
+
const onClose = () => {
|
|
801
|
+
if (stream.writableEnded === true) {
|
|
802
|
+
dispose();
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
abort(new Error("The AWS Lambda response stream closed during streaming."));
|
|
806
|
+
};
|
|
807
|
+
const onError = (error) => {
|
|
808
|
+
abort(error instanceof Error ? error : new Error(String(error ?? "AWS Lambda stream error")));
|
|
809
|
+
};
|
|
810
|
+
const canRemoveListeners = typeof stream.off === "function" || typeof stream.removeListener === "function";
|
|
811
|
+
if (canRemoveListeners) {
|
|
812
|
+
addLambdaStreamListener(stream, "close", onClose);
|
|
813
|
+
addLambdaStreamListener(stream, "error", onError);
|
|
814
|
+
}
|
|
815
|
+
if (abortController.signal.aborted) {
|
|
816
|
+
dispose();
|
|
817
|
+
}
|
|
818
|
+
else if (stream.destroyed === true && stream.writableEnded !== true) {
|
|
819
|
+
onClose();
|
|
820
|
+
}
|
|
821
|
+
return { dispose, signal: abortController.signal };
|
|
822
|
+
}
|
|
823
|
+
function readLambdaBodyWithAbort(reader, signal) {
|
|
824
|
+
if (signal.aborted) {
|
|
825
|
+
return Promise.reject(lambdaAbortReason(signal));
|
|
826
|
+
}
|
|
827
|
+
return new Promise((resolve, reject) => {
|
|
828
|
+
const onAbort = () => {
|
|
829
|
+
signal.removeEventListener("abort", onAbort);
|
|
830
|
+
reject(lambdaAbortReason(signal));
|
|
831
|
+
};
|
|
832
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
833
|
+
void reader.read().then((result) => {
|
|
834
|
+
signal.removeEventListener("abort", onAbort);
|
|
835
|
+
resolve(result);
|
|
836
|
+
}, (error) => {
|
|
837
|
+
signal.removeEventListener("abort", onAbort);
|
|
838
|
+
reject(error);
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
function removeLambdaStreamListener(stream, event, listener) {
|
|
843
|
+
if (typeof stream.off === "function") {
|
|
844
|
+
stream.off(event, listener);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
stream.removeListener?.(event, listener);
|
|
848
|
+
}
|
|
849
|
+
function addLambdaStreamListener(stream, event, listener) {
|
|
850
|
+
const once = stream.once;
|
|
851
|
+
try {
|
|
852
|
+
once?.call(stream, event, listener);
|
|
853
|
+
}
|
|
854
|
+
catch {
|
|
855
|
+
// A structurally compatible drain-only stream may reject lifecycle event names.
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function lambdaAbortReason(signal) {
|
|
859
|
+
return signal.reason instanceof Error
|
|
860
|
+
? signal.reason
|
|
861
|
+
: new Error("AWS Lambda response streaming was aborted.");
|
|
862
|
+
}
|
|
662
863
|
function awsLambdaRuntime() {
|
|
663
|
-
const runtime = globalThis
|
|
664
|
-
.awslambda;
|
|
864
|
+
const runtime = globalThis.awslambda;
|
|
665
865
|
if (typeof runtime?.streamifyResponse !== "function" ||
|
|
666
866
|
typeof runtime.HttpResponseStream?.from !== "function") {
|
|
667
867
|
throw new Error("AWS Lambda response streaming requires the Node.js Lambda runtime awslambda.streamifyResponse() and awslambda.HttpResponseStream.from().");
|
|
@@ -704,18 +904,56 @@ function splitSetCookieHeader(header) {
|
|
|
704
904
|
cookies.push(header.slice(start).trim());
|
|
705
905
|
return cookies.filter((cookie) => cookie.length > 0);
|
|
706
906
|
}
|
|
707
|
-
function
|
|
907
|
+
function isUtf8TextContentType(contentType) {
|
|
708
908
|
if (contentType === null) {
|
|
709
|
-
return
|
|
909
|
+
return false;
|
|
710
910
|
}
|
|
711
|
-
const
|
|
712
|
-
|
|
911
|
+
const [rawType = "", ...parameters] = contentType.split(";");
|
|
912
|
+
const type = rawType.trim().toLowerCase();
|
|
913
|
+
const textual = type.startsWith("text/") ||
|
|
713
914
|
type === "application/json" ||
|
|
714
915
|
type === "application/javascript" ||
|
|
715
916
|
type === "application/x-javascript" ||
|
|
716
917
|
type === "application/xml" ||
|
|
717
918
|
type === "application/x-www-form-urlencoded" ||
|
|
718
919
|
type.endsWith("+json") ||
|
|
719
|
-
type.endsWith("+xml")
|
|
920
|
+
type.endsWith("+xml");
|
|
921
|
+
if (!textual) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
let hasCharset = false;
|
|
925
|
+
for (const parameter of parameters) {
|
|
926
|
+
const equals = parameter.indexOf("=");
|
|
927
|
+
const name = (equals === -1 ? parameter : parameter.slice(0, equals)).trim().toLowerCase();
|
|
928
|
+
if (name !== "charset") {
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
hasCharset = true;
|
|
932
|
+
if (equals === -1) {
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
935
|
+
const value = unquoteContentTypeParameter(parameter.slice(equals + 1).trim()).toLowerCase();
|
|
936
|
+
if (value !== "utf-8" && value !== "utf8") {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (hasCharset) {
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
return (type === "application/json" ||
|
|
944
|
+
type === "application/javascript" ||
|
|
945
|
+
type === "application/x-javascript" ||
|
|
946
|
+
type === "text/javascript" ||
|
|
947
|
+
type.endsWith("+json"));
|
|
948
|
+
}
|
|
949
|
+
function unquoteContentTypeParameter(value) {
|
|
950
|
+
if (value.length < 2) {
|
|
951
|
+
return value;
|
|
952
|
+
}
|
|
953
|
+
const first = value[0];
|
|
954
|
+
const last = value.at(-1);
|
|
955
|
+
return (first === '"' && last === '"') || (first === "'" && last === "'")
|
|
956
|
+
? value.slice(1, -1)
|
|
957
|
+
: value;
|
|
720
958
|
}
|
|
721
959
|
//# sourceMappingURL=aws-lambda.js.map
|