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,126 @@
1
+ /**
2
+ * Which of register()'s arguments is the rest entry rather than a slice.
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.
8
+ */
9
+ const isRestNotMockedEntry = (slice) => typeof slice.restNotMockedReason === "string";
10
+ /**
11
+ * Which mock answers an endpoint: the registered entries, and the overrides
12
+ * standing over them.
13
+ *
14
+ * 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.
20
+ *
21
+ * Generic over the contract only so the entries keep their type through the
22
+ * map; the registry itself never reads one.
23
+ */
24
+ export class LambderMockEntryRegistry {
25
+ entries = new Map();
26
+ /**
27
+ * The overrides standing over one endpoint, innermost last. A stack
28
+ * rather than one slot because overrides nest: an override a describe
29
+ * scopes and one an it scopes are both live, and restoring the inner one
30
+ * has to uncover the outer rather than the registry.
31
+ */
32
+ overrideStacks = new Map();
33
+ /** The reason register() was given a rest entry with; null while it was given none. */
34
+ restReason = null;
35
+ /**
36
+ * What a call to an endpoint no slice registered is refused with, or null
37
+ * when no rest entry was registered. A registration like any other, so
38
+ * reset() keeps it.
39
+ */
40
+ get restNotMockedReason() {
41
+ return this.restReason;
42
+ }
43
+ /**
44
+ * 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.
50
+ */
51
+ addSlices(slices) {
52
+ const staged = new Map();
53
+ let stagedRestReason = null;
54
+ for (const slice of slices) {
55
+ 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.
60
+ const registered = this.restReason ?? stagedRestReason;
61
+ if (registered !== null) {
62
+ 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.`);
63
+ }
64
+ stagedRestReason = slice.restNotMockedReason;
65
+ continue;
66
+ }
67
+ for (const [key, entry] of Object.entries(slice)) {
68
+ // 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.
75
+ if (key !== entry.name) {
76
+ 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
+ }
78
+ if (this.entries.has(entry.name) || staged.has(entry.name)) {
79
+ throw new Error(`LambderMockApp: endpoint "${entry.name}" is mocked in more than one slice.`);
80
+ }
81
+ staged.set(entry.name, entry);
82
+ }
83
+ }
84
+ for (const [name, entry] of staged)
85
+ this.entries.set(name, entry);
86
+ if (stagedRestReason !== null)
87
+ this.restReason = stagedRestReason;
88
+ }
89
+ /** The registered entry for a name, before any override; null when there is none. */
90
+ registered(apiName) {
91
+ return this.entries.get(apiName) ?? null;
92
+ }
93
+ /** Stacks an override over a registered entry and hands back its removal. */
94
+ pushOverride(name, entry) {
95
+ const stack = this.overrideStacks.get(name) ?? [];
96
+ stack.push(entry);
97
+ this.overrideStacks.set(name, stack);
98
+ // Removes this override wherever it sits rather than popping the top:
99
+ // 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.
103
+ const restore = () => {
104
+ const current = this.overrideStacks.get(name);
105
+ const at = current?.lastIndexOf(entry) ?? -1;
106
+ if (!current || at === -1)
107
+ return;
108
+ current.splice(at, 1);
109
+ if (!current.length)
110
+ this.overrideStacks.delete(name);
111
+ };
112
+ return { restore };
113
+ }
114
+ /** Puts every overridden handler back, however deeply they were stacked. */
115
+ restoreOverrides() {
116
+ this.overrideStacks.clear();
117
+ }
118
+ /** The registered endpoint names. */
119
+ get names() {
120
+ return [...this.entries.keys()];
121
+ }
122
+ /** What answers this call: the innermost override, else the registered entry. */
123
+ entryFor(apiName) {
124
+ return this.overrideStacks.get(apiName)?.at(-1) ?? this.entries.get(apiName) ?? null;
125
+ }
126
+ }
@@ -0,0 +1,60 @@
1
+ import type { LambderApiAnswer } from "../api/LambderApiAnswer.js";
2
+ import type { LambderApiRequest } from "../api/LambderApiRequest.js";
3
+ import type { LambderMockFailure, LambderMockFailureReason, LambderMockLatency } from "./LambderMockTypes.js";
4
+ /**
5
+ * A failure the transport reports rather than an answer the caller reads:
6
+ * an injected network failure or timeout. The caller maps a rejected
7
+ * transport to `network`, or `timeout` when its own abort fired.
8
+ */
9
+ export declare class LambderMockTransportError extends Error {
10
+ readonly reason: "network" | "timeout" | "offline";
11
+ constructor(reason: "network" | "timeout" | "offline");
12
+ }
13
+ /**
14
+ * What a call fails with and how long it takes to do it: the queued and
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.
22
+ *
23
+ * Injected refusals are rendered through the real envelope helpers, never
24
+ * hand-written: an injected 429 and an earned one have to be the same bytes,
25
+ * or a test written against the injected shape passes while the real one
26
+ * would fail.
27
+ */
28
+ export declare class LambderMockFailureInjector {
29
+ private readonly failuresNext;
30
+ private readonly failuresSet;
31
+ private offlineFlag;
32
+ private latency;
33
+ /** What latency was configured at creation, so reset() can put it back. */
34
+ private readonly initialLatency;
35
+ private readonly apiVersion;
36
+ constructor(options: {
37
+ apiVersion: string | null;
38
+ latency: LambderMockLatency;
39
+ });
40
+ /** True while every call should reject at the transport, as with no network at all. */
41
+ get offline(): boolean;
42
+ /** The next call to the endpoint fails this way; several calls queue in order. */
43
+ failNext(apiName: string, failure: LambderMockFailure | LambderMockFailureReason): void;
44
+ /** Every call to the endpoint fails this way until cleared with null. */
45
+ setFailure(apiName: string, failure: LambderMockFailure | LambderMockFailureReason | null): void;
46
+ setOffline(offline: boolean): void;
47
+ setLatency(latency: LambderMockLatency): void;
48
+ /** The failure this call should take, queued one first; null when the call should run. */
49
+ take(apiName: string): LambderMockFailure | null;
50
+ /** Milliseconds this call should take before it is answered. */
51
+ latencyFor(apiName: string): number;
52
+ /** Waits the configured latency, cancelled by the caller's abort. */
53
+ wait(ms: number, signal: AbortSignal | undefined): Promise<void>;
54
+ /** Waits for the caller's own abort, which is what a timeout is; a call with no signal waits for ever. */
55
+ waitForAbort(signal: AbortSignal | undefined): Promise<never>;
56
+ /** The answer an injected failure produces, or a throw for the ones that never reach the caller as answers. */
57
+ answerFor(failure: LambderMockFailure, request: LambderApiRequest): Promise<LambderApiAnswer>;
58
+ /** Clears every injected failure and puts the configured latency back. */
59
+ reset(): void;
60
+ }
@@ -0,0 +1,138 @@
1
+ import { LambderApiRefusal } from "../shared/wire/LambderApiRefusal.js";
2
+ import { crashAnswer, refusalAnswer, sessionExpiredAnswer, versionExpiredAnswer } from "../api/LambderApiEnvelope.js";
3
+ import { rateLimitRefusal } from "../api/LambderApiRateLimits.js";
4
+ /** An injected refusal's message, from a string, a full message, or the reason's own wording. */
5
+ const toRefusalMessage = (message, fallback) => typeof message === "string" ? { type: "warning", content: message }
6
+ : message ?? { type: "warning", content: fallback };
7
+ /**
8
+ * A failure the transport reports rather than an answer the caller reads:
9
+ * an injected network failure or timeout. The caller maps a rejected
10
+ * transport to `network`, or `timeout` when its own abort fired.
11
+ */
12
+ export class LambderMockTransportError extends Error {
13
+ reason;
14
+ constructor(reason) {
15
+ super(`LambderMockApp: ${reason === "offline" ? "the mock is offline" : `injected ${reason} failure`}`);
16
+ this.name = "LambderMockTransportError";
17
+ this.reason = reason;
18
+ }
19
+ }
20
+ /**
21
+ * What a call fails with and how long it takes to do it: the queued and
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.
29
+ *
30
+ * Injected refusals are rendered through the real envelope helpers, never
31
+ * hand-written: an injected 429 and an earned one have to be the same bytes,
32
+ * or a test written against the injected shape passes while the real one
33
+ * would fail.
34
+ */
35
+ export class LambderMockFailureInjector {
36
+ failuresNext = new Map();
37
+ failuresSet = new Map();
38
+ offlineFlag = false;
39
+ latency;
40
+ /** What latency was configured at creation, so reset() can put it back. */
41
+ initialLatency;
42
+ apiVersion;
43
+ constructor(options) {
44
+ this.apiVersion = options.apiVersion;
45
+ this.latency = options.latency;
46
+ this.initialLatency = options.latency;
47
+ }
48
+ /** True while every call should reject at the transport, as with no network at all. */
49
+ get offline() { return this.offlineFlag; }
50
+ /** The next call to the endpoint fails this way; several calls queue in order. */
51
+ failNext(apiName, failure) {
52
+ const queue = this.failuresNext.get(apiName) ?? [];
53
+ queue.push(typeof failure === "string" ? { reason: failure } : failure);
54
+ this.failuresNext.set(apiName, queue);
55
+ }
56
+ /** Every call to the endpoint fails this way until cleared with null. */
57
+ setFailure(apiName, failure) {
58
+ if (failure === null)
59
+ this.failuresSet.delete(apiName);
60
+ else
61
+ this.failuresSet.set(apiName, typeof failure === "string" ? { reason: failure } : failure);
62
+ }
63
+ setOffline(offline) { this.offlineFlag = offline; }
64
+ setLatency(latency) { this.latency = latency; }
65
+ /** The failure this call should take, queued one first; null when the call should run. */
66
+ take(apiName) {
67
+ const queue = this.failuresNext.get(apiName);
68
+ if (queue?.length) {
69
+ const next = queue.shift();
70
+ if (!queue.length)
71
+ this.failuresNext.delete(apiName);
72
+ return next;
73
+ }
74
+ return this.failuresSet.get(apiName) ?? null;
75
+ }
76
+ /** Milliseconds this call should take before it is answered. */
77
+ latencyFor(apiName) {
78
+ const latency = this.latency;
79
+ if (typeof latency === "number")
80
+ return latency;
81
+ if (typeof latency === "function")
82
+ return latency(apiName);
83
+ return latency.min + Math.random() * Math.max(0, latency.max - latency.min);
84
+ }
85
+ /** Waits the configured latency, cancelled by the caller's abort. */
86
+ wait(ms, signal) {
87
+ if (ms <= 0)
88
+ return Promise.resolve();
89
+ return new Promise((resolve, reject) => {
90
+ const timer = setTimeout(() => { signal?.removeEventListener("abort", onAbort); resolve(); }, ms);
91
+ const onAbort = () => { clearTimeout(timer); reject(new LambderMockTransportError("network")); };
92
+ if (signal?.aborted) {
93
+ clearTimeout(timer);
94
+ reject(new LambderMockTransportError("network"));
95
+ return;
96
+ }
97
+ signal?.addEventListener("abort", onAbort, { once: true });
98
+ });
99
+ }
100
+ /** Waits for the caller's own abort, which is what a timeout is; a call with no signal waits for ever. */
101
+ waitForAbort(signal) {
102
+ return new Promise((_resolve, reject) => {
103
+ if (!signal)
104
+ return;
105
+ if (signal.aborted) {
106
+ reject(new LambderMockTransportError("timeout"));
107
+ return;
108
+ }
109
+ signal.addEventListener("abort", () => reject(new LambderMockTransportError("timeout")), { once: true });
110
+ });
111
+ }
112
+ /** The answer an injected failure produces, or a throw for the ones that never reach the caller as answers. */
113
+ async answerFor(failure, request) {
114
+ switch (failure.reason) {
115
+ case "network": throw new LambderMockTransportError("network");
116
+ case "timeout": return await this.waitForAbort(request.signal);
117
+ case "server": return crashAnswer(this.apiVersion);
118
+ case "refusal": {
119
+ const message = toRefusalMessage(failure.message, "Injected refusal.");
120
+ return refusalAnswer(new LambderApiRefusal(message.content, { errorMessage: message, statusCode: failure.statusCode }), this.apiVersion);
121
+ }
122
+ case "notAuthorized": {
123
+ const message = toRefusalMessage(failure.message, "Injected authorization refusal.");
124
+ return refusalAnswer(new LambderApiRefusal(message.content, { errorMessage: message, notAuthorized: true }), this.apiVersion);
125
+ }
126
+ case "sessionExpired": return sessionExpiredAnswer(this.apiVersion);
127
+ case "versionExpired": return versionExpiredAnswer(this.apiVersion);
128
+ case "rateLimited": return refusalAnswer(rateLimitRefusal("Injected rate limit.", failure.retryAfterSeconds ?? 30, failure.message), this.apiVersion);
129
+ }
130
+ }
131
+ /** Clears every injected failure and puts the configured latency back. */
132
+ reset() {
133
+ this.failuresNext.clear();
134
+ this.failuresSet.clear();
135
+ this.offlineFlag = false;
136
+ this.latency = this.initialLatency;
137
+ }
138
+ }