lambder 7.2.5 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +1021 -3
  2. package/README.md +43 -21
  3. package/dist/api/LambderApiAnswer.d.ts +18 -22
  4. package/dist/api/LambderApiAnswer.js +6 -7
  5. package/dist/api/LambderApiCallContext.d.ts +21 -8
  6. package/dist/api/LambderApiCallContext.js +22 -4
  7. package/dist/api/LambderApiDefinition.d.ts +4 -3
  8. package/dist/api/LambderApiEnvelope.d.ts +14 -9
  9. package/dist/api/LambderApiEnvelope.js +33 -34
  10. package/dist/api/LambderApiGuards.d.ts +78 -51
  11. package/dist/api/LambderApiGuards.js +34 -36
  12. package/dist/api/LambderApiIdempotency.d.ts +74 -61
  13. package/dist/api/LambderApiIdempotency.js +226 -151
  14. package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
  15. package/dist/api/LambderApiOutputValidationError.js +50 -0
  16. package/dist/api/LambderApiPipeline.d.ts +77 -39
  17. package/dist/api/LambderApiPipeline.js +135 -62
  18. package/dist/api/LambderApiRateLimits.d.ts +208 -54
  19. package/dist/api/LambderApiRateLimits.js +197 -108
  20. package/dist/api/LambderApiRequest.d.ts +27 -21
  21. package/dist/api/LambderApiRequest.js +26 -19
  22. package/dist/api/LambderApiSignature.d.ts +12 -15
  23. package/dist/api/LambderApiSignature.js +28 -51
  24. package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
  25. package/dist/api/LambderApiValidationRefusal.js +10 -10
  26. package/dist/build/freshProcessVerifier.d.ts +13 -0
  27. package/dist/build/freshProcessVerifier.js +19 -0
  28. package/dist/build/writeApiSignatures.d.ts +109 -0
  29. package/dist/build/writeApiSignatures.js +222 -0
  30. package/dist/build.d.ts +9 -0
  31. package/dist/build.js +8 -0
  32. package/dist/client/LambderCaller.d.ts +13 -44
  33. package/dist/client/LambderCaller.js +77 -84
  34. package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
  35. package/dist/client/LambderReloadLoopBreaker.js +90 -46
  36. package/dist/client/lambderFetchTransport.d.ts +4 -1
  37. package/dist/client/lambderFetchTransport.js +52 -28
  38. package/dist/client.d.ts +5 -3
  39. package/dist/client.js +2 -1
  40. package/dist/core/Lambder.d.ts +161 -69
  41. package/dist/core/Lambder.js +370 -226
  42. package/dist/core/LambderContext.d.ts +82 -15
  43. package/dist/core/LambderContext.js +107 -20
  44. package/dist/core/LambderCors.d.ts +21 -3
  45. package/dist/core/LambderCors.js +35 -16
  46. package/dist/core/LambderCrashHandling.d.ts +40 -0
  47. package/dist/core/LambderCrashHandling.js +97 -0
  48. package/dist/core/LambderCreateOptions.d.ts +151 -75
  49. package/dist/core/LambderCreateOptions.js +16 -23
  50. package/dist/core/LambderFiles.d.ts +28 -7
  51. package/dist/core/LambderFiles.js +73 -33
  52. package/dist/core/LambderIndexHtml.js +12 -11
  53. package/dist/core/LambderPolicyBuilders.d.ts +17 -5
  54. package/dist/core/LambderPolicyBuilders.js +17 -5
  55. package/dist/core/LambderPublicFiles.d.ts +11 -5
  56. package/dist/core/LambderPublicFiles.js +32 -4
  57. package/dist/core/LambderRequestPath.d.ts +43 -0
  58. package/dist/core/LambderRequestPath.js +63 -0
  59. package/dist/core/LambderResponse.d.ts +26 -5
  60. package/dist/core/LambderResponse.js +157 -70
  61. package/dist/core/LambderResponseBuilder.d.ts +49 -4
  62. package/dist/core/LambderResponseBuilder.js +64 -3
  63. package/dist/core/LambderRouting.d.ts +2 -3
  64. package/dist/core/LambderRouting.js +22 -7
  65. package/dist/core/LambderTemplatingEngine.js +211 -32
  66. package/dist/index.d.ts +15 -8
  67. package/dist/index.js +5 -4
  68. package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
  69. package/dist/invoke/LambderInvokeCaller.js +76 -66
  70. package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
  71. package/dist/invoke/LambderInvokeOutcome.js +9 -22
  72. package/dist/invoke/LambderLambdaEvent.d.ts +44 -10
  73. package/dist/invoke/LambderLambdaEvent.js +80 -37
  74. package/dist/invoke/lambderHandlerTransport.d.ts +12 -10
  75. package/dist/invoke/lambderHandlerTransport.js +16 -19
  76. package/dist/mock/LambderMockApp.d.ts +67 -83
  77. package/dist/mock/LambderMockApp.js +167 -153
  78. package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
  79. package/dist/mock/LambderMockBrowserCookies.js +24 -28
  80. package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
  81. package/dist/mock/LambderMockCallRecorder.js +19 -28
  82. package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
  83. package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
  84. package/dist/mock/LambderMockEntryRegistry.js +24 -29
  85. package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
  86. package/dist/mock/LambderMockFailureInjector.js +3 -6
  87. package/dist/mock/LambderMockTypes.d.ts +78 -108
  88. package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
  89. package/dist/mock/lambderMockInvokeTransport.js +11 -10
  90. package/dist/mock/lambderMockMswHandler.d.ts +33 -29
  91. package/dist/mock/lambderMockMswHandler.js +50 -39
  92. package/dist/mock.d.ts +3 -1
  93. package/dist/mock.js +5 -3
  94. package/dist/session/LambderSessionController.d.ts +108 -89
  95. package/dist/session/LambderSessionController.js +187 -168
  96. package/dist/session/LambderSessionCrypto.d.ts +16 -7
  97. package/dist/session/LambderSessionCrypto.js +26 -12
  98. package/dist/session/LambderSessionManager.d.ts +136 -47
  99. package/dist/session/LambderSessionManager.js +280 -139
  100. package/dist/shared/LambderHtml.d.ts +42 -3
  101. package/dist/shared/LambderHtml.js +127 -7
  102. package/dist/shared/LambderHtmlPositions.d.ts +173 -0
  103. package/dist/shared/LambderHtmlPositions.js +652 -0
  104. package/dist/shared/LambderI18n.d.ts +10 -11
  105. package/dist/shared/LambderI18n.js +33 -21
  106. package/dist/shared/contracts/LambderCache.d.ts +66 -0
  107. package/dist/shared/contracts/LambderCache.js +11 -0
  108. package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
  109. package/dist/shared/contracts/LambderFileSource.js +5 -8
  110. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
  111. package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
  112. package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
  113. package/dist/shared/contracts/LambderRateLimiter.js +4 -5
  114. package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
  115. package/dist/shared/contracts/LambderSessionStore.js +5 -6
  116. package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
  117. package/dist/shared/transport/LambderApiTransport.js +7 -7
  118. package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
  119. package/dist/shared/transport/LambderCookieJar.js +54 -66
  120. package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
  121. package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
  122. package/dist/shared/util/LambderCallAbort.d.ts +5 -5
  123. package/dist/shared/util/LambderCallAbort.js +5 -5
  124. package/dist/shared/util/LambderClientIp.d.ts +27 -11
  125. package/dist/shared/util/LambderClientIp.js +96 -13
  126. package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
  127. package/dist/shared/util/LambderExpiringMap.js +41 -57
  128. package/dist/shared/util/LambderNodeModules.js +6 -7
  129. package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
  130. package/dist/shared/util/LambderOptionChecks.js +4 -4
  131. package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
  132. package/dist/shared/util/LambderResponseBrand.js +5 -5
  133. package/dist/shared/util/LambderTestingDoors.d.ts +29 -0
  134. package/dist/shared/util/LambderTestingDoors.js +29 -0
  135. package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
  136. package/dist/shared/util/LambderTypeUtilities.js +3 -3
  137. package/dist/shared/util/boundKeyField.d.ts +20 -0
  138. package/dist/shared/util/boundKeyField.js +34 -0
  139. package/dist/shared/util/canonicalJson.d.ts +11 -0
  140. package/dist/shared/util/canonicalJson.js +28 -0
  141. package/dist/shared/util/joinKeyFields.d.ts +20 -0
  142. package/dist/shared/util/joinKeyFields.js +22 -0
  143. package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
  144. package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
  145. package/dist/shared/wire/LambderApiContract.d.ts +107 -32
  146. package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
  147. package/dist/shared/wire/LambderApiOutcome.js +48 -23
  148. package/dist/shared/wire/LambderApiRefusal.d.ts +39 -27
  149. package/dist/shared/wire/LambderApiRefusal.js +36 -7
  150. package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
  151. package/dist/shared/wire/LambderApiSignature.js +16 -19
  152. package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
  153. package/dist/shared/wire/LambderCallOptions.js +9 -11
  154. package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
  155. package/dist/shared/wire/LambderCompressionCodec.js +31 -36
  156. package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
  157. package/dist/shared/wire/LambderCompressionOption.js +9 -9
  158. package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
  159. package/dist/shared/wire/LambderCrashDetail.js +12 -15
  160. package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
  161. package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
  162. package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
  163. package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
  164. package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
  165. package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
  166. package/dist/shared/wire/LambderInvokeApiId.js +27 -0
  167. package/dist/shared/wire/LambderOutcomeAssertions.d.ts +79 -0
  168. package/dist/shared/wire/LambderOutcomeAssertions.js +112 -0
  169. package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
  170. package/dist/shared/wire/LambderRequestPayload.js +4 -6
  171. package/dist/stores/LambderCacheFiller.d.ts +48 -0
  172. package/dist/stores/LambderCacheFiller.js +119 -0
  173. package/dist/stores/LambderCacheKeys.d.ts +26 -0
  174. package/dist/stores/LambderCacheKeys.js +54 -0
  175. package/dist/stores/LambderCacheValues.d.ts +45 -0
  176. package/dist/stores/LambderCacheValues.js +74 -0
  177. package/dist/stores/LambderDdbCache.d.ts +121 -56
  178. package/dist/stores/LambderDdbCache.js +528 -225
  179. package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
  180. package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
  181. package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
  182. package/dist/stores/LambderDdbRateLimiter.js +151 -39
  183. package/dist/stores/LambderDdbSdk.d.ts +43 -31
  184. package/dist/stores/LambderDdbSdk.js +79 -33
  185. package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
  186. package/dist/stores/LambderDdbSessionStore.js +119 -47
  187. package/dist/stores/LambderHttpFileSource.d.ts +15 -6
  188. package/dist/stores/LambderHttpFileSource.js +15 -13
  189. package/dist/stores/LambderMemoryCache.d.ts +49 -0
  190. package/dist/stores/LambderMemoryCache.js +113 -0
  191. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
  192. package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
  193. package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
  194. package/dist/stores/LambderMemoryRateLimiter.js +14 -13
  195. package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
  196. package/dist/stores/LambderMemorySessionStore.js +38 -19
  197. package/dist/stores/LambderS3FileSource.d.ts +21 -6
  198. package/dist/stores/LambderS3FileSource.js +12 -7
  199. package/dist/testing/LambderTestApp.d.ts +176 -0
  200. package/dist/testing/LambderTestApp.js +204 -0
  201. package/dist/testing/LambderTestVisitor.d.ts +153 -0
  202. package/dist/testing/LambderTestVisitor.js +154 -0
  203. package/dist/testing.d.ts +27 -0
  204. package/dist/testing.js +24 -0
  205. package/package.json +20 -3
  206. package/dist/api/LambderApiPolicyEngine.d.ts +0 -36
  207. package/dist/api/LambderApiPolicyEngine.js +0 -77
  208. package/dist/shared/util/LambderKeyFields.d.ts +0 -32
  209. package/dist/shared/util/LambderKeyFields.js +0 -34
@@ -3,9 +3,11 @@ 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";
10
+ import { LAMBDER_BACKEND_SWAP } from "../shared/util/LambderTestingDoors.js";
9
11
  /** Refusal a rate-limited request answers unless the policy or the API's override names its own. */
10
12
  export declare const DEFAULT_RATE_LIMIT_REFUSAL: {
11
13
  type: "warning";
@@ -22,22 +24,20 @@ export declare const DEFAULT_RATE_LIMIT_REFUSAL: {
22
24
  export declare const rateLimitRefusal: (detail: string, retryAfterSeconds: number, message?: LambderAppRefusalMessage) => LambderApiRefusal;
23
25
  /**
24
26
  * A custom rate-limit key. `apiInput` names the fields of the API's OWN
25
- * 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
26
28
  * payload before `handler` runs (failures answer like regular input
27
- * validation, through setApiInputValidationErrorHandler when set) and the
28
- * handler receives it typed. Referencing the policy from an API whose input
29
- * schema does not carry those fields is a compile error, so the API's schema
30
- * stays the single owner of the field. Build with lambderRateLimitKey() so
31
- * the handler's payload type follows `apiInput`. The context is the
32
- * adapter's (the render context on the server); the engine reads nothing
33
- * 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.
34
35
  *
35
- * ONE member, with `apiInput` optional, rather than a union of the two
36
- * shapes: a union with a function member in each arm defeats contextual
37
- * typing, so annotating a policies map with LambderRateLimitPer or
38
- * LambderApiRateLimitPolicyConfig left `ctx` implicitly any and the
39
- * annotation did not compile at all. The builder's overloads are where the
40
- * 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.
41
41
  */
42
42
  export type LambderRateLimitKeyFn<TInput extends z.ZodType = z.ZodType, TCtx = any> = {
43
43
  apiInput?: TInput;
@@ -70,11 +70,10 @@ export type LambderRateLimitKeyBuilder<TCtx> = {
70
70
  * exposes it as `rateLimitKey`.
71
71
  *
72
72
  * Bound rather than left open because the engine hands the handler whatever
73
- * context the adapter runs on, and the two adapters run on different ones. A
74
- * single builder pinned to the server's context type compiled against the
75
- * mock and then handed the handler a context with no `ip`, `method` or
76
- * `path`, so every caller collapsed onto one counter and the limit a test was
77
- * 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.
78
77
  */
79
78
  export declare const lambderRateLimitKeyBuilder: <TCtx>() => LambderRateLimitKeyBuilder<TCtx>;
80
79
  /** What one rate-limit counter tracks: the client IP, the session identity, or a custom payload-derived key. */
@@ -92,19 +91,47 @@ export type LambderRateLimitPer<TCtx = any> = "ip" | "session" | LambderRateLimi
92
91
  * and report APIs separate shared budgets, declare two policies.
93
92
  */
94
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";
95
109
  /**
96
110
  * A named rate-limit policy: fixed windows, the key one counter tracks, and
97
111
  * what one budget spans.
98
112
  *
99
113
  * Generic over the context a custom key handler receives, so the adapter's
100
114
  * policies map pins it: the server's is the render context, the mock's is the
101
- * mock call context. Left open, a handler written for one adapter compiled
102
- * 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.
103
117
  */
104
118
  export type LambderApiRateLimitPolicyConfig<TCtx = any> = LambderRateLimitPolicy & {
105
- 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>;
106
127
  /** Whether the windows are a per-API ceiling (default) or one budget shared by every referencing API. See LambderRateLimitBudget. */
107
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;
108
135
  /** Envelope errorMessage for refused requests; inherits code "lambder/rate-limited" unless it sets its own. Default: a warning saying too many requests. */
109
136
  errorMessage?: LambderAppRefusalMessage;
110
137
  };
@@ -118,28 +145,101 @@ export type LambderApiRateLimitsConfig<TPolicies extends Record<string, LambderA
118
145
  * down, an IAM action is missing), instead of failing the request.
119
146
  * Default: true, and the failure is logged either way.
120
147
  *
121
- * It lives here rather than on a limiter implementation because it is a
122
- * decision about the REQUEST, not about a store: a custom limiter had no
123
- * fail-open at all, and two limiters could answer the same outage
124
- * differently. Set it to false on an app where an unmetered request is
125
- * 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.
126
153
  */
127
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;
128
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;
129
170
  /**
130
171
  * Policy names an API may reference: session-keyed policies only on session
131
- * APIs, and apiInput-keyed policies only when the API's payload carries the
132
- * 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.
133
180
  */
134
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> = {
135
194
  [K in keyof TPolicies]: TPolicies[K] extends {
136
- per: "session";
137
- } ? (TIncludeSession extends true ? K : never) : TPolicies[K] extends {
138
- per: {
139
- apiInput: infer S extends z.ZodType;
140
- };
141
- } ? (TPayload extends z.output<S> ? K : never) : K;
195
+ per: LambderRateLimitKeyFn<any, any>;
196
+ } ? never : K;
142
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
+ };
143
243
  type LambderRateLimitOverrideFor<TPolicy> = TPolicy extends {
144
244
  budget: "perPolicy";
145
245
  } ? Pick<LambderRateLimitOverride, "errorMessage"> : LambderRateLimitOverride;
@@ -153,37 +253,57 @@ type LambderRateLimitMap<TPolicies, TPayload, TIncludeSession extends boolean> =
153
253
  * (`true` applies the policy as declared). Map entries are checked in
154
254
  * insertion order.
155
255
  *
156
- * Every form is non-empty by construction, the same machinery the guards
157
- * option uses (LambderNonEmptyOptionMap): `rateLimit: {}`, `rateLimit: []`
158
- * 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.
159
259
  */
160
260
  export type LambderRateLimitOption<TPolicies, TPayload, TIncludeSession extends boolean> = LambderAllowedPolicyNames<TPolicies, TPayload, TIncludeSession> | readonly [
161
261
  LambderAllowedPolicyNames<TPolicies, TPayload, TIncludeSession>,
162
262
  ...LambderAllowedPolicyNames<TPolicies, TPayload, TIncludeSession>[]
163
263
  ] | LambderNonEmptyOptionMap<LambderRateLimitMap<TPolicies, TPayload, TIncludeSession>>;
164
264
  /**
165
- * When in a call a policy can be checked. A `per: "ip"` counter is known from
166
- * the request alone, so it runs before the session read and bounds how often
167
- * one address may make the session store look a token up. Everything else
168
- * runs after: `per: "session"` needs the session, and a custom key handler is
169
- * 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".
170
275
  */
171
- type LambderRateLimitPhase = "beforeSession" | "afterSession";
276
+ type LambderRateLimitPhase = "beforeSession" | LambderRateLimitChargeAt;
172
277
  /**
173
278
  * Runtime side of the rate-limit subsystem: holds the limiter and its named
174
279
  * policies, asserts API registrations against them at startup, and checks an
175
280
  * API's declared policies during preflight. Composed into
176
- * LambderApiPolicyEngine. Reads the request's ip and the context's session
177
- * and nothing else, so it runs unchanged under the server and the mock
178
- * 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.
179
285
  */
180
286
  export declare class LambderApiRateLimitsEngine {
181
287
  private limiter;
182
288
  private failOpen;
289
+ private ipv6PrefixLength;
183
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;
184
298
  /** True once rateLimits were configured. */
185
299
  get isConfigured(): boolean;
186
300
  configure(config: LambderApiRateLimitsConfig<Record<string, LambderApiRateLimitPolicyConfig>>): void;
301
+ /**
302
+ * Puts the engine over another limiter, for `lambder/testing`; the named
303
+ * policies and failOpen stay as configured. False when rateLimits were
304
+ * never configured: there is nothing for a limiter to sit under.
305
+ */
306
+ [LAMBDER_BACKEND_SWAP](limiter: LambderRateLimiter): boolean;
187
307
  /** Startup validation of one API registration's rateLimit option. */
188
308
  assertRegistration(apiName: string, mode: LambderApiMode, rateLimitOption?: LambderRateLimitOptionValue): void;
189
309
  /**
@@ -193,14 +313,48 @@ export declare class LambderApiRateLimitsEngine {
193
313
  * counter, when a later guard or validation refuses) keeps its increment,
194
314
  * so list first the policy you want charged on refusals.
195
315
  *
196
- * Run twice per call, once per phase: the policies whose key needs no
197
- * session are checked BEFORE the session is read, so a flood of requests
198
- * carrying bogus session cookies is refused without touching the session
199
- * store; the rest are checked after it, since `per: "session"` and a
200
- * custom key handler may both read ctx.session. Declared order is kept
201
- * 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.
202
322
  */
203
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;
204
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;
205
359
  }
206
360
  export {};