lambder 7.3.1 → 8.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/CHANGELOG.md +1047 -3
  2. package/README.md +46 -21
  3. package/dist/api/LambderApiAnswer.d.ts +18 -22
  4. package/dist/api/LambderApiAnswer.js +6 -7
  5. package/dist/api/LambderApiCallContext.d.ts +21 -8
  6. package/dist/api/LambderApiCallContext.js +22 -4
  7. package/dist/api/LambderApiDefinition.d.ts +4 -3
  8. package/dist/api/LambderApiEnvelope.d.ts +14 -9
  9. package/dist/api/LambderApiEnvelope.js +33 -34
  10. package/dist/api/LambderApiGuards.d.ts +78 -51
  11. package/dist/api/LambderApiGuards.js +34 -36
  12. package/dist/api/LambderApiIdempotency.d.ts +68 -62
  13. package/dist/api/LambderApiIdempotency.js +214 -151
  14. package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
  15. package/dist/api/LambderApiOutputValidationError.js +50 -0
  16. package/dist/api/LambderApiPipeline.d.ts +47 -38
  17. package/dist/api/LambderApiPipeline.js +122 -63
  18. package/dist/api/LambderApiRateLimits.d.ts +201 -54
  19. package/dist/api/LambderApiRateLimits.js +185 -108
  20. package/dist/api/LambderApiRequest.d.ts +27 -21
  21. package/dist/api/LambderApiRequest.js +26 -19
  22. package/dist/api/LambderApiSignature.d.ts +12 -15
  23. package/dist/api/LambderApiSignature.js +28 -51
  24. package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
  25. package/dist/api/LambderApiValidationRefusal.js +10 -10
  26. package/dist/build/ContractTypePrinter.d.ts +85 -0
  27. package/dist/build/ContractTypePrinter.js +402 -0
  28. package/dist/build/freshProcessVerifier.d.ts +13 -0
  29. package/dist/build/freshProcessVerifier.js +19 -0
  30. package/dist/build/moduleLocation.d.ts +11 -0
  31. package/dist/build/moduleLocation.js +6 -0
  32. package/dist/build/writeApiContract.d.ts +78 -0
  33. package/dist/build/writeApiContract.js +302 -0
  34. package/dist/build/writeApiSignatures.d.ts +114 -0
  35. package/dist/build/writeApiSignatures.js +217 -0
  36. package/dist/build/writeFileAtomically.d.ts +8 -0
  37. package/dist/build/writeFileAtomically.js +22 -0
  38. package/dist/build.d.ts +14 -0
  39. package/dist/build.js +11 -0
  40. package/dist/client/LambderCaller.d.ts +13 -44
  41. package/dist/client/LambderCaller.js +77 -84
  42. package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
  43. package/dist/client/LambderReloadLoopBreaker.js +90 -46
  44. package/dist/client/LambderUploadRunner.d.ts +96 -0
  45. package/dist/client/LambderUploadRunner.js +234 -0
  46. package/dist/client/lambderFetchTransport.d.ts +4 -1
  47. package/dist/client/lambderFetchTransport.js +52 -28
  48. package/dist/client.d.ts +9 -3
  49. package/dist/client.js +6 -1
  50. package/dist/core/Lambder.d.ts +143 -79
  51. package/dist/core/Lambder.js +350 -231
  52. package/dist/core/LambderContext.d.ts +82 -15
  53. package/dist/core/LambderContext.js +107 -20
  54. package/dist/core/LambderCors.d.ts +21 -3
  55. package/dist/core/LambderCors.js +35 -16
  56. package/dist/core/LambderCrashHandling.d.ts +40 -0
  57. package/dist/core/LambderCrashHandling.js +97 -0
  58. package/dist/core/LambderCreateOptions.d.ts +151 -75
  59. package/dist/core/LambderCreateOptions.js +16 -23
  60. package/dist/core/LambderFiles.d.ts +21 -7
  61. package/dist/core/LambderFiles.js +62 -34
  62. package/dist/core/LambderIndexHtml.js +12 -11
  63. package/dist/core/LambderPolicyBuilders.d.ts +17 -5
  64. package/dist/core/LambderPolicyBuilders.js +17 -5
  65. package/dist/core/LambderPublicFiles.d.ts +11 -5
  66. package/dist/core/LambderPublicFiles.js +32 -4
  67. package/dist/core/LambderRequestPath.d.ts +43 -0
  68. package/dist/core/LambderRequestPath.js +63 -0
  69. package/dist/core/LambderResponse.d.ts +26 -5
  70. package/dist/core/LambderResponse.js +157 -70
  71. package/dist/core/LambderResponseBuilder.d.ts +49 -4
  72. package/dist/core/LambderResponseBuilder.js +64 -3
  73. package/dist/core/LambderRouting.d.ts +2 -3
  74. package/dist/core/LambderRouting.js +22 -7
  75. package/dist/core/LambderTemplatingEngine.js +211 -32
  76. package/dist/index.d.ts +25 -8
  77. package/dist/index.js +13 -4
  78. package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
  79. package/dist/invoke/LambderInvokeCaller.js +76 -66
  80. package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
  81. package/dist/invoke/LambderInvokeOutcome.js +9 -22
  82. package/dist/invoke/LambderLambdaEvent.d.ts +29 -9
  83. package/dist/invoke/LambderLambdaEvent.js +40 -22
  84. package/dist/invoke/lambderHandlerTransport.d.ts +9 -10
  85. package/dist/invoke/lambderHandlerTransport.js +15 -18
  86. package/dist/mock/LambderMockApp.d.ts +67 -83
  87. package/dist/mock/LambderMockApp.js +167 -153
  88. package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
  89. package/dist/mock/LambderMockBrowserCookies.js +24 -28
  90. package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
  91. package/dist/mock/LambderMockCallRecorder.js +19 -28
  92. package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
  93. package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
  94. package/dist/mock/LambderMockEntryRegistry.js +24 -29
  95. package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
  96. package/dist/mock/LambderMockFailureInjector.js +3 -6
  97. package/dist/mock/LambderMockTypes.d.ts +78 -108
  98. package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
  99. package/dist/mock/lambderMockInvokeTransport.js +11 -10
  100. package/dist/mock/lambderMockMswHandler.d.ts +43 -33
  101. package/dist/mock/lambderMockMswHandler.js +50 -39
  102. package/dist/mock/lambderMockUploadMswHandler.d.ts +26 -0
  103. package/dist/mock/lambderMockUploadMswHandler.js +28 -0
  104. package/dist/mock.d.ts +4 -1
  105. package/dist/mock.js +6 -3
  106. package/dist/session/LambderSessionController.d.ts +108 -89
  107. package/dist/session/LambderSessionController.js +187 -168
  108. package/dist/session/LambderSessionCrypto.d.ts +16 -7
  109. package/dist/session/LambderSessionCrypto.js +26 -12
  110. package/dist/session/LambderSessionManager.d.ts +124 -46
  111. package/dist/session/LambderSessionManager.js +262 -137
  112. package/dist/shared/LambderHtml.d.ts +42 -3
  113. package/dist/shared/LambderHtml.js +127 -7
  114. package/dist/shared/LambderHtmlPositions.d.ts +173 -0
  115. package/dist/shared/LambderHtmlPositions.js +652 -0
  116. package/dist/shared/LambderI18n.d.ts +10 -11
  117. package/dist/shared/LambderI18n.js +33 -21
  118. package/dist/shared/contracts/LambderCache.d.ts +66 -0
  119. package/dist/shared/contracts/LambderCache.js +11 -0
  120. package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
  121. package/dist/shared/contracts/LambderFileSource.js +5 -8
  122. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
  123. package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
  124. package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
  125. package/dist/shared/contracts/LambderRateLimiter.js +4 -5
  126. package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
  127. package/dist/shared/contracts/LambderSessionStore.js +5 -6
  128. package/dist/shared/contracts/LambderUploadBucket.d.ts +154 -0
  129. package/dist/shared/contracts/LambderUploadBucket.js +74 -0
  130. package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
  131. package/dist/shared/transport/LambderApiTransport.js +7 -7
  132. package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
  133. package/dist/shared/transport/LambderCookieJar.js +54 -66
  134. package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
  135. package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
  136. package/dist/shared/util/LambderCallAbort.d.ts +5 -5
  137. package/dist/shared/util/LambderCallAbort.js +5 -5
  138. package/dist/shared/util/LambderClientIp.d.ts +27 -11
  139. package/dist/shared/util/LambderClientIp.js +96 -13
  140. package/dist/shared/util/LambderContentDisposition.d.ts +10 -0
  141. package/dist/shared/util/LambderContentDisposition.js +13 -0
  142. package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
  143. package/dist/shared/util/LambderExpiringMap.js +41 -57
  144. package/dist/shared/util/LambderNodeModules.js +6 -7
  145. package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
  146. package/dist/shared/util/LambderOptionChecks.js +4 -4
  147. package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
  148. package/dist/shared/util/LambderResponseBrand.js +5 -5
  149. package/dist/shared/util/LambderTextDigest.d.ts +7 -5
  150. package/dist/shared/util/LambderTextDigest.js +11 -5
  151. package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
  152. package/dist/shared/util/LambderTypeUtilities.js +3 -3
  153. package/dist/shared/util/boundKeyField.d.ts +20 -0
  154. package/dist/shared/util/boundKeyField.js +34 -0
  155. package/dist/shared/util/canonicalJson.d.ts +11 -0
  156. package/dist/shared/util/canonicalJson.js +28 -0
  157. package/dist/shared/util/joinKeyFields.d.ts +20 -0
  158. package/dist/shared/util/joinKeyFields.js +22 -0
  159. package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
  160. package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
  161. package/dist/shared/wire/LambderApiContract.d.ts +98 -53
  162. package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
  163. package/dist/shared/wire/LambderApiOutcome.js +48 -23
  164. package/dist/shared/wire/LambderApiRefusal.d.ts +45 -27
  165. package/dist/shared/wire/LambderApiRefusal.js +42 -7
  166. package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
  167. package/dist/shared/wire/LambderApiSignature.js +16 -19
  168. package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
  169. package/dist/shared/wire/LambderCallOptions.js +9 -11
  170. package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
  171. package/dist/shared/wire/LambderCompressionCodec.js +31 -36
  172. package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
  173. package/dist/shared/wire/LambderCompressionOption.js +9 -9
  174. package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
  175. package/dist/shared/wire/LambderCrashDetail.js +12 -15
  176. package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
  177. package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
  178. package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
  179. package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
  180. package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
  181. package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
  182. package/dist/shared/wire/LambderInvokeApiId.js +27 -0
  183. package/dist/shared/wire/LambderOutcomeAssertions.d.ts +6 -7
  184. package/dist/shared/wire/LambderOutcomeAssertions.js +6 -7
  185. package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
  186. package/dist/shared/wire/LambderRequestPayload.js +4 -6
  187. package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -0
  188. package/dist/shared/wire/LambderUploadObjectFields.js +24 -0
  189. package/dist/shared/wire/LambderUploadRefusal.d.ts +9 -0
  190. package/dist/shared/wire/LambderUploadRefusal.js +18 -0
  191. package/dist/shared/wire/LambderUploadSchemas.d.ts +12 -0
  192. package/dist/shared/wire/LambderUploadSchemas.js +30 -0
  193. package/dist/stores/LambderCacheFiller.d.ts +48 -0
  194. package/dist/stores/LambderCacheFiller.js +119 -0
  195. package/dist/stores/LambderCacheKeys.d.ts +26 -0
  196. package/dist/stores/LambderCacheKeys.js +54 -0
  197. package/dist/stores/LambderCacheValues.d.ts +45 -0
  198. package/dist/stores/LambderCacheValues.js +74 -0
  199. package/dist/stores/LambderDdbCache.d.ts +121 -56
  200. package/dist/stores/LambderDdbCache.js +528 -225
  201. package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
  202. package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
  203. package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
  204. package/dist/stores/LambderDdbRateLimiter.js +151 -39
  205. package/dist/stores/LambderDdbSdk.d.ts +43 -31
  206. package/dist/stores/LambderDdbSdk.js +80 -38
  207. package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
  208. package/dist/stores/LambderDdbSessionStore.js +119 -47
  209. package/dist/stores/LambderHttpFileSource.d.ts +15 -6
  210. package/dist/stores/LambderHttpFileSource.js +15 -13
  211. package/dist/stores/LambderMemoryCache.d.ts +49 -0
  212. package/dist/stores/LambderMemoryCache.js +113 -0
  213. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
  214. package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
  215. package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
  216. package/dist/stores/LambderMemoryRateLimiter.js +14 -13
  217. package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
  218. package/dist/stores/LambderMemorySessionStore.js +38 -19
  219. package/dist/stores/LambderMemoryUploadBucket.d.ts +99 -0
  220. package/dist/stores/LambderMemoryUploadBucket.js +219 -0
  221. package/dist/stores/LambderS3FileSource.d.ts +21 -6
  222. package/dist/stores/LambderS3FileSource.js +12 -7
  223. package/dist/stores/LambderS3UploadBucket.d.ts +73 -0
  224. package/dist/stores/LambderS3UploadBucket.js +144 -0
  225. package/dist/stores/LambderSdkInstallHint.d.ts +11 -0
  226. package/dist/stores/LambderSdkInstallHint.js +14 -0
  227. package/dist/testing/LambderTestApp.d.ts +23 -25
  228. package/dist/testing/LambderTestApp.js +22 -24
  229. package/dist/testing/LambderTestVisitor.d.ts +10 -12
  230. package/dist/testing/LambderTestVisitor.js +15 -15
  231. package/dist/testing.d.ts +3 -0
  232. package/dist/testing.js +2 -0
  233. package/package.json +26 -3
  234. package/dist/api/LambderApiPolicyEngine.d.ts +0 -47
  235. package/dist/api/LambderApiPolicyEngine.js +0 -85
  236. package/dist/shared/util/LambderKeyFields.d.ts +0 -32
  237. package/dist/shared/util/LambderKeyFields.js +0 -34
@@ -3,7 +3,8 @@ import type { LambderRateLimitOptionValue, LambderRateLimitOverride } from "../s
3
3
  import type { z } from "zod";
4
4
  import type { LambderApiRequest } from "./LambderApiRequest.js";
5
5
  import type { LambderApiCallContext } from "./LambderApiCallContext.js";
6
- import { type LambderRateLimiter, type LambderRateLimitPolicy } from "../shared/contracts/LambderRateLimiter.js";
6
+ import { type LambderRateLimiter, type LambderRateLimitExceeded, type LambderRateLimitPolicy } from "../shared/contracts/LambderRateLimiter.js";
7
+ import type { LambderSessionRecord } from "../shared/contracts/LambderSessionStore.js";
7
8
  import { LambderApiRefusal, type LambderAppRefusalMessage } from "../shared/wire/LambderApiRefusal.js";
8
9
  import type { LambderNonEmptyOptionMap } from "../shared/util/LambderTypeUtilities.js";
9
10
  import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
@@ -23,22 +24,20 @@ export declare const DEFAULT_RATE_LIMIT_REFUSAL: {
23
24
  export declare const rateLimitRefusal: (detail: string, retryAfterSeconds: number, message?: LambderAppRefusalMessage) => LambderApiRefusal;
24
25
  /**
25
26
  * A custom rate-limit key. `apiInput` names the fields of the API's OWN
26
- * payload the key derives from: the slice is validated against the raw
27
+ * payload the key derives from: that slice is validated against the raw
27
28
  * payload before `handler` runs (failures answer like regular input
28
- * validation, through setApiInputValidationErrorHandler when set) and the
29
- * handler receives it typed. Referencing the policy from an API whose input
30
- * schema does not carry those fields is a compile error, so the API's schema
31
- * stays the single owner of the field. Build with lambderRateLimitKey() so
32
- * the handler's payload type follows `apiInput`. The context is the
33
- * adapter's (the render context on the server); the engine reads nothing
34
- * from it itself.
29
+ * validation, through setApiInputValidationErrorHandler when set) and reaches
30
+ * the handler typed. Referencing the policy from an API whose input schema
31
+ * lacks those fields is a compile error, so the API's schema stays the single
32
+ * owner of the field. Build with lambderRateLimitKey() so the handler's
33
+ * payload type follows `apiInput`. The context is the adapter's (the render
34
+ * context on the server); the engine itself reads nothing from it.
35
35
  *
36
- * ONE member, with `apiInput` optional, rather than a union of the two
37
- * shapes: a union with a function member in each arm defeats contextual
38
- * typing, so annotating a policies map with LambderRateLimitPer or
39
- * LambderApiRateLimitPolicyConfig left `ctx` implicitly any and the
40
- * annotation did not compile at all. The builder's overloads are where the
41
- * apiInput/payload correlation is kept.
36
+ * One member with `apiInput` optional, not a union of two shapes: a union
37
+ * with a function in each arm defeats contextual typing, so annotating a
38
+ * policies map with LambderRateLimitPer or LambderApiRateLimitPolicyConfig
39
+ * would leave `ctx` implicitly any and fail to compile. The builder's
40
+ * overloads keep the apiInput/payload correlation instead.
42
41
  */
43
42
  export type LambderRateLimitKeyFn<TInput extends z.ZodType = z.ZodType, TCtx = any> = {
44
43
  apiInput?: TInput;
@@ -71,11 +70,10 @@ export type LambderRateLimitKeyBuilder<TCtx> = {
71
70
  * exposes it as `rateLimitKey`.
72
71
  *
73
72
  * Bound rather than left open because the engine hands the handler whatever
74
- * context the adapter runs on, and the two adapters run on different ones. A
75
- * single builder pinned to the server's context type compiled against the
76
- * mock and then handed the handler a context with no `ip`, `method` or
77
- * `path`, so every caller collapsed onto one counter and the limit a test was
78
- * written to prove silently proved nothing.
73
+ * context the adapter runs on, and the two adapters differ. A builder pinned
74
+ * to the server's context type would compile against the mock, then receive
75
+ * a context with no `ip`, `method` or `path`: every caller would share one
76
+ * counter and a test of the limit would silently prove nothing.
79
77
  */
80
78
  export declare const lambderRateLimitKeyBuilder: <TCtx>() => LambderRateLimitKeyBuilder<TCtx>;
81
79
  /** What one rate-limit counter tracks: the client IP, the session identity, or a custom payload-derived key. */
@@ -93,19 +91,47 @@ export type LambderRateLimitPer<TCtx = any> = "ip" | "session" | LambderRateLimi
93
91
  * and report APIs separate shared budgets, declare two policies.
94
92
  */
95
93
  export type LambderRateLimitBudget = "perApi" | "perPolicy";
94
+ /**
95
+ * When a custom-keyed policy is charged, relative to the guards and the input
96
+ * schema.
97
+ *
98
+ * - "afterGuards" (default): after every guard and the input schema passed.
99
+ * The key is a value the caller chose (an email in the payload), so a
100
+ * caller who never passes a captcha guard cannot spend a victim's budget
101
+ * and lock them out of reset, register and send-code.
102
+ * - "beforeGuards": before the guards and the input schema, so an attempt
103
+ * they refuse is counted too. For a limit on guessing a secret a guard or
104
+ * the schema checks (a one-time code checked by a guard, keyed per email):
105
+ * charged after them, a wrong guess is refused before it is ever counted.
106
+ * Pair it with an IP limit, since anyone may spend this budget.
107
+ */
108
+ export type LambderRateLimitChargeAt = "beforeGuards" | "afterGuards";
96
109
  /**
97
110
  * A named rate-limit policy: fixed windows, the key one counter tracks, and
98
111
  * what one budget spans.
99
112
  *
100
113
  * Generic over the context a custom key handler receives, so the adapter's
101
114
  * policies map pins it: the server's is the render context, the mock's is the
102
- * mock call context. Left open, a handler written for one adapter compiled
103
- * against the other and then read fields that were not there.
115
+ * mock call context. Left open, a handler written for one adapter would
116
+ * compile against the other and read fields that are not there.
104
117
  */
105
118
  export type LambderApiRateLimitPolicyConfig<TCtx = any> = LambderRateLimitPolicy & {
106
- per: LambderRateLimitPer<TCtx>;
119
+ /**
120
+ * What one counter tracks when an API declares the policy. Left out, the
121
+ * policy is keyed by the code that charges it (`ctx.rateLimit(name, key)`
122
+ * in a handler), for a key only the handler knows, such as one recipient
123
+ * of an invitation; such a policy cannot be named in an API's `rateLimit`
124
+ * option, since the request alone does not say what to count.
125
+ */
126
+ per?: LambderRateLimitPer<TCtx>;
107
127
  /** Whether the windows are a per-API ceiling (default) or one budget shared by every referencing API. See LambderRateLimitBudget. */
108
128
  budget?: LambderRateLimitBudget;
129
+ /**
130
+ * When a policy keyed by a `{ apiInput?, handler }` key is charged. See
131
+ * LambderRateLimitChargeAt. Default: "afterGuards". Only such a policy
132
+ * takes it: `per: "ip"` and `per: "session"` have one place each.
133
+ */
134
+ chargeAt?: LambderRateLimitChargeAt;
109
135
  /** Envelope errorMessage for refused requests; inherits code "lambder/rate-limited" unless it sets its own. Default: a warning saying too many requests. */
110
136
  errorMessage?: LambderAppRefusalMessage;
111
137
  };
@@ -119,28 +145,101 @@ export type LambderApiRateLimitsConfig<TPolicies extends Record<string, LambderA
119
145
  * down, an IAM action is missing), instead of failing the request.
120
146
  * Default: true, and the failure is logged either way.
121
147
  *
122
- * It lives here rather than on a limiter implementation because it is a
123
- * decision about the REQUEST, not about a store: a custom limiter had no
124
- * fail-open at all, and two limiters could answer the same outage
125
- * differently. Set it to false on an app where an unmetered request is
126
- * worse than a refused one.
148
+ * It lives here rather than on a limiter because it is a decision about
149
+ * the REQUEST, not the store: on the limiter, every custom limiter would
150
+ * need its own, and two limiters could answer the same outage
151
+ * differently. Set it to false where an unmetered request is worse than
152
+ * a refused one.
127
153
  */
128
154
  failOpen?: boolean;
155
+ /**
156
+ * How much of an IPv6 address one `per: "ip"` counter covers. A
157
+ * subscriber, a VPS included, holds at least a /64 and may pick any
158
+ * address inside it, so counting full addresses gives anyone who rotates
159
+ * a fresh counter per request. Default: 64. A smaller number (48, 56)
160
+ * counts a whole allocation as one caller.
161
+ */
162
+ ipv6PrefixLength?: number;
129
163
  };
164
+ /**
165
+ * A policy's `per` as its type declares it: undefined for a policy declared
166
+ * without one, and a union holding undefined for a policy typed as the
167
+ * general LambderApiRateLimitPolicyConfig, where only registration can tell.
168
+ */
169
+ type LambderPolicyPerOf<TPolicy> = "per" extends keyof TPolicy ? TPolicy["per" & keyof TPolicy] : undefined;
130
170
  /**
131
171
  * Policy names an API may reference: session-keyed policies only on session
132
- * APIs, and apiInput-keyed policies only when the API's payload carries the
133
- * key's fields.
172
+ * APIs, apiInput-keyed policies only when the API's payload carries the
173
+ * key's fields, and never a policy without `per`, whose key only the code
174
+ * that charges it knows. A policy whose type does not settle its `per` is
175
+ * allowed here and checked at registration.
176
+ *
177
+ * The payload is compared whole, as the guards' check does it: a union input
178
+ * one of whose members lacks the key's fields does not carry them, and every
179
+ * request of that member would be refused by the key slice's parse.
134
180
  */
135
181
  export type LambderAllowedPolicyNames<TPolicies, TPayload, TIncludeSession extends boolean> = {
182
+ [K in keyof TPolicies]: [
183
+ LambderPolicyPerOf<TPolicies[K]>
184
+ ] extends [undefined] ? never : [LambderPolicyPerOf<TPolicies[K]>] extends ["session"] ? (TIncludeSession extends true ? K : never) : [LambderPolicyPerOf<TPolicies[K]>] extends [{
185
+ apiInput: infer S extends z.ZodType;
186
+ }] ? ([TPayload] extends [z.input<S>] ? K : never) : K;
187
+ }[keyof TPolicies] & string;
188
+ /**
189
+ * Policy names a handler may charge itself: every policy except one keyed by
190
+ * a `{ apiInput?, handler }` key, which derives its key from an API's payload
191
+ * and so is charged by the APIs that declare it.
192
+ */
193
+ export type LambderChargeablePolicyNames<TPolicies> = {
136
194
  [K in keyof TPolicies]: TPolicies[K] extends {
137
- per: "session";
138
- } ? (TIncludeSession extends true ? K : never) : TPolicies[K] extends {
139
- per: {
140
- apiInput: infer S extends z.ZodType;
141
- };
142
- } ? (TPayload extends z.output<S> ? K : never) : K;
195
+ per: LambderRateLimitKeyFn<any, any>;
196
+ } ? never : K;
143
197
  }[keyof TPolicies] & string;
198
+ /**
199
+ * The key argument charging a policy takes: none for `per: "ip"` and
200
+ * `per: "session"`, which the request supplies, and the key itself for a
201
+ * policy that declares no `per`. Optional where the types cannot tell: on a
202
+ * context that does not know the app's policies (a hook's, a guard's), and
203
+ * for a policy typed as the general LambderApiRateLimitPolicyConfig.
204
+ */
205
+ export type LambderChargeKeyArgs<TPolicies, K> = string extends K ? [key?: string] : string extends keyof TPolicies ? [key?: string] : K extends keyof TPolicies ? [LambderPolicyPerOf<TPolicies[K]>] extends [undefined] ? [key: string] : [LambderPolicyPerOf<TPolicies[K]>] extends ["ip" | "session"] ? [] : undefined extends LambderPolicyPerOf<TPolicies[K]> ? [key?: string] : [key: string] : never;
206
+ /**
207
+ * What `ctx.isRateLimited` answers: false when the attempt was allowed,
208
+ * otherwise the window that refused it, when that window resets, and the
209
+ * seconds until then.
210
+ */
211
+ export type LambderRateLimitCheckResult = false | (LambderRateLimitExceeded & {
212
+ retryAfterSeconds: number;
213
+ });
214
+ /**
215
+ * `ctx.rateLimit(policy, key?)`: counts one attempt against a named policy
216
+ * and, when it is over, refuses the request the way a declared limit does (a
217
+ * 429 with Retry-After and the policy's errorMessage). For a limit whose key
218
+ * only the handler knows, or one to charge only on some paths through it.
219
+ *
220
+ * The key tuple is NoInfer: left inferable, a key passed where none belongs
221
+ * would infer K as a string, which the constraint widens to every policy
222
+ * name, and the call would then accept the key it has to refuse.
223
+ */
224
+ export type LambderContextRateLimit<TPolicies> = <K extends LambderChargeablePolicyNames<TPolicies>>(policy: K, ...key: NoInfer<LambderChargeKeyArgs<TPolicies, K>>) => Promise<void>;
225
+ /**
226
+ * `ctx.isRateLimited(policy, key?)`: the same count, answered rather than
227
+ * thrown, for a handler that says "too many" in its own output shape.
228
+ */
229
+ export type LambderContextRateLimitCheck<TPolicies> = <K extends LambderChargeablePolicyNames<TPolicies>>(policy: K, ...key: NoInfer<LambderChargeKeyArgs<TPolicies, K>>) => Promise<LambderRateLimitCheckResult>;
230
+ /** Who is charging a policy from code: the API the call is (null on a route), and what a `per: "ip"` or `per: "session"` key reads. */
231
+ export type LambderRateLimitChargeSubject = {
232
+ apiName: string | null;
233
+ ip: string;
234
+ session: LambderSessionRecord<any> | null;
235
+ /** The key the code supplied; required for a policy without `per`, refused for any other. */
236
+ key: string | undefined;
237
+ };
238
+ /** What charging a policy from code came to: the check result, and the refusal to throw when it is over. */
239
+ export type LambderRateLimitChargeResult = {
240
+ checkResult: LambderRateLimitCheckResult;
241
+ refusal: LambderApiRefusal | null;
242
+ };
144
243
  type LambderRateLimitOverrideFor<TPolicy> = TPolicy extends {
145
244
  budget: "perPolicy";
146
245
  } ? Pick<LambderRateLimitOverride, "errorMessage"> : LambderRateLimitOverride;
@@ -154,34 +253,48 @@ type LambderRateLimitMap<TPolicies, TPayload, TIncludeSession extends boolean> =
154
253
  * (`true` applies the policy as declared). Map entries are checked in
155
254
  * insertion order.
156
255
  *
157
- * Every form is non-empty by construction, the same machinery the guards
158
- * option uses (LambderNonEmptyOptionMap): `rateLimit: {}`, `rateLimit: []`
159
- * and `rateLimit: { policy: undefined }` announce a limit and enforce none.
256
+ * Every form is non-empty by construction (LambderNonEmptyOptionMap, as the
257
+ * guards option uses), since `rateLimit: {}`, `rateLimit: []` and
258
+ * `rateLimit: { policy: undefined }` would announce a limit and enforce none.
160
259
  */
161
260
  export type LambderRateLimitOption<TPolicies, TPayload, TIncludeSession extends boolean> = LambderAllowedPolicyNames<TPolicies, TPayload, TIncludeSession> | readonly [
162
261
  LambderAllowedPolicyNames<TPolicies, TPayload, TIncludeSession>,
163
262
  ...LambderAllowedPolicyNames<TPolicies, TPayload, TIncludeSession>[]
164
263
  ] | LambderNonEmptyOptionMap<LambderRateLimitMap<TPolicies, TPayload, TIncludeSession>>;
165
264
  /**
166
- * When in a call a policy can be checked. A `per: "ip"` counter is known from
167
- * the request alone, so it runs before the session read and bounds how often
168
- * one address may make the session store look a token up. Everything else
169
- * runs after: `per: "session"` needs the session, and a custom key handler is
170
- * app code that may read ctx.session too.
265
+ * When in a call a policy is checked.
266
+ *
267
+ * - `per: "ip"` is known from the request alone, so it runs before the
268
+ * session read and bounds how often one address may make the session store
269
+ * look a token up.
270
+ * - `per: "session"` needs the session, so it runs after the read, and
271
+ * before the guards, which it protects the same way.
272
+ * - A custom key runs where its policy's `chargeAt` puts it (see
273
+ * LambderRateLimitChargeAt): after the guards by default, or with the
274
+ * session-keyed limits, before the guards, for "beforeGuards".
171
275
  */
172
- type LambderRateLimitPhase = "beforeSession" | "afterSession";
276
+ type LambderRateLimitPhase = "beforeSession" | LambderRateLimitChargeAt;
173
277
  /**
174
278
  * Runtime side of the rate-limit subsystem: holds the limiter and its named
175
279
  * policies, asserts API registrations against them at startup, and checks an
176
280
  * API's declared policies during preflight. Composed into
177
- * LambderApiPolicyEngine. Reads the request's ip and the context's session
178
- * and nothing else, so it runs unchanged under the server and the mock
179
- * runtime.
281
+ * LambderApiPipeline. Reads the request (its ip, and a key's payload
282
+ * slice) and hands the context to a custom key's handler, reading nothing
283
+ * of the context itself but its session, so it runs unchanged under the
284
+ * server and the mock runtime.
180
285
  */
181
286
  export declare class LambderApiRateLimitsEngine {
182
287
  private limiter;
183
288
  private failOpen;
289
+ private ipv6PrefixLength;
184
290
  private policies;
291
+ /**
292
+ * The limiter failures already logged. A limiter answering a run of
293
+ * requests with one continuing failure throws the same error for each
294
+ * (see LambderRateLimiter), and a flood of a few thousand requests a
295
+ * second would otherwise be as many identical log lines.
296
+ */
297
+ private readonly loggedFailures;
185
298
  /** True once rateLimits were configured. */
186
299
  get isConfigured(): boolean;
187
300
  configure(config: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>): void;
@@ -200,14 +313,48 @@ export declare class LambderApiRateLimitsEngine {
200
313
  * counter, when a later guard or validation refuses) keeps its increment,
201
314
  * so list first the policy you want charged on refusals.
202
315
  *
203
- * Run twice per call, once per phase: the policies whose key needs no
204
- * session are checked BEFORE the session is read, so a flood of requests
205
- * carrying bogus session cookies is refused without touching the session
206
- * store; the rest are checked after it, since `per: "session"` and a
207
- * custom key handler may both read ctx.session. Declared order is kept
208
- * inside each phase.
316
+ * Runs once per phase (see LambderRateLimitPhase), keeping declared order
317
+ * within each: `per: "ip"` before the session read, so a flood of bogus
318
+ * session cookies never reaches the session store; `per: "session"` after
319
+ * it; a custom key after the guards unless its policy's `chargeAt` says
320
+ * "beforeGuards", so a caller they refuse cannot spend somebody else's
321
+ * budget.
209
322
  */
210
323
  run(apiName: string, request: LambderApiRequest, ctx: LambderApiCallContext, rateLimitOption: LambderRateLimitOptionValue | undefined, phase: LambderRateLimitPhase): Promise<void>;
324
+ /**
325
+ * One policy charged by code rather than by a declaration, for
326
+ * `ctx.rateLimit` and `ctx.isRateLimited`. Counters, failOpen, key
327
+ * bounding and refusal are those of a declared limit; only who knows the
328
+ * key differs. A policy without `per` takes the key the code passes, a
329
+ * `per: "ip"` or `per: "session"` one reads it off the request, and one
330
+ * keyed by an API payload is refused, since only its APIs know the key.
331
+ */
332
+ chargePolicy(name: string, subject: LambderRateLimitChargeSubject): Promise<LambderRateLimitChargeResult>;
333
+ /**
334
+ * Seconds until the refusing window resets, read against the limiter's
335
+ * own clock when it keeps one: `resetAt` is a second on that clock, and a
336
+ * limiter under a test clock would otherwise be told a Retry-After
337
+ * measured from another time entirely.
338
+ */
339
+ private retryAfterOf;
340
+ /**
341
+ * Counts one attempt, or lets it through when the limiter itself fails
342
+ * and failOpen is on. The log line names the policy and its windows,
343
+ * never the tracker key: the key carries whatever a custom handler
344
+ * returned, which the docs' own example makes an email address. A
345
+ * failure the limiter throws again (see loggedFailures) is not logged
346
+ * again.
347
+ */
348
+ private countAttempt;
349
+ private chargeKeyOf;
211
350
  private resolveKey;
351
+ /**
352
+ * The key a `per: "ip"` or `per: "session"` policy counts under: what the
353
+ * request carries, however the policy is charged. A session key is
354
+ * bounded like a custom one (see boundKeyField); an address needs no
355
+ * bound, since normalizeClientIp caps it at 45 characters whichever
356
+ * header or gateway field named it.
357
+ */
358
+ private requestKeyOf;
212
359
  }
213
360
  export {};