@stimulcross/rate-limiter 0.0.1 → 0.0.2

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 (183) hide show
  1. package/README.md +20 -0
  2. package/lib/core/cancellable.js +1 -0
  3. package/lib/core/clock.js +1 -0
  4. package/lib/core/decision.js +1 -0
  5. package/lib/core/rate-limit-policy.js +1 -0
  6. package/lib/core/rate-limiter-status.js +1 -0
  7. package/lib/core/rate-limiter.js +1 -0
  8. package/lib/core/state-storage.js +1 -0
  9. package/lib/enums/rate-limit-error-code.js +26 -0
  10. package/lib/errors/custom.error.js +12 -0
  11. package/lib/errors/invalid-cost.error.js +25 -0
  12. package/lib/errors/rate-limit.error.js +74 -0
  13. package/{src/errors/rate-limiter-destroyed.error.ts → lib/errors/rate-limiter-destroyed.error.js} +3 -3
  14. package/lib/index.js +4 -0
  15. package/lib/interfaces/rate-limiter-options.js +1 -0
  16. package/lib/interfaces/rate-limiter-queue-options.js +1 -0
  17. package/lib/interfaces/rate-limiter-run-options.js +1 -0
  18. package/lib/limiters/abstract-rate-limiter.js +132 -0
  19. package/lib/limiters/composite.policy.js +72 -0
  20. package/lib/limiters/fixed-window/fixed-window.limiter.js +84 -0
  21. package/lib/limiters/fixed-window/fixed-window.options.js +1 -0
  22. package/lib/limiters/fixed-window/fixed-window.policy.js +120 -0
  23. package/lib/limiters/fixed-window/fixed-window.state.js +1 -0
  24. package/lib/limiters/fixed-window/fixed-window.status.js +1 -0
  25. package/lib/limiters/fixed-window/index.js +1 -0
  26. package/lib/limiters/generic-cell/generic-cell.limiter.js +73 -0
  27. package/lib/limiters/generic-cell/generic-cell.options.js +1 -0
  28. package/lib/limiters/generic-cell/generic-cell.policy.js +86 -0
  29. package/lib/limiters/generic-cell/generic-cell.state.js +1 -0
  30. package/lib/limiters/generic-cell/generic-cell.status.js +1 -0
  31. package/lib/limiters/generic-cell/index.js +1 -0
  32. package/lib/limiters/http-response-based/http-limit-info.extractor.js +1 -0
  33. package/lib/limiters/http-response-based/http-limit.info.js +1 -0
  34. package/lib/limiters/http-response-based/http-response-based-limiter.options.js +1 -0
  35. package/lib/limiters/http-response-based/http-response-based-limiter.state.js +1 -0
  36. package/lib/limiters/http-response-based/http-response-based-limiter.status.js +1 -0
  37. package/lib/limiters/http-response-based/http-response-based.limiter.js +379 -0
  38. package/lib/limiters/http-response-based/index.js +1 -0
  39. package/lib/limiters/leaky-bucket/index.js +1 -0
  40. package/lib/limiters/leaky-bucket/leaky-bucket.limiter.js +74 -0
  41. package/lib/limiters/leaky-bucket/leaky-bucket.options.js +1 -0
  42. package/lib/limiters/leaky-bucket/leaky-bucket.policy.js +100 -0
  43. package/lib/limiters/leaky-bucket/leaky-bucket.state.js +1 -0
  44. package/lib/limiters/leaky-bucket/leaky-bucket.status.js +1 -0
  45. package/lib/limiters/sliding-window-counter/index.js +1 -0
  46. package/lib/limiters/sliding-window-counter/sliding-window-counter.limiter.js +46 -0
  47. package/lib/limiters/sliding-window-counter/sliding-window-counter.options.js +1 -0
  48. package/lib/limiters/sliding-window-counter/sliding-window-counter.policy.js +127 -0
  49. package/lib/limiters/sliding-window-counter/sliding-window-counter.state.js +1 -0
  50. package/lib/limiters/sliding-window-counter/sliding-window-counter.status.js +1 -0
  51. package/lib/limiters/sliding-window-log/index.js +1 -0
  52. package/lib/limiters/sliding-window-log/sliding-window-log.limiter.js +43 -0
  53. package/lib/limiters/sliding-window-log/sliding-window-log.options.js +1 -0
  54. package/lib/limiters/sliding-window-log/sliding-window-log.policy.js +123 -0
  55. package/lib/limiters/sliding-window-log/sliding-window-log.state.js +1 -0
  56. package/lib/limiters/sliding-window-log/sliding-window-log.status.js +1 -0
  57. package/lib/limiters/token-bucket/index.js +1 -0
  58. package/lib/limiters/token-bucket/token-bucket.limiter.js +74 -0
  59. package/lib/limiters/token-bucket/token-bucket.options.js +1 -0
  60. package/lib/limiters/token-bucket/token-bucket.policy.js +115 -0
  61. package/lib/limiters/token-bucket/token-bucket.state.js +1 -0
  62. package/lib/limiters/token-bucket/token-bucket.status.js +1 -0
  63. package/lib/runtime/default-clock.js +6 -0
  64. package/lib/runtime/execution-tickets.js +26 -0
  65. package/lib/runtime/in-memory-state-store.js +96 -0
  66. package/lib/runtime/rate-limiter.executor.js +195 -0
  67. package/lib/runtime/semaphore.js +27 -0
  68. package/lib/runtime/task.js +100 -0
  69. package/lib/types/limit-behavior.js +1 -0
  70. package/lib/utils/generate-random-string.js +12 -0
  71. package/lib/utils/promise-with-resolvers.js +14 -0
  72. package/lib/utils/sanitize-error.js +4 -0
  73. package/lib/utils/sanitize-priority.js +17 -0
  74. package/lib/utils/validate-cost.js +13 -0
  75. package/package.json +12 -2
  76. package/.editorconfig +0 -21
  77. package/.github/workflows/node.yml +0 -87
  78. package/.husky/commit-msg +0 -1
  79. package/.husky/pre-commit +0 -1
  80. package/.megaignore +0 -8
  81. package/.prettierignore +0 -3
  82. package/commitlint.config.js +0 -8
  83. package/eslint.config.js +0 -65
  84. package/lint-staged.config.js +0 -4
  85. package/prettier.config.cjs +0 -1
  86. package/src/core/cancellable.ts +0 -4
  87. package/src/core/clock.ts +0 -9
  88. package/src/core/decision.ts +0 -27
  89. package/src/core/rate-limit-policy.ts +0 -15
  90. package/src/core/rate-limiter-status.ts +0 -14
  91. package/src/core/rate-limiter.ts +0 -37
  92. package/src/core/state-storage.ts +0 -51
  93. package/src/enums/rate-limit-error-code.ts +0 -29
  94. package/src/errors/custom.error.ts +0 -14
  95. package/src/errors/invalid-cost.error.ts +0 -33
  96. package/src/errors/rate-limit.error.ts +0 -91
  97. package/src/index.ts +0 -11
  98. package/src/interfaces/rate-limiter-options.ts +0 -84
  99. package/src/interfaces/rate-limiter-queue-options.ts +0 -45
  100. package/src/interfaces/rate-limiter-run-options.ts +0 -58
  101. package/src/limiters/abstract-rate-limiter.ts +0 -206
  102. package/src/limiters/composite.policy.ts +0 -102
  103. package/src/limiters/fixed-window/fixed-window.limiter.ts +0 -121
  104. package/src/limiters/fixed-window/fixed-window.options.ts +0 -29
  105. package/src/limiters/fixed-window/fixed-window.policy.ts +0 -159
  106. package/src/limiters/fixed-window/fixed-window.state.ts +0 -10
  107. package/src/limiters/fixed-window/fixed-window.status.ts +0 -46
  108. package/src/limiters/fixed-window/index.ts +0 -4
  109. package/src/limiters/generic-cell/generic-cell.limiter.ts +0 -108
  110. package/src/limiters/generic-cell/generic-cell.options.ts +0 -23
  111. package/src/limiters/generic-cell/generic-cell.policy.ts +0 -115
  112. package/src/limiters/generic-cell/generic-cell.state.ts +0 -8
  113. package/src/limiters/generic-cell/generic-cell.status.ts +0 -54
  114. package/src/limiters/generic-cell/index.ts +0 -4
  115. package/src/limiters/http-response-based/http-limit-info.extractor.ts +0 -20
  116. package/src/limiters/http-response-based/http-limit.info.ts +0 -41
  117. package/src/limiters/http-response-based/http-response-based-limiter.options.ts +0 -18
  118. package/src/limiters/http-response-based/http-response-based-limiter.state.ts +0 -13
  119. package/src/limiters/http-response-based/http-response-based-limiter.status.ts +0 -74
  120. package/src/limiters/http-response-based/http-response-based.limiter.ts +0 -512
  121. package/src/limiters/http-response-based/index.ts +0 -6
  122. package/src/limiters/leaky-bucket/index.ts +0 -4
  123. package/src/limiters/leaky-bucket/leaky-bucket.limiter.ts +0 -105
  124. package/src/limiters/leaky-bucket/leaky-bucket.options.ts +0 -23
  125. package/src/limiters/leaky-bucket/leaky-bucket.policy.ts +0 -134
  126. package/src/limiters/leaky-bucket/leaky-bucket.state.ts +0 -9
  127. package/src/limiters/leaky-bucket/leaky-bucket.status.ts +0 -36
  128. package/src/limiters/sliding-window-counter/index.ts +0 -7
  129. package/src/limiters/sliding-window-counter/sliding-window-counter.limiter.ts +0 -76
  130. package/src/limiters/sliding-window-counter/sliding-window-counter.options.ts +0 -20
  131. package/src/limiters/sliding-window-counter/sliding-window-counter.policy.ts +0 -167
  132. package/src/limiters/sliding-window-counter/sliding-window-counter.state.ts +0 -10
  133. package/src/limiters/sliding-window-counter/sliding-window-counter.status.ts +0 -53
  134. package/src/limiters/sliding-window-log/index.ts +0 -4
  135. package/src/limiters/sliding-window-log/sliding-window-log.limiter.ts +0 -65
  136. package/src/limiters/sliding-window-log/sliding-window-log.options.ts +0 -20
  137. package/src/limiters/sliding-window-log/sliding-window-log.policy.ts +0 -166
  138. package/src/limiters/sliding-window-log/sliding-window-log.state.ts +0 -19
  139. package/src/limiters/sliding-window-log/sliding-window-log.status.ts +0 -44
  140. package/src/limiters/token-bucket/index.ts +0 -4
  141. package/src/limiters/token-bucket/token-bucket.limiter.ts +0 -110
  142. package/src/limiters/token-bucket/token-bucket.options.ts +0 -17
  143. package/src/limiters/token-bucket/token-bucket.policy.ts +0 -155
  144. package/src/limiters/token-bucket/token-bucket.state.ts +0 -10
  145. package/src/limiters/token-bucket/token-bucket.status.ts +0 -36
  146. package/src/runtime/default-clock.ts +0 -8
  147. package/src/runtime/execution-tickets.ts +0 -34
  148. package/src/runtime/in-memory-state-store.ts +0 -135
  149. package/src/runtime/rate-limiter.executor.ts +0 -286
  150. package/src/runtime/semaphore.ts +0 -31
  151. package/src/runtime/task.ts +0 -141
  152. package/src/types/limit-behavior.ts +0 -8
  153. package/src/utils/generate-random-string.ts +0 -16
  154. package/src/utils/promise-with-resolvers.ts +0 -23
  155. package/src/utils/sanitize-error.ts +0 -4
  156. package/src/utils/sanitize-priority.ts +0 -22
  157. package/src/utils/validate-cost.ts +0 -16
  158. package/tests/integration/limiters/fixed-window.limiter.spec.ts +0 -371
  159. package/tests/integration/limiters/generic-cell.limiter.spec.ts +0 -361
  160. package/tests/integration/limiters/http-response-based.limiter.spec.ts +0 -833
  161. package/tests/integration/limiters/leaky-bucket.spec.ts +0 -357
  162. package/tests/integration/limiters/sliding-window-counter.limiter.spec.ts +0 -175
  163. package/tests/integration/limiters/sliding-window-log.spec.ts +0 -185
  164. package/tests/integration/limiters/token-bucket.limiter.spec.ts +0 -363
  165. package/tests/tsconfig.json +0 -4
  166. package/tests/unit/policies/composite.policy.spec.ts +0 -244
  167. package/tests/unit/policies/fixed-window.policy.spec.ts +0 -260
  168. package/tests/unit/policies/generic-cell.policy.spec.ts +0 -178
  169. package/tests/unit/policies/leaky-bucket.policy.spec.ts +0 -215
  170. package/tests/unit/policies/sliding-window-counter.policy.spec.ts +0 -209
  171. package/tests/unit/policies/sliding-window-log.policy.spec.ts +0 -285
  172. package/tests/unit/policies/token-bucket.policy.spec.ts +0 -371
  173. package/tests/unit/runtime/execution-tickets.spec.ts +0 -121
  174. package/tests/unit/runtime/in-memory-state-store.spec.ts +0 -238
  175. package/tests/unit/runtime/rate-limiter.executor.spec.ts +0 -353
  176. package/tests/unit/runtime/semaphore.spec.ts +0 -98
  177. package/tests/unit/runtime/task.spec.ts +0 -182
  178. package/tests/unit/utils/generate-random-string.spec.ts +0 -51
  179. package/tests/unit/utils/promise-with-resolvers.spec.ts +0 -57
  180. package/tests/unit/utils/sanitize-priority.spec.ts +0 -46
  181. package/tests/unit/utils/validate-cost.spec.ts +0 -48
  182. package/tsconfig.json +0 -14
  183. package/vitest.config.js +0 -22
@@ -1,8 +0,0 @@
1
- export default {
2
- extends: ['@stimulcross/commitlint-config'],
3
- rules: {
4
- 'body-max-line-length': [2, 'always', 500],
5
- 'footer-max-line-length': [2, 'always', 500],
6
- }
7
-
8
- };
package/eslint.config.js DELETED
@@ -1,65 +0,0 @@
1
- import { resolveFlatConfig } from '@leancodepl/resolve-eslint-flat-config';
2
- import typescript from '@stimulcross/eslint-config-typescript';
3
- import typescriptStyle from '@stimulcross/eslint-config-typescript/style';
4
- import { defineConfig, globalIgnores } from 'eslint/config';
5
- import globals from 'globals';
6
-
7
- export const globs = {
8
- js: ['**/*.js', '**/*.cjs', '**/*.mjs'],
9
- ts: ['**/*.ts', '**/*.cts', '**/*.mts'],
10
- jsSpec: ['**/*.spec.js', '**/*.spec.cjs', '**/*.spec.mjs'],
11
- tsSpec: ['**/*.spec.ts', '**/*.spec.cts', '**/*.spec.mts'],
12
- lib: '**/dist',
13
- nodeModules: '**/node_modules',
14
- coverage: '**/coverage',
15
- dts: '**/*.d.ts',
16
- };
17
-
18
- /** @type {import("eslint").Linter.Config[]} */
19
- export const config = resolveFlatConfig(
20
- defineConfig(
21
- globalIgnores([globs.lib, globs.nodeModules, globs.dts, globs.coverage]),
22
- {
23
- files: [...globs.js, ...globs.ts, ...globs.jsSpec, ...globs.tsSpec],
24
- languageOptions: {
25
- globals: {
26
- ...globals.node,
27
- ...globals.es2022,
28
- },
29
- },
30
- },
31
- {
32
- files: [...globs.js, ...globs.ts, ...globs.tsSpec],
33
- extends: [typescript, typescriptStyle],
34
- },
35
- {
36
- files: [...globs.ts, ...globs.tsSpec],
37
- rules: {
38
- 'id-length': 'off',
39
- 'no-await-in-loop': 'off',
40
- 'unicorn/no-new-array': 'off',
41
- 'unicorn/no-thenable': 'off',
42
- 'unicorn/no-useless-undefined': 'error',
43
- '@typescript-eslint/no-explicit-any': 'off',
44
- '@typescript-eslint/no-non-null-assertion': 'off',
45
- '@typescript-eslint/no-unnecessary-condition': ['warn', { allowConstantLoopConditions: true }],
46
- },
47
- },
48
- {
49
- files: [...globs.jsSpec, ...globs.tsSpec],
50
- rules: {
51
- 'id-length': 'off',
52
- 'max-nested-callbacks': 'off',
53
- 'unicorn/consistent-function-scoping': 'off',
54
- 'unicorn/no-array-push-push': 'off',
55
- '@typescript-eslint/naming-convention': 'off',
56
- '@typescript-eslint/no-empty-function': 'off',
57
- '@typescript-eslint/no-unsafe-member-access': 'off',
58
- '@typescript-eslint/no-unsafe-return': 'off',
59
- '@typescript-eslint/unbound-method': 'off',
60
- },
61
- },
62
- ),
63
- );
64
-
65
- export default config;
@@ -1,4 +0,0 @@
1
- export default {
2
- '*.{js,mjs,ts,json,md}': 'prettier --write ',
3
- 'src/*.{js,ts}': 'eslint src',
4
- };
@@ -1 +0,0 @@
1
- module.exports = require('@stimulcross/prettier-config');
@@ -1,4 +0,0 @@
1
- /** @internal */
2
- export interface Cancellable {
3
- cancel(): void;
4
- }
package/src/core/clock.ts DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Clock interface.
3
- */
4
- export interface Clock {
5
- /**
6
- * Returns the current timestamp in milliseconds.
7
- */
8
- now(): number;
9
- }
@@ -1,27 +0,0 @@
1
- /** @internal */
2
- export type DecisionKind = 'allow' | 'deny' | 'delay';
3
-
4
- /** @internal */
5
- export interface DecisionBase {
6
- kind: DecisionKind;
7
- }
8
-
9
- /** @internal */
10
- export interface DecisionAllow extends DecisionBase {
11
- kind: Extract<DecisionKind, 'allow'>;
12
- }
13
-
14
- /** @internal */
15
- export interface DecisionDeny extends DecisionBase {
16
- kind: Extract<DecisionKind, 'deny'>;
17
- retryAt: number;
18
- }
19
-
20
- /** @internal */
21
- export interface DecisionDelay extends DecisionBase {
22
- kind: Extract<DecisionKind, 'delay'>;
23
- runAt: number;
24
- }
25
-
26
- /** @internal */
27
- export type Decision = DecisionAllow | DecisionDeny | DecisionDelay;
@@ -1,15 +0,0 @@
1
- import { type Decision } from './decision.js';
2
-
3
- /** @internal */
4
- export interface RateLimitPolicyResult<S> {
5
- decision: Decision;
6
- nextState: S;
7
- }
8
-
9
- /** @internal */
10
- export interface RateLimitPolicy<TState extends object = object, TStatus extends object = object> {
11
- getInitialState(now: number): TState;
12
- getStatus(state: TState, now: number): TStatus;
13
- evaluate(state: TState, now: number, cost: number, shouldReserve?: boolean): RateLimitPolicyResult<TState>;
14
- revert(state: TState, cost: number, now: number): TState;
15
- }
@@ -1,14 +0,0 @@
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
- /**
11
- * The timestamp (in milliseconds) when the rate limiter will reset.
12
- */
13
- readonly resetAt: number;
14
- }
@@ -1,37 +0,0 @@
1
- import { type RateLimiterRunOptions } from '../interfaces/rate-limiter-run-options.js';
2
-
3
- /**
4
- * Rate limiter interface.
5
- *
6
- * @template TStatus The type of the rate limiter status returned by {@link getStatus} method.
7
- */
8
- export interface RateLimiter<TStatus extends object = object> {
9
- /**
10
- * Runs the given task.
11
- *
12
- * @param task The task to run.
13
- * @param options Options for running the task.
14
- */
15
- run<T>(task: () => T | Promise<T>, options?: RateLimiterRunOptions): Promise<T>;
16
-
17
- /**
18
- * Clears the rate limiter state.
19
- *
20
- * @param key The optional key to clear the state for.
21
- */
22
- clear(key?: string): Promise<void>;
23
-
24
- /**
25
- * Gets the rate limiter's status.
26
- *
27
- * @param key The optional key to get the status for.
28
- */
29
- getStatus?(key?: string): Promise<TStatus>;
30
-
31
- /**
32
- * Destroys the rate limiter.
33
- *
34
- * The limiter cannot be used after it has been destroyed. It should be used only for graceful shutdown.
35
- */
36
- destroy?(): Promise<void>;
37
- }
@@ -1,51 +0,0 @@
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
- /**
13
- * Sets the state for the given key.
14
- *
15
- * @param key The key to set the state for.
16
- * @param value The state to set.
17
- * @param ttlMs Optional TTL in milliseconds.
18
- */
19
- set(key: string, value: TState, ttlMs?: number): Promise<void>;
20
-
21
- /**
22
- * Deletes the state for the given key.
23
- *
24
- * @param key The key to delete the state for.
25
- */
26
- delete(key: string): Promise<void>;
27
-
28
- /**
29
- * Clears all stored states.
30
- */
31
- clear(): Promise<void>;
32
-
33
- /**
34
- * Destroys the storage.
35
- */
36
- destroy?(): Promise<void>;
37
-
38
- /**
39
- * Acquires a lock for the given key.
40
- *
41
- * @param key The key to acquire the lock for.
42
- */
43
- acquireLock?(key: string): Promise<void>;
44
-
45
- /**
46
- * Releases the lock for the given key.
47
- *
48
- * @param key The key to release the lock for.
49
- */
50
- releaseLock?(key: string): Promise<void>;
51
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * Rate limiter error codes.
3
- */
4
- export enum RateLimitErrorCode {
5
- /**
6
- * Indicates that the limit has been reached.
7
- */
8
- LimitExceeded = 'LIMIT_EXCEEDED',
9
-
10
- /**
11
- * Indicates that the execution queue is full.
12
- */
13
- QueueOverflow = 'QUEUE_OVERFLOW',
14
-
15
- /**
16
- * Indicates that the task has expired.
17
- */
18
- Expired = 'EXPIRED',
19
-
20
- /**
21
- * Indicates that a task was cleared before it was executed.
22
- */
23
- Destroyed = 'DESTROYED',
24
-
25
- /**
26
- * Indicates that a task was canceled via abort controller before it was executed.
27
- */
28
- Cancelled = 'CANCELLED',
29
- }
@@ -1,14 +0,0 @@
1
- /** @internal */
2
- export abstract class CustomError extends Error {
3
- protected constructor(message: string) {
4
- super(message);
5
-
6
- Object.setPrototypeOf(this, new.target.prototype);
7
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
8
- Error.captureStackTrace?.(this, new.target.constructor);
9
- }
10
-
11
- public get name(): string {
12
- return this.constructor.name;
13
- }
14
- }
@@ -1,33 +0,0 @@
1
- import { CustomError } from './custom.error.js';
2
-
3
- export interface InvalidCostErrorPlainObject extends Error {
4
- cost: number;
5
- }
6
-
7
- /**
8
- * Error thrown when the cost is invalid.
9
- *
10
- * The cost must be a positive integer or zero.
11
- */
12
- export class InvalidCostError extends CustomError {
13
- constructor(
14
- message: string,
15
- private readonly _cost: number,
16
- ) {
17
- super(message);
18
- }
19
-
20
- public get cost(): number {
21
- return this._cost;
22
- }
23
-
24
- // eslint-disable-next-line @typescript-eslint/naming-convention
25
- public toJSON(): InvalidCostErrorPlainObject {
26
- return {
27
- name: this.name,
28
- message: this.message,
29
- cost: this._cost,
30
- stack: this.stack,
31
- };
32
- }
33
- }
@@ -1,91 +0,0 @@
1
- import { RateLimitErrorCode } from '../enums/rate-limit-error-code.js';
2
-
3
- export interface RateLimitErrorPlainObject extends Error {
4
- code: RateLimitErrorCode;
5
- retryAt: number | null;
6
- }
7
-
8
- /**
9
- * An error thrown when a rate limit is exceeded.
10
- *
11
- * This error has a {@link code} property that indicates the type of error.
12
- *
13
- * The `code` can be:
14
- * - `LIMIT_EXCEEDED` - When the rate limit is exceeded.
15
- * - `QUEUE_OVERFLOW` - When the queue is full (if the limiter has a queue and the capacity has been exceeded).
16
- * - `EXPIRED` - When the task has expired (waited too long in the queue). This is related to the `maxWaitMs` option.
17
- * **NOTE:** This is never thrown if the task is executing too long. Such scenarios should be handled by the
18
- * task itself.
19
- * - `DESTROYED` - When the task is destroyed due to the rate limiter's `clear()` or `destroy()` methods.
20
- * - `CANCELLED` - When the task is cancelled using an abort signal.
21
- */
22
- export class RateLimitError extends Error {
23
- private readonly _code: RateLimitErrorCode;
24
- private readonly _retryAt: number | null;
25
-
26
- /** @internal */
27
- constructor(code: RateLimitErrorCode, retryAt?: number, message?: string) {
28
- if (!message) {
29
- switch (code) {
30
- case RateLimitErrorCode.LimitExceeded: {
31
- message = `Rate limit exceeded.${retryAt ? ` Retry at ${new Date(retryAt).toISOString()}.` : ''}`;
32
- break;
33
- }
34
-
35
- case RateLimitErrorCode.QueueOverflow: {
36
- message = 'Queue overflow.';
37
- break;
38
- }
39
-
40
- case RateLimitErrorCode.Expired: {
41
- message = 'Task expired.';
42
- break;
43
- }
44
-
45
- case RateLimitErrorCode.Destroyed: {
46
- message = 'Task destroyed.';
47
- break;
48
- }
49
-
50
- case RateLimitErrorCode.Cancelled: {
51
- message = 'Task cancelled.';
52
- break;
53
- }
54
-
55
- // No default
56
- }
57
- }
58
-
59
- super(message);
60
-
61
- this._code = code;
62
- this._retryAt = retryAt ?? null;
63
- }
64
-
65
- /**
66
- * The error code.
67
- */
68
- public get code(): RateLimitErrorCode {
69
- return this._code;
70
- }
71
-
72
- /**
73
- * The timestamp (in milliseconds) when the task can be retried.
74
- *
75
- * Can be `null` if the retry time is not known.
76
- */
77
- public get retryAt(): number | null {
78
- return this._retryAt ?? null;
79
- }
80
-
81
- // eslint-disable-next-line @typescript-eslint/naming-convention
82
- public toJSON(): RateLimitErrorPlainObject {
83
- return {
84
- name: this.name,
85
- message: this.message,
86
- code: this._code,
87
- retryAt: this._retryAt,
88
- stack: this.stack,
89
- };
90
- }
91
- }
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
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';
@@ -1,84 +0,0 @@
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
- /**
8
- * A function that generates a unique ID for the task.
9
- */
10
- export type IdGenerator = () => string;
11
-
12
- /**
13
- * A function that resolves a global key for the given key.
14
- */
15
- export type KeyResolver = (key?: string) => string;
16
-
17
- /**
18
- * Rate limiter options.
19
- *
20
- * @template TState The type of the rate limiter state.
21
- */
22
- export interface RateLimiterOptions<TState = unknown> {
23
- /**
24
- * A custom clock implementation.
25
- */
26
- clock?: Clock;
27
-
28
- /**
29
- * An optional key that can be either a string or a key resolver function.
30
- *
31
- * If a string is provided, it will be used as a global prefix for all keys.
32
- *
33
- * If a function is provided, it will be called with the provided key and should return a unique global key.
34
- *
35
- * Useful for distributed stores.
36
- *
37
- * @default limiter
38
- */
39
- key?: string | KeyResolver;
40
-
41
- /**
42
- * A custom ID factory function.
43
- *
44
- * It is used to generate unique IDs for each task for logging and debugging purposes.
45
- */
46
- idGenerator?: IdGenerator;
47
-
48
- /**
49
- * State storage implementation for persisting rate limiter state.
50
- *
51
- * By default, an in-memory state store is used, which is unique to each process.
52
- * This is suitable for single-instance applications or when rate limiting doesn't need
53
- * to be shared across multiple processes.
54
- *
55
- * For distributed applications, you can implement a custom state store using Redis, Memcached,
56
- * or other distributed storage systems. However, be aware that this introduces network latency
57
- * due to multiple round-trips (typically 3-4 requests with lock acquire/release).
58
- *
59
- * For distributed rate limiting, consider using, for example, Redis with Lua scripts.
60
- * This allows atomic operations and minimizes latency.
61
- */
62
- store?: StateStorage<TState>;
63
-
64
- /**
65
- * Defines the behavior when the limit is reached.
66
- *
67
- * Available options:
68
- * - `reject` - rejects the task with `LIMIT_EXCEEDED` error code
69
- * - `enqueue` - enqueues the task
70
- *
71
- * @default 'reject'
72
- */
73
- limitBehavior?: LimitBehavior;
74
-
75
- /**
76
- * Logger options.
77
- */
78
- loggerOptions?: Omit<LoggerOptions, 'context'>;
79
-
80
- /**
81
- * Queue settings.
82
- */
83
- queue?: RateLimiterQueueOptions;
84
- }
@@ -1,45 +0,0 @@
1
- import { type SelectionPolicy } from '@stimulcross/ds-policy-priority-queue';
2
-
3
- /**
4
- * Queue options for rate limiter.
5
- *
6
- * These options are applied to limiters that support delayed execution.
7
- */
8
- export interface RateLimiterQueueOptions {
9
- /**
10
- * Defines the maximum number of tasks that can be executed concurrently.
11
- *
12
- * @default Infinity
13
- */
14
- concurrency?: number;
15
-
16
- /**
17
- * Maximum time to wait in the queue (in milliseconds).
18
- *
19
- * If a task is not started within this time, it will be rejected with `EXPIRED` error code.
20
- *
21
- * @default Infinity
22
- */
23
- maxWaitMs?: number;
24
-
25
- /**
26
- * Maximum queue size.
27
- *
28
- * When overflowed, new tasks will be rejected immediately.
29
- *
30
- * @default Infinity
31
- */
32
- capacity?: number;
33
-
34
- /**
35
- * Selection policy for the priority queue.
36
- *
37
- * Defaults to Weighted round-robin (WRR) with the following weights:
38
- * - `Priority.Lowest` - 1
39
- * - `Priority.Low` - 2
40
- * - `Priority.Normal` - 4
41
- * - `Priority.High` - 8
42
- * - `Priority.Highest` - 16
43
- */
44
- selectionPolicy?: SelectionPolicy;
45
- }
@@ -1,58 +0,0 @@
1
- import { type Priority } from '@stimulcross/ds-policy-priority-queue';
2
- import { type LimitBehavior } from '../types/limit-behavior.js';
3
-
4
- /**
5
- * Options for running a single task.
6
- */
7
- export interface RateLimiterRunOptions {
8
- /**
9
- * A unique identifier for the task for logging and debugging.
10
- *
11
- * If not provided, the library will generate a unique ID.
12
- */
13
- id?: string;
14
-
15
- /**
16
- * A storage key for the task.
17
- */
18
- key?: string;
19
-
20
- /**
21
- * The cost to consume the limit.
22
- *
23
- * @default 1
24
- */
25
- cost?: number;
26
-
27
- /**
28
- * Defines the behavior when the limit is reached for the current task. Overrides the global limit behavior
29
- * set in {@link RateLimiterOptions.limitBehavior}.
30
- *
31
- * - `reject` - rejects the task with `LIMIT_EXCEEDED` error code
32
- * - `enqueue` - enqueues the task if possible
33
- *
34
- * Defaults to global {@link RateLimiterOptions.limitBehavior}
35
- */
36
- limitBehavior?: LimitBehavior;
37
-
38
- /**
39
- * Task priority.
40
- *
41
- * @default Priority.Normal (3)
42
- */
43
- priority?: Priority;
44
-
45
- /**
46
- * An abort signal to abort the task execution.
47
- */
48
- signal?: AbortSignal;
49
-
50
- /**
51
- * Maximum wait time in milliseconds for the task in the queue.
52
- *
53
- * This does not affect execution time.
54
- *
55
- * @default Infinity
56
- */
57
- maxWaitMs?: number;
58
- }