lambder 7.2.5 → 8.0.2
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 +1021 -3
- package/README.md +43 -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 +74 -61
- package/dist/api/LambderApiIdempotency.js +226 -151
- package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
- package/dist/api/LambderApiOutputValidationError.js +50 -0
- package/dist/api/LambderApiPipeline.d.ts +77 -39
- package/dist/api/LambderApiPipeline.js +135 -62
- package/dist/api/LambderApiRateLimits.d.ts +208 -54
- package/dist/api/LambderApiRateLimits.js +197 -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/freshProcessVerifier.d.ts +13 -0
- package/dist/build/freshProcessVerifier.js +19 -0
- package/dist/build/writeApiSignatures.d.ts +109 -0
- package/dist/build/writeApiSignatures.js +222 -0
- package/dist/build.d.ts +9 -0
- package/dist/build.js +8 -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/lambderFetchTransport.d.ts +4 -1
- package/dist/client/lambderFetchTransport.js +52 -28
- package/dist/client.d.ts +5 -3
- package/dist/client.js +2 -1
- package/dist/core/Lambder.d.ts +161 -69
- package/dist/core/Lambder.js +370 -226
- 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 +28 -7
- package/dist/core/LambderFiles.js +73 -33
- 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 +15 -8
- package/dist/index.js +5 -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 +44 -10
- package/dist/invoke/LambderLambdaEvent.js +80 -37
- package/dist/invoke/lambderHandlerTransport.d.ts +12 -10
- package/dist/invoke/lambderHandlerTransport.js +16 -19
- 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 +33 -29
- package/dist/mock/lambderMockMswHandler.js +50 -39
- package/dist/mock.d.ts +3 -1
- package/dist/mock.js +5 -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 +136 -47
- package/dist/session/LambderSessionManager.js +280 -139
- 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/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/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/LambderTestingDoors.d.ts +29 -0
- package/dist/shared/util/LambderTestingDoors.js +29 -0
- 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 +107 -32
- package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
- package/dist/shared/wire/LambderApiOutcome.js +48 -23
- package/dist/shared/wire/LambderApiRefusal.d.ts +39 -27
- package/dist/shared/wire/LambderApiRefusal.js +36 -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 +79 -0
- package/dist/shared/wire/LambderOutcomeAssertions.js +112 -0
- package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
- package/dist/shared/wire/LambderRequestPayload.js +4 -6
- 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 +79 -33
- 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/LambderS3FileSource.d.ts +21 -6
- package/dist/stores/LambderS3FileSource.js +12 -7
- package/dist/testing/LambderTestApp.d.ts +176 -0
- package/dist/testing/LambderTestApp.js +204 -0
- package/dist/testing/LambderTestVisitor.d.ts +153 -0
- package/dist/testing/LambderTestVisitor.js +154 -0
- package/dist/testing.d.ts +27 -0
- package/dist/testing.js +24 -0
- package/package.json +20 -3
- package/dist/api/LambderApiPolicyEngine.d.ts +0 -36
- package/dist/api/LambderApiPolicyEngine.js +0 -77
- package/dist/shared/util/LambderKeyFields.d.ts +0 -32
- package/dist/shared/util/LambderKeyFields.js +0 -34
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { z } from "zod";
|
|
9
9
|
import type { LambderApiMode, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractGuardInputsOf, LambderContractGuardNames, LambderContractGuardsOf, LambderContractIdempotencyOf, LambderContractKeysWithMode, LambderContractMode, LambderContractRateLimitOf } from "../shared/wire/LambderApiContract.js";
|
|
10
10
|
import type { LambderApiGuard, LambderGuardDataOf, LambderGuardMetaMap } from "../api/LambderApiGuards.js";
|
|
11
|
-
import type { LambderApiRateLimitPolicyConfig } from "../api/LambderApiRateLimits.js";
|
|
11
|
+
import type { LambderApiRateLimitPolicyConfig, LambderContextRateLimit, LambderContextRateLimitCheck } from "../api/LambderApiRateLimits.js";
|
|
12
12
|
import type { LambderApiCallContext } from "../api/LambderApiCallContext.js";
|
|
13
13
|
import type { LambderApiRequest } from "../api/LambderApiRequest.js";
|
|
14
14
|
import type { LambderHttpStatusCode } from "../shared/wire/LambderHttpStatus.js";
|
|
@@ -28,21 +28,16 @@ export type LambderMockOutputOf<C, K extends keyof C> = C[K] extends {
|
|
|
28
28
|
* `never`, so a typo is an error on the key itself.
|
|
29
29
|
*
|
|
30
30
|
* Needed once per nesting level: inferring a generic from an object literal
|
|
31
|
-
* (`const G`, `const P` on create()) switches excess-property checking off
|
|
32
|
-
* the WHOLE literal,
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
31
|
+
* (`const G`, `const P` on create()) switches excess-property checking off
|
|
32
|
+
* for the WHOLE literal, so `idempotency: { failOpn: false }` would compile
|
|
33
|
+
* and be dropped. Intersected into a nested position because the type
|
|
34
|
+
* variable must stay naked somewhere for the literal to be inferred at all.
|
|
35
|
+
* `unknown` for a non-object (`idempotency: true`), since mapping Boolean's
|
|
36
|
+
* prototype keys to `never` would refuse the boolean form.
|
|
37
37
|
*
|
|
38
|
-
* `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* The server's create() has the same construction in `LambderNoExtraKeys`
|
|
43
|
-
* (core/Lambder.ts). It is written twice on purpose: `lambder/mock` is
|
|
44
|
-
* browser-safe by its import graph, and reaching into core/ for a type alias
|
|
45
|
-
* would pull the server's whole type graph (aws-lambda included) back into it.
|
|
38
|
+
* Duplicates `LambderNoExtraKeys` (core/LambderCreateOptions.ts) on purpose: importing it
|
|
39
|
+
* would pull the server's type graph (aws-lambda included) into the
|
|
40
|
+
* browser-safe `lambder/mock`.
|
|
46
41
|
*/
|
|
47
42
|
export type LambderMockSurplusKeys<TOptions, TShape> = [
|
|
48
43
|
TOptions
|
|
@@ -55,8 +50,16 @@ export type LambderMockSurplusKeys<TOptions, TShape> = [
|
|
|
55
50
|
export type LambderMockCallContext<S = any> = LambderApiCallContext<S> & {
|
|
56
51
|
apiName: string;
|
|
57
52
|
request: LambderApiRequest;
|
|
58
|
-
/** Create, rotate, refresh and end sessions, exactly as a server handler does through
|
|
59
|
-
|
|
53
|
+
/** Create, rotate, refresh and end sessions, exactly as a server handler does through its own ctx.sessionController. */
|
|
54
|
+
sessionController: LambderSessionController<S>;
|
|
55
|
+
/**
|
|
56
|
+
* Charges a named policy from code, as a server handler does through its
|
|
57
|
+
* own ctx.rateLimit: a 429 refusal when it is over. The name is any
|
|
58
|
+
* string and the key optional here; the charge checks both.
|
|
59
|
+
*/
|
|
60
|
+
rateLimit: LambderContextRateLimit<Record<string, LambderApiRateLimitPolicyConfig>>;
|
|
61
|
+
/** The same count, answered instead of thrown, as ctx.isRateLimited on the server. */
|
|
62
|
+
isRateLimited: LambderContextRateLimitCheck<Record<string, LambderApiRateLimitPolicyConfig>>;
|
|
60
63
|
signal: AbortSignal;
|
|
61
64
|
/**
|
|
62
65
|
* The envelope fields that travel beside the payload, the mock's stand-in
|
|
@@ -88,18 +91,17 @@ export type LambderMockContext<C, K extends keyof C, S, G> = Omit<LambderMockCal
|
|
|
88
91
|
};
|
|
89
92
|
/**
|
|
90
93
|
* The guard map a mock app must declare: one guard per name any endpoint of
|
|
91
|
-
* the contract declares,
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* public endpoint fails at the `guards` option.
|
|
94
|
+
* the contract declares, plus, for each guardInput-mode guard, a `guardInput`
|
|
95
|
+
* schema whose input is what the contract says a client sends. A guard a
|
|
96
|
+
* public endpoint names may not require a session. A missing name, a schema
|
|
97
|
+
* that parses to something else, or such a session guard fails at the
|
|
98
|
+
* `guards` option.
|
|
97
99
|
*/
|
|
98
100
|
export type LambderMockGuards<C, S = any> = {
|
|
99
101
|
[N in LambderContractGuardNames<C>]: LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>;
|
|
100
102
|
} & {
|
|
101
103
|
[N in LambderContractGuardInputNames<C>]: {
|
|
102
|
-
guardInput: z.ZodType<LambderContractGuardInput<C, N
|
|
104
|
+
guardInput: z.ZodType<unknown, LambderContractGuardInput<C, N>>;
|
|
103
105
|
};
|
|
104
106
|
} & {
|
|
105
107
|
[N in LambderContractGuardNames<C, "public">]: {
|
|
@@ -110,14 +112,10 @@ export type LambderMockHandler<C, K extends keyof C, S, G> = (ctx: LambderMockCo
|
|
|
110
112
|
/**
|
|
111
113
|
* The guards field of an entry: required whenever the contract declares any
|
|
112
114
|
* guard for the endpoint, and type-equal to the server's own declaration.
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* where the server answers notAuthorized. The guards that carry no
|
|
118
|
-
* guardInput are exactly the "may this role call it" ones (session-only,
|
|
119
|
-
* param-only), so keying on guardInputs made the authorization checks the
|
|
120
|
-
* droppable half.
|
|
115
|
+
* The restatement is what tells the runtime which guards to run, so a guard
|
|
116
|
+
* left out would let the mock answer 200 where the server answers
|
|
117
|
+
* notAuthorized. Keyed on the guards rather than on guardInputs, since the
|
|
118
|
+
* guards without a guardInput are the "may this role call it" ones.
|
|
121
119
|
*/
|
|
122
120
|
type LambderMockGuardsField<C, K extends keyof C> = [
|
|
123
121
|
LambderContractGuardsOf<C, K>
|
|
@@ -128,10 +126,9 @@ type LambderMockGuardsField<C, K extends keyof C> = [
|
|
|
128
126
|
};
|
|
129
127
|
/**
|
|
130
128
|
* The rate-limit field of an entry: required whenever the contract declares
|
|
131
|
-
* one, absent otherwise.
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* answers 200 where the server answers 429.
|
|
129
|
+
* one, absent otherwise. As with LambderMockGuardsField, the restatement is
|
|
130
|
+
* the only thing that tells the runtime to apply the limit, so an entry that
|
|
131
|
+
* left it out would answer 200 where the server answers 429.
|
|
135
132
|
*/
|
|
136
133
|
type LambderMockRateLimitField<C, K extends keyof C> = [
|
|
137
134
|
LambderContractRateLimitOf<C, K>
|
|
@@ -157,56 +154,47 @@ type LambderMockIdempotencyField<C, K extends keyof C> = [
|
|
|
157
154
|
* What override() hands back: call restore() to put the original handler
|
|
158
155
|
* back.
|
|
159
156
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* 'SymbolConstructor'" out of the published .d.ts, from importing the entry at
|
|
165
|
-
* all, whenever skipLibCheck was off. A scoped override is a try/finally,
|
|
166
|
-
* which needs no lib.
|
|
157
|
+
* Deliberately no `[Symbol.dispose]` for `using`: it is declared only in
|
|
158
|
+
* `lib: ESNext`, so a consumer on `lib: ES2022` with skipLibCheck off would
|
|
159
|
+
* get TS2550 from the published .d.ts just by importing the entry. A scoped
|
|
160
|
+
* override is a try/finally, which needs no lib.
|
|
167
161
|
*/
|
|
168
162
|
export type LambderMockOverride = {
|
|
169
163
|
restore(): void;
|
|
170
164
|
};
|
|
171
165
|
/**
|
|
172
|
-
* What an entry's `input` schema must
|
|
173
|
-
* input, in both directions
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* added to reproduce, produced by the thing added to reproduce it. Requiring
|
|
182
|
-
* assignability the other way as well makes the schema's output the contract's
|
|
183
|
-
* input and nothing else.
|
|
166
|
+
* What an entry's `input` schema must take and give: it takes exactly the
|
|
167
|
+
* endpoint's contract input (the form a client posts), in both directions,
|
|
168
|
+
* and what it parses to still reads as that input, which is how the mock's
|
|
169
|
+
* handler is typed. `z.ZodType<Input>` alone is covariant, so a schema
|
|
170
|
+
* taking a SUBTYPE (an extra required field, a literal where the contract
|
|
171
|
+
* says string) would pass, and the mock would then answer 422 to payloads
|
|
172
|
+
* the server accepts. A default passes (the parsed field is simply there);
|
|
173
|
+
* a transform that changes a field's type does not, since the handler would
|
|
174
|
+
* read it as the posted type.
|
|
184
175
|
*
|
|
185
176
|
* Intersected onto the schema rather than mapped to `never`, so the compiler
|
|
186
|
-
* quotes the reason at the `input` property. `z.any()` passes
|
|
187
|
-
*
|
|
188
|
-
* not.
|
|
177
|
+
* quotes the reason at the `input` property. `z.any()` passes both ways as
|
|
178
|
+
* the one deliberate escape hatch; `z.unknown()` does not.
|
|
189
179
|
*/
|
|
190
180
|
type LambderMockInputPin<C, K extends keyof C, TSchema extends z.ZodType> = [
|
|
191
|
-
z.
|
|
192
|
-
] extends [LambderMockInputOf<C, K>] ? ([LambderMockInputOf<C, K>] extends [z.output<TSchema>] ? unknown : {
|
|
193
|
-
"LambderMockApp: this input schema
|
|
181
|
+
z.input<TSchema>
|
|
182
|
+
] extends [LambderMockInputOf<C, K>] ? ([LambderMockInputOf<C, K>] extends [z.input<TSchema>] ? ([z.output<TSchema>] extends [LambderMockInputOf<C, K>] ? unknown : {
|
|
183
|
+
"LambderMockApp: this input schema transforms the payload into a type the mock handler is not typed for (it reads the payload as the endpoint's contract input)": LambderMockInputOf<C, K>;
|
|
194
184
|
}) : {
|
|
195
|
-
"LambderMockApp: this input schema
|
|
185
|
+
"LambderMockApp: this input schema takes less than the endpoint takes (an extra required field, or a narrower type), so the mock would answer 422 to payloads the server accepts": LambderMockInputOf<C, K>;
|
|
186
|
+
}) : {
|
|
187
|
+
"LambderMockApp: this input schema takes something else than the endpoint's contract input": LambderMockInputOf<C, K>;
|
|
196
188
|
};
|
|
197
189
|
/** An entry written in full: the declarations restated and pinned, plus the handler. */
|
|
198
190
|
export type LambderMockEntryOptions<C, K extends keyof C, S, G, TInputSchema extends z.ZodType = z.ZodType> = LambderMockGuardsField<C, K> & LambderMockRateLimitField<C, K> & LambderMockIdempotencyField<C, K> & {
|
|
199
191
|
/**
|
|
200
|
-
* A schema to validate the posted payload against,
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* payload reaches the handler, as it does today.
|
|
207
|
-
*
|
|
208
|
-
* Pinned to the contract's input all the same: the schema is the mock's,
|
|
209
|
-
* but what it parses to is the server's, in both directions (see
|
|
192
|
+
* A schema to validate the posted payload against, so the mock answers
|
|
193
|
+
* 422 exactly as the server would. Optional, and the mock's own: the
|
|
194
|
+
* contract is type-only, and importing the server's schemas would put the
|
|
195
|
+
* whole endpoint surface into the browser bundle. Restate it for endpoints
|
|
196
|
+
* whose rejection path a test exercises; without it a bad payload reaches
|
|
197
|
+
* the handler. What it parses to is pinned to the contract's input (see
|
|
210
198
|
* LambderMockInputPin).
|
|
211
199
|
*/
|
|
212
200
|
input?: TInputSchema & LambderMockInputPin<C, K, TInputSchema>;
|
|
@@ -215,14 +203,9 @@ export type LambderMockEntryOptions<C, K extends keyof C, S, G, TInputSchema ext
|
|
|
215
203
|
/**
|
|
216
204
|
* What publicApi/sessionApi accept: a bare handler only for an endpoint the
|
|
217
205
|
* contract declares nothing for, the full options otherwise, so the form that
|
|
218
|
-
* cannot carry a restatement is unavailable exactly where one is owed.
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* restatement required INSIDE the options form, and the bare handler is the
|
|
222
|
-
* form that has no fields at all, so keying this on guards left every
|
|
223
|
-
* guardless endpoint free to drop its rate limit and its idempotency again:
|
|
224
|
-
* the handler ran twice for one key and a perMin limit never answered 429,
|
|
225
|
-
* which is the whole of what those two fields exist to prevent.
|
|
206
|
+
* cannot carry a restatement is unavailable exactly where one is owed. Keyed
|
|
207
|
+
* on all three declarations: keyed on guards alone, a guardless endpoint
|
|
208
|
+
* could drop its rate limit and idempotency through the bare form.
|
|
226
209
|
*/
|
|
227
210
|
export type LambderMockEntryInput<C, K extends keyof C, S, G, TInputSchema extends z.ZodType = z.ZodType> = [
|
|
228
211
|
LambderContractGuardsOf<C, K> | LambderContractRateLimitOf<C, K> | LambderContractIdempotencyOf<C, K>
|
|
@@ -244,13 +227,9 @@ export type LambderMockSlice<C, K extends keyof C & string> = {
|
|
|
244
227
|
};
|
|
245
228
|
/**
|
|
246
229
|
* What restNotMocked(reason) hands register(): "every endpoint the slices
|
|
247
|
-
* beside me leave out is not mocked, for this reason".
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* answers the names nothing else claimed, and which those are is only known
|
|
251
|
-
* once the other arguments have been read. Every check below filters it out
|
|
252
|
-
* before it reads a name, so the field it does carry is neither a stray nor
|
|
253
|
-
* half of a duplicate; the one clause it changes is completeness.
|
|
230
|
+
* beside me leave out is not mocked, for this reason". A one-field object
|
|
231
|
+
* rather than a slice, because it names no endpoint; every check below
|
|
232
|
+
* filters it out before reading names, so it changes only completeness.
|
|
254
233
|
*/
|
|
255
234
|
export type LambderMockRestEntry = {
|
|
256
235
|
readonly restNotMockedReason: string;
|
|
@@ -259,15 +238,11 @@ export type LambderMockRestEntry = {
|
|
|
259
238
|
* The names one slice holds; distributes over a union of slices.
|
|
260
239
|
*
|
|
261
240
|
* A slice typed with an index signature (`Record<string, LambderMockEntry>`,
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* The rest entry contributes nothing either, and for the opposite reason: the
|
|
269
|
-
* one key it carries is not an endpoint name, so reading it would report
|
|
270
|
-
* "restNotMockedReason" as a stray, and two rest entries as a duplicate of it.
|
|
241
|
+
* a list built in a loop) has `string` as its key type, which would pass the
|
|
242
|
+
* completeness check while registering almost nothing, so it contributes
|
|
243
|
+
* nothing here and LambderMockUncheckableSlices reports it. The rest entry
|
|
244
|
+
* contributes nothing either: its one key is not an endpoint name, and
|
|
245
|
+
* reading it would report "restNotMockedReason" as a stray or a duplicate.
|
|
271
246
|
*/
|
|
272
247
|
type LambderMockSliceNames<S> = S extends unknown ? (S extends LambderMockRestEntry ? never : (string extends keyof S ? never : keyof S & string)) : never;
|
|
273
248
|
/**
|
|
@@ -288,10 +263,9 @@ type LambderMockHasRestEntry<Slices extends readonly unknown[]> = [
|
|
|
288
263
|
* The endpoints register() would leave unanswered: the ones no slice covers,
|
|
289
264
|
* unless a rest entry stands for them.
|
|
290
265
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* rest entry changes is whether leaving it out is a mistake.
|
|
266
|
+
* Separate from LambderMockMissingNames, because an endpoint the rest entry
|
|
267
|
+
* answers still has no mock of its own; the rest entry only changes whether
|
|
268
|
+
* leaving it out is a mistake.
|
|
295
269
|
*/
|
|
296
270
|
type LambderMockUncoveredNames<C, Slices extends readonly unknown[]> = LambderMockHasRestEntry<Slices> extends true ? never : LambderMockMissingNames<C, Slices>;
|
|
297
271
|
/** Names the slices carry that the contract does not declare. */
|
|
@@ -300,14 +274,10 @@ export type LambderMockStrayNames<C, Slices extends readonly unknown[]> = Exclud
|
|
|
300
274
|
export type LambderMockDuplicateNames<Slices extends readonly unknown[]> = Slices extends readonly [infer Head, ...infer Tail] ? (LambderMockSliceNames<Head> & LambderMockSliceNames<Tail[number]>) | LambderMockDuplicateNames<Tail> : never;
|
|
301
275
|
/**
|
|
302
276
|
* True for a slice list whose length the compiler does not know: an array
|
|
303
|
-
* type rather than a tuple,
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
* `never` base case on the first step, and completeness reduces to "the
|
|
308
|
-
* element type mentions these names", which one element satisfies as well as
|
|
309
|
-
* twenty. `const slices = [userMocks, orderMocks]` spread into register() is
|
|
310
|
-
* exactly that type, so the array form is refused rather than passed.
|
|
277
|
+
* type rather than a tuple. Every check here walks a tuple, and an array
|
|
278
|
+
* type quietly disables them all (completeness reduces to "the element type
|
|
279
|
+
* mentions these names"), so the array form, such as a spread
|
|
280
|
+
* `const slices = [userMocks, orderMocks]`, is refused.
|
|
311
281
|
*/
|
|
312
282
|
type LambderMockUncountableSlices<Slices extends readonly unknown[]> = number extends Slices["length"] ? true : false;
|
|
313
283
|
/**
|
|
@@ -4,14 +4,12 @@ import { type LambderApiRequest } from "../api/LambderApiRequest.js";
|
|
|
4
4
|
* The fields of an invoke's synthesized event this transport reads, declared
|
|
5
5
|
* structurally rather than imported as APIGatewayProxyEventV2.
|
|
6
6
|
*
|
|
7
|
-
* `lambder/mock` is browser-safe,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* event is assignable and the returned function still fits
|
|
14
|
-
* LambderInvokeTransport wherever a caller expects one.
|
|
7
|
+
* `lambder/mock` is browser-safe, type graph included: a type-only import of
|
|
8
|
+
* the invoke caller would pull `aws-lambda` and `@aws-sdk/client-lambda` into
|
|
9
|
+
* the mock entry's .d.ts graph, and a frontend compiling without
|
|
10
|
+
* `skipLibCheck` or those @types would get errors from a module it never
|
|
11
|
+
* loads. Every field is optional, so a real event is assignable and the
|
|
12
|
+
* returned function still fits LambderInvokeTransport.
|
|
15
13
|
*/
|
|
16
14
|
export type LambderMockInvokeEvent = {
|
|
17
15
|
body?: string | undefined;
|
|
@@ -37,11 +35,11 @@ export type LambderMockInvokeResult = {
|
|
|
37
35
|
};
|
|
38
36
|
};
|
|
39
37
|
/**
|
|
40
|
-
* The mock app as the callee of a LambderInvokeCaller: the
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
38
|
+
* The mock app as the callee of a LambderInvokeCaller: the synthesized event
|
|
39
|
+
* is read the way the callee's createContext would read it, and the answer
|
|
40
|
+
* goes back as the Lambda response object the caller decodes. A server test
|
|
41
|
+
* can then point its typed invoke caller at a mock of the function it
|
|
42
|
+
* depends on, with the same registry a browser test uses.
|
|
45
43
|
*/
|
|
46
44
|
export declare const lambderMockInvokeTransport: (mockApp: {
|
|
47
45
|
handleRequest(request: LambderApiRequest): Promise<LambderApiAnswer>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { readApiEnvelope, cookieValuesByName, lowercaseHeaderNames } from "../api/LambderApiRequest.js";
|
|
1
|
+
import { readApiEnvelope, cookieValuesByName, isApiCallContentType, lowercaseHeaderNames } from "../api/LambderApiRequest.js";
|
|
2
2
|
import { getAnswerHeader } from "../shared/wire/LambderAnswerHeaders.js";
|
|
3
3
|
import { base64ToText } from "../shared/util/LambderBase64.js";
|
|
4
4
|
import { normalizeClientIp } from "../shared/util/LambderClientIp.js";
|
|
5
5
|
/**
|
|
6
|
-
* The mock app as the callee of a LambderInvokeCaller: the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The mock app as the callee of a LambderInvokeCaller: the synthesized event
|
|
7
|
+
* is read the way the callee's createContext would read it, and the answer
|
|
8
|
+
* goes back as the Lambda response object the caller decodes. A server test
|
|
9
|
+
* can then point its typed invoke caller at a mock of the function it
|
|
10
|
+
* depends on, with the same registry a browser test uses.
|
|
11
11
|
*/
|
|
12
12
|
export const lambderMockInvokeTransport = (mockApp) => async (event, { signal }) => {
|
|
13
13
|
const rawBody = event.body ?? "";
|
|
@@ -21,10 +21,11 @@ export const lambderMockInvokeTransport = (mockApp) => async (event, { signal })
|
|
|
21
21
|
}
|
|
22
22
|
const headers = lowercaseHeaderNames(event.headers);
|
|
23
23
|
const cookies = cookieValuesByName(event.cookies ?? []);
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
24
|
+
// A POST of another type is no API call on the server either. The
|
|
25
|
+
// address is the one the synthesized event carries in sourceIp, as the
|
|
26
|
+
// server's createContext reads it; no forwarding header is trusted here
|
|
27
|
+
// either, so a per-IP limit keys the same address under both adapters.
|
|
28
|
+
const request = isApiCallContentType(headers) && readApiEnvelope(post, {
|
|
28
29
|
headers, cookies,
|
|
29
30
|
ip: normalizeClientIp(event.requestContext?.http?.sourceIp ?? ""),
|
|
30
31
|
host: headers.host || event.requestContext?.domainName || "lambder-invoke",
|
|
@@ -4,13 +4,11 @@ import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* The parts of the msw module the adapter uses: `import * as msw from "msw"`.
|
|
6
6
|
*
|
|
7
|
-
* Written so the real package satisfies it
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* here and the handler this returned did not fit `setupWorker`. Both errors
|
|
13
|
-
* landed on the documented five-line wiring.
|
|
7
|
+
* Written so the real package satisfies it. msw's resolver answers a
|
|
8
|
+
* Response, or `undefined` to hand the request back (its
|
|
9
|
+
* AsyncResponseResolverReturnType); a resolver declared to return
|
|
10
|
+
* `Promise<unknown>` is not assignable to that, so `http.post` would not fit
|
|
11
|
+
* here and the returned handler would not fit `setupWorker`.
|
|
14
12
|
*/
|
|
15
13
|
export type LambderMswModule = {
|
|
16
14
|
http: {
|
|
@@ -43,10 +41,10 @@ export type LambderMockMswTarget = {
|
|
|
43
41
|
/**
|
|
44
42
|
* The host the runtime's cookies belong to, which this adapter's jar is
|
|
45
43
|
* scoped by. The runtime's value, not the request URL's: signIn plants at
|
|
46
|
-
* the app's cookieHost and the direct transport's jar sends from there
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* the app's cookieHost and the direct transport's jar sends from there.
|
|
45
|
+
* Scoped by the page's host instead, the jar would hold session cookies
|
|
46
|
+
* at a host it never sends them to, and every session call behind the
|
|
47
|
+
* worker would answer sessionExpired with a full jar.
|
|
50
48
|
*/
|
|
51
49
|
readonly cookieHost: string;
|
|
52
50
|
};
|
|
@@ -54,25 +52,31 @@ export type LambderMockMswTarget = {
|
|
|
54
52
|
* ONE MSW request handler for the whole API path, over the mock app: the
|
|
55
53
|
* opt-in that makes mocked calls appear in the browser's network panel as
|
|
56
54
|
* genuine requests, with real method, status, timing and bodies. The request
|
|
57
|
-
* is read the way the server reads it,
|
|
58
|
-
*
|
|
55
|
+
* is read the way the server reads it, headers included, with the cookies a
|
|
56
|
+
* browser would send.
|
|
59
57
|
*
|
|
60
|
-
* Session cookies are held in a jar here
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
58
|
+
* Session cookies are held in a jar here, because the browser will not hold
|
|
59
|
+
* them: a response a service worker synthesizes never reaches the cookie
|
|
60
|
+
* store, and MSW's own jar comma-joins the Set-Cookie headers before parsing
|
|
61
|
+
* them, losing every cookie after the first. So the answer's cookies go into
|
|
62
|
+
* the jar, the next request carries them back, and the ones a page's scripts
|
|
63
|
+
* may see are mirrored into document.cookie. The Set-Cookie headers still
|
|
64
|
+
* travel on the response, where the network panel shows them.
|
|
65
|
+
*
|
|
66
|
+
* A request's cookies are therefore the jar's and document.cookie's, never
|
|
67
|
+
* its Cookie header: MSW fills that from its own store, which captures the
|
|
68
|
+
* HttpOnly session cookie off those Set-Cookie headers and keeps it in
|
|
69
|
+
* localStorage across reloads. Reading it would send a second session after
|
|
70
|
+
* a user switch (every call answering sessionExpired), keep a cleared jar
|
|
71
|
+
* signed in, and put the raw token into request events.
|
|
68
72
|
*
|
|
69
73
|
* Lambder never depends on msw: the app installs it and passes the module
|
|
70
74
|
* in. An injected network failure answers MSW's network error. A POST whose
|
|
71
75
|
* body is not an API envelope is left to other handlers.
|
|
72
76
|
*
|
|
73
77
|
* Generic over the module so the handler keeps msw's own handler type, which
|
|
74
|
-
* is what `setupWorker(...)` and `setupServer(...)` take
|
|
75
|
-
*
|
|
78
|
+
* is what `setupWorker(...)` and `setupServer(...)` take; typed `unknown`,
|
|
79
|
+
* the documented wiring would be a type error at the consumer.
|
|
76
80
|
*/
|
|
77
81
|
export declare const lambderMockMswHandler: <M extends LambderMswModule>(mockApp: LambderMockMswTarget, options: {
|
|
78
82
|
msw: M;
|
|
@@ -86,12 +90,12 @@ export declare const lambderMockMswHandler: <M extends LambderMswModule>(mockApp
|
|
|
86
90
|
* a partially mocked app runs in while its remaining endpoints still
|
|
87
91
|
* come from a real backend.
|
|
88
92
|
*
|
|
89
|
-
* This and `mockApp.restNotMocked(reason)`
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
93
|
+
* This and `mockApp.restNotMocked(reason)` answer the same question,
|
|
94
|
+
* and the rest entry wins: it gives the runtime an entry for every
|
|
95
|
+
* name, so nothing is unmocked here and a call is answered notMocked
|
|
96
|
+
* rather than passed on. Pick the rest entry for an app with no
|
|
97
|
+
* backend to reach, and this for one whose remaining endpoints are
|
|
98
|
+
* served by a real one.
|
|
95
99
|
*/
|
|
96
100
|
onUnmocked?: "refuse" | "passthrough";
|
|
97
101
|
/** The client IP its calls are read as arriving from. Default: the runtime's own defaultClientIp. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LambderMockTransportError } from "./LambderMockFailureInjector.js";
|
|
2
|
-
import { readApiEnvelope, cookieValuesByName, lowercaseHeaderNames } from "../api/LambderApiRequest.js";
|
|
2
|
+
import { readApiEnvelope, cookieValuesByName, isApiCallContentType, lowercaseHeaderNames } from "../api/LambderApiRequest.js";
|
|
3
3
|
import { getAnswerHeader } from "../shared/wire/LambderAnswerHeaders.js";
|
|
4
4
|
import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
|
|
5
5
|
import { normalizeClientIp } from "../shared/util/LambderClientIp.js";
|
|
@@ -7,25 +7,31 @@ import { normalizeClientIp } from "../shared/util/LambderClientIp.js";
|
|
|
7
7
|
* ONE MSW request handler for the whole API path, over the mock app: the
|
|
8
8
|
* opt-in that makes mocked calls appear in the browser's network panel as
|
|
9
9
|
* genuine requests, with real method, status, timing and bodies. The request
|
|
10
|
-
* is read the way the server reads it,
|
|
11
|
-
*
|
|
10
|
+
* is read the way the server reads it, headers included, with the cookies a
|
|
11
|
+
* browser would send.
|
|
12
12
|
*
|
|
13
|
-
* Session cookies are held in a jar here
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
13
|
+
* Session cookies are held in a jar here, because the browser will not hold
|
|
14
|
+
* them: a response a service worker synthesizes never reaches the cookie
|
|
15
|
+
* store, and MSW's own jar comma-joins the Set-Cookie headers before parsing
|
|
16
|
+
* them, losing every cookie after the first. So the answer's cookies go into
|
|
17
|
+
* the jar, the next request carries them back, and the ones a page's scripts
|
|
18
|
+
* may see are mirrored into document.cookie. The Set-Cookie headers still
|
|
19
|
+
* travel on the response, where the network panel shows them.
|
|
20
|
+
*
|
|
21
|
+
* A request's cookies are therefore the jar's and document.cookie's, never
|
|
22
|
+
* its Cookie header: MSW fills that from its own store, which captures the
|
|
23
|
+
* HttpOnly session cookie off those Set-Cookie headers and keeps it in
|
|
24
|
+
* localStorage across reloads. Reading it would send a second session after
|
|
25
|
+
* a user switch (every call answering sessionExpired), keep a cleared jar
|
|
26
|
+
* signed in, and put the raw token into request events.
|
|
21
27
|
*
|
|
22
28
|
* Lambder never depends on msw: the app installs it and passes the module
|
|
23
29
|
* in. An injected network failure answers MSW's network error. A POST whose
|
|
24
30
|
* body is not an API envelope is left to other handlers.
|
|
25
31
|
*
|
|
26
32
|
* Generic over the module so the handler keeps msw's own handler type, which
|
|
27
|
-
* is what `setupWorker(...)` and `setupServer(...)` take
|
|
28
|
-
*
|
|
33
|
+
* is what `setupWorker(...)` and `setupServer(...)` take; typed `unknown`,
|
|
34
|
+
* the documented wiring would be a type error at the consumer.
|
|
29
35
|
*/
|
|
30
36
|
export const lambderMockMswHandler = (mockApp, options) => {
|
|
31
37
|
const { msw, apiPath } = options;
|
|
@@ -38,13 +44,11 @@ export const lambderMockMswHandler = (mockApp, options) => {
|
|
|
38
44
|
// cookies name.
|
|
39
45
|
if (!options.cookieJar)
|
|
40
46
|
mockApp.adoptCookieJar(jar);
|
|
41
|
-
// The runtime's default, not
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// Normalized the way every other adapter's is, so one address is one
|
|
47
|
-
// counter under a `per: "ip"` limit however it was spelled.
|
|
47
|
+
// The runtime's default, not one of this adapter's own, so the direct
|
|
48
|
+
// transport and the service worker agree on ctx.request.ip and a per-IP
|
|
49
|
+
// rate limit counts one client as one. Normalized the way every other
|
|
50
|
+
// adapter's is, so one address is one counter under a `per: "ip"` limit
|
|
51
|
+
// however it was spelled.
|
|
48
52
|
const clientIp = normalizeClientIp(options.clientIp ?? mockApp.defaultClientIp);
|
|
49
53
|
const handler = msw.http.post(apiPath, async ({ request }) => {
|
|
50
54
|
let post;
|
|
@@ -54,27 +58,34 @@ export const lambderMockMswHandler = (mockApp, options) => {
|
|
|
54
58
|
catch {
|
|
55
59
|
return undefined;
|
|
56
60
|
}
|
|
57
|
-
// Through the one header map every adapter builds,
|
|
58
|
-
// Object.create(null): a header literally named
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
// undefined.
|
|
61
|
+
// Through the one header map every adapter builds, on
|
|
62
|
+
// Object.create(null): on a plain object, a header literally named
|
|
63
|
+
// __proto__ would be dropped here though the server keeps it as an own
|
|
64
|
+
// key, and headers["toString"] would hand a guard an inherited
|
|
65
|
+
// function where every other adapter gives undefined.
|
|
62
66
|
const headers = lowercaseHeaderNames(Object.fromEntries(request.headers));
|
|
67
|
+
// A POST of another type is no API call on the server either: it
|
|
68
|
+
// goes on to MSW's other handlers and the network, as a non-envelope
|
|
69
|
+
// body does below, rather than working here and not in production.
|
|
70
|
+
if (!isApiCallContentType(headers))
|
|
71
|
+
return undefined;
|
|
72
|
+
// The cookies travel as `cookies` below, as the direct transport's do.
|
|
73
|
+
delete headers.cookie;
|
|
63
74
|
const url = new URL(request.url);
|
|
64
75
|
// Only the cookies whose scope covers this call, as a browser would
|
|
65
|
-
// send:
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// believe.
|
|
76
|
+
// send: the whole jar would send one host's session to another once a
|
|
77
|
+
// jar is shared across hosts, and cookies the request path is not in
|
|
78
|
+
// scope for. The host is the runtime's own, the path the one this call
|
|
79
|
+
// is going to. The jar's copies come first: where a name is in both,
|
|
80
|
+
// the jar holds what this runtime last set and the document's copy
|
|
81
|
+
// mirrors it, so the jar is the one to believe.
|
|
72
82
|
const cookieScope = { host: mockApp.cookieHost, path: url.pathname };
|
|
73
83
|
const cookies = cookieValuesByName(jar.cookiePairs(cookieScope));
|
|
74
84
|
// Pair by pair, so a name the document holds at two scopes keeps both
|
|
75
85
|
// values and the session controller can weigh them, as it does on
|
|
76
86
|
// the server.
|
|
77
|
-
|
|
87
|
+
const pageCookies = typeof document === "undefined" ? "" : document.cookie;
|
|
88
|
+
for (const [name, values] of Object.entries(cookieValuesByName(pageCookies.split(";")))) {
|
|
78
89
|
for (const value of values) {
|
|
79
90
|
if (!cookies[name]?.includes(value))
|
|
80
91
|
(cookies[name] ??= []).push(value);
|
|
@@ -105,12 +116,12 @@ export const lambderMockMswHandler = (mockApp, options) => {
|
|
|
105
116
|
const setCookies = getAnswerHeader(answer.headers, "Set-Cookie") ?? [];
|
|
106
117
|
jar.storeSetCookies(setCookies, cookieScope);
|
|
107
118
|
// The cookies a page's own scripts may see are mirrored into
|
|
108
|
-
// document.cookie, so it reads the same in mocked development as
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
119
|
+
// document.cookie, so it reads the same in mocked development as
|
|
120
|
+
// against the real backend. Not decoration: the browser caller reads
|
|
121
|
+
// the CSRF token from there and posts it on the envelope, so without
|
|
122
|
+
// this every session call fails its CSRF check. Done by the runtime,
|
|
123
|
+
// which owns the one mirror implementation and remembers what was
|
|
124
|
+
// planted for reset().
|
|
114
125
|
mockApp.mirrorCookiesIntoDocument(setCookies);
|
|
115
126
|
const responseHeaders = new Headers();
|
|
116
127
|
for (const [key, values] of Object.entries(answer.headers)) {
|