lambder 8.1.1 → 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 +164 -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/ContractTypePrinter.d.ts +39 -9
- package/dist/build/ContractTypePrinter.js +89 -31
- package/dist/build/generatedTables.d.ts +72 -0
- package/dist/build/generatedTables.js +99 -0
- package/dist/build/writeApiContract.d.ts +2 -1
- package/dist/build/writeApiContract.js +2 -1
- 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 +23 -32
- 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/util/escapeXmlText.d.ts +8 -0
- package/dist/shared/util/escapeXmlText.js +8 -0
- package/dist/shared/wire/LambderApiOptionEntries.d.ts +148 -0
- package/dist/shared/wire/LambderApiOptionEntries.js +35 -0
- package/dist/shared/wire/LambderUploadObjectFields.js +2 -2
- 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/stores/LambderMemoryUploadBucket.js +2 -2
- 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
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
|
};
|
|
@@ -47,7 +47,8 @@ const defaultCookieHost = () => globalThis.location?.host || "localhost";
|
|
|
47
47
|
*
|
|
48
48
|
* Create one with initLambderMock<Contract, SessionData>().create(...),
|
|
49
49
|
* which fixes the contract and session types first so everything else is
|
|
50
|
-
* inferred from the options.
|
|
50
|
+
* inferred from the options. `TDerived` is true for a mock created with the
|
|
51
|
+
* generated `apiOptions` table, whose entries are their handlers alone.
|
|
51
52
|
*/
|
|
52
53
|
export class LambderMockApp {
|
|
53
54
|
apiVersion;
|
|
@@ -85,10 +86,13 @@ export class LambderMockApp {
|
|
|
85
86
|
recorder;
|
|
86
87
|
/** Registered entries and the overrides over them (see LambderMockEntryRegistry). */
|
|
87
88
|
registry = new LambderMockEntryRegistry();
|
|
89
|
+
/** The server's declared options per API, when create() was given the generated table; the entries' declarations come from here. */
|
|
90
|
+
apiOptions;
|
|
88
91
|
/** The jars the runtime owns and what it planted in document.cookie (see LambderMockBrowserCookies). */
|
|
89
92
|
browserCookies = new LambderMockBrowserCookies();
|
|
90
93
|
constructor(options) {
|
|
91
94
|
this.apiVersion = options.apiVersion ?? null;
|
|
95
|
+
this.apiOptions = options.apiOptions ?? null;
|
|
92
96
|
this.failures = new LambderMockFailureInjector({ apiVersion: this.apiVersion, latency: options.latency ?? 0 });
|
|
93
97
|
this.recorder = new LambderMockCallRecorder({ callLogSize: options.callLogSize ?? DEFAULT_CALL_LOG_SIZE });
|
|
94
98
|
// The loopback address when nothing names a client, as for a request
|
|
@@ -196,13 +200,49 @@ export class LambderMockApp {
|
|
|
196
200
|
throw new Error(`LambderMockApp: session endpoint "${definition.name}" needs the sessions option at creation.`);
|
|
197
201
|
}
|
|
198
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* The table's entry for an endpoint, when create() was given one: null
|
|
205
|
+
* without a table, and a throw for a name the table does not hold or an
|
|
206
|
+
* entry registered under the other mode. The compiler already refuses
|
|
207
|
+
* both against the contract; this is where a stale table meets a caller
|
|
208
|
+
* the compiler did not see.
|
|
209
|
+
*/
|
|
210
|
+
declaredOptionsOf(name, mode) {
|
|
211
|
+
if (!this.apiOptions)
|
|
212
|
+
return null;
|
|
213
|
+
const declared = Object.prototype.hasOwnProperty.call(this.apiOptions, name) ? this.apiOptions[name] : undefined;
|
|
214
|
+
if (!declared) {
|
|
215
|
+
throw new Error(`LambderMockApp: "${name}" has no entry in the apiOptions table given to create(). The table predates this endpoint: regenerate it with writeApiOptions.`);
|
|
216
|
+
}
|
|
217
|
+
if (declared.mode !== mode) {
|
|
218
|
+
throw new Error(`LambderMockApp: "${name}" is a ${declared.mode} endpoint in the apiOptions table, registered here as a ${mode} one.`);
|
|
219
|
+
}
|
|
220
|
+
return declared;
|
|
221
|
+
}
|
|
222
|
+
/** The mode the apiOptions table gives a name, or null without a table or for a name it does not hold. */
|
|
223
|
+
declaredModeOf(name) {
|
|
224
|
+
if (!this.apiOptions || !Object.prototype.hasOwnProperty.call(this.apiOptions, name))
|
|
225
|
+
return null;
|
|
226
|
+
return this.apiOptions[name].mode;
|
|
227
|
+
}
|
|
199
228
|
buildEntry(name, mode, input) {
|
|
200
229
|
const options = (typeof input === "function" ? { handler: input } : input);
|
|
230
|
+
const declared = this.declaredOptionsOf(name, mode);
|
|
231
|
+
if (declared) {
|
|
232
|
+
// A caller the compiler did not see (a JavaScript slice, a cast)
|
|
233
|
+
// would otherwise have its restatement silently lose to the table.
|
|
234
|
+
for (const field of ["guards", "rateLimit", "idempotency"]) {
|
|
235
|
+
if (options[field] !== undefined) {
|
|
236
|
+
throw new Error(`LambderMockApp: "${name}" restates its ${field} option, which the apiOptions table given to create() already declares. Leave it out of the entry.`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const declarations = declared ?? options;
|
|
201
241
|
const definition = {
|
|
202
242
|
name, mode,
|
|
203
|
-
guards:
|
|
204
|
-
rateLimit:
|
|
205
|
-
idempotency:
|
|
243
|
+
guards: declarations.guards,
|
|
244
|
+
rateLimit: declarations.rateLimit,
|
|
245
|
+
idempotency: declarations.idempotency,
|
|
206
246
|
// No cast: the entry's schema is a z.ZodType, the same type the
|
|
207
247
|
// definition holds. A structural { safeParse } here would let a
|
|
208
248
|
// validator that is not a zod schema reach the 422 body as
|
|
@@ -257,18 +297,21 @@ export class LambderMockApp {
|
|
|
257
297
|
* still refused, and an entry registered later (registerPartial, or a
|
|
258
298
|
* second register) takes its endpoint back from the rest.
|
|
259
299
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
300
|
+
* The mode of an unregistered name comes from the apiOptions table, when
|
|
301
|
+
* create() was given one, so an unmocked session endpoint still reads the
|
|
302
|
+
* session and a signed-out call answers sessionExpired as on the server.
|
|
303
|
+
* Without the table the mode is not knowable at runtime (the contract is
|
|
304
|
+
* a type), and a call it answers is processed as public: the protocol's
|
|
305
|
+
* pre-pass still runs, so a stale client still hears versionExpired, but
|
|
306
|
+
* a signed-out call to an unmocked session endpoint answers "not mocked"
|
|
307
|
+
* where the server answers sessionExpired. Declare an endpoint whose
|
|
308
|
+
* signed-out path a test cares about with sessionNotMocked there.
|
|
267
309
|
*/
|
|
268
310
|
restNotMocked(reason) {
|
|
269
311
|
return { restNotMockedReason: reason };
|
|
270
312
|
}
|
|
271
313
|
buildNotMockedEntry(name, mode, reason) {
|
|
314
|
+
this.declaredOptionsOf(name, mode);
|
|
272
315
|
const definition = { name, mode };
|
|
273
316
|
this.assertEntryRegistration(definition);
|
|
274
317
|
return { name, mode, definition, handler: null, notMockedReason: reason };
|
|
@@ -352,16 +395,18 @@ export class LambderMockApp {
|
|
|
352
395
|
/**
|
|
353
396
|
* The entry that answers a name nothing registered, when register() was
|
|
354
397
|
* given a rest entry: the notMocked refusal carrying its reason, run
|
|
355
|
-
* through the pipeline
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
398
|
+
* through the pipeline under the mode the apiOptions table gives the
|
|
399
|
+
* name, and as a public endpoint where there is no table to say (the
|
|
400
|
+
* mode of an unregistered name cannot otherwise be recovered at runtime).
|
|
401
|
+
* Everything before dispatch still runs (the signature gate, the payload
|
|
402
|
+
* restore, and for a session endpoint the session read).
|
|
359
403
|
*/
|
|
360
404
|
restNotMockedEntry(apiName) {
|
|
361
405
|
const reason = this.registry.restNotMockedReason;
|
|
362
406
|
if (reason === null)
|
|
363
407
|
return null;
|
|
364
|
-
|
|
408
|
+
const mode = this.declaredModeOf(apiName) ?? "public";
|
|
409
|
+
return { name: apiName, mode, definition: { name: apiName, mode }, handler: null, notMockedReason: reason };
|
|
365
410
|
}
|
|
366
411
|
// -----------------------------------------------------------------------
|
|
367
412
|
// Control surface
|
|
@@ -581,12 +626,13 @@ export class LambderMockApp {
|
|
|
581
626
|
const id = this.recorder.nextCallId();
|
|
582
627
|
const registered = this.entryFor(request.apiName);
|
|
583
628
|
// The rest entry answers whatever nothing registered, when register()
|
|
584
|
-
// was given one. The mode reported
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
//
|
|
629
|
+
// was given one. The mode reported is the registered entry's, or the
|
|
630
|
+
// apiOptions table's for a name it holds, and null otherwise, exactly
|
|
631
|
+
// as for a name nothing knows: without the table a rest answer is
|
|
632
|
+
// processed as public, which is a property of the answer rather than
|
|
633
|
+
// a claim about the endpoint.
|
|
588
634
|
const entry = registered ?? this.restNotMockedEntry(request.apiName);
|
|
589
|
-
const mode = registered?.mode ??
|
|
635
|
+
const mode = registered?.mode ?? this.declaredModeOf(request.apiName);
|
|
590
636
|
const facts = this.callFacts(id, request, mode);
|
|
591
637
|
const startedAt = facts.startedAt;
|
|
592
638
|
const ctx = this.createContext(request);
|
|
@@ -3,8 +3,9 @@ import type { LambderApiSignatureMap } from "../shared/wire/LambderApiSignature.
|
|
|
3
3
|
import type { LambderApiResponseConfig } from "../shared/wire/LambderApiContract.js";
|
|
4
4
|
import type { LambderHttpStatusCode } from "../shared/wire/LambderHttpStatus.js";
|
|
5
5
|
import type { MaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
6
|
-
import type { LambderContractGuardNames, LambderContractIdempotencyOf, LambderContractKeysWithMode, LambderContractRateLimitNames, LambderContractRateLimitOf } from "../shared/wire/LambderApiContract.js";
|
|
6
|
+
import type { LambderContractGuardNames, LambderContractIdempotencyOf, LambderContractKeysWithMode, LambderContractMode, LambderContractRateLimitNames, LambderContractRateLimitOf } from "../shared/wire/LambderApiContract.js";
|
|
7
7
|
import type { LambderApiGuard } from "../api/LambderApiGuards.js";
|
|
8
|
+
import type { LambderApiOptionEntry, LambderGuardDeclarationEntry } from "../shared/wire/LambderApiOptionEntries.js";
|
|
8
9
|
import type { LambderApiRateLimitPolicyConfig } from "../api/LambderApiRateLimits.js";
|
|
9
10
|
import type { LambderApiRequest } from "../api/LambderApiRequest.js";
|
|
10
11
|
import type { LambderApiTransport } from "../shared/transport/LambderApiTransport.js";
|
|
@@ -114,6 +115,37 @@ type LambderMockRateLimitsOptions<C, S, P extends LambderMockRateLimitPolicies<S
|
|
|
114
115
|
/** Let a call through when the limiter throws, instead of refusing it. Default: true. */
|
|
115
116
|
failOpen?: boolean;
|
|
116
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* The shape a mock guard has to have to stand in for a server guard the
|
|
120
|
+
* generated `guardDeclarations` table describes: the same input mode and the
|
|
121
|
+
* same session requirement. The contract cannot say these for every guard
|
|
122
|
+
* (it names a guardInput's shape, and nothing about a guard fed from the
|
|
123
|
+
* payload or from nothing), so a mock guard that reads a payload slice the
|
|
124
|
+
* server's guard never sees, or that requires a session where the server's
|
|
125
|
+
* does not, would run and decide differently without this.
|
|
126
|
+
*/
|
|
127
|
+
export type LambderMockGuardShapeOf<D> = (D extends {
|
|
128
|
+
input: "apiInput";
|
|
129
|
+
} ? {
|
|
130
|
+
apiInput: z.ZodType;
|
|
131
|
+
} : D extends {
|
|
132
|
+
input: "guardInput";
|
|
133
|
+
} ? {
|
|
134
|
+
guardInput: z.ZodType;
|
|
135
|
+
} : {
|
|
136
|
+
apiInput?: undefined;
|
|
137
|
+
guardInput?: undefined;
|
|
138
|
+
}) & (D extends {
|
|
139
|
+
session: true;
|
|
140
|
+
} ? {
|
|
141
|
+
session: true;
|
|
142
|
+
} : {
|
|
143
|
+
session?: false | undefined;
|
|
144
|
+
});
|
|
145
|
+
/** Each mock guard the declarations know held to its declared shape; a guard the table does not have is free. */
|
|
146
|
+
type LambderMockGuardsAgree<G, D> = {
|
|
147
|
+
[N in keyof G & keyof D]: G[N] extends LambderMockGuardShapeOf<D[N]> ? unknown : LambderMockGuardShapeOf<D[N]>;
|
|
148
|
+
};
|
|
117
149
|
/**
|
|
118
150
|
* The guards option: required whenever the contract declares any guard name,
|
|
119
151
|
* omittable only for a contract that declares none.
|
|
@@ -123,12 +155,12 @@ type LambderMockRateLimitsOptions<C, S, P extends LambderMockRateLimitPolicies<S
|
|
|
123
155
|
* would answer 200 here. Optional, it would be the droppable half of exactly
|
|
124
156
|
* the check it exists for.
|
|
125
157
|
*/
|
|
126
|
-
type LambderMockGuardsOption<C, S, G> = [
|
|
158
|
+
type LambderMockGuardsOption<C, S, G, D> = [
|
|
127
159
|
LambderContractGuardNames<C>
|
|
128
160
|
] extends [never] ? {
|
|
129
|
-
guards?: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G
|
|
161
|
+
guards?: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D>>;
|
|
130
162
|
} : {
|
|
131
|
-
guards: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G
|
|
163
|
+
guards: G & LambderMockGuards<C, S> & LambderMockGuardShapes<S, G> & LambderMockGuardsAgree<G, NoInfer<D>>;
|
|
132
164
|
};
|
|
133
165
|
/**
|
|
134
166
|
* The sessions, idempotency and rateLimits options: each required whenever
|
|
@@ -168,7 +200,38 @@ type LambderMockRateLimitsOption<C, S, P extends LambderMockRateLimitPolicies<S>
|
|
|
168
200
|
type LambderMockGuardShapes<S, G> = {
|
|
169
201
|
[N in keyof G]: LambderMockSurplusKeys<G[N], LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>>;
|
|
170
202
|
};
|
|
171
|
-
|
|
203
|
+
/**
|
|
204
|
+
* What the generated `apiOptions` table has to hold to stand in for the
|
|
205
|
+
* restated declarations of a contract's entries: an entry for every endpoint
|
|
206
|
+
* the contract declares, of the endpoint's mode. A table generated before an
|
|
207
|
+
* endpoint was added, or before one changed mode, is a compile error at the
|
|
208
|
+
* option rather than a throw when that endpoint's entry registers.
|
|
209
|
+
*/
|
|
210
|
+
export type LambderMockApiOptionsCover<C> = {
|
|
211
|
+
[K in keyof C & string]: {
|
|
212
|
+
mode: LambderContractMode<C, K>;
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
export type LambderMockAppOptions<C, S, G, P extends LambderMockRateLimitPolicies<S> = LambderMockRateLimitPolicies<S>, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S>, D extends Record<string, LambderGuardDeclarationEntry> = {}, A extends Record<string, LambderApiOptionEntry> | undefined = undefined> = LambderMockGuardsOption<C, S, G, D> & LambderMockSessionsOption<C, S> & LambderMockIdempotencyOption<C, S, I> & LambderMockRateLimitsOption<C, S, P> & {
|
|
216
|
+
/**
|
|
217
|
+
* The server's guard declarations, as the generated options module
|
|
218
|
+
* exports them (`guardDeclarations`). Given, every mock guard of a name
|
|
219
|
+
* the table has is held to its input mode and session requirement at
|
|
220
|
+
* the `guards` option (see LambderMockGuardShapeOf). Nothing runs on it.
|
|
221
|
+
*/
|
|
222
|
+
guardDeclarations?: D;
|
|
223
|
+
/**
|
|
224
|
+
* The server's declared options per API, as the generated options module
|
|
225
|
+
* exports them (`apiOptions`). Given, every entry's guards, rateLimit and
|
|
226
|
+
* idempotency are read off the table rather than restated: an entry is
|
|
227
|
+
* its handler (and an input schema, if it has one), a restated option is
|
|
228
|
+
* a compile error, and an endpoint whose mode the table and the builder
|
|
229
|
+
* disagree on is refused at registration. A restNotMocked answer reads
|
|
230
|
+
* the endpoint's mode off the table too, so a session endpoint nothing
|
|
231
|
+
* mocks still reads the session first. The table has to cover the
|
|
232
|
+
* contract (see LambderMockApiOptionsCover).
|
|
233
|
+
*/
|
|
234
|
+
apiOptions?: A & LambderMockApiOptionsCover<C>;
|
|
172
235
|
/** Stamped on every answer's envelope as apiVersion, as the server's option is. */
|
|
173
236
|
apiVersion?: string;
|
|
174
237
|
/** The version floor, as on the server: a call naming a lower `version` answers versionExpired whatever its signature says. */
|