@cibule/rate-limit 0.3.0

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 (40) hide show
  1. package/README.md +170 -0
  2. package/dist/dts/packages/di/src/index.d.ts +8 -0
  3. package/dist/dts/packages/di/src/index.d.ts.map +1 -0
  4. package/dist/dts/packages/di/src/lib/context.d.ts +4 -0
  5. package/dist/dts/packages/di/src/lib/context.d.ts.map +1 -0
  6. package/dist/dts/packages/di/src/lib/inject.d.ts +5 -0
  7. package/dist/dts/packages/di/src/lib/inject.d.ts.map +1 -0
  8. package/dist/dts/packages/di/src/lib/injectable.d.ts +3 -0
  9. package/dist/dts/packages/di/src/lib/injectable.d.ts.map +1 -0
  10. package/dist/dts/packages/di/src/lib/injection-token.d.ts +6 -0
  11. package/dist/dts/packages/di/src/lib/injection-token.d.ts.map +1 -0
  12. package/dist/dts/packages/di/src/lib/injector.d.ts +23 -0
  13. package/dist/dts/packages/di/src/lib/injector.d.ts.map +1 -0
  14. package/dist/dts/packages/di/src/lib/provider.d.ts +22 -0
  15. package/dist/dts/packages/di/src/lib/provider.d.ts.map +1 -0
  16. package/dist/dts/packages/di/src/lib/run-in-injection-context.d.ts +3 -0
  17. package/dist/dts/packages/di/src/lib/run-in-injection-context.d.ts.map +1 -0
  18. package/dist/dts/packages/di/src/lib/token.d.ts +10 -0
  19. package/dist/dts/packages/di/src/lib/token.d.ts.map +1 -0
  20. package/dist/dts/packages/rate-limit/src/index.d.ts +10 -0
  21. package/dist/dts/packages/rate-limit/src/index.d.ts.map +1 -0
  22. package/dist/dts/packages/rate-limit/src/lib/cf-rate-limit-binding.d.ts +8 -0
  23. package/dist/dts/packages/rate-limit/src/lib/cf-rate-limit-binding.d.ts.map +1 -0
  24. package/dist/dts/packages/rate-limit/src/lib/cf-rate-limit-store.d.ts +10 -0
  25. package/dist/dts/packages/rate-limit/src/lib/cf-rate-limit-store.d.ts.map +1 -0
  26. package/dist/dts/packages/rate-limit/src/lib/memory-rate-limit-store.d.ts +21 -0
  27. package/dist/dts/packages/rate-limit/src/lib/memory-rate-limit-store.d.ts.map +1 -0
  28. package/dist/dts/packages/rate-limit/src/lib/provider.d.ts +4 -0
  29. package/dist/dts/packages/rate-limit/src/lib/provider.d.ts.map +1 -0
  30. package/dist/dts/packages/rate-limit/src/lib/rate-limit-result.d.ts +6 -0
  31. package/dist/dts/packages/rate-limit/src/lib/rate-limit-result.d.ts.map +1 -0
  32. package/dist/dts/packages/rate-limit/src/lib/rate-limit-store-accessor.d.ts +5 -0
  33. package/dist/dts/packages/rate-limit/src/lib/rate-limit-store-accessor.d.ts.map +1 -0
  34. package/dist/dts/packages/rate-limit/src/lib/rate-limit-store.d.ts +6 -0
  35. package/dist/dts/packages/rate-limit/src/lib/rate-limit-store.d.ts.map +1 -0
  36. package/dist/index.cjs +214 -0
  37. package/dist/index.cjs.map +7 -0
  38. package/dist/index.js +186 -0
  39. package/dist/index.js.map +7 -0
  40. package/package.json +36 -0
package/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # @cibule/rate-limit
2
+
3
+ Driver-agnostic rate limiting for TypeScript. Ships with an in-memory driver (sliding window with batch eviction) and a Cloudflare Rate Limiting binding driver.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @cibule/rate-limit
9
+ # or
10
+ bun add @cibule/rate-limit
11
+ # or
12
+ pnpm add @cibule/rate-limit
13
+ # or
14
+ yarn add @cibule/rate-limit
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import { MemoryRateLimitStore } from '@cibule/rate-limit';
21
+
22
+ const store = new MemoryRateLimitStore({ limit: 10, windowMs: 60_000 });
23
+
24
+ const result = await store.check('user:123');
25
+ console.log(result.allowed); // true
26
+ console.log(result.remaining); // 9
27
+ console.log(result.resetAt); // timestamp when the window resets
28
+ ```
29
+
30
+ ## API Reference
31
+
32
+ ### RateLimitStore (abstract class)
33
+
34
+ Base class that all rate limit drivers implement. Defines the core contract.
35
+
36
+ | Method | Signature | Description |
37
+ | ------- | ------------------------------------------ | ------------------------------------------- |
38
+ | `check` | `(key: string) → Promise<RateLimitResult>` | Check rate limit for a key and record a hit |
39
+ | `reset` | `(key: string) → Promise<void>` | Clear rate limit state for a key |
40
+
41
+ ### RateLimitResult
42
+
43
+ Returned by `check()`.
44
+
45
+ | Field | Type | Description |
46
+ | ----------- | --------- | ------------------------------------------ |
47
+ | `allowed` | `boolean` | Whether the request is within the limit |
48
+ | `remaining` | `number` | Number of requests remaining in the window |
49
+ | `resetAt` | `number` | Timestamp (ms) when the window resets |
50
+
51
+ ### MemoryRateLimitStore
52
+
53
+ In-memory sliding window driver with probabilistic cleanup and LRU batch eviction. Ideal for single-instance deployments, testing, and development.
54
+
55
+ ```typescript
56
+ import { MemoryRateLimitStore } from '@cibule/rate-limit';
57
+
58
+ const store = new MemoryRateLimitStore({
59
+ limit: 100, // max requests per window
60
+ windowMs: 60_000, // window size in milliseconds
61
+ maxEntries: 10_000, // max tracked keys (default: 10,000)
62
+ cleanupProbability: 0.01, // probability of expired entry cleanup per check (default: 0.01)
63
+ });
64
+ ```
65
+
66
+ #### MemoryRateLimitConfig
67
+
68
+ | Field | Type | Default | Description |
69
+ | -------------------- | -------- | -------- | ----------------------------------------------- |
70
+ | `limit` | `number` | required | Max requests allowed per window (min: 1) |
71
+ | `windowMs` | `number` | required | Window duration in milliseconds (min: 1) |
72
+ | `maxEntries` | `number` | `10000` | Max tracked keys before LRU eviction (min: 1) |
73
+ | `cleanupProbability` | `number` | `0.01` | Probability of purging expired entries (0 to 1) |
74
+
75
+ Key behaviors:
76
+
77
+ - **Sliding window** — each key tracks individual request timestamps, expired entries are filtered per check
78
+ - **Probabilistic cleanup** — on each `check()`, expired entries across all keys are purged with configurable probability (default 1%)
79
+ - **LRU batch eviction** — when `maxEntries` is exceeded, the least recently accessed 10% of entries are removed
80
+
81
+ ### CfRateLimitStore
82
+
83
+ Cloudflare Rate Limiting binding driver. Delegates to the platform's built-in rate limiting API, configured via `wrangler.toml`.
84
+
85
+ ```typescript
86
+ import { CfRateLimitStore } from '@cibule/rate-limit';
87
+
88
+ // In a Cloudflare Worker, pass the rate limiting binding
89
+ const store = new CfRateLimitStore(env.MY_RATE_LIMITER);
90
+
91
+ const result = await store.check('user:123');
92
+ ```
93
+
94
+ #### RateLimitBinding
95
+
96
+ The Cloudflare binding interface expected by `CfRateLimitStore`:
97
+
98
+ ```typescript
99
+ interface RateLimitBinding {
100
+ limit(options: { key: string }): Promise<{ success: boolean }>;
101
+ }
102
+ ```
103
+
104
+ Key behaviors:
105
+
106
+ - `remaining` is `1` when allowed, `0` when denied (Cloudflare does not expose remaining count)
107
+ - `resetAt` is always `0` (Cloudflare manages windows internally)
108
+ - `reset()` is a no-op (Cloudflare does not support manual reset)
109
+
110
+ ## DI Integration
111
+
112
+ ### RATE_LIMIT_STORE_ACCESSOR
113
+
114
+ Injection token for the accessor pattern, enabling per-request rate limit store instances:
115
+
116
+ ```typescript
117
+ import { Injector } from '@cibule/di';
118
+ import { RATE_LIMIT_STORE_ACCESSOR, MemoryRateLimitStore } from '@cibule/rate-limit';
119
+
120
+ const store = new MemoryRateLimitStore({ limit: 100, windowMs: 60_000 });
121
+
122
+ const injector = Injector.create({
123
+ providers: [{ provide: RATE_LIMIT_STORE_ACCESSOR, useValue: () => store }],
124
+ });
125
+
126
+ const accessor = injector.get(RATE_LIMIT_STORE_ACCESSOR);
127
+ const rateLimiter = accessor(); // RateLimitStore instance
128
+ ```
129
+
130
+ ### provideRateLimitStore()
131
+
132
+ Convenience helper to register the accessor via a factory:
133
+
134
+ ```typescript
135
+ import { Injector } from '@cibule/di';
136
+ import { provideRateLimitStore, MemoryRateLimitStore } from '@cibule/rate-limit';
137
+
138
+ const store = new MemoryRateLimitStore({ limit: 100, windowMs: 60_000 });
139
+
140
+ const injector = Injector.create({
141
+ providers: [provideRateLimitStore(() => () => store)],
142
+ });
143
+ ```
144
+
145
+ ## Type Exports
146
+
147
+ ```typescript
148
+ import type {
149
+ MemoryRateLimitConfig,
150
+ RateLimitBinding,
151
+ RateLimitResult,
152
+ RateLimitStoreAccessor,
153
+ } from '@cibule/rate-limit';
154
+ ```
155
+
156
+ ## Key Behaviors
157
+
158
+ | Behavior | MemoryRateLimitStore | CfRateLimitStore |
159
+ | --------------------- | --------------------------------------- | ---------------------------------- |
160
+ | **Persistence** | None (lost on process exit) | Managed by Cloudflare |
161
+ | **Window type** | Sliding window (per-timestamp tracking) | Configured in `wrangler.toml` |
162
+ | **Remaining count** | Exact | Binary (1 or 0) |
163
+ | **Reset timestamp** | Exact (oldest timestamp + window) | Not available (always 0) |
164
+ | **Manual reset** | Supported | No-op |
165
+ | **Memory management** | LRU eviction + probabilistic cleanup | Managed by Cloudflare |
166
+ | **Multi-instance** | Not shared across instances | Shared across Workers in same zone |
167
+
168
+ ## License
169
+
170
+ MIT
@@ -0,0 +1,8 @@
1
+ export { inject } from './lib/inject';
2
+ export { Injectable } from './lib/injectable';
3
+ export { InjectionToken } from './lib/injection-token';
4
+ export { Injector } from './lib/injector';
5
+ export type { ClassProvider, FactoryProvider, InjectOptions, Provider, SingleProvider, ValueProvider, } from './lib/provider';
6
+ export { runInInjectionContext } from './lib/run-in-injection-context';
7
+ export type { AbstractType, Token, Type } from './lib/token';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../di/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,YAAY,EACV,aAAa,EACb,eAAe,EACf,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,GACd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AACvE,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Injector } from './injector';
2
+ export declare function getCurrentInjector(): Injector | null;
3
+ export declare function setCurrentInjector(injector: Injector | null): Injector | null;
4
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,wBAAgB,kBAAkB,IAAI,QAAQ,GAAG,IAAI,CAEpD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,CAI7E"}
@@ -0,0 +1,5 @@
1
+ import type { InjectOptions } from './provider';
2
+ import type { Token } from './token';
3
+ export declare function inject<T>(token: Token<T>): T;
4
+ export declare function inject<T>(token: Token<T>, options: InjectOptions): T | null;
5
+ //# sourceMappingURL=inject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/inject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAC9C,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG,CAAC,GAAG,IAAI,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { AbstractType, Type } from './token';
2
+ export declare function Injectable(): (target: Type<unknown> | AbstractType<unknown>) => void;
3
+ //# sourceMappingURL=injectable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injectable.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/injectable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAElD,wBAAgB,UAAU,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,IAAI,CAEpF"}
@@ -0,0 +1,6 @@
1
+ export declare class InjectionToken<T> {
2
+ readonly description: string;
3
+ readonly _brand: T;
4
+ constructor(description: string);
5
+ }
6
+ //# sourceMappingURL=injection-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injection-token.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/injection-token.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAc,CAAC,CAAC;aAEC,WAAW,EAAE,MAAM;IAD/C,SAAiB,MAAM,EAAE,CAAC,CAAC;gBACC,WAAW,EAAE,MAAM;CAChD"}
@@ -0,0 +1,23 @@
1
+ import type { InjectOptions, Provider } from './provider';
2
+ import type { Token } from './token';
3
+ export declare class Injector {
4
+ private readonly parent?;
5
+ private readonly records;
6
+ private constructor();
7
+ static create(options: {
8
+ providers: Provider[];
9
+ parent?: Injector;
10
+ }): Injector;
11
+ get<T>(token: Token<T>): T;
12
+ get<T>(token: Token<T>, options: InjectOptions): T | null;
13
+ runInContext<T>(fn: () => T): T;
14
+ private resolve;
15
+ private invokeFactories;
16
+ private register;
17
+ private createFactory;
18
+ private addRecord;
19
+ private resolveParentMulti;
20
+ private flatten;
21
+ private tokenName;
22
+ }
23
+ //# sourceMappingURL=injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injector.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/injector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAC1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUrC,qBAAa,QAAQ;IAKjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAJ1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6C;IAErE,OAAO;WAUO,MAAM,CAAC,OAAO,EAAE;QAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QAAC,MAAM,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,QAAQ;IAI9E,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1B,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG,CAAC,GAAG,IAAI;IAmBzD,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAStC,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,QAAQ;IAkBhB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,SAAS;CASlB"}
@@ -0,0 +1,22 @@
1
+ import type { Token, Type } from './token';
2
+ export interface ValueProvider<T = unknown> {
3
+ readonly provide: Token<T>;
4
+ readonly useValue: T;
5
+ readonly multi?: boolean;
6
+ }
7
+ export interface ClassProvider<T = unknown> {
8
+ readonly provide: Token<T>;
9
+ readonly useClass: Type<T>;
10
+ readonly multi?: boolean;
11
+ }
12
+ export interface FactoryProvider<T = unknown> {
13
+ readonly provide: Token<T>;
14
+ readonly useFactory: () => T;
15
+ readonly multi?: boolean;
16
+ }
17
+ export type SingleProvider<T = unknown> = Type<T> | ValueProvider<T> | ClassProvider<T> | FactoryProvider<T>;
18
+ export type Provider = SingleProvider | Provider[];
19
+ export interface InjectOptions {
20
+ readonly optional: true;
21
+ }
22
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAClC,IAAI,CAAC,CAAC,CAAC,GACP,aAAa,CAAC,CAAC,CAAC,GAChB,aAAa,CAAC,CAAC,CAAC,GAChB,eAAe,CAAC,CAAC,CAAC,CAAC;AAEvB,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,QAAQ,EAAE,CAAC;AAEnD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;CACzB"}
@@ -0,0 +1,3 @@
1
+ import type { Injector } from './injector';
2
+ export declare function runInInjectionContext<T>(injector: Injector, fn: () => T): T;
3
+ //# sourceMappingURL=run-in-injection-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-in-injection-context.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/run-in-injection-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAE3E"}
@@ -0,0 +1,10 @@
1
+ import type { InjectionToken } from './injection-token';
2
+ export interface AbstractType<T> {
3
+ prototype: T;
4
+ name: string;
5
+ }
6
+ export interface Type<T> extends AbstractType<T> {
7
+ new (...args: unknown[]): T;
8
+ }
9
+ export type Token<T> = Type<T> | AbstractType<T> | InjectionToken<T>;
10
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../../../../../di/src/lib/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,SAAS,EAAE,CAAC,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,IAAI,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;IAC9C,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;CAC7B;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export type { RateLimitBinding } from './lib/cf-rate-limit-binding';
2
+ export { CfRateLimitStore } from './lib/cf-rate-limit-store';
3
+ export type { MemoryRateLimitConfig } from './lib/memory-rate-limit-store';
4
+ export { MemoryRateLimitStore } from './lib/memory-rate-limit-store';
5
+ export { provideRateLimitStore } from './lib/provider';
6
+ export type { RateLimitResult } from './lib/rate-limit-result';
7
+ export { RateLimitStore } from './lib/rate-limit-store';
8
+ export type { RateLimitStoreAccessor } from './lib/rate-limit-store-accessor';
9
+ export { RATE_LIMIT_STORE_ACCESSOR } from './lib/rate-limit-store-accessor';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface RateLimitBinding {
2
+ limit(options: {
3
+ key: string;
4
+ }): Promise<{
5
+ success: boolean;
6
+ }>;
7
+ }
8
+ //# sourceMappingURL=cf-rate-limit-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cf-rate-limit-binding.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/cf-rate-limit-binding.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAChE"}
@@ -0,0 +1,10 @@
1
+ import type { RateLimitBinding } from './cf-rate-limit-binding';
2
+ import type { RateLimitResult } from './rate-limit-result';
3
+ import { RateLimitStore } from './rate-limit-store';
4
+ export declare class CfRateLimitStore extends RateLimitStore {
5
+ private readonly binding;
6
+ constructor(binding: RateLimitBinding);
7
+ check(key: string): Promise<RateLimitResult>;
8
+ reset(_key: string): Promise<void>;
9
+ }
10
+ //# sourceMappingURL=cf-rate-limit-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cf-rate-limit-store.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/cf-rate-limit-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,qBAAa,gBAAiB,SAAQ,cAAc;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;gBAExB,OAAO,EAAE,gBAAgB;IAK/B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAUlD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG1C"}
@@ -0,0 +1,21 @@
1
+ import type { RateLimitResult } from './rate-limit-result';
2
+ import { RateLimitStore } from './rate-limit-store';
3
+ export interface MemoryRateLimitConfig {
4
+ readonly limit: number;
5
+ readonly windowMs: number;
6
+ readonly maxEntries?: number;
7
+ readonly cleanupProbability?: number;
8
+ }
9
+ export declare class MemoryRateLimitStore extends RateLimitStore {
10
+ private readonly entries;
11
+ private readonly limit;
12
+ private readonly windowMs;
13
+ private readonly maxEntries;
14
+ private readonly cleanupProbability;
15
+ constructor(config: MemoryRateLimitConfig);
16
+ check(key: string): Promise<RateLimitResult>;
17
+ reset(key: string): Promise<void>;
18
+ private maybePurgeExpired;
19
+ private evictIfOverCapacity;
20
+ }
21
+ //# sourceMappingURL=memory-rate-limit-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-rate-limit-store.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/memory-rate-limit-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAOD,qBAAa,oBAAqB,SAAQ,cAAc;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAEzB,MAAM,EAAE,qBAAqB;IAyBzC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAyB5C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,mBAAmB;CAkB5B"}
@@ -0,0 +1,4 @@
1
+ import type { Provider } from '@cibule/di';
2
+ import type { RateLimitStoreAccessor } from './rate-limit-store-accessor';
3
+ export declare function provideRateLimitStore(factory: () => RateLimitStoreAccessor): Provider;
4
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,sBAAsB,GAAG,QAAQ,CAErF"}
@@ -0,0 +1,6 @@
1
+ export interface RateLimitResult {
2
+ readonly allowed: boolean;
3
+ readonly remaining: number;
4
+ readonly resetAt: number;
5
+ }
6
+ //# sourceMappingURL=rate-limit-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-result.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/rate-limit-result.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B"}
@@ -0,0 +1,5 @@
1
+ import { InjectionToken } from '@cibule/di';
2
+ import type { RateLimitStore } from './rate-limit-store';
3
+ export type RateLimitStoreAccessor = () => RateLimitStore;
4
+ export declare const RATE_LIMIT_STORE_ACCESSOR: InjectionToken<RateLimitStoreAccessor>;
5
+ //# sourceMappingURL=rate-limit-store-accessor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-store-accessor.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/rate-limit-store-accessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,MAAM,sBAAsB,GAAG,MAAM,cAAc,CAAC;AAE1D,eAAO,MAAM,yBAAyB,wCAErC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { RateLimitResult } from './rate-limit-result';
2
+ export declare abstract class RateLimitStore {
3
+ abstract check(key: string): Promise<RateLimitResult>;
4
+ abstract reset(key: string): Promise<void>;
5
+ }
6
+ //# sourceMappingURL=rate-limit-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit-store.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/rate-limit-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,8BACsB,cAAc;aAClB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;aAE5C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAClD"}
package/dist/index.cjs ADDED
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
8
+ var __typeError = (msg) => {
9
+ throw TypeError(msg);
10
+ };
11
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
26
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
27
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
28
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
29
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
30
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
31
+ var __runInitializers = (array, flags, self, value) => {
32
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
33
+ return value;
34
+ };
35
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
36
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
37
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
38
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
39
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
40
+ return __privateGet(this, extra);
41
+ }, set [name](x) {
42
+ return __privateSet(this, extra, x);
43
+ } }, name));
44
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
45
+ for (var i = decorators.length - 1; i >= 0; i--) {
46
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
47
+ if (k) {
48
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
49
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
50
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
51
+ }
52
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
53
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
54
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
55
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
56
+ }
57
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
58
+ };
59
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
60
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
61
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
62
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
63
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
64
+
65
+ // src/index.ts
66
+ var index_exports = {};
67
+ __export(index_exports, {
68
+ CfRateLimitStore: () => CfRateLimitStore,
69
+ MemoryRateLimitStore: () => MemoryRateLimitStore,
70
+ RATE_LIMIT_STORE_ACCESSOR: () => RATE_LIMIT_STORE_ACCESSOR,
71
+ RateLimitStore: () => RateLimitStore,
72
+ provideRateLimitStore: () => provideRateLimitStore
73
+ });
74
+ module.exports = __toCommonJS(index_exports);
75
+
76
+ // ../di/src/lib/injectable.ts
77
+ function Injectable() {
78
+ return () => {
79
+ };
80
+ }
81
+
82
+ // ../di/src/lib/injection-token.ts
83
+ var InjectionToken = class {
84
+ constructor(description) {
85
+ this.description = description;
86
+ }
87
+ };
88
+
89
+ // src/lib/rate-limit-store.ts
90
+ var _RateLimitStore_decorators, _init;
91
+ _RateLimitStore_decorators = [Injectable()];
92
+ var RateLimitStore = class {
93
+ };
94
+ _init = __decoratorStart(null);
95
+ RateLimitStore = __decorateElement(_init, 0, "RateLimitStore", _RateLimitStore_decorators, RateLimitStore);
96
+ __runInitializers(_init, 1, RateLimitStore);
97
+
98
+ // src/lib/cf-rate-limit-store.ts
99
+ var CfRateLimitStore = class extends RateLimitStore {
100
+ binding;
101
+ constructor(binding) {
102
+ super();
103
+ this.binding = binding;
104
+ }
105
+ async check(key) {
106
+ const result = await this.binding.limit({ key });
107
+ return {
108
+ allowed: result.success,
109
+ remaining: result.success ? 1 : 0,
110
+ resetAt: 0
111
+ };
112
+ }
113
+ reset(_key) {
114
+ return Promise.resolve();
115
+ }
116
+ };
117
+
118
+ // src/lib/memory-rate-limit-store.ts
119
+ var MemoryRateLimitStore = class extends RateLimitStore {
120
+ entries = /* @__PURE__ */ new Map();
121
+ limit;
122
+ windowMs;
123
+ maxEntries;
124
+ cleanupProbability;
125
+ constructor(config) {
126
+ super();
127
+ if (config.limit < 1) {
128
+ throw new Error("limit must be at least 1");
129
+ }
130
+ if (config.windowMs < 1) {
131
+ throw new Error("windowMs must be at least 1");
132
+ }
133
+ if (config.maxEntries !== void 0 && config.maxEntries < 1) {
134
+ throw new Error("maxEntries must be at least 1");
135
+ }
136
+ if (config.cleanupProbability !== void 0 && (config.cleanupProbability < 0 || config.cleanupProbability > 1)) {
137
+ throw new Error("cleanupProbability must be between 0 and 1");
138
+ }
139
+ this.limit = config.limit;
140
+ this.windowMs = config.windowMs;
141
+ this.maxEntries = config.maxEntries ?? 1e4;
142
+ this.cleanupProbability = config.cleanupProbability ?? 0.01;
143
+ }
144
+ check(key) {
145
+ this.maybePurgeExpired();
146
+ this.evictIfOverCapacity();
147
+ const now = Date.now();
148
+ const windowStart = now - this.windowMs;
149
+ const entry = this.entries.get(key);
150
+ const activeTimestamps = entry ? entry.timestamps.filter((t) => t > windowStart) : [];
151
+ const allowed = activeTimestamps.length < this.limit;
152
+ const timestamps = allowed ? [...activeTimestamps, now] : activeTimestamps;
153
+ this.entries.set(key, { timestamps, lastAccessed: now });
154
+ const oldest = timestamps[0] ?? now;
155
+ return Promise.resolve({
156
+ allowed,
157
+ remaining: Math.max(0, this.limit - timestamps.length),
158
+ resetAt: oldest + this.windowMs
159
+ });
160
+ }
161
+ reset(key) {
162
+ this.entries.delete(key);
163
+ return Promise.resolve();
164
+ }
165
+ maybePurgeExpired() {
166
+ if (Math.random() >= this.cleanupProbability) {
167
+ return;
168
+ }
169
+ const windowStart = Date.now() - this.windowMs;
170
+ for (const [key, entry] of this.entries) {
171
+ const active = entry.timestamps.filter((t) => t > windowStart);
172
+ if (active.length === 0) {
173
+ this.entries.delete(key);
174
+ } else if (active.length < entry.timestamps.length) {
175
+ this.entries.set(key, { timestamps: active, lastAccessed: entry.lastAccessed });
176
+ }
177
+ }
178
+ }
179
+ evictIfOverCapacity() {
180
+ if (this.entries.size <= this.maxEntries) {
181
+ return;
182
+ }
183
+ const sorted = [...this.entries.entries()].sort(
184
+ (a, b) => a[1].lastAccessed - b[1].lastAccessed
185
+ );
186
+ const batchSize = Math.max(
187
+ this.entries.size - this.maxEntries,
188
+ Math.ceil(this.maxEntries * 0.1)
189
+ );
190
+ const toRemove = sorted.slice(0, batchSize);
191
+ for (const [key] of toRemove) {
192
+ this.entries.delete(key);
193
+ }
194
+ }
195
+ };
196
+
197
+ // src/lib/rate-limit-store-accessor.ts
198
+ var RATE_LIMIT_STORE_ACCESSOR = new InjectionToken(
199
+ "RATE_LIMIT_STORE_ACCESSOR"
200
+ );
201
+
202
+ // src/lib/provider.ts
203
+ function provideRateLimitStore(factory) {
204
+ return { provide: RATE_LIMIT_STORE_ACCESSOR, useFactory: factory };
205
+ }
206
+ // Annotate the CommonJS export names for ESM import in node:
207
+ 0 && (module.exports = {
208
+ CfRateLimitStore,
209
+ MemoryRateLimitStore,
210
+ RATE_LIMIT_STORE_ACCESSOR,
211
+ RateLimitStore,
212
+ provideRateLimitStore
213
+ });
214
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../../di/src/lib/injectable.ts", "../../di/src/lib/injection-token.ts", "../src/lib/rate-limit-store.ts", "../src/lib/cf-rate-limit-store.ts", "../src/lib/memory-rate-limit-store.ts", "../src/lib/rate-limit-store-accessor.ts", "../src/lib/provider.ts"],
4
+ "sourcesContent": ["export type { RateLimitBinding } from './lib/cf-rate-limit-binding';\nexport { CfRateLimitStore } from './lib/cf-rate-limit-store';\nexport type { MemoryRateLimitConfig } from './lib/memory-rate-limit-store';\nexport { MemoryRateLimitStore } from './lib/memory-rate-limit-store';\nexport { provideRateLimitStore } from './lib/provider';\nexport type { RateLimitResult } from './lib/rate-limit-result';\nexport { RateLimitStore } from './lib/rate-limit-store';\nexport type { RateLimitStoreAccessor } from './lib/rate-limit-store-accessor';\nexport { RATE_LIMIT_STORE_ACCESSOR } from './lib/rate-limit-store-accessor';\n", "import type { AbstractType, Type } from './token';\n\nexport function Injectable(): (target: Type<unknown> | AbstractType<unknown>) => void {\n return (): void => {};\n}\n", "export class InjectionToken<T> {\n declare readonly _brand: T;\n constructor(public readonly description: string) {}\n}\n", "import { Injectable } from '@cibule/di';\n\nimport type { RateLimitResult } from './rate-limit-result';\n\n@Injectable()\nexport abstract class RateLimitStore {\n public abstract check(key: string): Promise<RateLimitResult>;\n\n public abstract reset(key: string): Promise<void>;\n}\n", "import type { RateLimitBinding } from './cf-rate-limit-binding';\nimport type { RateLimitResult } from './rate-limit-result';\nimport { RateLimitStore } from './rate-limit-store';\n\nexport class CfRateLimitStore extends RateLimitStore {\n private readonly binding: RateLimitBinding;\n\n public constructor(binding: RateLimitBinding) {\n super();\n this.binding = binding;\n }\n\n public async check(key: string): Promise<RateLimitResult> {\n const result = await this.binding.limit({ key });\n\n return {\n allowed: result.success,\n remaining: result.success ? 1 : 0,\n resetAt: 0,\n };\n }\n\n public reset(_key: string): Promise<void> {\n return Promise.resolve();\n }\n}\n", "import type { RateLimitResult } from './rate-limit-result';\nimport { RateLimitStore } from './rate-limit-store';\n\nexport interface MemoryRateLimitConfig {\n readonly limit: number;\n readonly windowMs: number;\n readonly maxEntries?: number;\n readonly cleanupProbability?: number;\n}\n\ninterface BucketEntry {\n readonly timestamps: readonly number[];\n readonly lastAccessed: number;\n}\n\nexport class MemoryRateLimitStore extends RateLimitStore {\n private readonly entries = new Map<string, BucketEntry>();\n private readonly limit: number;\n private readonly windowMs: number;\n private readonly maxEntries: number;\n private readonly cleanupProbability: number;\n\n public constructor(config: MemoryRateLimitConfig) {\n super();\n\n if (config.limit < 1) {\n throw new Error('limit must be at least 1');\n }\n if (config.windowMs < 1) {\n throw new Error('windowMs must be at least 1');\n }\n if (config.maxEntries !== undefined && config.maxEntries < 1) {\n throw new Error('maxEntries must be at least 1');\n }\n if (\n config.cleanupProbability !== undefined &&\n (config.cleanupProbability < 0 || config.cleanupProbability > 1)\n ) {\n throw new Error('cleanupProbability must be between 0 and 1');\n }\n\n this.limit = config.limit;\n this.windowMs = config.windowMs;\n this.maxEntries = config.maxEntries ?? 10_000;\n this.cleanupProbability = config.cleanupProbability ?? 0.01;\n }\n\n public check(key: string): Promise<RateLimitResult> {\n this.maybePurgeExpired();\n this.evictIfOverCapacity();\n\n const now = Date.now();\n const windowStart = now - this.windowMs;\n const entry = this.entries.get(key);\n\n const activeTimestamps = entry ? entry.timestamps.filter(t => t > windowStart) : [];\n\n const allowed = activeTimestamps.length < this.limit;\n\n const timestamps = allowed ? [...activeTimestamps, now] : activeTimestamps;\n\n this.entries.set(key, { timestamps, lastAccessed: now });\n\n const oldest = timestamps[0] ?? now;\n\n return Promise.resolve({\n allowed,\n remaining: Math.max(0, this.limit - timestamps.length),\n resetAt: oldest + this.windowMs,\n });\n }\n\n public reset(key: string): Promise<void> {\n this.entries.delete(key);\n return Promise.resolve();\n }\n\n private maybePurgeExpired(): void {\n if (Math.random() >= this.cleanupProbability) {\n return;\n }\n\n const windowStart = Date.now() - this.windowMs;\n\n for (const [key, entry] of this.entries) {\n const active = entry.timestamps.filter(t => t > windowStart);\n if (active.length === 0) {\n this.entries.delete(key);\n } else if (active.length < entry.timestamps.length) {\n this.entries.set(key, { timestamps: active, lastAccessed: entry.lastAccessed });\n }\n }\n }\n\n private evictIfOverCapacity(): void {\n if (this.entries.size <= this.maxEntries) {\n return;\n }\n\n const sorted = [...this.entries.entries()].sort(\n (a, b) => a[1].lastAccessed - b[1].lastAccessed,\n );\n\n const batchSize = Math.max(\n this.entries.size - this.maxEntries,\n Math.ceil(this.maxEntries * 0.1),\n );\n const toRemove = sorted.slice(0, batchSize);\n for (const [key] of toRemove) {\n this.entries.delete(key);\n }\n }\n}\n", "import { InjectionToken } from '@cibule/di';\n\nimport type { RateLimitStore } from './rate-limit-store';\n\nexport type RateLimitStoreAccessor = () => RateLimitStore;\n\nexport const RATE_LIMIT_STORE_ACCESSOR = new InjectionToken<RateLimitStoreAccessor>(\n 'RATE_LIMIT_STORE_ACCESSOR',\n);\n", "import type { Provider } from '@cibule/di';\n\nimport type { RateLimitStoreAccessor } from './rate-limit-store-accessor';\nimport { RATE_LIMIT_STORE_ACCESSOR } from './rate-limit-store-accessor';\n\nexport function provideRateLimitStore(factory: () => RateLimitStoreAccessor): Provider {\n return { provide: RATE_LIMIT_STORE_ACCESSOR, useFactory: factory };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/index.js ADDED
@@ -0,0 +1,186 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
+ var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
11
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
12
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
13
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
14
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
15
+ var __runInitializers = (array, flags, self, value) => {
16
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
17
+ return value;
18
+ };
19
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
20
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
21
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
22
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
23
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
24
+ return __privateGet(this, extra);
25
+ }, set [name](x) {
26
+ return __privateSet(this, extra, x);
27
+ } }, name));
28
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
29
+ for (var i = decorators.length - 1; i >= 0; i--) {
30
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
31
+ if (k) {
32
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
33
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
34
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
35
+ }
36
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
37
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
38
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
39
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
40
+ }
41
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
42
+ };
43
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
44
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
45
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
46
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
47
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
48
+
49
+ // ../di/src/lib/injectable.ts
50
+ function Injectable() {
51
+ return () => {
52
+ };
53
+ }
54
+
55
+ // ../di/src/lib/injection-token.ts
56
+ var InjectionToken = class {
57
+ constructor(description) {
58
+ this.description = description;
59
+ }
60
+ };
61
+
62
+ // src/lib/rate-limit-store.ts
63
+ var _RateLimitStore_decorators, _init;
64
+ _RateLimitStore_decorators = [Injectable()];
65
+ var RateLimitStore = class {
66
+ };
67
+ _init = __decoratorStart(null);
68
+ RateLimitStore = __decorateElement(_init, 0, "RateLimitStore", _RateLimitStore_decorators, RateLimitStore);
69
+ __runInitializers(_init, 1, RateLimitStore);
70
+
71
+ // src/lib/cf-rate-limit-store.ts
72
+ var CfRateLimitStore = class extends RateLimitStore {
73
+ binding;
74
+ constructor(binding) {
75
+ super();
76
+ this.binding = binding;
77
+ }
78
+ async check(key) {
79
+ const result = await this.binding.limit({ key });
80
+ return {
81
+ allowed: result.success,
82
+ remaining: result.success ? 1 : 0,
83
+ resetAt: 0
84
+ };
85
+ }
86
+ reset(_key) {
87
+ return Promise.resolve();
88
+ }
89
+ };
90
+
91
+ // src/lib/memory-rate-limit-store.ts
92
+ var MemoryRateLimitStore = class extends RateLimitStore {
93
+ entries = /* @__PURE__ */ new Map();
94
+ limit;
95
+ windowMs;
96
+ maxEntries;
97
+ cleanupProbability;
98
+ constructor(config) {
99
+ super();
100
+ if (config.limit < 1) {
101
+ throw new Error("limit must be at least 1");
102
+ }
103
+ if (config.windowMs < 1) {
104
+ throw new Error("windowMs must be at least 1");
105
+ }
106
+ if (config.maxEntries !== void 0 && config.maxEntries < 1) {
107
+ throw new Error("maxEntries must be at least 1");
108
+ }
109
+ if (config.cleanupProbability !== void 0 && (config.cleanupProbability < 0 || config.cleanupProbability > 1)) {
110
+ throw new Error("cleanupProbability must be between 0 and 1");
111
+ }
112
+ this.limit = config.limit;
113
+ this.windowMs = config.windowMs;
114
+ this.maxEntries = config.maxEntries ?? 1e4;
115
+ this.cleanupProbability = config.cleanupProbability ?? 0.01;
116
+ }
117
+ check(key) {
118
+ this.maybePurgeExpired();
119
+ this.evictIfOverCapacity();
120
+ const now = Date.now();
121
+ const windowStart = now - this.windowMs;
122
+ const entry = this.entries.get(key);
123
+ const activeTimestamps = entry ? entry.timestamps.filter((t) => t > windowStart) : [];
124
+ const allowed = activeTimestamps.length < this.limit;
125
+ const timestamps = allowed ? [...activeTimestamps, now] : activeTimestamps;
126
+ this.entries.set(key, { timestamps, lastAccessed: now });
127
+ const oldest = timestamps[0] ?? now;
128
+ return Promise.resolve({
129
+ allowed,
130
+ remaining: Math.max(0, this.limit - timestamps.length),
131
+ resetAt: oldest + this.windowMs
132
+ });
133
+ }
134
+ reset(key) {
135
+ this.entries.delete(key);
136
+ return Promise.resolve();
137
+ }
138
+ maybePurgeExpired() {
139
+ if (Math.random() >= this.cleanupProbability) {
140
+ return;
141
+ }
142
+ const windowStart = Date.now() - this.windowMs;
143
+ for (const [key, entry] of this.entries) {
144
+ const active = entry.timestamps.filter((t) => t > windowStart);
145
+ if (active.length === 0) {
146
+ this.entries.delete(key);
147
+ } else if (active.length < entry.timestamps.length) {
148
+ this.entries.set(key, { timestamps: active, lastAccessed: entry.lastAccessed });
149
+ }
150
+ }
151
+ }
152
+ evictIfOverCapacity() {
153
+ if (this.entries.size <= this.maxEntries) {
154
+ return;
155
+ }
156
+ const sorted = [...this.entries.entries()].sort(
157
+ (a, b) => a[1].lastAccessed - b[1].lastAccessed
158
+ );
159
+ const batchSize = Math.max(
160
+ this.entries.size - this.maxEntries,
161
+ Math.ceil(this.maxEntries * 0.1)
162
+ );
163
+ const toRemove = sorted.slice(0, batchSize);
164
+ for (const [key] of toRemove) {
165
+ this.entries.delete(key);
166
+ }
167
+ }
168
+ };
169
+
170
+ // src/lib/rate-limit-store-accessor.ts
171
+ var RATE_LIMIT_STORE_ACCESSOR = new InjectionToken(
172
+ "RATE_LIMIT_STORE_ACCESSOR"
173
+ );
174
+
175
+ // src/lib/provider.ts
176
+ function provideRateLimitStore(factory) {
177
+ return { provide: RATE_LIMIT_STORE_ACCESSOR, useFactory: factory };
178
+ }
179
+ export {
180
+ CfRateLimitStore,
181
+ MemoryRateLimitStore,
182
+ RATE_LIMIT_STORE_ACCESSOR,
183
+ RateLimitStore,
184
+ provideRateLimitStore
185
+ };
186
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../di/src/lib/injectable.ts", "../../di/src/lib/injection-token.ts", "../src/lib/rate-limit-store.ts", "../src/lib/cf-rate-limit-store.ts", "../src/lib/memory-rate-limit-store.ts", "../src/lib/rate-limit-store-accessor.ts", "../src/lib/provider.ts"],
4
+ "sourcesContent": ["import type { AbstractType, Type } from './token';\n\nexport function Injectable(): (target: Type<unknown> | AbstractType<unknown>) => void {\n return (): void => {};\n}\n", "export class InjectionToken<T> {\n declare readonly _brand: T;\n constructor(public readonly description: string) {}\n}\n", "import { Injectable } from '@cibule/di';\n\nimport type { RateLimitResult } from './rate-limit-result';\n\n@Injectable()\nexport abstract class RateLimitStore {\n public abstract check(key: string): Promise<RateLimitResult>;\n\n public abstract reset(key: string): Promise<void>;\n}\n", "import type { RateLimitBinding } from './cf-rate-limit-binding';\nimport type { RateLimitResult } from './rate-limit-result';\nimport { RateLimitStore } from './rate-limit-store';\n\nexport class CfRateLimitStore extends RateLimitStore {\n private readonly binding: RateLimitBinding;\n\n public constructor(binding: RateLimitBinding) {\n super();\n this.binding = binding;\n }\n\n public async check(key: string): Promise<RateLimitResult> {\n const result = await this.binding.limit({ key });\n\n return {\n allowed: result.success,\n remaining: result.success ? 1 : 0,\n resetAt: 0,\n };\n }\n\n public reset(_key: string): Promise<void> {\n return Promise.resolve();\n }\n}\n", "import type { RateLimitResult } from './rate-limit-result';\nimport { RateLimitStore } from './rate-limit-store';\n\nexport interface MemoryRateLimitConfig {\n readonly limit: number;\n readonly windowMs: number;\n readonly maxEntries?: number;\n readonly cleanupProbability?: number;\n}\n\ninterface BucketEntry {\n readonly timestamps: readonly number[];\n readonly lastAccessed: number;\n}\n\nexport class MemoryRateLimitStore extends RateLimitStore {\n private readonly entries = new Map<string, BucketEntry>();\n private readonly limit: number;\n private readonly windowMs: number;\n private readonly maxEntries: number;\n private readonly cleanupProbability: number;\n\n public constructor(config: MemoryRateLimitConfig) {\n super();\n\n if (config.limit < 1) {\n throw new Error('limit must be at least 1');\n }\n if (config.windowMs < 1) {\n throw new Error('windowMs must be at least 1');\n }\n if (config.maxEntries !== undefined && config.maxEntries < 1) {\n throw new Error('maxEntries must be at least 1');\n }\n if (\n config.cleanupProbability !== undefined &&\n (config.cleanupProbability < 0 || config.cleanupProbability > 1)\n ) {\n throw new Error('cleanupProbability must be between 0 and 1');\n }\n\n this.limit = config.limit;\n this.windowMs = config.windowMs;\n this.maxEntries = config.maxEntries ?? 10_000;\n this.cleanupProbability = config.cleanupProbability ?? 0.01;\n }\n\n public check(key: string): Promise<RateLimitResult> {\n this.maybePurgeExpired();\n this.evictIfOverCapacity();\n\n const now = Date.now();\n const windowStart = now - this.windowMs;\n const entry = this.entries.get(key);\n\n const activeTimestamps = entry ? entry.timestamps.filter(t => t > windowStart) : [];\n\n const allowed = activeTimestamps.length < this.limit;\n\n const timestamps = allowed ? [...activeTimestamps, now] : activeTimestamps;\n\n this.entries.set(key, { timestamps, lastAccessed: now });\n\n const oldest = timestamps[0] ?? now;\n\n return Promise.resolve({\n allowed,\n remaining: Math.max(0, this.limit - timestamps.length),\n resetAt: oldest + this.windowMs,\n });\n }\n\n public reset(key: string): Promise<void> {\n this.entries.delete(key);\n return Promise.resolve();\n }\n\n private maybePurgeExpired(): void {\n if (Math.random() >= this.cleanupProbability) {\n return;\n }\n\n const windowStart = Date.now() - this.windowMs;\n\n for (const [key, entry] of this.entries) {\n const active = entry.timestamps.filter(t => t > windowStart);\n if (active.length === 0) {\n this.entries.delete(key);\n } else if (active.length < entry.timestamps.length) {\n this.entries.set(key, { timestamps: active, lastAccessed: entry.lastAccessed });\n }\n }\n }\n\n private evictIfOverCapacity(): void {\n if (this.entries.size <= this.maxEntries) {\n return;\n }\n\n const sorted = [...this.entries.entries()].sort(\n (a, b) => a[1].lastAccessed - b[1].lastAccessed,\n );\n\n const batchSize = Math.max(\n this.entries.size - this.maxEntries,\n Math.ceil(this.maxEntries * 0.1),\n );\n const toRemove = sorted.slice(0, batchSize);\n for (const [key] of toRemove) {\n this.entries.delete(key);\n }\n }\n}\n", "import { InjectionToken } from '@cibule/di';\n\nimport type { RateLimitStore } from './rate-limit-store';\n\nexport type RateLimitStoreAccessor = () => RateLimitStore;\n\nexport const RATE_LIMIT_STORE_ACCESSOR = new InjectionToken<RateLimitStoreAccessor>(\n 'RATE_LIMIT_STORE_ACCESSOR',\n);\n", "import type { Provider } from '@cibule/di';\n\nimport type { RateLimitStoreAccessor } from './rate-limit-store-accessor';\nimport { RATE_LIMIT_STORE_ACCESSOR } from './rate-limit-store-accessor';\n\nexport function provideRateLimitStore(factory: () => RateLimitStoreAccessor): Provider {\n return { provide: RATE_LIMIT_STORE_ACCESSOR, useFactory: factory };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@cibule/rate-limit",
3
+ "version": "0.3.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/dts/packages/rate-limit/src/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/dts/packages/rate-limit/src/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/dts/packages/rate-limit/src/index.d.ts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": false,
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://gitlab.com/LadaBr/cibule",
28
+ "directory": "packages/rate-limit"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@cibule/di": "^0.3.0"
35
+ }
36
+ }