sliding-window-limiter 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/rate_limiter.js +26 -2
- package/lib/window.js +4 -2
- package/package.json +1 -1
- package/test/rate_limiter.spec.js +40 -0
package/lib/rate_limiter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const {isNumber} = require('lodash/fp');
|
|
1
|
+
const {isNumber, isObject, isString} = require('lodash/fp');
|
|
2
|
+
const {DateTime} = require('luxon');
|
|
2
3
|
const {Window} = require('./window');
|
|
3
4
|
const {NullStore} = require('./null_store');
|
|
4
5
|
|
|
@@ -42,7 +43,7 @@ class RateLimiter {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
// should update the buckets according to configuration
|
|
46
|
+
// should update the buckets according to configuration.
|
|
46
47
|
// returns true if update successful
|
|
47
48
|
async update(value, timestamp) {
|
|
48
49
|
if (!isNumber(value) || value <= 0)
|
|
@@ -62,6 +63,29 @@ class RateLimiter {
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
// should update the buckets according to configuration.
|
|
67
|
+
// only updates the time and recalculates window's buckets
|
|
68
|
+
async updateTime(timestamp) {
|
|
69
|
+
let _timestamp = timestamp;
|
|
70
|
+
if (timestamp instanceof Date)
|
|
71
|
+
_timestamp = DateTime.fromJSDate(timestamp);
|
|
72
|
+
if (isString(timestamp))
|
|
73
|
+
_timestamp = DateTime.fromISO(timestamp);
|
|
74
|
+
if (DateTime.isDateTime(_timestamp) && !_timestamp.isValid)
|
|
75
|
+
throw new TypeError('timestamp must be valid DateTime, Date or ISO string');
|
|
76
|
+
if (!_timestamp)
|
|
77
|
+
throw new TypeError('timestamp is required');
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
this.window = this.window || Window.fromConfig(this.windowConfig);
|
|
81
|
+
this.window.recalculate(_timestamp);
|
|
82
|
+
await this.store.set(this.name, this.window.toObject());
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
throw new Error(`Failed to update rate-limit window ${this.name}`, {cause: err});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
set(state) {
|
|
66
90
|
if (this.window) return this;
|
|
67
91
|
this.window = state ? new Window(state) : Window.fromConfig(this.windowConfig);
|
package/lib/window.js
CHANGED
|
@@ -45,12 +45,13 @@ class Window {
|
|
|
45
45
|
_timestamp = DateTime.fromJSDate(timestamp);
|
|
46
46
|
if (isString(timestamp))
|
|
47
47
|
_timestamp = DateTime.fromISO(timestamp);
|
|
48
|
-
if (!_timestamp.isValid)
|
|
48
|
+
if (DateTime.isDateTime(_timestamp) && !_timestamp.isValid)
|
|
49
49
|
throw new TypeError('timestamp must be valid DateTime, Date or ISO string');
|
|
50
|
+
if (!_timestamp)
|
|
51
|
+
throw new TypeError('timestamp is required');
|
|
50
52
|
|
|
51
53
|
this.recalculate(_timestamp);
|
|
52
54
|
this.timeSlices[0] += value;
|
|
53
|
-
this.updated = _timestamp;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
slicesBetween(now, start) {
|
|
@@ -79,6 +80,7 @@ class Window {
|
|
|
79
80
|
...slice(0, -invalidSlices)(this.timeSlices),
|
|
80
81
|
];
|
|
81
82
|
}
|
|
83
|
+
this.updated = timestamp;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
valueOf() {
|
package/package.json
CHANGED
|
@@ -147,4 +147,44 @@ describe('RateLimiter', () => {
|
|
|
147
147
|
await expect(fn()).rejects.toThrow('Failed to update rate-limit window ip-req-per-minute-max10-u00001');
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
|
+
|
|
151
|
+
describe('#updateTime', () => {
|
|
152
|
+
test('should update window with ISO timestamp string', async () => {
|
|
153
|
+
const lim = new RateLimiter(DefaultOptions());
|
|
154
|
+
await expect(lim.updateTime('2025-01-01T00:00:30Z')).resolves.toBeUndefined();
|
|
155
|
+
expect(MockStore.set).toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should update window with Date object', async () => {
|
|
159
|
+
const lim = new RateLimiter(DefaultOptions());
|
|
160
|
+
const date = new Date('2025-01-01T00:00:30Z');
|
|
161
|
+
await expect(lim.updateTime(date)).resolves.toBeUndefined();
|
|
162
|
+
expect(MockStore.set).toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('should throw error if timestamp is invalid', async () => {
|
|
166
|
+
const lim = new RateLimiter(DefaultOptions());
|
|
167
|
+
const fn = () => lim.updateTime('invalid-timestamp');
|
|
168
|
+
await expect(fn()).rejects.toThrow('timestamp must be valid DateTime, Date or ISO string');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should throw error if timestamp is null or undefined', async () => {
|
|
172
|
+
const lim = new RateLimiter(DefaultOptions());
|
|
173
|
+
const fn = () => lim.updateTime(null);
|
|
174
|
+
await expect(fn()).rejects.toThrow('timestamp is required');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('should throw an error if failed to update store', async () => {
|
|
178
|
+
const lim = new RateLimiter({...DefaultOptions(), store: ErrorStore});
|
|
179
|
+
const fn = () => lim.updateTime('2025-01-01T00:00:30Z');
|
|
180
|
+
await expect(fn()).rejects.toThrow('Failed to update rate-limit window ip-req-per-minute-max10-u00001');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('should initialize window if not already initialized', async () => {
|
|
184
|
+
const lim = new RateLimiter(DefaultOptions());
|
|
185
|
+
expect(lim.window).toBeFalsy();
|
|
186
|
+
await lim.updateTime('2025-01-01T00:00:30Z');
|
|
187
|
+
expect(lim.window).toBeTruthy();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
150
190
|
});
|