lambder 6.0.1 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CHANGELOG.md +2316 -0
  2. package/README.md +60 -33
  3. package/dist/api/LambderApiAnswer.d.ts +40 -0
  4. package/dist/api/LambderApiAnswer.js +19 -0
  5. package/dist/api/LambderApiCallContext.d.ts +38 -0
  6. package/dist/api/LambderApiCallContext.js +13 -0
  7. package/dist/api/LambderApiDefinition.d.ts +18 -0
  8. package/dist/api/LambderApiDefinition.js +1 -0
  9. package/dist/api/LambderApiEnvelope.d.ts +67 -0
  10. package/dist/api/LambderApiEnvelope.js +180 -0
  11. package/dist/api/LambderApiGuards.d.ts +302 -0
  12. package/dist/api/LambderApiGuards.js +134 -0
  13. package/dist/api/LambderApiIdempotency.d.ts +122 -0
  14. package/dist/api/LambderApiIdempotency.js +330 -0
  15. package/dist/api/LambderApiPipeline.d.ts +134 -0
  16. package/dist/api/LambderApiPipeline.js +221 -0
  17. package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
  18. package/dist/api/LambderApiPolicyEngine.js +77 -0
  19. package/dist/api/LambderApiRateLimits.d.ts +206 -0
  20. package/dist/api/LambderApiRateLimits.js +239 -0
  21. package/dist/api/LambderApiRequest.d.ts +101 -0
  22. package/dist/api/LambderApiRequest.js +129 -0
  23. package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
  24. package/dist/api/LambderApiValidationRefusal.js +40 -0
  25. package/dist/client/LambderCaller.d.ts +62 -55
  26. package/dist/client/LambderCaller.js +147 -90
  27. package/dist/client/lambderFetchTransport.d.ts +9 -0
  28. package/dist/client/lambderFetchTransport.js +71 -0
  29. package/dist/client.d.ts +20 -10
  30. package/dist/client.js +11 -5
  31. package/dist/core/Lambder.d.ts +117 -253
  32. package/dist/core/Lambder.js +374 -341
  33. package/dist/core/LambderContext.d.ts +54 -44
  34. package/dist/core/LambderContext.js +41 -110
  35. package/dist/core/LambderCreateOptions.d.ts +285 -0
  36. package/dist/core/LambderCreateOptions.js +44 -0
  37. package/dist/core/LambderFiles.d.ts +1 -45
  38. package/dist/core/LambderFiles.js +18 -38
  39. package/dist/core/LambderIndexHtml.d.ts +37 -0
  40. package/dist/core/LambderIndexHtml.js +87 -0
  41. package/dist/core/LambderPolicyBuilders.d.ts +17 -0
  42. package/dist/core/LambderPolicyBuilders.js +16 -0
  43. package/dist/core/LambderPublicFiles.d.ts +5 -2
  44. package/dist/core/LambderPublicFiles.js +7 -2
  45. package/dist/core/LambderResolver.d.ts +8 -6
  46. package/dist/core/LambderResponse.d.ts +29 -11
  47. package/dist/core/LambderResponse.js +96 -49
  48. package/dist/core/LambderResponseBuilder.d.ts +18 -14
  49. package/dist/core/LambderResponseBuilder.js +19 -25
  50. package/dist/core/LambderRouting.d.ts +18 -7
  51. package/dist/core/LambderRouting.js +17 -7
  52. package/dist/core/LambderTemplatingEngine.d.ts +0 -62
  53. package/dist/core/LambderTemplatingEngine.js +7 -3
  54. package/dist/index.d.ts +85 -32
  55. package/dist/index.js +44 -16
  56. package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
  57. package/dist/invoke/LambderInvokeCaller.js +140 -335
  58. package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
  59. package/dist/invoke/LambderInvokeOutcome.js +129 -0
  60. package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
  61. package/dist/invoke/LambderLambdaEvent.js +187 -0
  62. package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
  63. package/dist/invoke/lambderHandlerTransport.js +89 -0
  64. package/dist/mock/LambderMockApp.d.ts +352 -0
  65. package/dist/mock/LambderMockApp.js +815 -0
  66. package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
  67. package/dist/mock/LambderMockBrowserCookies.js +76 -0
  68. package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
  69. package/dist/mock/LambderMockCallRecorder.js +183 -0
  70. package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
  71. package/dist/mock/LambderMockCreateOptions.js +9 -0
  72. package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
  73. package/dist/mock/LambderMockEntryRegistry.js +126 -0
  74. package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
  75. package/dist/mock/LambderMockFailureInjector.js +138 -0
  76. package/dist/mock/LambderMockTypes.d.ts +421 -0
  77. package/dist/mock/LambderMockTypes.js +8 -0
  78. package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
  79. package/dist/mock/lambderMockConsoleLogger.js +35 -0
  80. package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
  81. package/dist/mock/lambderMockInvokeTransport.js +52 -0
  82. package/dist/mock/lambderMockMswHandler.d.ts +99 -0
  83. package/dist/mock/lambderMockMswHandler.js +126 -0
  84. package/dist/mock.d.ts +34 -0
  85. package/dist/mock.js +27 -0
  86. package/dist/session/LambderSessionController.d.ts +199 -30
  87. package/dist/session/LambderSessionController.js +396 -82
  88. package/dist/session/LambderSessionCrypto.d.ts +66 -0
  89. package/dist/session/LambderSessionCrypto.js +101 -0
  90. package/dist/session/LambderSessionManager.d.ts +118 -80
  91. package/dist/session/LambderSessionManager.js +212 -184
  92. package/dist/shared/LambderI18n.d.ts +6 -6
  93. package/dist/shared/LambderI18n.js +1 -1
  94. package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
  95. package/dist/shared/contracts/LambderFileSource.js +19 -0
  96. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
  97. package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
  98. package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
  99. package/dist/shared/contracts/LambderRateLimiter.js +24 -0
  100. package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
  101. package/dist/shared/contracts/LambderSessionStore.js +13 -0
  102. package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
  103. package/dist/shared/transport/LambderApiTransport.js +65 -0
  104. package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
  105. package/dist/shared/transport/LambderCookieJar.js +246 -0
  106. package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
  107. package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
  108. package/dist/shared/util/LambderBase64.d.ts +10 -0
  109. package/dist/shared/util/LambderBase64.js +27 -0
  110. package/dist/shared/util/LambderCallAbort.d.ts +62 -0
  111. package/dist/shared/util/LambderCallAbort.js +80 -0
  112. package/dist/shared/util/LambderClientIp.d.ts +32 -0
  113. package/dist/shared/util/LambderClientIp.js +56 -0
  114. package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
  115. package/dist/shared/util/LambderExpiringMap.js +217 -0
  116. package/dist/shared/util/LambderKeyFields.d.ts +32 -0
  117. package/dist/shared/util/LambderKeyFields.js +34 -0
  118. package/dist/shared/util/LambderNodeModules.d.ts +9 -0
  119. package/dist/shared/util/LambderNodeModules.js +39 -0
  120. package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
  121. package/dist/shared/util/LambderOptionChecks.js +33 -0
  122. package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
  123. package/dist/shared/util/LambderResponseBrand.js +18 -0
  124. package/dist/shared/util/LambderTextDigest.d.ts +17 -0
  125. package/dist/shared/util/LambderTextDigest.js +34 -0
  126. package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
  127. package/dist/shared/util/LambderTypeUtilities.js +8 -0
  128. package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
  129. package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
  130. package/dist/shared/wire/LambderApiContract.d.ts +129 -0
  131. package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
  132. package/dist/shared/wire/LambderApiOptionValues.js +11 -0
  133. package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
  134. package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
  135. package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
  136. package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
  137. package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
  138. package/dist/shared/wire/LambderCallOptions.js +17 -0
  139. package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
  140. package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
  141. package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
  142. package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
  143. package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
  144. package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
  145. package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
  146. package/dist/shared/wire/LambderHttpStatus.js +1 -0
  147. package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
  148. package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
  149. package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
  150. package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
  151. package/dist/stores/LambderDdbCache.d.ts +12 -9
  152. package/dist/stores/LambderDdbCache.js +56 -47
  153. package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
  154. package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
  155. package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
  156. package/dist/stores/LambderDdbRateLimiter.js +47 -45
  157. package/dist/stores/LambderDdbSdk.d.ts +83 -6
  158. package/dist/stores/LambderDdbSdk.js +83 -2
  159. package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
  160. package/dist/stores/LambderDdbSessionStore.js +161 -0
  161. package/dist/stores/LambderHttpFileSource.d.ts +1 -1
  162. package/dist/stores/LambderHttpFileSource.js +10 -1
  163. package/dist/stores/LambderLocalFileSource.d.ts +15 -0
  164. package/dist/stores/LambderLocalFileSource.js +28 -0
  165. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
  166. package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
  167. package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
  168. package/dist/stores/LambderMemoryRateLimiter.js +64 -0
  169. package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
  170. package/dist/stores/LambderMemorySessionStore.js +74 -0
  171. package/dist/stores/LambderS3FileSource.d.ts +1 -1
  172. package/dist/stores/LambderS3FileSource.js +1 -1
  173. package/package.json +26 -24
  174. package/dist/client/LambderMSW.d.ts +0 -69
  175. package/dist/client/LambderMSW.js +0 -121
  176. package/dist/policies/LambderApiGuards.d.ts +0 -256
  177. package/dist/policies/LambderApiGuards.js +0 -94
  178. package/dist/policies/LambderApiIdempotency.d.ts +0 -58
  179. package/dist/policies/LambderApiIdempotency.js +0 -219
  180. package/dist/policies/LambderApiPolicies.d.ts +0 -42
  181. package/dist/policies/LambderApiPolicies.js +0 -52
  182. package/dist/policies/LambderApiRateLimits.d.ts +0 -132
  183. package/dist/policies/LambderApiRateLimits.js +0 -119
  184. package/dist/shared/LambderApiContract.d.ts +0 -57
  185. package/dist/shared/LambderApiOutcome.d.ts +0 -69
  186. package/dist/shared/LambderCallOptions.d.ts +0 -71
  187. package/dist/shared/LambderCallOptions.js +0 -16
  188. package/dist/shared/node-polyfills.d.ts +0 -4
  189. package/dist/shared/node-polyfills.js +0 -58
  190. package/dist/stores/LambderDdbIdempotency.js +0 -229
  191. package/dist/testing.d.ts +0 -9
  192. package/dist/testing.js +0 -8
  193. /package/dist/shared/{LambderApiContract.js → wire/LambderApiContract.js} +0 -0
  194. /package/dist/{core → shared/wire}/LambderCookie.d.ts +0 -0
  195. /package/dist/{core → shared/wire}/LambderCookie.js +0 -0
@@ -0,0 +1,421 @@
1
+ /**
2
+ * The types of the mock runtime: what a registry entry is, what a handler
3
+ * receives, how a registry is checked against a contract, and what the
4
+ * subscription emits. Everything a consumer needs at runtime it restates in
5
+ * the builders; these types pin every restatement to the contract type, so
6
+ * the contract stays a type-only import in the consuming app.
7
+ */
8
+ import type { z } from "zod";
9
+ import type { LambderApiMode, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractGuardInputsOf, LambderContractGuardNames, LambderContractGuardsOf, LambderContractIdempotencyOf, LambderContractKeysWithMode, LambderContractMode, LambderContractRateLimitOf } from "../shared/wire/LambderApiContract.js";
10
+ import type { LambderApiGuard, LambderGuardDataOf, LambderGuardMetaMap } from "../api/LambderApiGuards.js";
11
+ import type { LambderApiRateLimitPolicyConfig } from "../api/LambderApiRateLimits.js";
12
+ import type { LambderApiCallContext } from "../api/LambderApiCallContext.js";
13
+ import type { LambderApiRequest } from "../api/LambderApiRequest.js";
14
+ import type { LambderHttpStatusCode } from "../shared/wire/LambderHttpStatus.js";
15
+ import type { LambderApiDefinition } from "../api/LambderApiDefinition.js";
16
+ import type { LambderApiEnvelopeBody } from "../shared/wire/LambderApiContract.js";
17
+ import type { LambderAppRefusalMessage } from "../shared/wire/LambderApiRefusal.js";
18
+ import type { LambderSessionRecord } from "../shared/contracts/LambderSessionStore.js";
19
+ import type LambderSessionController from "../session/LambderSessionController.js";
20
+ export type LambderMockInputOf<C, K extends keyof C> = C[K] extends {
21
+ input: infer I;
22
+ } ? I : never;
23
+ export type LambderMockOutputOf<C, K extends keyof C> = C[K] extends {
24
+ output: infer O;
25
+ } ? O : never;
26
+ /**
27
+ * Maps every key an option object carries that its shape does not have to
28
+ * `never`, so a typo is an error on the key itself.
29
+ *
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 for
32
+ * the WHOLE literal, nested objects included, so `idempotency: { failOpn:
33
+ * false }` compiled, was dropped in silence, and left the runtime on the
34
+ * default. Intersected into a nested position rather than applied to the
35
+ * option type as a whole, because the type variable has to stay naked
36
+ * somewhere for the literal to be inferred from at all.
37
+ *
38
+ * `unknown` for anything that is not an object (`idempotency: true`), which
39
+ * intersects away: `keyof boolean` is Boolean's own prototype members, and
40
+ * mapping those to `never` would refuse the boolean form outright.
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.
46
+ */
47
+ export type LambderMockSurplusKeys<TOptions, TShape> = [
48
+ TOptions
49
+ ] extends [object] ? Record<Exclude<keyof TOptions, keyof TShape>, never> : unknown;
50
+ /**
51
+ * The context every mock guard and mock handler sees beside its own typed
52
+ * fields: the API core's call context plus the request, a session
53
+ * controller for the call, and the caller's abort signal.
54
+ */
55
+ export type LambderMockCallContext<S = any> = LambderApiCallContext<S> & {
56
+ apiName: string;
57
+ request: LambderApiRequest;
58
+ /** Create, rotate, refresh and end sessions, exactly as a server handler does through getSessionController(ctx). */
59
+ sessions: LambderSessionController<S>;
60
+ signal: AbortSignal;
61
+ /**
62
+ * The envelope fields that travel beside the payload, the mock's stand-in
63
+ * for the server's `res.api(payload, config)`: a mock handler returns its
64
+ * payload, so this is where the rest of the envelope goes. `logList` is
65
+ * the usual channel and lives on the context itself.
66
+ */
67
+ envelope: {
68
+ message?: string;
69
+ };
70
+ };
71
+ /** The same, with the session present: what a `session: true` mock guard and a session endpoint's handler see. */
72
+ export type LambderMockSessionCallContext<S = any> = Omit<LambderMockCallContext<S>, "session"> & {
73
+ session: LambderSessionRecord<S>;
74
+ };
75
+ /** What a mock handler for endpoint K receives. */
76
+ export type LambderMockContext<C, K extends keyof C, S, G> = Omit<LambderMockCallContext<S>, "apiName" | "session" | "guardData"> & {
77
+ apiName: K;
78
+ /** The payload as posted; never optional, the way a validated payload reaches a server handler. */
79
+ payload: LambderMockInputOf<C, K>;
80
+ /** The session on a session endpoint, null on a public one. */
81
+ session: LambderContractMode<C, K> extends "session" ? LambderSessionRecord<S> : LambderContractMode<C, K> extends "public" ? null : LambderSessionRecord<S> | null;
82
+ /** What the endpoint's guards returned, keyed by name, typed from the mock guard map and the endpoint's declaration. */
83
+ guardData: LambderGuardDataOf<LambderGuardMetaMap<G>, LambderContractGuardsOf<C, K>>;
84
+ /** The guard inputs the caller sent, typed by the contract; undefined for an endpoint that declares none. */
85
+ guardInputs: [LambderContractGuardInputsOf<C, K>] extends [never] ? undefined : LambderContractGuardInputsOf<C, K>;
86
+ /** The key the caller sent, when it sent a string; anything else is not a key and reaches a handler as undefined, having already been refused wherever it mattered. */
87
+ idempotencyKey: string | undefined;
88
+ };
89
+ /**
90
+ * The guard map a mock app must declare: one guard per name any endpoint of
91
+ * the contract declares, and for every guard the contract knows in
92
+ * guardInput mode, a `guardInput` schema whose output is what the server
93
+ * inferred. A missing name or a schema that parses to something else fails
94
+ * at the `guards` option.
95
+ */
96
+ export type LambderMockGuards<C, S = any> = {
97
+ [N in LambderContractGuardNames<C>]: LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>;
98
+ } & {
99
+ [N in LambderContractGuardInputNames<C>]: {
100
+ guardInput: z.ZodType<LambderContractGuardInput<C, N>, any>;
101
+ };
102
+ };
103
+ export type LambderMockHandler<C, K extends keyof C, S, G> = (ctx: LambderMockContext<C, K, S, G>) => LambderMockOutputOf<C, K> | Promise<LambderMockOutputOf<C, K>>;
104
+ /**
105
+ * The guards field of an entry: required whenever the contract declares any
106
+ * guard for the endpoint, and type-equal to the server's own declaration.
107
+ *
108
+ * Keyed on the guards themselves rather than on guardInputs, because the
109
+ * restatement is the only thing that tells the runtime which guards to run:
110
+ * a guard the entry leaves out simply does not run, and the mock answers 200
111
+ * where the server answers notAuthorized. The guards that carry no
112
+ * guardInput are exactly the "may this role call it" ones (session-only,
113
+ * param-only), so keying on guardInputs made the authorization checks the
114
+ * droppable half.
115
+ */
116
+ type LambderMockGuardsField<C, K extends keyof C> = [
117
+ LambderContractGuardsOf<C, K>
118
+ ] extends [never] ? {
119
+ guards?: never;
120
+ } : {
121
+ guards: LambderContractGuardsOf<C, K>;
122
+ };
123
+ /**
124
+ * The rate-limit field of an entry: required whenever the contract declares
125
+ * one, absent otherwise. Same reasoning as LambderMockGuardsField, and the
126
+ * argument transfers word for word: the restatement is the only thing that
127
+ * tells the runtime to apply the limit, so an entry that leaves it out
128
+ * answers 200 where the server answers 429.
129
+ */
130
+ type LambderMockRateLimitField<C, K extends keyof C> = [
131
+ LambderContractRateLimitOf<C, K>
132
+ ] extends [never] ? {
133
+ rateLimit?: never;
134
+ } : {
135
+ rateLimit: LambderContractRateLimitOf<C, K>;
136
+ };
137
+ /**
138
+ * The idempotency field of an entry: required whenever the contract declares
139
+ * one. An entry that leaves it out takes no claim and stores no record, so a
140
+ * retry re-runs the handler and the mock answers 200 where the server answers
141
+ * a replay or a 409.
142
+ */
143
+ type LambderMockIdempotencyField<C, K extends keyof C> = [
144
+ LambderContractIdempotencyOf<C, K>
145
+ ] extends [never] ? {
146
+ idempotency?: never;
147
+ } : {
148
+ idempotency: LambderContractIdempotencyOf<C, K>;
149
+ };
150
+ /**
151
+ * What override() hands back: call restore() to put the original handler
152
+ * back.
153
+ *
154
+ * Restore and nothing else. The handle also carried a `[Symbol.dispose]`
155
+ * member, for `using`, and that member is declared in `lib: ESNext` alone: a
156
+ * consumer on `lib: ES2022` (a Vue app's own setting, and the only consumer
157
+ * this package has) got TS2550 "Property 'dispose' does not exist on type
158
+ * 'SymbolConstructor'" out of the published .d.ts, from importing the entry at
159
+ * all, whenever skipLibCheck was off. A scoped override is a try/finally,
160
+ * which needs no lib.
161
+ */
162
+ export type LambderMockOverride = {
163
+ restore(): void;
164
+ };
165
+ /**
166
+ * What an entry's `input` schema must parse to: the endpoint's contract
167
+ * input, in both directions.
168
+ *
169
+ * One direction is not enough, and the missing one is the direction the drift
170
+ * actually travels in. `z.ZodType<Input>` is covariant in its output, so a
171
+ * schema parsing to a SUBTYPE of the contract input passed: an extra required
172
+ * field, or a literal where the contract says string. Such a schema refuses
173
+ * payloads the server accepts, and the mock then answers 422 to a call that
174
+ * works against the real backend, which is the exact failure the schema was
175
+ * added to reproduce, produced by the thing added to reproduce it. Requiring
176
+ * assignability the other way as well makes the schema's output the contract's
177
+ * input and nothing else.
178
+ *
179
+ * Intersected onto the schema rather than mapped to `never`, so the compiler
180
+ * quotes the reason at the `input` property. `z.any()` passes in both
181
+ * directions, which is the one deliberate escape hatch; `z.unknown()` does
182
+ * not.
183
+ */
184
+ type LambderMockInputPin<C, K extends keyof C, TSchema extends z.ZodType> = [
185
+ z.output<TSchema>
186
+ ] extends [LambderMockInputOf<C, K>] ? ([LambderMockInputOf<C, K>] extends [z.output<TSchema>] ? unknown : {
187
+ "LambderMockApp: this input schema parses to 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>;
188
+ }) : {
189
+ "LambderMockApp: this input schema parses to something else than the endpoint's contract input": LambderMockInputOf<C, K>;
190
+ };
191
+ /** An entry written in full: the declarations restated and pinned, plus the handler. */
192
+ 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> & {
193
+ /**
194
+ * A schema to validate the posted payload against, which makes the mock
195
+ * answer 422 exactly as the server would. Optional, and deliberately the
196
+ * mock's own: the contract is a type, so the server's schemas do not exist
197
+ * at runtime on this side, and importing them would put the whole endpoint
198
+ * surface into the browser bundle. Restate the shape for the endpoints
199
+ * whose rejection path a test needs to exercise; leave it off and a bad
200
+ * payload reaches the handler, as it does today.
201
+ *
202
+ * Pinned to the contract's input all the same: the schema is the mock's,
203
+ * but what it parses to is the server's, in both directions (see
204
+ * LambderMockInputPin).
205
+ */
206
+ input?: TInputSchema & LambderMockInputPin<C, K, TInputSchema>;
207
+ handler: LambderMockHandler<C, K, S, G>;
208
+ };
209
+ /**
210
+ * What publicApi/sessionApi accept: a bare handler only for an endpoint the
211
+ * contract declares nothing for, the full options otherwise, so the form that
212
+ * cannot carry a restatement is unavailable exactly where one is owed.
213
+ *
214
+ * All three declarations, not guards alone. The three fields above make each
215
+ * restatement required INSIDE the options form, and the bare handler is the
216
+ * form that has no fields at all, so keying this on guards left every
217
+ * guardless endpoint free to drop its rate limit and its idempotency again:
218
+ * the handler ran twice for one key and a perMin limit never answered 429,
219
+ * which is the whole of what those two fields exist to prevent.
220
+ */
221
+ export type LambderMockEntryInput<C, K extends keyof C, S, G, TInputSchema extends z.ZodType = z.ZodType> = [
222
+ LambderContractGuardsOf<C, K> | LambderContractRateLimitOf<C, K> | LambderContractIdempotencyOf<C, K>
223
+ ] extends [never] ? LambderMockHandler<C, K, S, G> | LambderMockEntryOptions<C, K, S, G, TInputSchema> : LambderMockEntryOptions<C, K, S, G, TInputSchema>;
224
+ /** One registry entry: the endpoint's definition as the pipeline runs it, and its handler (null when registered as not mocked). */
225
+ export type LambderMockEntry<C, K extends keyof C & string> = {
226
+ readonly name: K;
227
+ readonly mode: LambderApiMode;
228
+ readonly definition: LambderApiDefinition;
229
+ readonly handler: ((ctx: any) => Promise<unknown>) | null;
230
+ readonly notMockedReason: string | null;
231
+ };
232
+ /** The endpoint names a mock app may register under each mode. */
233
+ export type LambderMockPublicNames<C> = LambderContractKeysWithMode<C, "public">;
234
+ export type LambderMockSessionNames<C> = LambderContractKeysWithMode<C, "session">;
235
+ /** A slice: the entries of one module, keyed by endpoint name. */
236
+ export type LambderMockSlice<C, K extends keyof C & string> = {
237
+ readonly [P in K]: LambderMockEntry<C, P>;
238
+ };
239
+ /**
240
+ * What restNotMocked(reason) hands register(): "every endpoint the slices
241
+ * beside me leave out is not mocked, for this reason".
242
+ *
243
+ * A one-field object rather than a slice, because it names no endpoint: it
244
+ * answers the names nothing else claimed, and which those are is only known
245
+ * once the other arguments have been read. Every check below filters it out
246
+ * before it reads a name, so the field it does carry is neither a stray nor
247
+ * half of a duplicate; the one clause it changes is completeness.
248
+ */
249
+ export type LambderMockRestEntry = {
250
+ readonly restNotMockedReason: string;
251
+ };
252
+ /**
253
+ * The names one slice holds; distributes over a union of slices.
254
+ *
255
+ * A slice typed with an index signature (`Record<string, LambderMockEntry>`,
256
+ * or a list built in a loop) holds `string` as its key type, which would
257
+ * subtract every name from the missing list and pass the completeness check
258
+ * while registering almost nothing. Such a slice names nothing the compiler
259
+ * can check, so it contributes nothing here and LambderMockUncheckableSlices
260
+ * reports it for what it is.
261
+ *
262
+ * The rest entry contributes nothing either, and for the opposite reason: the
263
+ * one key it carries is not an endpoint name, so reading it would report
264
+ * "restNotMockedReason" as a stray, and two rest entries as a duplicate of it.
265
+ */
266
+ type LambderMockSliceNames<S> = S extends unknown ? (S extends LambderMockRestEntry ? never : (string extends keyof S ? never : keyof S & string)) : never;
267
+ /**
268
+ * Slices whose key set is an index signature rather than a finite list of
269
+ * names. Walks the tuple rather than distributing over `Slices[number]`,
270
+ * because the union loses which element held the index signature and the
271
+ * error should name it; LambderMockUncountableSlices rejects the lists this
272
+ * walk cannot cover.
273
+ */
274
+ type LambderMockUncheckableSlices<Slices extends readonly unknown[]> = Slices extends readonly [infer Head, ...infer Tail] ? (string extends keyof Head ? Head : never) | LambderMockUncheckableSlices<Tail> : never;
275
+ /** Endpoints of the contract no slice covers. */
276
+ export type LambderMockMissingNames<C, Slices extends readonly unknown[]> = Exclude<keyof C & string, LambderMockSliceNames<Slices[number]>>;
277
+ /** True when one of register()'s arguments is the rest entry. */
278
+ type LambderMockHasRestEntry<Slices extends readonly unknown[]> = [
279
+ Extract<Slices[number], LambderMockRestEntry>
280
+ ] extends [never] ? false : true;
281
+ /**
282
+ * The endpoints register() would leave unanswered: the ones no slice covers,
283
+ * unless a rest entry stands for them.
284
+ *
285
+ * The completeness clause and nothing else. An endpoint the rest entry answers
286
+ * is still an endpoint with no mock of its own, which is what
287
+ * LambderMockMissingNames says and why that one keeps its meaning; what the
288
+ * rest entry changes is whether leaving it out is a mistake.
289
+ */
290
+ type LambderMockUncoveredNames<C, Slices extends readonly unknown[]> = LambderMockHasRestEntry<Slices> extends true ? never : LambderMockMissingNames<C, Slices>;
291
+ /** Names the slices carry that the contract does not declare. */
292
+ export type LambderMockStrayNames<C, Slices extends readonly unknown[]> = Exclude<LambderMockSliceNames<Slices[number]>, keyof C & string>;
293
+ /** Endpoints covered by more than one slice. */
294
+ export type LambderMockDuplicateNames<Slices extends readonly unknown[]> = Slices extends readonly [infer Head, ...infer Tail] ? (LambderMockSliceNames<Head> & LambderMockSliceNames<Tail[number]>) | LambderMockDuplicateNames<Tail> : never;
295
+ /**
296
+ * True for a slice list whose length the compiler does not know: an array
297
+ * type rather than a tuple, `length: number`.
298
+ *
299
+ * Every check below is written over a tuple, and an array type quietly
300
+ * disables all of them: the overlap and index-signature walks fall to their
301
+ * `never` base case on the first step, and completeness reduces to "the
302
+ * element type mentions these names", which one element satisfies as well as
303
+ * twenty. `const slices = [userMocks, orderMocks]` spread into register() is
304
+ * exactly that type, so the array form is refused rather than passed.
305
+ */
306
+ type LambderMockUncountableSlices<Slices extends readonly unknown[]> = number extends Slices["length"] ? true : false;
307
+ /**
308
+ * What register() intersects its slices with: nothing when they cover the
309
+ * contract exactly once each, otherwise a shape naming what is wrong, which
310
+ * no slice list is assignable to. The key of that shape is the compiler's
311
+ * error message.
312
+ */
313
+ export type LambderMockRegistryCheck<C, Slices extends readonly unknown[]> = LambderMockUncountableSlices<Slices> extends true ? {
314
+ "LambderMockApp: register() was given an array of slices rather than a fixed list, so it cannot see how many there are and cannot check the contract is covered. Pass the slices as arguments, register(a, b, c), or declare the list with `as const` before spreading it": Slices;
315
+ } : [LambderMockUncheckableSlices<Slices>] extends [never] ? ([LambderMockStrayNames<C, Slices>] extends [never] ? ([LambderMockUncoveredNames<C, Slices>] extends [never] ? ([LambderMockDuplicateNames<Slices>] extends [never] ? unknown : {
316
+ "LambderMockApp: these endpoints are mocked in more than one slice": LambderMockDuplicateNames<Slices>;
317
+ }) : {
318
+ "LambderMockApp: these endpoints have no mock (add one, mockApp.notMocked with a reason, or mockApp.restNotMocked(reason) for everything left out)": LambderMockUncoveredNames<C, Slices>;
319
+ }) : {
320
+ "LambderMockApp: these names are not endpoints of the contract": LambderMockStrayNames<C, Slices>;
321
+ }) : {
322
+ "LambderMockApp: a slice is typed with an index signature, so register() cannot see which endpoints it covers. Build it with mockApp.apiSlice(...) or annotate it as LambderMockSlice": LambderMockUncheckableSlices<Slices>;
323
+ };
324
+ /** A latency setting: milliseconds, a range to draw from, or a function of the api name. */
325
+ export type LambderMockLatency = number | {
326
+ min: number;
327
+ max: number;
328
+ } | ((apiName: string) => number);
329
+ /**
330
+ * An injected failure. Each is rendered by the same function the pipeline
331
+ * uses for the real thing, so an injected 429 carries the Retry-After a
332
+ * real one does. `network` rejects the transport; `timeout` waits for the
333
+ * caller's own abort (a call with no timeout configured waits for its
334
+ * external signal, or for ever, which is what a timeout is).
335
+ */
336
+ export type LambderMockFailure = {
337
+ reason: "network";
338
+ } | {
339
+ reason: "timeout";
340
+ } | {
341
+ reason: "server";
342
+ } | {
343
+ reason: "refusal";
344
+ message?: LambderAppRefusalMessage | string;
345
+ statusCode?: LambderHttpStatusCode;
346
+ } | {
347
+ reason: "notAuthorized";
348
+ message?: LambderAppRefusalMessage | string;
349
+ } | {
350
+ reason: "sessionExpired";
351
+ } | {
352
+ reason: "versionExpired";
353
+ } | {
354
+ reason: "rateLimited";
355
+ retryAfterSeconds?: number;
356
+ message?: LambderAppRefusalMessage;
357
+ };
358
+ /** Every sibling in the package spells this `reason`: an outcome's, a transport failure's, an invoke failure's. */
359
+ export type LambderMockFailureReason = LambderMockFailure["reason"];
360
+ /**
361
+ * How one call ended, as the runtime saw it. `passthrough` is the MSW
362
+ * adapter's: the runtime answered nothing and the request went on to MSW's
363
+ * other handlers and the network.
364
+ */
365
+ export type LambderMockOutcome = "ok" | "refusal" | "notAuthorized" | "sessionExpired" | "versionExpired" | "rateLimited" | "replayed" | "validation" | "notMocked" | "unknownApi" | "crash" | "injected" | "passthrough";
366
+ export type LambderMockRequestEvent = {
367
+ phase: "request";
368
+ id: number;
369
+ apiName: string;
370
+ /** The endpoint's mode from the registry; null for a name the registry does not know. */
371
+ mode: LambderApiMode | null;
372
+ payload: unknown;
373
+ guardInputs: Record<string, unknown> | undefined;
374
+ /** Exactly as posted, so `unknown`: the key is client data and only the idempotency engine judges it. */
375
+ idempotencyKey: unknown;
376
+ version: string | null;
377
+ headers: Record<string, string>;
378
+ /** True when the request carried a session cookie. */
379
+ hasSessionCookie: boolean;
380
+ at: number;
381
+ };
382
+ export type LambderMockResponseEvent = {
383
+ phase: "response";
384
+ id: number;
385
+ apiName: string;
386
+ mode: LambderApiMode | null;
387
+ durationMs: number;
388
+ /** Absent when the transport rejected (an injected network failure or timeout), and on a passthrough. */
389
+ statusCode: number | null;
390
+ /**
391
+ * The answer's headers, with every Set-Cookie value replaced by
392
+ * "[redacted]": the cookie's name and attributes still read (did this
393
+ * call start a session, did it clear one), while the session token they
394
+ * carry stays out of a log a dev panel renders and a test snapshots.
395
+ */
396
+ headers: Record<string, string[]>;
397
+ /** The parsed envelope, when the answer was one. */
398
+ envelope: LambderApiEnvelopeBody<unknown> | null;
399
+ outcome: LambderMockOutcome;
400
+ guardsRun: string[];
401
+ /** The crash, or the injected transport failure. */
402
+ error?: Error;
403
+ at: number;
404
+ };
405
+ export type LambderMockCallEvent = LambderMockRequestEvent | LambderMockResponseEvent;
406
+ /** One completed call, for assertions and panels: the response event plus what was asked. */
407
+ export type LambderMockCallRecord = LambderMockResponseEvent & {
408
+ payload: unknown;
409
+ guardInputs: Record<string, unknown> | undefined;
410
+ };
411
+ export type LambderMockListener = (event: LambderMockCallEvent) => void;
412
+ /**
413
+ * The mock's rate-limit policies, keyed by name. Bound to the mock's own call
414
+ * context so a custom key handler is typed for the runtime that will actually
415
+ * call it: a handler built with the server's lambderRateLimitKey() reads the
416
+ * render context's ip/method/path, none of which the mock context has, so it
417
+ * would compile here and then key every caller identically. Build these with
418
+ * `rateLimitKey` from initLambderMock().
419
+ */
420
+ export type LambderMockRateLimitPolicies<S = any> = Record<string, LambderApiRateLimitPolicyConfig<LambderMockCallContext<S>>>;
421
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The types of the mock runtime: what a registry entry is, what a handler
3
+ * receives, how a registry is checked against a contract, and what the
4
+ * subscription emits. Everything a consumer needs at runtime it restates in
5
+ * the builders; these types pin every restatement to the contract type, so
6
+ * the contract stays a type-only import in the consuming app.
7
+ */
8
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { LambderMockListener } from "./LambderMockTypes.js";
2
+ export type LambderMockConsoleLoggerOptions = {
3
+ /** Log the request payload and the answer's envelope beside each line. Default: false. */
4
+ payloads?: boolean;
5
+ /** With payloads on, group each call's detail under a collapsed console group. Default: true. */
6
+ collapsed?: boolean;
7
+ /** Where to write. Default: console. */
8
+ console?: Pick<Console, "log" | "group" | "groupCollapsed" | "groupEnd">;
9
+ };
10
+ /**
11
+ * A ready-made subscriber: one line per completed call, naming the endpoint,
12
+ * how it ended, the status and the time it took; with `payloads` on, the
13
+ * request payload and the answer's envelope under a collapsed group. So
14
+ * `mockApp.subscribe("console", lambderMockConsoleLogger())` logs everything.
15
+ */
16
+ export declare const lambderMockConsoleLogger: (options?: LambderMockConsoleLoggerOptions) => LambderMockListener;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A ready-made subscriber: one line per completed call, naming the endpoint,
3
+ * how it ended, the status and the time it took; with `payloads` on, the
4
+ * request payload and the answer's envelope under a collapsed group. So
5
+ * `mockApp.subscribe("console", lambderMockConsoleLogger())` logs everything.
6
+ */
7
+ export const lambderMockConsoleLogger = (options = {}) => {
8
+ const output = options.console ?? console;
9
+ const collapsed = options.collapsed ?? true;
10
+ const payloads = new Map();
11
+ return (event) => {
12
+ if (event.phase === "request") {
13
+ if (options.payloads)
14
+ payloads.set(event.id, event.payload);
15
+ return;
16
+ }
17
+ const status = event.statusCode === null ? "no answer" : String(event.statusCode);
18
+ const line = `[lambder mock] ${event.apiName} → ${event.outcome} (${status}, ${event.durationMs}ms)`;
19
+ if (!options.payloads) {
20
+ output.log(line);
21
+ return;
22
+ }
23
+ const payload = payloads.get(event.id);
24
+ payloads.delete(event.id);
25
+ (collapsed ? output.groupCollapsed : output.group).call(output, line);
26
+ output.log("payload", payload);
27
+ if (event.envelope)
28
+ output.log("envelope", event.envelope);
29
+ if (event.guardsRun.length)
30
+ output.log("guards", event.guardsRun.join(", "));
31
+ if (event.error)
32
+ output.log("error", event.error);
33
+ output.groupEnd();
34
+ };
35
+ };
@@ -0,0 +1,50 @@
1
+ import type { LambderApiAnswer } from "../api/LambderApiAnswer.js";
2
+ import { type LambderApiRequest } from "../api/LambderApiRequest.js";
3
+ /**
4
+ * The fields of an invoke's synthesized event this transport reads, declared
5
+ * structurally rather than imported as APIGatewayProxyEventV2.
6
+ *
7
+ * `lambder/mock` is browser-safe, and its type graph is part of that claim: a
8
+ * type-only import of the invoke caller pulled `aws-lambda` and
9
+ * `@aws-sdk/client-lambda` into the .d.ts graph of the mock entry, so a
10
+ * frontend compiling without `skipLibCheck` and without those @types got
11
+ * errors from inside node_modules for a module it never loads. Every field a
12
+ * real event carries is optional here and no field is required, so a real
13
+ * event is assignable and the returned function still fits
14
+ * LambderInvokeTransport wherever a caller expects one.
15
+ */
16
+ export type LambderMockInvokeEvent = {
17
+ body?: string | undefined;
18
+ isBase64Encoded?: boolean | undefined;
19
+ headers?: Record<string, string | undefined> | undefined;
20
+ cookies?: string[] | undefined;
21
+ requestContext?: {
22
+ http?: {
23
+ sourceIp?: string;
24
+ } | undefined;
25
+ domainName?: string;
26
+ } | undefined;
27
+ };
28
+ /** What the callee answers with: the Lambda response object LambderInvokeCaller decodes. */
29
+ export type LambderMockInvokeResult = {
30
+ functionError: string | null;
31
+ result: {
32
+ statusCode: number;
33
+ headers: Record<string, string>;
34
+ cookies?: string[];
35
+ body: string;
36
+ isBase64Encoded: boolean;
37
+ };
38
+ };
39
+ /**
40
+ * The mock app as the callee of a LambderInvokeCaller: the invoke's
41
+ * synthesized event is read the way the callee's createContext would read
42
+ * it, and the answer goes back as the Lambda response object the caller
43
+ * decodes. So a server test can point its typed invoke caller at a mock of
44
+ * the function it depends on, with the same registry a browser test uses.
45
+ */
46
+ export declare const lambderMockInvokeTransport: (mockApp: {
47
+ handleRequest(request: LambderApiRequest): Promise<LambderApiAnswer>;
48
+ }) => ((event: LambderMockInvokeEvent, options: {
49
+ signal?: AbortSignal;
50
+ }) => Promise<LambderMockInvokeResult>);
@@ -0,0 +1,52 @@
1
+ import { readApiEnvelope, cookieValuesByName, lowercaseHeaderNames } from "../api/LambderApiRequest.js";
2
+ import { getAnswerHeader } from "../shared/wire/LambderAnswerHeaders.js";
3
+ import { base64ToText } from "../shared/util/LambderBase64.js";
4
+ import { normalizeClientIp } from "../shared/util/LambderClientIp.js";
5
+ /**
6
+ * The mock app as the callee of a LambderInvokeCaller: the invoke's
7
+ * synthesized event is read the way the callee's createContext would read
8
+ * it, and the answer goes back as the Lambda response object the caller
9
+ * decodes. So a server test can point its typed invoke caller at a mock of
10
+ * the function it depends on, with the same registry a browser test uses.
11
+ */
12
+ export const lambderMockInvokeTransport = (mockApp) => async (event, { signal }) => {
13
+ const rawBody = event.body ?? "";
14
+ const body = event.isBase64Encoded ? base64ToText(rawBody) : rawBody;
15
+ let post = {};
16
+ try {
17
+ post = JSON.parse(body || "{}") ?? {};
18
+ }
19
+ catch {
20
+ post = {};
21
+ }
22
+ const headers = lowercaseHeaderNames(event.headers);
23
+ const cookies = cookieValuesByName(event.cookies ?? []);
24
+ // The address the synthesized event carries in sourceIp, as the server's
25
+ // createContext reads it; no forwarding header is trusted here either,
26
+ // so a per-IP limit keys the same address under both adapters.
27
+ const request = readApiEnvelope(post, {
28
+ headers, cookies,
29
+ ip: normalizeClientIp(event.requestContext?.http?.sourceIp ?? ""),
30
+ host: headers.host || event.requestContext?.domainName || "lambder-invoke",
31
+ ...(signal ? { signal } : {}),
32
+ });
33
+ if (!request) {
34
+ return { functionError: null, result: { statusCode: 404, headers: { "content-type": "text/plain; charset=utf-8" }, body: "Not found.", isBase64Encoded: false } };
35
+ }
36
+ const answer = await mockApp.handleRequest(request);
37
+ const singleHeaders = {};
38
+ for (const [key, values] of Object.entries(answer.headers)) {
39
+ if (key.toLowerCase() !== "set-cookie")
40
+ singleHeaders[key] = values.join(", ");
41
+ }
42
+ return {
43
+ functionError: null,
44
+ result: {
45
+ statusCode: answer.statusCode,
46
+ headers: singleHeaders,
47
+ cookies: getAnswerHeader(answer.headers, "Set-Cookie") ?? [],
48
+ body: answer.body,
49
+ isBase64Encoded: answer.isBodyBase64 ?? false,
50
+ },
51
+ };
52
+ };