@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.
- package/LICENSE.md +24 -0
- package/README.md +204 -0
- package/dist/CompoundLimiter.d.ts +33 -0
- package/dist/CompoundLimiter.d.ts.map +1 -0
- package/dist/CompoundLimiter.js +62 -0
- package/dist/CompoundLimiter.js.map +1 -0
- package/dist/CompoundRateLimiterFactory.d.ts +19 -0
- package/dist/CompoundRateLimiterFactory.d.ts.map +1 -0
- package/dist/CompoundRateLimiterFactory.js +29 -0
- package/dist/CompoundRateLimiterFactory.js.map +1 -0
- package/dist/LimiterInterface.d.ts +32 -0
- package/dist/LimiterInterface.d.ts.map +1 -0
- package/dist/LimiterInterface.js +3 -0
- package/dist/LimiterInterface.js.map +1 -0
- package/dist/LimiterStateInterface.d.ts +16 -0
- package/dist/LimiterStateInterface.d.ts.map +1 -0
- package/dist/LimiterStateInterface.js +3 -0
- package/dist/LimiterStateInterface.js.map +1 -0
- package/dist/RateLimit.d.ts +43 -0
- package/dist/RateLimit.d.ts.map +1 -0
- package/dist/RateLimit.js +68 -0
- package/dist/RateLimit.js.map +1 -0
- package/dist/RateLimiterFactory.d.ts +83 -0
- package/dist/RateLimiterFactory.d.ts.map +1 -0
- package/dist/RateLimiterFactory.js +115 -0
- package/dist/RateLimiterFactory.js.map +1 -0
- package/dist/RateLimiterFactoryInterface.d.ts +17 -0
- package/dist/RateLimiterFactoryInterface.d.ts.map +1 -0
- package/dist/RateLimiterFactoryInterface.js +3 -0
- package/dist/RateLimiterFactoryInterface.js.map +1 -0
- package/dist/Reservation.d.ts +29 -0
- package/dist/Reservation.d.ts.map +1 -0
- package/dist/Reservation.js +44 -0
- package/dist/Reservation.js.map +1 -0
- package/dist/__tests__/CompoundLimiter.test.d.ts +2 -0
- package/dist/__tests__/CompoundLimiter.test.d.ts.map +1 -0
- package/dist/__tests__/CompoundLimiter.test.js +231 -0
- package/dist/__tests__/CompoundLimiter.test.js.map +1 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.d.ts +2 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.d.ts.map +1 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.js +213 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.js.map +1 -0
- package/dist/__tests__/RateLimit.test.d.ts +2 -0
- package/dist/__tests__/RateLimit.test.d.ts.map +1 -0
- package/dist/__tests__/RateLimit.test.js +108 -0
- package/dist/__tests__/RateLimit.test.js.map +1 -0
- package/dist/__tests__/RateLimiterFactory.test.d.ts +2 -0
- package/dist/__tests__/RateLimiterFactory.test.d.ts.map +1 -0
- package/dist/__tests__/RateLimiterFactory.test.js +323 -0
- package/dist/__tests__/RateLimiterFactory.test.js.map +1 -0
- package/dist/__tests__/Reservation.test.d.ts +2 -0
- package/dist/__tests__/Reservation.test.d.ts.map +1 -0
- package/dist/__tests__/Reservation.test.js +110 -0
- package/dist/__tests__/Reservation.test.js.map +1 -0
- package/dist/errors/InvalidIntervalError.d.ts +10 -0
- package/dist/errors/InvalidIntervalError.d.ts.map +1 -0
- package/dist/errors/InvalidIntervalError.js +18 -0
- package/dist/errors/InvalidIntervalError.js.map +1 -0
- package/dist/errors/MaxWaitDurationExceededError.d.ts +15 -0
- package/dist/errors/MaxWaitDurationExceededError.d.ts.map +1 -0
- package/dist/errors/MaxWaitDurationExceededError.js +24 -0
- package/dist/errors/MaxWaitDurationExceededError.js.map +1 -0
- package/dist/errors/RateLimitExceededError.d.ts +27 -0
- package/dist/errors/RateLimitExceededError.d.ts.map +1 -0
- package/dist/errors/RateLimitExceededError.js +42 -0
- package/dist/errors/RateLimitExceededError.js.map +1 -0
- package/dist/errors/ReserveNotSupportedError.d.ts +10 -0
- package/dist/errors/ReserveNotSupportedError.d.ts.map +1 -0
- package/dist/errors/ReserveNotSupportedError.js +18 -0
- package/dist/errors/ReserveNotSupportedError.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/policy/FixedWindowLimiter.d.ts +36 -0
- package/dist/policy/FixedWindowLimiter.d.ts.map +1 -0
- package/dist/policy/FixedWindowLimiter.js +105 -0
- package/dist/policy/FixedWindowLimiter.js.map +1 -0
- package/dist/policy/NoLimiter.d.ts +23 -0
- package/dist/policy/NoLimiter.d.ts.map +1 -0
- package/dist/policy/NoLimiter.js +34 -0
- package/dist/policy/NoLimiter.js.map +1 -0
- package/dist/policy/Rate.d.ts +69 -0
- package/dist/policy/Rate.d.ts.map +1 -0
- package/dist/policy/Rate.js +121 -0
- package/dist/policy/Rate.js.map +1 -0
- package/dist/policy/SlidingWindow.d.ts +74 -0
- package/dist/policy/SlidingWindow.d.ts.map +1 -0
- package/dist/policy/SlidingWindow.js +130 -0
- package/dist/policy/SlidingWindow.js.map +1 -0
- package/dist/policy/SlidingWindowLimiter.d.ts +41 -0
- package/dist/policy/SlidingWindowLimiter.d.ts.map +1 -0
- package/dist/policy/SlidingWindowLimiter.js +127 -0
- package/dist/policy/SlidingWindowLimiter.js.map +1 -0
- package/dist/policy/TokenBucket.d.ts +63 -0
- package/dist/policy/TokenBucket.d.ts.map +1 -0
- package/dist/policy/TokenBucket.js +92 -0
- package/dist/policy/TokenBucket.js.map +1 -0
- package/dist/policy/TokenBucketLimiter.d.ts +38 -0
- package/dist/policy/TokenBucketLimiter.d.ts.map +1 -0
- package/dist/policy/TokenBucketLimiter.js +114 -0
- package/dist/policy/TokenBucketLimiter.js.map +1 -0
- package/dist/policy/Window.d.ts +58 -0
- package/dist/policy/Window.d.ts.map +1 -0
- package/dist/policy/Window.js +105 -0
- package/dist/policy/Window.js.map +1 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.js +180 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/NoLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/NoLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/NoLimiter.test.js +40 -0
- package/dist/policy/__tests__/NoLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/Rate.test.d.ts +2 -0
- package/dist/policy/__tests__/Rate.test.d.ts.map +1 -0
- package/dist/policy/__tests__/Rate.test.js +162 -0
- package/dist/policy/__tests__/Rate.test.js.map +1 -0
- package/dist/policy/__tests__/SlidingWindow.test.d.ts +2 -0
- package/dist/policy/__tests__/SlidingWindow.test.d.ts.map +1 -0
- package/dist/policy/__tests__/SlidingWindow.test.js +257 -0
- package/dist/policy/__tests__/SlidingWindow.test.js.map +1 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.js +201 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/TokenBucket.test.d.ts +2 -0
- package/dist/policy/__tests__/TokenBucket.test.d.ts.map +1 -0
- package/dist/policy/__tests__/TokenBucket.test.js +171 -0
- package/dist/policy/__tests__/TokenBucket.test.js.map +1 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.js +175 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/Window.test.d.ts +2 -0
- package/dist/policy/__tests__/Window.test.d.ts.map +1 -0
- package/dist/policy/__tests__/Window.test.js +193 -0
- package/dist/policy/__tests__/Window.test.js.map +1 -0
- package/dist/storage/InMemoryStorage.d.ts +34 -0
- package/dist/storage/InMemoryStorage.d.ts.map +1 -0
- package/dist/storage/InMemoryStorage.js +62 -0
- package/dist/storage/InMemoryStorage.js.map +1 -0
- package/dist/storage/LockInterface.d.ts +41 -0
- package/dist/storage/LockInterface.d.ts.map +1 -0
- package/dist/storage/LockInterface.js +19 -0
- package/dist/storage/LockInterface.js.map +1 -0
- package/dist/storage/StorageInterface.d.ts +29 -0
- package/dist/storage/StorageInterface.d.ts.map +1 -0
- package/dist/storage/StorageInterface.js +3 -0
- package/dist/storage/StorageInterface.js.map +1 -0
- package/dist/storage/__tests__/InMemoryStorage.test.d.ts +2 -0
- package/dist/storage/__tests__/InMemoryStorage.test.d.ts.map +1 -0
- package/dist/storage/__tests__/InMemoryStorage.test.js +154 -0
- package/dist/storage/__tests__/InMemoryStorage.test.js.map +1 -0
- package/dist/util/TimeUtil.d.ts +35 -0
- package/dist/util/TimeUtil.d.ts.map +1 -0
- package/dist/util/TimeUtil.js +87 -0
- package/dist/util/TimeUtil.js.map +1 -0
- package/dist/util/__tests__/TimeUtil.test.d.ts +2 -0
- package/dist/util/__tests__/TimeUtil.test.d.ts.map +1 -0
- package/dist/util/__tests__/TimeUtil.test.js +132 -0
- package/dist/util/__tests__/TimeUtil.test.js.map +1 -0
- 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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"}
|