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.
- package/CHANGELOG.md +1047 -3
- package/README.md +46 -21
- package/dist/api/LambderApiAnswer.d.ts +18 -22
- package/dist/api/LambderApiAnswer.js +6 -7
- package/dist/api/LambderApiCallContext.d.ts +21 -8
- package/dist/api/LambderApiCallContext.js +22 -4
- package/dist/api/LambderApiDefinition.d.ts +4 -3
- package/dist/api/LambderApiEnvelope.d.ts +14 -9
- package/dist/api/LambderApiEnvelope.js +33 -34
- package/dist/api/LambderApiGuards.d.ts +78 -51
- package/dist/api/LambderApiGuards.js +34 -36
- package/dist/api/LambderApiIdempotency.d.ts +68 -62
- package/dist/api/LambderApiIdempotency.js +214 -151
- package/dist/api/LambderApiOutputValidationError.d.ts +32 -0
- package/dist/api/LambderApiOutputValidationError.js +50 -0
- package/dist/api/LambderApiPipeline.d.ts +47 -38
- package/dist/api/LambderApiPipeline.js +122 -63
- package/dist/api/LambderApiRateLimits.d.ts +201 -54
- package/dist/api/LambderApiRateLimits.js +185 -108
- package/dist/api/LambderApiRequest.d.ts +27 -21
- package/dist/api/LambderApiRequest.js +26 -19
- package/dist/api/LambderApiSignature.d.ts +12 -15
- package/dist/api/LambderApiSignature.js +28 -51
- package/dist/api/LambderApiValidationRefusal.d.ts +9 -9
- package/dist/api/LambderApiValidationRefusal.js +10 -10
- package/dist/build/ContractTypePrinter.d.ts +85 -0
- package/dist/build/ContractTypePrinter.js +402 -0
- package/dist/build/freshProcessVerifier.d.ts +13 -0
- package/dist/build/freshProcessVerifier.js +19 -0
- package/dist/build/moduleLocation.d.ts +11 -0
- package/dist/build/moduleLocation.js +6 -0
- package/dist/build/writeApiContract.d.ts +78 -0
- package/dist/build/writeApiContract.js +302 -0
- package/dist/build/writeApiSignatures.d.ts +114 -0
- package/dist/build/writeApiSignatures.js +217 -0
- package/dist/build/writeFileAtomically.d.ts +8 -0
- package/dist/build/writeFileAtomically.js +22 -0
- package/dist/build.d.ts +14 -0
- package/dist/build.js +11 -0
- package/dist/client/LambderCaller.d.ts +13 -44
- package/dist/client/LambderCaller.js +77 -84
- package/dist/client/LambderReloadLoopBreaker.d.ts +56 -26
- package/dist/client/LambderReloadLoopBreaker.js +90 -46
- package/dist/client/LambderUploadRunner.d.ts +96 -0
- package/dist/client/LambderUploadRunner.js +234 -0
- package/dist/client/lambderFetchTransport.d.ts +4 -1
- package/dist/client/lambderFetchTransport.js +52 -28
- package/dist/client.d.ts +9 -3
- package/dist/client.js +6 -1
- package/dist/core/Lambder.d.ts +143 -79
- package/dist/core/Lambder.js +350 -231
- package/dist/core/LambderContext.d.ts +82 -15
- package/dist/core/LambderContext.js +107 -20
- package/dist/core/LambderCors.d.ts +21 -3
- package/dist/core/LambderCors.js +35 -16
- package/dist/core/LambderCrashHandling.d.ts +40 -0
- package/dist/core/LambderCrashHandling.js +97 -0
- package/dist/core/LambderCreateOptions.d.ts +151 -75
- package/dist/core/LambderCreateOptions.js +16 -23
- package/dist/core/LambderFiles.d.ts +21 -7
- package/dist/core/LambderFiles.js +62 -34
- package/dist/core/LambderIndexHtml.js +12 -11
- package/dist/core/LambderPolicyBuilders.d.ts +17 -5
- package/dist/core/LambderPolicyBuilders.js +17 -5
- package/dist/core/LambderPublicFiles.d.ts +11 -5
- package/dist/core/LambderPublicFiles.js +32 -4
- package/dist/core/LambderRequestPath.d.ts +43 -0
- package/dist/core/LambderRequestPath.js +63 -0
- package/dist/core/LambderResponse.d.ts +26 -5
- package/dist/core/LambderResponse.js +157 -70
- package/dist/core/LambderResponseBuilder.d.ts +49 -4
- package/dist/core/LambderResponseBuilder.js +64 -3
- package/dist/core/LambderRouting.d.ts +2 -3
- package/dist/core/LambderRouting.js +22 -7
- package/dist/core/LambderTemplatingEngine.js +211 -32
- package/dist/index.d.ts +25 -8
- package/dist/index.js +13 -4
- package/dist/invoke/LambderInvokeCaller.d.ts +37 -42
- package/dist/invoke/LambderInvokeCaller.js +76 -66
- package/dist/invoke/LambderInvokeOutcome.d.ts +27 -26
- package/dist/invoke/LambderInvokeOutcome.js +9 -22
- package/dist/invoke/LambderLambdaEvent.d.ts +29 -9
- package/dist/invoke/LambderLambdaEvent.js +40 -22
- package/dist/invoke/lambderHandlerTransport.d.ts +9 -10
- package/dist/invoke/lambderHandlerTransport.js +15 -18
- package/dist/mock/LambderMockApp.d.ts +67 -83
- package/dist/mock/LambderMockApp.js +167 -153
- package/dist/mock/LambderMockBrowserCookies.d.ts +24 -28
- package/dist/mock/LambderMockBrowserCookies.js +24 -28
- package/dist/mock/LambderMockCallRecorder.d.ts +15 -22
- package/dist/mock/LambderMockCallRecorder.js +19 -28
- package/dist/mock/LambderMockCreateOptions.d.ts +42 -24
- package/dist/mock/LambderMockEntryRegistry.d.ts +11 -12
- package/dist/mock/LambderMockEntryRegistry.js +24 -29
- package/dist/mock/LambderMockFailureInjector.d.ts +3 -6
- package/dist/mock/LambderMockFailureInjector.js +3 -6
- package/dist/mock/LambderMockTypes.d.ts +78 -108
- package/dist/mock/lambderMockInvokeTransport.d.ts +11 -13
- package/dist/mock/lambderMockInvokeTransport.js +11 -10
- package/dist/mock/lambderMockMswHandler.d.ts +43 -33
- package/dist/mock/lambderMockMswHandler.js +50 -39
- package/dist/mock/lambderMockUploadMswHandler.d.ts +26 -0
- package/dist/mock/lambderMockUploadMswHandler.js +28 -0
- package/dist/mock.d.ts +4 -1
- package/dist/mock.js +6 -3
- package/dist/session/LambderSessionController.d.ts +108 -89
- package/dist/session/LambderSessionController.js +187 -168
- package/dist/session/LambderSessionCrypto.d.ts +16 -7
- package/dist/session/LambderSessionCrypto.js +26 -12
- package/dist/session/LambderSessionManager.d.ts +124 -46
- package/dist/session/LambderSessionManager.js +262 -137
- package/dist/shared/LambderHtml.d.ts +42 -3
- package/dist/shared/LambderHtml.js +127 -7
- package/dist/shared/LambderHtmlPositions.d.ts +173 -0
- package/dist/shared/LambderHtmlPositions.js +652 -0
- package/dist/shared/LambderI18n.d.ts +10 -11
- package/dist/shared/LambderI18n.js +33 -21
- package/dist/shared/contracts/LambderCache.d.ts +66 -0
- package/dist/shared/contracts/LambderCache.js +11 -0
- package/dist/shared/contracts/LambderFileSource.d.ts +6 -6
- package/dist/shared/contracts/LambderFileSource.js +5 -8
- package/dist/shared/contracts/LambderIdempotencyStore.d.ts +51 -22
- package/dist/shared/contracts/LambderIdempotencyStore.js +4 -5
- package/dist/shared/contracts/LambderRateLimiter.d.ts +27 -15
- package/dist/shared/contracts/LambderRateLimiter.js +4 -5
- package/dist/shared/contracts/LambderSessionStore.d.ts +65 -26
- package/dist/shared/contracts/LambderSessionStore.js +5 -6
- package/dist/shared/contracts/LambderUploadBucket.d.ts +154 -0
- package/dist/shared/contracts/LambderUploadBucket.js +74 -0
- package/dist/shared/transport/LambderApiTransport.d.ts +27 -27
- package/dist/shared/transport/LambderApiTransport.js +7 -7
- package/dist/shared/transport/LambderCookieJar.d.ts +28 -35
- package/dist/shared/transport/LambderCookieJar.js +54 -66
- package/dist/shared/transport/lambderCookieJarTransport.d.ts +11 -13
- package/dist/shared/transport/lambderCookieJarTransport.js +24 -23
- package/dist/shared/util/LambderCallAbort.d.ts +5 -5
- package/dist/shared/util/LambderCallAbort.js +5 -5
- package/dist/shared/util/LambderClientIp.d.ts +27 -11
- package/dist/shared/util/LambderClientIp.js +96 -13
- package/dist/shared/util/LambderContentDisposition.d.ts +10 -0
- package/dist/shared/util/LambderContentDisposition.js +13 -0
- package/dist/shared/util/LambderExpiringMap.d.ts +35 -49
- package/dist/shared/util/LambderExpiringMap.js +41 -57
- package/dist/shared/util/LambderNodeModules.js +6 -7
- package/dist/shared/util/LambderOptionChecks.d.ts +4 -4
- package/dist/shared/util/LambderOptionChecks.js +4 -4
- package/dist/shared/util/LambderResponseBrand.d.ts +5 -5
- package/dist/shared/util/LambderResponseBrand.js +5 -5
- package/dist/shared/util/LambderTextDigest.d.ts +7 -5
- package/dist/shared/util/LambderTextDigest.js +11 -5
- package/dist/shared/util/LambderTypeUtilities.d.ts +7 -8
- package/dist/shared/util/LambderTypeUtilities.js +3 -3
- package/dist/shared/util/boundKeyField.d.ts +20 -0
- package/dist/shared/util/boundKeyField.js +34 -0
- package/dist/shared/util/canonicalJson.d.ts +11 -0
- package/dist/shared/util/canonicalJson.js +28 -0
- package/dist/shared/util/joinKeyFields.d.ts +20 -0
- package/dist/shared/util/joinKeyFields.js +22 -0
- package/dist/shared/wire/LambderAnswerHeaders.d.ts +12 -16
- package/dist/shared/wire/LambderAnswerHeaders.js +12 -16
- package/dist/shared/wire/LambderApiContract.d.ts +98 -53
- package/dist/shared/wire/LambderApiOutcome.d.ts +43 -31
- package/dist/shared/wire/LambderApiOutcome.js +48 -23
- package/dist/shared/wire/LambderApiRefusal.d.ts +45 -27
- package/dist/shared/wire/LambderApiRefusal.js +42 -7
- package/dist/shared/wire/LambderApiSignature.d.ts +18 -22
- package/dist/shared/wire/LambderApiSignature.js +16 -19
- package/dist/shared/wire/LambderCallOptions.d.ts +38 -47
- package/dist/shared/wire/LambderCallOptions.js +9 -11
- package/dist/shared/wire/LambderCompressionCodec.d.ts +29 -34
- package/dist/shared/wire/LambderCompressionCodec.js +31 -36
- package/dist/shared/wire/LambderCompressionOption.d.ts +9 -9
- package/dist/shared/wire/LambderCompressionOption.js +9 -9
- package/dist/shared/wire/LambderCrashDetail.d.ts +12 -15
- package/dist/shared/wire/LambderCrashDetail.js +12 -15
- package/dist/shared/wire/LambderDefaultApiPath.d.ts +6 -0
- package/dist/shared/wire/LambderDefaultApiPath.js +6 -0
- package/dist/shared/wire/LambderHttpStatus.d.ts +6 -7
- package/dist/shared/wire/LambderIdempotencyKeyScope.d.ts +89 -0
- package/dist/shared/wire/LambderIdempotencyKeyScope.js +146 -0
- package/dist/shared/wire/LambderInvokeApiId.d.ts +27 -0
- package/dist/shared/wire/LambderInvokeApiId.js +27 -0
- package/dist/shared/wire/LambderOutcomeAssertions.d.ts +6 -7
- package/dist/shared/wire/LambderOutcomeAssertions.js +6 -7
- package/dist/shared/wire/LambderRequestPayload.d.ts +18 -20
- package/dist/shared/wire/LambderRequestPayload.js +4 -6
- package/dist/shared/wire/LambderUploadObjectFields.d.ts +10 -0
- package/dist/shared/wire/LambderUploadObjectFields.js +24 -0
- package/dist/shared/wire/LambderUploadRefusal.d.ts +9 -0
- package/dist/shared/wire/LambderUploadRefusal.js +18 -0
- package/dist/shared/wire/LambderUploadSchemas.d.ts +12 -0
- package/dist/shared/wire/LambderUploadSchemas.js +30 -0
- package/dist/stores/LambderCacheFiller.d.ts +48 -0
- package/dist/stores/LambderCacheFiller.js +119 -0
- package/dist/stores/LambderCacheKeys.d.ts +26 -0
- package/dist/stores/LambderCacheKeys.js +54 -0
- package/dist/stores/LambderCacheValues.d.ts +45 -0
- package/dist/stores/LambderCacheValues.js +74 -0
- package/dist/stores/LambderDdbCache.d.ts +121 -56
- package/dist/stores/LambderDdbCache.js +528 -225
- package/dist/stores/LambderDdbIdempotencyStore.d.ts +33 -22
- package/dist/stores/LambderDdbIdempotencyStore.js +75 -50
- package/dist/stores/LambderDdbRateLimiter.d.ts +76 -20
- package/dist/stores/LambderDdbRateLimiter.js +151 -39
- package/dist/stores/LambderDdbSdk.d.ts +43 -31
- package/dist/stores/LambderDdbSdk.js +80 -38
- package/dist/stores/LambderDdbSessionStore.d.ts +27 -14
- package/dist/stores/LambderDdbSessionStore.js +119 -47
- package/dist/stores/LambderHttpFileSource.d.ts +15 -6
- package/dist/stores/LambderHttpFileSource.js +15 -13
- package/dist/stores/LambderMemoryCache.d.ts +49 -0
- package/dist/stores/LambderMemoryCache.js +113 -0
- package/dist/stores/LambderMemoryIdempotencyStore.d.ts +13 -12
- package/dist/stores/LambderMemoryIdempotencyStore.js +31 -30
- package/dist/stores/LambderMemoryRateLimiter.d.ts +8 -9
- package/dist/stores/LambderMemoryRateLimiter.js +14 -13
- package/dist/stores/LambderMemorySessionStore.d.ts +14 -11
- package/dist/stores/LambderMemorySessionStore.js +38 -19
- package/dist/stores/LambderMemoryUploadBucket.d.ts +99 -0
- package/dist/stores/LambderMemoryUploadBucket.js +219 -0
- package/dist/stores/LambderS3FileSource.d.ts +21 -6
- package/dist/stores/LambderS3FileSource.js +12 -7
- package/dist/stores/LambderS3UploadBucket.d.ts +73 -0
- package/dist/stores/LambderS3UploadBucket.js +144 -0
- package/dist/stores/LambderSdkInstallHint.d.ts +11 -0
- package/dist/stores/LambderSdkInstallHint.js +14 -0
- package/dist/testing/LambderTestApp.d.ts +23 -25
- package/dist/testing/LambderTestApp.js +22 -24
- package/dist/testing/LambderTestVisitor.d.ts +10 -12
- package/dist/testing/LambderTestVisitor.js +15 -15
- package/dist/testing.d.ts +3 -0
- package/dist/testing.js +2 -0
- package/package.json +26 -3
- package/dist/api/LambderApiPolicyEngine.d.ts +0 -47
- package/dist/api/LambderApiPolicyEngine.js +0 -85
- package/dist/shared/util/LambderKeyFields.d.ts +0 -32
- 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:
|
|
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
|
|
29
|
-
* handler
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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
|
|
103
|
-
* against the other and
|
|
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
|
-
|
|
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
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* differently. Set it to false
|
|
126
|
-
*
|
|
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,
|
|
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:
|
|
138
|
-
} ?
|
|
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,
|
|
158
|
-
* option uses
|
|
159
|
-
*
|
|
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
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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" |
|
|
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
|
-
*
|
|
178
|
-
* and
|
|
179
|
-
*
|
|
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
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
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 {};
|