@stimulcross/rate-limiter 0.0.2 → 0.0.4
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/lib/core/cancellable.d.ts +5 -0
- package/lib/core/cancellable.js +1 -0
- package/lib/core/clock.d.ts +10 -0
- package/lib/core/clock.js +1 -0
- package/lib/core/decision.d.ts +23 -0
- package/lib/core/decision.js +1 -0
- package/lib/core/rate-limit-policy.d.ts +14 -0
- package/lib/core/rate-limit-policy.js +1 -0
- package/lib/core/rate-limiter-status.d.ts +14 -0
- package/lib/core/rate-limiter-status.js +1 -0
- package/lib/core/rate-limiter.d.ts +34 -0
- package/lib/core/rate-limiter.js +1 -0
- package/lib/core/state-storage.d.ts +46 -0
- package/lib/core/state-storage.js +1 -0
- package/lib/enums/rate-limit-error-code.d.ts +26 -0
- package/lib/enums/rate-limit-error-code.js +1 -0
- package/lib/errors/custom.error.d.ts +6 -0
- package/lib/errors/custom.error.js +1 -0
- package/lib/errors/invalid-cost.error.d.ts +16 -0
- package/lib/errors/invalid-cost.error.js +1 -0
- package/lib/errors/rate-limit.error.d.ts +37 -0
- package/lib/errors/rate-limit.error.js +1 -0
- package/lib/errors/rate-limiter-destroyed.error.d.ts +7 -0
- package/lib/errors/rate-limiter-destroyed.error.js +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +1 -0
- package/lib/interfaces/rate-limiter-options.d.ts +76 -0
- package/lib/interfaces/rate-limiter-options.js +1 -0
- package/lib/interfaces/rate-limiter-queue-options.d.ts +42 -0
- package/lib/interfaces/rate-limiter-queue-options.js +1 -0
- package/lib/interfaces/rate-limiter-run-options.d.ts +52 -0
- package/lib/interfaces/rate-limiter-run-options.js +1 -0
- package/lib/limiters/abstract-rate-limiter.d.ts +44 -0
- package/lib/limiters/abstract-rate-limiter.js +2 -1
- package/lib/limiters/composite.policy.d.ts +15 -0
- package/lib/limiters/composite.policy.js +1 -0
- package/lib/limiters/fixed-window/fixed-window.limiter.d.ts +33 -0
- package/lib/limiters/fixed-window/fixed-window.limiter.js +1 -0
- package/lib/limiters/fixed-window/fixed-window.options.d.ts +27 -0
- package/lib/limiters/fixed-window/fixed-window.options.js +1 -0
- package/lib/limiters/fixed-window/fixed-window.policy.d.ts +19 -0
- package/lib/limiters/fixed-window/fixed-window.policy.js +1 -0
- package/lib/limiters/fixed-window/fixed-window.state.d.ts +11 -0
- package/lib/limiters/fixed-window/fixed-window.state.js +1 -0
- package/lib/limiters/fixed-window/fixed-window.status.d.ts +39 -0
- package/lib/limiters/fixed-window/fixed-window.status.js +1 -0
- package/lib/limiters/fixed-window/index.d.ts +5 -0
- package/lib/limiters/fixed-window/index.js +1 -0
- package/lib/limiters/generic-cell/generic-cell.limiter.d.ts +30 -0
- package/lib/limiters/generic-cell/generic-cell.limiter.js +1 -0
- package/lib/limiters/generic-cell/generic-cell.options.d.ts +22 -0
- package/lib/limiters/generic-cell/generic-cell.options.js +1 -0
- package/lib/limiters/generic-cell/generic-cell.policy.d.ts +18 -0
- package/lib/limiters/generic-cell/generic-cell.policy.js +1 -0
- package/lib/limiters/generic-cell/generic-cell.state.d.ts +9 -0
- package/lib/limiters/generic-cell/generic-cell.state.js +1 -0
- package/lib/limiters/generic-cell/generic-cell.status.d.ts +49 -0
- package/lib/limiters/generic-cell/generic-cell.status.js +1 -0
- package/lib/limiters/generic-cell/index.d.ts +5 -0
- package/lib/limiters/generic-cell/index.js +1 -0
- package/lib/limiters/http-response-based/http-limit-info.extractor.d.ts +16 -0
- package/lib/limiters/http-response-based/http-limit-info.extractor.js +1 -0
- package/lib/limiters/http-response-based/http-limit.info.d.ts +39 -0
- package/lib/limiters/http-response-based/http-limit.info.js +1 -0
- package/lib/limiters/http-response-based/http-response-based-limiter.options.d.ts +17 -0
- package/lib/limiters/http-response-based/http-response-based-limiter.options.js +1 -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 +1 -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 +1 -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 +14 -7
- package/lib/limiters/http-response-based/index.d.ts +7 -0
- package/lib/limiters/http-response-based/index.js +1 -0
- package/lib/limiters/leaky-bucket/index.d.ts +5 -0
- package/lib/limiters/leaky-bucket/index.js +1 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.limiter.d.ts +30 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.limiter.js +1 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.options.d.ts +22 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.options.js +1 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.policy.d.ts +19 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.policy.js +1 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.state.d.ts +10 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.state.js +1 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.status.d.ts +31 -0
- package/lib/limiters/leaky-bucket/leaky-bucket.status.js +1 -0
- package/lib/limiters/sliding-window-counter/index.d.ts +5 -0
- package/lib/limiters/sliding-window-counter/index.js +1 -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 +1 -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 +1 -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 +1 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.state.d.ts +11 -0
- package/lib/limiters/sliding-window-counter/sliding-window-counter.state.js +1 -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 +1 -0
- package/lib/limiters/sliding-window-log/index.d.ts +5 -0
- package/lib/limiters/sliding-window-log/index.js +1 -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 +1 -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 +1 -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 +1 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.state.d.ts +18 -0
- package/lib/limiters/sliding-window-log/sliding-window-log.state.js +1 -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 +1 -0
- package/lib/limiters/token-bucket/index.d.ts +5 -0
- package/lib/limiters/token-bucket/index.js +1 -0
- package/lib/limiters/token-bucket/token-bucket.limiter.d.ts +30 -0
- package/lib/limiters/token-bucket/token-bucket.limiter.js +1 -0
- package/lib/limiters/token-bucket/token-bucket.options.d.ts +16 -0
- package/lib/limiters/token-bucket/token-bucket.options.js +1 -0
- package/lib/limiters/token-bucket/token-bucket.policy.d.ts +19 -0
- package/lib/limiters/token-bucket/token-bucket.policy.js +1 -0
- package/lib/limiters/token-bucket/token-bucket.state.d.ts +11 -0
- package/lib/limiters/token-bucket/token-bucket.state.js +1 -0
- package/lib/limiters/token-bucket/token-bucket.status.d.ts +31 -0
- package/lib/limiters/token-bucket/token-bucket.status.js +1 -0
- package/lib/runtime/default-clock.d.ts +4 -0
- package/lib/runtime/default-clock.js +1 -0
- package/lib/runtime/execution-tickets.d.ts +12 -0
- package/lib/runtime/execution-tickets.js +1 -0
- package/lib/runtime/in-memory-state-store.d.ts +19 -0
- package/lib/runtime/in-memory-state-store.js +1 -0
- package/lib/runtime/rate-limiter.executor.d.ts +47 -0
- package/lib/runtime/rate-limiter.executor.js +1 -0
- package/lib/runtime/semaphore.d.ts +9 -0
- package/lib/runtime/semaphore.js +1 -0
- package/lib/runtime/task.d.ts +41 -0
- package/lib/runtime/task.js +1 -0
- package/lib/types/limit-behavior.d.ts +9 -0
- package/lib/types/limit-behavior.js +1 -0
- package/lib/utils/generate-random-string.d.ts +3 -0
- package/lib/utils/generate-random-string.js +1 -0
- package/lib/utils/promise-with-resolvers.d.ts +9 -0
- package/lib/utils/promise-with-resolvers.js +1 -0
- package/lib/utils/sanitize-error.d.ts +3 -0
- package/lib/utils/sanitize-error.js +1 -0
- package/lib/utils/sanitize-priority.d.ts +4 -0
- package/lib/utils/sanitize-priority.js +1 -0
- package/lib/utils/validate-cost.d.ts +3 -0
- package/lib/utils/validate-cost.js +1 -0
- package/package.json +3 -2
package/lib/core/cancellable.js
CHANGED
package/lib/core/clock.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export type DecisionKind = 'allow' | 'deny' | 'delay';
|
|
3
|
+
/** @internal */
|
|
4
|
+
export interface DecisionBase {
|
|
5
|
+
kind: DecisionKind;
|
|
6
|
+
}
|
|
7
|
+
/** @internal */
|
|
8
|
+
export interface DecisionAllow extends DecisionBase {
|
|
9
|
+
kind: Extract<DecisionKind, 'allow'>;
|
|
10
|
+
}
|
|
11
|
+
/** @internal */
|
|
12
|
+
export interface DecisionDeny extends DecisionBase {
|
|
13
|
+
kind: Extract<DecisionKind, 'deny'>;
|
|
14
|
+
retryAt: number;
|
|
15
|
+
}
|
|
16
|
+
/** @internal */
|
|
17
|
+
export interface DecisionDelay extends DecisionBase {
|
|
18
|
+
kind: Extract<DecisionKind, 'delay'>;
|
|
19
|
+
runAt: number;
|
|
20
|
+
}
|
|
21
|
+
/** @internal */
|
|
22
|
+
export type Decision = DecisionAllow | DecisionDeny | DecisionDelay;
|
|
23
|
+
//# sourceMappingURL=decision.d.ts.map
|
package/lib/core/decision.js
CHANGED
|
@@ -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
|
package/lib/core/rate-limiter.js
CHANGED
|
@@ -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,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,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
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { Clock } from './core/clock.js';
|
|
2
|
+
export type { StateStorage } from './core/state-storage.js';
|
|
3
|
+
export type { RateLimiter } from './core/rate-limiter.js';
|
|
4
|
+
export type { LimitBehavior } from './types/limit-behavior.js';
|
|
5
|
+
export type { KeyResolver, RateLimiterOptions } from './interfaces/rate-limiter-options.js';
|
|
6
|
+
export type { RateLimiterQueueOptions } from './interfaces/rate-limiter-queue-options.js';
|
|
7
|
+
export type { RateLimiterRunOptions } from './interfaces/rate-limiter-run-options.js';
|
|
8
|
+
export { type RateLimitErrorPlainObject, RateLimitError } from './errors/rate-limit.error.js';
|
|
9
|
+
export { RateLimiterDestroyedError } from './errors/rate-limiter-destroyed.error.js';
|
|
10
|
+
export { type InvalidCostErrorPlainObject, InvalidCostError } from './errors/invalid-cost.error.js';
|
|
11
|
+
export { RateLimitErrorCode } from './enums/rate-limit-error-code.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { RateLimitError } from './errors/rate-limit.error.js';
|
|
|
2
2
|
export { RateLimiterDestroyedError } from './errors/rate-limiter-destroyed.error.js';
|
|
3
3
|
export { InvalidCostError } from './errors/invalid-cost.error.js';
|
|
4
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?: Partial<LoggerOptions>;
|
|
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
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Priority } from '@stimulcross/ds-policy-priority-queue';
|
|
2
|
+
import { type Logger } from '@stimulcross/logger';
|
|
3
|
+
import { type Clock } from '../core/clock.js';
|
|
4
|
+
import { type RateLimitPolicy } from '../core/rate-limit-policy.js';
|
|
5
|
+
import { type RateLimiterStatus } from '../core/rate-limiter-status.js';
|
|
6
|
+
import { type RateLimiter } from '../core/rate-limiter.js';
|
|
7
|
+
import { type StateStorage } from '../core/state-storage.js';
|
|
8
|
+
import { type IdGenerator, type KeyResolver, type RateLimiterOptions } from '../interfaces/rate-limiter-options.js';
|
|
9
|
+
import { type RateLimiterRunOptions } from '../interfaces/rate-limiter-run-options.js';
|
|
10
|
+
import { RateLimiterExecutor } from '../runtime/rate-limiter.executor.js';
|
|
11
|
+
import { type LimitBehavior } from '../types/limit-behavior.js';
|
|
12
|
+
/** @internal */
|
|
13
|
+
export interface ExecutionContext {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly key: string;
|
|
16
|
+
readonly cost: number;
|
|
17
|
+
readonly limitBehavior?: LimitBehavior;
|
|
18
|
+
readonly priority?: Priority;
|
|
19
|
+
readonly signal?: AbortSignal;
|
|
20
|
+
readonly maxWaitMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/** @internal */
|
|
23
|
+
export declare abstract class AbstractRateLimiter<TState extends object, TStatus extends RateLimiterStatus | RateLimiterStatus[], TResult = unknown> implements RateLimiter<TStatus> {
|
|
24
|
+
protected readonly _logger: Logger;
|
|
25
|
+
protected readonly _clock: Clock;
|
|
26
|
+
protected readonly _store: StateStorage<TState>;
|
|
27
|
+
protected readonly _executor: RateLimiterExecutor;
|
|
28
|
+
protected readonly _getStoreKey: KeyResolver;
|
|
29
|
+
protected readonly _generateId: IdGenerator;
|
|
30
|
+
private _isDestroyed;
|
|
31
|
+
protected abstract readonly _policy: RateLimitPolicy<TState, TStatus>;
|
|
32
|
+
protected constructor(options?: RateLimiterOptions<TState>);
|
|
33
|
+
run<T = TResult>(fn: () => T | Promise<T>, options?: RateLimiterRunOptions): Promise<T>;
|
|
34
|
+
getStatus(key?: string): Promise<TStatus>;
|
|
35
|
+
clear(key?: string): Promise<void>;
|
|
36
|
+
destroy(): Promise<void>;
|
|
37
|
+
protected get _shouldPrintDebug(): boolean;
|
|
38
|
+
protected abstract _runInternal<T = TResult>(fn: () => T | Promise<T>, ctx: ExecutionContext): Promise<T>;
|
|
39
|
+
protected _execute<T = TResult>(fn: () => T | Promise<T>, runAt: number, storeTtlMs: number, ctx: ExecutionContext, expiresAt?: number): Promise<T>;
|
|
40
|
+
protected abstract _getDebugStateString(state: TState): string;
|
|
41
|
+
protected _shouldRevert(e: unknown): boolean;
|
|
42
|
+
private _ensureCanExecute;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=abstract-rate-limiter.d.ts.map
|
|
@@ -18,7 +18,7 @@ export class AbstractRateLimiter {
|
|
|
18
18
|
_generateId;
|
|
19
19
|
_isDestroyed = false;
|
|
20
20
|
constructor(options) {
|
|
21
|
-
this._logger = createLogger(new.target.name,
|
|
21
|
+
this._logger = createLogger({ context: new.target.name, minLevel: 'WARNING', ...options?.loggerOptions });
|
|
22
22
|
this._clock = options?.clock ?? defaultClock;
|
|
23
23
|
this._store = options?.store ?? new InMemoryStateStore(this._clock);
|
|
24
24
|
this._executor = new RateLimiterExecutor(this._logger, this._clock, options?.queue);
|
|
@@ -130,3 +130,4 @@ export class AbstractRateLimiter {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
//# sourceMappingURL=abstract-rate-limiter.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RateLimitPolicy, type RateLimitPolicyResult } from '../core/rate-limit-policy.js';
|
|
2
|
+
type CompositeState<T> = T[];
|
|
3
|
+
type CompositeInfo<T> = T[];
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare class CompositePolicy<S extends object = object, I extends object = object, P extends RateLimitPolicy<S, I> = RateLimitPolicy<S, I>> implements RateLimitPolicy<CompositeState<S>, CompositeInfo<I>> {
|
|
6
|
+
private readonly _policies;
|
|
7
|
+
constructor(_policies: P[]);
|
|
8
|
+
get policies(): P[];
|
|
9
|
+
getInitialState(now: number): CompositeState<S>;
|
|
10
|
+
getStatus(states: CompositeState<S>, now: number): CompositeInfo<I>;
|
|
11
|
+
evaluate(states: CompositeState<S>, now: number, cost?: number, shouldReserve?: boolean): RateLimitPolicyResult<CompositeState<S>>;
|
|
12
|
+
revert(states: S[], cost: number, now: number): S[];
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=composite.policy.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type FixedWindowOptions } from './fixed-window.options.js';
|
|
2
|
+
import { FixedWindowPolicy } from './fixed-window.policy.js';
|
|
3
|
+
import { type FixedWindowState } from './fixed-window.state.js';
|
|
4
|
+
import { type FixedWindowStatus } from './fixed-window.status.js';
|
|
5
|
+
import { AbstractRateLimiter, type ExecutionContext } from '../abstract-rate-limiter.js';
|
|
6
|
+
import { CompositePolicy } from '../composite.policy.js';
|
|
7
|
+
/**
|
|
8
|
+
* Fixed Window rate limiter.
|
|
9
|
+
*
|
|
10
|
+
* Designed primarily for client-side use to respect third-party limits or protect resources.
|
|
11
|
+
* While this can be used as a server-side limiter with custom distributed storage
|
|
12
|
+
* (e.g., Redis), it is best-effort and not recommended due to high network round-trip latency.
|
|
13
|
+
*
|
|
14
|
+
* Key features:
|
|
15
|
+
* - **Composite limits** - supports multiple windows simultaneously (e.g., 10 per second AND 1000 per hour)
|
|
16
|
+
* - **Queueing & overflow** - optionally enqueues excess requests up to a maximum allowed overflow capacity
|
|
17
|
+
* - **Concurrency** - limits how many requests can be executed simultaneously
|
|
18
|
+
* - **Priority** - supports task priorities (with fairness and custom policy) to execute critical requests first
|
|
19
|
+
* - **Cancellation** - supports `AbortSignal` to safely remove pending requests from the queue
|
|
20
|
+
* - **Expiration** - automatically drops queued requests that wait longer than the allowed `maxWaitMs`
|
|
21
|
+
* - **Auto-rollback** - reverts spent quota if an enqueued task is canceled or expired
|
|
22
|
+
*/
|
|
23
|
+
export declare class FixedWindowLimiter extends AbstractRateLimiter<FixedWindowState[], FixedWindowStatus[]> {
|
|
24
|
+
private readonly _defaultLimitBehaviour;
|
|
25
|
+
private readonly _defaultMaxWaitMs;
|
|
26
|
+
private readonly _maxWindowSizeMs;
|
|
27
|
+
protected readonly _policy: CompositePolicy<FixedWindowState, FixedWindowStatus, FixedWindowPolicy>;
|
|
28
|
+
constructor(options: FixedWindowOptions);
|
|
29
|
+
protected _runInternal<T>(fn: () => T | Promise<T>, ctx: ExecutionContext): Promise<T>;
|
|
30
|
+
protected _getDebugStateString(state: FixedWindowState[]): string;
|
|
31
|
+
private _printDebug;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=fixed-window.limiter.d.ts.map
|