lambder 7.2.5 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +1021 -3
  2. package/README.md +43 -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 +74 -61
  13. package/dist/api/LambderApiIdempotency.js +226 -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 +77 -39
  17. package/dist/api/LambderApiPipeline.js +135 -62
  18. package/dist/api/LambderApiRateLimits.d.ts +208 -54
  19. package/dist/api/LambderApiRateLimits.js +197 -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/freshProcessVerifier.d.ts +13 -0
  27. package/dist/build/freshProcessVerifier.js +19 -0
  28. package/dist/build/writeApiSignatures.d.ts +109 -0
  29. package/dist/build/writeApiSignatures.js +222 -0
  30. package/dist/build.d.ts +9 -0
  31. package/dist/build.js +8 -0
  32. package/dist/client/LambderCaller.d.ts +13 -44
  33. package/dist/client/LambderCaller.js +77 -84
  34. package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
  35. package/dist/client/LambderReloadLoopBreaker.js +90 -46
  36. package/dist/client/lambderFetchTransport.d.ts +4 -1
  37. package/dist/client/lambderFetchTransport.js +52 -28
  38. package/dist/client.d.ts +5 -3
  39. package/dist/client.js +2 -1
  40. package/dist/core/Lambder.d.ts +161 -69
  41. package/dist/core/Lambder.js +370 -226
  42. package/dist/core/LambderContext.d.ts +82 -15
  43. package/dist/core/LambderContext.js +107 -20
  44. package/dist/core/LambderCors.d.ts +21 -3
  45. package/dist/core/LambderCors.js +35 -16
  46. package/dist/core/LambderCrashHandling.d.ts +40 -0
  47. package/dist/core/LambderCrashHandling.js +97 -0
  48. package/dist/core/LambderCreateOptions.d.ts +151 -75
  49. package/dist/core/LambderCreateOptions.js +16 -23
  50. package/dist/core/LambderFiles.d.ts +28 -7
  51. package/dist/core/LambderFiles.js +73 -33
  52. package/dist/core/LambderIndexHtml.js +12 -11
  53. package/dist/core/LambderPolicyBuilders.d.ts +17 -5
  54. package/dist/core/LambderPolicyBuilders.js +17 -5
  55. package/dist/core/LambderPublicFiles.d.ts +11 -5
  56. package/dist/core/LambderPublicFiles.js +32 -4
  57. package/dist/core/LambderRequestPath.d.ts +43 -0
  58. package/dist/core/LambderRequestPath.js +63 -0
  59. package/dist/core/LambderResponse.d.ts +26 -5
  60. package/dist/core/LambderResponse.js +157 -70
  61. package/dist/core/LambderResponseBuilder.d.ts +49 -4
  62. package/dist/core/LambderResponseBuilder.js +64 -3
  63. package/dist/core/LambderRouting.d.ts +2 -3
  64. package/dist/core/LambderRouting.js +22 -7
  65. package/dist/core/LambderTemplatingEngine.js +211 -32
  66. package/dist/index.d.ts +15 -8
  67. package/dist/index.js +5 -4
  68. package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
  69. package/dist/invoke/LambderInvokeCaller.js +76 -66
  70. package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
  71. package/dist/invoke/LambderInvokeOutcome.js +9 -22
  72. package/dist/invoke/LambderLambdaEvent.d.ts +44 -10
  73. package/dist/invoke/LambderLambdaEvent.js +80 -37
  74. package/dist/invoke/lambderHandlerTransport.d.ts +12 -10
  75. package/dist/invoke/lambderHandlerTransport.js +16 -19
  76. package/dist/mock/LambderMockApp.d.ts +67 -83
  77. package/dist/mock/LambderMockApp.js +167 -153
  78. package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
  79. package/dist/mock/LambderMockBrowserCookies.js +24 -28
  80. package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
  81. package/dist/mock/LambderMockCallRecorder.js +19 -28
  82. package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
  83. package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
  84. package/dist/mock/LambderMockEntryRegistry.js +24 -29
  85. package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
  86. package/dist/mock/LambderMockFailureInjector.js +3 -6
  87. package/dist/mock/LambderMockTypes.d.ts +78 -108
  88. package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
  89. package/dist/mock/lambderMockInvokeTransport.js +11 -10
  90. package/dist/mock/lambderMockMswHandler.d.ts +33 -29
  91. package/dist/mock/lambderMockMswHandler.js +50 -39
  92. package/dist/mock.d.ts +3 -1
  93. package/dist/mock.js +5 -3
  94. package/dist/session/LambderSessionController.d.ts +108 -89
  95. package/dist/session/LambderSessionController.js +187 -168
  96. package/dist/session/LambderSessionCrypto.d.ts +16 -7
  97. package/dist/session/LambderSessionCrypto.js +26 -12
  98. package/dist/session/LambderSessionManager.d.ts +136 -47
  99. package/dist/session/LambderSessionManager.js +280 -139
  100. package/dist/shared/LambderHtml.d.ts +42 -3
  101. package/dist/shared/LambderHtml.js +127 -7
  102. package/dist/shared/LambderHtmlPositions.d.ts +173 -0
  103. package/dist/shared/LambderHtmlPositions.js +652 -0
  104. package/dist/shared/LambderI18n.d.ts +10 -11
  105. package/dist/shared/LambderI18n.js +33 -21
  106. package/dist/shared/contracts/LambderCache.d.ts +66 -0
  107. package/dist/shared/contracts/LambderCache.js +11 -0
  108. package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
  109. package/dist/shared/contracts/LambderFileSource.js +5 -8
  110. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
  111. package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
  112. package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
  113. package/dist/shared/contracts/LambderRateLimiter.js +4 -5
  114. package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
  115. package/dist/shared/contracts/LambderSessionStore.js +5 -6
  116. package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
  117. package/dist/shared/transport/LambderApiTransport.js +7 -7
  118. package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
  119. package/dist/shared/transport/LambderCookieJar.js +54 -66
  120. package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
  121. package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
  122. package/dist/shared/util/LambderCallAbort.d.ts +5 -5
  123. package/dist/shared/util/LambderCallAbort.js +5 -5
  124. package/dist/shared/util/LambderClientIp.d.ts +27 -11
  125. package/dist/shared/util/LambderClientIp.js +96 -13
  126. package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
  127. package/dist/shared/util/LambderExpiringMap.js +41 -57
  128. package/dist/shared/util/LambderNodeModules.js +6 -7
  129. package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
  130. package/dist/shared/util/LambderOptionChecks.js +4 -4
  131. package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
  132. package/dist/shared/util/LambderResponseBrand.js +5 -5
  133. package/dist/shared/util/LambderTestingDoors.d.ts +29 -0
  134. package/dist/shared/util/LambderTestingDoors.js +29 -0
  135. package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
  136. package/dist/shared/util/LambderTypeUtilities.js +3 -3
  137. package/dist/shared/util/boundKeyField.d.ts +20 -0
  138. package/dist/shared/util/boundKeyField.js +34 -0
  139. package/dist/shared/util/canonicalJson.d.ts +11 -0
  140. package/dist/shared/util/canonicalJson.js +28 -0
  141. package/dist/shared/util/joinKeyFields.d.ts +20 -0
  142. package/dist/shared/util/joinKeyFields.js +22 -0
  143. package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
  144. package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
  145. package/dist/shared/wire/LambderApiContract.d.ts +107 -32
  146. package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
  147. package/dist/shared/wire/LambderApiOutcome.js +48 -23
  148. package/dist/shared/wire/LambderApiRefusal.d.ts +39 -27
  149. package/dist/shared/wire/LambderApiRefusal.js +36 -7
  150. package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
  151. package/dist/shared/wire/LambderApiSignature.js +16 -19
  152. package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
  153. package/dist/shared/wire/LambderCallOptions.js +9 -11
  154. package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
  155. package/dist/shared/wire/LambderCompressionCodec.js +31 -36
  156. package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
  157. package/dist/shared/wire/LambderCompressionOption.js +9 -9
  158. package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
  159. package/dist/shared/wire/LambderCrashDetail.js +12 -15
  160. package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
  161. package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
  162. package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
  163. package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
  164. package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
  165. package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
  166. package/dist/shared/wire/LambderInvokeApiId.js +27 -0
  167. package/dist/shared/wire/LambderOutcomeAssertions.d.ts +79 -0
  168. package/dist/shared/wire/LambderOutcomeAssertions.js +112 -0
  169. package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
  170. package/dist/shared/wire/LambderRequestPayload.js +4 -6
  171. package/dist/stores/LambderCacheFiller.d.ts +48 -0
  172. package/dist/stores/LambderCacheFiller.js +119 -0
  173. package/dist/stores/LambderCacheKeys.d.ts +26 -0
  174. package/dist/stores/LambderCacheKeys.js +54 -0
  175. package/dist/stores/LambderCacheValues.d.ts +45 -0
  176. package/dist/stores/LambderCacheValues.js +74 -0
  177. package/dist/stores/LambderDdbCache.d.ts +121 -56
  178. package/dist/stores/LambderDdbCache.js +528 -225
  179. package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
  180. package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
  181. package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
  182. package/dist/stores/LambderDdbRateLimiter.js +151 -39
  183. package/dist/stores/LambderDdbSdk.d.ts +43 -31
  184. package/dist/stores/LambderDdbSdk.js +79 -33
  185. package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
  186. package/dist/stores/LambderDdbSessionStore.js +119 -47
  187. package/dist/stores/LambderHttpFileSource.d.ts +15 -6
  188. package/dist/stores/LambderHttpFileSource.js +15 -13
  189. package/dist/stores/LambderMemoryCache.d.ts +49 -0
  190. package/dist/stores/LambderMemoryCache.js +113 -0
  191. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
  192. package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
  193. package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
  194. package/dist/stores/LambderMemoryRateLimiter.js +14 -13
  195. package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
  196. package/dist/stores/LambderMemorySessionStore.js +38 -19
  197. package/dist/stores/LambderS3FileSource.d.ts +21 -6
  198. package/dist/stores/LambderS3FileSource.js +12 -7
  199. package/dist/testing/LambderTestApp.d.ts +176 -0
  200. package/dist/testing/LambderTestApp.js +204 -0
  201. package/dist/testing/LambderTestVisitor.d.ts +153 -0
  202. package/dist/testing/LambderTestVisitor.js +154 -0
  203. package/dist/testing.d.ts +27 -0
  204. package/dist/testing.js +24 -0
  205. package/package.json +20 -3
  206. package/dist/api/LambderApiPolicyEngine.d.ts +0 -36
  207. package/dist/api/LambderApiPolicyEngine.js +0 -77
  208. package/dist/shared/util/LambderKeyFields.d.ts +0 -32
  209. package/dist/shared/util/LambderKeyFields.js +0 -34
@@ -9,16 +9,27 @@ import type LambderSessionController from "../session/LambderSessionController.j
9
9
  import { type LambderPublicFilesOptions } from "./LambderPublicFiles.js";
10
10
  import { type LambderIndexHtmlOptions } from "./LambderIndexHtml.js";
11
11
  import { LambderFiles } from "./LambderFiles.js";
12
+ import { type LambderPipelineBackends, type LambderPipelineBackendSwap } from "../api/LambderApiPipeline.js";
13
+ import type { LambderFileSource } from "../shared/contracts/LambderFileSource.js";
14
+ import { LAMBDER_BACKEND_SWAP, LAMBDER_CRASH_WATCH } from "../shared/util/LambderTestingDoors.js";
12
15
  import { type LambderApiSignatureEntry } from "../api/LambderApiSignature.js";
13
16
  import { type LambderApiSignatureMap } from "../shared/wire/LambderApiSignature.js";
14
17
  import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
15
18
  import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../api/LambderApiGuards.js";
16
19
  import type { LambderApiRateLimitPolicyConfig, LambderRateLimitOption } from "../api/LambderApiRateLimits.js";
17
20
  import type { LambderApiIdempotencyConfig } from "../api/LambderApiIdempotency.js";
18
- import type { LambderContractEntry, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
21
+ import type { LambderContractEntry, LambderJsonOutputOf, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
19
22
  import { type LambderHttpEvent, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
20
23
  import type { MaybePromise } from "../shared/util/LambderTypeUtilities.js";
21
24
  import { type LambderRouteHandler, type LambderInputValidationHandler, type LambderFallbackHandler, type LambderGlobalErrorHandler, type LambderAfterRenderHook, type LambderBeforeRenderHook, type LambderFallbackHook, type LambderActionTools, type LambderCreateOptions, type LambderGivenOption, type LambderHandler, type LambderNestedOptionChecks, type LambderNoExtraKeys, type LambderRequirableGuardsField, type LambderSessionEnabledInstance, type LambderSessionRouteHandler } from "./LambderCreateOptions.js";
25
+ /** Everything `lambder/testing` may put under a built instance: the pipeline's stores, and the source its files are read from. */
26
+ export type LambderInstanceBackends = LambderPipelineBackends & {
27
+ fileSource?: LambderFileSource;
28
+ };
29
+ /** What the instance had a place for; see LambderPipelineBackendSwap. `files` is false on an instance created without the files option. */
30
+ export type LambderInstanceBackendSwap = LambderPipelineBackendSwap & {
31
+ files: boolean;
32
+ };
22
33
  /**
23
34
  * The "created" hook: run once the instance exists, with the instance. It is
24
35
  * declared here rather than beside the other hooks in LambderCreateOptions
@@ -39,7 +50,7 @@ export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, a
39
50
  * @typeParam _TIdempotencyEnabled - @internal True when create() received idempotency (do not pass manually)
40
51
  * @typeParam _TSessionGuardsRequired - @internal True when create() received requireSessionApiGuards (do not pass manually)
41
52
  * @typeParam _TPublicGuardsRequired - @internal True when create() received requirePublicApiGuards (do not pass manually)
42
- * @typeParam _TSessionsEnabled - @internal True when create() received the session option (do not pass manually). It defaults to TRUE, unlike its siblings: a plugin module annotates its parameter as the bare Lambder<SessionData>, and that annotation has to keep registering session APIs. create() is where the option is actually known, so create() is where the false comes from; `new Lambder(...)` keeps only the registration-time throw.
53
+ * @typeParam _TSessionsEnabled - @internal True when create() received the session option (do not pass manually). Defaults to true, unlike its siblings, so a plugin annotating its parameter as the bare Lambder<SessionData> can still register session APIs. create() knows the option and supplies the false; `new Lambder(...)` relies on the registration-time throw alone.
43
54
  *
44
55
  * @example
45
56
  * ```typescript
@@ -57,15 +68,15 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
57
68
  /** The instance's file reader (source + caches), or null without the files option. */
58
69
  files: LambderFiles | null;
59
70
  /**
60
- * Type property for extracting the API contract
61
- * Use this to export your API types to the frontend
71
+ * Type property for extracting the API contract, to export your API
72
+ * types to the frontend.
62
73
  *
63
74
  * Export it as an interface extending LambderFlattenContract, not as a
64
75
  * type alias. Chaining builds the contract as an intersection one member
65
- * deep per endpoint, and an interface collapses that into one declared
66
- * set of members, which every generic read of the contract (a mock
67
- * registry, a needs map, the typed caller) is then far cheaper against.
68
- * See LambderFlattenContract for the measurements.
76
+ * deep per endpoint; an interface collapses that into one declared set of
77
+ * members, which every generic read of the contract (a mock registry, a
78
+ * needs map, the typed caller) checks far more cheaply. See
79
+ * LambderFlattenContract for the measurements.
69
80
  *
70
81
  * @example
71
82
  * ```typescript
@@ -95,24 +106,37 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
95
106
  private corsConfig;
96
107
  private finalizeOptions;
97
108
  private requireSessionApiGuards;
109
+ /** Told what a request threw, beside whatever answers it; null outside a test. See LAMBDER_CRASH_WATCH. */
110
+ private crashWatcher;
111
+ /** The crashes option applied: reporting, and the framework's own 500. */
112
+ private readonly crashHandling;
113
+ /** What this instance binds onto every context it renders (ctx.sessionController, ctx.rateLimit, ctx.isRateLimited). */
114
+ private readonly contextTools;
98
115
  private readonly trustedClientIpHeaders;
116
+ private readonly trustedHostHeaders;
99
117
  private requirePublicApiGuards;
100
118
  constructor(options?: LambderCreateOptions<TSessionData>);
101
119
  setRouteFallbackHandler(routeFallbackHandler: LambderFallbackHandler): this;
102
120
  setApiFallbackHandler(apiFallbackHandler: LambderFallbackHandler): this;
103
121
  setApiInputValidationErrorHandler(apiInputValidationErrorHandler: LambderInputValidationHandler): this;
104
122
  setGlobalErrorHandler(globalErrorHandler: LambderGlobalErrorHandler): this;
105
- /** Response for session routes when the session is missing/expired (non-API). Default: 401. */
123
+ /**
124
+ * Response for a session route when the session is missing or expired,
125
+ * and for any non-API request whose route or hook meets a
126
+ * LambderSessionNotFoundError (the session ended while the request held
127
+ * it, or a session read found none, or cookies naming several: a
128
+ * LambderSessionAmbiguousError). Default: 401.
129
+ */
106
130
  setSessionExpiredRouteHandler(handler: LambderFallbackHandler): this;
107
131
  /**
108
132
  * Terminal public-file layer. Runs only when no route matched, so it can
109
133
  * never shadow routes registered after it. Serves files from the `files`
110
134
  * source configured at creation, under the reader's path rule, mime-typed,
111
135
  * memory-cached, with the immutable-cache heuristic for content-hashed
112
- * assets. Only configured methods reach it, default GET/HEAD, as for
113
- * serveIndexHtml; a gated-out method and a path the source has no file
114
- * for both fall through to setRouteFallbackHandler, where the app decides
115
- * what remains (e.g. render an app shell with res.templateFile).
136
+ * assets. Only configured methods reach it (default GET/HEAD); a
137
+ * gated-out method or a path with no file falls through to
138
+ * setRouteFallbackHandler, where the app decides what remains (e.g.
139
+ * render an app shell with res.templateFile).
116
140
  */
117
141
  servePublicFiles(options?: LambderPublicFilesOptions): this;
118
142
  /**
@@ -125,28 +149,36 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
125
149
  * res.templateFile (markers optional) with no-cache.
126
150
  */
127
151
  serveIndexHtml(handler?: LambderFallbackHandler, options?: LambderIndexHtmlOptions): this;
128
- addRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: (ctx: LambderRenderContext<any, LambderPathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
152
+ addRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: (ctx: LambderRenderContext<any, LambderPathParamsOf<TPath>, {}, TSessionData, _TRateLimitPolicies>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
129
153
  addRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderRouteHandler): this;
130
- addSessionRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: ((ctx: LambderSessionRenderContext<any, TSessionData, LambderPathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>) & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
154
+ addSessionRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: ((ctx: LambderSessionRenderContext<any, TSessionData, LambderPathParamsOf<TPath>, {}, _TRateLimitPolicies>, resolver: LambderResolver) => MaybePromise<LambderResponse>) & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
131
155
  addSessionRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderSessionRouteHandler<TSessionData> & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
132
- addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.infer<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, false> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
156
+ addApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, false> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, false> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
133
157
  input: TInput;
134
158
  output: TOutput;
135
159
  } & {
136
- /** Named rate limits, checked in declared order before guards and validation: a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
160
+ /** Named rate limits, checked in declared order within their phase (per ip before the session read, per session before the guards, a custom key after the guards and input validation): a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
137
161
  rateLimit?: TRateOpt;
138
162
  /** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
139
163
  idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
140
- } & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt>, handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.infer<TInput>, z.infer<TOutput>, "public", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
141
- addSessionApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.infer<TInput>, true> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.infer<TInput>, true> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
164
+ } & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt>, handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, TSessionData, _TRateLimitPolicies>, resolver: LambderResolver<z.input<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOutputOf<z.output<TOutput>>, "public", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
165
+ addSessionApi<TName extends string, TInput extends z.ZodType, TOutput extends z.ZodType, const TRateOpt extends LambderRateLimitOption<_TRateLimitPolicies, z.input<TInput>, true> = never, const TGuardsOpt extends LambderGuardsOption<_TGuards, z.input<TInput>, true> = never, const TIdempotencyOpt extends LambderApiIdempotencyOption = never>(name: TName, schema: {
142
166
  input: TInput;
143
167
  output: TOutput;
144
168
  } & {
145
- /** Named rate limits, checked in declared order before guards and validation: a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
169
+ /** Named rate limits, checked in declared order within their phase (per ip before the session read, per session before the guards, a custom key after the guards and input validation): a name, a list of names, or a { name: true | override } map (windows overridable on perApi budgets, errorMessage on any). The first exceeded one refuses (429 envelope + Retry-After); attempts count on every counter checked before it. */
146
170
  rateLimit?: TRateOpt;
147
171
  /** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
148
172
  idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
149
- } & LambderRequirableGuardsField<_TSessionGuardsRequired, TGuardsOpt> & LambderSessionEnabledInstance<_TSessionsEnabled>, handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.infer<TInput>, z.infer<TOutput>, "session", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
173
+ } & LambderRequirableGuardsField<_TSessionGuardsRequired, TGuardsOpt> & LambderSessionEnabledInstance<_TSessionsEnabled>, handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>, _TRateLimitPolicies>, resolver: LambderResolver<z.input<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, LambderMergeContract<_TContract, TName, LambderContractEntry<z.input<TInput>, LambderJsonOutputOf<z.output<TOutput>>, "session", LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt, TRateOpt, TIdempotencyOpt>>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
174
+ /**
175
+ * What registering an API is, for addApi and addSessionApi alike: the
176
+ * checks that can refuse it, then its definition recorded (what
177
+ * apiSignatures() digests) and its action appended to the first-match
178
+ * chain. The two public methods differ only in the mode and in the types
179
+ * they give the handler.
180
+ */
181
+ private registerApi;
150
182
  addHook(hookEvent: 'created', hookFn: LambderCreatedHook, priority?: number): this;
151
183
  addHook(hookEvent: 'beforeRender', hookFn: LambderBeforeRenderHook, priority?: number): this;
152
184
  addHook(hookEvent: 'afterRender', hookFn: LambderAfterRenderHook, priority?: number): this;
@@ -171,34 +203,48 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
171
203
  addAction(filter: (event: unknown, ctx: LambderRenderContext | null) => boolean, actionFn: (event: unknown, tools: LambderActionTools) => MaybePromise<unknown>): this;
172
204
  use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract, any, any, any, any, any, any>) => Lambder<TSessionData, _TNewContract, any, any, any, any, any, any>): Lambder<TSessionData, _TNewContract extends _TContract ? _TNewContract : (_TContract & _TNewContract), _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired, _TSessionsEnabled>;
173
205
  /**
174
- * Sessions for this request: what handlers create, rotate, refresh and
175
- * end sessions with. An API call presents its posted CSRF token; a route
176
- * presents cookies alone.
206
+ * A session controller for a context: what creates, rotates, refreshes
207
+ * and ends sessions. An API call presents its posted CSRF token; a route
208
+ * presents cookies alone. A context this instance renders already
209
+ * carries one as `ctx.sessionController`; this is for a context it did
210
+ * not render, such as one createContext() built from an event on its own.
177
211
  */
178
212
  getSessionController(ctx: LambderRenderContext | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
179
213
  /** The session manager, for code that works on sessions outside a request (maintenance, tests). */
180
214
  getSessionManager(): LambderSessionManager<TSessionData>;
215
+ /**
216
+ * The backend swap `lambder/testing` performs: the stores given go under
217
+ * this instance in place, so every handler and guard that closed over it
218
+ * reaches them, and the production ones are out of reach from then on.
219
+ * Keyed by a symbol no entry point exports, so it is not part of what an
220
+ * app can call; see LAMBDER_BACKEND_SWAP.
221
+ */
222
+ [LAMBDER_BACKEND_SWAP](backends: LambderInstanceBackends): LambderInstanceBackendSwap;
223
+ /**
224
+ * The crash watch `lambder/testing` sets: told every error a request
225
+ * throws past the framework's own handling, before the global error
226
+ * handler or the last-resort 500 answers it. The answer is unchanged.
227
+ */
228
+ [LAMBDER_CRASH_WATCH](watcher: (error: Error) => void): void;
181
229
  /**
182
230
  * Every registered endpoint's signature, keyed by its hashed name: the
183
231
  * LambderApiSignatureMap both sides ship with. A generator imports the
184
- * finished instance, awaits this, and writes the result to a file the
185
- * frontend passes to LambderCaller as apiSignatures and the server passes
186
- * to create() as apiSignatures; at request time the pipeline compares a
187
- * call's signature with the server's copy of the same map. This is the
188
- * one place a digest is computed, so it has nothing to agree with but
189
- * itself. Keys are sorted, so the generated file diffs by endpoint.
232
+ * finished instance, awaits this, and writes a file that LambderCaller
233
+ * and create() both take as apiSignatures; at request time the pipeline
234
+ * compares a call's signature with the server's copy. This is the only
235
+ * place a digest is computed, so there is no second computation to drift
236
+ * from it. Keys are sorted, so the generated file diffs by endpoint.
190
237
  */
191
238
  apiSignatures(): Promise<LambderApiSignatureMap>;
192
239
  /**
193
- * The same signatures with the endpoint name each one was digested from,
194
- * sorted by key as the map is. What apiSignatures() leaves out on purpose:
195
- * the map a client ships lists no names, so a generator that only had the
196
- * map could report that four signatures changed but not which endpoints.
197
- * Reading this instead, it can name them.
240
+ * The same signatures, sorted by key as the map is, with the endpoint
241
+ * name each was digested from. The map a client ships deliberately lists
242
+ * no names, so a generator reading only the map could say how many
243
+ * signatures changed but not which endpoints; this lets it name them.
198
244
  *
199
- * A build-time view by construction. It comes off the server instance,
200
- * which a generator imports and a client never does, so nothing here
201
- * reaches a bundle unless the generator writes it there.
245
+ * A build-time view: it comes off the server instance, which a generator
246
+ * imports and a client never does, so nothing here reaches a bundle
247
+ * unless the generator writes it there.
202
248
  */
203
249
  apiSignatureEntries(): Promise<LambderApiSignatureEntry[]>;
204
250
  getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder<any>;
@@ -212,38 +258,84 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
212
258
  * The beforeRender hooks, in priority order: the replaced context to
213
259
  * continue with, or the response one of them answered with.
214
260
  *
215
- * Its own method because BOTH request paths run it. Left inline after the
216
- * match, it ran for routes and APIs and for nothing else, so a
217
- * servePublicFiles or serveIndexHtml answer, which is every asset and
218
- * every app-shell page, skipped the one hook that can inspect a request,
219
- * replace its context or short-circuit it: a security header written in a
220
- * hook reached the API answers and not the HTML it was written for, and a
221
- * maintenance-mode hook served the whole frontend anyway.
261
+ * Its own method because both request paths run it. Run only after a
262
+ * match, it would skip every servePublicFiles and serveIndexHtml answer
263
+ * (every asset and app-shell page): a security header written in a hook
264
+ * would miss the HTML it was written for, and a maintenance-mode hook
265
+ * would still serve the whole frontend.
266
+ *
267
+ * Each replacement is also handed to `onContextReplaced` as it is made,
268
+ * rather than only returned: render() answers from it after the handler
269
+ * too (the afterRender hooks, a crash's report, reveal and global error
270
+ * handler), and a handler or a later hook that throws returns nothing.
222
271
  */
223
272
  private runBeforeRenderHooks;
224
273
  private handleNoMatchedAction;
225
274
  /**
226
275
  * True for the OPTIONS request the CORS layer answers by itself. Asked
227
- * twice: once to build the 204, once at the end of render() to decide
228
- * which form of the headers goes on. Asking once and letting the tail
229
- * apply the ordinary headers on top of the 204's would put both forms on
230
- * a preflight, answering `Vary: Origin, Origin` and an
231
- * Access-Control-Expose-Headers that means nothing before a request.
276
+ * twice: once to build the 204, once at the end of render() to pick which
277
+ * form of the headers goes on. Applying the ordinary headers on top of
278
+ * the 204's would put both forms on a preflight: `Vary: Origin, Origin`
279
+ * and an Access-Control-Expose-Headers that means nothing before a
280
+ * request.
232
281
  */
233
282
  private isCorsPreflight;
234
283
  private resolveRequest;
235
284
  render(event: LambderHttpEvent, lambdaContext: Context): Promise<LambderHttpResponse>;
285
+ /**
286
+ * A thrown value that is an answer rather than a crash, as the response;
287
+ * anything else is rethrown to the crash path.
288
+ *
289
+ * - A LambderResponse IS the response (res.die.*, throw res.html(...)).
290
+ * - A LambderApiRefusal on an API call is its structured refusal
291
+ * (brand-checked, not instanceof, to survive duplicate installs).
292
+ * - A LambderSessionNotFoundError is a missing session: one that ended
293
+ * while the request held it (a logout or a password change landing
294
+ * mid-request), or one a route or hook asked for that the request
295
+ * never had or whose cookies named several (its subclass
296
+ * LambderSessionAmbiguousError). It is answered the way a missing
297
+ * session is answered here, the decision the API pipeline makes for a
298
+ * handler, applied to the routes and hooks it never sees.
299
+ */
300
+ private answerThrown;
301
+ /**
302
+ * A crash, from the thrown value to the answer. It is told to the test
303
+ * watch and reported before anything answers, so the report depends on
304
+ * nothing the answer might break; then the app's global error handler
305
+ * answers it, or the framework's own 500 does when there is none or it
306
+ * failed too.
307
+ */
308
+ private answerCrash;
309
+ /**
310
+ * An answer to a crash, carrying what the call wrote and its CORS headers.
311
+ * As on the success path, headers belong to the call: a call that wrote a
312
+ * session cookie and then threw still owes the browser that cookie, and a
313
+ * cross-origin caller cannot read the error at all without CORS headers.
314
+ * The CORS verdict is the one the request settled before it crashed, so
315
+ * answering a crash runs none of the app's code.
316
+ */
317
+ private withCallHeaders;
236
318
  /**
237
319
  * Fetch the session for a session route or short-circuit it with the
238
- * sessionExpiredRouteHandler response (default 401). Session APIs never
239
- * come through here: the pipeline answers them with the protocol's
240
- * { sessionExpired: true } envelope itself.
320
+ * answer for a missing session. Session APIs never come through here:
321
+ * the pipeline answers them with the protocol's { sessionExpired: true }
322
+ * envelope itself.
241
323
  */
242
324
  private requireSession;
243
- /** Dispatch a non-HTTP Lambda event to the registered actions. */
325
+ /**
326
+ * The answer to a request that needed a session and has none, whether it
327
+ * never had one or it ended while the request held it: an API call gets
328
+ * the protocol's sessionExpired envelope, as the pipeline gives a session
329
+ * API, and anything else the setSessionExpiredRouteHandler answer, a 401
330
+ * by default.
331
+ */
332
+ private sessionMissingResponse;
333
+ /**
334
+ * Dispatch a non-HTTP Lambda event to the registered actions. What an
335
+ * action throws is reported (crashes.report) and then rethrown untouched,
336
+ * so Lambda's retries and dead-letter queues still see the failure.
337
+ */
244
338
  renderEvent(event: unknown, lambdaContext: Context): Promise<unknown>;
245
- /** Registration-time checks shared by addApi/addSessionApi. */
246
- private assertApiRegistration;
247
339
  /**
248
340
  * The answer for a rejected input: the app's
249
341
  * setApiInputValidationErrorHandler when set, otherwise the standard 422
@@ -257,7 +349,8 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
257
349
  * via res.die.*) becomes the answer the pipeline stores and hands back.
258
350
  * The context is the pipeline's context, so a session it fetched is on
259
351
  * ctx.session and the validated payload is on ctx.apiPayload when the
260
- * handler runs.
352
+ * handler runs. The handler's resolver knows the API's output schema, so
353
+ * every success payload is parsed through it before it is sent.
261
354
  */
262
355
  private runApi;
263
356
  /** A thrown LambderApiRefusal (from a hook, say) as the structured API envelope: the core's one mapping. */
@@ -265,11 +358,10 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
265
358
  }
266
359
  /**
267
360
  * The canonical way to create an instance: fix the session data type first,
268
- * then create with the full configuration in one declaration; the policy,
269
- * guard, and idempotency types are INFERRED from the options, so the
270
- * instance is born fully typed and `typeof lambderApp` is the annotation
271
- * type for api modules. No enable/define chain exists, so there are no
272
- * ordering rules and nothing can be half-configured.
361
+ * then create with the full configuration in one declaration. The policy,
362
+ * guard and idempotency types are inferred from the options, so the instance
363
+ * is born fully typed and `typeof lambderApp` is the annotation type for api
364
+ * modules. There are no ordering rules, and nothing can be half-configured.
273
365
  *
274
366
  * ```typescript
275
367
  * // app.ts (imports no api modules, so modules can import the type back)
@@ -290,16 +382,16 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
290
382
  * export const handler = lambder.getHandler();
291
383
  * ```
292
384
  *
293
- * Why curried (`initLambder<S>().create(...)` rather than
294
- * `new Lambder<S>(...)`): TypeScript type arguments are all-or-nothing per
295
- * call, so explicitly passing the session data type to the constructor
296
- * would silently WIDEN the inferred policy and guard types to their {}
297
- * defaults. Fixing the session type in the first call lets the second call
298
- * infer everything else from the options. `new Lambder(options)` remains
299
- * for untyped or session-data-free instances.
385
+ * Curried because TypeScript type arguments are all-or-nothing per call:
386
+ * passing the session data type to `new Lambder<S>(...)` would silently
387
+ * widen the inferred policy and guard types to their {} defaults. Fixing the
388
+ * session type in the first call lets the second infer everything else.
389
+ * `new Lambder(options)` serves untyped or session-data-free instances.
300
390
  */
301
391
  export declare const initLambder: <TSessionData = any>() => {
302
392
  create<const TOptions extends LambderCreateOptions<TSessionData>>(options: LambderNoExtraKeys<TOptions, LambderCreateOptions<TSessionData>> & LambderNestedOptionChecks<TSessionData, TOptions>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
303
393
  policies: infer TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>;
304
394
  } ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, [LambderGivenOption<TOptions, "requireSessionApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "requirePublicApiGuards">] extends [false | undefined] ? false : true, [LambderGivenOption<TOptions, "session">] extends [undefined] ? false : true>;
395
+ guard: import("../api/LambderApiGuards.js").LambderGuardBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>, LambderSessionRenderContext<any, TSessionData>>;
396
+ rateLimitKey: import("../api/LambderApiRateLimits.js").LambderRateLimitKeyBuilder<LambderRenderContext<any, Record<string, string>, {}, TSessionData>>;
305
397
  };