@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.
Files changed (238) hide show
  1. package/README.md +20 -0
  2. package/lib/core/cancellable.d.ts +5 -0
  3. package/lib/core/cancellable.js +2 -0
  4. package/lib/core/clock.d.ts +10 -0
  5. package/lib/core/clock.js +2 -0
  6. package/{src/core/decision.ts → lib/core/decision.d.ts} +7 -11
  7. package/lib/core/decision.js +2 -0
  8. package/lib/core/rate-limit-policy.d.ts +14 -0
  9. package/lib/core/rate-limit-policy.js +2 -0
  10. package/lib/core/rate-limiter-status.d.ts +14 -0
  11. package/lib/core/rate-limiter-status.js +2 -0
  12. package/lib/core/rate-limiter.d.ts +34 -0
  13. package/lib/core/rate-limiter.js +2 -0
  14. package/lib/core/state-storage.d.ts +46 -0
  15. package/lib/core/state-storage.js +2 -0
  16. package/lib/enums/rate-limit-error-code.d.ts +26 -0
  17. package/lib/enums/rate-limit-error-code.js +27 -0
  18. package/lib/errors/custom.error.d.ts +6 -0
  19. package/lib/errors/custom.error.js +13 -0
  20. package/lib/errors/invalid-cost.error.d.ts +16 -0
  21. package/lib/errors/invalid-cost.error.js +26 -0
  22. package/lib/errors/rate-limit.error.d.ts +37 -0
  23. package/lib/errors/rate-limit.error.js +75 -0
  24. package/lib/errors/rate-limiter-destroyed.error.d.ts +7 -0
  25. package/lib/errors/rate-limiter-destroyed.error.js +9 -0
  26. package/{src/index.ts → lib/index.d.ts} +1 -0
  27. package/lib/index.js +5 -0
  28. package/lib/interfaces/rate-limiter-options.d.ts +76 -0
  29. package/lib/interfaces/rate-limiter-options.js +2 -0
  30. package/lib/interfaces/rate-limiter-queue-options.d.ts +42 -0
  31. package/lib/interfaces/rate-limiter-queue-options.js +2 -0
  32. package/lib/interfaces/rate-limiter-run-options.d.ts +52 -0
  33. package/lib/interfaces/rate-limiter-run-options.js +2 -0
  34. package/lib/limiters/abstract-rate-limiter.d.ts +44 -0
  35. package/lib/limiters/abstract-rate-limiter.js +133 -0
  36. package/lib/limiters/composite.policy.d.ts +15 -0
  37. package/lib/limiters/composite.policy.js +73 -0
  38. package/lib/limiters/fixed-window/fixed-window.limiter.d.ts +33 -0
  39. package/lib/limiters/fixed-window/fixed-window.limiter.js +85 -0
  40. package/lib/limiters/fixed-window/fixed-window.options.d.ts +27 -0
  41. package/lib/limiters/fixed-window/fixed-window.options.js +2 -0
  42. package/lib/limiters/fixed-window/fixed-window.policy.d.ts +19 -0
  43. package/lib/limiters/fixed-window/fixed-window.policy.js +121 -0
  44. package/{src/limiters/fixed-window/fixed-window.state.ts → lib/limiters/fixed-window/fixed-window.state.d.ts} +4 -3
  45. package/lib/limiters/fixed-window/fixed-window.state.js +2 -0
  46. package/lib/limiters/fixed-window/fixed-window.status.d.ts +39 -0
  47. package/lib/limiters/fixed-window/fixed-window.status.js +2 -0
  48. package/{src/limiters/fixed-window/index.ts → lib/limiters/fixed-window/index.d.ts} +1 -0
  49. package/lib/limiters/fixed-window/index.js +2 -0
  50. package/lib/limiters/generic-cell/generic-cell.limiter.d.ts +30 -0
  51. package/lib/limiters/generic-cell/generic-cell.limiter.js +74 -0
  52. package/lib/limiters/generic-cell/generic-cell.options.d.ts +22 -0
  53. package/lib/limiters/generic-cell/generic-cell.options.js +2 -0
  54. package/lib/limiters/generic-cell/generic-cell.policy.d.ts +18 -0
  55. package/lib/limiters/generic-cell/generic-cell.policy.js +87 -0
  56. package/{src/limiters/generic-cell/generic-cell.state.ts → lib/limiters/generic-cell/generic-cell.state.d.ts} +2 -1
  57. package/lib/limiters/generic-cell/generic-cell.state.js +2 -0
  58. package/lib/limiters/generic-cell/generic-cell.status.d.ts +49 -0
  59. package/lib/limiters/generic-cell/generic-cell.status.js +2 -0
  60. package/{src/limiters/generic-cell/index.ts → lib/limiters/generic-cell/index.d.ts} +1 -0
  61. package/lib/limiters/generic-cell/index.js +2 -0
  62. package/{src/limiters/http-response-based/http-limit-info.extractor.ts → lib/limiters/http-response-based/http-limit-info.extractor.d.ts} +2 -6
  63. package/lib/limiters/http-response-based/http-limit-info.extractor.js +2 -0
  64. package/lib/limiters/http-response-based/http-limit.info.d.ts +39 -0
  65. package/lib/limiters/http-response-based/http-limit.info.js +2 -0
  66. 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
  67. package/lib/limiters/http-response-based/http-response-based-limiter.options.js +2 -0
  68. package/lib/limiters/http-response-based/http-response-based-limiter.state.d.ts +14 -0
  69. package/lib/limiters/http-response-based/http-response-based-limiter.state.js +2 -0
  70. package/lib/limiters/http-response-based/http-response-based-limiter.status.d.ts +70 -0
  71. package/lib/limiters/http-response-based/http-response-based-limiter.status.js +2 -0
  72. package/lib/limiters/http-response-based/http-response-based.limiter.d.ts +56 -0
  73. package/lib/limiters/http-response-based/http-response-based.limiter.js +386 -0
  74. package/{src/limiters/http-response-based/index.ts → lib/limiters/http-response-based/index.d.ts} +1 -0
  75. package/lib/limiters/http-response-based/index.js +2 -0
  76. package/{src/limiters/leaky-bucket/index.ts → lib/limiters/leaky-bucket/index.d.ts} +1 -0
  77. package/lib/limiters/leaky-bucket/index.js +2 -0
  78. package/lib/limiters/leaky-bucket/leaky-bucket.limiter.d.ts +30 -0
  79. package/lib/limiters/leaky-bucket/leaky-bucket.limiter.js +75 -0
  80. package/lib/limiters/leaky-bucket/leaky-bucket.options.d.ts +22 -0
  81. package/lib/limiters/leaky-bucket/leaky-bucket.options.js +2 -0
  82. package/lib/limiters/leaky-bucket/leaky-bucket.policy.d.ts +19 -0
  83. package/lib/limiters/leaky-bucket/leaky-bucket.policy.js +101 -0
  84. package/{src/limiters/leaky-bucket/leaky-bucket.state.ts → lib/limiters/leaky-bucket/leaky-bucket.state.d.ts} +3 -2
  85. package/lib/limiters/leaky-bucket/leaky-bucket.state.js +2 -0
  86. package/lib/limiters/leaky-bucket/leaky-bucket.status.d.ts +31 -0
  87. package/lib/limiters/leaky-bucket/leaky-bucket.status.js +2 -0
  88. package/{src/limiters/sliding-window-counter/index.ts → lib/limiters/sliding-window-counter/index.d.ts} +2 -4
  89. package/lib/limiters/sliding-window-counter/index.js +2 -0
  90. package/lib/limiters/sliding-window-counter/sliding-window-counter.limiter.d.ts +28 -0
  91. package/lib/limiters/sliding-window-counter/sliding-window-counter.limiter.js +47 -0
  92. package/lib/limiters/sliding-window-counter/sliding-window-counter.options.d.ts +16 -0
  93. package/lib/limiters/sliding-window-counter/sliding-window-counter.options.js +2 -0
  94. package/lib/limiters/sliding-window-counter/sliding-window-counter.policy.d.ts +18 -0
  95. package/lib/limiters/sliding-window-counter/sliding-window-counter.policy.js +128 -0
  96. package/{src/limiters/sliding-window-counter/sliding-window-counter.state.ts → lib/limiters/sliding-window-counter/sliding-window-counter.state.d.ts} +4 -3
  97. package/lib/limiters/sliding-window-counter/sliding-window-counter.state.js +2 -0
  98. package/lib/limiters/sliding-window-counter/sliding-window-counter.status.d.ts +45 -0
  99. package/lib/limiters/sliding-window-counter/sliding-window-counter.status.js +2 -0
  100. package/{src/limiters/sliding-window-log/index.ts → lib/limiters/sliding-window-log/index.d.ts} +1 -0
  101. package/lib/limiters/sliding-window-log/index.js +2 -0
  102. package/lib/limiters/sliding-window-log/sliding-window-log.limiter.d.ts +27 -0
  103. package/lib/limiters/sliding-window-log/sliding-window-log.limiter.js +44 -0
  104. package/lib/limiters/sliding-window-log/sliding-window-log.options.d.ts +16 -0
  105. package/lib/limiters/sliding-window-log/sliding-window-log.options.js +2 -0
  106. package/lib/limiters/sliding-window-log/sliding-window-log.policy.d.ts +18 -0
  107. package/lib/limiters/sliding-window-log/sliding-window-log.policy.js +124 -0
  108. package/{src/limiters/sliding-window-log/sliding-window-log.state.ts → lib/limiters/sliding-window-log/sliding-window-log.state.d.ts} +5 -6
  109. package/lib/limiters/sliding-window-log/sliding-window-log.state.js +2 -0
  110. package/lib/limiters/sliding-window-log/sliding-window-log.status.d.ts +39 -0
  111. package/lib/limiters/sliding-window-log/sliding-window-log.status.js +2 -0
  112. package/{src/limiters/token-bucket/index.ts → lib/limiters/token-bucket/index.d.ts} +1 -0
  113. package/lib/limiters/token-bucket/index.js +2 -0
  114. package/lib/limiters/token-bucket/token-bucket.limiter.d.ts +30 -0
  115. package/lib/limiters/token-bucket/token-bucket.limiter.js +75 -0
  116. package/{src/limiters/token-bucket/token-bucket.options.ts → lib/limiters/token-bucket/token-bucket.options.d.ts} +9 -10
  117. package/lib/limiters/token-bucket/token-bucket.options.js +2 -0
  118. package/lib/limiters/token-bucket/token-bucket.policy.d.ts +19 -0
  119. package/lib/limiters/token-bucket/token-bucket.policy.js +116 -0
  120. package/{src/limiters/token-bucket/token-bucket.state.ts → lib/limiters/token-bucket/token-bucket.state.d.ts} +4 -3
  121. package/lib/limiters/token-bucket/token-bucket.state.js +2 -0
  122. package/lib/limiters/token-bucket/token-bucket.status.d.ts +31 -0
  123. package/lib/limiters/token-bucket/token-bucket.status.js +2 -0
  124. package/lib/runtime/default-clock.d.ts +4 -0
  125. package/lib/runtime/default-clock.js +7 -0
  126. package/lib/runtime/execution-tickets.d.ts +12 -0
  127. package/lib/runtime/execution-tickets.js +27 -0
  128. package/lib/runtime/in-memory-state-store.d.ts +19 -0
  129. package/lib/runtime/in-memory-state-store.js +97 -0
  130. package/lib/runtime/rate-limiter.executor.d.ts +47 -0
  131. package/lib/runtime/rate-limiter.executor.js +196 -0
  132. package/lib/runtime/semaphore.d.ts +9 -0
  133. package/lib/runtime/semaphore.js +28 -0
  134. package/lib/runtime/task.d.ts +41 -0
  135. package/lib/runtime/task.js +101 -0
  136. package/{src/types/limit-behavior.ts → lib/types/limit-behavior.d.ts} +1 -0
  137. package/lib/types/limit-behavior.js +2 -0
  138. package/lib/utils/generate-random-string.d.ts +3 -0
  139. package/lib/utils/generate-random-string.js +13 -0
  140. package/lib/utils/promise-with-resolvers.d.ts +9 -0
  141. package/lib/utils/promise-with-resolvers.js +15 -0
  142. package/lib/utils/sanitize-error.d.ts +3 -0
  143. package/lib/utils/sanitize-error.js +5 -0
  144. package/lib/utils/sanitize-priority.d.ts +4 -0
  145. package/lib/utils/sanitize-priority.js +18 -0
  146. package/lib/utils/validate-cost.d.ts +3 -0
  147. package/lib/utils/validate-cost.js +14 -0
  148. package/package.json +13 -2
  149. package/.editorconfig +0 -21
  150. package/.github/workflows/node.yml +0 -87
  151. package/.husky/commit-msg +0 -1
  152. package/.husky/pre-commit +0 -1
  153. package/.megaignore +0 -8
  154. package/.prettierignore +0 -3
  155. package/commitlint.config.js +0 -8
  156. package/eslint.config.js +0 -65
  157. package/lint-staged.config.js +0 -4
  158. package/prettier.config.cjs +0 -1
  159. package/src/core/cancellable.ts +0 -4
  160. package/src/core/clock.ts +0 -9
  161. package/src/core/rate-limit-policy.ts +0 -15
  162. package/src/core/rate-limiter-status.ts +0 -14
  163. package/src/core/rate-limiter.ts +0 -37
  164. package/src/core/state-storage.ts +0 -51
  165. package/src/enums/rate-limit-error-code.ts +0 -29
  166. package/src/errors/custom.error.ts +0 -14
  167. package/src/errors/invalid-cost.error.ts +0 -33
  168. package/src/errors/rate-limit.error.ts +0 -91
  169. package/src/errors/rate-limiter-destroyed.error.ts +0 -8
  170. package/src/interfaces/rate-limiter-options.ts +0 -84
  171. package/src/interfaces/rate-limiter-queue-options.ts +0 -45
  172. package/src/interfaces/rate-limiter-run-options.ts +0 -58
  173. package/src/limiters/abstract-rate-limiter.ts +0 -206
  174. package/src/limiters/composite.policy.ts +0 -102
  175. package/src/limiters/fixed-window/fixed-window.limiter.ts +0 -121
  176. package/src/limiters/fixed-window/fixed-window.options.ts +0 -29
  177. package/src/limiters/fixed-window/fixed-window.policy.ts +0 -159
  178. package/src/limiters/fixed-window/fixed-window.status.ts +0 -46
  179. package/src/limiters/generic-cell/generic-cell.limiter.ts +0 -108
  180. package/src/limiters/generic-cell/generic-cell.options.ts +0 -23
  181. package/src/limiters/generic-cell/generic-cell.policy.ts +0 -115
  182. package/src/limiters/generic-cell/generic-cell.status.ts +0 -54
  183. package/src/limiters/http-response-based/http-limit.info.ts +0 -41
  184. package/src/limiters/http-response-based/http-response-based-limiter.state.ts +0 -13
  185. package/src/limiters/http-response-based/http-response-based-limiter.status.ts +0 -74
  186. package/src/limiters/http-response-based/http-response-based.limiter.ts +0 -512
  187. package/src/limiters/leaky-bucket/leaky-bucket.limiter.ts +0 -105
  188. package/src/limiters/leaky-bucket/leaky-bucket.options.ts +0 -23
  189. package/src/limiters/leaky-bucket/leaky-bucket.policy.ts +0 -134
  190. package/src/limiters/leaky-bucket/leaky-bucket.status.ts +0 -36
  191. package/src/limiters/sliding-window-counter/sliding-window-counter.limiter.ts +0 -76
  192. package/src/limiters/sliding-window-counter/sliding-window-counter.options.ts +0 -20
  193. package/src/limiters/sliding-window-counter/sliding-window-counter.policy.ts +0 -167
  194. package/src/limiters/sliding-window-counter/sliding-window-counter.status.ts +0 -53
  195. package/src/limiters/sliding-window-log/sliding-window-log.limiter.ts +0 -65
  196. package/src/limiters/sliding-window-log/sliding-window-log.options.ts +0 -20
  197. package/src/limiters/sliding-window-log/sliding-window-log.policy.ts +0 -166
  198. package/src/limiters/sliding-window-log/sliding-window-log.status.ts +0 -44
  199. package/src/limiters/token-bucket/token-bucket.limiter.ts +0 -110
  200. package/src/limiters/token-bucket/token-bucket.policy.ts +0 -155
  201. package/src/limiters/token-bucket/token-bucket.status.ts +0 -36
  202. package/src/runtime/default-clock.ts +0 -8
  203. package/src/runtime/execution-tickets.ts +0 -34
  204. package/src/runtime/in-memory-state-store.ts +0 -135
  205. package/src/runtime/rate-limiter.executor.ts +0 -286
  206. package/src/runtime/semaphore.ts +0 -31
  207. package/src/runtime/task.ts +0 -141
  208. package/src/utils/generate-random-string.ts +0 -16
  209. package/src/utils/promise-with-resolvers.ts +0 -23
  210. package/src/utils/sanitize-error.ts +0 -4
  211. package/src/utils/sanitize-priority.ts +0 -22
  212. package/src/utils/validate-cost.ts +0 -16
  213. package/tests/integration/limiters/fixed-window.limiter.spec.ts +0 -371
  214. package/tests/integration/limiters/generic-cell.limiter.spec.ts +0 -361
  215. package/tests/integration/limiters/http-response-based.limiter.spec.ts +0 -833
  216. package/tests/integration/limiters/leaky-bucket.spec.ts +0 -357
  217. package/tests/integration/limiters/sliding-window-counter.limiter.spec.ts +0 -175
  218. package/tests/integration/limiters/sliding-window-log.spec.ts +0 -185
  219. package/tests/integration/limiters/token-bucket.limiter.spec.ts +0 -363
  220. package/tests/tsconfig.json +0 -4
  221. package/tests/unit/policies/composite.policy.spec.ts +0 -244
  222. package/tests/unit/policies/fixed-window.policy.spec.ts +0 -260
  223. package/tests/unit/policies/generic-cell.policy.spec.ts +0 -178
  224. package/tests/unit/policies/leaky-bucket.policy.spec.ts +0 -215
  225. package/tests/unit/policies/sliding-window-counter.policy.spec.ts +0 -209
  226. package/tests/unit/policies/sliding-window-log.policy.spec.ts +0 -285
  227. package/tests/unit/policies/token-bucket.policy.spec.ts +0 -371
  228. package/tests/unit/runtime/execution-tickets.spec.ts +0 -121
  229. package/tests/unit/runtime/in-memory-state-store.spec.ts +0 -238
  230. package/tests/unit/runtime/rate-limiter.executor.spec.ts +0 -353
  231. package/tests/unit/runtime/semaphore.spec.ts +0 -98
  232. package/tests/unit/runtime/task.spec.ts +0 -182
  233. package/tests/unit/utils/generate-random-string.spec.ts +0 -51
  234. package/tests/unit/utils/promise-with-resolvers.spec.ts +0 -57
  235. package/tests/unit/utils/sanitize-priority.spec.ts +0 -46
  236. package/tests/unit/utils/validate-cost.spec.ts +0 -48
  237. package/tsconfig.json +0 -14
  238. package/vitest.config.js +0 -22
@@ -0,0 +1,31 @@
1
+ import { type RateLimiterStatus } from '../../core/rate-limiter-status.js';
2
+ /**
3
+ * The status of the Leaky Bucket rate limiter.
4
+ */
5
+ export interface LeakyBucketStatus extends RateLimiterStatus {
6
+ /**
7
+ * The maximum capacity of the bucket (maximum number of requests that can be queued).
8
+ */
9
+ capacity: number;
10
+ /**
11
+ * The rate at which requests leak from the bucket (requests per second).
12
+ */
13
+ leakRate: number;
14
+ /**
15
+ * The current number of requests in the bucket waiting to be processed.
16
+ */
17
+ level: number;
18
+ /**
19
+ * The number of requests that can still be added to the bucket before reaching capacity.
20
+ */
21
+ remaining: number;
22
+ /**
23
+ * Timestamp (in milliseconds) when the next request slot will become available.
24
+ */
25
+ nextAvailableAt: number;
26
+ /**
27
+ * Timestamp (in milliseconds) when all queued requests will have leaked (bucket becomes empty).
28
+ */
29
+ resetAt: number;
30
+ }
31
+ //# sourceMappingURL=leaky-bucket.status.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=leaky-bucket.status.js.map
@@ -1,7 +1,5 @@
1
1
  export type { SlidingWindowCounterState } from './sliding-window-counter.state.js';
2
2
  export type { SlidingWindowCounterStatus } from './sliding-window-counter.status.js';
3
3
  export type { SlidingWindowCounterOptions } from './sliding-window-counter.options.js';
4
- export {
5
- type SlidingWindowCounterLimiterRunOptions,
6
- SlidingWindowCounterLimiter,
7
- } from './sliding-window-counter.limiter.js';
4
+ export { type SlidingWindowCounterLimiterRunOptions, SlidingWindowCounterLimiter, } from './sliding-window-counter.limiter.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { SlidingWindowCounterLimiter, } from './sliding-window-counter.limiter.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ import { type SlidingWindowCounterOptions } from './sliding-window-counter.options.js';
2
+ import { SlidingWindowCounterPolicy } from './sliding-window-counter.policy.js';
3
+ import { type SlidingWindowCounterState } from './sliding-window-counter.state.js';
4
+ import { type SlidingWindowCounterStatus } from './sliding-window-counter.status.js';
5
+ import { type RateLimiterRunOptions } from '../../interfaces/rate-limiter-run-options.js';
6
+ import { AbstractRateLimiter, type ExecutionContext } from '../abstract-rate-limiter.js';
7
+ /**
8
+ * The options for running a task in the Sliding Window Counter rate limiter.
9
+ */
10
+ export type SlidingWindowCounterLimiterRunOptions = Omit<RateLimiterRunOptions, 'limitBehavior' | 'priority' | 'maxWaitMs'>;
11
+ /**
12
+ * Sliding Window Counter rate limiter.
13
+ *
14
+ * Designed primarily for client-side use to respect third-party limits or protect resources.
15
+ * While this can be used as a server-side limiter with custom distributed storage
16
+ * (e.g., Redis), it is best-effort and not recommended due to high network round-trip latency.
17
+ *
18
+ * Note: Unlike queue-based limiters, this implementation operates in strict immediate mode.
19
+ * It does not support request queueing, delays, priorities, or task cancellation.
20
+ */
21
+ export declare class SlidingWindowCounterLimiter extends AbstractRateLimiter<SlidingWindowCounterState, SlidingWindowCounterStatus> {
22
+ protected readonly _policy: SlidingWindowCounterPolicy;
23
+ private readonly _storeTtl;
24
+ constructor(options: SlidingWindowCounterOptions);
25
+ protected _runInternal<T>(fn: () => T | Promise<T>, ctx: ExecutionContext): Promise<T>;
26
+ protected _getDebugStateString(state: SlidingWindowCounterState): string;
27
+ }
28
+ //# sourceMappingURL=sliding-window-counter.limiter.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { SlidingWindowCounterPolicy } from './sliding-window-counter.policy.js';
2
+ import { RateLimitErrorCode } from '../../enums/rate-limit-error-code.js';
3
+ import { RateLimitError } from '../../errors/rate-limit.error.js';
4
+ import { AbstractRateLimiter } from '../abstract-rate-limiter.js';
5
+ /**
6
+ * Sliding Window Counter rate limiter.
7
+ *
8
+ * Designed primarily for client-side use to respect third-party limits or protect resources.
9
+ * While this can be used as a server-side limiter with custom distributed storage
10
+ * (e.g., Redis), it is best-effort and not recommended due to high network round-trip latency.
11
+ *
12
+ * Note: Unlike queue-based limiters, this implementation operates in strict immediate mode.
13
+ * It does not support request queueing, delays, priorities, or task cancellation.
14
+ */
15
+ export class SlidingWindowCounterLimiter extends AbstractRateLimiter {
16
+ _policy;
17
+ _storeTtl;
18
+ constructor(options) {
19
+ super(options);
20
+ this._policy = new SlidingWindowCounterPolicy(options.limit, options.windowMs);
21
+ this._storeTtl = options.windowMs * 2;
22
+ }
23
+ async _runInternal(fn, ctx) {
24
+ const now = this._clock.now();
25
+ await this._store.acquireLock?.(ctx.key);
26
+ try {
27
+ const state = (await this._store.get(ctx.key)) ?? this._policy.getInitialState();
28
+ const { decision, nextState } = this._policy.evaluate(state, now, ctx.cost);
29
+ if (decision.kind === 'deny') {
30
+ this._shouldPrintDebug &&
31
+ this._logger.debug(`[DENY] [id: ${ctx.id}, key: ${ctx.key}] - Retry: +${decision.retryAt - now}ms`);
32
+ throw new RateLimitError(RateLimitErrorCode.LimitExceeded, decision.retryAt);
33
+ }
34
+ await this._store.set(ctx.key, nextState, this._storeTtl);
35
+ this._shouldPrintDebug &&
36
+ this._logger.debug(`[ALLOW] [id: ${ctx.id}, key: ${ctx.key}] - ${this._getDebugStateString(nextState)}`);
37
+ }
38
+ finally {
39
+ await this._store.releaseLock?.(ctx.key);
40
+ }
41
+ return await this._execute(fn, now, this._storeTtl, ctx);
42
+ }
43
+ _getDebugStateString(state) {
44
+ return `lim: ${this._policy.limit}; c/p: ${state.currentCount}/${state.previousCount}`;
45
+ }
46
+ }
47
+ //# sourceMappingURL=sliding-window-counter.limiter.js.map
@@ -0,0 +1,16 @@
1
+ import { type SlidingWindowCounterState } from './sliding-window-counter.state.js';
2
+ import { type RateLimiterOptions } from '../../interfaces/rate-limiter-options.js';
3
+ /**
4
+ * Options for the Sliding Window Counter rate limiter.
5
+ */
6
+ export interface SlidingWindowCounterOptions extends Omit<RateLimiterOptions<SlidingWindowCounterState>, 'queue' | 'limitBehavior'> {
7
+ /**
8
+ * Maximum number of requests allowed within the time window.
9
+ */
10
+ limit: number;
11
+ /**
12
+ * Duration of the time window in milliseconds.
13
+ */
14
+ windowMs: number;
15
+ }
16
+ //# sourceMappingURL=sliding-window-counter.options.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sliding-window-counter.options.js.map
@@ -0,0 +1,18 @@
1
+ import { type SlidingWindowCounterState } from './sliding-window-counter.state.js';
2
+ import { type SlidingWindowCounterStatus } from './sliding-window-counter.status.js';
3
+ import { type RateLimitPolicy, type RateLimitPolicyResult } from '../../core/rate-limit-policy.js';
4
+ /** @internal */
5
+ export declare class SlidingWindowCounterPolicy implements RateLimitPolicy<SlidingWindowCounterState, SlidingWindowCounterStatus> {
6
+ private readonly _limit;
7
+ private readonly _windowMs;
8
+ constructor(_limit: number, _windowMs: number);
9
+ get limit(): number;
10
+ get windowMs(): number;
11
+ getInitialState(): SlidingWindowCounterState;
12
+ getStatus(state: SlidingWindowCounterState, now: number): SlidingWindowCounterStatus;
13
+ evaluate(state: SlidingWindowCounterState, now: number, cost: number): RateLimitPolicyResult<SlidingWindowCounterState>;
14
+ revert(state: SlidingWindowCounterState, cost: number, now: number): SlidingWindowCounterState;
15
+ private _syncState;
16
+ private _calculateAvailableAt;
17
+ }
18
+ //# sourceMappingURL=sliding-window-counter.policy.d.ts.map
@@ -0,0 +1,128 @@
1
+ import { validateCost } from '../../utils/validate-cost.js';
2
+ /** @internal */
3
+ export class SlidingWindowCounterPolicy {
4
+ _limit;
5
+ _windowMs;
6
+ constructor(_limit, _windowMs) {
7
+ this._limit = _limit;
8
+ this._windowMs = _windowMs;
9
+ if (!Number.isFinite(_limit) || !Number.isInteger(_limit) || _limit < 0) {
10
+ throw new Error(`Invalid limit: ${_limit}. Must be a positive integer.`);
11
+ }
12
+ if (!Number.isFinite(_windowMs) || !Number.isInteger(_windowMs) || _windowMs <= 0) {
13
+ throw new Error(`Invalid windowMs: ${_windowMs}. Must be a positive integer.`);
14
+ }
15
+ }
16
+ get limit() {
17
+ return this._limit;
18
+ }
19
+ get windowMs() {
20
+ return this._windowMs;
21
+ }
22
+ getInitialState() {
23
+ return {
24
+ windowStart: 0,
25
+ currentCount: 0,
26
+ previousCount: 0,
27
+ };
28
+ }
29
+ getStatus(state, now) {
30
+ const syncedState = this._syncState(state, now);
31
+ const { windowStart, previousCount, currentCount } = syncedState;
32
+ const timeIntoCurrentWindow = now - windowStart;
33
+ const weight = (this._windowMs - timeIntoCurrentWindow) / this._windowMs;
34
+ const estimatedCount = Math.floor(previousCount * weight + currentCount);
35
+ const remaining = Math.max(0, this._limit - estimatedCount);
36
+ const nextAvailableAt = this._calculateAvailableAt(windowStart, previousCount, currentCount, 1, now);
37
+ let resetAt;
38
+ if (currentCount > 0) {
39
+ resetAt = windowStart + 2 * this._windowMs;
40
+ }
41
+ else if (previousCount > 0) {
42
+ resetAt = windowStart + this._windowMs;
43
+ }
44
+ else {
45
+ resetAt = now;
46
+ }
47
+ return {
48
+ limit: this._limit,
49
+ windowMs: this._windowMs,
50
+ windowStart,
51
+ currentCount,
52
+ previousCount,
53
+ estimatedCount,
54
+ remaining,
55
+ nextAvailableAt,
56
+ resetAt,
57
+ };
58
+ }
59
+ evaluate(state, now, cost) {
60
+ validateCost(cost, this._limit);
61
+ const syncedState = this._syncState(state, now);
62
+ const { windowStart, previousCount, currentCount } = syncedState;
63
+ const timeIntoCurrentWindow = now - windowStart;
64
+ const weight = (this._windowMs - timeIntoCurrentWindow) / this._windowMs;
65
+ const estimatedCount = Math.floor(previousCount * weight + currentCount);
66
+ if (estimatedCount + cost <= this._limit) {
67
+ return {
68
+ decision: { kind: 'allow' },
69
+ nextState: {
70
+ windowStart,
71
+ previousCount,
72
+ currentCount: currentCount + cost,
73
+ },
74
+ };
75
+ }
76
+ const availableAt = this._calculateAvailableAt(windowStart, previousCount, currentCount, cost, now);
77
+ return {
78
+ decision: { kind: 'deny', retryAt: availableAt },
79
+ nextState: syncedState,
80
+ };
81
+ }
82
+ revert(state, cost, now) {
83
+ if (cost <= 0) {
84
+ return state;
85
+ }
86
+ const syncedState = this._syncState(state, now);
87
+ return {
88
+ ...syncedState,
89
+ currentCount: Math.max(0, syncedState.currentCount - cost),
90
+ };
91
+ }
92
+ _syncState(state, now) {
93
+ const currentWindowStart = Math.floor(now / this._windowMs) * this._windowMs;
94
+ if (currentWindowStart <= state.windowStart) {
95
+ return state;
96
+ }
97
+ const windowsPassed = Math.floor((currentWindowStart - state.windowStart) / this._windowMs);
98
+ if (windowsPassed > 1) {
99
+ return { windowStart: currentWindowStart, previousCount: 0, currentCount: 0 };
100
+ }
101
+ return {
102
+ windowStart: currentWindowStart,
103
+ previousCount: state.currentCount,
104
+ currentCount: 0,
105
+ };
106
+ }
107
+ _calculateAvailableAt(windowStart, prev, curr, cost, now) {
108
+ if (curr + cost <= this._limit) {
109
+ if (prev === 0) {
110
+ return now;
111
+ }
112
+ const numerator = this._windowMs * (prev + curr + cost - this._limit - 1);
113
+ const t = Math.max(0, Math.floor(numerator / prev) + 1);
114
+ const absoluteTime = windowStart + t;
115
+ return Math.max(now, absoluteTime);
116
+ }
117
+ const newPrev = curr;
118
+ const newWindowStart = windowStart + this._windowMs;
119
+ if (newPrev === 0) {
120
+ return Math.max(now, newWindowStart);
121
+ }
122
+ const numerator = this._windowMs * (newPrev + cost - this._limit - 1);
123
+ const t = Math.max(0, Math.floor(numerator / newPrev) + 1);
124
+ const absoluteTime = newWindowStart + t;
125
+ return Math.max(now, absoluteTime);
126
+ }
127
+ }
128
+ //# sourceMappingURL=sliding-window-counter.policy.js.map
@@ -4,7 +4,8 @@
4
4
  * When using a distributed state store, make sure it properly serializes and deserializes the state.
5
5
  */
6
6
  export interface SlidingWindowCounterState {
7
- windowStart: number;
8
- currentCount: number;
9
- previousCount: number;
7
+ windowStart: number;
8
+ currentCount: number;
9
+ previousCount: number;
10
10
  }
11
+ //# sourceMappingURL=sliding-window-counter.state.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sliding-window-counter.state.js.map
@@ -0,0 +1,45 @@
1
+ import { type RateLimiterStatus } from '../../core/rate-limiter-status.js';
2
+ /**
3
+ * The status of the Sliding Window Counter rate limiter.
4
+ */
5
+ export interface SlidingWindowCounterStatus extends RateLimiterStatus {
6
+ /**
7
+ * Maximum number of requests allowed within the time window.
8
+ */
9
+ limit: number;
10
+ /**
11
+ * Duration of the time window in milliseconds.
12
+ */
13
+ windowMs: number;
14
+ /**
15
+ * Timestamp (in milliseconds) when the current window started.
16
+ */
17
+ windowStart: number;
18
+ /**
19
+ * Number of requests made in the current window.
20
+ */
21
+ currentCount: number;
22
+ /**
23
+ * Number of requests made in the previous window.
24
+ */
25
+ previousCount: number;
26
+ /**
27
+ * Estimated request count based on the sliding window algorithm.
28
+ *
29
+ * Calculated by combining current and previous window counts proportionally.
30
+ */
31
+ estimatedCount: number;
32
+ /**
33
+ * Number of requests remaining before hitting the limit.
34
+ */
35
+ remaining: number;
36
+ /**
37
+ * Timestamp (in milliseconds) when the next request slot becomes available.
38
+ */
39
+ nextAvailableAt: number;
40
+ /**
41
+ * Timestamp (in milliseconds) when the current window resets.
42
+ */
43
+ resetAt: number;
44
+ }
45
+ //# sourceMappingURL=sliding-window-counter.status.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sliding-window-counter.status.js.map
@@ -2,3 +2,4 @@ export type { SlidingWindowLogEntry, SlidingWindowLogState } from './sliding-win
2
2
  export type { SlidingWindowLogStatus } from './sliding-window-log.status.js';
3
3
  export type { SlidingWindowLogOptions } from './sliding-window-log.options.js';
4
4
  export { type SlidingWindowLogLimiterRunOptions, SlidingWindowLogLimiter } from './sliding-window-log.limiter.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { SlidingWindowLogLimiter } from './sliding-window-log.limiter.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,27 @@
1
+ import { type SlidingWindowLogOptions } from './sliding-window-log.options.js';
2
+ import { SlidingWindowLogPolicy } from './sliding-window-log.policy.js';
3
+ import { type SlidingWindowLogState } from './sliding-window-log.state.js';
4
+ import { type SlidingWindowLogStatus } from './sliding-window-log.status.js';
5
+ import { type RateLimiterRunOptions } from '../../interfaces/rate-limiter-run-options.js';
6
+ import { AbstractRateLimiter, type ExecutionContext } from '../abstract-rate-limiter.js';
7
+ /**
8
+ * The options for running a task in the Sliding Window Log rate limiter.
9
+ */
10
+ export type SlidingWindowLogLimiterRunOptions = Omit<RateLimiterRunOptions, 'limitBehavior' | 'priority'>;
11
+ /**
12
+ * Sliding Window Log rate limiter.
13
+ *
14
+ * Designed primarily for client-side use to respect third-party limits or protect resources.
15
+ * While this can be used as a server-side limiter with custom distributed storage
16
+ * (e.g., Redis), it is best-effort and not recommended due to high network round-trip latency.
17
+ *
18
+ * Note: Unlike queue-based limiters, this implementation operates in strict immediate mode.
19
+ * It does not support request queueing, delays, priorities, or task cancellation.
20
+ */
21
+ export declare class SlidingWindowLogLimiter extends AbstractRateLimiter<SlidingWindowLogState, SlidingWindowLogStatus> {
22
+ protected readonly _policy: SlidingWindowLogPolicy;
23
+ constructor(options: SlidingWindowLogOptions);
24
+ protected _runInternal<T>(fn: () => T | Promise<T>, ctx: ExecutionContext): Promise<T>;
25
+ protected _getDebugStateString(state: SlidingWindowLogState): string;
26
+ }
27
+ //# sourceMappingURL=sliding-window-log.limiter.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { SlidingWindowLogPolicy } from './sliding-window-log.policy.js';
2
+ import { RateLimitErrorCode } from '../../enums/rate-limit-error-code.js';
3
+ import { RateLimitError } from '../../errors/rate-limit.error.js';
4
+ import { AbstractRateLimiter } from '../abstract-rate-limiter.js';
5
+ /**
6
+ * Sliding Window Log rate limiter.
7
+ *
8
+ * Designed primarily for client-side use to respect third-party limits or protect resources.
9
+ * While this can be used as a server-side limiter with custom distributed storage
10
+ * (e.g., Redis), it is best-effort and not recommended due to high network round-trip latency.
11
+ *
12
+ * Note: Unlike queue-based limiters, this implementation operates in strict immediate mode.
13
+ * It does not support request queueing, delays, priorities, or task cancellation.
14
+ */
15
+ export class SlidingWindowLogLimiter extends AbstractRateLimiter {
16
+ _policy;
17
+ constructor(options) {
18
+ super(options);
19
+ this._policy = new SlidingWindowLogPolicy(options.limit, options.windowMs);
20
+ }
21
+ async _runInternal(fn, ctx) {
22
+ const now = this._clock.now();
23
+ const storeTtlMs = this._policy.windowMs;
24
+ await this._store.acquireLock?.(ctx.key);
25
+ try {
26
+ const state = (await this._store.get(ctx.key)) ?? this._policy.getInitialState();
27
+ const { decision, nextState } = this._policy.evaluate(state, now, ctx.cost);
28
+ if (decision.kind === 'deny') {
29
+ this._logger.debug(`[DENY] [id: ${ctx.id}, key: ${ctx.key}] - Retry: +${decision.retryAt - now}ms`);
30
+ throw new RateLimitError(RateLimitErrorCode.LimitExceeded, decision.retryAt);
31
+ }
32
+ await this._store.set(ctx.key, nextState, storeTtlMs);
33
+ this._logger.debug(`[ALLOW] [id: ${ctx.id}, key: ${ctx.key}] - used/lim: ${nextState.totalUsed}/${this._policy.limit} logs: ${nextState.logs.size}`);
34
+ }
35
+ finally {
36
+ await this._store.releaseLock?.(ctx.key);
37
+ }
38
+ return await this._execute(fn, now, storeTtlMs, ctx);
39
+ }
40
+ _getDebugStateString(state) {
41
+ return `used/lim: ${state.totalUsed}/${this._policy.limit} logs: ${state.logs.size}`;
42
+ }
43
+ }
44
+ //# sourceMappingURL=sliding-window-log.limiter.js.map
@@ -0,0 +1,16 @@
1
+ import { type SlidingWindowLogState } from './sliding-window-log.state.js';
2
+ import { type RateLimiterOptions } from '../../interfaces/rate-limiter-options.js';
3
+ /**
4
+ * Options for the Sliding Window Log rate limiter.
5
+ */
6
+ export interface SlidingWindowLogOptions extends Omit<RateLimiterOptions<SlidingWindowLogState>, 'queue' | 'limitBehavior'> {
7
+ /**
8
+ * Maximum number of requests allowed within the time window.
9
+ */
10
+ limit: number;
11
+ /**
12
+ * Duration of the time window in milliseconds.
13
+ */
14
+ windowMs: number;
15
+ }
16
+ //# sourceMappingURL=sliding-window-log.options.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sliding-window-log.options.js.map
@@ -0,0 +1,18 @@
1
+ import { type SlidingWindowLogState } from './sliding-window-log.state.js';
2
+ import { type SlidingWindowLogStatus } from './sliding-window-log.status.js';
3
+ import { type RateLimitPolicy, type RateLimitPolicyResult } from '../../core/rate-limit-policy.js';
4
+ /** @internal */
5
+ export declare class SlidingWindowLogPolicy implements RateLimitPolicy<SlidingWindowLogState, SlidingWindowLogStatus> {
6
+ private readonly _limit;
7
+ private readonly _windowMs;
8
+ constructor(_limit: number, _windowMs: number);
9
+ get limit(): number;
10
+ get windowMs(): number;
11
+ getInitialState(): SlidingWindowLogState;
12
+ getStatus(state: SlidingWindowLogState, now: number): SlidingWindowLogStatus;
13
+ evaluate(state: SlidingWindowLogState, now: number, cost: number): RateLimitPolicyResult<SlidingWindowLogState>;
14
+ revert(state: SlidingWindowLogState, cost: number, now: number): SlidingWindowLogState;
15
+ private _syncState;
16
+ private _calculateAvailableAt;
17
+ }
18
+ //# sourceMappingURL=sliding-window-log.policy.d.ts.map
@@ -0,0 +1,124 @@
1
+ import { Deque } from '@stimulcross/ds-deque';
2
+ import { validateCost } from '../../utils/validate-cost.js';
3
+ /** @internal */
4
+ export class SlidingWindowLogPolicy {
5
+ _limit;
6
+ _windowMs;
7
+ constructor(_limit, _windowMs) {
8
+ this._limit = _limit;
9
+ this._windowMs = _windowMs;
10
+ if (!Number.isFinite(_limit) || !Number.isInteger(_limit) || _limit <= 0) {
11
+ throw new Error(`Invalid limit: ${_limit}. Must be a positive integer.`);
12
+ }
13
+ if (!Number.isFinite(_windowMs) || !Number.isInteger(_windowMs) || _windowMs <= 0) {
14
+ throw new Error(`Invalid windowMs: ${_windowMs}. Must be a positive integer.`);
15
+ }
16
+ }
17
+ get limit() {
18
+ return this._limit;
19
+ }
20
+ get windowMs() {
21
+ return this._windowMs;
22
+ }
23
+ getInitialState() {
24
+ return {
25
+ logs: new Deque(),
26
+ totalUsed: 0,
27
+ };
28
+ }
29
+ getStatus(state, now) {
30
+ const syncedState = this._syncState(state, now);
31
+ const { logs, totalUsed } = syncedState;
32
+ const remaining = Math.max(0, this._limit - totalUsed);
33
+ const nextAvailableAt = this._calculateAvailableAt(logs, totalUsed, 1, now);
34
+ const tail = logs.peekTail();
35
+ const resetAt = tail ? tail.ts + this._windowMs : now;
36
+ return {
37
+ limit: this._limit,
38
+ windowMs: this._windowMs,
39
+ totalUsed,
40
+ remaining,
41
+ nextAvailableAt,
42
+ resetAt,
43
+ };
44
+ }
45
+ evaluate(state, now, cost) {
46
+ validateCost(cost, this._limit);
47
+ const syncedState = this._syncState(state, now);
48
+ const { logs, totalUsed } = syncedState;
49
+ if (totalUsed + cost <= this._limit) {
50
+ const tail = logs.peekTail();
51
+ if (tail?.ts === now) {
52
+ tail.count += cost;
53
+ }
54
+ else {
55
+ logs.push({ ts: now, count: cost });
56
+ }
57
+ return {
58
+ decision: { kind: 'allow' },
59
+ nextState: { logs, totalUsed: totalUsed + cost },
60
+ };
61
+ }
62
+ const retryAt = this._calculateAvailableAt(logs, totalUsed, cost, now);
63
+ return {
64
+ decision: { kind: 'deny', retryAt },
65
+ nextState: syncedState,
66
+ };
67
+ }
68
+ revert(state, cost, now) {
69
+ if (cost <= 0 || state.totalUsed === 0) {
70
+ return state;
71
+ }
72
+ const syncedState = this._syncState(state, now);
73
+ const { logs } = syncedState;
74
+ let { totalUsed } = syncedState;
75
+ let remainingToRemove = cost;
76
+ while (remainingToRemove > 0 && !logs.isEmpty) {
77
+ const tail = logs.peekTail();
78
+ if (tail.count <= remainingToRemove) {
79
+ remainingToRemove -= tail.count;
80
+ totalUsed -= tail.count;
81
+ logs.pop();
82
+ }
83
+ else {
84
+ tail.count -= remainingToRemove;
85
+ totalUsed -= remainingToRemove;
86
+ remainingToRemove = 0;
87
+ }
88
+ }
89
+ return { logs, totalUsed: Math.max(0, totalUsed) };
90
+ }
91
+ _syncState(state, now) {
92
+ const { logs } = state;
93
+ let { totalUsed } = state;
94
+ const windowStart = now - this._windowMs;
95
+ while (!logs.isEmpty) {
96
+ const head = logs.peekHead();
97
+ if (head.ts > windowStart) {
98
+ break;
99
+ }
100
+ const removed = logs.shift();
101
+ if (removed) {
102
+ totalUsed -= removed.count;
103
+ }
104
+ }
105
+ return { logs, totalUsed: Math.max(0, totalUsed) };
106
+ }
107
+ _calculateAvailableAt(logs, totalUsed, cost, now) {
108
+ if (totalUsed + cost <= this._limit) {
109
+ return now;
110
+ }
111
+ const targetToFree = totalUsed + cost - this._limit;
112
+ let freed = 0;
113
+ let availableAt = now;
114
+ for (const entry of logs) {
115
+ freed += entry.count;
116
+ if (freed >= targetToFree) {
117
+ availableAt = entry.ts + this._windowMs;
118
+ break;
119
+ }
120
+ }
121
+ return Math.max(now, availableAt);
122
+ }
123
+ }
124
+ //# sourceMappingURL=sliding-window-log.policy.js.map
@@ -1,19 +1,18 @@
1
1
  import { type Deque } from '@stimulcross/ds-deque';
2
-
3
2
  /**
4
3
  * A log entry in the sliding window log rate limiter.
5
4
  */
6
5
  export interface SlidingWindowLogEntry {
7
- ts: number;
8
- count: number;
6
+ ts: number;
7
+ count: number;
9
8
  }
10
-
11
9
  /**
12
10
  * The state of the sliding window log rate limiter.
13
11
  *
14
12
  * When using a distributed state store, make sure it properly serializes and deserializes the state.
15
13
  */
16
14
  export interface SlidingWindowLogState {
17
- logs: Deque<SlidingWindowLogEntry>;
18
- totalUsed: number;
15
+ logs: Deque<SlidingWindowLogEntry>;
16
+ totalUsed: number;
19
17
  }
18
+ //# sourceMappingURL=sliding-window-log.state.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sliding-window-log.state.js.map