@zeitar/throttle 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/LICENSE.md +24 -0
  2. package/README.md +204 -0
  3. package/dist/CompoundLimiter.d.ts +33 -0
  4. package/dist/CompoundLimiter.d.ts.map +1 -0
  5. package/dist/CompoundLimiter.js +62 -0
  6. package/dist/CompoundLimiter.js.map +1 -0
  7. package/dist/CompoundRateLimiterFactory.d.ts +19 -0
  8. package/dist/CompoundRateLimiterFactory.d.ts.map +1 -0
  9. package/dist/CompoundRateLimiterFactory.js +29 -0
  10. package/dist/CompoundRateLimiterFactory.js.map +1 -0
  11. package/dist/LimiterInterface.d.ts +32 -0
  12. package/dist/LimiterInterface.d.ts.map +1 -0
  13. package/dist/LimiterInterface.js +3 -0
  14. package/dist/LimiterInterface.js.map +1 -0
  15. package/dist/LimiterStateInterface.d.ts +16 -0
  16. package/dist/LimiterStateInterface.d.ts.map +1 -0
  17. package/dist/LimiterStateInterface.js +3 -0
  18. package/dist/LimiterStateInterface.js.map +1 -0
  19. package/dist/RateLimit.d.ts +43 -0
  20. package/dist/RateLimit.d.ts.map +1 -0
  21. package/dist/RateLimit.js +68 -0
  22. package/dist/RateLimit.js.map +1 -0
  23. package/dist/RateLimiterFactory.d.ts +83 -0
  24. package/dist/RateLimiterFactory.d.ts.map +1 -0
  25. package/dist/RateLimiterFactory.js +115 -0
  26. package/dist/RateLimiterFactory.js.map +1 -0
  27. package/dist/RateLimiterFactoryInterface.d.ts +17 -0
  28. package/dist/RateLimiterFactoryInterface.d.ts.map +1 -0
  29. package/dist/RateLimiterFactoryInterface.js +3 -0
  30. package/dist/RateLimiterFactoryInterface.js.map +1 -0
  31. package/dist/Reservation.d.ts +29 -0
  32. package/dist/Reservation.d.ts.map +1 -0
  33. package/dist/Reservation.js +44 -0
  34. package/dist/Reservation.js.map +1 -0
  35. package/dist/__tests__/CompoundLimiter.test.d.ts +2 -0
  36. package/dist/__tests__/CompoundLimiter.test.d.ts.map +1 -0
  37. package/dist/__tests__/CompoundLimiter.test.js +231 -0
  38. package/dist/__tests__/CompoundLimiter.test.js.map +1 -0
  39. package/dist/__tests__/CompoundRateLimiterFactory.test.d.ts +2 -0
  40. package/dist/__tests__/CompoundRateLimiterFactory.test.d.ts.map +1 -0
  41. package/dist/__tests__/CompoundRateLimiterFactory.test.js +213 -0
  42. package/dist/__tests__/CompoundRateLimiterFactory.test.js.map +1 -0
  43. package/dist/__tests__/RateLimit.test.d.ts +2 -0
  44. package/dist/__tests__/RateLimit.test.d.ts.map +1 -0
  45. package/dist/__tests__/RateLimit.test.js +108 -0
  46. package/dist/__tests__/RateLimit.test.js.map +1 -0
  47. package/dist/__tests__/RateLimiterFactory.test.d.ts +2 -0
  48. package/dist/__tests__/RateLimiterFactory.test.d.ts.map +1 -0
  49. package/dist/__tests__/RateLimiterFactory.test.js +323 -0
  50. package/dist/__tests__/RateLimiterFactory.test.js.map +1 -0
  51. package/dist/__tests__/Reservation.test.d.ts +2 -0
  52. package/dist/__tests__/Reservation.test.d.ts.map +1 -0
  53. package/dist/__tests__/Reservation.test.js +110 -0
  54. package/dist/__tests__/Reservation.test.js.map +1 -0
  55. package/dist/errors/InvalidIntervalError.d.ts +10 -0
  56. package/dist/errors/InvalidIntervalError.d.ts.map +1 -0
  57. package/dist/errors/InvalidIntervalError.js +18 -0
  58. package/dist/errors/InvalidIntervalError.js.map +1 -0
  59. package/dist/errors/MaxWaitDurationExceededError.d.ts +15 -0
  60. package/dist/errors/MaxWaitDurationExceededError.d.ts.map +1 -0
  61. package/dist/errors/MaxWaitDurationExceededError.js +24 -0
  62. package/dist/errors/MaxWaitDurationExceededError.js.map +1 -0
  63. package/dist/errors/RateLimitExceededError.d.ts +27 -0
  64. package/dist/errors/RateLimitExceededError.d.ts.map +1 -0
  65. package/dist/errors/RateLimitExceededError.js +42 -0
  66. package/dist/errors/RateLimitExceededError.js.map +1 -0
  67. package/dist/errors/ReserveNotSupportedError.d.ts +10 -0
  68. package/dist/errors/ReserveNotSupportedError.d.ts.map +1 -0
  69. package/dist/errors/ReserveNotSupportedError.js +18 -0
  70. package/dist/errors/ReserveNotSupportedError.js.map +1 -0
  71. package/dist/index.d.ts +35 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +58 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/policy/FixedWindowLimiter.d.ts +36 -0
  76. package/dist/policy/FixedWindowLimiter.d.ts.map +1 -0
  77. package/dist/policy/FixedWindowLimiter.js +105 -0
  78. package/dist/policy/FixedWindowLimiter.js.map +1 -0
  79. package/dist/policy/NoLimiter.d.ts +23 -0
  80. package/dist/policy/NoLimiter.d.ts.map +1 -0
  81. package/dist/policy/NoLimiter.js +34 -0
  82. package/dist/policy/NoLimiter.js.map +1 -0
  83. package/dist/policy/Rate.d.ts +69 -0
  84. package/dist/policy/Rate.d.ts.map +1 -0
  85. package/dist/policy/Rate.js +121 -0
  86. package/dist/policy/Rate.js.map +1 -0
  87. package/dist/policy/SlidingWindow.d.ts +74 -0
  88. package/dist/policy/SlidingWindow.d.ts.map +1 -0
  89. package/dist/policy/SlidingWindow.js +130 -0
  90. package/dist/policy/SlidingWindow.js.map +1 -0
  91. package/dist/policy/SlidingWindowLimiter.d.ts +41 -0
  92. package/dist/policy/SlidingWindowLimiter.d.ts.map +1 -0
  93. package/dist/policy/SlidingWindowLimiter.js +127 -0
  94. package/dist/policy/SlidingWindowLimiter.js.map +1 -0
  95. package/dist/policy/TokenBucket.d.ts +63 -0
  96. package/dist/policy/TokenBucket.d.ts.map +1 -0
  97. package/dist/policy/TokenBucket.js +92 -0
  98. package/dist/policy/TokenBucket.js.map +1 -0
  99. package/dist/policy/TokenBucketLimiter.d.ts +38 -0
  100. package/dist/policy/TokenBucketLimiter.d.ts.map +1 -0
  101. package/dist/policy/TokenBucketLimiter.js +114 -0
  102. package/dist/policy/TokenBucketLimiter.js.map +1 -0
  103. package/dist/policy/Window.d.ts +58 -0
  104. package/dist/policy/Window.d.ts.map +1 -0
  105. package/dist/policy/Window.js +105 -0
  106. package/dist/policy/Window.js.map +1 -0
  107. package/dist/policy/__tests__/FixedWindowLimiter.test.d.ts +2 -0
  108. package/dist/policy/__tests__/FixedWindowLimiter.test.d.ts.map +1 -0
  109. package/dist/policy/__tests__/FixedWindowLimiter.test.js +180 -0
  110. package/dist/policy/__tests__/FixedWindowLimiter.test.js.map +1 -0
  111. package/dist/policy/__tests__/NoLimiter.test.d.ts +2 -0
  112. package/dist/policy/__tests__/NoLimiter.test.d.ts.map +1 -0
  113. package/dist/policy/__tests__/NoLimiter.test.js +40 -0
  114. package/dist/policy/__tests__/NoLimiter.test.js.map +1 -0
  115. package/dist/policy/__tests__/Rate.test.d.ts +2 -0
  116. package/dist/policy/__tests__/Rate.test.d.ts.map +1 -0
  117. package/dist/policy/__tests__/Rate.test.js +162 -0
  118. package/dist/policy/__tests__/Rate.test.js.map +1 -0
  119. package/dist/policy/__tests__/SlidingWindow.test.d.ts +2 -0
  120. package/dist/policy/__tests__/SlidingWindow.test.d.ts.map +1 -0
  121. package/dist/policy/__tests__/SlidingWindow.test.js +257 -0
  122. package/dist/policy/__tests__/SlidingWindow.test.js.map +1 -0
  123. package/dist/policy/__tests__/SlidingWindowLimiter.test.d.ts +2 -0
  124. package/dist/policy/__tests__/SlidingWindowLimiter.test.d.ts.map +1 -0
  125. package/dist/policy/__tests__/SlidingWindowLimiter.test.js +201 -0
  126. package/dist/policy/__tests__/SlidingWindowLimiter.test.js.map +1 -0
  127. package/dist/policy/__tests__/TokenBucket.test.d.ts +2 -0
  128. package/dist/policy/__tests__/TokenBucket.test.d.ts.map +1 -0
  129. package/dist/policy/__tests__/TokenBucket.test.js +171 -0
  130. package/dist/policy/__tests__/TokenBucket.test.js.map +1 -0
  131. package/dist/policy/__tests__/TokenBucketLimiter.test.d.ts +2 -0
  132. package/dist/policy/__tests__/TokenBucketLimiter.test.d.ts.map +1 -0
  133. package/dist/policy/__tests__/TokenBucketLimiter.test.js +175 -0
  134. package/dist/policy/__tests__/TokenBucketLimiter.test.js.map +1 -0
  135. package/dist/policy/__tests__/Window.test.d.ts +2 -0
  136. package/dist/policy/__tests__/Window.test.d.ts.map +1 -0
  137. package/dist/policy/__tests__/Window.test.js +193 -0
  138. package/dist/policy/__tests__/Window.test.js.map +1 -0
  139. package/dist/storage/InMemoryStorage.d.ts +34 -0
  140. package/dist/storage/InMemoryStorage.d.ts.map +1 -0
  141. package/dist/storage/InMemoryStorage.js +62 -0
  142. package/dist/storage/InMemoryStorage.js.map +1 -0
  143. package/dist/storage/LockInterface.d.ts +41 -0
  144. package/dist/storage/LockInterface.d.ts.map +1 -0
  145. package/dist/storage/LockInterface.js +19 -0
  146. package/dist/storage/LockInterface.js.map +1 -0
  147. package/dist/storage/StorageInterface.d.ts +29 -0
  148. package/dist/storage/StorageInterface.d.ts.map +1 -0
  149. package/dist/storage/StorageInterface.js +3 -0
  150. package/dist/storage/StorageInterface.js.map +1 -0
  151. package/dist/storage/__tests__/InMemoryStorage.test.d.ts +2 -0
  152. package/dist/storage/__tests__/InMemoryStorage.test.d.ts.map +1 -0
  153. package/dist/storage/__tests__/InMemoryStorage.test.js +154 -0
  154. package/dist/storage/__tests__/InMemoryStorage.test.js.map +1 -0
  155. package/dist/util/TimeUtil.d.ts +35 -0
  156. package/dist/util/TimeUtil.d.ts.map +1 -0
  157. package/dist/util/TimeUtil.js +87 -0
  158. package/dist/util/TimeUtil.js.map +1 -0
  159. package/dist/util/__tests__/TimeUtil.test.d.ts +2 -0
  160. package/dist/util/__tests__/TimeUtil.test.d.ts.map +1 -0
  161. package/dist/util/__tests__/TimeUtil.test.js +132 -0
  162. package/dist/util/__tests__/TimeUtil.test.js.map +1 -0
  163. package/package.json +59 -0
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SlidingWindow = void 0;
4
+ const TimeUtil_1 = require("../util/TimeUtil");
5
+ /**
6
+ * Serializable state for SlidingWindowLimiter.
7
+ *
8
+ * Tracks hit counts across current and previous windows to implement
9
+ * a sliding window algorithm.
10
+ */
11
+ class SlidingWindow {
12
+ constructor(id, intervalInSeconds, hitCount, hitCountForLastWindow, windowEndAt) {
13
+ this.id = id;
14
+ this.intervalInSeconds = intervalInSeconds;
15
+ this.hitCount = hitCount ?? 0;
16
+ this.hitCountForLastWindow = hitCountForLastWindow ?? 0;
17
+ this.windowEndAt = windowEndAt ?? TimeUtil_1.TimeUtil.now() + intervalInSeconds;
18
+ }
19
+ getId() {
20
+ return this.id;
21
+ }
22
+ /**
23
+ * Get the expiration time in seconds since epoch.
24
+ */
25
+ getExpirationTime() {
26
+ // Expires after current window ends plus one more interval
27
+ return Math.ceil(this.windowEndAt + this.intervalInSeconds);
28
+ }
29
+ /**
30
+ * Create a new window from a previous window (window transition).
31
+ */
32
+ static createFromPreviousWindow(id, intervalInSeconds, previousWindow, now) {
33
+ // The previous window's current hits become the "last window" hits
34
+ const newWindowEndAt = now + intervalInSeconds;
35
+ return new SlidingWindow(id, intervalInSeconds, 0, // New window starts with 0 hits
36
+ previousWindow.hitCount, // Previous current becomes last
37
+ newWindowEndAt);
38
+ }
39
+ /**
40
+ * Check if the window has expired.
41
+ */
42
+ isExpired(now = TimeUtil_1.TimeUtil.now()) {
43
+ return now >= this.windowEndAt + this.intervalInSeconds;
44
+ }
45
+ /**
46
+ * Add hits to the current window.
47
+ */
48
+ add(hits) {
49
+ this.hitCount += hits;
50
+ }
51
+ /**
52
+ * Get the sliding window hit count at a given time.
53
+ *
54
+ * Uses the formula:
55
+ * (previousWindowHits * (1 - percentIntoCurrentWindow)) + currentWindowHits
56
+ */
57
+ getHitCount(now = TimeUtil_1.TimeUtil.now()) {
58
+ // If we're past the current window end, we need a transition
59
+ if (now >= this.windowEndAt) {
60
+ return this.hitCount;
61
+ }
62
+ // Calculate how far we are into the current window
63
+ const windowStartAt = this.windowEndAt - this.intervalInSeconds;
64
+ const timeIntoWindow = now - windowStartAt;
65
+ const percentIntoWindow = timeIntoWindow / this.intervalInSeconds;
66
+ // Sliding window formula
67
+ const previousContribution = this.hitCountForLastWindow * (1 - percentIntoWindow);
68
+ const currentContribution = this.hitCount;
69
+ return Math.floor(previousContribution + currentContribution);
70
+ }
71
+ /**
72
+ * Get the current window's hit count (not sliding).
73
+ */
74
+ getCurrentWindowHitCount() {
75
+ return this.hitCount;
76
+ }
77
+ /**
78
+ * Get the previous window's hit count.
79
+ */
80
+ getPreviousWindowHitCount() {
81
+ return this.hitCountForLastWindow;
82
+ }
83
+ /**
84
+ * Get when the current window ends.
85
+ */
86
+ getWindowEndAt() {
87
+ return this.windowEndAt;
88
+ }
89
+ /**
90
+ * Get the interval in seconds.
91
+ */
92
+ getInterval() {
93
+ return this.intervalInSeconds;
94
+ }
95
+ /**
96
+ * Calculate time needed for N tokens to become available.
97
+ */
98
+ calculateTimeForTokens(maxSize, tokens, now = TimeUtil_1.TimeUtil.now()) {
99
+ const currentCount = this.getHitCount(now);
100
+ const available = Math.max(0, maxSize - currentCount);
101
+ if (available >= tokens) {
102
+ return 0; // Already available
103
+ }
104
+ // Need to wait - in sliding window, we need to wait until the window slides
105
+ // enough that we have space
106
+ const windowStartAt = this.windowEndAt - this.intervalInSeconds;
107
+ // Simplified: wait for next window
108
+ return this.windowEndAt - now;
109
+ }
110
+ /**
111
+ * Convert to JSON for serialization.
112
+ */
113
+ toJSON() {
114
+ return {
115
+ id: this.id,
116
+ hitCount: this.hitCount,
117
+ hitCountForLastWindow: this.hitCountForLastWindow,
118
+ windowEndAt: this.windowEndAt,
119
+ intervalInSeconds: this.intervalInSeconds,
120
+ };
121
+ }
122
+ /**
123
+ * Create from JSON.
124
+ */
125
+ static fromJSON(data) {
126
+ return new SlidingWindow(data.id, data.intervalInSeconds, data.hitCount, data.hitCountForLastWindow, data.windowEndAt);
127
+ }
128
+ }
129
+ exports.SlidingWindow = SlidingWindow;
130
+ //# sourceMappingURL=SlidingWindow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlidingWindow.js","sourceRoot":"","sources":["../../src/policy/SlidingWindow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { LimiterInterface } from '../LimiterInterface';
2
+ import type { StorageInterface } from '../storage/StorageInterface';
3
+ import type { LockInterface } from '../storage/LockInterface';
4
+ import { RateLimit } from '../RateLimit';
5
+ import { Reservation } from '../Reservation';
6
+ /**
7
+ * Sliding window rate limiter implementation.
8
+ *
9
+ * Uses a weighted combination of the previous and current windows to smooth
10
+ * out burst behavior at window boundaries. More sophisticated than fixed window
11
+ * but slightly more expensive computationally.
12
+ */
13
+ export declare class SlidingWindowLimiter implements LimiterInterface {
14
+ private readonly id;
15
+ private readonly limit;
16
+ private readonly intervalInSeconds;
17
+ private readonly storage;
18
+ private readonly lock;
19
+ constructor(id: string, limit: number, intervalInSeconds: number, storage: StorageInterface, lock?: LockInterface);
20
+ /**
21
+ * Reserve tokens with optional maximum wait time.
22
+ */
23
+ reserve(tokens?: number, maxTime?: number | null): Promise<Reservation>;
24
+ /**
25
+ * Try to consume tokens immediately.
26
+ */
27
+ consume(tokens?: number): Promise<RateLimit>;
28
+ /**
29
+ * Reset the rate limiter state.
30
+ */
31
+ reset(): Promise<void>;
32
+ /**
33
+ * Get existing window or create a new one.
34
+ */
35
+ private getOrCreateWindow;
36
+ /**
37
+ * Transition to a new window if the current one has ended.
38
+ */
39
+ private maybeTransitionWindow;
40
+ }
41
+ //# sourceMappingURL=SlidingWindowLimiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlidingWindowLimiter.d.ts","sourceRoot":"","sources":["../../src/policy/SlidingWindowLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAM7C;;;;;;GAMG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IAC3D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;gBAGnC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,iBAAiB,EAAE,MAAM,EACzB,OAAO,EAAE,gBAAgB,EACzB,IAAI,CAAC,EAAE,aAAa;IAStB;;OAEG;IACG,OAAO,CAAC,MAAM,GAAE,MAAU,EAAE,OAAO,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAuDtF;;OAEG;IACG,OAAO,CAAC,MAAM,GAAE,MAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAgCrD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;OAEG;YACW,iBAAiB;IAW/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAsB9B"}
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SlidingWindowLimiter = void 0;
4
+ const RateLimit_1 = require("../RateLimit");
5
+ const Reservation_1 = require("../Reservation");
6
+ const MaxWaitDurationExceededError_1 = require("../errors/MaxWaitDurationExceededError");
7
+ const SlidingWindow_1 = require("./SlidingWindow");
8
+ const TimeUtil_1 = require("../util/TimeUtil");
9
+ const LockInterface_1 = require("../storage/LockInterface");
10
+ /**
11
+ * Sliding window rate limiter implementation.
12
+ *
13
+ * Uses a weighted combination of the previous and current windows to smooth
14
+ * out burst behavior at window boundaries. More sophisticated than fixed window
15
+ * but slightly more expensive computationally.
16
+ */
17
+ class SlidingWindowLimiter {
18
+ constructor(id, limit, intervalInSeconds, storage, lock) {
19
+ this.id = id;
20
+ this.limit = limit;
21
+ this.intervalInSeconds = intervalInSeconds;
22
+ this.storage = storage;
23
+ this.lock = lock ?? new LockInterface_1.NoLock();
24
+ }
25
+ /**
26
+ * Reserve tokens with optional maximum wait time.
27
+ */
28
+ async reserve(tokens = 1, maxTime = null) {
29
+ return this.lock.withLock(this.id, async () => {
30
+ const now = TimeUtil_1.TimeUtil.now();
31
+ const window = await this.getOrCreateWindow(now);
32
+ // Transition to new window if needed
33
+ const updatedWindow = this.maybeTransitionWindow(window, now);
34
+ const currentHitCount = updatedWindow.getHitCount(now);
35
+ const availableTokens = Math.max(0, this.limit - currentHitCount);
36
+ if (tokens > this.limit) {
37
+ throw new Error(`Cannot reserve ${tokens} tokens, limit is ${this.limit}`);
38
+ }
39
+ let timeToAct;
40
+ let waitTime;
41
+ if (availableTokens >= tokens) {
42
+ // Tokens available now
43
+ timeToAct = now;
44
+ waitTime = 0;
45
+ updatedWindow.add(tokens);
46
+ }
47
+ else {
48
+ // Need to wait for window to slide
49
+ waitTime = updatedWindow.calculateTimeForTokens(this.limit, tokens, now);
50
+ timeToAct = now + waitTime;
51
+ }
52
+ // Check max wait time
53
+ if (maxTime !== null && waitTime > maxTime) {
54
+ const retryAfter = new Date((now + waitTime) * 1000);
55
+ const rateLimit = new RateLimit_1.RateLimit(availableTokens, retryAfter, false, this.limit);
56
+ throw new MaxWaitDurationExceededError_1.MaxWaitDurationExceededError(`Cannot reserve ${tokens} tokens within ${maxTime} seconds`, rateLimit);
57
+ }
58
+ await this.storage.save(updatedWindow);
59
+ const retryAfter = new Date(timeToAct * 1000);
60
+ const rateLimit = new RateLimit_1.RateLimit(Math.max(0, availableTokens - tokens), retryAfter, true, this.limit);
61
+ return new Reservation_1.Reservation(timeToAct * 1000, rateLimit); // Convert to ms
62
+ });
63
+ }
64
+ /**
65
+ * Try to consume tokens immediately.
66
+ */
67
+ async consume(tokens = 1) {
68
+ return this.lock.withLock(this.id, async () => {
69
+ const now = TimeUtil_1.TimeUtil.now();
70
+ const window = await this.getOrCreateWindow(now);
71
+ // Transition to new window if needed
72
+ const updatedWindow = this.maybeTransitionWindow(window, now);
73
+ const currentHitCount = updatedWindow.getHitCount(now);
74
+ const availableTokens = Math.max(0, this.limit - currentHitCount);
75
+ if (availableTokens >= tokens) {
76
+ // Success
77
+ updatedWindow.add(tokens);
78
+ await this.storage.save(updatedWindow);
79
+ return new RateLimit_1.RateLimit(availableTokens - tokens, new Date(now * 1000), true, this.limit);
80
+ }
81
+ else {
82
+ // Rate limit exceeded
83
+ const waitTime = updatedWindow.calculateTimeForTokens(this.limit, tokens, now);
84
+ const retryAfter = new Date((now + waitTime) * 1000);
85
+ return new RateLimit_1.RateLimit(availableTokens, retryAfter, false, this.limit);
86
+ }
87
+ });
88
+ }
89
+ /**
90
+ * Reset the rate limiter state.
91
+ */
92
+ async reset() {
93
+ await this.lock.withLock(this.id, async () => {
94
+ await this.storage.delete(this.id);
95
+ });
96
+ }
97
+ /**
98
+ * Get existing window or create a new one.
99
+ */
100
+ async getOrCreateWindow(now) {
101
+ const state = await this.storage.fetch(this.id);
102
+ if (state instanceof SlidingWindow_1.SlidingWindow) {
103
+ return state;
104
+ }
105
+ // Create new window
106
+ return new SlidingWindow_1.SlidingWindow(this.id, this.intervalInSeconds);
107
+ }
108
+ /**
109
+ * Transition to a new window if the current one has ended.
110
+ */
111
+ maybeTransitionWindow(window, now) {
112
+ const windowEndAt = window.getWindowEndAt();
113
+ if (now < windowEndAt) {
114
+ // Still in current window
115
+ return window;
116
+ }
117
+ // Check if we're completely past the window (expired)
118
+ if (window.isExpired(now)) {
119
+ // Create fresh window
120
+ return new SlidingWindow_1.SlidingWindow(this.id, this.intervalInSeconds);
121
+ }
122
+ // Transition to new window, carrying over current hits as "last window"
123
+ return SlidingWindow_1.SlidingWindow.createFromPreviousWindow(this.id, this.intervalInSeconds, window, now);
124
+ }
125
+ }
126
+ exports.SlidingWindowLimiter = SlidingWindowLimiter;
127
+ //# sourceMappingURL=SlidingWindowLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SlidingWindowLimiter.js","sourceRoot":"","sources":["../../src/policy/SlidingWindowLimiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { LimiterStateInterface } from '../LimiterStateInterface';
2
+ import { Rate } from './Rate';
3
+ /**
4
+ * Serializable state for TokenBucketLimiter.
5
+ *
6
+ * Tracks the number of available tokens and when they were last updated.
7
+ */
8
+ export declare class TokenBucket implements LimiterStateInterface {
9
+ private id;
10
+ private tokens;
11
+ private readonly burstSize;
12
+ private timer;
13
+ private readonly rate;
14
+ constructor(id: string, burstSize: number, rate: Rate, tokens?: number, timer?: number);
15
+ getId(): string;
16
+ /**
17
+ * Get the expiration time in seconds since epoch.
18
+ * Token bucket expires after enough time has passed to refill completely.
19
+ */
20
+ getExpirationTime(): number;
21
+ /**
22
+ * Get the available tokens at a given time, accounting for refills.
23
+ */
24
+ getAvailableTokens(now?: number): number;
25
+ /**
26
+ * Set the number of tokens.
27
+ */
28
+ setTokens(tokens: number): void;
29
+ /**
30
+ * Get the timer (last update time).
31
+ */
32
+ getTimer(): number;
33
+ /**
34
+ * Set the timer (last update time).
35
+ */
36
+ setTimer(timer: number): void;
37
+ /**
38
+ * Get the burst size (maximum tokens).
39
+ */
40
+ getBurstSize(): number;
41
+ /**
42
+ * Get the refill rate.
43
+ */
44
+ getRate(): Rate;
45
+ /**
46
+ * Convert to JSON for serialization.
47
+ */
48
+ toJSON(): Record<string, unknown>;
49
+ /**
50
+ * Create from JSON.
51
+ */
52
+ static fromJSON(data: {
53
+ id: string;
54
+ tokens: number;
55
+ burstSize: number;
56
+ timer: number;
57
+ rate: {
58
+ interval: number;
59
+ amount: number;
60
+ };
61
+ }): TokenBucket;
62
+ }
63
+ //# sourceMappingURL=TokenBucket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenBucket.d.ts","sourceRoot":"","sources":["../../src/policy/TokenBucket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B;;;;GAIG;AACH,qBAAa,WAAY,YAAW,qBAAqB;IACvD,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;gBAG1B,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,IAAI,EACV,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM;IAShB,KAAK,IAAI,MAAM;IAIf;;;OAGG;IACH,iBAAiB,IAAI,MAAM;IAK3B;;OAEG;IACH,kBAAkB,CAAC,GAAG,GAAE,MAAuB,GAAG,MAAM;IAMxD;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,QAAQ,IAAI,MAAM;IAIlB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,OAAO,IAAI,IAAI;IAIf;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAajC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QACpB,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAC5C,GAAG,WAAW;CAUhB"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenBucket = void 0;
4
+ const Rate_1 = require("./Rate");
5
+ const TimeUtil_1 = require("../util/TimeUtil");
6
+ /**
7
+ * Serializable state for TokenBucketLimiter.
8
+ *
9
+ * Tracks the number of available tokens and when they were last updated.
10
+ */
11
+ class TokenBucket {
12
+ constructor(id, burstSize, rate, tokens, timer) {
13
+ this.id = id;
14
+ this.burstSize = burstSize;
15
+ this.rate = rate;
16
+ this.tokens = tokens ?? burstSize;
17
+ this.timer = timer ?? TimeUtil_1.TimeUtil.now();
18
+ }
19
+ getId() {
20
+ return this.id;
21
+ }
22
+ /**
23
+ * Get the expiration time in seconds since epoch.
24
+ * Token bucket expires after enough time has passed to refill completely.
25
+ */
26
+ getExpirationTime() {
27
+ const refillTime = this.rate.calculateTimeForTokens(this.burstSize);
28
+ return Math.ceil(this.timer + refillTime);
29
+ }
30
+ /**
31
+ * Get the available tokens at a given time, accounting for refills.
32
+ */
33
+ getAvailableTokens(now = TimeUtil_1.TimeUtil.now()) {
34
+ const elapsed = now - this.timer;
35
+ const newTokens = this.rate.calculateNewTokensDuringInterval(elapsed);
36
+ return Math.min(this.burstSize, this.tokens + newTokens);
37
+ }
38
+ /**
39
+ * Set the number of tokens.
40
+ */
41
+ setTokens(tokens) {
42
+ this.tokens = tokens;
43
+ }
44
+ /**
45
+ * Get the timer (last update time).
46
+ */
47
+ getTimer() {
48
+ return this.timer;
49
+ }
50
+ /**
51
+ * Set the timer (last update time).
52
+ */
53
+ setTimer(timer) {
54
+ this.timer = timer;
55
+ }
56
+ /**
57
+ * Get the burst size (maximum tokens).
58
+ */
59
+ getBurstSize() {
60
+ return this.burstSize;
61
+ }
62
+ /**
63
+ * Get the refill rate.
64
+ */
65
+ getRate() {
66
+ return this.rate;
67
+ }
68
+ /**
69
+ * Convert to JSON for serialization.
70
+ */
71
+ toJSON() {
72
+ return {
73
+ id: this.id,
74
+ tokens: this.tokens,
75
+ burstSize: this.burstSize,
76
+ timer: this.timer,
77
+ rate: {
78
+ interval: this.rate.getInterval(),
79
+ amount: this.rate.getAmount(),
80
+ },
81
+ };
82
+ }
83
+ /**
84
+ * Create from JSON.
85
+ */
86
+ static fromJSON(data) {
87
+ const rate = new Rate_1.Rate(data.rate.interval, data.rate.amount);
88
+ return new TokenBucket(data.id, data.burstSize, rate, data.tokens, data.timer);
89
+ }
90
+ }
91
+ exports.TokenBucket = TokenBucket;
92
+ //# sourceMappingURL=TokenBucket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenBucket.js","sourceRoot":"","sources":["../../src/policy/TokenBucket.ts"],"names":[],"mappings":";;;AACA,iCAA8B;AAC9B,+CAA4C;AAE5C;;;;GAIG;AACH,MAAa,WAAW;IAOtB,YACE,EAAU,EACV,SAAiB,EACjB,IAAU,EACV,MAAe,EACf,KAAc;QAEd,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,SAAS,CAAC;QAClC,IAAI,CAAC,KAAK,GAAG,KAAK,IAAI,mBAAQ,CAAC,GAAG,EAAE,CAAC;IACvC,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED;;;OAGG;IACH,iBAAiB;QACf,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,kBAAkB,CAAC,MAAc,mBAAQ,CAAC,GAAG,EAAE;QAC7C,MAAM,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,OAAO,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3D,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,KAAa;QACpB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO;YACL,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE;gBACJ,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;gBACjC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;aAC9B;SACF,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,IAMf;QACC,MAAM,IAAI,GAAG,IAAI,WAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5D,OAAO,IAAI,WAAW,CACpB,IAAI,CAAC,EAAE,EACP,IAAI,CAAC,SAAS,EACd,IAAI,EACJ,IAAI,CAAC,MAAM,EACX,IAAI,CAAC,KAAK,CACX,CAAC;IACJ,CAAC;CACF;AAjHD,kCAiHC"}
@@ -0,0 +1,38 @@
1
+ import type { LimiterInterface } from '../LimiterInterface';
2
+ import type { StorageInterface } from '../storage/StorageInterface';
3
+ import type { LockInterface } from '../storage/LockInterface';
4
+ import { RateLimit } from '../RateLimit';
5
+ import { Reservation } from '../Reservation';
6
+ import { Rate } from './Rate';
7
+ /**
8
+ * Token bucket rate limiter implementation.
9
+ *
10
+ * The token bucket algorithm allows bursts up to the bucket size while
11
+ * maintaining a steady refill rate. This is the most flexible algorithm
12
+ * and supports both reserve() and consume() patterns.
13
+ */
14
+ export declare class TokenBucketLimiter implements LimiterInterface {
15
+ private readonly id;
16
+ private readonly burstSize;
17
+ private readonly rate;
18
+ private readonly storage;
19
+ private readonly lock;
20
+ constructor(id: string, burstSize: number, rate: Rate, storage: StorageInterface, lock?: LockInterface);
21
+ /**
22
+ * Reserve tokens with optional maximum wait time.
23
+ */
24
+ reserve(tokens?: number, maxTime?: number | null): Promise<Reservation>;
25
+ /**
26
+ * Try to consume tokens immediately.
27
+ */
28
+ consume(tokens?: number): Promise<RateLimit>;
29
+ /**
30
+ * Reset the rate limiter state.
31
+ */
32
+ reset(): Promise<void>;
33
+ /**
34
+ * Get existing bucket or create a new one.
35
+ */
36
+ private getOrCreateBucket;
37
+ }
38
+ //# sourceMappingURL=TokenBucketLimiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenBucketLimiter.d.ts","sourceRoot":"","sources":["../../src/policy/TokenBucketLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAI9B;;;;;;GAMG;AACH,qBAAa,kBAAmB,YAAW,gBAAgB;IACzD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;gBAGnC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,gBAAgB,EACzB,IAAI,CAAC,EAAE,aAAa;IAStB;;OAEG;IACG,OAAO,CAAC,MAAM,GAAE,MAAU,EAAE,OAAO,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IA0DtF;;OAEG;IACG,OAAO,CAAC,MAAM,GAAE,MAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAiCrD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;OAEG;YACW,iBAAiB;CAUhC"}
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TokenBucketLimiter = void 0;
4
+ const RateLimit_1 = require("../RateLimit");
5
+ const Reservation_1 = require("../Reservation");
6
+ const MaxWaitDurationExceededError_1 = require("../errors/MaxWaitDurationExceededError");
7
+ const TokenBucket_1 = require("./TokenBucket");
8
+ const TimeUtil_1 = require("../util/TimeUtil");
9
+ const LockInterface_1 = require("../storage/LockInterface");
10
+ /**
11
+ * Token bucket rate limiter implementation.
12
+ *
13
+ * The token bucket algorithm allows bursts up to the bucket size while
14
+ * maintaining a steady refill rate. This is the most flexible algorithm
15
+ * and supports both reserve() and consume() patterns.
16
+ */
17
+ class TokenBucketLimiter {
18
+ constructor(id, burstSize, rate, storage, lock) {
19
+ this.id = id;
20
+ this.burstSize = burstSize;
21
+ this.rate = rate;
22
+ this.storage = storage;
23
+ this.lock = lock ?? new LockInterface_1.NoLock();
24
+ }
25
+ /**
26
+ * Reserve tokens with optional maximum wait time.
27
+ */
28
+ async reserve(tokens = 1, maxTime = null) {
29
+ return this.lock.withLock(this.id, async () => {
30
+ const now = TimeUtil_1.TimeUtil.now();
31
+ const bucket = await this.getOrCreateBucket(now);
32
+ const availableTokens = bucket.getAvailableTokens(now);
33
+ // Update bucket state to account for elapsed time
34
+ bucket.setTokens(availableTokens);
35
+ bucket.setTimer(now);
36
+ if (tokens > this.burstSize) {
37
+ throw new Error(`Cannot reserve ${tokens} tokens, burst size is ${this.burstSize}`);
38
+ }
39
+ let timeToAct;
40
+ let waitTime;
41
+ if (availableTokens >= tokens) {
42
+ // Tokens available now
43
+ timeToAct = now;
44
+ waitTime = 0;
45
+ bucket.setTokens(availableTokens - tokens);
46
+ }
47
+ else {
48
+ // Need to wait for tokens
49
+ const tokensNeeded = tokens - availableTokens;
50
+ waitTime = this.rate.calculateTimeForTokens(tokensNeeded);
51
+ timeToAct = now + waitTime;
52
+ bucket.setTokens(0); // All current tokens consumed
53
+ bucket.setTimer(timeToAct); // Next refill starts after timeToAct
54
+ }
55
+ // Check max wait time
56
+ if (maxTime !== null && waitTime > maxTime) {
57
+ const retryAfter = new Date((now + waitTime) * 1000);
58
+ const rateLimit = new RateLimit_1.RateLimit(availableTokens, retryAfter, false, this.burstSize);
59
+ throw new MaxWaitDurationExceededError_1.MaxWaitDurationExceededError(`Cannot reserve ${tokens} tokens within ${maxTime} seconds`, rateLimit);
60
+ }
61
+ await this.storage.save(bucket);
62
+ const retryAfter = new Date(timeToAct * 1000);
63
+ const rateLimit = new RateLimit_1.RateLimit(Math.max(0, availableTokens - tokens), retryAfter, true, this.burstSize);
64
+ return new Reservation_1.Reservation(timeToAct * 1000, rateLimit); // Convert to ms
65
+ });
66
+ }
67
+ /**
68
+ * Try to consume tokens immediately.
69
+ */
70
+ async consume(tokens = 1) {
71
+ return this.lock.withLock(this.id, async () => {
72
+ const now = TimeUtil_1.TimeUtil.now();
73
+ const bucket = await this.getOrCreateBucket(now);
74
+ const availableTokens = bucket.getAvailableTokens(now);
75
+ // Update bucket state
76
+ bucket.setTokens(availableTokens);
77
+ bucket.setTimer(now);
78
+ if (availableTokens >= tokens) {
79
+ // Success
80
+ bucket.setTokens(availableTokens - tokens);
81
+ await this.storage.save(bucket);
82
+ return new RateLimit_1.RateLimit(availableTokens - tokens, new Date(now * 1000), true, this.burstSize);
83
+ }
84
+ else {
85
+ // Rate limit exceeded
86
+ const tokensNeeded = tokens - availableTokens;
87
+ const waitTime = this.rate.calculateTimeForTokens(tokensNeeded);
88
+ const retryAfter = new Date((now + waitTime) * 1000);
89
+ return new RateLimit_1.RateLimit(availableTokens, retryAfter, false, this.burstSize);
90
+ }
91
+ });
92
+ }
93
+ /**
94
+ * Reset the rate limiter state.
95
+ */
96
+ async reset() {
97
+ await this.lock.withLock(this.id, async () => {
98
+ await this.storage.delete(this.id);
99
+ });
100
+ }
101
+ /**
102
+ * Get existing bucket or create a new one.
103
+ */
104
+ async getOrCreateBucket(now) {
105
+ const state = await this.storage.fetch(this.id);
106
+ if (state instanceof TokenBucket_1.TokenBucket) {
107
+ return state;
108
+ }
109
+ // Create new bucket
110
+ return new TokenBucket_1.TokenBucket(this.id, this.burstSize, this.rate, undefined, now);
111
+ }
112
+ }
113
+ exports.TokenBucketLimiter = TokenBucketLimiter;
114
+ //# sourceMappingURL=TokenBucketLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TokenBucketLimiter.js","sourceRoot":"","sources":["../../src/policy/TokenBucketLimiter.ts"],"names":[],"mappings":"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"}