lambder 6.0.2 → 7.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.
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 +21 -19
  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,89 @@
1
+ import { coerceToError } from "../shared/wire/LambderCrashDetail.js";
2
+ import { buildTransportEnvelope, LambderTransportFailure, resolveApiPathTarget } from "../shared/transport/LambderApiTransport.js";
3
+ import { DEFAULT_MAX_RESTORED_PAYLOAD_BYTES } from "../shared/wire/LambderRequestPayload.js";
4
+ import { stopWaitingWhenAborted } from "../shared/util/LambderCallAbort.js";
5
+ import { LOOPBACK_CLIENT_IP } from "../shared/util/LambderClientIp.js";
6
+ import { decodeLambdaHttpResult, localLambdaContext, synthesizeLambdaHttpEvent } from "./LambderLambdaEvent.js";
7
+ /** The rejection an abort produces here: the signal's own reason, which is a DOMException("AbortError") unless the aborting code named another. */
8
+ const isAbortError = (err, signal) => signal !== undefined && signal.aborted && err === signal.reason;
9
+ /**
10
+ * A transport that calls a Lambder handler in this process, the way a
11
+ * browser's request would reach it: a browser-shaped API Gateway event (no
12
+ * invoke marker), the real handler, and its answer decoded back, compressed
13
+ * bodies included. With the memory stores, that is an integration test of a
14
+ * real app through the typed caller with no HTTP and no AWS. Wrap it in
15
+ * lambderCookieJarTransport to hold a session across calls.
16
+ *
17
+ * A handler that throws (which a Lambder app never does on the HTTP path,
18
+ * since render() answers its own last-resort 500) produced no answer at all,
19
+ * so the call fails as `protocol` carrying the handler's own error as its
20
+ * cause. API Gateway would have turned it into a bare 502, and synthesizing
21
+ * one here would throw the error away; keeping it is the point of an
22
+ * in-process transport, and there is nowhere in an HTTP answer to put one
23
+ * except the user-facing `message` field, which is the wrong channel for an
24
+ * internal fault.
25
+ *
26
+ * `request.signal` ends the wait, as the transport contract requires. The
27
+ * handler keeps running to completion either way, because a function call in
28
+ * this process cannot be cancelled: what a timeout buys here is the caller's
29
+ * answer, not the callee's attention.
30
+ */
31
+ export const lambderHandlerTransport = (handler, options = {}) => {
32
+ const clientIp = options.clientIp ?? LOOPBACK_CLIENT_IP;
33
+ const maxResponseBytes = options.maxResponseBytes ?? DEFAULT_MAX_RESTORED_PAYLOAD_BYTES;
34
+ return async (request) => {
35
+ // Before anything is built or called: a call the caller has already
36
+ // given up on should not reach the handler at all, and an abort must
37
+ // travel as the signal's own reason rather than as a transport fault.
38
+ request.signal?.throwIfAborted();
39
+ // An absolute apiPath (what lambderFetchTransport tells a caller
40
+ // outside a browser to configure) is a URL, and a URL as the event's
41
+ // rawPath matches no route: every call would 404 on an app that is
42
+ // wired correctly.
43
+ const target = resolveApiPathTarget(request.apiPath);
44
+ const host = options.host ?? target.host ?? "localhost";
45
+ const event = synthesizeLambdaHttpEvent({
46
+ method: "POST",
47
+ path: target.path,
48
+ host,
49
+ headers: request.headers,
50
+ clientIp: request.clientIp ?? clientIp,
51
+ cookies: request.cookies,
52
+ body: JSON.stringify(buildTransportEnvelope({ ...request, siteHost: request.siteHost || host })),
53
+ }, { invoke: false });
54
+ let result;
55
+ try {
56
+ result = await stopWaitingWhenAborted(handler(event, localLambdaContext("lambder-local", options.context)), request.signal);
57
+ }
58
+ catch (err) {
59
+ if (isAbortError(err, request.signal))
60
+ throw err;
61
+ // The whole point of this transport is in-process integration
62
+ // testing, so the handler's own error is the useful part. A thrown
63
+ // handler answered nothing, and a transport failure is the one
64
+ // channel that carries a cause: swallowing it into a synthetic 502
65
+ // left the caller an outcome.error reading "Request failed: 502"
66
+ // and no way to reach what actually threw.
67
+ throw new LambderTransportFailure("protocol", `the handler threw instead of answering: ${coerceToError(err).message}`, { cause: err });
68
+ }
69
+ // Decoding failures are the callee answering with something that is
70
+ // not an HTTP result, or with more than the ceiling allows. Neither is
71
+ // a network failure, and reporting them as one sends whoever is
72
+ // debugging an integration test looking at their connection.
73
+ let http;
74
+ try {
75
+ http = await decodeLambdaHttpResult(result, maxResponseBytes);
76
+ }
77
+ catch (err) {
78
+ throw new LambderTransportFailure("protocol", coerceToError(err).message, { cause: err });
79
+ }
80
+ return {
81
+ status: http.statusCode,
82
+ statusText: "",
83
+ header: (name) => http.headers[name.toLowerCase()] ?? null,
84
+ json: async () => http.json(),
85
+ text: async () => http.text(),
86
+ setCookies: http.cookies,
87
+ };
88
+ };
89
+ };
@@ -0,0 +1,352 @@
1
+ import type { z } from "zod";
2
+ import type { LambderApiContractShape } from "../shared/wire/LambderApiContract.js";
3
+ import { type LambderApiRequest } from "../api/LambderApiRequest.js";
4
+ import { type LambderApiAnswer } from "../api/LambderApiAnswer.js";
5
+ import { type LambderApiTransport, type LambderApiTransportRequest } from "../shared/transport/LambderApiTransport.js";
6
+ import { LambderCookieJar } from "../shared/transport/LambderCookieJar.js";
7
+ import { type LambderApiGuard, type LambderGuardBuilder } from "../api/LambderApiGuards.js";
8
+ import { LambderMemoryRateLimiter } from "../stores/LambderMemoryRateLimiter.js";
9
+ import { LambderMemoryIdempotencyStore } from "../stores/LambderMemoryIdempotencyStore.js";
10
+ import { LambderMemorySessionStore } from "../stores/LambderMemorySessionStore.js";
11
+ import LambderSessionManager, { type LambderCreatedSession } from "../session/LambderSessionManager.js";
12
+ import type { LambderMockAppOptions, LambderMockIdempotencyOptions, LambderMockTransport, LambderMockTransportOptions } from "./LambderMockCreateOptions.js";
13
+ import type { LambderMockCallContext, LambderMockCallRecord, LambderMockEntry, LambderMockEntryInput, LambderMockFailure, LambderMockFailureReason, LambderMockHandler, LambderMockLatency, LambderMockListener, LambderMockPublicNames, LambderMockRateLimitPolicies, LambderMockRegistryCheck, LambderMockRestEntry, LambderMockSessionCallContext, LambderMockSessionNames, LambderMockSlice, LambderMockOverride } from "./LambderMockTypes.js";
14
+ /**
15
+ * The mock runtime: the API core (LambderApiPipeline, the same class the
16
+ * Lambda server runs) over memory stores, with a registry of typed mock
17
+ * handlers where the server has app handlers, and mock guards where it has
18
+ * app guards. Everything the protocol does (envelope, refusals, sessions
19
+ * and their cookies, guards, rate limits, idempotency, the version gate)
20
+ * happens in the core; this class only resolves a name to an entry, wraps
21
+ * the handler's return into the envelope, and adds what a mock needs on
22
+ * top: failure injection, latency, a subscription, a call log, reset.
23
+ *
24
+ * Create one with initLambderMock<Contract, SessionData>().create(...),
25
+ * which fixes the contract and session types first so everything else is
26
+ * inferred from the options.
27
+ */
28
+ export declare class LambderMockApp<C extends LambderApiContractShape, S = any, G extends Record<string, LambderApiGuard<any, any, any>> = {}> {
29
+ readonly apiVersion: string | null;
30
+ /** The memory stores, for assertions and reset; null for a subsystem that is off or backed by a store of yours. */
31
+ readonly sessionStore: LambderMemorySessionStore<S> | null;
32
+ readonly rateLimiter: LambderMemoryRateLimiter | null;
33
+ readonly idempotencyStore: LambderMemoryIdempotencyStore | null;
34
+ readonly tokenCookieKey: string;
35
+ readonly csrfCookieKey: string;
36
+ /**
37
+ * The client IP a transport request carrying none is read as. Public
38
+ * because an adapter has to read the same default the direct transport
39
+ * uses: the MSW adapter had a hardcoded "127.0.0.1" of its own, so an app
40
+ * that set defaultClientIp saw one address through the transport and
41
+ * another through the service worker, and a per-IP rate limit counted two
42
+ * clients where there was one.
43
+ */
44
+ readonly defaultClientIp: string;
45
+ /** The host this runtime's cookies belong to (see the cookieHost option). */
46
+ readonly cookieHost: string;
47
+ /**
48
+ * The API core, every protocol step of it. Private: the mock's surface is
49
+ * the app, and a consumer reaching past it would be configuring the
50
+ * server's pipeline through a development tool. The three adapters take
51
+ * what they need from the app's own methods (handleRequest,
52
+ * requestFromTransport), which is why none of them names this.
53
+ */
54
+ private readonly pipeline;
55
+ /** The cookie scope signIn plants under, so signOut can name the same one when it clears them. */
56
+ private readonly sessionCookieOptions;
57
+ private readonly sessionTtlSeconds;
58
+ private readonly onReset;
59
+ private readonly revealHandlerErrors;
60
+ /** Injected failures, the offline switch and the configured latency (see LambderMockFailureInjector). */
61
+ private readonly failures;
62
+ /** Subscriptions and the bounded call log (see LambderMockCallRecorder). */
63
+ private readonly recorder;
64
+ /** Registered entries and the overrides over them (see LambderMockEntryRegistry). */
65
+ private readonly registry;
66
+ /** The jars the runtime owns and what it planted in document.cookie (see LambderMockBrowserCookies). */
67
+ private readonly browserCookies;
68
+ constructor(options: LambderMockAppOptions<C, S, G>);
69
+ /**
70
+ * The registration-time checks every entry goes through, mocked or not:
71
+ * the ones the server runs on a definition, and the mock's own "a session
72
+ * endpoint needs the sessions option".
73
+ *
74
+ * One place, so no registration path can skip either check. A session
75
+ * endpoint on a mock without sessions would otherwise register silently
76
+ * and answer the first call with a 500 from inside the pipeline, naming
77
+ * the SERVER's option name, for a mistake whose fix is one option at
78
+ * create().
79
+ */
80
+ private assertEntryRegistration;
81
+ private buildEntry;
82
+ /** A mock for a public endpoint: a handler, or the handler with the endpoint's declarations restated. */
83
+ publicApi<K extends LambderMockPublicNames<C>, TInputSchema extends z.ZodType = z.ZodType>(name: K, entry: LambderMockEntryInput<C, K, S, G, TInputSchema>): LambderMockEntry<C, K>;
84
+ /** A mock for a session endpoint: the pipeline fetches the session before the handler runs, and refuses without one. */
85
+ sessionApi<K extends LambderMockSessionNames<C>, TInputSchema extends z.ZodType = z.ZodType>(name: K, entry: LambderMockEntryInput<C, K, S, G, TInputSchema>): LambderMockEntry<C, K>;
86
+ /**
87
+ * A public endpoint deliberately left without a mock; a call answers the
88
+ * notMocked refusal carrying the reason.
89
+ *
90
+ * Public and session have separate builders for the same reason publicApi
91
+ * and sessionApi do: the refusal runs through the pipeline so that the
92
+ * steps BEFORE dispatch still happen, and the session read is one of them.
93
+ * Declaring every not-mocked endpoint public switched that step off, so a
94
+ * session endpoint with no session answered "not mocked" where the server
95
+ * answers sessionExpired, and the mode on its events and call log was
96
+ * wrong too. The mode cannot be recovered at runtime, because the contract
97
+ * is a type, so the builder is where it has to be said.
98
+ */
99
+ notMocked<K extends LambderMockPublicNames<C>>(name: K, reason: string): LambderMockEntry<C, K>;
100
+ /** A session endpoint deliberately left without a mock: the session is still read, and refused before the notMocked refusal. */
101
+ sessionNotMocked<K extends LambderMockSessionNames<C>>(name: K, reason: string): LambderMockEntry<C, K>;
102
+ /**
103
+ * "Everything I did not register is not mocked, for this reason", as an
104
+ * argument to the same register() call:
105
+ *
106
+ * ```ts
107
+ * mockApp.register(userMocks, billingMocks, mockApp.restNotMocked("not mocked yet"));
108
+ * ```
109
+ *
110
+ * What it buys is adoption over a contract the mocks do not cover yet:
111
+ * register() stays exhaustive by construction, and the endpoints nothing
112
+ * claims answer the notMocked refusal carrying this reason instead of
113
+ * apiNotFound, so a screen that reaches one says "not mocked yet" rather
114
+ * than "unknown error". Strays and duplicates in the explicit slices are
115
+ * refused exactly as they are without it, and an entry registered later
116
+ * (registerPartial, or a second register) takes the endpoint back from the
117
+ * rest.
118
+ *
119
+ * The one thing it cannot do is the session read. A call it answers is
120
+ * processed as a public endpoint: the protocol's pre-pass still runs, so a
121
+ * stale client still hears versionExpired, but the mode of a name nothing
122
+ * registered is not knowable at runtime, the contract being a type. So a
123
+ * signed-out call to an unmocked session endpoint is answered "not mocked"
124
+ * where the server answers sessionExpired, and the endpoint whose
125
+ * signed-out path a test cares about is the one to declare with
126
+ * sessionNotMocked instead.
127
+ */
128
+ restNotMocked(reason: string): LambderMockRestEntry;
129
+ private buildNotMockedEntry;
130
+ /** The entries of one module as a slice, keyed by name. Two entries for one endpoint is an error here. */
131
+ apiSlice<const E extends readonly LambderMockEntry<C, keyof C & string>[]>(...entries: E): LambderMockSlice<C, E[number]["name"]>;
132
+ private addSlices;
133
+ /**
134
+ * Registers the whole contract: every endpoint in exactly one slice, or
135
+ * in the reach of a restNotMocked entry passed beside them.
136
+ * Completeness, strays and overlap are checked by the compiler against
137
+ * the contract type; overlap and key-to-name agreement are checked again
138
+ * at runtime for slices built dynamically, and a second rest entry is
139
+ * refused there the way a duplicate name is.
140
+ */
141
+ register<const Slices extends readonly (Record<string, LambderMockEntry<C, any>> | LambderMockRestEntry)[]>(...slices: Slices & LambderMockRegistryCheck<C, Slices>): this;
142
+ /** Registers some endpoints, for a test that wants three and not three hundred. Overlap is still an error. */
143
+ registerPartial(...slices: readonly Record<string, LambderMockEntry<C, any>>[]): this;
144
+ /**
145
+ * Replaces one endpoint's handler until restored: returns its own undo,
146
+ * which a test scopes with try/finally. The entry's declarations (mode,
147
+ * guards, rate limit, idempotency) stay as registered; only the handler
148
+ * changes.
149
+ *
150
+ * Overrides nest. A second override over the same endpoint stands on the
151
+ * first, and restoring it uncovers the first rather than the registry, so
152
+ * an override one `it` scoped cannot drop the one a describe put in place
153
+ * around it.
154
+ */
155
+ override<K extends keyof C & string>(name: K, handler: LambderMockHandler<C, K, S, G>): LambderMockOverride;
156
+ /** Puts every overridden handler back, however deeply they were stacked. */
157
+ restoreOverrides(): void;
158
+ /** The registered endpoint names. */
159
+ get registeredNames(): string[];
160
+ /**
161
+ * Whether a call to this name would be answered from the registry, which
162
+ * is what an adapter asks before passing one on.
163
+ *
164
+ * True for every name once a rest entry is registered, because the rest
165
+ * entry is what answers the names nothing else claimed. That is what makes
166
+ * a rest entry and the MSW adapter's `onUnmocked: "passthrough"`
167
+ * alternatives rather than layers: with one registered, the runtime
168
+ * answers everything itself and nothing is handed on to the network.
169
+ */
170
+ hasRegisteredEntry(apiName: string): boolean;
171
+ private entryFor;
172
+ /**
173
+ * The entry that answers a name nothing registered, when register() was
174
+ * given a rest entry: the notMocked refusal carrying its reason, run
175
+ * through the pipeline as a public endpoint.
176
+ *
177
+ * Public because the mode of an unregistered name cannot be recovered at
178
+ * runtime, the contract being a type. Everything that precedes dispatch
179
+ * still runs (the version gate, the payload restore); the session read is
180
+ * the one step this answer cannot have, which is the fidelity limit
181
+ * restNotMocked documents.
182
+ */
183
+ private restNotMockedEntry;
184
+ /** The next call to the endpoint fails this way; several calls queue in order. */
185
+ failNext(apiName: keyof C & string, failure: LambderMockFailure | LambderMockFailureReason): void;
186
+ /** Every call to the endpoint fails this way until cleared with null. */
187
+ setFailure(apiName: keyof C & string, failure: LambderMockFailure | LambderMockFailureReason | null): void;
188
+ /** Every call rejects at the transport, as with no network at all. */
189
+ setOffline(offline: boolean): void;
190
+ setLatency(latency: LambderMockLatency): void;
191
+ /**
192
+ * Rewinds the runtime: sessions, rate-limit counters, replay records,
193
+ * overrides, injected failures, the offline switch, the configured
194
+ * latency, the call log and its numbering, the cookies its own transports
195
+ * hold, then onReset, so the app rewinds its own data too.
196
+ *
197
+ * The cookies matter as much as the sessions do: emptying the session
198
+ * store while a jar still holds the token for one of them leaves the next
199
+ * call carrying a session that no longer exists, which reads as signed in
200
+ * until the answer says sessionExpired. So every jar transport() built
201
+ * for itself is emptied, and the cookies a "document" transport mirrored
202
+ * are expired again.
203
+ *
204
+ * The registry survives, being what the runtime was configured with
205
+ * rather than what it accumulated. Subscriptions survive too, because
206
+ * they are how a test watches the runtime rather than state it is
207
+ * testing; a listener muted for throwing is unmuted, so one bad call does
208
+ * not silence it for the rest of the run. A session store or a cookie jar
209
+ * the app supplied itself survives: the runtime did not create it and
210
+ * does not know what else holds it.
211
+ */
212
+ reset(): void;
213
+ /**
214
+ * The four session members refuse in the mock's own words, naming the
215
+ * option a mock is created with.
216
+ *
217
+ * The pipeline's guard says "Configure the session option at creation",
218
+ * which is the SERVER's option name: the mock's is `sessions`, and a
219
+ * reader who goes looking for `session` on create() does not find it.
220
+ * The registration path was fixed for exactly this one method over.
221
+ */
222
+ private assertSessionsConfigured;
223
+ /** The session manager, for tests that inspect or manipulate sessions directly. Throws when sessions are off. */
224
+ get sessionManager(): LambderSessionManager<S>;
225
+ /**
226
+ * Starts a session without a login endpoint: creates it through the
227
+ * session controller, the way a login handler does, plants its cookies
228
+ * into the jar when one is given, so the jar's transport is signed in
229
+ * from its next call, and mirrors the readable ones into document.cookie
230
+ * the way an answer's cookies are. Returns the raw tokens too.
231
+ */
232
+ signIn(sessionKey: string, data: S, options?: {
233
+ jar?: LambderCookieJar;
234
+ ttlSeconds?: number;
235
+ host?: string;
236
+ }): Promise<LambderCreatedSession<S>>;
237
+ /**
238
+ * Ends every session of the subject ("log this subject out everywhere")
239
+ * and clears what signIn planted: the cookies in the jar given, and the
240
+ * copies in document.cookie.
241
+ *
242
+ * Symmetric on purpose, the way reset() is. The records alone leave the
243
+ * jar and the page carrying a token for a session that no longer exists,
244
+ * which reads as signed in until an answer says otherwise.
245
+ */
246
+ signOut(sessionKey: string, options?: {
247
+ jar?: LambderCookieJar;
248
+ host?: string;
249
+ }): Promise<void>;
250
+ /** Marks the subject's session data stale, so the next read renews it through dataRefresh. */
251
+ expireSessionData(sessionKey: string): Promise<void>;
252
+ /**
253
+ * Listens to every call, both phases. Keyed, so a hot-reloaded module
254
+ * replaces its own listener instead of stacking a duplicate. Returns the
255
+ * unsubscribe.
256
+ */
257
+ subscribe(key: string, listener: LambderMockListener): () => void;
258
+ /** The completed calls, oldest first, bounded by callLogSize. */
259
+ get calls(): readonly LambderMockCallRecord[];
260
+ private emit;
261
+ /** The context one call runs on: the core's call context plus what mock guards and handlers see. */
262
+ private createContext;
263
+ /** What a call looks like on the way in, for the runtime's own calls and for one an adapter passes on. */
264
+ private requestEvent;
265
+ /**
266
+ * Records a call an adapter handed on instead of answering: the MSW
267
+ * adapter's passthrough. Without it a name the registry does not know
268
+ * leaves no trace at all, and a mistyped endpoint reaches the real
269
+ * network with nothing in the call log or on the subscription to say so,
270
+ * which is the one failure the log exists to make visible.
271
+ */
272
+ notePassthrough(request: LambderApiRequest): void;
273
+ /** What every record of one call repeats (see LambderMockCallFacts). */
274
+ private callFacts;
275
+ /** One call from a parsed request to its answer, events included. The entry point every transport and adapter shares. */
276
+ handleRequest(request: LambderApiRequest): Promise<LambderApiAnswer>;
277
+ /**
278
+ * A transport request as the core's request: the envelope read the way the
279
+ * server reads it. Public because the adapters call it, which is what
280
+ * keeps them from each reading a transport request their own way.
281
+ */
282
+ requestFromTransport(transportRequest: LambderApiTransportRequest): LambderApiRequest;
283
+ /** One call from a transport request to its answer: what the mock transport and the adapters call. */
284
+ handle(transportRequest: LambderApiTransportRequest): Promise<LambderApiAnswer>;
285
+ /**
286
+ * The direct transport: a caller's request into handle(), the answer
287
+ * back in the form the caller reads, cookies carried by a jar the way
288
+ * a browser carries them. Each transport gets its own jar unless one is
289
+ * given, so two transports hold two sessions; the jar is on the returned
290
+ * transport as `cookieJar`, so a test can read or clear the one it did
291
+ * not create itself, and reset() empties it.
292
+ */
293
+ transport(options?: LambderMockTransportOptions): LambderMockTransport;
294
+ /**
295
+ * Mirrors an answer's non-HttpOnly cookies into document.cookie and
296
+ * remembers them, so reset() expires them again. The direct transport's
297
+ * "document" mode and the MSW adapter both come through here: one
298
+ * implementation of the mirror, one record of what was planted.
299
+ */
300
+ mirrorCookiesIntoDocument(setCookies: readonly string[]): void;
301
+ /**
302
+ * Takes a jar an adapter built for itself as the runtime's own, so reset()
303
+ * empties it with the rest. The MSW adapter's jar holds the session
304
+ * cookies of calls that never touch transport(), and a reset that leaves
305
+ * it full is the same stale-session bug: the store is empty and the next
306
+ * request still carries a token for one of its sessions.
307
+ */
308
+ adoptCookieJar(jar: LambderCookieJar): void;
309
+ /** caller.setTransport(mockApp.transport(options)); returns the transport, its jar on it. */
310
+ attach(caller: {
311
+ setTransport(transport: LambderApiTransport): unknown;
312
+ }, options?: LambderMockTransportOptions): LambderMockTransport;
313
+ }
314
+ /**
315
+ * Fixes the contract and session data types, then hands out the guard
316
+ * builder bound to the mock's contexts and the create() that infers
317
+ * everything else (the guard map, the rate-limit policies) from its options.
318
+ * Curried for the same reason initLambder is: TypeScript type arguments are
319
+ * all-or-nothing per call.
320
+ *
321
+ * ```ts
322
+ * const mock = initLambderMock<ApiContractType, SessionData>();
323
+ * const mockApp = mock.create({
324
+ * apiVersion: "1.4.0",
325
+ * sessions: true,
326
+ * guards: { tenant: mock.guard({ guardInput: z.object({ tenantId: z.uuid() }), session: true, handler: ... }) },
327
+ * });
328
+ * ```
329
+ */
330
+ export declare const initLambderMock: <C extends LambderApiContractShape, S = any>() => {
331
+ /** Builds a mock guard: the server guard's shape, the handler seeing the mock's contexts. */
332
+ guard: LambderGuardBuilder<LambderMockCallContext<S>, LambderMockSessionCallContext<S>>;
333
+ /**
334
+ * Builds a mock rate-limit key, the counterpart of `guard`. Bound to the
335
+ * mock's own call context, because the server's lambderRateLimitKey() is
336
+ * bound to the render context and a handler written with it compiles here
337
+ * while reading fields the mock context does not have.
338
+ */
339
+ rateLimitKey: import("../api/LambderApiRateLimits.js").LambderRateLimitKeyBuilder<LambderMockCallContext<S>>;
340
+ /**
341
+ * The mock app, with the guard map and the rate-limit policies inferred
342
+ * from the options.
343
+ *
344
+ * `const` on each of them is what pins a restatement to the contract, and
345
+ * it has one cost: inferring a generic from an object literal switches
346
+ * excess-property checking off for the whole literal, nested objects
347
+ * included, so a typo inside `rateLimits.policies` or `idempotency`
348
+ * compiled and was dropped in silence. `I` exists for the same reason `P`
349
+ * does, and LambderMockSurplusKeys puts the error back on the key.
350
+ */
351
+ create<const G extends Record<string, LambderApiGuard<any, any, any, LambderMockCallContext<S>, LambderMockSessionCallContext<S>>> = {}, const P extends LambderMockRateLimitPolicies<S> = {}, I extends boolean | LambderMockIdempotencyOptions<S> = boolean | LambderMockIdempotencyOptions<S>>(options: LambderMockAppOptions<C, S, G, P, I>): LambderMockApp<C, S, G>;
352
+ };