lambder 6.0.2 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CHANGELOG.md +2316 -0
  2. package/README.md +60 -33
  3. package/dist/api/LambderApiAnswer.d.ts +40 -0
  4. package/dist/api/LambderApiAnswer.js +19 -0
  5. package/dist/api/LambderApiCallContext.d.ts +38 -0
  6. package/dist/api/LambderApiCallContext.js +13 -0
  7. package/dist/api/LambderApiDefinition.d.ts +18 -0
  8. package/dist/api/LambderApiDefinition.js +1 -0
  9. package/dist/api/LambderApiEnvelope.d.ts +67 -0
  10. package/dist/api/LambderApiEnvelope.js +180 -0
  11. package/dist/api/LambderApiGuards.d.ts +302 -0
  12. package/dist/api/LambderApiGuards.js +134 -0
  13. package/dist/api/LambderApiIdempotency.d.ts +122 -0
  14. package/dist/api/LambderApiIdempotency.js +330 -0
  15. package/dist/api/LambderApiPipeline.d.ts +134 -0
  16. package/dist/api/LambderApiPipeline.js +221 -0
  17. package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
  18. package/dist/api/LambderApiPolicyEngine.js +77 -0
  19. package/dist/api/LambderApiRateLimits.d.ts +206 -0
  20. package/dist/api/LambderApiRateLimits.js +239 -0
  21. package/dist/api/LambderApiRequest.d.ts +101 -0
  22. package/dist/api/LambderApiRequest.js +129 -0
  23. package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
  24. package/dist/api/LambderApiValidationRefusal.js +40 -0
  25. package/dist/client/LambderCaller.d.ts +62 -55
  26. package/dist/client/LambderCaller.js +147 -90
  27. package/dist/client/lambderFetchTransport.d.ts +9 -0
  28. package/dist/client/lambderFetchTransport.js +71 -0
  29. package/dist/client.d.ts +20 -10
  30. package/dist/client.js +11 -5
  31. package/dist/core/Lambder.d.ts +117 -253
  32. package/dist/core/Lambder.js +374 -341
  33. package/dist/core/LambderContext.d.ts +54 -44
  34. package/dist/core/LambderContext.js +41 -110
  35. package/dist/core/LambderCreateOptions.d.ts +285 -0
  36. package/dist/core/LambderCreateOptions.js +44 -0
  37. package/dist/core/LambderFiles.d.ts +1 -45
  38. package/dist/core/LambderFiles.js +18 -38
  39. package/dist/core/LambderIndexHtml.d.ts +37 -0
  40. package/dist/core/LambderIndexHtml.js +87 -0
  41. package/dist/core/LambderPolicyBuilders.d.ts +17 -0
  42. package/dist/core/LambderPolicyBuilders.js +16 -0
  43. package/dist/core/LambderPublicFiles.d.ts +5 -2
  44. package/dist/core/LambderPublicFiles.js +7 -2
  45. package/dist/core/LambderResolver.d.ts +8 -6
  46. package/dist/core/LambderResponse.d.ts +29 -11
  47. package/dist/core/LambderResponse.js +96 -49
  48. package/dist/core/LambderResponseBuilder.d.ts +18 -14
  49. package/dist/core/LambderResponseBuilder.js +19 -25
  50. package/dist/core/LambderRouting.d.ts +18 -7
  51. package/dist/core/LambderRouting.js +17 -7
  52. package/dist/core/LambderTemplatingEngine.d.ts +0 -62
  53. package/dist/core/LambderTemplatingEngine.js +7 -3
  54. package/dist/index.d.ts +85 -32
  55. package/dist/index.js +44 -16
  56. package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
  57. package/dist/invoke/LambderInvokeCaller.js +140 -335
  58. package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
  59. package/dist/invoke/LambderInvokeOutcome.js +129 -0
  60. package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
  61. package/dist/invoke/LambderLambdaEvent.js +187 -0
  62. package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
  63. package/dist/invoke/lambderHandlerTransport.js +89 -0
  64. package/dist/mock/LambderMockApp.d.ts +352 -0
  65. package/dist/mock/LambderMockApp.js +815 -0
  66. package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
  67. package/dist/mock/LambderMockBrowserCookies.js +76 -0
  68. package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
  69. package/dist/mock/LambderMockCallRecorder.js +183 -0
  70. package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
  71. package/dist/mock/LambderMockCreateOptions.js +9 -0
  72. package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
  73. package/dist/mock/LambderMockEntryRegistry.js +126 -0
  74. package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
  75. package/dist/mock/LambderMockFailureInjector.js +138 -0
  76. package/dist/mock/LambderMockTypes.d.ts +421 -0
  77. package/dist/mock/LambderMockTypes.js +8 -0
  78. package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
  79. package/dist/mock/lambderMockConsoleLogger.js +35 -0
  80. package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
  81. package/dist/mock/lambderMockInvokeTransport.js +52 -0
  82. package/dist/mock/lambderMockMswHandler.d.ts +99 -0
  83. package/dist/mock/lambderMockMswHandler.js +126 -0
  84. package/dist/mock.d.ts +34 -0
  85. package/dist/mock.js +27 -0
  86. package/dist/session/LambderSessionController.d.ts +199 -30
  87. package/dist/session/LambderSessionController.js +396 -82
  88. package/dist/session/LambderSessionCrypto.d.ts +66 -0
  89. package/dist/session/LambderSessionCrypto.js +101 -0
  90. package/dist/session/LambderSessionManager.d.ts +118 -80
  91. package/dist/session/LambderSessionManager.js +212 -184
  92. package/dist/shared/LambderI18n.d.ts +6 -6
  93. package/dist/shared/LambderI18n.js +1 -1
  94. package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
  95. package/dist/shared/contracts/LambderFileSource.js +19 -0
  96. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
  97. package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
  98. package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
  99. package/dist/shared/contracts/LambderRateLimiter.js +24 -0
  100. package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
  101. package/dist/shared/contracts/LambderSessionStore.js +13 -0
  102. package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
  103. package/dist/shared/transport/LambderApiTransport.js +65 -0
  104. package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
  105. package/dist/shared/transport/LambderCookieJar.js +246 -0
  106. package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
  107. package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
  108. package/dist/shared/util/LambderBase64.d.ts +10 -0
  109. package/dist/shared/util/LambderBase64.js +27 -0
  110. package/dist/shared/util/LambderCallAbort.d.ts +62 -0
  111. package/dist/shared/util/LambderCallAbort.js +80 -0
  112. package/dist/shared/util/LambderClientIp.d.ts +32 -0
  113. package/dist/shared/util/LambderClientIp.js +56 -0
  114. package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
  115. package/dist/shared/util/LambderExpiringMap.js +217 -0
  116. package/dist/shared/util/LambderKeyFields.d.ts +32 -0
  117. package/dist/shared/util/LambderKeyFields.js +34 -0
  118. package/dist/shared/util/LambderNodeModules.d.ts +9 -0
  119. package/dist/shared/util/LambderNodeModules.js +39 -0
  120. package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
  121. package/dist/shared/util/LambderOptionChecks.js +33 -0
  122. package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
  123. package/dist/shared/util/LambderResponseBrand.js +18 -0
  124. package/dist/shared/util/LambderTextDigest.d.ts +17 -0
  125. package/dist/shared/util/LambderTextDigest.js +34 -0
  126. package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
  127. package/dist/shared/util/LambderTypeUtilities.js +8 -0
  128. package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
  129. package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
  130. package/dist/shared/wire/LambderApiContract.d.ts +129 -0
  131. package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
  132. package/dist/shared/wire/LambderApiOptionValues.js +11 -0
  133. package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
  134. package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
  135. package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
  136. package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
  137. package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
  138. package/dist/shared/wire/LambderCallOptions.js +17 -0
  139. package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
  140. package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
  141. package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
  142. package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
  143. package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
  144. package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
  145. package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
  146. package/dist/shared/wire/LambderHttpStatus.js +1 -0
  147. package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
  148. package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
  149. package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
  150. package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
  151. package/dist/stores/LambderDdbCache.d.ts +12 -9
  152. package/dist/stores/LambderDdbCache.js +56 -47
  153. package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
  154. package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
  155. package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
  156. package/dist/stores/LambderDdbRateLimiter.js +47 -45
  157. package/dist/stores/LambderDdbSdk.d.ts +83 -6
  158. package/dist/stores/LambderDdbSdk.js +83 -2
  159. package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
  160. package/dist/stores/LambderDdbSessionStore.js +161 -0
  161. package/dist/stores/LambderHttpFileSource.d.ts +1 -1
  162. package/dist/stores/LambderHttpFileSource.js +10 -1
  163. package/dist/stores/LambderLocalFileSource.d.ts +15 -0
  164. package/dist/stores/LambderLocalFileSource.js +28 -0
  165. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
  166. package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
  167. package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
  168. package/dist/stores/LambderMemoryRateLimiter.js +64 -0
  169. package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
  170. package/dist/stores/LambderMemorySessionStore.js +74 -0
  171. package/dist/stores/LambderS3FileSource.d.ts +1 -1
  172. package/dist/stores/LambderS3FileSource.js +1 -1
  173. package/package.json +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,71 @@
1
+ import { buildTransportEnvelope, LambderTransportFailure } from "../shared/transport/LambderApiTransport.js";
2
+ /**
3
+ * fetch, with one failure worth a better sentence than the platform gives it.
4
+ * A relative apiPath is resolved against the page; outside a page there is no
5
+ * page to resolve it against, so fetch rejects with a URL parse error that
6
+ * arrives at the caller looking like the network is down. The condition is
7
+ * read from the actual failure rather than guessed beforehand, so a runtime
8
+ * that resolves relative URLs some other way is left alone.
9
+ */
10
+ const fetchOrExplain = async (url, init) => {
11
+ try {
12
+ return await fetch(url, init);
13
+ }
14
+ catch (err) {
15
+ // Narrow on purpose: only the URL failing to parse, which is what
16
+ // this is. A transport that rejected for any other reason, a stubbed
17
+ // one included, keeps its own meaning.
18
+ const failedToParseUrl = err instanceof TypeError && /failed to parse url|invalid url/i.test(String(err.message));
19
+ if (failedToParseUrl && url.startsWith("/") && typeof globalThis.location?.href !== "string") {
20
+ throw new LambderTransportFailure("protocol", `lambderFetchTransport could not resolve the relative apiPath "${url}": there is no page to resolve it against outside a browser. `
21
+ + "Give the caller an absolute apiPath (https://api.example.com/api), or a transport that does not need one.", { cause: err });
22
+ }
23
+ throw err;
24
+ }
25
+ };
26
+ /**
27
+ * The production transport: one POST of the envelope to the API path over
28
+ * fetch, with the cookie and CORS behaviour a browser call needs. The
29
+ * caller's default, built from its isCorsEnabled option.
30
+ */
31
+ export const lambderFetchTransport = (options = {}) => async (request) => {
32
+ const cors = options.cors ?? false;
33
+ const response = await fetchOrExplain(request.apiPath, {
34
+ method: 'POST', cache: 'no-cache',
35
+ // Cross-origin API hosts need CORS mode and included credentials.
36
+ mode: cors ? 'cors' : 'same-origin',
37
+ credentials: cors ? 'include' : 'same-origin',
38
+ redirect: 'follow', referrerPolicy: 'origin',
39
+ headers: {
40
+ // The caller's own headers go on first, so the ones this transport
41
+ // owns cannot be displaced by them, the way the synthesized invoke
42
+ // event does it. With the spread last, a per-call `Cookie` header
43
+ // (to carry one extra cookie, say) replaced the whole Cookie
44
+ // header a jar had just built, and the session went missing with
45
+ // nothing in the failure pointing at the cause.
46
+ ...(request.headers ?? {}),
47
+ 'Content-Type': 'application/json',
48
+ // The cookies the request is meant to carry, which is how a cookie
49
+ // jar works over fetch outside a browser: undici sends this
50
+ // header, and a page cannot (Cookie is a forbidden header name, so
51
+ // a browser drops it silently and uses its own cookie store, which
52
+ // is the right answer there). Without it the jar collected every
53
+ // Set-Cookie and sent none of them back, so a Node script against
54
+ // a deployed app got its CSRF token filled in and sessionExpired
55
+ // on every session call.
56
+ ...(request.cookies?.length ? { Cookie: request.cookies.join('; ') } : {}),
57
+ },
58
+ body: JSON.stringify(buildTransportEnvelope(request)),
59
+ ...(request.signal ? { signal: request.signal } : {}),
60
+ });
61
+ return {
62
+ status: response.status,
63
+ statusText: response.statusText,
64
+ header: (name) => response.headers?.get?.(name) ?? null,
65
+ json: () => response.json(),
66
+ text: () => response.text(),
67
+ // Set-Cookie is unreadable from a page's script; where the runtime
68
+ // exposes it (Node's fetch), a cookie jar can still consume it.
69
+ ...(typeof response.headers?.getSetCookie === "function" ? { setCookies: response.headers.getSetCookie() } : {}),
70
+ };
71
+ };
package/dist/client.d.ts CHANGED
@@ -7,16 +7,26 @@
7
7
  * the root entry (`"lambder"`) is the server surface.
8
8
  */
9
9
  export { default as LambderCaller } from "./client/LambderCaller.js";
10
- export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError, LambderCallOptions, LambderCallerOptions, LambderGuardInputsProvider, LambderProvidedGuardInputs, LambderIdempotencyKeyScope, } from "./client/LambderCaller.js";
11
- export { LambderApiError, isLambderApiError, refuse, LAMBDER_REFUSAL_CODES } from "./shared/LambderApiError.js";
12
- export type { LambderApiErrorOptions, LambderRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/LambderApiError.js";
13
- export type { ApiContractShape, LambderApiResponse, LambderApiResponseConfig } from "./shared/LambderApiContract.js";
14
- export { describeCrash, errorFromCrashDetail } from "./shared/LambderCrashDetail.js";
15
- export type { LambderCrashDetail, LambderCrashCause } from "./shared/LambderCrashDetail.js";
16
- export { compressPayloadGzip, decompressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/LambderRequestPayload.js";
17
- export type { LambderCompressedGzipPayload, LambderCompressedBrotliPayload, LambderRequestCompressionOption, LambderRequestCompressionSettings, } from "./shared/LambderRequestPayload.js";
18
- export { resolveCompressionOption } from "./shared/LambderCompressionOption.js";
19
- export type { LambderCompressionOption, LambderCompressionSettingsBase } from "./shared/LambderCompressionOption.js";
10
+ export { DEFAULT_SESSION_TOKEN_COOKIE_KEY, DEFAULT_SESSION_CSRF_COOKIE_KEY } from "./shared/wire/LambderSessionCookieNames.js";
11
+ export { lambderFetchTransport } from "./client/lambderFetchTransport.js";
12
+ export { buildTransportEnvelope, LambderTransportFailure, isLambderTransportFailure } from "./shared/transport/LambderApiTransport.js";
13
+ export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTransport.js";
14
+ export type { LambderApiTransport, LambderApiTransportRequest, LambderTransportFailureReason } from "./shared/transport/LambderApiTransport.js";
15
+ export { LambderCookieJar, parseSetCookie } from "./shared/transport/LambderCookieJar.js";
16
+ export type { LambderStoredCookie } from "./shared/transport/LambderCookieJar.js";
17
+ export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
18
+ export type { LambderApiAnswerOutcome, LambderApiSuccessOutcome, LambderApiCallFailure, LambderApiValidationFailure, LambderApiEnvelopeFailure, LambderApiHttpAnswer, } from "./shared/wire/LambderApiOutcome.js";
19
+ export type { LambderApiOutcome, LambderApiFailureReason, LambderValidationError, LambderCallOptions, LambderCallerOptions, LambderGuardInputsProvider, LambderProvidedGuardInputs, LambderIdempotencyKeyScope, LambderLogListHandler, } from "./client/LambderCaller.js";
20
+ export { LambderApiRefusal, isLambderApiRefusal, refuse, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
21
+ export type { LambderApiRefusalOptions, LambderRefusalMessage, LambderAppRefusalMessage, LambderRefusalCode, LambderRefuseOptions } from "./shared/wire/LambderApiRefusal.js";
22
+ export type { LambderApiContractShape, LambderApiMode, LambderApiEnvelopeBody, LambderApiResponseConfig, LambderGuardNamesIn, LambderContractMode, LambderContractKeysWithMode, LambderContractGuardsOf, LambderContractGuardNames, LambderContractGuardInputsOf, LambderContractGuardInput, LambderContractGuardInputNames, LambderContractRateLimitOf, LambderContractIdempotencyOf, } from "./shared/wire/LambderApiContract.js";
23
+ export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
24
+ export type { LambderCrashDetail, LambderCrashCause } from "./shared/wire/LambderCrashDetail.js";
25
+ export { compressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/wire/LambderRequestPayload.js";
26
+ export type { LambderCompressedGzipPayload, LambderCompressedBrotliPayload, LambderRequestCompressionOption, LambderRequestCompressionSettings, } from "./shared/wire/LambderRequestPayload.js";
27
+ export { resolveCompressionOption } from "./shared/wire/LambderCompressionOption.js";
28
+ export type { LambderCompressionOption, LambderCompressionSettingsBase } from "./shared/wire/LambderCompressionOption.js";
20
29
  export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml, type LambderHtmlValue } from "./shared/LambderHtml.js";
21
30
  export { createLambderI18n } from "./shared/LambderI18n.js";
22
31
  export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./shared/LambderI18n.js";
32
+ export type { LambderHttpStatusCode } from "./shared/wire/LambderHttpStatus.js";
package/dist/client.js CHANGED
@@ -6,17 +6,23 @@
6
6
  * import from this entry so their bundles can never pick up server code;
7
7
  * the root entry (`"lambder"`) is the server surface.
8
8
  */
9
- // The typed API caller.
9
+ // The typed API caller, and how it reaches the server.
10
10
  export { default as LambderCaller } from "./client/LambderCaller.js";
11
+ export { DEFAULT_SESSION_TOKEN_COOKIE_KEY, DEFAULT_SESSION_CSRF_COOKIE_KEY } from "./shared/wire/LambderSessionCookieNames.js";
12
+ export { lambderFetchTransport } from "./client/lambderFetchTransport.js";
13
+ export { buildTransportEnvelope, LambderTransportFailure, isLambderTransportFailure } from "./shared/transport/LambderApiTransport.js";
14
+ export { lambderCookieJarTransport } from "./shared/transport/lambderCookieJarTransport.js";
15
+ export { LambderCookieJar, parseSetCookie } from "./shared/transport/LambderCookieJar.js";
16
+ export { resolveApiOutcome } from "./shared/wire/LambderApiOutcome.js";
11
17
  // Typed API refusals (isomorphic: shared code may throw them from anywhere;
12
18
  // in the browser they are plain Errors).
13
- export { LambderApiError, isLambderApiError, refuse, LAMBDER_REFUSAL_CODES } from "./shared/LambderApiError.js";
19
+ export { LambderApiRefusal, isLambderApiRefusal, refuse, LAMBDER_REFUSAL_CODES } from "./shared/wire/LambderApiRefusal.js";
14
20
  // A crash described for a caller allowed to see it (the envelope's `crash` field; pure, no Node built-ins).
15
- export { describeCrash, errorFromCrashDetail } from "./shared/LambderCrashDetail.js";
21
+ export { describeCrash, errorFromCrashDetail } from "./shared/wire/LambderCrashDetail.js";
16
22
  // Request payload compression (browser-safe: gzip via CompressionStream, no Node built-ins).
17
- export { compressPayloadGzip, decompressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/LambderRequestPayload.js";
23
+ export { compressPayloadGzip, isRequestCompressionAvailable, COMPRESSED_PAYLOAD_GZ_FIELD, COMPRESSED_PAYLOAD_BR_FIELD, COMPRESSED_PAYLOAD_BYTES_FIELD, DEFAULT_REQUEST_COMPRESSION_SETTINGS, } from "./shared/wire/LambderRequestPayload.js";
18
24
  // The compression option vocabulary every Lambder surface shares (pure: no zlib).
19
- export { resolveCompressionOption } from "./shared/LambderCompressionOption.js";
25
+ export { resolveCompressionOption } from "./shared/wire/LambderCompressionOption.js";
20
26
  // Type-safe templating (tagged templates with auto-escaping)
21
27
  export { html, xml, raw, jsonScript, escapeHtml, renderHtmlValue, LambderSafeHtml } from "./shared/LambderHtml.js";
22
28
  // Typed translations (standalone, isomorphic)
@@ -2,195 +2,28 @@ import type { z } from "zod";
2
2
  import type { Context } from "aws-lambda";
3
3
  import LambderResolver from "./LambderResolver.js";
4
4
  import LambderResponseBuilder from "./LambderResponseBuilder.js";
5
- import { LambderResponse, type LambderHttpResponse, type LambderResponseCompressionOption } from "./LambderResponse.js";
6
- import { type ConditionFunction, type LambderRouteMatcher, type PathParamsOf } from "./LambderRouting.js";
7
- import { type LambderCorsConfig } from "./LambderCors.js";
8
- import { type LambderSessionDataRefreshConfig } from "../session/LambderSessionManager.js";
9
- import { type LambderCompressionOption } from "../shared/LambderCompressionOption.js";
10
- import LambderSessionController, { type LambderSessionCookieOptions } from "../session/LambderSessionController.js";
5
+ import { LambderResponse, type LambderHttpResponse } from "./LambderResponse.js";
6
+ import { type LambderRouteConditionFn, type LambderRouteMatcher, type LambderPathParamsOf, type LambderRoutePath } from "./LambderRouting.js";
7
+ import LambderSessionManager from "../session/LambderSessionManager.js";
8
+ import type LambderSessionController from "../session/LambderSessionController.js";
11
9
  import { type LambderPublicFilesOptions } from "./LambderPublicFiles.js";
12
- import { LambderFiles, type LambderFilesOption } from "./LambderFiles.js";
13
- import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../policies/LambderApiGuards.js";
14
- import type { LambderApiRateLimitPolicyConfig, LambderApiRateLimitsConfig, LambderRateLimitOption } from "../policies/LambderApiRateLimits.js";
15
- import type { LambderApiIdempotencyConfig } from "../policies/LambderApiIdempotency.js";
16
- import type { MergeContract } from "../shared/LambderApiContract.js";
10
+ import { type LambderIndexHtmlOptions } from "./LambderIndexHtml.js";
11
+ import { LambderFiles } from "./LambderFiles.js";
12
+ import type { LambderApiIdempotencyOption } from "../shared/wire/LambderApiOptionValues.js";
13
+ import type { LambderApiGuard, LambderGuardMetaMap, LambderGuardsOption, LambderGuardDataOf, LambderGuardInputsOf } from "../api/LambderApiGuards.js";
14
+ import type { LambderApiRateLimitPolicyConfig, LambderRateLimitOption } from "../api/LambderApiRateLimits.js";
15
+ import type { LambderApiIdempotencyConfig } from "../api/LambderApiIdempotency.js";
16
+ import type { LambderContractEntry, LambderMergeContract } from "../shared/wire/LambderApiContract.js";
17
17
  import { type LambderHttpEvent, type LambderRenderContext, type LambderSessionRenderContext } from "./LambderContext.js";
18
- export type { PathParamsOf, RouteCondition, ConditionFunction, LambderRouteMatcher } from "./LambderRouting.js";
19
- export type { LambderCorsConfig } from "./LambderCors.js";
20
- type MaybePromise<T> = T | Promise<T>;
21
- type Path = `/${string}`;
22
- type ActionFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => MaybePromise<LambderResponse>;
23
- type SessionActionFunction<SessionData = any> = (ctx: LambderSessionRenderContext<any, SessionData>, resolver: LambderResolver) => MaybePromise<LambderResponse>;
24
- type HookCreatedFunction = (lambderInstance: Lambder<any, any, any, any, any, any, any>) => void | Promise<void>;
25
- /** Return the (possibly replaced) ctx to continue, a LambderResponse to short-circuit, or an Error to fail. */
26
- type HookBeforeRenderFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => MaybePromise<LambderRenderContext | LambderResponse | Error>;
27
- type HookAfterRenderFunction = (ctx: LambderRenderContext, resolver: LambderResolver, response: LambderResponse) => MaybePromise<LambderResponse | Error>;
28
- type HookFallbackFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => void | Promise<void>;
29
- type GlobalErrorHandlerFunction = (err: Error, ctx: LambderRenderContext | null, response: LambderResponseBuilder, logListToApiResponse?: any[]) => MaybePromise<LambderResponse>;
30
- type FallbackHandlerFunction = (ctx: LambderRenderContext, resolver: LambderResolver) => MaybePromise<LambderResponse>;
31
- type ApiInputValidationErrorHandlerFunction = (ctx: LambderRenderContext, resolver: LambderResolver, zodError: z.ZodError) => MaybePromise<LambderResponse>;
32
- export type LambderIndexHtmlOptions = {
33
- /** Methods that reach the index handler. Default: ["GET", "HEAD"]. */
34
- methods?: string[];
35
- /**
36
- * Skip paths whose last segment contains a dot, treating them as missing
37
- * assets rather than app routes. Default: false — real files have already
38
- * been served by servePublicFiles at this point, and plenty of app routes
39
- * carry dots (JWTs, coordinates, domain names, version numbers). Turn it
40
- * on to get 404s instead of a 200 shell for missing-asset requests.
41
- */
42
- skipFilePaths?: boolean;
43
- /** 301-redirect trailing-slash paths to the canonical no-slash URL. Default: false. */
44
- redirectTrailingSlash?: boolean;
45
- /** Shell served by the default handler. Default: "index.html". */
46
- indexFile?: string | ((ctx: LambderRenderContext) => string);
47
- /** Compression override, like servePublicFiles: "auto" (default), true/false, or (ctx) => boolean | "auto". */
48
- compress?: boolean | "auto" | ((ctx: LambderRenderContext) => boolean | "auto");
49
- };
18
+ import type { MaybePromise } from "../shared/util/LambderTypeUtilities.js";
19
+ 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";
50
20
  /**
51
- * Second argument of an addAction handler. Discriminated on `ctx`: HTTP
52
- * invocations get the full context and a resolver, non-HTTP invocations get
53
- * null for both.
21
+ * The "created" hook: run once the instance exists, with the instance. It is
22
+ * declared here rather than beside the other hooks in LambderCreateOptions
23
+ * because its parameter is the class, and an options module that names the
24
+ * class cannot be read without it.
54
25
  */
55
- export type LambderActionTools = {
56
- ctx: LambderRenderContext;
57
- res: LambderResolver;
58
- lambdaContext: Context;
59
- } | {
60
- ctx: null;
61
- res: null;
62
- lambdaContext: Context;
63
- };
64
- /** Overloaded handler type returned by getHandler(): HTTP events get a typed response, others dispatch to actions. */
65
- export type LambderHandler = {
66
- (event: LambderHttpEvent, context: Context): Promise<LambderHttpResponse>;
67
- (event: unknown, context: Context): Promise<unknown>;
68
- };
69
- /** DynamoDB session configuration (the `session` option of create/new). */
70
- export type LambderSessionOptions<TSessionData = any> = {
71
- tableName: string;
72
- tableRegion: string;
73
- sessionSalt: string;
74
- enableSlidingExpiration?: boolean;
75
- /** Min seconds between sliding-expiration writes. Default: max(60, 5% of TTL). */
76
- slidingWriteIntervalSeconds?: number;
77
- /** Session cookie attributes, e.g. { domain: ".example.com" } for cross-subdomain sessions. `domain` may be a (hostname) => string function for multi-domain deployments. */
78
- cookie?: LambderSessionCookieOptions;
79
- /** Session cookie names. Defaults: LMDRSESSIONTKID / LMDRSESSIONCSTK. */
80
- tokenCookieKey?: string;
81
- csrfCookieKey?: string;
82
- partitionKey?: string;
83
- sortKey?: string;
84
- /**
85
- * Opt-in freshness for session.data derived from external state (roles,
86
- * permissions, feature flags...). Every session read renews data past
87
- * its ttlSeconds via your refresh callback, persisting in place on the
88
- * same record: same tokens, same cookies. Return null from refresh to
89
- * end the session. See LambderSessionDataRefreshConfig for the exact
90
- * semantics.
91
- */
92
- dataRefresh?: LambderSessionDataRefreshConfig<TSessionData>;
93
- /**
94
- * Brotli compression of session.data at rest. `true` (the default)
95
- * compresses every record, the same as `{ minBytes: 0 }`; `false` turns
96
- * it off; `{ minBytes }` compresses only records whose JSON is at least
97
- * that many bytes. Records written under either setting read back, so
98
- * it can be switched on or off on a live table.
99
- */
100
- compression?: LambderCompressionOption;
101
- };
102
- /**
103
- * Everything an instance is configured with, in ONE declaration: base
104
- * serving options plus the type-affecting policy layer (rate limits,
105
- * guards, idempotency) and session/CORS config. There are no enable/define
106
- * chain methods; the instance is born fully configured and fully typed
107
- * (via initLambder), so no ordering rules exist and no partially-configured
108
- * instance type ever needs a name.
109
- */
110
- export type LambderCreateOptions<TSessionData = any> = {
111
- /**
112
- * Where the app's files come from, for servePublicFiles, serveIndexHtml,
113
- * res.file and res.templateFile: a LambderLocalFileSource over a folder
114
- * (the build output bundled with the deployment), a LambderS3FileSource
115
- * (S3, R2), or any LambderFileSource; or `{ source, memoryCache }` to
116
- * tune or disable the in-memory file cache. Required by those features.
117
- */
118
- files?: LambderFilesOption;
119
- apiPath?: string;
120
- apiVersion?: string;
121
- /**
122
- * Automatic compression for compressible responses. `true` (the default)
123
- * is `{ minBytes: 860, encodings: ["br", "gzip"], quality: 5 }`; `false`
124
- * disables it. `encodings` is a preference order, so `["gzip"]` opts out
125
- * of Brotli for a client or CDN that mishandles it, and `quality` is the
126
- * Brotli quality, the same field the at-rest stores take.
127
- */
128
- compression?: LambderResponseCompressionOption;
129
- /** Automatic ETag + If-None-Match 304 on GET/HEAD 200 responses. Default: true. */
130
- etag?: boolean;
131
- /** Guard threshold for Lambda's ~6MB response cap. Default: 5,500,000. */
132
- maxResponseBytes?: number;
133
- /**
134
- * Ceiling on what a gzipped request payload may restore to (Lambda's
135
- * ~6MB invoke cap already bounds the compressed bytes). Default:
136
- * 20,000,000. Requests over it are refused rather than decompressed.
137
- * The restored JSON is parsed in full before any policy or session
138
- * check, so size it to the function's memory.
139
- */
140
- maxRequestPayloadBytes?: number;
141
- /** CORS: true allows any origin; or pass a LambderCorsConfig. Default: off. */
142
- cors?: boolean | LambderCorsConfig;
143
- /** DynamoDB-backed sessions; required for addSessionApi/addSessionRoute. */
144
- session?: LambderSessionOptions<TSessionData>;
145
- /** Declarative per-API rate limiting: your limiter plus named policies APIs reference (typed) via the `rateLimit` option. */
146
- rateLimits?: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>;
147
- /** Named guards APIs reference (typed) via the `guards` option; build each with lambderGuard(). */
148
- guards?: Record<string, LambderApiGuard<any, any, any>>;
149
- /**
150
- * Make an authorization declaration part of registering a session API:
151
- * every addSessionApi must declare `guards`, at the type level (a
152
- * missing `guards` is a compile error) and at registration (a plain-JS
153
- * caller throws). An API that legitimately needs none, because the
154
- * session itself is the whole authorization (the signed-in user's own
155
- * account), declares a named no-op session guard, so every opt-out is
156
- * explicit and one grep lists them all. Needs a guards map to pick
157
- * from. Default: false.
158
- */
159
- requireSessionApiGuards?: boolean;
160
- /**
161
- * The same for public APIs: every addApi must declare `guards`, at the
162
- * type level and at registration.
163
- *
164
- * Public APIs are open by default and that is the right default, so this
165
- * is off unless an app decides otherwise. What it buys an app that turns
166
- * it on is that a public endpoint's openness becomes a written decision
167
- * rather than an omission: the ones anybody may call declare a named no-op
168
- * guard carrying the reason, and the ones that authorize their caller some
169
- * other way (a signature, a device secret, a one-shot token) name where
170
- * that happens. One grep over the guard names then lists every public
171
- * door and why it is open, which is the review question a growing public
172
- * surface makes expensive to answer any other way. Needs a guards map to
173
- * pick from. Default: false.
174
- */
175
- requirePublicApiGuards?: boolean;
176
- /** Declarative idempotency: your store plus replay defaults; APIs opt in via `idempotency: true | { ttlSeconds }`. */
177
- idempotency?: LambderApiIdempotencyConfig;
178
- };
179
- /**
180
- * The `guards` field of an API's options: optional by default, required once
181
- * create() received the require*ApiGuards flag for that kind of API, so that
182
- * an authorization declaration cannot be forgotten at the type level.
183
- *
184
- * One type for both kinds: the requirement is the same shape either way, and
185
- * only which flag switches it on differs.
186
- */
187
- type LambderRequirableGuardsField<TRequired extends boolean, TGuardsOpt> = TRequired extends true ? {
188
- /** Named guards, run in declared order before input validation: a name, a non-empty list of names, or a non-empty { name: param } map for parameterized guards. Required on this instance: an API that needs no authorization declares a named no-op guard, so every opt-out is explicit and one grep lists them all. Their input requirements merge into this API's contract input; their return values land typed on ctx.guardData. */
189
- guards: TGuardsOpt;
190
- } : {
191
- /** Named guards, run in declared order before input validation: a name, a non-empty list of names, or a non-empty { name: param } map for parameterized guards. Their input requirements merge into this API's contract input; their return values land typed on ctx.guardData. */
192
- guards?: TGuardsOpt;
193
- };
26
+ export type LambderCreatedHook = (lambderInstance: Lambder<any, any, any, any, any, any, any, any>) => void | Promise<void>;
194
27
  /**
195
28
  * Main Lambder class for building type-safe serverless APIs. Create
196
29
  * instances with initLambder<SessionData>().create({...}) (see below): the
@@ -204,6 +37,7 @@ type LambderRequirableGuardsField<TRequired extends boolean, TGuardsOpt> = TRequ
204
37
  * @typeParam _TIdempotencyEnabled - @internal True when create() received idempotency (do not pass manually)
205
38
  * @typeParam _TSessionGuardsRequired - @internal True when create() received requireSessionApiGuards (do not pass manually)
206
39
  * @typeParam _TPublicGuardsRequired - @internal True when create() received requirePublicApiGuards (do not pass manually)
40
+ * @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.
207
41
  *
208
42
  * @example
209
43
  * ```typescript
@@ -214,7 +48,7 @@ type LambderRequirableGuardsField<TRequired extends boolean, TGuardsOpt> = TRequ
214
48
  * .addApi('createUser', { input: z.object({...}), output: z.object({...}) }, handler);
215
49
  * ```
216
50
  */
217
- export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}, _TRateLimitPolicies extends Record<string, LambderApiRateLimitPolicyConfig> = {}, _TGuards extends Record<string, any> = {}, _TIdempotencyEnabled extends boolean = false, _TSessionGuardsRequired extends boolean = false, _TPublicGuardsRequired extends boolean = false> {
51
+ export default class Lambder<TSessionData = any, _TContract extends Record<string, any> = {}, _TRateLimitPolicies extends Record<string, LambderApiRateLimitPolicyConfig> = {}, _TGuards extends Record<string, any> = {}, _TIdempotencyEnabled extends boolean = false, _TSessionGuardsRequired extends boolean = false, _TPublicGuardsRequired extends boolean = false, _TSessionsEnabled extends boolean = true> {
218
52
  apiPath: string;
219
53
  apiVersion: null | string;
220
54
  /** The instance's file reader (source + caches), or null without the files option. */
@@ -231,7 +65,8 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
231
65
  */
232
66
  readonly ApiContract: _TContract;
233
67
  private actionList;
234
- private apiPolicyEngine;
68
+ /** The API core: the pipeline every API call runs through, shared in shape with the mock runtime. */
69
+ private readonly pipeline;
235
70
  private registeredApiNames;
236
71
  private hookList;
237
72
  private createdHooks;
@@ -242,32 +77,29 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
242
77
  private apiInputValidationErrorHandler;
243
78
  private sessionExpiredRouteHandler;
244
79
  private publicFilesHandler;
245
- private indexHtmlConfig;
80
+ private indexHtmlHandler;
246
81
  private eventActionList;
247
82
  private corsConfig;
248
83
  private finalizeOptions;
249
- private maxRequestPayloadBytes;
250
84
  private requireSessionApiGuards;
85
+ private readonly trustedClientIpHeaders;
251
86
  private requirePublicApiGuards;
252
- private lambderSessionManager?;
253
- private sessionCookieOptions;
254
- private sessionTokenCookieKey;
255
- private sessionCsrfCookieKey;
256
87
  constructor(options?: LambderCreateOptions<TSessionData>);
257
- setRouteFallbackHandler(routeFallbackHandler: FallbackHandlerFunction): this;
258
- setApiFallbackHandler(apiFallbackHandler: FallbackHandlerFunction): this;
259
- setApiInputValidationErrorHandler(apiInputValidationErrorHandler: ApiInputValidationErrorHandlerFunction): this;
260
- setGlobalErrorHandler(globalErrorHandler: GlobalErrorHandlerFunction): this;
88
+ setRouteFallbackHandler(routeFallbackHandler: LambderFallbackHandler): this;
89
+ setApiFallbackHandler(apiFallbackHandler: LambderFallbackHandler): this;
90
+ setApiInputValidationErrorHandler(apiInputValidationErrorHandler: LambderInputValidationHandler): this;
91
+ setGlobalErrorHandler(globalErrorHandler: LambderGlobalErrorHandler): this;
261
92
  /** Response for session routes when the session is missing/expired (non-API). Default: 401. */
262
- setSessionExpiredRouteHandler(handler: FallbackHandlerFunction): this;
93
+ setSessionExpiredRouteHandler(handler: LambderFallbackHandler): this;
263
94
  /**
264
95
  * Terminal public-file layer. Runs only when no route matched, so it can
265
96
  * never shadow routes registered after it. Serves files from the `files`
266
- * source configured at creation, traversal-safe, mime-typed,
97
+ * source configured at creation, under the reader's path rule, mime-typed,
267
98
  * memory-cached, with the immutable-cache heuristic for content-hashed
268
- * assets; when the source has no such file the request falls through to
269
- * setRouteFallbackHandler, where the app decides what remains (e.g.
270
- * render an app shell with res.templateFile).
99
+ * assets. Only configured methods reach it, default GET/HEAD, as for
100
+ * serveIndexHtml; a gated-out method and a path the source has no file
101
+ * for both fall through to setRouteFallbackHandler, where the app decides
102
+ * what remains (e.g. render an app shell with res.templateFile).
271
103
  */
272
104
  servePublicFiles(options?: LambderPublicFilesOptions): this;
273
105
  /**
@@ -279,64 +111,33 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
279
111
  * Without a handler, index.html from the files source is served via
280
112
  * res.templateFile (markers optional) with no-cache.
281
113
  */
282
- serveIndexHtml(handler?: FallbackHandlerFunction, options?: LambderIndexHtmlOptions): this;
283
- /** Apply the serveIndexHtml gates; null means fall through. */
284
- private tryServeIndexHtml;
285
- private getOrCreatePolicyEngine;
286
- /**
287
- * The response for a rejected input: the app's
288
- * setApiInputValidationErrorHandler when set, otherwise the standard 422
289
- * body. The API's own schema and every preflight slice (guard inputs,
290
- * rate-limit keys) answer through here, so one failure has one shape.
291
- */
292
- private inputValidationRefusal;
293
- /** Registration-time checks shared by addApi/addSessionApi. */
294
- private assertApiRegistration;
295
- addRoute<TPath extends Path>(condition: TPath, actionFn: (ctx: LambderRenderContext<any, PathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
296
- addRoute(condition: RegExp | ConditionFunction | LambderRouteMatcher, actionFn: ActionFunction): this;
297
- addSessionRoute<TPath extends Path>(condition: TPath, actionFn: (ctx: LambderSessionRenderContext<any, TSessionData, PathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
298
- addSessionRoute(condition: RegExp | ConditionFunction | LambderRouteMatcher, actionFn: SessionActionFunction<TSessionData>): this;
299
- use<_TNewContract extends Record<string, any>>(plugin: (lambder: Lambder<TSessionData, _TContract, any, any, any, any, any>) => Lambder<TSessionData, _TNewContract, any, any, any, any, any>): Lambder<TSessionData, _TNewContract extends _TContract ? _TNewContract : (_TContract & _TNewContract), _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired>;
300
- 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>(name: TName, schema: {
114
+ serveIndexHtml(handler?: LambderFallbackHandler, options?: LambderIndexHtmlOptions): this;
115
+ addRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: (ctx: LambderRenderContext<any, LambderPathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>): this;
116
+ addRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderRouteHandler): this;
117
+ addSessionRoute<TPath extends LambderRoutePath>(condition: TPath, actionFn: ((ctx: LambderSessionRenderContext<any, TSessionData, LambderPathParamsOf<TPath>>, resolver: LambderResolver) => MaybePromise<LambderResponse>) & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
118
+ addSessionRoute(condition: RegExp | LambderRouteConditionFn | LambderRouteMatcher, actionFn: LambderSessionRouteHandler<TSessionData> & LambderSessionEnabledInstance<_TSessionsEnabled>): this;
119
+ 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: {
301
120
  input: TInput;
302
121
  output: TOutput;
303
122
  } & {
304
123
  /** 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. */
305
124
  rateLimit?: TRateOpt;
306
125
  /** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
307
- idempotency?: _TIdempotencyEnabled extends true ? (boolean | {
308
- ttlSeconds?: number;
309
- }) : never;
310
- } & LambderRequirableGuardsField<_TPublicGuardsRequired, TGuardsOpt>, handler: (ctx: LambderRenderContext<z.infer<TInput>, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>, LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired>;
311
- 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>(name: TName, schema: {
126
+ idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
127
+ } & 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>;
128
+ 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: {
312
129
  input: TInput;
313
130
  output: TOutput;
314
131
  } & {
315
132
  /** 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. */
316
133
  rateLimit?: TRateOpt;
317
134
  /** Replay-protect this API per client idempotencyKey. Requires the idempotency option at creation. */
318
- idempotency?: _TIdempotencyEnabled extends true ? (boolean | {
319
- ttlSeconds?: number;
320
- }) : never;
321
- } & LambderRequirableGuardsField<_TSessionGuardsRequired, TGuardsOpt>, handler: (ctx: LambderSessionRenderContext<z.infer<TInput>, TSessionData, Record<string, string>, LambderGuardDataOf<_TGuards, TGuardsOpt>>, resolver: LambderResolver<z.infer<TOutput>>) => MaybePromise<LambderResponse>): Lambder<TSessionData, MergeContract<_TContract, TName, z.infer<TInput>, z.infer<TOutput>, LambderGuardInputsOf<_TGuards, TGuardsOpt>, TGuardsOpt>, _TRateLimitPolicies, _TGuards, _TIdempotencyEnabled, _TSessionGuardsRequired, _TPublicGuardsRequired>;
322
- /**
323
- * Fetch the session or short-circuit the request: API calls get the
324
- * protocol's { sessionExpired: true } response (handled by LambderCaller),
325
- * routes get the sessionExpiredRouteHandler response (default 401).
326
- */
327
- private requireSession;
328
- addHook(hookEvent: 'created', hookFn: HookCreatedFunction, priority?: number): this;
329
- addHook(hookEvent: 'beforeRender', hookFn: HookBeforeRenderFunction, priority?: number): this;
330
- addHook(hookEvent: 'afterRender', hookFn: HookAfterRenderFunction, priority?: number): this;
331
- addHook(hookEvent: 'fallback', hookFn: HookFallbackFunction, priority?: number): this;
332
- getSessionController(ctx: LambderRenderContext | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
333
- getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder<any>;
334
- private getResolver;
335
- /** Map a thrown LambderApiError onto the structured API envelope. */
336
- private apiErrorResponse;
337
- getHandler(): LambderHandler;
338
- /** True when the Lambda event is an API Gateway HTTP event (REST API v1 or HTTP API / Function URL v2). */
339
- static isHttpEvent(event: unknown): event is LambderHttpEvent;
135
+ idempotency?: _TIdempotencyEnabled extends true ? TIdempotencyOpt : never;
136
+ } & 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>;
137
+ addHook(hookEvent: 'created', hookFn: LambderCreatedHook, priority?: number): this;
138
+ addHook(hookEvent: 'beforeRender', hookFn: LambderBeforeRenderHook, priority?: number): this;
139
+ addHook(hookEvent: 'afterRender', hookFn: LambderAfterRenderHook, priority?: number): this;
140
+ addHook(hookEvent: 'fallback', hookFn: LambderFallbackHook, priority?: number): this;
340
141
  /**
341
142
  * Register an action that filters on the raw Lambda event and, for HTTP
342
143
  * invocations, the context (ctx is null otherwise).
@@ -355,13 +156,76 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
355
156
  */
356
157
  addAction<TEvent>(filter: (event: unknown, ctx: LambderRenderContext | null) => event is TEvent, actionFn: (event: TEvent, tools: LambderActionTools) => MaybePromise<unknown>): this;
357
158
  addAction(filter: (event: unknown, ctx: LambderRenderContext | null) => boolean, actionFn: (event: unknown, tools: LambderActionTools) => MaybePromise<unknown>): this;
358
- /** Dispatch a non-HTTP Lambda event to the registered actions. */
359
- renderEvent(event: unknown, lambdaContext: Context): Promise<unknown>;
159
+ 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>;
160
+ /**
161
+ * Sessions for this request: what handlers create, rotate, refresh and
162
+ * end sessions with. An API call presents its posted CSRF token; a route
163
+ * presents cookies alone.
164
+ */
165
+ getSessionController(ctx: LambderRenderContext | LambderSessionRenderContext<any, TSessionData>): LambderSessionController<TSessionData>;
166
+ /** The session manager, for code that works on sessions outside a request (maintenance, tests). */
167
+ getSessionManager(): LambderSessionManager<TSessionData>;
168
+ getResponseBuilder(ctx?: LambderRenderContext): LambderResponseBuilder<any>;
169
+ private getResolver;
170
+ getHandler(): LambderHandler;
171
+ /** True when the Lambda event is an API Gateway HTTP event (REST API v1 or HTTP API / Function URL v2). */
172
+ static isHttpEvent(event: unknown): event is LambderHttpEvent;
360
173
  private ensureInitialized;
361
174
  private applyCors;
175
+ /**
176
+ * The beforeRender hooks, in priority order: the replaced context to
177
+ * continue with, or the response one of them answered with.
178
+ *
179
+ * Its own method because BOTH request paths run it. Left inline after the
180
+ * match, it ran for routes and APIs and for nothing else, so a
181
+ * servePublicFiles or serveIndexHtml answer, which is every asset and
182
+ * every app-shell page, skipped the one hook that can inspect a request,
183
+ * replace its context or short-circuit it: a security header written in a
184
+ * hook reached the API answers and not the HTML it was written for, and a
185
+ * maintenance-mode hook served the whole frontend anyway.
186
+ */
187
+ private runBeforeRenderHooks;
362
188
  private handleNoMatchedAction;
189
+ /**
190
+ * True for the OPTIONS request the CORS layer answers by itself. Asked
191
+ * twice: once to build the 204, once at the end of render() to decide
192
+ * which form of the headers goes on. Asking once and letting the tail
193
+ * apply the ordinary headers on top of the 204's would put both forms on
194
+ * a preflight, answering `Vary: Origin, Origin` and an
195
+ * Access-Control-Expose-Headers that means nothing before a request.
196
+ */
197
+ private isCorsPreflight;
363
198
  private resolveRequest;
364
199
  render(event: LambderHttpEvent, lambdaContext: Context): Promise<LambderHttpResponse>;
200
+ /**
201
+ * Fetch the session for a session route or short-circuit it with the
202
+ * sessionExpiredRouteHandler response (default 401). Session APIs never
203
+ * come through here: the pipeline answers them with the protocol's
204
+ * { sessionExpired: true } envelope itself.
205
+ */
206
+ private requireSession;
207
+ /** Dispatch a non-HTTP Lambda event to the registered actions. */
208
+ renderEvent(event: unknown, lambdaContext: Context): Promise<unknown>;
209
+ /** Registration-time checks shared by addApi/addSessionApi. */
210
+ private assertApiRegistration;
211
+ /**
212
+ * The answer for a rejected input: the app's
213
+ * setApiInputValidationErrorHandler when set, otherwise the standard 422
214
+ * body. The API's own schema and every preflight slice (guard inputs,
215
+ * rate-limit keys) answer through here, so one failure has one shape.
216
+ */
217
+ private inputValidationRefusal;
218
+ /**
219
+ * One API call through the core: the pipeline runs the protocol steps and
220
+ * calls back for the handler, whose LambderResponse (returned, or thrown
221
+ * via res.die.*) becomes the answer the pipeline stores and hands back.
222
+ * The context is the pipeline's context, so a session it fetched is on
223
+ * ctx.session and the validated payload is on ctx.apiPayload when the
224
+ * handler runs.
225
+ */
226
+ private runApi;
227
+ /** A thrown LambderApiRefusal (from a hook, say) as the structured API envelope: the core's one mapping. */
228
+ private apiErrorResponse;
365
229
  }
366
230
  /**
367
231
  * The canonical way to create an instance: fix the session data type first,
@@ -375,7 +239,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
375
239
  * // app.ts (imports no api modules, so modules can import the type back)
376
240
  * export const lambderApp = initLambder<SessionData>().create({
377
241
  * apiPath: "/api",
378
- * session: { tableName: "app-session", tableRegion: "us-east-1", sessionSalt: "..." },
242
+ * session: { store: new LambderDdbSessionStore({ tableName: "app-session", region: "us-east-1" }), sessionSalt: "..." },
379
243
  * rateLimits: { limiter, policies },
380
244
  * guards,
381
245
  * idempotency: { store },
@@ -399,7 +263,7 @@ export default class Lambder<TSessionData = any, _TContract extends Record<strin
399
263
  * for untyped or session-data-free instances.
400
264
  */
401
265
  export declare const initLambder: <TSessionData = any>() => {
402
- create<const TOptions extends LambderCreateOptions<TSessionData>>(options: TOptions): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
266
+ create<const TOptions extends LambderCreateOptions<TSessionData>>(options: LambderNoExtraKeys<TOptions, LambderCreateOptions<TSessionData>> & LambderNestedOptionChecks<TSessionData, TOptions>): Lambder<TSessionData, {}, TOptions["rateLimits"] extends {
403
267
  policies: infer TPolicies extends Record<string, LambderApiRateLimitPolicyConfig>;
404
- } ? TPolicies : {}, TOptions["guards"] extends Record<string, LambderApiGuard<any, any, any>> ? LambderGuardMetaMap<TOptions["guards"]> : {}, TOptions["idempotency"] extends LambderApiIdempotencyConfig ? true : false, TOptions["requireSessionApiGuards"] extends true ? true : false, TOptions["requirePublicApiGuards"] extends true ? true : false>;
268
+ } ? 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>;
405
269
  };