@stimulcross/rate-limiter 0.0.1 → 0.0.3
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/README.md +20 -0
- package/lib/core/cancellable.d.ts +5 -0
- package/lib/core/cancellable.js +2 -0
- package/lib/core/clock.d.ts +10 -0
- package/lib/core/clock.js +2 -0
- package/{src/core/decision.ts → lib/core/decision.d.ts} +7 -11
- package/lib/core/decision.js +2 -0
- package/lib/core/rate-limit-policy.d.ts +14 -0
- package/lib/core/rate-limit-policy.js +2 -0
- package/lib/core/rate-limiter-status.d.ts +14 -0
- package/lib/core/rate-limiter-status.js +2 -0
- package/lib/core/rate-limiter.d.ts +34 -0
- package/lib/core/rate-limiter.js +2 -0
- package/lib/core/state-storage.d.ts +46 -0
- package/lib/core/state-storage.js +2 -0
- package/lib/enums/rate-limit-error-code.d.ts +26 -0
- package/lib/enums/rate-limit-error-code.js +27 -0
- package/lib/errors/custom.error.d.ts +6 -0
- package/lib/errors/custom.error.js +13 -0
- package/lib/errors/invalid-cost.error.d.ts +16 -0
- package/lib/errors/invalid-cost.error.js +26 -0
- package/lib/errors/rate-limit.error.d.ts +37 -0
- package/lib/errors/rate-limit.error.js +75 -0
- package/lib/errors/rate-limiter-destroyed.error.d.ts +7 -0
- package/lib/errors/rate-limiter-destroyed.error.js +9 -0
- package/{src/index.ts → lib/index.d.ts} +1 -0
- package/lib/index.js +5 -0
- package/lib/interfaces/rate-limiter-options.d.ts +76 -0
- package/lib/interfaces/rate-limiter-options.js +2 -0
- package/lib/interfaces/rate-limiter-queue-options.d.ts +42 -0
- package/lib/interfaces/rate-limiter-queue-options.js +2 -0
- package/lib/interfaces/rate-limiter-run-options.d.ts +52 -0
- package/lib/interfaces/rate-limiter-run-options.js +2 -0
- package/lib/limiters/abstract-rate-limiter.d.ts +44 -0
- package/lib/limiters/abstract-rate-limiter.js +133 -0
- package/lib/limiters/composite.policy.d.ts +15 -0
- package/lib/limiters/composite.policy.js +73 -0
- package/lib/limiters/fixed-window/fixed-window.limiter.d.ts +33 -0
- package/lib/limiters/fixed-window/fixed-window.limiter.js +85 -0
- package/lib/limiters/fixed-window/fixed-window.options.d.ts +27 -0
- package/lib/limiters/fixed-window/fixed-window.options.js +2 -0
- package/lib/limiters/fixed-window/fixed-window.policy.d.ts +19 -0
- package/lib/limiters/fixed-window/fixed-window.policy.js +121 -0
- package/{src/limiters/fixed-window/fixed-window.state.ts → lib/limiters/fixed-window/fixed-window.state.d.ts} +4 -3
- package/lib/limiters/fixed-window/fixed-window.state.js +2 -0
- package/lib/limiters/fixed-window/fixed-window.status.d.ts +39 -0
- package/lib/limiters/fixed-window/fixed-window.status.js +2 -0
- package/{src/limiters/fixed-window/index.ts → lib/limiters/fixed-window/index.d.ts} +1 -0
- package/lib/limiters/fixed-window/index.js +2 -0
- package/lib/limiters/generic-cell/generic-cell.limiter.d.ts +30 -0
- package/lib/limiters/generic-cell/generic-cell.limiter.js +74 -0
- package/lib/limiters/generic-cell/generic-cell.options.d.ts +22 -0
- package/lib/limiters/generic-cell/generic-cell.options.js +2 -0
- package/lib/limiters/generic-cell/generic-cell.policy.d.ts +18 -0
- package/lib/limiters/generic-cell/generic-cell.policy.js +87 -0
- package/{src/limiters/generic-cell/generic-cell.state.ts → lib/limiters/generic-cell/generic-cell.state.d.ts} +2 -1
- package/lib/limiters/generic-cell/generic-cell.state.js +2 -0
- package/lib/limiters/generic-cell/generic-cell.status.d.ts +49 -0
- package/lib/limiters/generic-cell/generic-cell.status.js +2 -0
- package/{src/limiters/generic-cell/index.ts → lib/limiters/generic-cell/index.d.ts} +1 -0
- package/lib/limiters/generic-cell/index.js +2 -0
- package/{src/limiters/http-response-based/http-limit-info.extractor.ts → lib/limiters/http-response-based/http-limit-info.extractor.d.ts} +2 -6
- package/lib/limiters/http-response-based/http-limit-info.extractor.js +2 -0
- package/lib/limiters/http-response-based/http-limit.info.d.ts +39 -0
- package/lib/limiters/http-response-based/http-limit.info.js +2 -0
- package/{src/limiters/http-response-based/http-response-based-limiter.options.ts → lib/limiters/http-response-based/http-response-based-limiter.options.d.ts} +9 -10
- package/lib/limiters/http-response-based/http-response-based-limiter.options.js +2 -0
- package/lib/limiters/http-response-based/http-response-based-limiter.state.d.ts +14 -0
- package/lib/limiters/http-response-based/http-response-based-limiter.state.js +2 -0
- package/lib/limiters/http-response-based/http-response-based-limiter.status.d.ts +70 -0
- package/lib/limiters/http-response-based/http-response-based-limiter.status.js +2 -0
- package/lib/limiters/http-response-based/http-response-based.limiter.d.ts +56 -0
- package/lib/limiters/http-response-based/http-response-based.limiter.js +386 -0
- package/{src/limiters/http-response-based/index.ts → lib/limiters/http-response-based/index.d.ts} +1 -0
- package/lib/limiters/http-response-based/index.js +2 -0
- package/{src/limiters/leaky-bucket/index.ts → lib/limiters/leaky-bucket/index.d.ts} +1 -0
- package/lib/limiters/leaky-bucket/index.js +2 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.limiter.d.ts +30 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.limiter.js +75 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.options.d.ts +22 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.options.js +2 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.policy.d.ts +19 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.policy.js +101 -0
- package/{src/limiters/leaky-bucket/leaky-bucket.state.ts → lib/limiters/leaky-bucket/leaky-bucket.state.d.ts} +3 -2
- package/lib/limiters/leaky-bucket/leaky-bucket.state.js +2 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.status.d.ts +31 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.status.js +2 -0
- package/{src/limiters/sliding-window-counter/index.ts → lib/limiters/sliding-window-counter/index.d.ts} +2 -4
- package/lib/limiters/sliding-window-counter/index.js +2 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.limiter.d.ts +28 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.limiter.js +47 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.options.d.ts +16 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.options.js +2 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.policy.d.ts +18 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.policy.js +128 -0
- package/{src/limiters/sliding-window-counter/sliding-window-counter.state.ts → lib/limiters/sliding-window-counter/sliding-window-counter.state.d.ts} +4 -3
- package/lib/limiters/sliding-window-counter/sliding-window-counter.state.js +2 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.status.d.ts +45 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.status.js +2 -0
- package/{src/limiters/sliding-window-log/index.ts → lib/limiters/sliding-window-log/index.d.ts} +1 -0
- package/lib/limiters/sliding-window-log/index.js +2 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.limiter.d.ts +27 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.limiter.js +44 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.options.d.ts +16 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.options.js +2 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.policy.d.ts +18 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.policy.js +124 -0
- package/{src/limiters/sliding-window-log/sliding-window-log.state.ts → lib/limiters/sliding-window-log/sliding-window-log.state.d.ts} +5 -6
- package/lib/limiters/sliding-window-log/sliding-window-log.state.js +2 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.status.d.ts +39 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.status.js +2 -0
- package/{src/limiters/token-bucket/index.ts → lib/limiters/token-bucket/index.d.ts} +1 -0
- package/lib/limiters/token-bucket/index.js +2 -0
- package/lib/limiters/token-bucket/token-bucket.limiter.d.ts +30 -0
- package/lib/limiters/token-bucket/token-bucket.limiter.js +75 -0
- package/{src/limiters/token-bucket/token-bucket.options.ts → lib/limiters/token-bucket/token-bucket.options.d.ts} +9 -10
- package/lib/limiters/token-bucket/token-bucket.options.js +2 -0
- package/lib/limiters/token-bucket/token-bucket.policy.d.ts +19 -0
- package/lib/limiters/token-bucket/token-bucket.policy.js +116 -0
- package/{src/limiters/token-bucket/token-bucket.state.ts → lib/limiters/token-bucket/token-bucket.state.d.ts} +4 -3
- package/lib/limiters/token-bucket/token-bucket.state.js +2 -0
- package/lib/limiters/token-bucket/token-bucket.status.d.ts +31 -0
- package/lib/limiters/token-bucket/token-bucket.status.js +2 -0
- package/lib/runtime/default-clock.d.ts +4 -0
- package/lib/runtime/default-clock.js +7 -0
- package/lib/runtime/execution-tickets.d.ts +12 -0
- package/lib/runtime/execution-tickets.js +27 -0
- package/lib/runtime/in-memory-state-store.d.ts +19 -0
- package/lib/runtime/in-memory-state-store.js +97 -0
- package/lib/runtime/rate-limiter.executor.d.ts +47 -0
- package/lib/runtime/rate-limiter.executor.js +196 -0
- package/lib/runtime/semaphore.d.ts +9 -0
- package/lib/runtime/semaphore.js +28 -0
- package/lib/runtime/task.d.ts +41 -0
- package/lib/runtime/task.js +101 -0
- package/{src/types/limit-behavior.ts → lib/types/limit-behavior.d.ts} +1 -0
- package/lib/types/limit-behavior.js +2 -0
- package/lib/utils/generate-random-string.d.ts +3 -0
- package/lib/utils/generate-random-string.js +13 -0
- package/lib/utils/promise-with-resolvers.d.ts +9 -0
- package/lib/utils/promise-with-resolvers.js +15 -0
- package/lib/utils/sanitize-error.d.ts +3 -0
- package/lib/utils/sanitize-error.js +5 -0
- package/lib/utils/sanitize-priority.d.ts +4 -0
- package/lib/utils/sanitize-priority.js +18 -0
- package/lib/utils/validate-cost.d.ts +3 -0
- package/lib/utils/validate-cost.js +14 -0
- package/package.json +13 -2
- package/.editorconfig +0 -21
- package/.github/workflows/node.yml +0 -87
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.megaignore +0 -8
- package/.prettierignore +0 -3
- package/commitlint.config.js +0 -8
- package/eslint.config.js +0 -65
- package/lint-staged.config.js +0 -4
- package/prettier.config.cjs +0 -1
- package/src/core/cancellable.ts +0 -4
- package/src/core/clock.ts +0 -9
- package/src/core/rate-limit-policy.ts +0 -15
- package/src/core/rate-limiter-status.ts +0 -14
- package/src/core/rate-limiter.ts +0 -37
- package/src/core/state-storage.ts +0 -51
- package/src/enums/rate-limit-error-code.ts +0 -29
- package/src/errors/custom.error.ts +0 -14
- package/src/errors/invalid-cost.error.ts +0 -33
- package/src/errors/rate-limit.error.ts +0 -91
- package/src/errors/rate-limiter-destroyed.error.ts +0 -8
- package/src/interfaces/rate-limiter-options.ts +0 -84
- package/src/interfaces/rate-limiter-queue-options.ts +0 -45
- package/src/interfaces/rate-limiter-run-options.ts +0 -58
- package/src/limiters/abstract-rate-limiter.ts +0 -206
- package/src/limiters/composite.policy.ts +0 -102
- package/src/limiters/fixed-window/fixed-window.limiter.ts +0 -121
- package/src/limiters/fixed-window/fixed-window.options.ts +0 -29
- package/src/limiters/fixed-window/fixed-window.policy.ts +0 -159
- package/src/limiters/fixed-window/fixed-window.status.ts +0 -46
- package/src/limiters/generic-cell/generic-cell.limiter.ts +0 -108
- package/src/limiters/generic-cell/generic-cell.options.ts +0 -23
- package/src/limiters/generic-cell/generic-cell.policy.ts +0 -115
- package/src/limiters/generic-cell/generic-cell.status.ts +0 -54
- package/src/limiters/http-response-based/http-limit.info.ts +0 -41
- package/src/limiters/http-response-based/http-response-based-limiter.state.ts +0 -13
- package/src/limiters/http-response-based/http-response-based-limiter.status.ts +0 -74
- package/src/limiters/http-response-based/http-response-based.limiter.ts +0 -512
- package/src/limiters/leaky-bucket/leaky-bucket.limiter.ts +0 -105
- package/src/limiters/leaky-bucket/leaky-bucket.options.ts +0 -23
- package/src/limiters/leaky-bucket/leaky-bucket.policy.ts +0 -134
- package/src/limiters/leaky-bucket/leaky-bucket.status.ts +0 -36
- package/src/limiters/sliding-window-counter/sliding-window-counter.limiter.ts +0 -76
- package/src/limiters/sliding-window-counter/sliding-window-counter.options.ts +0 -20
- package/src/limiters/sliding-window-counter/sliding-window-counter.policy.ts +0 -167
- package/src/limiters/sliding-window-counter/sliding-window-counter.status.ts +0 -53
- package/src/limiters/sliding-window-log/sliding-window-log.limiter.ts +0 -65
- package/src/limiters/sliding-window-log/sliding-window-log.options.ts +0 -20
- package/src/limiters/sliding-window-log/sliding-window-log.policy.ts +0 -166
- package/src/limiters/sliding-window-log/sliding-window-log.status.ts +0 -44
- package/src/limiters/token-bucket/token-bucket.limiter.ts +0 -110
- package/src/limiters/token-bucket/token-bucket.policy.ts +0 -155
- package/src/limiters/token-bucket/token-bucket.status.ts +0 -36
- package/src/runtime/default-clock.ts +0 -8
- package/src/runtime/execution-tickets.ts +0 -34
- package/src/runtime/in-memory-state-store.ts +0 -135
- package/src/runtime/rate-limiter.executor.ts +0 -286
- package/src/runtime/semaphore.ts +0 -31
- package/src/runtime/task.ts +0 -141
- package/src/utils/generate-random-string.ts +0 -16
- package/src/utils/promise-with-resolvers.ts +0 -23
- package/src/utils/sanitize-error.ts +0 -4
- package/src/utils/sanitize-priority.ts +0 -22
- package/src/utils/validate-cost.ts +0 -16
- package/tests/integration/limiters/fixed-window.limiter.spec.ts +0 -371
- package/tests/integration/limiters/generic-cell.limiter.spec.ts +0 -361
- package/tests/integration/limiters/http-response-based.limiter.spec.ts +0 -833
- package/tests/integration/limiters/leaky-bucket.spec.ts +0 -357
- package/tests/integration/limiters/sliding-window-counter.limiter.spec.ts +0 -175
- package/tests/integration/limiters/sliding-window-log.spec.ts +0 -185
- package/tests/integration/limiters/token-bucket.limiter.spec.ts +0 -363
- package/tests/tsconfig.json +0 -4
- package/tests/unit/policies/composite.policy.spec.ts +0 -244
- package/tests/unit/policies/fixed-window.policy.spec.ts +0 -260
- package/tests/unit/policies/generic-cell.policy.spec.ts +0 -178
- package/tests/unit/policies/leaky-bucket.policy.spec.ts +0 -215
- package/tests/unit/policies/sliding-window-counter.policy.spec.ts +0 -209
- package/tests/unit/policies/sliding-window-log.policy.spec.ts +0 -285
- package/tests/unit/policies/token-bucket.policy.spec.ts +0 -371
- package/tests/unit/runtime/execution-tickets.spec.ts +0 -121
- package/tests/unit/runtime/in-memory-state-store.spec.ts +0 -238
- package/tests/unit/runtime/rate-limiter.executor.spec.ts +0 -353
- package/tests/unit/runtime/semaphore.spec.ts +0 -98
- package/tests/unit/runtime/task.spec.ts +0 -182
- package/tests/unit/utils/generate-random-string.spec.ts +0 -51
- package/tests/unit/utils/promise-with-resolvers.spec.ts +0 -57
- package/tests/unit/utils/sanitize-priority.spec.ts +0 -46
- package/tests/unit/utils/validate-cost.spec.ts +0 -48
- package/tsconfig.json +0 -14
- package/vitest.config.js +0 -22
package/README.md
CHANGED
|
@@ -5,3 +5,23 @@ A collection of rate limiters designed primarily for **outbound request throttli
|
|
|
5
5
|
They are suited for client-side usage to respect third-party limits or protect internal resources.
|
|
6
6
|
|
|
7
7
|
While it is technically possible to use these limiters for server-side traffic backed by a distributed store like Redis, it is **not recommended**. The algorithms evaluate state within the application process, so distributed usage requires multiple network operations per request introducing significant round-trip latency.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
**npm**
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npm add @stimulcross/rate-limiter
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**pnpm**
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
pnpm add @stimulcross/rate-limiter
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**yarn**
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
yarn add @stimulcross/rate-limiter
|
|
27
|
+
```
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
/** @internal */
|
|
2
2
|
export type DecisionKind = 'allow' | 'deny' | 'delay';
|
|
3
|
-
|
|
4
3
|
/** @internal */
|
|
5
4
|
export interface DecisionBase {
|
|
6
|
-
|
|
5
|
+
kind: DecisionKind;
|
|
7
6
|
}
|
|
8
|
-
|
|
9
7
|
/** @internal */
|
|
10
8
|
export interface DecisionAllow extends DecisionBase {
|
|
11
|
-
|
|
9
|
+
kind: Extract<DecisionKind, 'allow'>;
|
|
12
10
|
}
|
|
13
|
-
|
|
14
11
|
/** @internal */
|
|
15
12
|
export interface DecisionDeny extends DecisionBase {
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
kind: Extract<DecisionKind, 'deny'>;
|
|
14
|
+
retryAt: number;
|
|
18
15
|
}
|
|
19
|
-
|
|
20
16
|
/** @internal */
|
|
21
17
|
export interface DecisionDelay extends DecisionBase {
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
kind: Extract<DecisionKind, 'delay'>;
|
|
19
|
+
runAt: number;
|
|
24
20
|
}
|
|
25
|
-
|
|
26
21
|
/** @internal */
|
|
27
22
|
export type Decision = DecisionAllow | DecisionDeny | DecisionDelay;
|
|
23
|
+
//# sourceMappingURL=decision.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Decision } from './decision.js';
|
|
2
|
+
/** @internal */
|
|
3
|
+
export interface RateLimitPolicyResult<S> {
|
|
4
|
+
decision: Decision;
|
|
5
|
+
nextState: S;
|
|
6
|
+
}
|
|
7
|
+
/** @internal */
|
|
8
|
+
export interface RateLimitPolicy<TState extends object = object, TStatus extends object = object> {
|
|
9
|
+
getInitialState(now: number): TState;
|
|
10
|
+
getStatus(state: TState, now: number): TStatus;
|
|
11
|
+
evaluate(state: TState, now: number, cost: number, shouldReserve?: boolean): RateLimitPolicyResult<TState>;
|
|
12
|
+
revert(state: TState, cost: number, now: number): TState;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=rate-limit-policy.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The status of the rate limiter.
|
|
3
|
+
*/
|
|
4
|
+
export interface RateLimiterStatus {
|
|
5
|
+
/**
|
|
6
|
+
* The timestamp (in milliseconds) when a rate limiter will allow a single request.
|
|
7
|
+
*/
|
|
8
|
+
readonly nextAvailableAt: number;
|
|
9
|
+
/**
|
|
10
|
+
* The timestamp (in milliseconds) when the rate limiter will reset.
|
|
11
|
+
*/
|
|
12
|
+
readonly resetAt: number;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=rate-limiter-status.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type RateLimiterRunOptions } from '../interfaces/rate-limiter-run-options.js';
|
|
2
|
+
/**
|
|
3
|
+
* Rate limiter interface.
|
|
4
|
+
*
|
|
5
|
+
* @template TStatus The type of the rate limiter status returned by {@link getStatus} method.
|
|
6
|
+
*/
|
|
7
|
+
export interface RateLimiter<TStatus extends object = object> {
|
|
8
|
+
/**
|
|
9
|
+
* Runs the given task.
|
|
10
|
+
*
|
|
11
|
+
* @param task The task to run.
|
|
12
|
+
* @param options Options for running the task.
|
|
13
|
+
*/
|
|
14
|
+
run<T>(task: () => T | Promise<T>, options?: RateLimiterRunOptions): Promise<T>;
|
|
15
|
+
/**
|
|
16
|
+
* Clears the rate limiter state.
|
|
17
|
+
*
|
|
18
|
+
* @param key The optional key to clear the state for.
|
|
19
|
+
*/
|
|
20
|
+
clear(key?: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Gets the rate limiter's status.
|
|
23
|
+
*
|
|
24
|
+
* @param key The optional key to get the status for.
|
|
25
|
+
*/
|
|
26
|
+
getStatus?(key?: string): Promise<TStatus>;
|
|
27
|
+
/**
|
|
28
|
+
* Destroys the rate limiter.
|
|
29
|
+
*
|
|
30
|
+
* The limiter cannot be used after it has been destroyed. It should be used only for graceful shutdown.
|
|
31
|
+
*/
|
|
32
|
+
destroy?(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State storage interface.
|
|
3
|
+
*/
|
|
4
|
+
export interface StateStorage<TState> {
|
|
5
|
+
/**
|
|
6
|
+
* Gets the state for the given key.
|
|
7
|
+
*
|
|
8
|
+
* @param key The key to get the state for.
|
|
9
|
+
*/
|
|
10
|
+
get(key: string): Promise<TState | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Sets the state for the given key.
|
|
13
|
+
*
|
|
14
|
+
* @param key The key to set the state for.
|
|
15
|
+
* @param value The state to set.
|
|
16
|
+
* @param ttlMs Optional TTL in milliseconds.
|
|
17
|
+
*/
|
|
18
|
+
set(key: string, value: TState, ttlMs?: number): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Deletes the state for the given key.
|
|
21
|
+
*
|
|
22
|
+
* @param key The key to delete the state for.
|
|
23
|
+
*/
|
|
24
|
+
delete(key: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Clears all stored states.
|
|
27
|
+
*/
|
|
28
|
+
clear(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Destroys the storage.
|
|
31
|
+
*/
|
|
32
|
+
destroy?(): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Acquires a lock for the given key.
|
|
35
|
+
*
|
|
36
|
+
* @param key The key to acquire the lock for.
|
|
37
|
+
*/
|
|
38
|
+
acquireLock?(key: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Releases the lock for the given key.
|
|
41
|
+
*
|
|
42
|
+
* @param key The key to release the lock for.
|
|
43
|
+
*/
|
|
44
|
+
releaseLock?(key: string): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=state-storage.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limiter error codes.
|
|
3
|
+
*/
|
|
4
|
+
export declare enum RateLimitErrorCode {
|
|
5
|
+
/**
|
|
6
|
+
* Indicates that the limit has been reached.
|
|
7
|
+
*/
|
|
8
|
+
LimitExceeded = "LIMIT_EXCEEDED",
|
|
9
|
+
/**
|
|
10
|
+
* Indicates that the execution queue is full.
|
|
11
|
+
*/
|
|
12
|
+
QueueOverflow = "QUEUE_OVERFLOW",
|
|
13
|
+
/**
|
|
14
|
+
* Indicates that the task has expired.
|
|
15
|
+
*/
|
|
16
|
+
Expired = "EXPIRED",
|
|
17
|
+
/**
|
|
18
|
+
* Indicates that a task was cleared before it was executed.
|
|
19
|
+
*/
|
|
20
|
+
Destroyed = "DESTROYED",
|
|
21
|
+
/**
|
|
22
|
+
* Indicates that a task was canceled via abort controller before it was executed.
|
|
23
|
+
*/
|
|
24
|
+
Cancelled = "CANCELLED"
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=rate-limit-error-code.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limiter error codes.
|
|
3
|
+
*/
|
|
4
|
+
export var RateLimitErrorCode;
|
|
5
|
+
(function (RateLimitErrorCode) {
|
|
6
|
+
/**
|
|
7
|
+
* Indicates that the limit has been reached.
|
|
8
|
+
*/
|
|
9
|
+
RateLimitErrorCode["LimitExceeded"] = "LIMIT_EXCEEDED";
|
|
10
|
+
/**
|
|
11
|
+
* Indicates that the execution queue is full.
|
|
12
|
+
*/
|
|
13
|
+
RateLimitErrorCode["QueueOverflow"] = "QUEUE_OVERFLOW";
|
|
14
|
+
/**
|
|
15
|
+
* Indicates that the task has expired.
|
|
16
|
+
*/
|
|
17
|
+
RateLimitErrorCode["Expired"] = "EXPIRED";
|
|
18
|
+
/**
|
|
19
|
+
* Indicates that a task was cleared before it was executed.
|
|
20
|
+
*/
|
|
21
|
+
RateLimitErrorCode["Destroyed"] = "DESTROYED";
|
|
22
|
+
/**
|
|
23
|
+
* Indicates that a task was canceled via abort controller before it was executed.
|
|
24
|
+
*/
|
|
25
|
+
RateLimitErrorCode["Cancelled"] = "CANCELLED";
|
|
26
|
+
})(RateLimitErrorCode || (RateLimitErrorCode = {}));
|
|
27
|
+
//# sourceMappingURL=rate-limit-error-code.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export class CustomError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
7
|
+
Error.captureStackTrace?.(this, new.target.constructor);
|
|
8
|
+
}
|
|
9
|
+
get name() {
|
|
10
|
+
return this.constructor.name;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=custom.error.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CustomError } from './custom.error.js';
|
|
2
|
+
export interface InvalidCostErrorPlainObject extends Error {
|
|
3
|
+
cost: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when the cost is invalid.
|
|
7
|
+
*
|
|
8
|
+
* The cost must be a positive integer or zero.
|
|
9
|
+
*/
|
|
10
|
+
export declare class InvalidCostError extends CustomError {
|
|
11
|
+
private readonly _cost;
|
|
12
|
+
constructor(message: string, _cost: number);
|
|
13
|
+
get cost(): number;
|
|
14
|
+
toJSON(): InvalidCostErrorPlainObject;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=invalid-cost.error.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CustomError } from './custom.error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when the cost is invalid.
|
|
4
|
+
*
|
|
5
|
+
* The cost must be a positive integer or zero.
|
|
6
|
+
*/
|
|
7
|
+
export class InvalidCostError extends CustomError {
|
|
8
|
+
_cost;
|
|
9
|
+
constructor(message, _cost) {
|
|
10
|
+
super(message);
|
|
11
|
+
this._cost = _cost;
|
|
12
|
+
}
|
|
13
|
+
get cost() {
|
|
14
|
+
return this._cost;
|
|
15
|
+
}
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
17
|
+
toJSON() {
|
|
18
|
+
return {
|
|
19
|
+
name: this.name,
|
|
20
|
+
message: this.message,
|
|
21
|
+
cost: this._cost,
|
|
22
|
+
stack: this.stack,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=invalid-cost.error.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RateLimitErrorCode } from '../enums/rate-limit-error-code.js';
|
|
2
|
+
export interface RateLimitErrorPlainObject extends Error {
|
|
3
|
+
code: RateLimitErrorCode;
|
|
4
|
+
retryAt: number | null;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* An error thrown when a rate limit is exceeded.
|
|
8
|
+
*
|
|
9
|
+
* This error has a {@link code} property that indicates the type of error.
|
|
10
|
+
*
|
|
11
|
+
* The `code` can be:
|
|
12
|
+
* - `LIMIT_EXCEEDED` - When the rate limit is exceeded.
|
|
13
|
+
* - `QUEUE_OVERFLOW` - When the queue is full (if the limiter has a queue and the capacity has been exceeded).
|
|
14
|
+
* - `EXPIRED` - When the task has expired (waited too long in the queue). This is related to the `maxWaitMs` option.
|
|
15
|
+
* **NOTE:** This is never thrown if the task is executing too long. Such scenarios should be handled by the
|
|
16
|
+
* task itself.
|
|
17
|
+
* - `DESTROYED` - When the task is destroyed due to the rate limiter's `clear()` or `destroy()` methods.
|
|
18
|
+
* - `CANCELLED` - When the task is cancelled using an abort signal.
|
|
19
|
+
*/
|
|
20
|
+
export declare class RateLimitError extends Error {
|
|
21
|
+
private readonly _code;
|
|
22
|
+
private readonly _retryAt;
|
|
23
|
+
/** @internal */
|
|
24
|
+
constructor(code: RateLimitErrorCode, retryAt?: number, message?: string);
|
|
25
|
+
/**
|
|
26
|
+
* The error code.
|
|
27
|
+
*/
|
|
28
|
+
get code(): RateLimitErrorCode;
|
|
29
|
+
/**
|
|
30
|
+
* The timestamp (in milliseconds) when the task can be retried.
|
|
31
|
+
*
|
|
32
|
+
* Can be `null` if the retry time is not known.
|
|
33
|
+
*/
|
|
34
|
+
get retryAt(): number | null;
|
|
35
|
+
toJSON(): RateLimitErrorPlainObject;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=rate-limit.error.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { RateLimitErrorCode } from '../enums/rate-limit-error-code.js';
|
|
2
|
+
/**
|
|
3
|
+
* An error thrown when a rate limit is exceeded.
|
|
4
|
+
*
|
|
5
|
+
* This error has a {@link code} property that indicates the type of error.
|
|
6
|
+
*
|
|
7
|
+
* The `code` can be:
|
|
8
|
+
* - `LIMIT_EXCEEDED` - When the rate limit is exceeded.
|
|
9
|
+
* - `QUEUE_OVERFLOW` - When the queue is full (if the limiter has a queue and the capacity has been exceeded).
|
|
10
|
+
* - `EXPIRED` - When the task has expired (waited too long in the queue). This is related to the `maxWaitMs` option.
|
|
11
|
+
* **NOTE:** This is never thrown if the task is executing too long. Such scenarios should be handled by the
|
|
12
|
+
* task itself.
|
|
13
|
+
* - `DESTROYED` - When the task is destroyed due to the rate limiter's `clear()` or `destroy()` methods.
|
|
14
|
+
* - `CANCELLED` - When the task is cancelled using an abort signal.
|
|
15
|
+
*/
|
|
16
|
+
export class RateLimitError extends Error {
|
|
17
|
+
_code;
|
|
18
|
+
_retryAt;
|
|
19
|
+
/** @internal */
|
|
20
|
+
constructor(code, retryAt, message) {
|
|
21
|
+
if (!message) {
|
|
22
|
+
switch (code) {
|
|
23
|
+
case RateLimitErrorCode.LimitExceeded: {
|
|
24
|
+
message = `Rate limit exceeded.${retryAt ? ` Retry at ${new Date(retryAt).toISOString()}.` : ''}`;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
case RateLimitErrorCode.QueueOverflow: {
|
|
28
|
+
message = 'Queue overflow.';
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case RateLimitErrorCode.Expired: {
|
|
32
|
+
message = 'Task expired.';
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case RateLimitErrorCode.Destroyed: {
|
|
36
|
+
message = 'Task destroyed.';
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case RateLimitErrorCode.Cancelled: {
|
|
40
|
+
message = 'Task cancelled.';
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
// No default
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
super(message);
|
|
47
|
+
this._code = code;
|
|
48
|
+
this._retryAt = retryAt ?? null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The error code.
|
|
52
|
+
*/
|
|
53
|
+
get code() {
|
|
54
|
+
return this._code;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The timestamp (in milliseconds) when the task can be retried.
|
|
58
|
+
*
|
|
59
|
+
* Can be `null` if the retry time is not known.
|
|
60
|
+
*/
|
|
61
|
+
get retryAt() {
|
|
62
|
+
return this._retryAt ?? null;
|
|
63
|
+
}
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
65
|
+
toJSON() {
|
|
66
|
+
return {
|
|
67
|
+
name: this.name,
|
|
68
|
+
message: this.message,
|
|
69
|
+
code: this._code,
|
|
70
|
+
retryAt: this._retryAt,
|
|
71
|
+
stack: this.stack,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=rate-limit.error.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An error thrown when running a task on a destroyed rate limiter.
|
|
3
|
+
*/
|
|
4
|
+
export class RateLimiterDestroyedError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super('Rate limiter has been destroyed and cannot be used.');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=rate-limiter-destroyed.error.js.map
|
|
@@ -9,3 +9,4 @@ export { type RateLimitErrorPlainObject, RateLimitError } from './errors/rate-li
|
|
|
9
9
|
export { RateLimiterDestroyedError } from './errors/rate-limiter-destroyed.error.js';
|
|
10
10
|
export { type InvalidCostErrorPlainObject, InvalidCostError } from './errors/invalid-cost.error.js';
|
|
11
11
|
export { RateLimitErrorCode } from './enums/rate-limit-error-code.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { RateLimitError } from './errors/rate-limit.error.js';
|
|
2
|
+
export { RateLimiterDestroyedError } from './errors/rate-limiter-destroyed.error.js';
|
|
3
|
+
export { InvalidCostError } from './errors/invalid-cost.error.js';
|
|
4
|
+
export { RateLimitErrorCode } from './enums/rate-limit-error-code.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type LoggerOptions } from '@stimulcross/logger';
|
|
2
|
+
import { type RateLimiterQueueOptions } from './rate-limiter-queue-options.js';
|
|
3
|
+
import { type Clock } from '../core/clock.js';
|
|
4
|
+
import { type StateStorage } from '../core/state-storage.js';
|
|
5
|
+
import { type LimitBehavior } from '../types/limit-behavior.js';
|
|
6
|
+
/**
|
|
7
|
+
* A function that generates a unique ID for the task.
|
|
8
|
+
*/
|
|
9
|
+
export type IdGenerator = () => string;
|
|
10
|
+
/**
|
|
11
|
+
* A function that resolves a global key for the given key.
|
|
12
|
+
*/
|
|
13
|
+
export type KeyResolver = (key?: string) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Rate limiter options.
|
|
16
|
+
*
|
|
17
|
+
* @template TState The type of the rate limiter state.
|
|
18
|
+
*/
|
|
19
|
+
export interface RateLimiterOptions<TState = unknown> {
|
|
20
|
+
/**
|
|
21
|
+
* A custom clock implementation.
|
|
22
|
+
*/
|
|
23
|
+
clock?: Clock;
|
|
24
|
+
/**
|
|
25
|
+
* An optional key that can be either a string or a key resolver function.
|
|
26
|
+
*
|
|
27
|
+
* If a string is provided, it will be used as a global prefix for all keys.
|
|
28
|
+
*
|
|
29
|
+
* If a function is provided, it will be called with the provided key and should return a unique global key.
|
|
30
|
+
*
|
|
31
|
+
* Useful for distributed stores.
|
|
32
|
+
*
|
|
33
|
+
* @default limiter
|
|
34
|
+
*/
|
|
35
|
+
key?: string | KeyResolver;
|
|
36
|
+
/**
|
|
37
|
+
* A custom ID factory function.
|
|
38
|
+
*
|
|
39
|
+
* It is used to generate unique IDs for each task for logging and debugging purposes.
|
|
40
|
+
*/
|
|
41
|
+
idGenerator?: IdGenerator;
|
|
42
|
+
/**
|
|
43
|
+
* State storage implementation for persisting rate limiter state.
|
|
44
|
+
*
|
|
45
|
+
* By default, an in-memory state store is used, which is unique to each process.
|
|
46
|
+
* This is suitable for single-instance applications or when rate limiting doesn't need
|
|
47
|
+
* to be shared across multiple processes.
|
|
48
|
+
*
|
|
49
|
+
* For distributed applications, you can implement a custom state store using Redis, Memcached,
|
|
50
|
+
* or other distributed storage systems. However, be aware that this introduces network latency
|
|
51
|
+
* due to multiple round-trips (typically 3-4 requests with lock acquire/release).
|
|
52
|
+
*
|
|
53
|
+
* For distributed rate limiting, consider using, for example, Redis with Lua scripts.
|
|
54
|
+
* This allows atomic operations and minimizes latency.
|
|
55
|
+
*/
|
|
56
|
+
store?: StateStorage<TState>;
|
|
57
|
+
/**
|
|
58
|
+
* Defines the behavior when the limit is reached.
|
|
59
|
+
*
|
|
60
|
+
* Available options:
|
|
61
|
+
* - `reject` - rejects the task with `LIMIT_EXCEEDED` error code
|
|
62
|
+
* - `enqueue` - enqueues the task
|
|
63
|
+
*
|
|
64
|
+
* @default 'reject'
|
|
65
|
+
*/
|
|
66
|
+
limitBehavior?: LimitBehavior;
|
|
67
|
+
/**
|
|
68
|
+
* Logger options.
|
|
69
|
+
*/
|
|
70
|
+
loggerOptions?: Omit<LoggerOptions, 'context'>;
|
|
71
|
+
/**
|
|
72
|
+
* Queue settings.
|
|
73
|
+
*/
|
|
74
|
+
queue?: RateLimiterQueueOptions;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=rate-limiter-options.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type SelectionPolicy } from '@stimulcross/ds-policy-priority-queue';
|
|
2
|
+
/**
|
|
3
|
+
* Queue options for rate limiter.
|
|
4
|
+
*
|
|
5
|
+
* These options are applied to limiters that support delayed execution.
|
|
6
|
+
*/
|
|
7
|
+
export interface RateLimiterQueueOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Defines the maximum number of tasks that can be executed concurrently.
|
|
10
|
+
*
|
|
11
|
+
* @default Infinity
|
|
12
|
+
*/
|
|
13
|
+
concurrency?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Maximum time to wait in the queue (in milliseconds).
|
|
16
|
+
*
|
|
17
|
+
* If a task is not started within this time, it will be rejected with `EXPIRED` error code.
|
|
18
|
+
*
|
|
19
|
+
* @default Infinity
|
|
20
|
+
*/
|
|
21
|
+
maxWaitMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum queue size.
|
|
24
|
+
*
|
|
25
|
+
* When overflowed, new tasks will be rejected immediately.
|
|
26
|
+
*
|
|
27
|
+
* @default Infinity
|
|
28
|
+
*/
|
|
29
|
+
capacity?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Selection policy for the priority queue.
|
|
32
|
+
*
|
|
33
|
+
* Defaults to Weighted round-robin (WRR) with the following weights:
|
|
34
|
+
* - `Priority.Lowest` - 1
|
|
35
|
+
* - `Priority.Low` - 2
|
|
36
|
+
* - `Priority.Normal` - 4
|
|
37
|
+
* - `Priority.High` - 8
|
|
38
|
+
* - `Priority.Highest` - 16
|
|
39
|
+
*/
|
|
40
|
+
selectionPolicy?: SelectionPolicy;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=rate-limiter-queue-options.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Priority } from '@stimulcross/ds-policy-priority-queue';
|
|
2
|
+
import { type LimitBehavior } from '../types/limit-behavior.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for running a single task.
|
|
5
|
+
*/
|
|
6
|
+
export interface RateLimiterRunOptions {
|
|
7
|
+
/**
|
|
8
|
+
* A unique identifier for the task for logging and debugging.
|
|
9
|
+
*
|
|
10
|
+
* If not provided, the library will generate a unique ID.
|
|
11
|
+
*/
|
|
12
|
+
id?: string;
|
|
13
|
+
/**
|
|
14
|
+
* A storage key for the task.
|
|
15
|
+
*/
|
|
16
|
+
key?: string;
|
|
17
|
+
/**
|
|
18
|
+
* The cost to consume the limit.
|
|
19
|
+
*
|
|
20
|
+
* @default 1
|
|
21
|
+
*/
|
|
22
|
+
cost?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Defines the behavior when the limit is reached for the current task. Overrides the global limit behavior
|
|
25
|
+
* set in {@link RateLimiterOptions.limitBehavior}.
|
|
26
|
+
*
|
|
27
|
+
* - `reject` - rejects the task with `LIMIT_EXCEEDED` error code
|
|
28
|
+
* - `enqueue` - enqueues the task if possible
|
|
29
|
+
*
|
|
30
|
+
* Defaults to global {@link RateLimiterOptions.limitBehavior}
|
|
31
|
+
*/
|
|
32
|
+
limitBehavior?: LimitBehavior;
|
|
33
|
+
/**
|
|
34
|
+
* Task priority.
|
|
35
|
+
*
|
|
36
|
+
* @default Priority.Normal (3)
|
|
37
|
+
*/
|
|
38
|
+
priority?: Priority;
|
|
39
|
+
/**
|
|
40
|
+
* An abort signal to abort the task execution.
|
|
41
|
+
*/
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
/**
|
|
44
|
+
* Maximum wait time in milliseconds for the task in the queue.
|
|
45
|
+
*
|
|
46
|
+
* This does not affect execution time.
|
|
47
|
+
*
|
|
48
|
+
* @default Infinity
|
|
49
|
+
*/
|
|
50
|
+
maxWaitMs?: number;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=rate-limiter-run-options.d.ts.map
|