lambder 6.0.2 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2316 -0
- package/README.md +60 -33
- package/dist/api/LambderApiAnswer.d.ts +40 -0
- package/dist/api/LambderApiAnswer.js +19 -0
- package/dist/api/LambderApiCallContext.d.ts +38 -0
- package/dist/api/LambderApiCallContext.js +13 -0
- package/dist/api/LambderApiDefinition.d.ts +18 -0
- package/dist/api/LambderApiDefinition.js +1 -0
- package/dist/api/LambderApiEnvelope.d.ts +67 -0
- package/dist/api/LambderApiEnvelope.js +180 -0
- package/dist/api/LambderApiGuards.d.ts +302 -0
- package/dist/api/LambderApiGuards.js +134 -0
- package/dist/api/LambderApiIdempotency.d.ts +122 -0
- package/dist/api/LambderApiIdempotency.js +330 -0
- package/dist/api/LambderApiPipeline.d.ts +134 -0
- package/dist/api/LambderApiPipeline.js +221 -0
- package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
- package/dist/api/LambderApiPolicyEngine.js +77 -0
- package/dist/api/LambderApiRateLimits.d.ts +206 -0
- package/dist/api/LambderApiRateLimits.js +239 -0
- package/dist/api/LambderApiRequest.d.ts +101 -0
- package/dist/api/LambderApiRequest.js +129 -0
- package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
- package/dist/api/LambderApiValidationRefusal.js +40 -0
- package/dist/client/LambderCaller.d.ts +62 -55
- package/dist/client/LambderCaller.js +147 -90
- package/dist/client/lambderFetchTransport.d.ts +9 -0
- package/dist/client/lambderFetchTransport.js +71 -0
- package/dist/client.d.ts +20 -10
- package/dist/client.js +11 -5
- package/dist/core/Lambder.d.ts +117 -253
- package/dist/core/Lambder.js +374 -341
- package/dist/core/LambderContext.d.ts +54 -44
- package/dist/core/LambderContext.js +41 -110
- package/dist/core/LambderCreateOptions.d.ts +285 -0
- package/dist/core/LambderCreateOptions.js +44 -0
- package/dist/core/LambderFiles.d.ts +1 -45
- package/dist/core/LambderFiles.js +18 -38
- package/dist/core/LambderIndexHtml.d.ts +37 -0
- package/dist/core/LambderIndexHtml.js +87 -0
- package/dist/core/LambderPolicyBuilders.d.ts +17 -0
- package/dist/core/LambderPolicyBuilders.js +16 -0
- package/dist/core/LambderPublicFiles.d.ts +5 -2
- package/dist/core/LambderPublicFiles.js +7 -2
- package/dist/core/LambderResolver.d.ts +8 -6
- package/dist/core/LambderResponse.d.ts +29 -11
- package/dist/core/LambderResponse.js +96 -49
- package/dist/core/LambderResponseBuilder.d.ts +18 -14
- package/dist/core/LambderResponseBuilder.js +19 -25
- package/dist/core/LambderRouting.d.ts +18 -7
- package/dist/core/LambderRouting.js +17 -7
- package/dist/core/LambderTemplatingEngine.d.ts +0 -62
- package/dist/core/LambderTemplatingEngine.js +7 -3
- package/dist/index.d.ts +85 -32
- package/dist/index.js +44 -16
- package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
- package/dist/invoke/LambderInvokeCaller.js +140 -335
- package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
- package/dist/invoke/LambderInvokeOutcome.js +129 -0
- package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
- package/dist/invoke/LambderLambdaEvent.js +187 -0
- package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
- package/dist/invoke/lambderHandlerTransport.js +89 -0
- package/dist/mock/LambderMockApp.d.ts +352 -0
- package/dist/mock/LambderMockApp.js +815 -0
- package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
- package/dist/mock/LambderMockBrowserCookies.js +76 -0
- package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
- package/dist/mock/LambderMockCallRecorder.js +183 -0
- package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
- package/dist/mock/LambderMockCreateOptions.js +9 -0
- package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
- package/dist/mock/LambderMockEntryRegistry.js +126 -0
- package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
- package/dist/mock/LambderMockFailureInjector.js +138 -0
- package/dist/mock/LambderMockTypes.d.ts +421 -0
- package/dist/mock/LambderMockTypes.js +8 -0
- package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
- package/dist/mock/lambderMockConsoleLogger.js +35 -0
- package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
- package/dist/mock/lambderMockInvokeTransport.js +52 -0
- package/dist/mock/lambderMockMswHandler.d.ts +99 -0
- package/dist/mock/lambderMockMswHandler.js +126 -0
- package/dist/mock.d.ts +34 -0
- package/dist/mock.js +27 -0
- package/dist/session/LambderSessionController.d.ts +199 -30
- package/dist/session/LambderSessionController.js +396 -82
- package/dist/session/LambderSessionCrypto.d.ts +66 -0
- package/dist/session/LambderSessionCrypto.js +101 -0
- package/dist/session/LambderSessionManager.d.ts +118 -80
- package/dist/session/LambderSessionManager.js +212 -184
- package/dist/shared/LambderI18n.d.ts +6 -6
- package/dist/shared/LambderI18n.js +1 -1
- package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
- package/dist/shared/contracts/LambderFileSource.js +19 -0
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
- package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
- package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
- package/dist/shared/contracts/LambderRateLimiter.js +24 -0
- package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
- package/dist/shared/contracts/LambderSessionStore.js +13 -0
- package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
- package/dist/shared/transport/LambderApiTransport.js +65 -0
- package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
- package/dist/shared/transport/LambderCookieJar.js +246 -0
- package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
- package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
- package/dist/shared/util/LambderBase64.d.ts +10 -0
- package/dist/shared/util/LambderBase64.js +27 -0
- package/dist/shared/util/LambderCallAbort.d.ts +62 -0
- package/dist/shared/util/LambderCallAbort.js +80 -0
- package/dist/shared/util/LambderClientIp.d.ts +32 -0
- package/dist/shared/util/LambderClientIp.js +56 -0
- package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
- package/dist/shared/util/LambderExpiringMap.js +217 -0
- package/dist/shared/util/LambderKeyFields.d.ts +32 -0
- package/dist/shared/util/LambderKeyFields.js +34 -0
- package/dist/shared/util/LambderNodeModules.d.ts +9 -0
- package/dist/shared/util/LambderNodeModules.js +39 -0
- package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
- package/dist/shared/util/LambderOptionChecks.js +33 -0
- package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
- package/dist/shared/util/LambderResponseBrand.js +18 -0
- package/dist/shared/util/LambderTextDigest.d.ts +17 -0
- package/dist/shared/util/LambderTextDigest.js +34 -0
- package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
- package/dist/shared/util/LambderTypeUtilities.js +8 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
- package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
- package/dist/shared/wire/LambderApiContract.d.ts +129 -0
- package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
- package/dist/shared/wire/LambderApiOptionValues.js +11 -0
- package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
- package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
- package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
- package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
- package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
- package/dist/shared/wire/LambderCallOptions.js +17 -0
- package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
- package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
- package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
- package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
- package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
- package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
- package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
- package/dist/shared/wire/LambderHttpStatus.js +1 -0
- package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
- package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
- package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
- package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
- package/dist/stores/LambderDdbCache.d.ts +12 -9
- package/dist/stores/LambderDdbCache.js +56 -47
- package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
- package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
- package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
- package/dist/stores/LambderDdbRateLimiter.js +47 -45
- package/dist/stores/LambderDdbSdk.d.ts +83 -6
- package/dist/stores/LambderDdbSdk.js +83 -2
- package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
- package/dist/stores/LambderDdbSessionStore.js +161 -0
- package/dist/stores/LambderHttpFileSource.d.ts +1 -1
- package/dist/stores/LambderHttpFileSource.js +10 -1
- package/dist/stores/LambderLocalFileSource.d.ts +15 -0
- package/dist/stores/LambderLocalFileSource.js +28 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
- package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
- package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
- package/dist/stores/LambderMemoryRateLimiter.js +64 -0
- package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
- package/dist/stores/LambderMemorySessionStore.js +74 -0
- package/dist/stores/LambderS3FileSource.d.ts +1 -1
- package/dist/stores/LambderS3FileSource.js +1 -1
- package/package.json +21 -19
- package/dist/client/LambderMSW.d.ts +0 -69
- package/dist/client/LambderMSW.js +0 -121
- package/dist/policies/LambderApiGuards.d.ts +0 -256
- package/dist/policies/LambderApiGuards.js +0 -94
- package/dist/policies/LambderApiIdempotency.d.ts +0 -58
- package/dist/policies/LambderApiIdempotency.js +0 -219
- package/dist/policies/LambderApiPolicies.d.ts +0 -42
- package/dist/policies/LambderApiPolicies.js +0 -52
- package/dist/policies/LambderApiRateLimits.d.ts +0 -132
- package/dist/policies/LambderApiRateLimits.js +0 -119
- package/dist/shared/LambderApiContract.d.ts +0 -57
- package/dist/shared/LambderApiOutcome.d.ts +0 -69
- package/dist/shared/LambderCallOptions.d.ts +0 -71
- package/dist/shared/LambderCallOptions.js +0 -16
- package/dist/shared/node-polyfills.d.ts +0 -4
- package/dist/shared/node-polyfills.js +0 -58
- package/dist/stores/LambderDdbIdempotency.js +0 -229
- package/dist/testing.d.ts +0 -9
- package/dist/testing.js +0 -8
- /package/dist/shared/{LambderApiContract.js → wire/LambderApiContract.js} +0 -0
- /package/dist/{core → shared/wire}/LambderCookie.d.ts +0 -0
- /package/dist/{core → shared/wire}/LambderCookie.js +0 -0
|
@@ -1,27 +1,5 @@
|
|
|
1
1
|
import { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
|
|
2
|
-
|
|
3
|
-
export type LambderFile = {
|
|
4
|
-
body: Buffer;
|
|
5
|
-
mimeType?: string;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Where an app's files come from: the `files` option at creation, read by
|
|
9
|
-
* servePublicFiles, serveIndexHtml, res.file and res.templateFile alike,
|
|
10
|
-
* through the instance's one reader (LambderFiles). Implement `read` over
|
|
11
|
-
* any backing store: LambderLocalFileSource (a folder), LambderS3FileSource
|
|
12
|
-
* (S3, or R2 and other S3-compatible stores), LambderHttpFileSource (any
|
|
13
|
-
* origin serving files by path), or your own. The reader does the rest for
|
|
14
|
-
* every source: traversal check, memory cache, mime fallback from the
|
|
15
|
-
* extension.
|
|
16
|
-
*/
|
|
17
|
-
export interface LambderFileSource {
|
|
18
|
-
/**
|
|
19
|
-
* The file at a relative path (no leading slash, no ".." segments: the
|
|
20
|
-
* reader rejects those before calling), or null when there is no such
|
|
21
|
-
* file, which lets a request fall through to the route fallback.
|
|
22
|
-
*/
|
|
23
|
-
read(relativePath: string): Promise<LambderFile | null>;
|
|
24
|
-
}
|
|
2
|
+
import type { LambderFileSource } from "../shared/contracts/LambderFileSource.js";
|
|
25
3
|
/** In-memory cache of files for warm invocations. Default: { maxBytes: 32MB, maxFileBytes: 2MB }. false disables it. */
|
|
26
4
|
export type LambderFileMemoryCacheOption = false | {
|
|
27
5
|
maxBytes?: number;
|
|
@@ -38,28 +16,6 @@ export type LambderReadFile = {
|
|
|
38
16
|
mimeType: string;
|
|
39
17
|
relativePath: string;
|
|
40
18
|
};
|
|
41
|
-
/**
|
|
42
|
-
* Files from a folder on the Lambda's filesystem, typically the build output
|
|
43
|
-
* bundled into the deployment package. Reads stay under root.
|
|
44
|
-
*/
|
|
45
|
-
export declare class LambderLocalFileSource implements LambderFileSource {
|
|
46
|
-
private root;
|
|
47
|
-
constructor({ root }: {
|
|
48
|
-
root: string;
|
|
49
|
-
});
|
|
50
|
-
read(relativePath: string): Promise<LambderFile | null>;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* A file a remote store returned: the store's Content-Type unless it is a
|
|
54
|
-
* generic octet-stream, in which case the extension decides, as for local
|
|
55
|
-
* files.
|
|
56
|
-
*/
|
|
57
|
-
export declare const remoteStoreFile: (body: Buffer, contentType: string | null | undefined) => LambderFile;
|
|
58
|
-
/**
|
|
59
|
-
* The path a source is asked for: leading slash stripped, traversal
|
|
60
|
-
* rejected; null for a path that names no file (empty, or a directory).
|
|
61
|
-
*/
|
|
62
|
-
export declare const toRelativePath: (target: string) => string | null;
|
|
63
19
|
/**
|
|
64
20
|
* The app's file reader, owned by the Lambder instance: one source, one
|
|
65
21
|
* path rule, one memory cache and one compiled-template cache, shared by
|
|
@@ -1,48 +1,28 @@
|
|
|
1
1
|
import mimeTypeResolver from "mime-types";
|
|
2
|
-
import { getFS, getPath } from "../shared/node-polyfills.js";
|
|
3
2
|
import { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
|
|
4
3
|
const DEFAULT_MEMORY_CACHE_MAX_BYTES = 32 * 1024 * 1024;
|
|
5
4
|
const DEFAULT_MEMORY_CACHE_MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The path a source is asked for, or null for one that names no file.
|
|
7
|
+
*
|
|
8
|
+
* This is the whole path rule, and it belongs to the reader: every source is
|
|
9
|
+
* handed the result, and a source that resolves it against a base (a URL, a
|
|
10
|
+
* filesystem root) is safe only if the value really is the plain relative
|
|
11
|
+
* path the interface promises. Stripping only ONE leading slash would hand a
|
|
12
|
+
* source "//attacker.example/evil.html" as "/attacker.example/evil.html",
|
|
13
|
+
* which the HTTP source resolves as a protocol-relative reference: the app's
|
|
14
|
+
* origin credentials would go to a host the caller chose and its bytes would
|
|
15
|
+
* come back under the app's own domain. So every leading slash goes, and every segment is
|
|
16
|
+
* checked rather than only the ".." ones: an empty inner segment is how a
|
|
17
|
+
* host or a root gets back into the value, and a backslash is a separator to
|
|
18
|
+
* Windows paths and to every browser reading a Location.
|
|
9
19
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
constructor({ root }) {
|
|
13
|
-
this.root = root;
|
|
14
|
-
}
|
|
15
|
-
async read(relativePath) {
|
|
16
|
-
const fs = await getFS();
|
|
17
|
-
const path = await getPath();
|
|
18
|
-
if (!fs || !path)
|
|
19
|
-
throw new Error("LambderLocalFileSource requires a Node.js environment.");
|
|
20
|
-
const base = path.resolve(this.root);
|
|
21
|
-
const absolute = path.resolve(base, relativePath);
|
|
22
|
-
if (absolute !== base && !absolute.startsWith(base + path.sep))
|
|
23
|
-
return null;
|
|
24
|
-
const stat = await fs.promises.stat(absolute).catch(() => null);
|
|
25
|
-
if (!stat?.isFile())
|
|
26
|
-
return null;
|
|
27
|
-
return { body: await fs.promises.readFile(absolute) };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* A file a remote store returned: the store's Content-Type unless it is a
|
|
32
|
-
* generic octet-stream, in which case the extension decides, as for local
|
|
33
|
-
* files.
|
|
34
|
-
*/
|
|
35
|
-
export const remoteStoreFile = (body, contentType) => contentType && !contentType.endsWith("octet-stream") ? { body, mimeType: contentType } : { body };
|
|
36
|
-
/**
|
|
37
|
-
* The path a source is asked for: leading slash stripped, traversal
|
|
38
|
-
* rejected; null for a path that names no file (empty, or a directory).
|
|
39
|
-
*/
|
|
40
|
-
export const toRelativePath = (target) => {
|
|
41
|
-
if (target.split("/").some((segment) => segment === ".."))
|
|
42
|
-
return null;
|
|
43
|
-
const relative = target.startsWith("/") ? target.slice(1) : target;
|
|
20
|
+
const toRelativePath = (target) => {
|
|
21
|
+
const relative = target.replace(/^\/+/, "");
|
|
44
22
|
if (relative === "" || relative.endsWith("/"))
|
|
45
23
|
return null;
|
|
24
|
+
if (relative.split("/").some((segment) => segment === "" || segment === "." || segment === ".." || segment.includes("\\")))
|
|
25
|
+
return null;
|
|
46
26
|
return relative;
|
|
47
27
|
};
|
|
48
28
|
/**
|
|
@@ -99,7 +79,7 @@ export class LambderFiles {
|
|
|
99
79
|
return cached;
|
|
100
80
|
const file = await this.read(path);
|
|
101
81
|
if (!file)
|
|
102
|
-
throw new Error(`
|
|
82
|
+
throw new Error(`Lambder: res.templateFile found no such file in the files source: ${path}`);
|
|
103
83
|
const template = new LambderTemplatingEngine(file.body.toString("utf8"), { htmlVirtualSlots: options.htmlVirtualSlots });
|
|
104
84
|
this.templates.set(key, template);
|
|
105
85
|
return template;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type LambderRenderContext } from "./LambderContext.js";
|
|
2
|
+
import type LambderResolver from "./LambderResolver.js";
|
|
3
|
+
import type { LambderResponse } from "./LambderResponse.js";
|
|
4
|
+
import type { LambderFallbackHandler } from "./LambderCreateOptions.js";
|
|
5
|
+
export type LambderIndexHtmlOptions = {
|
|
6
|
+
/** Methods that reach the index handler. Default: ["GET", "HEAD"]; a list that names GET and not HEAD accepts HEAD too, as a route matcher's `method` does. */
|
|
7
|
+
methods?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* Skip paths whose last segment contains a dot, treating them as missing
|
|
10
|
+
* assets rather than app routes. Default: false, because real files have already
|
|
11
|
+
* been served by servePublicFiles at this point, and plenty of app routes
|
|
12
|
+
* carry dots (JWTs, coordinates, domain names, version numbers). Turn it
|
|
13
|
+
* on to get 404s instead of a 200 shell for missing-asset requests.
|
|
14
|
+
*/
|
|
15
|
+
skipFilePaths?: boolean;
|
|
16
|
+
/** 301-redirect trailing-slash paths to the canonical no-slash URL. Default: false. */
|
|
17
|
+
redirectTrailingSlash?: boolean;
|
|
18
|
+
/** Shell served by the default handler. Default: "index.html". */
|
|
19
|
+
indexFile?: string | ((ctx: LambderRenderContext) => string);
|
|
20
|
+
/** Compression override, like servePublicFiles: "auto" (default), true/false, or (ctx) => boolean | "auto". */
|
|
21
|
+
compress?: boolean | "auto" | ((ctx: LambderRenderContext) => boolean | "auto");
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Serves the app shell for page requests that nothing else handled,
|
|
25
|
+
* registered via lambder.serveIndexHtml(). Runs after servePublicFiles in the
|
|
26
|
+
* fallback chain, so real files are already gone and everything left is an
|
|
27
|
+
* app route. A request the gates exclude (a method that is not configured, a
|
|
28
|
+
* dotted path under skipFilePaths) falls through to the route fallback.
|
|
29
|
+
*/
|
|
30
|
+
export declare class LambderIndexHtmlHandler {
|
|
31
|
+
private handler;
|
|
32
|
+
private options;
|
|
33
|
+
private methods;
|
|
34
|
+
constructor(handler: LambderFallbackHandler | null, options: LambderIndexHtmlOptions);
|
|
35
|
+
/** Serve the shell, or return null to fall through. */
|
|
36
|
+
handle(ctx: LambderRenderContext, resolver: LambderResolver): Promise<LambderResponse | null>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isV2HttpEvent } from "./LambderContext.js";
|
|
2
|
+
import { allowsRequestMethod } from "./LambderRouting.js";
|
|
3
|
+
/**
|
|
4
|
+
* Serves the app shell for page requests that nothing else handled,
|
|
5
|
+
* registered via lambder.serveIndexHtml(). Runs after servePublicFiles in the
|
|
6
|
+
* fallback chain, so real files are already gone and everything left is an
|
|
7
|
+
* app route. A request the gates exclude (a method that is not configured, a
|
|
8
|
+
* dotted path under skipFilePaths) falls through to the route fallback.
|
|
9
|
+
*/
|
|
10
|
+
export class LambderIndexHtmlHandler {
|
|
11
|
+
handler;
|
|
12
|
+
options;
|
|
13
|
+
methods;
|
|
14
|
+
constructor(handler, options) {
|
|
15
|
+
this.handler = handler;
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.methods = new Set((options.methods ?? ["GET", "HEAD"]).map((method) => method.toUpperCase()));
|
|
18
|
+
}
|
|
19
|
+
/** Serve the shell, or return null to fall through. */
|
|
20
|
+
async handle(ctx, resolver) {
|
|
21
|
+
const { handler, options } = this;
|
|
22
|
+
if (!allowsRequestMethod(this.methods, ctx.method))
|
|
23
|
+
return null;
|
|
24
|
+
if ((options.skipFilePaths ?? false) && (ctx.path.split("/").pop() ?? "").includes("."))
|
|
25
|
+
return null;
|
|
26
|
+
if (options.redirectTrailingSlash && ctx.path.length > 1 && ctx.path.endsWith("/")) {
|
|
27
|
+
const target = sameOriginRedirectTarget(ctx.path);
|
|
28
|
+
if (target !== null)
|
|
29
|
+
return resolver.redirect(target + buildQueryString(ctx), 301);
|
|
30
|
+
}
|
|
31
|
+
const response = handler
|
|
32
|
+
? await handler(ctx, resolver)
|
|
33
|
+
: await resolver.templateFile(typeof options.indexFile === "function" ? options.indexFile(ctx) : (options.indexFile ?? "index.html"), {}, { cacheControl: "no-cache" });
|
|
34
|
+
if (options.compress !== undefined) {
|
|
35
|
+
response.compress = typeof options.compress === "function" ? options.compress(ctx) : options.compress;
|
|
36
|
+
}
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The canonical no-trailing-slash form of a request path, as a Location that
|
|
42
|
+
* cannot leave this origin, or null when no such form exists.
|
|
43
|
+
*
|
|
44
|
+
* `Location: //evil.example` is a protocol-relative URL, so a browser
|
|
45
|
+
* navigates to that host; every browser normalizes backslashes into slashes
|
|
46
|
+
* first, so `/\evil.example` is the same thing. This header is built from the
|
|
47
|
+
* request path, which the caller writes, so redirectTrailingSlash was an open
|
|
48
|
+
* redirect for anyone who asked for `GET //evil.example/`. The leading run of
|
|
49
|
+
* slashes and backslashes collapses to the single slash a path may have, and
|
|
50
|
+
* the result is then checked rather than assumed: the check is what the
|
|
51
|
+
* header's safety rests on, and it costs one comparison.
|
|
52
|
+
*/
|
|
53
|
+
const sameOriginRedirectTarget = (path) => {
|
|
54
|
+
const target = path.replace(/^[/\\]+/, "/").replace(/[/\\]+$/, "") || "/";
|
|
55
|
+
if (target.startsWith("//") || target.startsWith("/\\"))
|
|
56
|
+
return null;
|
|
57
|
+
return target;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Rebuild the query string from the API Gateway event for redirects.
|
|
61
|
+
*
|
|
62
|
+
* From the raw event rather than from ctx.get, which has flattened repeated
|
|
63
|
+
* keys to one value each and lost the order they arrived in; a redirect has
|
|
64
|
+
* to hand back the query it was given.
|
|
65
|
+
*/
|
|
66
|
+
const buildQueryString = (ctx) => {
|
|
67
|
+
if (isV2HttpEvent(ctx.event)) {
|
|
68
|
+
return ctx.event.rawQueryString ? `?${ctx.event.rawQueryString}` : "";
|
|
69
|
+
}
|
|
70
|
+
const multi = ctx.event.multiValueQueryStringParameters;
|
|
71
|
+
const single = ctx.event.queryStringParameters;
|
|
72
|
+
const params = new URLSearchParams();
|
|
73
|
+
if (multi) {
|
|
74
|
+
for (const [key, values] of Object.entries(multi)) {
|
|
75
|
+
for (const value of values ?? [])
|
|
76
|
+
params.append(key, value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (single) {
|
|
80
|
+
for (const [key, value] of Object.entries(single)) {
|
|
81
|
+
if (value !== undefined)
|
|
82
|
+
params.append(key, value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const queryString = params.toString();
|
|
86
|
+
return queryString ? `?${queryString}` : "";
|
|
87
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server's policy builders: the generic builders from `api/`, bound to
|
|
3
|
+
* the render contexts a Lambda handler runs on.
|
|
4
|
+
*
|
|
5
|
+
* They live in `core/` rather than beside the engines because binding them is
|
|
6
|
+
* the one thing about a guard or a rate-limit key that is the SERVER's, and
|
|
7
|
+
* having them in `api/` was the last reason that layer imported from `core/`
|
|
8
|
+
* at all. The mock runtime binds the same builders to its own call contexts,
|
|
9
|
+
* in its own layer, which is why the builders themselves are generic.
|
|
10
|
+
*/
|
|
11
|
+
import type { LambderRenderContext, LambderSessionRenderContext } from "./LambderContext.js";
|
|
12
|
+
import { type LambderGuardBuilder } from "../api/LambderApiGuards.js";
|
|
13
|
+
import { type LambderRateLimitKeyBuilder } from "../api/LambderApiRateLimits.js";
|
|
14
|
+
/** Builder for the server's guards: the handler sees the render context (session-typed when `session: true`). */
|
|
15
|
+
export declare const lambderGuard: LambderGuardBuilder<LambderRenderContext, LambderSessionRenderContext<any, any>>;
|
|
16
|
+
/** Builder for the server's rate-limit keys: the handler sees the render context. */
|
|
17
|
+
export declare const lambderRateLimitKey: LambderRateLimitKeyBuilder<LambderRenderContext>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server's policy builders: the generic builders from `api/`, bound to
|
|
3
|
+
* the render contexts a Lambda handler runs on.
|
|
4
|
+
*
|
|
5
|
+
* They live in `core/` rather than beside the engines because binding them is
|
|
6
|
+
* the one thing about a guard or a rate-limit key that is the SERVER's, and
|
|
7
|
+
* having them in `api/` was the last reason that layer imported from `core/`
|
|
8
|
+
* at all. The mock runtime binds the same builders to its own call contexts,
|
|
9
|
+
* in its own layer, which is why the builders themselves are generic.
|
|
10
|
+
*/
|
|
11
|
+
import { lambderGuardBuilder } from "../api/LambderApiGuards.js";
|
|
12
|
+
import { lambderRateLimitKeyBuilder } from "../api/LambderApiRateLimits.js";
|
|
13
|
+
/** Builder for the server's guards: the handler sees the render context (session-typed when `session: true`). */
|
|
14
|
+
export const lambderGuard = lambderGuardBuilder();
|
|
15
|
+
/** Builder for the server's rate-limit keys: the handler sees the render context. */
|
|
16
|
+
export const lambderRateLimitKey = lambderRateLimitKeyBuilder();
|
|
@@ -3,6 +3,8 @@ import type { LambderFiles } from "./LambderFiles.js";
|
|
|
3
3
|
import { LambderResponse } from "./LambderResponse.js";
|
|
4
4
|
/** Per-registration policy of servePublicFiles: how a request maps to a file and how the response is cached. */
|
|
5
5
|
export type LambderPublicFilesOptions = {
|
|
6
|
+
/** Methods that reach the public-file layer. Default: ["GET", "HEAD"], the same gate and the same default its serveIndexHtml sibling has. */
|
|
7
|
+
methods?: string[];
|
|
6
8
|
/**
|
|
7
9
|
* Map the request to a file path (app-owned logic, e.g. per-tenant
|
|
8
10
|
* roots: (ctx) => `${brand(ctx.host)}${ctx.path}`). Return
|
|
@@ -24,14 +26,15 @@ export type LambderPublicFilesOptions = {
|
|
|
24
26
|
/**
|
|
25
27
|
* Terminal public-file handler registered via lambder.servePublicFiles().
|
|
26
28
|
* Runs only when no route matched, so it can never shadow routes registered
|
|
27
|
-
* after it. Serves files through the instance's reader (
|
|
29
|
+
* after it. Serves files through the instance's reader (one path rule,
|
|
28
30
|
* mime-typed, memory-cached) with the immutable-cache heuristic for
|
|
29
31
|
* content-hashed assets, and falls through to the route fallback when the
|
|
30
|
-
* source has no such file.
|
|
32
|
+
* method is not configured or the source has no such file.
|
|
31
33
|
*/
|
|
32
34
|
export declare class LambderPublicFilesHandler {
|
|
33
35
|
private files;
|
|
34
36
|
private options;
|
|
37
|
+
private methods;
|
|
35
38
|
constructor(files: LambderFiles, options: LambderPublicFilesOptions);
|
|
36
39
|
/** Serve the mapped file, or return null to fall through. */
|
|
37
40
|
handle(ctx: LambderRenderContext): Promise<LambderResponse | null>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LambderResponse } from "./LambderResponse.js";
|
|
2
|
+
import { allowsRequestMethod } from "./LambderRouting.js";
|
|
2
3
|
// Content-hashed build outputs (Vite/webpack/Rollup): a [-.] separated run of
|
|
3
4
|
// 8+ hash chars containing at least one digit, before the extension.
|
|
4
5
|
const DEFAULT_IMMUTABLE_PATTERN = /[-.](?=[A-Za-z0-9_-]*\d)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9]+$/;
|
|
@@ -7,20 +8,24 @@ const DEFAULT_CACHE_CONTROL = "public, max-age=3600";
|
|
|
7
8
|
/**
|
|
8
9
|
* Terminal public-file handler registered via lambder.servePublicFiles().
|
|
9
10
|
* Runs only when no route matched, so it can never shadow routes registered
|
|
10
|
-
* after it. Serves files through the instance's reader (
|
|
11
|
+
* after it. Serves files through the instance's reader (one path rule,
|
|
11
12
|
* mime-typed, memory-cached) with the immutable-cache heuristic for
|
|
12
13
|
* content-hashed assets, and falls through to the route fallback when the
|
|
13
|
-
* source has no such file.
|
|
14
|
+
* method is not configured or the source has no such file.
|
|
14
15
|
*/
|
|
15
16
|
export class LambderPublicFilesHandler {
|
|
16
17
|
files;
|
|
17
18
|
options;
|
|
19
|
+
methods;
|
|
18
20
|
constructor(files, options) {
|
|
19
21
|
this.files = files;
|
|
20
22
|
this.options = options;
|
|
23
|
+
this.methods = new Set((options.methods ?? ["GET", "HEAD"]).map((method) => method.toUpperCase()));
|
|
21
24
|
}
|
|
22
25
|
/** Serve the mapped file, or return null to fall through. */
|
|
23
26
|
async handle(ctx) {
|
|
27
|
+
if (!allowsRequestMethod(this.methods, ctx.method))
|
|
28
|
+
return null;
|
|
24
29
|
const mappedPath = this.options.path ? this.options.path(ctx) : ctx.path;
|
|
25
30
|
if (!mappedPath)
|
|
26
31
|
return null;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { LambderApiNullAnswerConfig } from "../shared/wire/LambderApiContract.js";
|
|
2
|
+
import LambderResponseBuilder, { type LambderResolverApiMethod, type LambderApiResponseConfig, type LambderResponseOptions } from "./LambderResponseBuilder.js";
|
|
2
3
|
import type { LambderResponse } from "./LambderResponse.js";
|
|
3
4
|
type SyncDie<T extends (...args: any[]) => LambderResponse> = (...args: Parameters<T>) => never;
|
|
4
5
|
type AsyncDie<T extends (...args: any[]) => Promise<LambderResponse>> = (...args: Parameters<T>) => Promise<never>;
|
|
5
|
-
|
|
6
|
+
/** The `res.die.*` surface: every builder method, throwing what it built. Internal to the resolver, which is the only thing that has one. */
|
|
7
|
+
interface DieResolverMethods<TOutput> {
|
|
6
8
|
raw: SyncDie<LambderResponseBuilder["raw"]>;
|
|
7
9
|
json: SyncDie<LambderResponseBuilder["json"]>;
|
|
8
10
|
text: SyncDie<LambderResponseBuilder["text"]>;
|
|
@@ -13,8 +15,8 @@ export interface DieResolverMethods<TOutput> {
|
|
|
13
15
|
redirect: SyncDie<LambderResponseBuilder["redirect"]>;
|
|
14
16
|
versionExpired: SyncDie<LambderResponseBuilder["versionExpired"]>;
|
|
15
17
|
fileBase64: SyncDie<LambderResponseBuilder["fileBase64"]>;
|
|
16
|
-
api:
|
|
17
|
-
apiBinary:
|
|
18
|
+
api: LambderResolverApiMethod<TOutput, never>;
|
|
19
|
+
apiBinary: LambderResolverApiMethod<TOutput, never>;
|
|
18
20
|
file: AsyncDie<LambderResponseBuilder["file"]>;
|
|
19
21
|
templateFile: AsyncDie<LambderResponseBuilder["templateFile"]>;
|
|
20
22
|
}
|
|
@@ -30,8 +32,8 @@ export default class LambderResolver<TOutput = any> extends LambderResponseBuild
|
|
|
30
32
|
die: DieResolverMethods<TOutput>;
|
|
31
33
|
constructor(...args: ConstructorParameters<typeof LambderResponseBuilder>);
|
|
32
34
|
api(payload: TOutput, config?: LambderApiResponseConfig, options?: LambderResponseOptions): LambderResponse;
|
|
33
|
-
api(payload: null, config:
|
|
35
|
+
api(payload: null, config: LambderApiNullAnswerConfig, options?: LambderResponseOptions): LambderResponse;
|
|
34
36
|
apiBinary(payload: TOutput, config?: LambderApiResponseConfig, options?: LambderResponseOptions): LambderResponse;
|
|
35
|
-
apiBinary(payload: null, config:
|
|
37
|
+
apiBinary(payload: null, config: LambderApiNullAnswerConfig, options?: LambderResponseOptions): LambderResponse;
|
|
36
38
|
}
|
|
37
39
|
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
|
|
1
|
+
import { LAMBDER_RESPONSE_BRAND } from "../shared/util/LambderResponseBrand.js";
|
|
2
|
+
import type { LambderCompressionOption, LambderCompressionSettingsBase, LambderEncoding } from "../shared/wire/LambderCompressionOption.js";
|
|
3
|
+
import type { LambderRenderContext, LambderHttpEventFormat } from "./LambderContext.js";
|
|
4
|
+
import type { LambderApiAnswer } from "../api/LambderApiAnswer.js";
|
|
5
|
+
import type { LambderHttpStatusCode } from "../shared/wire/LambderHttpStatus.js";
|
|
4
6
|
export type LambderHeadersInput = Record<string, string | string[]>;
|
|
5
7
|
/**
|
|
6
8
|
* Final Lambda response: v1 (REST API) uses multiValueHeaders, v2 (HTTP API /
|
|
@@ -18,10 +20,9 @@ export type LambderHttpResponse = {
|
|
|
18
20
|
headers?: Record<string, string>;
|
|
19
21
|
cookies?: string[];
|
|
20
22
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
statusCode: HttpStatusCode;
|
|
23
|
+
/** The constructor argument of LambderResponse; built through the resolver rather than by hand, so it is internal to this module. */
|
|
24
|
+
type LambderResponseInit = {
|
|
25
|
+
statusCode: LambderHttpStatusCode;
|
|
25
26
|
headers?: LambderHeadersInput;
|
|
26
27
|
body?: string | Buffer | null;
|
|
27
28
|
/** True when body is already a base64-encoded string (pre-encoded binary content). */
|
|
@@ -41,7 +42,8 @@ export type LambderResponseInit = {
|
|
|
41
42
|
* the request: the thrown response becomes the response.
|
|
42
43
|
*/
|
|
43
44
|
export declare class LambderResponse {
|
|
44
|
-
|
|
45
|
+
readonly [LAMBDER_RESPONSE_BRAND]: true;
|
|
46
|
+
statusCode: LambderHttpStatusCode;
|
|
45
47
|
headers: Record<string, string[]>;
|
|
46
48
|
body: string | Buffer | null;
|
|
47
49
|
isBodyBase64: boolean;
|
|
@@ -52,8 +54,16 @@ export declare class LambderResponse {
|
|
|
52
54
|
setHeader(key: string, value: string | string[]): this;
|
|
53
55
|
addHeader(key: string, value: string): this;
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
/**
|
|
58
|
+
* A handler's response as a core answer: what the API pipeline stores,
|
|
59
|
+
* replays and hands back. A Buffer body travels base64-encoded and marked
|
|
60
|
+
* as such, so the idempotency engine never caches it and finalization
|
|
61
|
+
* passes it through untouched; the compress and etag flags ride along so
|
|
62
|
+
* nothing a handler asked for is lost on the way through the core.
|
|
63
|
+
*/
|
|
64
|
+
export declare const answerFromResponse: (response: LambderResponse) => LambderApiAnswer;
|
|
65
|
+
/** A core answer as the response hooks, CORS and finalization work on. */
|
|
66
|
+
export declare const responseFromAnswer: (answer: LambderApiAnswer) => LambderResponse;
|
|
57
67
|
/** Response-side settings: the threshold plus what the wire can negotiate. */
|
|
58
68
|
export type LambderResponseCompressionSettings = LambderCompressionSettingsBase & {
|
|
59
69
|
/** Preference order; the first the client accepts wins. */
|
|
@@ -80,10 +90,18 @@ export type LambderFinalizeOptions = {
|
|
|
80
90
|
*/
|
|
81
91
|
export declare const DEFAULT_RESPONSE_COMPRESSION_SETTINGS: LambderResponseCompressionSettings;
|
|
82
92
|
export declare const DEFAULT_FINALIZE_OPTIONS: LambderFinalizeOptions;
|
|
93
|
+
/**
|
|
94
|
+
* Emit the format-specific Lambda response shape. Exported because the
|
|
95
|
+
* last-resort crash path has to emit without finalizing (finalization may be
|
|
96
|
+
* what failed) and must still get the shape right; hand-writing it there left
|
|
97
|
+
* the v1/v2 split in four places.
|
|
98
|
+
*/
|
|
99
|
+
export declare const emitResponse: (format: LambderHttpEventFormat, statusCode: number, headers: Record<string, string[]>, body: string, isBase64Encoded: boolean) => LambderHttpResponse;
|
|
83
100
|
/**
|
|
84
101
|
* Convert an intermediate LambderResponse into the final Lambda response:
|
|
85
102
|
* gzip negotiation (Accept-Encoding), ETag + If-None-Match 304, base64
|
|
86
103
|
* encoding, HEAD body stripping, and Lambda payload size guard. Emits the v1
|
|
87
104
|
* (REST API) or v2 (HTTP API / Function URL) response shape.
|
|
88
105
|
*/
|
|
89
|
-
export declare const finalizeResponse: (ctx: Pick<LambderRenderContext, "method" | "
|
|
106
|
+
export declare const finalizeResponse: (ctx: Pick<LambderRenderContext, "method" | "header"> | null, response: LambderResponse, options: LambderFinalizeOptions, format?: LambderHttpEventFormat) => Promise<LambderHttpResponse>;
|
|
107
|
+
export {};
|