lambder 8.1.2 → 8.3.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 +136 -0
- package/README.md +8 -7
- package/dist/api/LambderApiGuards.d.ts +2 -17
- package/dist/api/LambderApiRateLimits.d.ts +2 -29
- package/dist/build/generatedTables.d.ts +72 -0
- package/dist/build/generatedTables.js +99 -0
- package/dist/build/writeApiGuardParams.d.ts +60 -0
- package/dist/build/writeApiGuardParams.js +85 -0
- package/dist/build/writeApiOptions.d.ts +68 -0
- package/dist/build/writeApiOptions.js +102 -0
- package/dist/build.d.ts +10 -4
- package/dist/build.js +7 -4
- package/dist/client/LambderUploadRunner.d.ts +7 -7
- package/dist/client/LambderUploadRunner.js +12 -21
- package/dist/client.d.ts +7 -0
- package/dist/client.js +11 -0
- package/dist/core/Lambder.d.ts +21 -0
- package/dist/core/Lambder.js +69 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/mock/LambderMockApp.d.ts +34 -17
- package/dist/mock/LambderMockApp.js +67 -21
- package/dist/mock/LambderMockCreateOptions.d.ts +68 -5
- package/dist/mock/LambderMockTypes.d.ts +29 -10
- package/dist/mock/lambderMockPoliciesFrom.d.ts +51 -0
- package/dist/mock/lambderMockPoliciesFrom.js +46 -0
- package/dist/mock.d.ts +3 -0
- package/dist/mock.js +3 -0
- package/dist/secrets/LambderOneShotSecrets.d.ts +166 -0
- package/dist/secrets/LambderOneShotSecrets.js +217 -0
- package/dist/session/LambderSessionCrypto.js +6 -16
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +3 -2
- package/dist/shared/contracts/LambderOneShotSecretStore.d.ts +122 -0
- package/dist/shared/contracts/LambderOneShotSecretStore.js +38 -0
- package/dist/shared/util/LambderBackoffTimer.d.ts +82 -0
- package/dist/shared/util/LambderBackoffTimer.js +86 -0
- package/dist/shared/util/LambderBase64.d.ts +14 -0
- package/dist/shared/util/LambderBase64.js +17 -0
- package/dist/shared/util/LambderSignedClaims.d.ts +78 -0
- package/dist/shared/util/LambderSignedClaims.js +109 -0
- package/dist/shared/util/LambderTextDigest.d.ts +19 -5
- package/dist/shared/util/LambderTextDigest.js +30 -5
- package/dist/shared/util/assertPlainData.d.ts +9 -0
- package/dist/shared/util/assertPlainData.js +41 -0
- package/dist/shared/wire/LambderApiOptionEntries.d.ts +148 -0
- package/dist/shared/wire/LambderApiOptionEntries.js +35 -0
- package/dist/stores/LambderDdbOneShotSecretStore.d.ts +64 -0
- package/dist/stores/LambderDdbOneShotSecretStore.js +266 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +3 -2
- package/dist/stores/LambderMemoryIdempotencyStore.js +3 -2
- package/dist/stores/LambderMemoryOneShotSecretStore.d.ts +36 -0
- package/dist/stores/LambderMemoryOneShotSecretStore.js +93 -0
- package/dist/testing/LambderConformanceRunner.d.ts +46 -0
- package/dist/testing/LambderConformanceRunner.js +21 -0
- package/dist/testing/lambderIdempotencyStoreConformance.d.ts +33 -0
- package/dist/testing/lambderIdempotencyStoreConformance.js +237 -0
- package/dist/testing/lambderOneShotSecretStoreConformance.d.ts +43 -0
- package/dist/testing/lambderOneShotSecretStoreConformance.js +224 -0
- package/dist/testing/lambderRateLimiterConformance.d.ts +20 -0
- package/dist/testing/lambderRateLimiterConformance.js +72 -0
- package/dist/testing/lambderSessionStoreConformance.d.ts +27 -0
- package/dist/testing/lambderSessionStoreConformance.js +165 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { headerLinesOf, loadApiOptionEntries, nameChangesOf, readGeneratedTable, settleGeneratedFile, tableStatementLines, } from "./generatedTables.js";
|
|
4
|
+
/*
|
|
5
|
+
* The declared options of an app's APIs as a generated module of plain data:
|
|
6
|
+
* every API's mode and its guards, rateLimit and idempotency options as
|
|
7
|
+
* written, every rate-limit policy less its key handler, and every guard's
|
|
8
|
+
* input mode, session flag and place in the call. The contract already
|
|
9
|
+
* carries the options as types; this is the same fact as a value, for the
|
|
10
|
+
* code that decides something at runtime with it: a mock restating the
|
|
11
|
+
* server's declarations, a test walking the public surface. Neither imports
|
|
12
|
+
* the server for it. The file names every endpoint, so a production bundle
|
|
13
|
+
* imports none of it: a screen that gates on a guard reads that guard's
|
|
14
|
+
* parameters alone (writeApiGuardParams).
|
|
15
|
+
*
|
|
16
|
+
* Plain data by construction: the instance refuses to report a guard
|
|
17
|
+
* parameter that is code or a class instance (see Lambder.apiOptionEntries),
|
|
18
|
+
* a policy's key handler is reduced to `per: "custom"`, and a guard's schema
|
|
19
|
+
* to its input mode. No secret can reach the file, because nothing that could
|
|
20
|
+
* hold one is written.
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_HEADER = [
|
|
23
|
+
"Generated by writeApiOptions() from lambder/build. Do not edit.",
|
|
24
|
+
"",
|
|
25
|
+
"Every API the server registers with its declared options, every rate-limit",
|
|
26
|
+
"policy less its key handler, and every guard's input mode: the server's",
|
|
27
|
+
"declarations as plain data, for the code that decides something with them",
|
|
28
|
+
"without importing the server.",
|
|
29
|
+
].join("\n");
|
|
30
|
+
/** The three tables the module exports, in the order they are written. */
|
|
31
|
+
const TABLES = [
|
|
32
|
+
{ name: "apiOptions", key: "apis", type: "LambderApiOptionEntry", doc: "Every API the server registers: its mode and its guards, rateLimit and idempotency options as written." },
|
|
33
|
+
{ name: "rateLimitPolicies", key: "rateLimitPolicies", type: "LambderRateLimitPolicyEntry", doc: "Every rate-limit policy the server declares, its key reduced to what it counts: an address, a session, or a key the app derives (\"custom\")." },
|
|
34
|
+
{ name: "guardDeclarations", key: "guards", type: "LambderGuardDeclarationEntry", doc: "Every guard the server declares: how it is fed, whether it needs a session, and when it runs." },
|
|
35
|
+
];
|
|
36
|
+
/** The tables a generated file holds, read back as data, or null for a file that does not hold all three as written (never written, or rewritten by hand). */
|
|
37
|
+
export const readOptionTables = (contents) => {
|
|
38
|
+
const tables = {};
|
|
39
|
+
for (const table of TABLES) {
|
|
40
|
+
const read = readGeneratedTable(contents, table.name);
|
|
41
|
+
if (!read)
|
|
42
|
+
return null;
|
|
43
|
+
tables[table.key] = read;
|
|
44
|
+
}
|
|
45
|
+
return tables;
|
|
46
|
+
};
|
|
47
|
+
const renderOptionsFile = (entries, options) => {
|
|
48
|
+
const semicolon = options.semicolons === false ? "" : ";";
|
|
49
|
+
return [
|
|
50
|
+
...headerLinesOf(options.header ?? DEFAULT_HEADER),
|
|
51
|
+
`import type { ${TABLES.map((table) => table.type).join(", ")} } from "lambder/client"${semicolon}`,
|
|
52
|
+
...TABLES.flatMap((table) => [
|
|
53
|
+
"",
|
|
54
|
+
...tableStatementLines({ name: table.name, doc: table.doc, value: entries[table.key], satisfies: `Record<string, ${table.type}>`, semicolon }),
|
|
55
|
+
]),
|
|
56
|
+
"",
|
|
57
|
+
].join("\n");
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Writes the declared options of an app's APIs as a module of plain data, or
|
|
61
|
+
* checks the one on disk, and says which APIs, policies and guards moved.
|
|
62
|
+
*
|
|
63
|
+
* Call it from a generator script beside writeApiSignatures, naming the
|
|
64
|
+
* module that exports the app's instance:
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* import { writeApiOptions } from "lambder/build";
|
|
68
|
+
*
|
|
69
|
+
* const result = await writeApiOptions({
|
|
70
|
+
* module: "server/src/index.ts", // export const lambder = initLambder()...
|
|
71
|
+
* exportName: "lambder",
|
|
72
|
+
* file: "shared/generated/apiOptions.generated.ts",
|
|
73
|
+
* check: process.argv.includes("--check"),
|
|
74
|
+
* });
|
|
75
|
+
* console.log(result.lines.join("\n"));
|
|
76
|
+
* process.exit(result.ok ? 0 : 1);
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* The tables are compared as the data the file holds, so a checkout that
|
|
80
|
+
* rewrote its line endings or a formatter that re-indented it is neither
|
|
81
|
+
* stale nor rewritten, and a file whose data is current is left as it is.
|
|
82
|
+
* There is no fresh-process check: nothing here is digested, so nothing can
|
|
83
|
+
* differ per process. A module that does not load, an export that is not an
|
|
84
|
+
* instance, or a guard parameter that is not plain data throws.
|
|
85
|
+
*/
|
|
86
|
+
export const writeApiOptions = async (options) => {
|
|
87
|
+
const file = resolve(options.file);
|
|
88
|
+
const entries = await loadApiOptionEntries(options, "writeApiOptions");
|
|
89
|
+
const previousText = existsSync(file) ? readFileSync(file, "utf8") : null;
|
|
90
|
+
const previous = previousText === null ? null : readOptionTables(previousText);
|
|
91
|
+
const changes = Object.fromEntries(TABLES.map((table) => [table.key, nameChangesOf(entries[table.key], previous?.[table.key] ?? {})]));
|
|
92
|
+
const counts = Object.fromEntries(TABLES.map((table) => [table.key, Object.keys(entries[table.key]).length]));
|
|
93
|
+
const settled = settleGeneratedFile({
|
|
94
|
+
name: options.file, file, check: options.check, previousText, readBack: previous !== null,
|
|
95
|
+
tables: TABLES.map((table) => ({ name: table.name, count: counts[table.key], changes: changes[table.key] })),
|
|
96
|
+
held: `${counts.apis} APIs, ${counts.rateLimitPolicies} policies, ${counts.guards} guards`,
|
|
97
|
+
tablesNoun: "the three tables",
|
|
98
|
+
unmovedLine: "no options changed",
|
|
99
|
+
render: () => renderOptionsFile(entries, options),
|
|
100
|
+
});
|
|
101
|
+
return { ...settled, file, counts, changes };
|
|
102
|
+
};
|
package/dist/build.d.ts
CHANGED
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
* Build entry point (`import ... from "lambder/build"`).
|
|
3
3
|
*
|
|
4
4
|
* What a generator script runs at build time to write the files a deployment
|
|
5
|
-
* ships: the signature file both sides read,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* ships: the signature file both sides read, the declared options as plain
|
|
6
|
+
* data, and one guard's parameters as the least a browser needs of them, all
|
|
7
|
+
* from the app's own instance; and the contract a client compiles against,
|
|
8
|
+
* from the server's sources. Node-only and imported by nothing else
|
|
9
|
+
* in the package, so no deployment or bundle carries it.
|
|
9
10
|
*/
|
|
10
11
|
export { writeApiSignatures } from "./build/writeApiSignatures.js";
|
|
11
12
|
export type { LambderApiSignatureSource, LambderApiSignatureFileOptions, LambderApiSignatureFileResult, } from "./build/writeApiSignatures.js";
|
|
12
13
|
export { writeApiContract } from "./build/writeApiContract.js";
|
|
13
14
|
export type { LambderApiContractFileOptions, LambderApiContractFileResult, } from "./build/writeApiContract.js";
|
|
15
|
+
export { writeApiOptions } from "./build/writeApiOptions.js";
|
|
16
|
+
export type { LambderApiOptionsFileOptions, LambderApiOptionsFileResult, } from "./build/writeApiOptions.js";
|
|
17
|
+
export { writeApiGuardParams } from "./build/writeApiGuardParams.js";
|
|
18
|
+
export type { LambderApiGuardParamsFileOptions, LambderApiGuardParamsFileResult, } from "./build/writeApiGuardParams.js";
|
|
19
|
+
export type { LambderApiOptionsSource, LambderNameChanges } from "./build/generatedTables.js";
|
|
14
20
|
export type { LambderModuleLocation } from "./build/moduleLocation.js";
|
package/dist/build.js
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* Build entry point (`import ... from "lambder/build"`).
|
|
3
3
|
*
|
|
4
4
|
* What a generator script runs at build time to write the files a deployment
|
|
5
|
-
* ships: the signature file both sides read,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* ships: the signature file both sides read, the declared options as plain
|
|
6
|
+
* data, and one guard's parameters as the least a browser needs of them, all
|
|
7
|
+
* from the app's own instance; and the contract a client compiles against,
|
|
8
|
+
* from the server's sources. Node-only and imported by nothing else
|
|
9
|
+
* in the package, so no deployment or bundle carries it.
|
|
9
10
|
*/
|
|
10
11
|
export { writeApiSignatures } from "./build/writeApiSignatures.js";
|
|
11
12
|
export { writeApiContract } from "./build/writeApiContract.js";
|
|
13
|
+
export { writeApiOptions } from "./build/writeApiOptions.js";
|
|
14
|
+
export { writeApiGuardParams } from "./build/writeApiGuardParams.js";
|
|
@@ -48,10 +48,11 @@ export type LambderUploadRunnerOptions<Reference, Receipt> = {
|
|
|
48
48
|
/**
|
|
49
49
|
* How storage is tried again when it cannot be reached, stalls, or answers
|
|
50
50
|
* a failure a retry can cure (a 5xx, RequestTimeout, SlowDown). Each wait
|
|
51
|
-
* is a random
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
51
|
+
* is `baseDelayMs` plus a random share of a ceiling that starts at
|
|
52
|
+
* `baseDelayMs` and doubles with every failed attempt, the whole never
|
|
53
|
+
* past `maxDelayMs` (the ladder of LambderBackoffTimer), so many browsers
|
|
54
|
+
* dropped together do not come back in step, not even the first time.
|
|
55
|
+
* Default: 4 attempts, waits from one second to 15.
|
|
55
56
|
*/
|
|
56
57
|
storageRetry?: {
|
|
57
58
|
attempts?: number;
|
|
@@ -69,8 +70,8 @@ export type LambderUploadRunnerOptions<Reference, Receipt> = {
|
|
|
69
70
|
export declare class LambderUploadRunner<Reference, Receipt> {
|
|
70
71
|
private readonly options;
|
|
71
72
|
private readonly attempts;
|
|
72
|
-
|
|
73
|
-
private readonly
|
|
73
|
+
/** The ladder one upload's waits climb; each upload() builds a timer of its own from it, so two uploads never share a count. */
|
|
74
|
+
private readonly backoff;
|
|
74
75
|
private readonly stallTimeoutMs;
|
|
75
76
|
constructor(options: LambderUploadRunnerOptions<Reference, Receipt>);
|
|
76
77
|
/** For a file input's `accept`, so the picker only offers what the rule takes. */
|
|
@@ -90,7 +91,6 @@ export declare class LambderUploadRunner<Reference, Receipt> {
|
|
|
90
91
|
}): Promise<Receipt>;
|
|
91
92
|
/** Forgets a confirmed upload through the app's endpoint, when it declared one. */
|
|
92
93
|
discard(receipt: Receipt): Promise<void>;
|
|
93
|
-
private waitBeforeRetry;
|
|
94
94
|
/** One post of the file to storage. Never throws: every ending is an outcome. */
|
|
95
95
|
private post;
|
|
96
96
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { checkUploadRule, } from "../shared/contracts/LambderUploadBucket.js";
|
|
2
|
+
import { LambderBackoffTimer } from "../shared/util/LambderBackoffTimer.js";
|
|
2
3
|
import { sha256Base64Of } from "../shared/util/LambderTextDigest.js";
|
|
3
4
|
/** How an upload failed: `reason` for a screen to word, the underlying error as `cause`. */
|
|
4
5
|
export class LambderUploadError extends Error {
|
|
@@ -21,14 +22,16 @@ const TRANSIENT_STORAGE_CODES = new Set(["RequestTimeout", "SlowDown", "Internal
|
|
|
21
22
|
export class LambderUploadRunner {
|
|
22
23
|
options;
|
|
23
24
|
attempts;
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
/** The ladder one upload's waits climb; each upload() builds a timer of its own from it, so two uploads never share a count. */
|
|
26
|
+
backoff;
|
|
26
27
|
stallTimeoutMs;
|
|
27
28
|
constructor(options) {
|
|
28
29
|
this.options = options;
|
|
29
30
|
this.attempts = Math.max(1, options.storageRetry?.attempts ?? 4);
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const baseDelayMs = options.storageRetry?.baseDelayMs ?? 1_000;
|
|
32
|
+
const maxDelayMs = options.storageRetry?.maxDelayMs ?? 15_000;
|
|
33
|
+
// A longest wait below the shortest is every wait at the shortest.
|
|
34
|
+
this.backoff = { baseMs: baseDelayMs, maxMs: Math.max(baseDelayMs, maxDelayMs) };
|
|
32
35
|
this.stallTimeoutMs = options.stallTimeoutMs ?? 60_000;
|
|
33
36
|
}
|
|
34
37
|
/** For a file input's `accept`, so the picker only offers what the rule takes. */
|
|
@@ -81,6 +84,7 @@ export class LambderUploadRunner {
|
|
|
81
84
|
}
|
|
82
85
|
};
|
|
83
86
|
let issued = await requestTicket();
|
|
87
|
+
const backoff = new LambderBackoffTimer(this.backoff);
|
|
84
88
|
let failedAttempts = 0;
|
|
85
89
|
let ticketRenewals = 0;
|
|
86
90
|
for (;;) {
|
|
@@ -103,7 +107,10 @@ export class LambderUploadRunner {
|
|
|
103
107
|
// connection does not leave the app a record per attempt.
|
|
104
108
|
if (++failedAttempts >= this.attempts)
|
|
105
109
|
throw new LambderUploadError("networkFailed");
|
|
106
|
-
|
|
110
|
+
// The wait rejects only for the signal: nothing else cancels it.
|
|
111
|
+
await backoff.wait(signal).catch((cause) => {
|
|
112
|
+
throw new LambderUploadError("cancelled", { cause });
|
|
113
|
+
});
|
|
107
114
|
}
|
|
108
115
|
report("confirming", file.size);
|
|
109
116
|
try {
|
|
@@ -117,22 +124,6 @@ export class LambderUploadRunner {
|
|
|
117
124
|
async discard(receipt) {
|
|
118
125
|
await this.options.discardUpload?.(receipt);
|
|
119
126
|
}
|
|
120
|
-
waitBeforeRetry(failedAttempts, signal) {
|
|
121
|
-
const ceiling = Math.max(this.baseDelayMs, Math.min(this.baseDelayMs * 2 ** failedAttempts, this.maxDelayMs));
|
|
122
|
-
return new Promise((resolve, reject) => {
|
|
123
|
-
if (signal?.aborted)
|
|
124
|
-
return reject(new LambderUploadError("cancelled"));
|
|
125
|
-
const cancel = () => {
|
|
126
|
-
clearTimeout(timer);
|
|
127
|
-
reject(new LambderUploadError("cancelled"));
|
|
128
|
-
};
|
|
129
|
-
const timer = setTimeout(() => {
|
|
130
|
-
signal?.removeEventListener("abort", cancel);
|
|
131
|
-
resolve();
|
|
132
|
-
}, this.baseDelayMs + Math.random() * (ceiling - this.baseDelayMs));
|
|
133
|
-
signal?.addEventListener("abort", cancel, { once: true });
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
127
|
/** One post of the file to storage. Never throws: every ending is an outcome. */
|
|
137
128
|
post(ticket, file, signal, onSent) {
|
|
138
129
|
if (signal?.aborted)
|
package/dist/client.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type { LambderIdempotencyKeyScope } from "./shared/wire/LambderIdempotenc
|
|
|
26
26
|
export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
27
27
|
export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderAppRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/wire/LambderApiRefusal.js";
|
|
28
28
|
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractKeysWithGuard, LambderJsonOf, LambderJsonOutputOf, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
|
29
|
+
export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
|
|
30
|
+
export type { LambderApiOptionEntries, LambderApiOptionEntry, LambderRateLimitPolicyEntry, LambderGuardDeclarationEntry, LambderApisWithGuard, LambderApisGuardedBy, LambderApisWithMode, LambderGuardParamOf, } from "./shared/wire/LambderApiOptionEntries.js";
|
|
29
31
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
30
32
|
export type { LambderCrashDetail, LambderCrashCause } from "./shared/wire/LambderCrashDetail.js";
|
|
31
33
|
export { compressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/wire/LambderRequestPayload.js";
|
|
@@ -36,6 +38,11 @@ export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtm
|
|
|
36
38
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
37
39
|
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
38
40
|
export type { LambderHttpStatusCode } from "./shared/wire/LambderHttpStatus.js";
|
|
41
|
+
export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
42
|
+
export type { LambderBackoffTimerOptions } from "./shared/util/LambderBackoffTimer.js";
|
|
43
|
+
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
44
|
+
export type { LambderSignedClaimsOptions } from "./shared/util/LambderSignedClaims.js";
|
|
45
|
+
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
39
46
|
export { LambderUploadRunner, LambderUploadError } from "./client/LambderUploadRunner.js";
|
|
40
47
|
export type { LambderUploadRunnerOptions, LambderUploadProgress, LambderUploadPhase, LambderUploadFailureReason } from "./client/LambderUploadRunner.js";
|
|
41
48
|
export { checkUploadRule } from "./shared/contracts/LambderUploadBucket.js";
|
package/dist/client.js
CHANGED
|
@@ -23,6 +23,9 @@ export { createIdempotencyKey, createIdempotencyKeyScope } from "./shared/wire/L
|
|
|
23
23
|
// Typed API refusals (isomorphic: shared code may throw them from anywhere;
|
|
24
24
|
// in the browser they are plain Errors).
|
|
25
25
|
export { LambderApiRefusal, isLambderApiRefusal, refuse, refusalMessageOf, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
|
|
26
|
+
// The server's declared options as plain data (the generated options module's
|
|
27
|
+
// entry types) and the readers a client derives its own facts from.
|
|
28
|
+
export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
|
|
26
29
|
// A crash described for a caller allowed to see it (the envelope's `crash` field; pure, no Node built-ins).
|
|
27
30
|
export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
|
|
28
31
|
// Request payload compression (browser-safe: gzip via CompressionStream, no Node built-ins).
|
|
@@ -33,6 +36,14 @@ export { resolveCompressionOption } from "./shared/wire/LambderCompressionOption
|
|
|
33
36
|
export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml } from "./shared/LambderHtml.js";
|
|
34
37
|
// Typed translations (standalone, isomorphic)
|
|
35
38
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
39
|
+
// Waiting longer after each failure, once: for a reconnecting client or a
|
|
40
|
+
// screen that has to come back by itself.
|
|
41
|
+
export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
42
|
+
// Signed claims tokens, for the isomorphic code that verifies them where a
|
|
43
|
+
// server's secret is at hand (an edge Worker, a shared backend package);
|
|
44
|
+
// never in a page, which holds no secret to verify with.
|
|
45
|
+
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
46
|
+
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
36
47
|
// Direct uploads: the runner that takes a file from the browser straight to
|
|
37
48
|
// storage, and the vocabulary it shares with the server's bucket.
|
|
38
49
|
export { LambderUploadRunner, LambderUploadError } from "./client/LambderUploadRunner.js";
|
package/dist/core/Lambder.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { LambderFileSource } from "../shared/contracts/LambderFileSource.js
|
|
|
14
14
|
import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
|
|
15
15
|
import { type LambderApiSignatureEntry } from "../api/LambderApiSignature.js";
|
|
16
16
|
import { type LambderApiSignatureMap } from "../shared/wire/LambderApiSignature.js";
|
|
17
|
+
import type { LambderApiOptionEntries } from "../shared/wire/LambderApiOptionEntries.js";
|
|
17
18
|
import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
|
|
18
19
|
import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../api/LambderApiGuards.js";
|
|
19
20
|
import type { LambderApiRateLimitPolicyConfig, LambderRateLimitOption } from "../api/LambderApiRateLimits.js";
|
|
@@ -91,6 +92,8 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
91
92
|
private readonly apiDefinitions;
|
|
92
93
|
/** The guards map given at creation, kept for apiSignatures(): a guard's schema is part of the signature of every endpoint declaring it. */
|
|
93
94
|
private readonly guards;
|
|
95
|
+
/** The rate-limit policies given at creation, kept for apiOptionEntries(), which records each one less its key handler. */
|
|
96
|
+
private readonly rateLimitPolicies;
|
|
94
97
|
private hookList;
|
|
95
98
|
private createdHooks;
|
|
96
99
|
private initPromise;
|
|
@@ -246,6 +249,24 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
|
|
|
246
249
|
* unless the generator writes it there.
|
|
247
250
|
*/
|
|
248
251
|
apiSignatureEntries(): Promise<LambderApiSignatureEntry[]>;
|
|
252
|
+
/**
|
|
253
|
+
* Every registered API's mode and declared options as plain data, with
|
|
254
|
+
* the rate-limit policies and guards they name reduced to what is not
|
|
255
|
+
* code: what writeApiOptions (lambder/build) writes to a module a client,
|
|
256
|
+
* a mock or a test imports instead of the server. The contract carries
|
|
257
|
+
* the same options as types; this is the same fact as a value, for code
|
|
258
|
+
* that decides something at runtime with it.
|
|
259
|
+
*
|
|
260
|
+
* Nothing here is a secret or a handler by construction. A guard's
|
|
261
|
+
* parameter is written as it was declared, so it has to be plain data
|
|
262
|
+
* (a permission string, a list, a reason); one that is not fails by API
|
|
263
|
+
* and guard name. A policy's key handler is never written: its `per`
|
|
264
|
+
* says "custom" and no more. A guard's input schema is never written
|
|
265
|
+
* either; its declaration says only which of the three input modes it
|
|
266
|
+
* has. Every table is sorted by name, so the module diffs by endpoint
|
|
267
|
+
* and never moves when registrations are reordered.
|
|
268
|
+
*/
|
|
269
|
+
apiOptionEntries(): LambderApiOptionEntries;
|
|
249
270
|
getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder<any>;
|
|
250
271
|
private getResolver;
|
|
251
272
|
getHandler(): LambderHandler;
|
package/dist/core/Lambder.js
CHANGED
|
@@ -15,6 +15,7 @@ import { LambderApiPipeline } from "../api/LambderApiPipeline.js";
|
|
|
15
15
|
import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
|
|
16
16
|
import { apiSignatureOf } from "../api/LambderApiSignature.js";
|
|
17
17
|
import { apiNameKeyOf } from "../shared/wire/LambderApiSignature.js";
|
|
18
|
+
import { assertPlainData } from "../shared/util/assertPlainData.js";
|
|
18
19
|
import { apiNotFoundAnswer, refusalAnswer, sessionExpiredAnswer, } from "../api/LambderApiEnvelope.js";
|
|
19
20
|
import { bindContextTools, createContext, isV2HttpEvent, } from "./LambderContext.js";
|
|
20
21
|
import { COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD } from "../shared/wire/LambderRequestPayload.js";
|
|
@@ -80,6 +81,8 @@ export default class Lambder {
|
|
|
80
81
|
apiDefinitions = new Map();
|
|
81
82
|
/** The guards map given at creation, kept for apiSignatures(): a guard's schema is part of the signature of every endpoint declaring it. */
|
|
82
83
|
guards;
|
|
84
|
+
/** The rate-limit policies given at creation, kept for apiOptionEntries(), which records each one less its key handler. */
|
|
85
|
+
rateLimitPolicies;
|
|
83
86
|
hookList = { "beforeRender": [], "afterRender": [], "fallback": [] };
|
|
84
87
|
createdHooks = [];
|
|
85
88
|
initPromise = null;
|
|
@@ -122,6 +125,7 @@ export default class Lambder {
|
|
|
122
125
|
}
|
|
123
126
|
const session = options.session;
|
|
124
127
|
this.guards = options.guards;
|
|
128
|
+
this.rateLimitPolicies = options.rateLimits?.policies;
|
|
125
129
|
this.pipeline = new LambderApiPipeline({
|
|
126
130
|
apiVersion: this.apiVersion,
|
|
127
131
|
minApiVersion: options.minApiVersion,
|
|
@@ -413,6 +417,71 @@ export default class Lambder {
|
|
|
413
417
|
entries.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
414
418
|
return entries;
|
|
415
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Every registered API's mode and declared options as plain data, with
|
|
422
|
+
* the rate-limit policies and guards they name reduced to what is not
|
|
423
|
+
* code: what writeApiOptions (lambder/build) writes to a module a client,
|
|
424
|
+
* a mock or a test imports instead of the server. The contract carries
|
|
425
|
+
* the same options as types; this is the same fact as a value, for code
|
|
426
|
+
* that decides something at runtime with it.
|
|
427
|
+
*
|
|
428
|
+
* Nothing here is a secret or a handler by construction. A guard's
|
|
429
|
+
* parameter is written as it was declared, so it has to be plain data
|
|
430
|
+
* (a permission string, a list, a reason); one that is not fails by API
|
|
431
|
+
* and guard name. A policy's key handler is never written: its `per`
|
|
432
|
+
* says "custom" and no more. A guard's input schema is never written
|
|
433
|
+
* either; its declaration says only which of the three input modes it
|
|
434
|
+
* has. Every table is sorted by name, so the module diffs by endpoint
|
|
435
|
+
* and never moves when registrations are reordered.
|
|
436
|
+
*/
|
|
437
|
+
apiOptionEntries() {
|
|
438
|
+
const apis = {};
|
|
439
|
+
for (const name of [...this.apiDefinitions.keys()].sort()) {
|
|
440
|
+
const { mode, guards, rateLimit, idempotency } = this.apiDefinitions.get(name);
|
|
441
|
+
const entry = { mode };
|
|
442
|
+
if (guards !== undefined) {
|
|
443
|
+
assertPlainData(guards, `the guards option of API "${name}"`);
|
|
444
|
+
entry.guards = guards;
|
|
445
|
+
}
|
|
446
|
+
if (rateLimit !== undefined) {
|
|
447
|
+
assertPlainData(rateLimit, `the rateLimit option of API "${name}"`);
|
|
448
|
+
entry.rateLimit = rateLimit;
|
|
449
|
+
}
|
|
450
|
+
if (idempotency !== undefined)
|
|
451
|
+
entry.idempotency = idempotency;
|
|
452
|
+
apis[name] = entry;
|
|
453
|
+
}
|
|
454
|
+
const rateLimitPolicies = {};
|
|
455
|
+
for (const name of Object.keys(this.rateLimitPolicies ?? {}).sort()) {
|
|
456
|
+
const { per, budget, chargeAt, errorMessage, ...windows } = this.rateLimitPolicies[name];
|
|
457
|
+
const entry = {};
|
|
458
|
+
for (const [window, limit] of Object.entries(windows)) {
|
|
459
|
+
if (limit !== undefined)
|
|
460
|
+
entry[window] = limit;
|
|
461
|
+
}
|
|
462
|
+
if (per !== undefined)
|
|
463
|
+
entry.per = per === "ip" || per === "session" ? per : "custom";
|
|
464
|
+
if (budget !== undefined)
|
|
465
|
+
entry.budget = budget;
|
|
466
|
+
if (chargeAt !== undefined)
|
|
467
|
+
entry.chargeAt = chargeAt;
|
|
468
|
+
if (errorMessage !== undefined) {
|
|
469
|
+
assertPlainData(errorMessage, `the errorMessage of rate-limit policy "${name}"`);
|
|
470
|
+
entry.errorMessage = errorMessage;
|
|
471
|
+
}
|
|
472
|
+
rateLimitPolicies[name] = entry;
|
|
473
|
+
}
|
|
474
|
+
const guards = {};
|
|
475
|
+
for (const name of Object.keys(this.guards ?? {}).sort()) {
|
|
476
|
+
const guard = this.guards[name];
|
|
477
|
+
guards[name] = {
|
|
478
|
+
input: guard.apiInput ? "apiInput" : guard.guardInput ? "guardInput" : "none",
|
|
479
|
+
session: guard.session === true,
|
|
480
|
+
runAt: guard.runAt ?? "beforeInputValidation",
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return { apis, rateLimitPolicies, guards };
|
|
484
|
+
}
|
|
416
485
|
getResponseBuilder(ctx) {
|
|
417
486
|
return new LambderResponseBuilder({
|
|
418
487
|
files: this.files,
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTr
|
|
|
12
12
|
export type { LambderApiTransport, LambderApiTransportRequest, LambderTransportFailureReason } from "./shared/transport/LambderApiTransport.js";
|
|
13
13
|
export { LambderCookieJar, parseSetCookie } from "./shared/transport/LambderCookieJar.js";
|
|
14
14
|
export { LambderExpiringMap, LambderExpiringMapFullError } from "./shared/util/LambderExpiringMap.js";
|
|
15
|
+
export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
16
|
+
export type { LambderBackoffTimerOptions } from "./shared/util/LambderBackoffTimer.js";
|
|
17
|
+
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
18
|
+
export type { LambderSignedClaimsOptions } from "./shared/util/LambderSignedClaims.js";
|
|
19
|
+
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
15
20
|
export type { LambderStoredCookie } from "./shared/transport/LambderCookieJar.js";
|
|
16
21
|
export { lambderHandlerTransport } from "./invoke/lambderHandlerTransport.js";
|
|
17
22
|
export type { LambderHandlerTransportOptions } from "./invoke/lambderHandlerTransport.js";
|
|
@@ -106,6 +111,12 @@ export type { LambderRateLimiter, LambderRateLimitWindow, LambderRateLimitPolicy
|
|
|
106
111
|
export { LambderDdbRateLimiter } from "./stores/LambderDdbRateLimiter.js";
|
|
107
112
|
export type { LambderDdbRateLimiterOptions } from "./stores/LambderDdbRateLimiter.js";
|
|
108
113
|
export { LambderMemoryRateLimiter } from "./stores/LambderMemoryRateLimiter.js";
|
|
114
|
+
export { LambderOneShotSecrets } from "./secrets/LambderOneShotSecrets.js";
|
|
115
|
+
export type { LambderOneShotSecretKind, LambderOneShotSecretsOptions, LambderOneShotIssueResult, LambderOneShotRedeemResult, LambderOneShotCodeKindNames, LambderOneShotTokenKindNames, } from "./secrets/LambderOneShotSecrets.js";
|
|
116
|
+
export type { LambderOneShotSecretStore, LambderOneShotSecretRecord, LambderOneShotSecretDraft, LambderOneShotSecretShape, LambderOneShotIssueOutcome, } from "./shared/contracts/LambderOneShotSecretStore.js";
|
|
117
|
+
export { LambderDdbOneShotSecretStore } from "./stores/LambderDdbOneShotSecretStore.js";
|
|
118
|
+
export type { LambderDdbOneShotSecretStoreOptions } from "./stores/LambderDdbOneShotSecretStore.js";
|
|
119
|
+
export { LambderMemoryOneShotSecretStore } from "./stores/LambderMemoryOneShotSecretStore.js";
|
|
109
120
|
export type { LambderIdempotencyStore, LambderIdempotencyBeginResult, LambderIdempotencyDoneRecord, } from "./shared/contracts/LambderIdempotencyStore.js";
|
|
110
121
|
export { LambderDdbIdempotencyStore } from "./stores/LambderDdbIdempotencyStore.js";
|
|
111
122
|
export type { LambderDdbIdempotencyStoreOptions } from "./stores/LambderDdbIdempotencyStore.js";
|
|
@@ -118,6 +129,8 @@ export type { LambderRateLimitKeyBuilder } from "./api/LambderApiRateLimits.js";
|
|
|
118
129
|
export type { LambderContextRateLimit, LambderContextRateLimitCheck, LambderRateLimitCheckResult, LambderChargeablePolicyNames, LambderChargeKeyArgs, LambderRateLimitKeyFn, LambderRateLimitPer, LambderRateLimitBudget, LambderRateLimitChargeAt, LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig, LambderAllowedPolicyNames, LambderRateLimitOption, } from "./api/LambderApiRateLimits.js";
|
|
119
130
|
export type { LambderApiIdempotencyConfig } from "./api/LambderApiIdempotency.js";
|
|
120
131
|
export type { LambderGuardsOptionValue, LambderRateLimitOverride, LambderRateLimitOptionValue, LambderApiIdempotencyOption, } from "./shared/wire/LambderApiOptionValues.js";
|
|
132
|
+
export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
|
|
133
|
+
export type { LambderApiOptionEntries, LambderApiOptionEntry, LambderRateLimitPolicyEntry, LambderGuardDeclarationEntry, LambderApisWithGuard, LambderApisGuardedBy, LambderApisWithMode, LambderGuardParamOf, } from "./shared/wire/LambderApiOptionEntries.js";
|
|
121
134
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
122
135
|
export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nDictionaryLoader, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
|
|
123
136
|
export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderApiNullAnswerConfig, LambderContractEntry, LambderMergeContract, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractKeysWithGuard, LambderJsonOf, LambderJsonOutputOf, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractRateLimitNames, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,11 @@ export { buildTransportEnvelope, LambderTransportFailure, isLambderTransportFail
|
|
|
9
9
|
export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTransport.js";
|
|
10
10
|
export { LambderCookieJar, parseSetCookie } from "./shared/transport/LambderCookieJar.js";
|
|
11
11
|
export { LambderExpiringMap, LambderExpiringMapFullError } from "./shared/util/LambderExpiringMap.js";
|
|
12
|
+
// Waiting longer after each failure, once: what the upload runner and an app's own retrying code climb.
|
|
13
|
+
export { LambderBackoffTimer } from "./shared/util/LambderBackoffTimer.js";
|
|
14
|
+
// Signed claims tokens, and the keyed digest and random secret for the secrets an app stores.
|
|
15
|
+
export { LambderSignedClaims, keyedDigest, randomSecret } from "./shared/util/LambderSignedClaims.js";
|
|
16
|
+
export { constantTimeEquals } from "./shared/util/LambderTextDigest.js";
|
|
12
17
|
export { lambderHandlerTransport } from "./invoke/lambderHandlerTransport.js";
|
|
13
18
|
export { synthesizeLambdaHttpEvent, decodeLambdaHttpResult, localLambdaContext, LAMBDER_INVOKE_HEADER, LAMBDER_INVOKED_BY_HEADER, LAMBDER_INVOKE_PROTOCOL, } from "./invoke/LambderLambdaEvent.js";
|
|
14
19
|
// The API core: the request, answer, envelope and pipeline both the server and the mock runtime run
|
|
@@ -71,6 +76,11 @@ export { LambderMemoryCache } from "./stores/LambderMemoryCache.js";
|
|
|
71
76
|
export { RATE_LIMIT_WINDOWS } from "./shared/contracts/LambderRateLimiter.js";
|
|
72
77
|
export { LambderDdbRateLimiter } from "./stores/LambderDdbRateLimiter.js";
|
|
73
78
|
export { LambderMemoryRateLimiter } from "./stores/LambderMemoryRateLimiter.js";
|
|
79
|
+
// One-shot secrets: codes and tokens handed out once and taken back once, the
|
|
80
|
+
// store interface they live in, and the DynamoDB and in-memory stores
|
|
81
|
+
export { LambderOneShotSecrets } from "./secrets/LambderOneShotSecrets.js";
|
|
82
|
+
export { LambderDdbOneShotSecretStore } from "./stores/LambderDdbOneShotSecretStore.js";
|
|
83
|
+
export { LambderMemoryOneShotSecretStore } from "./stores/LambderMemoryOneShotSecretStore.js";
|
|
74
84
|
export { LambderDdbIdempotencyStore } from "./stores/LambderDdbIdempotencyStore.js";
|
|
75
85
|
export { LambderMemoryIdempotencyStore } from "./stores/LambderMemoryIdempotencyStore.js";
|
|
76
86
|
// Declarative per-API policies: guards
|
|
@@ -78,6 +88,9 @@ export { lambderGuard, lambderRateLimitKey } from "./core/LambderPolicyBuilders.
|
|
|
78
88
|
export { lambderGuardBuilder } from "./api/LambderApiGuards.js";
|
|
79
89
|
// Declarative per-API policies: rate limits
|
|
80
90
|
export { lambderRateLimitKeyBuilder, rateLimitRefusal, DEFAULT_RATE_LIMIT_REFUSAL } from "./api/LambderApiRateLimits.js";
|
|
91
|
+
// The declared options as plain data: what apiOptionEntries() reports and
|
|
92
|
+
// writeApiOptions writes, and the readers over the generated tables.
|
|
93
|
+
export { apiGuardParam } from "./shared/wire/LambderApiOptionEntries.js";
|
|
81
94
|
// Typed translations (standalone, isomorphic)
|
|
82
95
|
export { createLambderI18n } from "./shared/LambderI18n.js";
|
|
83
96
|
export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
|
|
@@ -10,6 +10,7 @@ import { LambderMemoryIdempotencyStore } from "../stores/LambderMemoryIdempotenc
|
|
|
10
10
|
import { LambderMemorySessionStore } from "../stores/LambderMemorySessionStore.js";
|
|
11
11
|
import LambderSessionManager, { type LambderCreatedSession } from "../session/LambderSessionManager.js";
|
|
12
12
|
import type { LambderMockAppOptions, LambderMockIdempotencyOptions, LambderMockTransport, LambderMockTransportOptions } from "./LambderMockCreateOptions.js";
|
|
13
|
+
import type { LambderApiOptionEntry, LambderGuardDeclarationEntry } from "../shared/wire/LambderApiOptionEntries.js";
|
|
13
14
|
import type { LambderMockCallContext, LambderMockCallRecord, LambderMockEntry, LambderMockEntryInput, LambderMockFailure, LambderMockFailureReason, LambderMockHandler, LambderMockLatency, LambderMockListener, LambderMockPublicNames, LambderMockRateLimitPolicies, LambderMockRegistryCheck, LambderMockRestEntry, LambderMockSessionCallContext, LambderMockSessionNames, LambderMockSlice, LambderMockOverride } from "./LambderMockTypes.js";
|
|
14
15
|
/**
|
|
15
16
|
* The mock runtime: the API core (LambderApiPipeline, the same class the
|
|
@@ -23,9 +24,10 @@ import type { LambderMockCallContext, LambderMockCallRecord, LambderMockEntry, L
|
|
|
23
24
|
*
|
|
24
25
|
* Create one with initLambderMock<Contract, SessionData>().create(...),
|
|
25
26
|
* which fixes the contract and session types first so everything else is
|
|
26
|
-
* inferred from the options.
|
|
27
|
+
* inferred from the options. `TDerived` is true for a mock created with the
|
|
28
|
+
* generated `apiOptions` table, whose entries are their handlers alone.
|
|
27
29
|
*/
|
|
28
|
-
export declare class LambderMockApp<C extends LambderApiContractShape, S = any, G extends Record<string, LambderApiGuard<any, any, any>> = {}> {
|
|
30
|
+
export declare class LambderMockApp<C extends LambderApiContractShape, S = any, G extends Record<string, LambderApiGuard<any, any, any>> = {}, TDerived extends boolean = false> {
|
|
29
31
|
readonly apiVersion: string | null;
|
|
30
32
|
/** The memory stores, for assertions and reset; null for a subsystem that is off or backed by a store of yours. */
|
|
31
33
|
readonly sessionStore: LambderMemorySessionStore<S> | null;
|
|
@@ -61,9 +63,11 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
61
63
|
private readonly recorder;
|
|
62
64
|
/** Registered entries and the overrides over them (see LambderMockEntryRegistry). */
|
|
63
65
|
private readonly registry;
|
|
66
|
+
/** The server's declared options per API, when create() was given the generated table; the entries' declarations come from here. */
|
|
67
|
+
private readonly apiOptions;
|
|
64
68
|
/** The jars the runtime owns and what it planted in document.cookie (see LambderMockBrowserCookies). */
|
|
65
69
|
private readonly browserCookies;
|
|
66
|
-
constructor(options: LambderMockAppOptions<C, S, G>);
|
|
70
|
+
constructor(options: LambderMockAppOptions<C, S, G, LambderMockRateLimitPolicies<S>, boolean | LambderMockIdempotencyOptions<S>, {}, Record<string, LambderApiOptionEntry> | undefined>);
|
|
67
71
|
/**
|
|
68
72
|
* The registration-time checks every entry goes through, mocked or not:
|
|
69
73
|
* the ones the server runs on a definition, and the mock's own "a session
|
|
@@ -75,11 +79,21 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
75
79
|
* option, for a mistake fixed by one option at create().
|
|
76
80
|
*/
|
|
77
81
|
private assertEntryRegistration;
|
|
82
|
+
/**
|
|
83
|
+
* The table's entry for an endpoint, when create() was given one: null
|
|
84
|
+
* without a table, and a throw for a name the table does not hold or an
|
|
85
|
+
* entry registered under the other mode. The compiler already refuses
|
|
86
|
+
* both against the contract; this is where a stale table meets a caller
|
|
87
|
+
* the compiler did not see.
|
|
88
|
+
*/
|
|
89
|
+
private declaredOptionsOf;
|
|
90
|
+
/** The mode the apiOptions table gives a name, or null without a table or for a name it does not hold. */
|
|
91
|
+
private declaredModeOf;
|
|
78
92
|
private buildEntry;
|
|
79
93
|
/** A mock for a public endpoint: a handler, or the handler with the endpoint's declarations restated. */
|
|
80
|
-
publicApi<K extends LambderMockPublicNames<C>, TInputSchema extends z.ZodType = z.ZodType>(name: K, entry: LambderMockEntryInput<C, K, S, G, TInputSchema>): LambderMockEntry<C, K>;
|
|
94
|
+
publicApi<K extends LambderMockPublicNames<C>, TInputSchema extends z.ZodType = z.ZodType>(name: K, entry: LambderMockEntryInput<C, K, S, G, TInputSchema, TDerived>): LambderMockEntry<C, K>;
|
|
81
95
|
/** A mock for a session endpoint: the pipeline fetches the session before the handler runs, and refuses without one. */
|
|
82
|
-
sessionApi<K extends LambderMockSessionNames<C>, TInputSchema extends z.ZodType = z.ZodType>(name: K, entry: LambderMockEntryInput<C, K, S, G, TInputSchema>): LambderMockEntry<C, K>;
|
|
96
|
+
sessionApi<K extends LambderMockSessionNames<C>, TInputSchema extends z.ZodType = z.ZodType>(name: K, entry: LambderMockEntryInput<C, K, S, G, TInputSchema, TDerived>): LambderMockEntry<C, K>;
|
|
83
97
|
/**
|
|
84
98
|
* A public endpoint deliberately left without a mock; a call answers the
|
|
85
99
|
* notMocked refusal carrying the reason.
|
|
@@ -111,13 +125,15 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
111
125
|
* still refused, and an entry registered later (registerPartial, or a
|
|
112
126
|
* second register) takes its endpoint back from the rest.
|
|
113
127
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
128
|
+
* The mode of an unregistered name comes from the apiOptions table, when
|
|
129
|
+
* create() was given one, so an unmocked session endpoint still reads the
|
|
130
|
+
* session and a signed-out call answers sessionExpired as on the server.
|
|
131
|
+
* Without the table the mode is not knowable at runtime (the contract is
|
|
132
|
+
* a type), and a call it answers is processed as public: the protocol's
|
|
133
|
+
* pre-pass still runs, so a stale client still hears versionExpired, but
|
|
134
|
+
* a signed-out call to an unmocked session endpoint answers "not mocked"
|
|
135
|
+
* where the server answers sessionExpired. Declare an endpoint whose
|
|
136
|
+
* signed-out path a test cares about with sessionNotMocked there.
|
|
121
137
|
*/
|
|
122
138
|
restNotMocked(reason: string): LambderMockRestEntry;
|
|
123
139
|
private buildNotMockedEntry;
|
|
@@ -165,10 +181,11 @@ export declare class LambderMockApp<C extends LambderApiContractShape, S = any,
|
|
|
165
181
|
/**
|
|
166
182
|
* The entry that answers a name nothing registered, when register() was
|
|
167
183
|
* given a rest entry: the notMocked refusal carrying its reason, run
|
|
168
|
-
* through the pipeline
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
184
|
+
* through the pipeline under the mode the apiOptions table gives the
|
|
185
|
+
* name, and as a public endpoint where there is no table to say (the
|
|
186
|
+
* mode of an unregistered name cannot otherwise be recovered at runtime).
|
|
187
|
+
* Everything before dispatch still runs (the signature gate, the payload
|
|
188
|
+
* restore, and for a session endpoint the session read).
|
|
172
189
|
*/
|
|
173
190
|
private restNotMockedEntry;
|
|
174
191
|
/** The next call to the endpoint fails this way; several calls queue in order. */
|
|
@@ -332,5 +349,5 @@ export declare const initLambderMock: <C extends LambderApiContractShape, S = an
|
|
|
332
349
|
* dropped. `I` exists for the same reason `P` does, and
|
|
333
350
|
* LambderMockSurplusKeys puts the error back on the key.
|
|
334
351
|
*/
|
|
335
|
-
create<const G extends Record<string, LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>> = {}, const P extends LambderMockRateLimitPolicies<S> = {}, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S
|
|
352
|
+
create<const G extends Record<string, LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>> = {}, const P extends LambderMockRateLimitPolicies<S> = {}, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S>, const D extends Record<string, LambderGuardDeclarationEntry> = {}, const A extends Record<string, LambderApiOptionEntry> | undefined = undefined>(options: LambderMockAppOptions<C, S, G, P, I, D, A>): LambderMockApp<C, S, G, [A] extends [undefined] ? false : true>;
|
|
336
353
|
};
|