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.
Files changed (237) hide show
  1. package/CHANGELOG.md +1047 -3
  2. package/README.md +46 -21
  3. package/dist/api/LambderApiAnswer.d.ts +18 -22
  4. package/dist/api/LambderApiAnswer.js +6 -7
  5. package/dist/api/LambderApiCallContext.d.ts +21 -8
  6. package/dist/api/LambderApiCallContext.js +22 -4
  7. package/dist/api/LambderApiDefinition.d.ts +4 -3
  8. package/dist/api/LambderApiEnvelope.d.ts +14 -9
  9. package/dist/api/LambderApiEnvelope.js +33 -34
  10. package/dist/api/LambderApiGuards.d.ts +78 -51
  11. package/dist/api/LambderApiGuards.js +34 -36
  12. package/dist/api/LambderApiIdempotency.d.ts +68 -62
  13. package/dist/api/LambderApiIdempotency.js +214 -151
  14. package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
  15. package/dist/api/LambderApiOutputValidationError.js +50 -0
  16. package/dist/api/LambderApiPipeline.d.ts +47 -38
  17. package/dist/api/LambderApiPipeline.js +122 -63
  18. package/dist/api/LambderApiRateLimits.d.ts +201 -54
  19. package/dist/api/LambderApiRateLimits.js +185 -108
  20. package/dist/api/LambderApiRequest.d.ts +27 -21
  21. package/dist/api/LambderApiRequest.js +26 -19
  22. package/dist/api/LambderApiSignature.d.ts +12 -15
  23. package/dist/api/LambderApiSignature.js +28 -51
  24. package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
  25. package/dist/api/LambderApiValidationRefusal.js +10 -10
  26. package/dist/build/ContractTypePrinter.d.ts +85 -0
  27. package/dist/build/ContractTypePrinter.js +402 -0
  28. package/dist/build/freshProcessVerifier.d.ts +13 -0
  29. package/dist/build/freshProcessVerifier.js +19 -0
  30. package/dist/build/moduleLocation.d.ts +11 -0
  31. package/dist/build/moduleLocation.js +6 -0
  32. package/dist/build/writeApiContract.d.ts +78 -0
  33. package/dist/build/writeApiContract.js +302 -0
  34. package/dist/build/writeApiSignatures.d.ts +114 -0
  35. package/dist/build/writeApiSignatures.js +217 -0
  36. package/dist/build/writeFileAtomically.d.ts +8 -0
  37. package/dist/build/writeFileAtomically.js +22 -0
  38. package/dist/build.d.ts +14 -0
  39. package/dist/build.js +11 -0
  40. package/dist/client/LambderCaller.d.ts +13 -44
  41. package/dist/client/LambderCaller.js +77 -84
  42. package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
  43. package/dist/client/LambderReloadLoopBreaker.js +90 -46
  44. package/dist/client/LambderUploadRunner.d.ts +96 -0
  45. package/dist/client/LambderUploadRunner.js +234 -0
  46. package/dist/client/lambderFetchTransport.d.ts +4 -1
  47. package/dist/client/lambderFetchTransport.js +52 -28
  48. package/dist/client.d.ts +9 -3
  49. package/dist/client.js +6 -1
  50. package/dist/core/Lambder.d.ts +143 -79
  51. package/dist/core/Lambder.js +350 -231
  52. package/dist/core/LambderContext.d.ts +82 -15
  53. package/dist/core/LambderContext.js +107 -20
  54. package/dist/core/LambderCors.d.ts +21 -3
  55. package/dist/core/LambderCors.js +35 -16
  56. package/dist/core/LambderCrashHandling.d.ts +40 -0
  57. package/dist/core/LambderCrashHandling.js +97 -0
  58. package/dist/core/LambderCreateOptions.d.ts +151 -75
  59. package/dist/core/LambderCreateOptions.js +16 -23
  60. package/dist/core/LambderFiles.d.ts +21 -7
  61. package/dist/core/LambderFiles.js +62 -34
  62. package/dist/core/LambderIndexHtml.js +12 -11
  63. package/dist/core/LambderPolicyBuilders.d.ts +17 -5
  64. package/dist/core/LambderPolicyBuilders.js +17 -5
  65. package/dist/core/LambderPublicFiles.d.ts +11 -5
  66. package/dist/core/LambderPublicFiles.js +32 -4
  67. package/dist/core/LambderRequestPath.d.ts +43 -0
  68. package/dist/core/LambderRequestPath.js +63 -0
  69. package/dist/core/LambderResponse.d.ts +26 -5
  70. package/dist/core/LambderResponse.js +157 -70
  71. package/dist/core/LambderResponseBuilder.d.ts +49 -4
  72. package/dist/core/LambderResponseBuilder.js +64 -3
  73. package/dist/core/LambderRouting.d.ts +2 -3
  74. package/dist/core/LambderRouting.js +22 -7
  75. package/dist/core/LambderTemplatingEngine.js +211 -32
  76. package/dist/index.d.ts +25 -8
  77. package/dist/index.js +13 -4
  78. package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
  79. package/dist/invoke/LambderInvokeCaller.js +76 -66
  80. package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
  81. package/dist/invoke/LambderInvokeOutcome.js +9 -22
  82. package/dist/invoke/LambderLambdaEvent.d.ts +29 -9
  83. package/dist/invoke/LambderLambdaEvent.js +40 -22
  84. package/dist/invoke/lambderHandlerTransport.d.ts +9 -10
  85. package/dist/invoke/lambderHandlerTransport.js +15 -18
  86. package/dist/mock/LambderMockApp.d.ts +67 -83
  87. package/dist/mock/LambderMockApp.js +167 -153
  88. package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
  89. package/dist/mock/LambderMockBrowserCookies.js +24 -28
  90. package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
  91. package/dist/mock/LambderMockCallRecorder.js +19 -28
  92. package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
  93. package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
  94. package/dist/mock/LambderMockEntryRegistry.js +24 -29
  95. package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
  96. package/dist/mock/LambderMockFailureInjector.js +3 -6
  97. package/dist/mock/LambderMockTypes.d.ts +78 -108
  98. package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
  99. package/dist/mock/lambderMockInvokeTransport.js +11 -10
  100. package/dist/mock/lambderMockMswHandler.d.ts +43 -33
  101. package/dist/mock/lambderMockMswHandler.js +50 -39
  102. package/dist/mock/lambderMockUploadMswHandler.d.ts +26 -0
  103. package/dist/mock/lambderMockUploadMswHandler.js +28 -0
  104. package/dist/mock.d.ts +4 -1
  105. package/dist/mock.js +6 -3
  106. package/dist/session/LambderSessionController.d.ts +108 -89
  107. package/dist/session/LambderSessionController.js +187 -168
  108. package/dist/session/LambderSessionCrypto.d.ts +16 -7
  109. package/dist/session/LambderSessionCrypto.js +26 -12
  110. package/dist/session/LambderSessionManager.d.ts +124 -46
  111. package/dist/session/LambderSessionManager.js +262 -137
  112. package/dist/shared/LambderHtml.d.ts +42 -3
  113. package/dist/shared/LambderHtml.js +127 -7
  114. package/dist/shared/LambderHtmlPositions.d.ts +173 -0
  115. package/dist/shared/LambderHtmlPositions.js +652 -0
  116. package/dist/shared/LambderI18n.d.ts +10 -11
  117. package/dist/shared/LambderI18n.js +33 -21
  118. package/dist/shared/contracts/LambderCache.d.ts +66 -0
  119. package/dist/shared/contracts/LambderCache.js +11 -0
  120. package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
  121. package/dist/shared/contracts/LambderFileSource.js +5 -8
  122. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
  123. package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
  124. package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
  125. package/dist/shared/contracts/LambderRateLimiter.js +4 -5
  126. package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
  127. package/dist/shared/contracts/LambderSessionStore.js +5 -6
  128. package/dist/shared/contracts/LambderUploadBucket.d.ts +154 -0
  129. package/dist/shared/contracts/LambderUploadBucket.js +74 -0
  130. package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
  131. package/dist/shared/transport/LambderApiTransport.js +7 -7
  132. package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
  133. package/dist/shared/transport/LambderCookieJar.js +54 -66
  134. package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
  135. package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
  136. package/dist/shared/util/LambderCallAbort.d.ts +5 -5
  137. package/dist/shared/util/LambderCallAbort.js +5 -5
  138. package/dist/shared/util/LambderClientIp.d.ts +27 -11
  139. package/dist/shared/util/LambderClientIp.js +96 -13
  140. package/dist/shared/util/LambderContentDisposition.d.ts +10 -0
  141. package/dist/shared/util/LambderContentDisposition.js +13 -0
  142. package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
  143. package/dist/shared/util/LambderExpiringMap.js +41 -57
  144. package/dist/shared/util/LambderNodeModules.js +6 -7
  145. package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
  146. package/dist/shared/util/LambderOptionChecks.js +4 -4
  147. package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
  148. package/dist/shared/util/LambderResponseBrand.js +5 -5
  149. package/dist/shared/util/LambderTextDigest.d.ts +7 -5
  150. package/dist/shared/util/LambderTextDigest.js +11 -5
  151. package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
  152. package/dist/shared/util/LambderTypeUtilities.js +3 -3
  153. package/dist/shared/util/boundKeyField.d.ts +20 -0
  154. package/dist/shared/util/boundKeyField.js +34 -0
  155. package/dist/shared/util/canonicalJson.d.ts +11 -0
  156. package/dist/shared/util/canonicalJson.js +28 -0
  157. package/dist/shared/util/joinKeyFields.d.ts +20 -0
  158. package/dist/shared/util/joinKeyFields.js +22 -0
  159. package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
  160. package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
  161. package/dist/shared/wire/LambderApiContract.d.ts +98 -53
  162. package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
  163. package/dist/shared/wire/LambderApiOutcome.js +48 -23
  164. package/dist/shared/wire/LambderApiRefusal.d.ts +45 -27
  165. package/dist/shared/wire/LambderApiRefusal.js +42 -7
  166. package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
  167. package/dist/shared/wire/LambderApiSignature.js +16 -19
  168. package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
  169. package/dist/shared/wire/LambderCallOptions.js +9 -11
  170. package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
  171. package/dist/shared/wire/LambderCompressionCodec.js +31 -36
  172. package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
  173. package/dist/shared/wire/LambderCompressionOption.js +9 -9
  174. package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
  175. package/dist/shared/wire/LambderCrashDetail.js +12 -15
  176. package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
  177. package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
  178. package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
  179. package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
  180. package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
  181. package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
  182. package/dist/shared/wire/LambderInvokeApiId.js +27 -0
  183. package/dist/shared/wire/LambderOutcomeAssertions.d.ts +6 -7
  184. package/dist/shared/wire/LambderOutcomeAssertions.js +6 -7
  185. package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
  186. package/dist/shared/wire/LambderRequestPayload.js +4 -6
  187. package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -0
  188. package/dist/shared/wire/LambderUploadObjectFields.js +24 -0
  189. package/dist/shared/wire/LambderUploadRefusal.d.ts +9 -0
  190. package/dist/shared/wire/LambderUploadRefusal.js +18 -0
  191. package/dist/shared/wire/LambderUploadSchemas.d.ts +12 -0
  192. package/dist/shared/wire/LambderUploadSchemas.js +30 -0
  193. package/dist/stores/LambderCacheFiller.d.ts +48 -0
  194. package/dist/stores/LambderCacheFiller.js +119 -0
  195. package/dist/stores/LambderCacheKeys.d.ts +26 -0
  196. package/dist/stores/LambderCacheKeys.js +54 -0
  197. package/dist/stores/LambderCacheValues.d.ts +45 -0
  198. package/dist/stores/LambderCacheValues.js +74 -0
  199. package/dist/stores/LambderDdbCache.d.ts +121 -56
  200. package/dist/stores/LambderDdbCache.js +528 -225
  201. package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
  202. package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
  203. package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
  204. package/dist/stores/LambderDdbRateLimiter.js +151 -39
  205. package/dist/stores/LambderDdbSdk.d.ts +43 -31
  206. package/dist/stores/LambderDdbSdk.js +80 -38
  207. package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
  208. package/dist/stores/LambderDdbSessionStore.js +119 -47
  209. package/dist/stores/LambderHttpFileSource.d.ts +15 -6
  210. package/dist/stores/LambderHttpFileSource.js +15 -13
  211. package/dist/stores/LambderMemoryCache.d.ts +49 -0
  212. package/dist/stores/LambderMemoryCache.js +113 -0
  213. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
  214. package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
  215. package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
  216. package/dist/stores/LambderMemoryRateLimiter.js +14 -13
  217. package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
  218. package/dist/stores/LambderMemorySessionStore.js +38 -19
  219. package/dist/stores/LambderMemoryUploadBucket.d.ts +99 -0
  220. package/dist/stores/LambderMemoryUploadBucket.js +219 -0
  221. package/dist/stores/LambderS3FileSource.d.ts +21 -6
  222. package/dist/stores/LambderS3FileSource.js +12 -7
  223. package/dist/stores/LambderS3UploadBucket.d.ts +73 -0
  224. package/dist/stores/LambderS3UploadBucket.js +144 -0
  225. package/dist/stores/LambderSdkInstallHint.d.ts +11 -0
  226. package/dist/stores/LambderSdkInstallHint.js +14 -0
  227. package/dist/testing/LambderTestApp.d.ts +23 -25
  228. package/dist/testing/LambderTestApp.js +22 -24
  229. package/dist/testing/LambderTestVisitor.d.ts +10 -12
  230. package/dist/testing/LambderTestVisitor.js +15 -15
  231. package/dist/testing.d.ts +3 -0
  232. package/dist/testing.js +2 -0
  233. package/package.json +26 -3
  234. package/dist/api/LambderApiPolicyEngine.d.ts +0 -47
  235. package/dist/api/LambderApiPolicyEngine.js +0 -85
  236. package/dist/shared/util/LambderKeyFields.d.ts +0 -32
  237. 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 own cookie storage.
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 fourth of LambderMockApp's collaborators, and the same argument as the
7
- * other three: it owns state nothing else touches and meets the runtime at two
8
- * calls (a transport's answer coming back, and reset()). It is the piece with
9
- * an outside dependency, `document`, so keeping it here is also what keeps the
10
- * app free of browser conditionals.
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, the
28
- * way an app-supplied session store does: the runtime did not create it and
29
- * does not know what else holds it.
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 exactly as a real browser skips them, the
39
- * jar being the store no script can reach. `Secure` is dropped where the
40
- * page is not a secure context, because the browser would refuse the write
41
- * and development over plain http on a LAN address has to keep working. A
42
- * `__Host-` or `__Secure-` cookie name is then discarded by the browser
43
- * for breaking its own prefix rule, which is correct: such a name cannot
44
- * work on plain http at all, and localhost is a secure context.
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
- * As much a part of a rewind as the session store is: emptying the store
63
- * while a jar still holds the token for one of its sessions leaves the next
64
- * call carrying a session that no longer exists, which reads as signed in
65
- * until the answer says sessionExpired.
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: a compressed
15
- * one is restored before anything reads it, and an endpoint with an input
16
- * schema replaces it with the parsed value. Copied up front, the log kept
17
- * the wire fields, so the compressed calls a developer opens a panel for
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
- * `subscribe` and `calls` stay on the app as one-line delegations, because
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
- * Ending a call is `settle`, one call for the whole of it: classification,
45
- * redaction, the event and the log row. A record literal built at each of the
46
- * three exits instead would drift between them, which is exactly what a log
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 down to the values, so a reader that sorts
50
- * guardsRun or deletes a header is not editing what the next reader sees, and
51
- * the caller is free to edit what it got. The Error is the exception, passed
52
- * by reference: a clone of it would no longer be the class a test asserts on,
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 say different things about the same call.
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 that a session cookie was written, the value goes, because a live
5
- * session token in a panel a developer renders and a test snapshots is no
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. structuredClone is the platform's deep copy and
20
- * everything logged arrived as JSON; a value it refuses (a function on the
21
- * payload of a hand-built request) is handed over as it is rather than
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
- * `subscribe` and `calls` stay on the app as one-line delegations, because
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
- * Ending a call is `settle`, one call for the whole of it: classification,
81
- * redaction, the event and the log row. A record literal built at each of the
82
- * three exits instead would drift between them, which is exactly what a log
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 down to the values, so a reader that sorts
86
- * guardsRun or deletes a header is not editing what the next reader sees, and
87
- * the caller is free to edit what it got. The Error is the exception, passed
88
- * by reference: a clone of it would no longer be the class a test asserts on,
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 once: a listener that throws on
131
- // one event throws on the next, so leaving it in the loop
132
- // costs every remaining call a thrown error and a swallowed
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 say different things about the same call.
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 stored
39
- * answer replays to whoever presents the key, so a mock replayed where the
40
- * server, configured with one, misses.
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 session. The server's option word
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, so offering it would be
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 are held to what the server's own policies were held to when
79
- * it registered the same endpoints, because an entry the mock's copy does not
80
- * fit cannot be registered, and this makes that an error at the option rather
81
- * than a throw when the registry loads. So the map names every policy the
82
- * contract references, a policy a public endpoint names is not keyed per
83
- * session, and a policy whose windows an endpoint overrides keeps a per-API
84
- * budget. Policies the contract does not reference may be added freely.
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) and
87
- * is here for the reason the idempotency option's twin is: with a limiter of
88
- * the app's own that fails, a mock that cannot express it always lets the
89
- * call through, so it answers 200 where a server configured to refuse answers
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, and a call the server answers notAuthorized
121
- * then answers 200 here. Optional, it was the droppable half of exactly the
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
- * "transit.localhost:5173", the session cookie is simply not sent, and
188
- * every session call in a browser served from anything but plain
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; it meets the rest of the runtime at one call, the lookup a request
8
- * makes. The app keeps the whole caller-facing surface (register,
9
- * registerPartial, override, restoreOverrides, registeredNames) as
10
- * delegations, because that surface is contract-typed and the checks that
11
- * make it safe are compile-time; what lives here is the bookkeeping.
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 the entries keep their type through the
14
- * map; the registry itself never reads one.
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. Slices are staged and committed together, so a slice that fails a
36
- * check leaves nothing behind: a caller that catches the error and retries
37
- * sees the problem it is fixing rather than a duplicate-name error from
38
- * its own first attempt. The rest entry is staged with them, for the same
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, which is also why that field
5
- * is named as it is: a slice is keyed by endpoint names holding entries, so
6
- * what tells the two apart has to be a key no endpoint name plausibly is,
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; it meets the rest of the runtime at one call, the lookup a request
16
- * makes. The app keeps the whole caller-facing surface (register,
17
- * registerPartial, override, restoreOverrides, registeredNames) as
18
- * delegations, because that surface is contract-typed and the checks that
19
- * make it safe are compile-time; what lives here is the bookkeeping.
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 the entries keep their type through the
22
- * map; the registry itself never reads one.
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. Slices are staged and committed together, so a slice that fails a
46
- * check leaves nothing behind: a caller that catches the error and retries
47
- * sees the problem it is fixing rather than a duplicate-name error from
48
- * its own first attempt. The rest entry is staged with them, for the same
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 of them answer the same calls with two different
57
- // reasons, and which one a call would get is registration
58
- // order, which is exactly what a duplicate name is refused
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
- // and registration reads entry.name, so the two have to agree
70
- // or the check is checking something else than what runs. A
71
- // hand-written slice is where they part: `{ ...userMocks,
72
- // "user.list": someOtherEntry }` registers the other endpoint
73
- // and leaves "user.list" unanswered, and the first symptom is
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 is still standing), and popping
101
- // would then take down somebody else's override. Restoring twice is a
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
- * One of the four pieces of state LambderMockApp holds that nothing else
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
- * One of the four pieces of state LambderMockApp holds that nothing else
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,