stratal 0.0.19 → 0.0.20
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/dist/{base-email.provider-mjynzewK.mjs → base-email.provider-CfQCA08m.mjs} +1 -1
- package/dist/{base-email.provider-mjynzewK.mjs.map → base-email.provider-CfQCA08m.mjs.map} +1 -1
- package/dist/bin/quarry.mjs +6 -0
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +2 -154
- package/dist/cache/index.d.mts.map +1 -1
- package/dist/cache/index.mjs +3 -5
- package/dist/cache/index.mjs.map +1 -1
- package/dist/cache.service-DsnKuNyO.d.mts +156 -0
- package/dist/cache.service-DsnKuNyO.d.mts.map +1 -0
- package/dist/cache.tokens-B7Rw1C9Q.mjs +6 -0
- package/dist/cache.tokens-B7Rw1C9Q.mjs.map +1 -0
- package/dist/{command-DsQq56Lp.d.mts → command-Bu-PjJrX.d.mts} +2 -2
- package/dist/{command-DsQq56Lp.d.mts.map → command-Bu-PjJrX.d.mts.map} +1 -1
- package/dist/config/index.d.mts +3 -3
- package/dist/config/index.mjs +2 -2
- package/dist/{consumer-registry-Doom7BEh.d.mts → consumer-registry-B7yUNh0q.d.mts} +1 -1
- package/dist/{consumer-registry-Doom7BEh.d.mts.map → consumer-registry-B7yUNh0q.d.mts.map} +1 -1
- package/dist/{controller.decorator-LZY9aHYG.mjs → controller.decorator-DQzenvSN.mjs} +2 -2
- package/dist/{controller.decorator-LZY9aHYG.mjs.map → controller.decorator-DQzenvSN.mjs.map} +1 -1
- package/dist/cron/index.d.mts +2 -2
- package/dist/cron/index.mjs +1 -1
- package/dist/{cron-manager-C30t9UZM.mjs → cron-manager-7Symz_TE.mjs} +2 -2
- package/dist/{cron-manager-C30t9UZM.mjs.map → cron-manager-7Symz_TE.mjs.map} +1 -1
- package/dist/{cron-manager-RuPtFVLy.d.mts → cron-manager-BEsH1mjW.d.mts} +3 -3
- package/dist/{cron-manager-RuPtFVLy.d.mts.map → cron-manager-BEsH1mjW.d.mts.map} +1 -1
- package/dist/di/index.d.mts +1 -1
- package/dist/di/index.mjs +1 -1
- package/dist/email/index.d.mts +3 -3
- package/dist/email/index.mjs +7 -7
- package/dist/{en-rHmW6vD9.mjs → en-DSH_bhh6.mjs} +7 -1
- package/dist/en-DSH_bhh6.mjs.map +1 -0
- package/dist/{env-CamWD-U1.d.mts → env-D1rcZ8_r.d.mts} +1 -1
- package/dist/env-D1rcZ8_r.d.mts.map +1 -0
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +1 -1
- package/dist/{errors-B4pYgYON.mjs → errors-BdyV5PnY.mjs} +20 -9
- package/dist/errors-BdyV5PnY.mjs.map +1 -0
- package/dist/{errors-BUyUfr2Z.mjs → errors-Da3Pz2X7.mjs} +2 -2
- package/dist/{errors-BUyUfr2Z.mjs.map → errors-Da3Pz2X7.mjs.map} +1 -1
- package/dist/events/index.d.mts +2 -2
- package/dist/{gateway-context-cqZ8wMoi.mjs → gateway-context-CdJjpUCW.mjs} +4 -8
- package/dist/{gateway-context-cqZ8wMoi.mjs.map → gateway-context-CdJjpUCW.mjs.map} +1 -1
- package/dist/guards/index.d.mts +3 -3
- package/dist/guards/index.mjs +1 -1
- package/dist/{guards-DMbsAxSX.mjs → guards-DUk_Kzst.mjs} +1 -1
- package/dist/{guards-DMbsAxSX.mjs.map → guards-DUk_Kzst.mjs.map} +1 -1
- package/dist/{http-method.decorator-BT3ufnz8.mjs → http-method.decorator-DXwxAfb_.mjs} +3 -3
- package/dist/{http-method.decorator-BT3ufnz8.mjs.map → http-method.decorator-DXwxAfb_.mjs.map} +1 -1
- package/dist/i18n/index.d.mts +2 -2
- package/dist/i18n/index.mjs +2 -2
- package/dist/i18n/messages/en/index.d.mts +1 -1
- package/dist/i18n/messages/en/index.mjs +1 -1
- package/dist/i18n/utils/index.mjs +1 -1
- package/dist/i18n/validation/index.d.mts +2 -2
- package/dist/i18n/validation/index.mjs +2 -2
- package/dist/{i18n.module-CI_prYFD.mjs → i18n.module-BBlNNlcG.mjs} +191 -39
- package/dist/i18n.module-BBlNNlcG.mjs.map +1 -0
- package/dist/{index-SHx31sBJ.d.mts → index-7-hU3GTV.d.mts} +1 -1
- package/dist/{index-SHx31sBJ.d.mts.map → index-7-hU3GTV.d.mts.map} +1 -1
- package/dist/{index-B437eK7p.d.mts → index-Bnpfq6uk.d.mts} +58 -10
- package/dist/index-Bnpfq6uk.d.mts.map +1 -0
- package/dist/{index-DFhEeFfC.d.mts → index-C1KvMncZ.d.mts} +7 -1
- package/dist/{index-DFhEeFfC.d.mts.map → index-C1KvMncZ.d.mts.map} +1 -1
- package/dist/{index-Dnqm9ZB6.d.mts → index-CjaQ6_tZ.d.mts} +5 -5
- package/dist/{index-Dnqm9ZB6.d.mts.map → index-CjaQ6_tZ.d.mts.map} +1 -1
- package/dist/{index-DPFqRs8L.d.mts → index-D0US0X14.d.mts} +313 -204
- package/dist/index-D0US0X14.d.mts.map +1 -0
- package/dist/{index-CWRS7Ri3.d.mts → index-DBd_2wv8.d.mts} +1 -1
- package/dist/{index-CWRS7Ri3.d.mts.map → index-DBd_2wv8.d.mts.map} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +1 -1
- package/dist/logger/index.d.mts +1 -1
- package/dist/macroable/index.d.mts +1 -1
- package/dist/module/index.d.mts +2 -2
- package/dist/module/index.mjs +1 -1
- package/dist/{module-qGE_1duv.mjs → module-Dk2qTa77.mjs} +132 -4
- package/dist/module-Dk2qTa77.mjs.map +1 -0
- package/dist/openapi/index.d.mts +3 -3
- package/dist/openapi/index.mjs +2 -2
- package/dist/{openapi-tools.service-CYWGuhue.mjs → openapi-tools.service-Zs-Ewv7F.mjs} +1 -1
- package/dist/{openapi-tools.service-CYWGuhue.mjs.map → openapi-tools.service-Zs-Ewv7F.mjs.map} +1 -1
- package/dist/{openapi.service-Bv_NioM9.d.mts → openapi.service-BLgvn3hJ.d.mts} +3 -3
- package/dist/{openapi.service-Bv_NioM9.d.mts.map → openapi.service-BLgvn3hJ.d.mts.map} +1 -1
- package/dist/quarry/index.d.mts +6 -6
- package/dist/quarry/index.mjs +2 -2
- package/dist/{quarry-registry-DFfRRkA7.mjs → quarry-registry-DNEej-Db.mjs} +5 -5
- package/dist/{quarry-registry-DFfRRkA7.mjs.map → quarry-registry-DNEej-Db.mjs.map} +1 -1
- package/dist/queue/index.d.mts +2 -2
- package/dist/queue/index.mjs +1 -1
- package/dist/{queue.module-P-G-nCYz.mjs → queue.module-BCdCiySt.mjs} +3 -3
- package/dist/{queue.module-P-G-nCYz.mjs.map → queue.module-BCdCiySt.mjs.map} +1 -1
- package/dist/{r2-storage.provider-LdzK9tfG.mjs → r2-storage.provider-Co6F0ZYV.mjs} +3 -3
- package/dist/{r2-storage.provider-LdzK9tfG.mjs.map → r2-storage.provider-Co6F0ZYV.mjs.map} +1 -1
- package/dist/rate-limit.decorator--o6Q6p9w.mjs +55 -0
- package/dist/rate-limit.decorator--o6Q6p9w.mjs.map +1 -0
- package/dist/rate-limiter/index.d.mts +420 -0
- package/dist/rate-limiter/index.d.mts.map +1 -0
- package/dist/rate-limiter/index.mjs +365 -0
- package/dist/rate-limiter/index.mjs.map +1 -0
- package/dist/{resend.provider-bwILp0WI.mjs → resend.provider-M6qRLrcy.mjs} +2 -2
- package/dist/{resend.provider-bwILp0WI.mjs.map → resend.provider-M6qRLrcy.mjs.map} +1 -1
- package/dist/router/index.d.mts +2 -2
- package/dist/router/index.mjs +6 -6
- package/dist/seeder/index.d.mts +3 -3
- package/dist/seeder/index.mjs +1 -1
- package/dist/{seeder-BcqIFa2X.mjs → seeder-CJAOHEIo.mjs} +2 -2
- package/dist/{seeder-BcqIFa2X.mjs.map → seeder-CJAOHEIo.mjs.map} +1 -1
- package/dist/{setup-CtekcwuO.mjs → setup-CefZKV_e.mjs} +1 -1
- package/dist/{setup-CtekcwuO.mjs.map → setup-CefZKV_e.mjs.map} +1 -1
- package/dist/{signed-url-COX7cCWR.mjs → signed-url-BQPbv2In.mjs} +1 -1
- package/dist/{signed-url-COX7cCWR.mjs.map → signed-url-BQPbv2In.mjs.map} +1 -1
- package/dist/{smtp.provider-B07yuARi.mjs → smtp.provider-w0Ve52Xg.mjs} +2 -2
- package/dist/{smtp.provider-B07yuARi.mjs.map → smtp.provider-w0Ve52Xg.mjs.map} +1 -1
- package/dist/storage/index.d.mts +3 -3
- package/dist/storage/index.mjs +2 -2
- package/dist/storage/providers/index.d.mts +2 -2
- package/dist/storage/providers/index.mjs +1 -1
- package/dist/{storage-P6X4h9So.mjs → storage-1zw-6Yiz.mjs} +8 -8
- package/dist/{storage-P6X4h9So.mjs.map → storage-1zw-6Yiz.mjs.map} +1 -1
- package/dist/{storage-provider.interface-CC1nniHk.d.mts → storage-provider.interface-Bd6vA4ak.d.mts} +2 -2
- package/dist/{storage-provider.interface-CC1nniHk.d.mts.map → storage-provider.interface-Bd6vA4ak.d.mts.map} +1 -1
- package/dist/{stratal-BCiwCFN9.mjs → stratal-DeEcGgdq.mjs} +8 -8
- package/dist/stratal-DeEcGgdq.mjs.map +1 -0
- package/dist/{types-DIWemRad.d.mts → types-cySNS_lp.d.mts} +1 -1
- package/dist/types-cySNS_lp.d.mts.map +1 -0
- package/dist/{usage-generator-MBcRo0Q2.mjs → usage-generator-BUdlhnCK.mjs} +1 -1
- package/dist/{usage-generator-MBcRo0Q2.mjs.map → usage-generator-BUdlhnCK.mjs.map} +1 -1
- package/dist/{validation-Dbg3ehdP.mjs → validation-DtJwAv7O.mjs} +62 -8
- package/dist/validation-DtJwAv7O.mjs.map +1 -0
- package/dist/websocket/index.d.mts +8 -3
- package/dist/websocket/index.d.mts.map +1 -1
- package/dist/websocket/index.mjs +1 -1
- package/dist/workers/index.d.mts +2 -2
- package/dist/workers/index.mjs +1 -1
- package/package.json +10 -6
- package/dist/en-rHmW6vD9.mjs.map +0 -1
- package/dist/env-CamWD-U1.d.mts.map +0 -1
- package/dist/errors-B4pYgYON.mjs.map +0 -1
- package/dist/i18n.module-CI_prYFD.mjs.map +0 -1
- package/dist/index-B437eK7p.d.mts.map +0 -1
- package/dist/index-DPFqRs8L.d.mts.map +0 -1
- package/dist/module-qGE_1duv.mjs.map +0 -1
- package/dist/stratal-BCiwCFN9.mjs.map +0 -1
- package/dist/types-DIWemRad.d.mts.map +0 -1
- package/dist/validation-Dbg3ehdP.mjs.map +0 -1
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { Cn as ModuleContext, Dr as Container, En as OnInitialize, Tn as OnException, V as RouterContext, d as ApplicationError, gn as AsyncModuleOptions, jn as ExceptionHandler, o as HttpException, on as Middleware, sn as Next, vn as DynamicModule } from "../index-D0US0X14.mjs";
|
|
2
|
+
import { t as Constructor } from "../types-cySNS_lp.mjs";
|
|
3
|
+
import { t as Macroable } from "../index-7-hU3GTV.mjs";
|
|
4
|
+
import { t as StratalEnv } from "../env-D1rcZ8_r.mjs";
|
|
5
|
+
import { t as CacheService } from "../cache.service-DsnKuNyO.mjs";
|
|
6
|
+
|
|
7
|
+
//#region src/rate-limiter/errors.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Thrown when a request exceeds a configured rate limit.
|
|
10
|
+
*
|
|
11
|
+
* HTTP Status: 429 Too Many Requests
|
|
12
|
+
* Error Code: 4290
|
|
13
|
+
*
|
|
14
|
+
* The {@link ExceptionHandler} renders the body via content negotiation
|
|
15
|
+
* (HTML for HTML clients, JSON for everything else). Standard rate-limit
|
|
16
|
+
* headers (`Retry-After`, `X-RateLimit-*`) are injected by the
|
|
17
|
+
* `respond()` callback registered via `RateLimiterModule.onException`.
|
|
18
|
+
*/
|
|
19
|
+
declare class TooManyRequestsError extends HttpException {
|
|
20
|
+
readonly info: {
|
|
21
|
+
retryAfter: number;
|
|
22
|
+
limit: number;
|
|
23
|
+
resetAt: number;
|
|
24
|
+
};
|
|
25
|
+
constructor(info: {
|
|
26
|
+
retryAfter: number;
|
|
27
|
+
limit: number;
|
|
28
|
+
resetAt: number;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Thrown when `RateLimiterRegistry.handle(name, ...)` is invoked for a
|
|
33
|
+
* name that was never registered via `.for()`.
|
|
34
|
+
*
|
|
35
|
+
* Most likely cause: a typo in `router.throttle('foo')` or `@RateLimit('foo')`,
|
|
36
|
+
* or the module that registers the limiter is missing from imports.
|
|
37
|
+
*/
|
|
38
|
+
declare class RateLimiterNotDefinedError extends ApplicationError {
|
|
39
|
+
readonly limiterName: string;
|
|
40
|
+
constructor(limiterName: string);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Thrown by `RateLimiterStoreFactory.create()` (during the module's eager
|
|
44
|
+
* `onInitialize` validation) when the user imported `RateLimiterModule`
|
|
45
|
+
* without calling `.forRoot({ store: ... })`. There is no implicit default
|
|
46
|
+
* store — the user must pick one.
|
|
47
|
+
*/
|
|
48
|
+
declare class RateLimiterNotConfiguredError extends HttpException {
|
|
49
|
+
constructor();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when a throttled route fires but `RateLimiterModule` was never
|
|
53
|
+
* imported in the user's AppModule (so the registry token is unbound).
|
|
54
|
+
*
|
|
55
|
+
* Distinct from {@link RateLimiterNotConfiguredError}, which fires when
|
|
56
|
+
* the module IS imported but `forRoot` was not called.
|
|
57
|
+
*/
|
|
58
|
+
declare class RateLimiterModuleNotImportedError extends ApplicationError {
|
|
59
|
+
readonly limiterName: string;
|
|
60
|
+
constructor(limiterName: string);
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/rate-limiter/limit.d.ts
|
|
64
|
+
/**
|
|
65
|
+
* Standard rate-limit response headers passed to custom response handlers.
|
|
66
|
+
* Keys are canonical HTTP header names; values are the stringified counts.
|
|
67
|
+
*/
|
|
68
|
+
interface RateLimitHeaders {
|
|
69
|
+
'Retry-After': string;
|
|
70
|
+
'X-RateLimit-Limit': string;
|
|
71
|
+
'X-RateLimit-Remaining': string;
|
|
72
|
+
'X-RateLimit-Reset': string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Custom response handler invoked when a limit is exceeded and the user
|
|
76
|
+
* supplied `.response()` on the {@link Limit}. Receives the request context
|
|
77
|
+
* and the precomputed standard headers (which the handler can spread, drop,
|
|
78
|
+
* or override).
|
|
79
|
+
*/
|
|
80
|
+
type RateLimitResponseHandler = (ctx: RouterContext, headers: RateLimitHeaders) => Response | Promise<Response>;
|
|
81
|
+
/**
|
|
82
|
+
* A single rate limit window.
|
|
83
|
+
*
|
|
84
|
+
* Build via the static factories (`perSecond`, `perMinute`, `perMinutes`,
|
|
85
|
+
* `perHour`, `perDay`, `none`). Chain `.by(key)` to scope per-actor and
|
|
86
|
+
* `.response(handler)` to override the default 429 response.
|
|
87
|
+
*
|
|
88
|
+
* Returned (singly or as an array) by limiter resolvers registered via
|
|
89
|
+
* `RateLimiterRegistry.for()`.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* Limit.perMinute(60).by(ctx.header('cf-connecting-ip') ?? 'global')
|
|
94
|
+
* Limit.perHour(100).by(userId)
|
|
95
|
+
* Limit.none() // bypass
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
declare class Limit {
|
|
99
|
+
readonly windowSeconds: number;
|
|
100
|
+
readonly max: number;
|
|
101
|
+
readonly disabled: boolean;
|
|
102
|
+
private _key?;
|
|
103
|
+
private _customResponse?;
|
|
104
|
+
private constructor();
|
|
105
|
+
static perSecond(max: number): Limit;
|
|
106
|
+
static perSeconds(seconds: number, max: number): Limit;
|
|
107
|
+
static perMinute(max: number): Limit;
|
|
108
|
+
static perMinutes(minutes: number, max: number): Limit;
|
|
109
|
+
static perHour(max: number): Limit;
|
|
110
|
+
static perDay(max: number): Limit;
|
|
111
|
+
/** Bypass the limiter entirely for this request. */
|
|
112
|
+
static none(): Limit;
|
|
113
|
+
/** Scope this limit to a specific actor (user id, IP, tenant, etc.). */
|
|
114
|
+
by(key: string | number): this;
|
|
115
|
+
/**
|
|
116
|
+
* Override the default 429 response. The handler receives the standard
|
|
117
|
+
* `RateLimitHeaders` so it can spread them onto its own Response or omit
|
|
118
|
+
* them as it sees fit.
|
|
119
|
+
*/
|
|
120
|
+
response(handler: RateLimitResponseHandler): this;
|
|
121
|
+
get key(): string | undefined;
|
|
122
|
+
get customResponse(): RateLimitResponseHandler | undefined;
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/rate-limiter/stores/rate-limiter-store.interface.d.ts
|
|
126
|
+
/**
|
|
127
|
+
* Result of a single hit against the rate limiter (registry-internal).
|
|
128
|
+
*
|
|
129
|
+
* The store no longer participates in the increment — it's a typed KV.
|
|
130
|
+
* `RateLimiterRegistry.handle()` produces this value as it runs the
|
|
131
|
+
* get-modify-set sequence.
|
|
132
|
+
*/
|
|
133
|
+
interface RateLimitHit {
|
|
134
|
+
/** Counter value AFTER the increment (1 on first hit). */
|
|
135
|
+
count: number;
|
|
136
|
+
/** Absolute reset timestamp in milliseconds since epoch. */
|
|
137
|
+
resetAt: number;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Pluggable backend for rate-limit storage — a typed key-value store with TTL.
|
|
141
|
+
*
|
|
142
|
+
* The framework ships `KvRateLimiterStore` (Cloudflare KV) and
|
|
143
|
+
* `InMemoryRateLimiterStore` (tests). Users can register a custom
|
|
144
|
+
* implementation through `RateLimiterModule.forRoot({ store: { useClass: ... } })`.
|
|
145
|
+
*
|
|
146
|
+
* The increment logic lives in `RateLimiterRegistry`; the store only persists
|
|
147
|
+
* arbitrary values keyed by string. Same shape consumed by the better-auth
|
|
148
|
+
* bridge in `@stratal/framework/auth`, which stores its own `RateLimit`
|
|
149
|
+
* records here under a separate key namespace.
|
|
150
|
+
*/
|
|
151
|
+
interface IRateLimiterStore {
|
|
152
|
+
/**
|
|
153
|
+
* Read the value at `key`. Returns `null` when missing or expired.
|
|
154
|
+
* Implementations are responsible for honouring TTL (lazy or active).
|
|
155
|
+
*/
|
|
156
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
157
|
+
/**
|
|
158
|
+
* Write `value` at `key` with `ttlSeconds` TTL. Overwrites any existing
|
|
159
|
+
* value. The TTL resets on every write — callers compute the remaining
|
|
160
|
+
* window themselves and pass it explicitly.
|
|
161
|
+
*/
|
|
162
|
+
set<T = unknown>(key: string, value: T, ttlSeconds: number): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Remove `key`. No-op when missing.
|
|
165
|
+
*/
|
|
166
|
+
delete(key: string): Promise<void>;
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/rate-limiter/rate-limiter-registry.d.ts
|
|
170
|
+
/**
|
|
171
|
+
* Resolver function registered via {@link RateLimiterRegistry.for}. Receives
|
|
172
|
+
* the request context and returns the limit (or limits) that apply to this
|
|
173
|
+
* request. Return `Limit.none()` to bypass for the current request.
|
|
174
|
+
*/
|
|
175
|
+
type LimitResolver = (ctx: RouterContext) => Limit | Limit[] | Promise<Limit | Limit[]>;
|
|
176
|
+
/**
|
|
177
|
+
* Central registry of named rate limiters and the request-time enforcement
|
|
178
|
+
* pipeline. Resolved as a singleton; consumed by `ThrottleMiddleware`.
|
|
179
|
+
*
|
|
180
|
+
* Register limiters in a module's `onInitialize` hook:
|
|
181
|
+
* ```typescript
|
|
182
|
+
* @Module({})
|
|
183
|
+
* export class RateLimitsModule implements OnInitialize {
|
|
184
|
+
* onInitialize({ container }: ModuleContext): void {
|
|
185
|
+
* const limiter = container.resolve<RateLimiterRegistry>(RATE_LIMITER_TOKENS.Registry)
|
|
186
|
+
* limiter.for('api', (ctx) => Limit.perMinute(60).by(ctx.header('cf-connecting-ip') ?? '*'))
|
|
187
|
+
* }
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* Extensible via `Macroable`: adapter packages (e.g. `@stratal/framework/auth`)
|
|
192
|
+
* can attach extra registration methods such as `forPath()` for better-auth
|
|
193
|
+
* `customRules` interop.
|
|
194
|
+
*/
|
|
195
|
+
declare class RateLimiterRegistry extends Macroable {
|
|
196
|
+
private readonly store;
|
|
197
|
+
private readonly resolvers;
|
|
198
|
+
constructor(store: IRateLimiterStore);
|
|
199
|
+
/**
|
|
200
|
+
* Register a named limiter. Names must be unique; calling `for()` again
|
|
201
|
+
* with the same name overwrites the previous resolver (matches Laravel
|
|
202
|
+
* `RateLimiter::for` semantics — last definition wins).
|
|
203
|
+
*/
|
|
204
|
+
for(name: string, resolver: LimitResolver): void;
|
|
205
|
+
has(name: string): boolean;
|
|
206
|
+
/**
|
|
207
|
+
* Enforce the named limiter for the current request. Called by
|
|
208
|
+
* `ThrottleMiddleware` (the per-name class produced by
|
|
209
|
+
* `createThrottleMiddleware`). Resolves the limiter, increments the store
|
|
210
|
+
* for each non-bypassed limit, sets `X-RateLimit-*` headers on success, and
|
|
211
|
+
* either invokes the limit's custom `.response()` or throws
|
|
212
|
+
* {@link TooManyRequestsError} when a limit is exceeded.
|
|
213
|
+
*/
|
|
214
|
+
handle(name: string, ctx: RouterContext, next: Next): Promise<Response | void>;
|
|
215
|
+
/**
|
|
216
|
+
* Get-modify-set increment over the typed KV store. Not atomic across
|
|
217
|
+
* concurrent edge requests on KV — see `KvRateLimiterStore`'s caveat.
|
|
218
|
+
*/
|
|
219
|
+
private hit;
|
|
220
|
+
private makeKey;
|
|
221
|
+
private makeHeaders;
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/rate-limiter/stores/store-factory.d.ts
|
|
225
|
+
/**
|
|
226
|
+
* Configuration for `RateLimiterModule.forRoot()`. Picks the backing store.
|
|
227
|
+
*
|
|
228
|
+
* - `'kv'`: Cloudflare KV. `binding` names the KVNamespace on `StratalEnv`.
|
|
229
|
+
* - `'memory'`: in-process Map. Tests / single-isolate only.
|
|
230
|
+
* - `{ useClass }`: any class implementing `IRateLimiterStore`. Resolved
|
|
231
|
+
* from the DI container (so the class can declare its own `@inject` deps,
|
|
232
|
+
* e.g. a Durable Object namespace from `StratalEnv`).
|
|
233
|
+
*/
|
|
234
|
+
type RateLimiterModuleOptions = {
|
|
235
|
+
store: 'kv';
|
|
236
|
+
binding: keyof StratalEnv;
|
|
237
|
+
} | {
|
|
238
|
+
store: 'memory';
|
|
239
|
+
} | {
|
|
240
|
+
store: {
|
|
241
|
+
useClass: Constructor<IRateLimiterStore>;
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
declare class RateLimiterStoreFactory {
|
|
245
|
+
private readonly env;
|
|
246
|
+
private readonly cache;
|
|
247
|
+
private readonly container;
|
|
248
|
+
private readonly options?;
|
|
249
|
+
constructor(env: StratalEnv, cache: CacheService, container: Container, options?: RateLimiterModuleOptions | undefined);
|
|
250
|
+
create(): IRateLimiterStore;
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/rate-limiter/rate-limiter.module.d.ts
|
|
254
|
+
/**
|
|
255
|
+
* Rate limiter module — opt-in, NOT registered automatically by Application.
|
|
256
|
+
*
|
|
257
|
+
* Usage:
|
|
258
|
+
* ```typescript
|
|
259
|
+
* @Module({
|
|
260
|
+
* imports: [RateLimiterModule.forRoot({ store: 'kv', binding: 'RATE_LIMITS' })],
|
|
261
|
+
* })
|
|
262
|
+
* export class AppModule {}
|
|
263
|
+
* ```
|
|
264
|
+
*
|
|
265
|
+
* Define limiters in any module's `onInitialize` hook by resolving
|
|
266
|
+
* `RATE_LIMITER_TOKENS.Registry` from the container. Apply them with
|
|
267
|
+
* `router.throttle('name')` or `@RateLimit('name')`.
|
|
268
|
+
*
|
|
269
|
+
* The module:
|
|
270
|
+
* - eagerly validates the store at app boot (`onInitialize` resolves the
|
|
271
|
+
* factory and calls `create()`); a missing `forRoot` surfaces
|
|
272
|
+
* `RateLimiterNotConfiguredError` before any request is served.
|
|
273
|
+
* - registers a `respond()` callback on the `ExceptionHandler` (via
|
|
274
|
+
* `onException`) that injects `Retry-After` and `X-RateLimit-*` headers
|
|
275
|
+
* on every {@link TooManyRequestsError} response, regardless of whether
|
|
276
|
+
* the body was rendered as JSON, HTML, or via Inertia.
|
|
277
|
+
*/
|
|
278
|
+
declare class RateLimiterModule implements OnInitialize, OnException {
|
|
279
|
+
/**
|
|
280
|
+
* Configure the rate limiter with a store choice.
|
|
281
|
+
*
|
|
282
|
+
* @param options - `{ store: 'kv', binding }` | `{ store: 'memory' }` | `{ store: { useClass } }`
|
|
283
|
+
*/
|
|
284
|
+
static forRoot(options: RateLimiterModuleOptions): DynamicModule;
|
|
285
|
+
/**
|
|
286
|
+
* Async configuration. Use when store options depend on other services
|
|
287
|
+
* (e.g. ConfigService).
|
|
288
|
+
*/
|
|
289
|
+
static forRootAsync(options: AsyncModuleOptions<RateLimiterModuleOptions>): DynamicModule;
|
|
290
|
+
/**
|
|
291
|
+
* Eagerly resolve the store so a missing `forRoot()` fails at boot,
|
|
292
|
+
* not on the first throttled request. Also registers a per-app marker
|
|
293
|
+
* value so ThrottleMiddleware can distinguish "module imported into this
|
|
294
|
+
* app" from "module class loaded somewhere in the process" (the latter is
|
|
295
|
+
* indistinguishable at the DI layer because @Module's `registry()` call
|
|
296
|
+
* binds providers globally at decoration time).
|
|
297
|
+
*/
|
|
298
|
+
onInitialize({
|
|
299
|
+
container
|
|
300
|
+
}: ModuleContext): void;
|
|
301
|
+
/**
|
|
302
|
+
* Inject `Retry-After` + `X-RateLimit-*` headers on every 429 response.
|
|
303
|
+
* The body itself is rendered by `ExceptionHandler.defaultRender` —
|
|
304
|
+
* content-negotiated, so HTML clients (browsers, Inertia) and JSON
|
|
305
|
+
* clients both get the right shape.
|
|
306
|
+
*/
|
|
307
|
+
onException(handler: ExceptionHandler): void;
|
|
308
|
+
}
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/rate-limiter/rate-limiter.tokens.d.ts
|
|
311
|
+
declare const RATE_LIMITER_TOKENS: {
|
|
312
|
+
readonly Registry: symbol;
|
|
313
|
+
readonly Store: symbol;
|
|
314
|
+
readonly StoreFactory: symbol;
|
|
315
|
+
readonly Options: symbol;
|
|
316
|
+
/**
|
|
317
|
+
* Per-app marker registered by RateLimiterModule.onInitialize. Used by
|
|
318
|
+
* ThrottleMiddleware to detect "module not imported" — the @Module
|
|
319
|
+
* decorator globally registers providers via tsyringe's registry(),
|
|
320
|
+
* so the Registry/Store tokens are globally bound the moment the module
|
|
321
|
+
* file is loaded. The only way to confirm the module was actually wired
|
|
322
|
+
* into the *user's* AppModule is to look for an artifact registered
|
|
323
|
+
* inside the user's app container (not the root container).
|
|
324
|
+
*/
|
|
325
|
+
readonly ModuleMarker: symbol;
|
|
326
|
+
};
|
|
327
|
+
type RateLimiterToken = (typeof RATE_LIMITER_TOKENS)[keyof typeof RATE_LIMITER_TOKENS];
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/rate-limiter/throttle.middleware.d.ts
|
|
330
|
+
/**
|
|
331
|
+
* Memoized factory that produces a Stratal `Middleware` class bound to a
|
|
332
|
+
* named limiter. Calling twice with the same name returns the *same* class
|
|
333
|
+
* — important for `Router.middleware` deduplication via class identity.
|
|
334
|
+
*
|
|
335
|
+
* Detection of "module not imported" works against a per-app marker
|
|
336
|
+
* registered by `RateLimiterModule.onInitialize` (NOT via inject decorator,
|
|
337
|
+
* because tsyringe would still try to construct Registry — whose Store
|
|
338
|
+
* inject would explode with a less-actionable tsyringe wrapping). We hold
|
|
339
|
+
* the user's container, then check `isRegistered(marker, recursive=true)`
|
|
340
|
+
* at request time before resolving Registry.
|
|
341
|
+
*/
|
|
342
|
+
declare function createThrottleMiddleware(name: string): Constructor<Middleware>;
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/rate-limiter/decorators/rate-limit.decorator.d.ts
|
|
345
|
+
/**
|
|
346
|
+
* Apply a named rate limiter to a controller class or a single route method.
|
|
347
|
+
*
|
|
348
|
+
* Stacks: multiple `@RateLimit` decorators on the same target push onto
|
|
349
|
+
* the metadata array — every named limiter is enforced. Class-level limits
|
|
350
|
+
* run before method-level limits in the resulting middleware chain.
|
|
351
|
+
*
|
|
352
|
+
* The named limiter must be registered separately via
|
|
353
|
+
* `RateLimiterRegistry.for('name', resolver)` (typically inside a
|
|
354
|
+
* module's `onInitialize` hook) and the user must import
|
|
355
|
+
* `RateLimiterModule.forRoot({ store: ... })` in their AppModule.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```typescript
|
|
359
|
+
* @Controller('/api/v1/users')
|
|
360
|
+
* @RateLimit('api')
|
|
361
|
+
* export class UsersController {
|
|
362
|
+
* @Get('/')
|
|
363
|
+
* list(ctx: RouterContext) { ... }
|
|
364
|
+
*
|
|
365
|
+
* @Post('/')
|
|
366
|
+
* @RateLimit('writes') // stacks with class-level 'api'
|
|
367
|
+
* create(ctx: RouterContext) { ... }
|
|
368
|
+
* }
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
371
|
+
declare function RateLimit(name: string): ClassDecorator & MethodDecorator;
|
|
372
|
+
/**
|
|
373
|
+
* Read the rate-limit names attached to a class or method via `@RateLimit`.
|
|
374
|
+
* Returns an empty array when no decorator was applied.
|
|
375
|
+
*
|
|
376
|
+
* @param target - For class metadata, pass the controller constructor.
|
|
377
|
+
* For method metadata, pass `Controller.prototype` and the method name.
|
|
378
|
+
*/
|
|
379
|
+
declare function getRateLimits(target: object, propertyKey?: string): string[];
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/rate-limiter/stores/kv-store.d.ts
|
|
382
|
+
/**
|
|
383
|
+
* Cloudflare KV-backed typed KV store.
|
|
384
|
+
*
|
|
385
|
+
* KV's minimum `expirationTtl` is 60 seconds; sub-60s windows are still
|
|
386
|
+
* enforced by the registry's algorithm via the persisted `resetAt`, but
|
|
387
|
+
* the key itself may live in KV longer than the logical window.
|
|
388
|
+
*
|
|
389
|
+
* KV has no native atomic increment, so concurrent writes from different
|
|
390
|
+
* edge locations may undercount under high contention. That's an inherent
|
|
391
|
+
* KV tradeoff — pick `{ useClass: MyDurableObjectStore }` for strict
|
|
392
|
+
* accuracy across edges.
|
|
393
|
+
*/
|
|
394
|
+
declare class KvRateLimiterStore implements IRateLimiterStore {
|
|
395
|
+
private readonly cache;
|
|
396
|
+
constructor(cache: CacheService);
|
|
397
|
+
get<T>(key: string): Promise<T | null>;
|
|
398
|
+
set<T>(key: string, value: T, ttlSeconds: number): Promise<void>;
|
|
399
|
+
delete(key: string): Promise<void>;
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region src/rate-limiter/stores/memory-store.d.ts
|
|
403
|
+
/**
|
|
404
|
+
* In-process Map-backed typed KV store.
|
|
405
|
+
*
|
|
406
|
+
* Suitable for tests and single-isolate scenarios. Not safe across
|
|
407
|
+
* Cloudflare Worker isolates — entries reset whenever a fresh isolate
|
|
408
|
+
* spins up. Use `KvRateLimiterStore` (or a custom Durable Object store)
|
|
409
|
+
* for production. Expiry is lazy: stale entries are dropped on the next
|
|
410
|
+
* `get`.
|
|
411
|
+
*/
|
|
412
|
+
declare class InMemoryRateLimiterStore implements IRateLimiterStore {
|
|
413
|
+
private readonly entries;
|
|
414
|
+
get<T>(key: string): Promise<T | null>;
|
|
415
|
+
set<T>(key: string, value: T, ttlSeconds: number): Promise<void>;
|
|
416
|
+
delete(key: string): Promise<void>;
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
export { type IRateLimiterStore, InMemoryRateLimiterStore, KvRateLimiterStore, Limit, type LimitResolver, RATE_LIMITER_TOKENS, RateLimit, type RateLimitHeaders, type RateLimitHit, type RateLimitResponseHandler, RateLimiterModule, RateLimiterModuleNotImportedError, type RateLimiterModuleOptions, RateLimiterNotConfiguredError, RateLimiterNotDefinedError, RateLimiterRegistry, RateLimiterStoreFactory, type RateLimiterToken, TooManyRequestsError, createThrottleMiddleware, getRateLimits };
|
|
420
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/rate-limiter/errors.ts","../../src/rate-limiter/limit.ts","../../src/rate-limiter/stores/rate-limiter-store.interface.ts","../../src/rate-limiter/rate-limiter-registry.ts","../../src/rate-limiter/stores/store-factory.ts","../../src/rate-limiter/rate-limiter.module.ts","../../src/rate-limiter/rate-limiter.tokens.ts","../../src/rate-limiter/throttle.middleware.ts","../../src/rate-limiter/decorators/rate-limit.decorator.ts","../../src/rate-limiter/stores/kv-store.ts","../../src/rate-limiter/stores/memory-store.ts"],"mappings":"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"}
|