lambder 7.3.1 → 8.1.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 +1047 -3
- package/README.md +46 -21
- package/dist/api/LambderApiAnswer.d.ts +18 -22
- package/dist/api/LambderApiAnswer.js +6 -7
- package/dist/api/LambderApiCallContext.d.ts +21 -8
- package/dist/api/LambderApiCallContext.js +22 -4
- package/dist/api/LambderApiDefinition.d.ts +4 -3
- package/dist/api/LambderApiEnvelope.d.ts +14 -9
- package/dist/api/LambderApiEnvelope.js +33 -34
- package/dist/api/LambderApiGuards.d.ts +78 -51
- package/dist/api/LambderApiGuards.js +34 -36
- package/dist/api/LambderApiIdempotency.d.ts +68 -62
- package/dist/api/LambderApiIdempotency.js +214 -151
- package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
- package/dist/api/LambderApiOutputValidationError.js +50 -0
- package/dist/api/LambderApiPipeline.d.ts +47 -38
- package/dist/api/LambderApiPipeline.js +122 -63
- package/dist/api/LambderApiRateLimits.d.ts +201 -54
- package/dist/api/LambderApiRateLimits.js +185 -108
- package/dist/api/LambderApiRequest.d.ts +27 -21
- package/dist/api/LambderApiRequest.js +26 -19
- package/dist/api/LambderApiSignature.d.ts +12 -15
- package/dist/api/LambderApiSignature.js +28 -51
- package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
- package/dist/api/LambderApiValidationRefusal.js +10 -10
- package/dist/build/ContractTypePrinter.d.ts +85 -0
- package/dist/build/ContractTypePrinter.js +402 -0
- package/dist/build/freshProcessVerifier.d.ts +13 -0
- package/dist/build/freshProcessVerifier.js +19 -0
- package/dist/build/moduleLocation.d.ts +11 -0
- package/dist/build/moduleLocation.js +6 -0
- package/dist/build/writeApiContract.d.ts +78 -0
- package/dist/build/writeApiContract.js +302 -0
- package/dist/build/writeApiSignatures.d.ts +114 -0
- package/dist/build/writeApiSignatures.js +217 -0
- package/dist/build/writeFileAtomically.d.ts +8 -0
- package/dist/build/writeFileAtomically.js +22 -0
- package/dist/build.d.ts +14 -0
- package/dist/build.js +11 -0
- package/dist/client/LambderCaller.d.ts +13 -44
- package/dist/client/LambderCaller.js +77 -84
- package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
- package/dist/client/LambderReloadLoopBreaker.js +90 -46
- package/dist/client/LambderUploadRunner.d.ts +96 -0
- package/dist/client/LambderUploadRunner.js +234 -0
- package/dist/client/lambderFetchTransport.d.ts +4 -1
- package/dist/client/lambderFetchTransport.js +52 -28
- package/dist/client.d.ts +9 -3
- package/dist/client.js +6 -1
- package/dist/core/Lambder.d.ts +143 -79
- package/dist/core/Lambder.js +350 -231
- package/dist/core/LambderContext.d.ts +82 -15
- package/dist/core/LambderContext.js +107 -20
- package/dist/core/LambderCors.d.ts +21 -3
- package/dist/core/LambderCors.js +35 -16
- package/dist/core/LambderCrashHandling.d.ts +40 -0
- package/dist/core/LambderCrashHandling.js +97 -0
- package/dist/core/LambderCreateOptions.d.ts +151 -75
- package/dist/core/LambderCreateOptions.js +16 -23
- package/dist/core/LambderFiles.d.ts +21 -7
- package/dist/core/LambderFiles.js +62 -34
- package/dist/core/LambderIndexHtml.js +12 -11
- package/dist/core/LambderPolicyBuilders.d.ts +17 -5
- package/dist/core/LambderPolicyBuilders.js +17 -5
- package/dist/core/LambderPublicFiles.d.ts +11 -5
- package/dist/core/LambderPublicFiles.js +32 -4
- package/dist/core/LambderRequestPath.d.ts +43 -0
- package/dist/core/LambderRequestPath.js +63 -0
- package/dist/core/LambderResponse.d.ts +26 -5
- package/dist/core/LambderResponse.js +157 -70
- package/dist/core/LambderResponseBuilder.d.ts +49 -4
- package/dist/core/LambderResponseBuilder.js +64 -3
- package/dist/core/LambderRouting.d.ts +2 -3
- package/dist/core/LambderRouting.js +22 -7
- package/dist/core/LambderTemplatingEngine.js +211 -32
- package/dist/index.d.ts +25 -8
- package/dist/index.js +13 -4
- package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
- package/dist/invoke/LambderInvokeCaller.js +76 -66
- package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
- package/dist/invoke/LambderInvokeOutcome.js +9 -22
- package/dist/invoke/LambderLambdaEvent.d.ts +29 -9
- package/dist/invoke/LambderLambdaEvent.js +40 -22
- package/dist/invoke/lambderHandlerTransport.d.ts +9 -10
- package/dist/invoke/lambderHandlerTransport.js +15 -18
- package/dist/mock/LambderMockApp.d.ts +67 -83
- package/dist/mock/LambderMockApp.js +167 -153
- package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
- package/dist/mock/LambderMockBrowserCookies.js +24 -28
- package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
- package/dist/mock/LambderMockCallRecorder.js +19 -28
- package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
- package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
- package/dist/mock/LambderMockEntryRegistry.js +24 -29
- package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
- package/dist/mock/LambderMockFailureInjector.js +3 -6
- package/dist/mock/LambderMockTypes.d.ts +78 -108
- package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
- package/dist/mock/lambderMockInvokeTransport.js +11 -10
- package/dist/mock/lambderMockMswHandler.d.ts +43 -33
- package/dist/mock/lambderMockMswHandler.js +50 -39
- package/dist/mock/lambderMockUploadMswHandler.d.ts +26 -0
- package/dist/mock/lambderMockUploadMswHandler.js +28 -0
- package/dist/mock.d.ts +4 -1
- package/dist/mock.js +6 -3
- package/dist/session/LambderSessionController.d.ts +108 -89
- package/dist/session/LambderSessionController.js +187 -168
- package/dist/session/LambderSessionCrypto.d.ts +16 -7
- package/dist/session/LambderSessionCrypto.js +26 -12
- package/dist/session/LambderSessionManager.d.ts +124 -46
- package/dist/session/LambderSessionManager.js +262 -137
- package/dist/shared/LambderHtml.d.ts +42 -3
- package/dist/shared/LambderHtml.js +127 -7
- package/dist/shared/LambderHtmlPositions.d.ts +173 -0
- package/dist/shared/LambderHtmlPositions.js +652 -0
- package/dist/shared/LambderI18n.d.ts +10 -11
- package/dist/shared/LambderI18n.js +33 -21
- package/dist/shared/contracts/LambderCache.d.ts +66 -0
- package/dist/shared/contracts/LambderCache.js +11 -0
- package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
- package/dist/shared/contracts/LambderFileSource.js +5 -8
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
- package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
- package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
- package/dist/shared/contracts/LambderRateLimiter.js +4 -5
- package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
- package/dist/shared/contracts/LambderSessionStore.js +5 -6
- package/dist/shared/contracts/LambderUploadBucket.d.ts +154 -0
- package/dist/shared/contracts/LambderUploadBucket.js +74 -0
- package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
- package/dist/shared/transport/LambderApiTransport.js +7 -7
- package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
- package/dist/shared/transport/LambderCookieJar.js +54 -66
- package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
- package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
- package/dist/shared/util/LambderCallAbort.d.ts +5 -5
- package/dist/shared/util/LambderCallAbort.js +5 -5
- package/dist/shared/util/LambderClientIp.d.ts +27 -11
- package/dist/shared/util/LambderClientIp.js +96 -13
- package/dist/shared/util/LambderContentDisposition.d.ts +10 -0
- package/dist/shared/util/LambderContentDisposition.js +13 -0
- package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
- package/dist/shared/util/LambderExpiringMap.js +41 -57
- package/dist/shared/util/LambderNodeModules.js +6 -7
- package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
- package/dist/shared/util/LambderOptionChecks.js +4 -4
- package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
- package/dist/shared/util/LambderResponseBrand.js +5 -5
- package/dist/shared/util/LambderTextDigest.d.ts +7 -5
- package/dist/shared/util/LambderTextDigest.js +11 -5
- package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
- package/dist/shared/util/LambderTypeUtilities.js +3 -3
- package/dist/shared/util/boundKeyField.d.ts +20 -0
- package/dist/shared/util/boundKeyField.js +34 -0
- package/dist/shared/util/canonicalJson.d.ts +11 -0
- package/dist/shared/util/canonicalJson.js +28 -0
- package/dist/shared/util/joinKeyFields.d.ts +20 -0
- package/dist/shared/util/joinKeyFields.js +22 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
- package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
- package/dist/shared/wire/LambderApiContract.d.ts +98 -53
- package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
- package/dist/shared/wire/LambderApiOutcome.js +48 -23
- package/dist/shared/wire/LambderApiRefusal.d.ts +45 -27
- package/dist/shared/wire/LambderApiRefusal.js +42 -7
- package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
- package/dist/shared/wire/LambderApiSignature.js +16 -19
- package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
- package/dist/shared/wire/LambderCallOptions.js +9 -11
- package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
- package/dist/shared/wire/LambderCompressionCodec.js +31 -36
- package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
- package/dist/shared/wire/LambderCompressionOption.js +9 -9
- package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
- package/dist/shared/wire/LambderCrashDetail.js +12 -15
- package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
- package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
- package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
- package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
- package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
- package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
- package/dist/shared/wire/LambderInvokeApiId.js +27 -0
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +6 -7
- package/dist/shared/wire/LambderOutcomeAssertions.js +6 -7
- package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
- package/dist/shared/wire/LambderRequestPayload.js +4 -6
- package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -0
- package/dist/shared/wire/LambderUploadObjectFields.js +24 -0
- package/dist/shared/wire/LambderUploadRefusal.d.ts +9 -0
- package/dist/shared/wire/LambderUploadRefusal.js +18 -0
- package/dist/shared/wire/LambderUploadSchemas.d.ts +12 -0
- package/dist/shared/wire/LambderUploadSchemas.js +30 -0
- package/dist/stores/LambderCacheFiller.d.ts +48 -0
- package/dist/stores/LambderCacheFiller.js +119 -0
- package/dist/stores/LambderCacheKeys.d.ts +26 -0
- package/dist/stores/LambderCacheKeys.js +54 -0
- package/dist/stores/LambderCacheValues.d.ts +45 -0
- package/dist/stores/LambderCacheValues.js +74 -0
- package/dist/stores/LambderDdbCache.d.ts +121 -56
- package/dist/stores/LambderDdbCache.js +528 -225
- package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
- package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
- package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
- package/dist/stores/LambderDdbRateLimiter.js +151 -39
- package/dist/stores/LambderDdbSdk.d.ts +43 -31
- package/dist/stores/LambderDdbSdk.js +80 -38
- package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
- package/dist/stores/LambderDdbSessionStore.js +119 -47
- package/dist/stores/LambderHttpFileSource.d.ts +15 -6
- package/dist/stores/LambderHttpFileSource.js +15 -13
- package/dist/stores/LambderMemoryCache.d.ts +49 -0
- package/dist/stores/LambderMemoryCache.js +113 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
- package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
- package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
- package/dist/stores/LambderMemoryRateLimiter.js +14 -13
- package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
- package/dist/stores/LambderMemorySessionStore.js +38 -19
- package/dist/stores/LambderMemoryUploadBucket.d.ts +99 -0
- package/dist/stores/LambderMemoryUploadBucket.js +219 -0
- package/dist/stores/LambderS3FileSource.d.ts +21 -6
- package/dist/stores/LambderS3FileSource.js +12 -7
- package/dist/stores/LambderS3UploadBucket.d.ts +73 -0
- package/dist/stores/LambderS3UploadBucket.js +144 -0
- package/dist/stores/LambderSdkInstallHint.d.ts +11 -0
- package/dist/stores/LambderSdkInstallHint.js +14 -0
- package/dist/testing/LambderTestApp.d.ts +23 -25
- package/dist/testing/LambderTestApp.js +22 -24
- package/dist/testing/LambderTestVisitor.d.ts +10 -12
- package/dist/testing/LambderTestVisitor.js +15 -15
- package/dist/testing.d.ts +3 -0
- package/dist/testing.js +2 -0
- package/package.json +26 -3
- package/dist/api/LambderApiPolicyEngine.d.ts +0 -47
- package/dist/api/LambderApiPolicyEngine.js +0 -85
- package/dist/shared/util/LambderKeyFields.d.ts +0 -32
- package/dist/shared/util/LambderKeyFields.js +0 -34
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import { parseSetCookie } from "../shared/transport/LambderCookieJar.js";
|
|
2
2
|
/**
|
|
3
3
|
* Where the runtime's cookies live outside its own answers: the jars it built
|
|
4
|
-
* for itself, and the copies it planted in the page's
|
|
4
|
+
* for itself, and the copies it planted in the page's cookie storage. A
|
|
5
|
+
* collaborator of LambderMockApp that owns state nothing else touches, meets
|
|
6
|
+
* the runtime at two calls (an answer coming back, reset()), and holds the
|
|
7
|
+
* one outside dependency, `document`, keeping the app free of browser
|
|
8
|
+
* conditionals.
|
|
5
9
|
*
|
|
6
|
-
* The
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Both the direct transport's "document" mode and the MSW adapter come through
|
|
13
|
-
* one instance, so there is one mirror implementation and one record of what
|
|
14
|
-
* was planted. They carried a copy each before: the MSW copy dropped `Secure`
|
|
15
|
-
* on a page that is not a secure context and the transport's did not, so on
|
|
16
|
-
* plain http (device testing on a LAN address) the browser silently refused
|
|
17
|
-
* the CSRF cookie and every session call failed its CSRF check with nothing in
|
|
18
|
-
* any log to say why.
|
|
10
|
+
* The direct transport's "document" mode and the MSW adapter share one
|
|
11
|
+
* instance, so they cannot disagree on dropping `Secure`: on plain http
|
|
12
|
+
* (device testing on a LAN address) a kept `Secure` makes the browser
|
|
13
|
+
* silently refuse the CSRF cookie, failing every session call's CSRF check
|
|
14
|
+
* with nothing in any log.
|
|
19
15
|
*/
|
|
20
16
|
export class LambderMockBrowserCookies {
|
|
21
17
|
/** The jars transport() and the adapters built for themselves, which reset() is therefore free to empty. */
|
|
@@ -24,9 +20,9 @@ export class LambderMockBrowserCookies {
|
|
|
24
20
|
mirroredCookies = new Map();
|
|
25
21
|
/**
|
|
26
22
|
* Takes a jar built for the runtime as the runtime's own, so reset()
|
|
27
|
-
* empties it with the rest. A jar the app passed in stays the app's,
|
|
28
|
-
*
|
|
29
|
-
*
|
|
23
|
+
* empties it with the rest. A jar the app passed in stays the app's, like
|
|
24
|
+
* an app-supplied session store: the runtime does not know what else
|
|
25
|
+
* holds it.
|
|
30
26
|
*/
|
|
31
27
|
adoptJar(jar) {
|
|
32
28
|
this.ownedJars.add(jar);
|
|
@@ -35,13 +31,13 @@ export class LambderMockBrowserCookies {
|
|
|
35
31
|
* Mirrors an answer's non-HttpOnly cookies into document.cookie and
|
|
36
32
|
* remembers them for reset().
|
|
37
33
|
*
|
|
38
|
-
* HttpOnly cookies are skipped
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `__Host-` or `__Secure-`
|
|
43
|
-
*
|
|
44
|
-
*
|
|
34
|
+
* HttpOnly cookies are skipped as a real browser skips them; the jar is
|
|
35
|
+
* the store no script can reach. `Secure` is dropped where the page is not
|
|
36
|
+
* a secure context, since the browser would refuse the write and plain
|
|
37
|
+
* http on a LAN address has to keep working. The browser then discards a
|
|
38
|
+
* `__Host-` or `__Secure-` name for breaking its prefix rule, which is
|
|
39
|
+
* correct: such a name cannot work on plain http, and localhost is a
|
|
40
|
+
* secure context.
|
|
45
41
|
*/
|
|
46
42
|
mirrorSetCookies(setCookies) {
|
|
47
43
|
if (typeof document === "undefined")
|
|
@@ -59,10 +55,10 @@ export class LambderMockBrowserCookies {
|
|
|
59
55
|
* Empties the jars the runtime owns and expires what it mirrored, which is
|
|
60
56
|
* what clearing the page's cookie storage would do.
|
|
61
57
|
*
|
|
62
|
-
*
|
|
63
|
-
* while a jar still holds
|
|
64
|
-
*
|
|
65
|
-
*
|
|
58
|
+
* Part of a rewind as much as the session store is: emptying the store
|
|
59
|
+
* while a jar still holds one of its session tokens leaves the next call
|
|
60
|
+
* carrying a dead session, which reads as signed in until the answer says
|
|
61
|
+
* sessionExpired.
|
|
66
62
|
*/
|
|
67
63
|
reset() {
|
|
68
64
|
for (const jar of this.ownedJars)
|
|
@@ -11,11 +11,10 @@ export type LambderMockCallFacts = {
|
|
|
11
11
|
startedAt: number;
|
|
12
12
|
/**
|
|
13
13
|
* The call's request, read when the call settles rather than copied when it
|
|
14
|
-
* starts. The pipeline rewrites the payload as the call goes
|
|
15
|
-
* one
|
|
16
|
-
* schema
|
|
17
|
-
*
|
|
18
|
-
* were the ones logged as `undefined`.
|
|
14
|
+
* starts. The pipeline rewrites the payload as the call goes (restoring a
|
|
15
|
+
* compressed one, replacing it with the parsed value under an input
|
|
16
|
+
* schema); a copy taken up front would hold the wire fields and log every
|
|
17
|
+
* compressed payload as `undefined`.
|
|
19
18
|
*/
|
|
20
19
|
request: {
|
|
21
20
|
payload: unknown;
|
|
@@ -37,20 +36,17 @@ type LambderMockCallEnding = {
|
|
|
37
36
|
* emitted to, and the bounded log of completed calls.
|
|
38
37
|
*
|
|
39
38
|
* One of the four pieces of state LambderMockApp holds that nothing else
|
|
40
|
-
* touches.
|
|
41
|
-
*
|
|
42
|
-
* they are the surface a dev panel reads.
|
|
39
|
+
* touches. `subscribe` and `calls` are one-line delegations on the app,
|
|
40
|
+
* because they are the surface a dev panel reads.
|
|
43
41
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* is read to rule out.
|
|
42
|
+
* `settle` ends a call in one place: classification, redaction, the event and
|
|
43
|
+
* the log row. A record built separately at each of the three exits would
|
|
44
|
+
* drift between them, which is exactly what a log is read to rule out.
|
|
48
45
|
*
|
|
49
|
-
* `calls` hands out copies
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* and an Error carries nothing worth protecting.
|
|
46
|
+
* `calls` hands out deep copies, so a reader that sorts guardsRun or deletes
|
|
47
|
+
* a header does not edit what the next reader sees. The Error is passed by
|
|
48
|
+
* reference: a clone would lose the class a test asserts on, and an Error
|
|
49
|
+
* carries nothing worth protecting.
|
|
54
50
|
*/
|
|
55
51
|
export declare class LambderMockCallRecorder {
|
|
56
52
|
private readonly listeners;
|
|
@@ -71,11 +67,8 @@ export declare class LambderMockCallRecorder {
|
|
|
71
67
|
/**
|
|
72
68
|
* Ends one call: reads how it went, redacts what a log has no business
|
|
73
69
|
* keeping, and emits the response event and the log row from one object,
|
|
74
|
-
* so the two cannot
|
|
75
|
-
*
|
|
76
|
-
* The event and the row carry copies of their own, so a listener that
|
|
77
|
-
* edits the event it was handed is not editing the row the log keeps; the
|
|
78
|
-
* row is copied again on the way in and on the way out.
|
|
70
|
+
* so the two cannot disagree about the call. Each carries its own copies,
|
|
71
|
+
* so a listener editing its event does not edit the logged row.
|
|
79
72
|
*/
|
|
80
73
|
settle(facts: LambderMockCallFacts, ending: LambderMockCallEnding): void;
|
|
81
74
|
private push;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { LAMBDER_REFUSAL_CODES } from "../shared/wire/LambderApiRefusal.js";
|
|
2
2
|
/**
|
|
3
3
|
* Set-Cookie values are redacted in the log: the name stays so a reader can
|
|
4
|
-
* see
|
|
5
|
-
*
|
|
6
|
-
* place to keep it.
|
|
4
|
+
* see a session cookie was written, the value goes, since a live session
|
|
5
|
+
* token does not belong in a panel a developer renders or a test snapshots.
|
|
7
6
|
*/
|
|
8
7
|
const loggedHeaders = (headers) => {
|
|
9
8
|
const copy = {};
|
|
@@ -16,10 +15,9 @@ const loggedHeaders = (headers) => {
|
|
|
16
15
|
};
|
|
17
16
|
/**
|
|
18
17
|
* A logged value copied, so a reader that reaches into a record cannot edit
|
|
19
|
-
* what the next reader sees.
|
|
20
|
-
*
|
|
21
|
-
* payload
|
|
22
|
-
* failing the read.
|
|
18
|
+
* what the next reader sees. Everything logged arrived as JSON, so
|
|
19
|
+
* structuredClone fits; a value it refuses (a function on a hand-built
|
|
20
|
+
* request's payload) is handed over as is rather than failing the read.
|
|
23
21
|
*/
|
|
24
22
|
const cloneLoggedValue = (value) => {
|
|
25
23
|
if (value === null || typeof value !== "object")
|
|
@@ -73,20 +71,17 @@ const classifyAnswer = (answer, envelope) => {
|
|
|
73
71
|
* emitted to, and the bounded log of completed calls.
|
|
74
72
|
*
|
|
75
73
|
* One of the four pieces of state LambderMockApp holds that nothing else
|
|
76
|
-
* touches.
|
|
77
|
-
*
|
|
78
|
-
* they are the surface a dev panel reads.
|
|
74
|
+
* touches. `subscribe` and `calls` are one-line delegations on the app,
|
|
75
|
+
* because they are the surface a dev panel reads.
|
|
79
76
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* is read to rule out.
|
|
77
|
+
* `settle` ends a call in one place: classification, redaction, the event and
|
|
78
|
+
* the log row. A record built separately at each of the three exits would
|
|
79
|
+
* drift between them, which is exactly what a log is read to rule out.
|
|
84
80
|
*
|
|
85
|
-
* `calls` hands out copies
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* and an Error carries nothing worth protecting.
|
|
81
|
+
* `calls` hands out deep copies, so a reader that sorts guardsRun or deletes
|
|
82
|
+
* a header does not edit what the next reader sees. The Error is passed by
|
|
83
|
+
* reference: a clone would lose the class a test asserts on, and an Error
|
|
84
|
+
* carries nothing worth protecting.
|
|
90
85
|
*/
|
|
91
86
|
export class LambderMockCallRecorder {
|
|
92
87
|
listeners = new Map();
|
|
@@ -127,10 +122,9 @@ export class LambderMockCallRecorder {
|
|
|
127
122
|
listener(event);
|
|
128
123
|
}
|
|
129
124
|
catch (err) {
|
|
130
|
-
// Muted, not merely reported
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
// one, for a listener that is already known to be broken.
|
|
125
|
+
// Muted, not merely reported: a listener that throws on one
|
|
126
|
+
// event throws on the next, and keeping it would cost every
|
|
127
|
+
// later call a thrown and swallowed error for nothing.
|
|
134
128
|
this.mutedListeners.add(key);
|
|
135
129
|
console.error(`[lambder mock] listener "${key}" threw and is muted until it subscribes again or the mock is reset`, err);
|
|
136
130
|
}
|
|
@@ -139,11 +133,8 @@ export class LambderMockCallRecorder {
|
|
|
139
133
|
/**
|
|
140
134
|
* Ends one call: reads how it went, redacts what a log has no business
|
|
141
135
|
* keeping, and emits the response event and the log row from one object,
|
|
142
|
-
* so the two cannot
|
|
143
|
-
*
|
|
144
|
-
* The event and the row carry copies of their own, so a listener that
|
|
145
|
-
* edits the event it was handed is not editing the row the log keeps; the
|
|
146
|
-
* row is copied again on the way in and on the way out.
|
|
136
|
+
* so the two cannot disagree about the call. Each carries its own copies,
|
|
137
|
+
* so a listener editing its event does not edit the logged row.
|
|
147
138
|
*/
|
|
148
139
|
settle(facts, ending) {
|
|
149
140
|
const answer = ending.answer;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
1
2
|
import type { LambderApiSignatureMap } from "../shared/wire/LambderApiSignature.js";
|
|
3
|
+
import type { LambderApiResponseConfig } from "../shared/wire/LambderApiContract.js";
|
|
4
|
+
import type { LambderHttpStatusCode } from "../shared/wire/LambderHttpStatus.js";
|
|
5
|
+
import type { MaybePromise } from "../shared/util/LambderTypeUtilities.js";
|
|
2
6
|
import type { LambderContractGuardNames, LambderContractIdempotencyOf, LambderContractKeysWithMode, LambderContractRateLimitNames, LambderContractRateLimitOf } from "../shared/wire/LambderApiContract.js";
|
|
3
7
|
import type { LambderApiGuard } from "../api/LambderApiGuards.js";
|
|
4
8
|
import type { LambderApiRateLimitPolicyConfig } from "../api/LambderApiRateLimits.js";
|
|
@@ -35,9 +39,9 @@ export type LambderMockSessionsOptions<S> = {
|
|
|
35
39
|
*
|
|
36
40
|
* `callerIdentity` and `defaultPendingTtlSeconds` are here because a mock
|
|
37
41
|
* that cannot express them answers differently from the server on exactly the
|
|
38
|
-
* calls idempotency exists for: without an identity a public endpoint's
|
|
39
|
-
* answer replays to whoever presents the key,
|
|
40
|
-
*
|
|
42
|
+
* calls idempotency exists for: without an identity, a public endpoint's
|
|
43
|
+
* stored answer replays to whoever presents the key, where a server
|
|
44
|
+
* configured with one misses.
|
|
41
45
|
*/
|
|
42
46
|
export type LambderMockIdempotencyOptions<S = any> = {
|
|
43
47
|
/** Seconds a stored answer replays for. Default: 86400 (24h). Per-endpoint override: `idempotency: { ttlSeconds }`. */
|
|
@@ -50,13 +54,12 @@ export type LambderMockIdempotencyOptions<S = any> = {
|
|
|
50
54
|
store?: LambderIdempotencyStore;
|
|
51
55
|
/**
|
|
52
56
|
* Who a call is acting as, for scoping a PUBLIC endpoint's stored answer;
|
|
53
|
-
* session endpoints already scope per
|
|
57
|
+
* session endpoints already scope per user. The server's option word
|
|
54
58
|
* for word (see LambderApiIdempotencyConfig), bound to the mock's own call
|
|
55
59
|
* context, because that is the context the engine hands it here.
|
|
56
60
|
*
|
|
57
61
|
* Without the session, as on the server: this runs on public endpoints
|
|
58
|
-
* alone, where the session is always null
|
|
59
|
-
* offering a field that answers nothing.
|
|
62
|
+
* alone, where the session is always null.
|
|
60
63
|
*/
|
|
61
64
|
callerIdentity?: (ctx: Omit<LambderMockCallContext<S>, "session">, request: LambderApiRequest) => string | null | Promise<string | null>;
|
|
62
65
|
};
|
|
@@ -75,19 +78,18 @@ type LambderContractIdempotentKeys<C> = {
|
|
|
75
78
|
* The rate limits option: the policies endpoints may restate, the limiter
|
|
76
79
|
* they are counted on, and what happens when that limiter throws.
|
|
77
80
|
*
|
|
78
|
-
* The policies
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
81
|
+
* The policies must meet the rules the server's policies met when it
|
|
82
|
+
* registered the same endpoints: an entry the mock's copy does not fit cannot
|
|
83
|
+
* be registered, and this makes that an error at the option rather than a
|
|
84
|
+
* throw when the registry loads. So the map names every policy the contract
|
|
85
|
+
* references, a policy a public endpoint names is not keyed per session, and
|
|
86
|
+
* a policy whose windows an endpoint overrides keeps a per-API budget.
|
|
87
|
+
* Policies the contract does not reference may be added freely.
|
|
85
88
|
*
|
|
86
|
-
* `failOpen` is the server's own option (see LambderApiRateLimitsConfig)
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* 429.
|
|
89
|
+
* `failOpen` is the server's own option (see LambderApiRateLimitsConfig),
|
|
90
|
+
* here for the idempotency option's reason: with a failing limiter of the
|
|
91
|
+
* app's own, a mock that cannot express it always lets the call through,
|
|
92
|
+
* answering 200 where a server configured to refuse answers 429.
|
|
91
93
|
*/
|
|
92
94
|
type LambderMockRateLimitsOptions<C, S, P extends LambderMockRateLimitPolicies<S>> = {
|
|
93
95
|
policies: P & {
|
|
@@ -117,9 +119,9 @@ type LambderMockRateLimitsOptions<C, S, P extends LambderMockRateLimitPolicies<S
|
|
|
117
119
|
* omittable only for a contract that declares none.
|
|
118
120
|
*
|
|
119
121
|
* The same reasoning the entry's own guards field carries: a guard the mock
|
|
120
|
-
* does not declare cannot run,
|
|
121
|
-
*
|
|
122
|
-
* check it exists for.
|
|
122
|
+
* does not declare cannot run, so a call the server answers notAuthorized
|
|
123
|
+
* would answer 200 here. Optional, it would be the droppable half of exactly
|
|
124
|
+
* the check it exists for.
|
|
123
125
|
*/
|
|
124
126
|
type LambderMockGuardsOption<C, S, G> = [
|
|
125
127
|
LambderContractGuardNames<C>
|
|
@@ -184,9 +186,8 @@ export type LambderMockAppOptions<C, S, G, P extends LambderMockRateLimitPolicie
|
|
|
184
186
|
*
|
|
185
187
|
* One host per app, because a jar checks a cookie's scope the way a
|
|
186
188
|
* browser does: planted at "localhost" and read back on
|
|
187
|
-
* "
|
|
188
|
-
*
|
|
189
|
-
* localhost answered sessionExpired.
|
|
189
|
+
* "shop.localhost:5173", the session cookie is not sent, and every
|
|
190
|
+
* session call would answer sessionExpired.
|
|
190
191
|
*/
|
|
191
192
|
cookieHost?: string;
|
|
192
193
|
/** Ceiling on what a compressed request payload may restore to. Default: 20,000,000. */
|
|
@@ -208,6 +209,23 @@ export type LambderMockAppOptions<C, S, G, P extends LambderMockRateLimitPolicie
|
|
|
208
209
|
revealHandlerErrors?: boolean;
|
|
209
210
|
/** Called at the end of reset(), so the app can rewind its own data. */
|
|
210
211
|
onReset?: () => void;
|
|
212
|
+
/**
|
|
213
|
+
* The answer to an input that fails its schema, where the server app
|
|
214
|
+
* sets setApiInputValidationErrorHandler: the same answer, stated as
|
|
215
|
+
* data. Return null for the standard 422 `validation` refusal, which is
|
|
216
|
+
* also what an app that sets neither gets.
|
|
217
|
+
*/
|
|
218
|
+
onInvalidInput?: (zodError: z.ZodError, ctx: LambderMockCallContext<S>) => MaybePromise<LambderMockInvalidInputAnswer | null>;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* What the server's input validation handler answers, as a mock states it:
|
|
222
|
+
* `res.api(payload, config)` as data, with the status it went out with (200
|
|
223
|
+
* unless named).
|
|
224
|
+
*/
|
|
225
|
+
export type LambderMockInvalidInputAnswer = {
|
|
226
|
+
payload?: unknown;
|
|
227
|
+
config?: LambderApiResponseConfig;
|
|
228
|
+
statusCode?: LambderHttpStatusCode;
|
|
211
229
|
};
|
|
212
230
|
/** How the mock transport carries cookies: a fresh memory jar (default), a jar of yours, the memory jar mirrored into document.cookie, or none. */
|
|
213
231
|
export type LambderMockTransportOptions = {
|
|
@@ -4,14 +4,14 @@ import type { LambderMockEntry, LambderMockOverride, LambderMockRestEntry } from
|
|
|
4
4
|
* standing over them.
|
|
5
5
|
*
|
|
6
6
|
* One of the four pieces of state LambderMockApp holds that nothing else
|
|
7
|
-
* touches;
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* touches; the rest of the runtime reaches it only through a request's
|
|
8
|
+
* lookup. The app keeps the caller-facing surface (register, registerPartial,
|
|
9
|
+
* override, restoreOverrides, registeredNames) as delegations, because that
|
|
10
|
+
* surface is contract-typed and its safety checks are compile-time; this
|
|
11
|
+
* class holds the bookkeeping.
|
|
12
12
|
*
|
|
13
|
-
* Generic over the contract only so
|
|
14
|
-
*
|
|
13
|
+
* Generic over the contract only so entries keep their type through the map;
|
|
14
|
+
* the registry itself never reads one.
|
|
15
15
|
*/
|
|
16
16
|
export declare class LambderMockEntryRegistry<C> {
|
|
17
17
|
private readonly entries;
|
|
@@ -32,11 +32,10 @@ export declare class LambderMockEntryRegistry<C> {
|
|
|
32
32
|
get restNotMockedReason(): string | null;
|
|
33
33
|
/**
|
|
34
34
|
* Adds every entry of every slice, and the rest entry where one is among
|
|
35
|
-
* them.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* reason.
|
|
35
|
+
* them. Everything is staged and committed together, so a failed check
|
|
36
|
+
* leaves nothing behind: a caller that catches the error and retries sees
|
|
37
|
+
* the problem it is fixing, not a duplicate-name error from its own first
|
|
38
|
+
* attempt.
|
|
40
39
|
*/
|
|
41
40
|
addSlices(slices: readonly (Record<string, LambderMockEntry<C, any>> | LambderMockRestEntry)[]): void;
|
|
42
41
|
/** The registered entry for a name, before any override; null when there is none. */
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Which of register()'s arguments is the rest entry rather than a slice.
|
|
3
3
|
*
|
|
4
|
-
* Read off the one field restNotMocked() writes
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* carrying a value no entry is.
|
|
4
|
+
* Read off the one field restNotMocked() writes. A slice maps endpoint names
|
|
5
|
+
* to entries, so the marker has to be a key no endpoint name plausibly is,
|
|
6
|
+
* holding a value no entry is.
|
|
8
7
|
*/
|
|
9
8
|
const isRestNotMockedEntry = (slice) => typeof slice.restNotMockedReason === "string";
|
|
10
9
|
/**
|
|
@@ -12,14 +11,14 @@ const isRestNotMockedEntry = (slice) => typeof slice.restNotMockedReason === "st
|
|
|
12
11
|
* standing over them.
|
|
13
12
|
*
|
|
14
13
|
* One of the four pieces of state LambderMockApp holds that nothing else
|
|
15
|
-
* touches;
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* touches; the rest of the runtime reaches it only through a request's
|
|
15
|
+
* lookup. The app keeps the caller-facing surface (register, registerPartial,
|
|
16
|
+
* override, restoreOverrides, registeredNames) as delegations, because that
|
|
17
|
+
* surface is contract-typed and its safety checks are compile-time; this
|
|
18
|
+
* class holds the bookkeeping.
|
|
20
19
|
*
|
|
21
|
-
* Generic over the contract only so
|
|
22
|
-
*
|
|
20
|
+
* Generic over the contract only so entries keep their type through the map;
|
|
21
|
+
* the registry itself never reads one.
|
|
23
22
|
*/
|
|
24
23
|
export class LambderMockEntryRegistry {
|
|
25
24
|
entries = new Map();
|
|
@@ -42,21 +41,19 @@ export class LambderMockEntryRegistry {
|
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
44
43
|
* Adds every entry of every slice, and the rest entry where one is among
|
|
45
|
-
* them.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* reason.
|
|
44
|
+
* them. Everything is staged and committed together, so a failed check
|
|
45
|
+
* leaves nothing behind: a caller that catches the error and retries sees
|
|
46
|
+
* the problem it is fixing, not a duplicate-name error from its own first
|
|
47
|
+
* attempt.
|
|
50
48
|
*/
|
|
51
49
|
addSlices(slices) {
|
|
52
50
|
const staged = new Map();
|
|
53
51
|
let stagedRestReason = null;
|
|
54
52
|
for (const slice of slices) {
|
|
55
53
|
if (isRestNotMockedEntry(slice)) {
|
|
56
|
-
// Two
|
|
57
|
-
// reasons,
|
|
58
|
-
//
|
|
59
|
-
// for.
|
|
54
|
+
// Two rest entries would answer the same calls with different
|
|
55
|
+
// reasons, picked by registration order: the same ambiguity a
|
|
56
|
+
// duplicate name is refused for.
|
|
60
57
|
const registered = this.restReason ?? stagedRestReason;
|
|
61
58
|
if (registered !== null) {
|
|
62
59
|
throw new Error(`LambderMockApp: the rest of the contract is already registered as not mocked ("${registered}"). One restNotMocked entry covers every endpoint the slices leave out.`);
|
|
@@ -66,12 +63,11 @@ export class LambderMockEntryRegistry {
|
|
|
66
63
|
}
|
|
67
64
|
for (const [key, entry] of Object.entries(slice)) {
|
|
68
65
|
// The compile-time completeness check reads the slice's KEYS
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// an overlap error naming an endpoint nobody wrote twice.
|
|
66
|
+
// while registration reads entry.name, so they must agree. A
|
|
67
|
+
// hand-written `{ ...userMocks, "user.list": someOtherEntry }`
|
|
68
|
+
// would register the other endpoint, leave "user.list"
|
|
69
|
+
// unanswered, and surface as an overlap error naming an
|
|
70
|
+
// endpoint nobody wrote twice.
|
|
75
71
|
if (key !== entry.name) {
|
|
76
72
|
throw new Error(`LambderMockApp: slice key "${key}" holds the mock for "${entry.name}". Key every entry by its own endpoint name, or build the slice with mockApp.apiSlice(...).`);
|
|
77
73
|
}
|
|
@@ -97,9 +93,8 @@ export class LambderMockEntryRegistry {
|
|
|
97
93
|
this.overrideStacks.set(name, stack);
|
|
98
94
|
// Removes this override wherever it sits rather than popping the top:
|
|
99
95
|
// scopes do not always unwind innermost first (an outer handle
|
|
100
|
-
// restored by hand while an inner one
|
|
101
|
-
//
|
|
102
|
-
// no-op.
|
|
96
|
+
// restored by hand while an inner one stands), and popping would take
|
|
97
|
+
// down somebody else's override. Restoring twice is a no-op.
|
|
103
98
|
const restore = () => {
|
|
104
99
|
const current = this.overrideStacks.get(name);
|
|
105
100
|
const at = current?.lastIndexOf(entry) ?? -1;
|
|
@@ -13,12 +13,9 @@ export declare class LambderMockTransportError extends Error {
|
|
|
13
13
|
/**
|
|
14
14
|
* What a call fails with and how long it takes to do it: the queued and
|
|
15
15
|
* standing failures per endpoint, the offline switch, and the configured
|
|
16
|
-
* latency.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* touches, so it is its own object. The app keeps the whole surface
|
|
20
|
-
* (failNext, setFailure, setOffline, setLatency) as one-line delegations,
|
|
21
|
-
* which is what a caller reads; what moved is the bookkeeping behind them.
|
|
16
|
+
* latency. State nothing else in LambderMockApp touches, so it is its own
|
|
17
|
+
* object; the app's failNext, setFailure, setOffline and setLatency delegate
|
|
18
|
+
* here.
|
|
22
19
|
*
|
|
23
20
|
* Injected refusals are rendered through the real envelope helpers, never
|
|
24
21
|
* hand-written: an injected 429 and an earned one have to be the same bytes,
|
|
@@ -20,12 +20,9 @@ export class LambderMockTransportError extends Error {
|
|
|
20
20
|
/**
|
|
21
21
|
* What a call fails with and how long it takes to do it: the queued and
|
|
22
22
|
* standing failures per endpoint, the offline switch, and the configured
|
|
23
|
-
* latency.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* touches, so it is its own object. The app keeps the whole surface
|
|
27
|
-
* (failNext, setFailure, setOffline, setLatency) as one-line delegations,
|
|
28
|
-
* which is what a caller reads; what moved is the bookkeeping behind them.
|
|
23
|
+
* latency. State nothing else in LambderMockApp touches, so it is its own
|
|
24
|
+
* object; the app's failNext, setFailure, setOffline and setLatency delegate
|
|
25
|
+
* here.
|
|
29
26
|
*
|
|
30
27
|
* Injected refusals are rendered through the real envelope helpers, never
|
|
31
28
|
* hand-written: an injected 429 and an earned one have to be the same bytes,
|