@tanstack/pacer-lite 0.2.1 → 0.3.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/README.md +29 -14
- package/dist/lite-batcher.d.ts +4 -6
- package/dist/lite-batcher.js +72 -45
- package/dist/lite-debouncer.d.ts +5 -9
- package/dist/lite-debouncer.js +58 -40
- package/dist/lite-queuer.d.ts +5 -7
- package/dist/lite-queuer.js +170 -89
- package/dist/lite-rate-limiter.d.ts +5 -9
- package/dist/lite-rate-limiter.js +89 -63
- package/dist/lite-throttler.d.ts +5 -9
- package/dist/lite-throttler.js +63 -40
- package/dist/pacer/dist/types.d.ts +7 -0
- package/package.json +13 -33
- package/dist/index.cjs +0 -16
- package/dist/index.d.cts +0 -6
- package/dist/lite-batcher.cjs +0 -175
- package/dist/lite-batcher.cjs.map +0 -1
- package/dist/lite-batcher.d.cts +0 -184
- package/dist/lite-batcher.js.map +0 -1
- package/dist/lite-debouncer.cjs +0 -125
- package/dist/lite-debouncer.cjs.map +0 -1
- package/dist/lite-debouncer.d.cts +0 -127
- package/dist/lite-debouncer.js.map +0 -1
- package/dist/lite-queuer.cjs +0 -208
- package/dist/lite-queuer.cjs.map +0 -1
- package/dist/lite-queuer.d.cts +0 -243
- package/dist/lite-queuer.js.map +0 -1
- package/dist/lite-rate-limiter.cjs +0 -149
- package/dist/lite-rate-limiter.cjs.map +0 -1
- package/dist/lite-rate-limiter.d.cts +0 -149
- package/dist/lite-rate-limiter.js.map +0 -1
- package/dist/lite-throttler.cjs +0 -127
- package/dist/lite-throttler.cjs.map +0 -1
- package/dist/lite-throttler.d.cts +0 -134
- package/dist/lite-throttler.js.map +0 -1
- package/src/index.ts +0 -5
- package/src/lite-batcher.ts +0 -267
- package/src/lite-debouncer.ts +0 -184
- package/src/lite-queuer.ts +0 -434
- package/src/lite-rate-limiter.ts +0 -246
- package/src/lite-throttler.ts +0 -195
package/dist/lite-queuer.js
CHANGED
|
@@ -60,96 +60,15 @@
|
|
|
60
60
|
* ```
|
|
61
61
|
*/
|
|
62
62
|
var LiteQueuer = class {
|
|
63
|
+
fn;
|
|
64
|
+
options;
|
|
65
|
+
items = [];
|
|
66
|
+
timeoutId = null;
|
|
67
|
+
isRunning = true;
|
|
68
|
+
pendingTick = false;
|
|
63
69
|
constructor(fn, options = {}) {
|
|
64
70
|
this.fn = fn;
|
|
65
71
|
this.options = options;
|
|
66
|
-
this.items = [];
|
|
67
|
-
this.timeoutId = null;
|
|
68
|
-
this.isRunning = true;
|
|
69
|
-
this.pendingTick = false;
|
|
70
|
-
this.addItem = (item, position = this.options.addItemsTo, startProcessing = true) => {
|
|
71
|
-
if (this.items.length >= this.options.maxSize) return false;
|
|
72
|
-
if (this.options.getPriority) {
|
|
73
|
-
const priority = this.options.getPriority(item);
|
|
74
|
-
if (priority !== void 0) {
|
|
75
|
-
const insertIndex = this.items.findIndex((existing) => {
|
|
76
|
-
return (this.options.getPriority(existing) ?? -Infinity) < priority;
|
|
77
|
-
});
|
|
78
|
-
if (insertIndex === -1) this.items.push(item);
|
|
79
|
-
else this.items.splice(insertIndex, 0, item);
|
|
80
|
-
} else this.insertAtPosition(item, position);
|
|
81
|
-
} else this.insertAtPosition(item, position);
|
|
82
|
-
if (startProcessing && this.isRunning && !this.pendingTick) this.tick();
|
|
83
|
-
return true;
|
|
84
|
-
};
|
|
85
|
-
this.insertAtPosition = (item, position) => {
|
|
86
|
-
if (position === "front") this.items.unshift(item);
|
|
87
|
-
else this.items.push(item);
|
|
88
|
-
};
|
|
89
|
-
this.getNextItem = (position = this.options.getItemsFrom) => {
|
|
90
|
-
if (this.items.length === 0) return;
|
|
91
|
-
let item;
|
|
92
|
-
if (this.options.getPriority || position === "front") item = this.items.shift();
|
|
93
|
-
else item = this.items.pop();
|
|
94
|
-
return item;
|
|
95
|
-
};
|
|
96
|
-
this.execute = (position) => {
|
|
97
|
-
const item = this.getNextItem(position);
|
|
98
|
-
if (item !== void 0) this.fn(item);
|
|
99
|
-
return item;
|
|
100
|
-
};
|
|
101
|
-
this.tick = () => {
|
|
102
|
-
if (!this.isRunning) {
|
|
103
|
-
this.pendingTick = false;
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
this.pendingTick = true;
|
|
107
|
-
while (this.items.length > 0) {
|
|
108
|
-
if (this.execute(this.options.getItemsFrom) === void 0) break;
|
|
109
|
-
const wait = this.options.wait;
|
|
110
|
-
if (wait > 0) {
|
|
111
|
-
this.timeoutId = setTimeout(() => this.tick(), wait);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
this.pendingTick = false;
|
|
116
|
-
};
|
|
117
|
-
this.start = () => {
|
|
118
|
-
this.isRunning = true;
|
|
119
|
-
if (!this.pendingTick && this.items.length > 0) this.tick();
|
|
120
|
-
};
|
|
121
|
-
this.stop = () => {
|
|
122
|
-
this.clearTimeout();
|
|
123
|
-
this.isRunning = false;
|
|
124
|
-
this.pendingTick = false;
|
|
125
|
-
};
|
|
126
|
-
this.clearTimeout = () => {
|
|
127
|
-
if (this.timeoutId) {
|
|
128
|
-
clearTimeout(this.timeoutId);
|
|
129
|
-
this.timeoutId = null;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
this.peekNextItem = (position = "front") => {
|
|
133
|
-
if (this.items.length === 0) return;
|
|
134
|
-
if (this.options.getPriority || position === "front") return this.items[0];
|
|
135
|
-
else return this.items[this.items.length - 1];
|
|
136
|
-
};
|
|
137
|
-
this.peekAllItems = () => {
|
|
138
|
-
return [...this.items];
|
|
139
|
-
};
|
|
140
|
-
this.flush = (numberOfItems = this.items.length, position) => {
|
|
141
|
-
this.clearTimeout();
|
|
142
|
-
for (let i = 0; i < numberOfItems && this.items.length > 0; i++) this.execute(position);
|
|
143
|
-
if (this.isRunning && this.items.length > 0 && !this.pendingTick) this.tick();
|
|
144
|
-
};
|
|
145
|
-
this.flushAsBatch = (batchFunction) => {
|
|
146
|
-
const items = this.peekAllItems();
|
|
147
|
-
this.clear();
|
|
148
|
-
batchFunction(items);
|
|
149
|
-
};
|
|
150
|
-
this.clear = () => {
|
|
151
|
-
this.items = [];
|
|
152
|
-
};
|
|
153
72
|
this.options.addItemsTo = this.options.addItemsTo ?? "back";
|
|
154
73
|
this.options.getItemsFrom = this.options.getItemsFrom ?? "front";
|
|
155
74
|
this.options.maxSize = this.options.maxSize ?? Infinity;
|
|
@@ -177,6 +96,169 @@ var LiteQueuer = class {
|
|
|
177
96
|
get isQueueRunning() {
|
|
178
97
|
return this.isRunning;
|
|
179
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Adds an item to the queue. If the queue is full, the item is rejected.
|
|
101
|
+
* Items can be inserted at the front or back, and priority ordering is applied if getPriority is configured.
|
|
102
|
+
*
|
|
103
|
+
* Returns true if the item was added, false if the queue is full.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* queue.addItem('task1'); // Add to default position (back)
|
|
108
|
+
* queue.addItem('task2', 'front'); // Add to front
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
addItem = (item, position = this.options.addItemsTo, startProcessing = true) => {
|
|
112
|
+
if (this.items.length >= this.options.maxSize) return false;
|
|
113
|
+
if (this.options.getPriority) {
|
|
114
|
+
const priority = this.options.getPriority(item);
|
|
115
|
+
if (priority !== void 0) {
|
|
116
|
+
const insertIndex = this.items.findIndex((existing) => {
|
|
117
|
+
return (this.options.getPriority(existing) ?? -Infinity) < priority;
|
|
118
|
+
});
|
|
119
|
+
if (insertIndex === -1) this.items.push(item);
|
|
120
|
+
else this.items.splice(insertIndex, 0, item);
|
|
121
|
+
} else this.insertAtPosition(item, position);
|
|
122
|
+
} else this.insertAtPosition(item, position);
|
|
123
|
+
if (startProcessing && this.isRunning && !this.pendingTick) this.tick();
|
|
124
|
+
return true;
|
|
125
|
+
};
|
|
126
|
+
insertAtPosition = (item, position) => {
|
|
127
|
+
if (position === "front") this.items.unshift(item);
|
|
128
|
+
else this.items.push(item);
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Removes and returns the next item from the queue without executing the function.
|
|
132
|
+
* Use for manual queue management. Normally, use execute() to process items.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const nextItem = queue.getNextItem(); // Get from default position (front)
|
|
137
|
+
* const lastItem = queue.getNextItem('back'); // Get from back (LIFO)
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
getNextItem = (position = this.options.getItemsFrom) => {
|
|
141
|
+
if (this.items.length === 0) return;
|
|
142
|
+
let item;
|
|
143
|
+
if (this.options.getPriority || position === "front") item = this.items.shift();
|
|
144
|
+
else item = this.items.pop();
|
|
145
|
+
return item;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Removes and returns the next item from the queue and processes it using the provided function.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* queue.execute(); // Execute from default position
|
|
153
|
+
* queue.execute('back'); // Execute from back (LIFO)
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
execute = (position) => {
|
|
157
|
+
const item = this.getNextItem(position);
|
|
158
|
+
if (item !== void 0) this.fn(item);
|
|
159
|
+
return item;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Internal method that processes items in the queue with wait intervals
|
|
163
|
+
*/
|
|
164
|
+
tick = () => {
|
|
165
|
+
if (!this.isRunning) {
|
|
166
|
+
this.pendingTick = false;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.pendingTick = true;
|
|
170
|
+
while (this.items.length > 0) {
|
|
171
|
+
if (this.execute(this.options.getItemsFrom) === void 0) break;
|
|
172
|
+
const wait = this.options.wait;
|
|
173
|
+
if (wait > 0) {
|
|
174
|
+
this.timeoutId = setTimeout(() => this.tick(), wait);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
this.pendingTick = false;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Starts processing items in the queue. If already running, does nothing.
|
|
182
|
+
*/
|
|
183
|
+
start = () => {
|
|
184
|
+
this.isRunning = true;
|
|
185
|
+
if (!this.pendingTick && this.items.length > 0) this.tick();
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Stops processing items in the queue. Does not clear the queue.
|
|
189
|
+
*/
|
|
190
|
+
stop = () => {
|
|
191
|
+
this.clearTimeout();
|
|
192
|
+
this.isRunning = false;
|
|
193
|
+
this.pendingTick = false;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Clears any pending timeout
|
|
197
|
+
*/
|
|
198
|
+
clearTimeout = () => {
|
|
199
|
+
if (this.timeoutId) {
|
|
200
|
+
clearTimeout(this.timeoutId);
|
|
201
|
+
this.timeoutId = null;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Returns the next item in the queue without removing it.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* const next = queue.peekNextItem(); // Peek at front
|
|
210
|
+
* const last = queue.peekNextItem('back'); // Peek at back
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
peekNextItem = (position = "front") => {
|
|
214
|
+
if (this.items.length === 0) return;
|
|
215
|
+
if (this.options.getPriority || position === "front") return this.items[0];
|
|
216
|
+
else return this.items[this.items.length - 1];
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Returns a copy of all items in the queue.
|
|
220
|
+
*/
|
|
221
|
+
peekAllItems = () => {
|
|
222
|
+
return [...this.items];
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Processes a specified number of items immediately with no wait time.
|
|
226
|
+
* If no numberOfItems is provided, all items will be processed.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* queue.flush(); // Process all items immediately
|
|
231
|
+
* queue.flush(3); // Process next 3 items immediately
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
flush = (numberOfItems = this.items.length, position) => {
|
|
235
|
+
this.clearTimeout();
|
|
236
|
+
for (let i = 0; i < numberOfItems && this.items.length > 0; i++) this.execute(position);
|
|
237
|
+
if (this.isRunning && this.items.length > 0 && !this.pendingTick) this.tick();
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Processes all items in the queue as a batch using the provided function.
|
|
241
|
+
* The queue is cleared after processing.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```ts
|
|
245
|
+
* queue.flushAsBatch((items) => {
|
|
246
|
+
* console.log('Processing batch:', items);
|
|
247
|
+
* // Process all items together
|
|
248
|
+
* });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
flushAsBatch = (batchFunction) => {
|
|
252
|
+
const items = this.peekAllItems();
|
|
253
|
+
this.clear();
|
|
254
|
+
batchFunction(items);
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Removes all items from the queue. Does not affect items being processed.
|
|
258
|
+
*/
|
|
259
|
+
clear = () => {
|
|
260
|
+
this.items = [];
|
|
261
|
+
};
|
|
180
262
|
};
|
|
181
263
|
/**
|
|
182
264
|
* Creates a lightweight queue that processes items using the provided function.
|
|
@@ -202,5 +284,4 @@ function liteQueue(fn, options = {}) {
|
|
|
202
284
|
}
|
|
203
285
|
|
|
204
286
|
//#endregion
|
|
205
|
-
export { LiteQueuer, liteQueue };
|
|
206
|
-
//# sourceMappingURL=lite-queuer.js.map
|
|
287
|
+
export { LiteQueuer, liteQueue };
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { AnyFunction } from "
|
|
2
|
-
|
|
1
|
+
import { AnyFunction } from "./pacer/dist/types.js";
|
|
3
2
|
//#region src/lite-rate-limiter.d.ts
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Options for configuring a lite rate-limited function
|
|
7
5
|
*/
|
|
8
|
-
interface LiteRateLimiterOptions<TFn extends AnyFunction = AnyFunction> {
|
|
6
|
+
export interface LiteRateLimiterOptions<TFn extends AnyFunction = AnyFunction> {
|
|
9
7
|
/**
|
|
10
8
|
* Maximum number of executions allowed within the time window.
|
|
11
9
|
*/
|
|
@@ -68,7 +66,7 @@ interface LiteRateLimiterOptions<TFn extends AnyFunction = AnyFunction> {
|
|
|
68
66
|
* }
|
|
69
67
|
* ```
|
|
70
68
|
*/
|
|
71
|
-
declare class LiteRateLimiter<TFn extends AnyFunction> {
|
|
69
|
+
export declare class LiteRateLimiter<TFn extends AnyFunction> {
|
|
72
70
|
fn: TFn;
|
|
73
71
|
options: LiteRateLimiterOptions<TFn>;
|
|
74
72
|
private executionTimes;
|
|
@@ -143,7 +141,5 @@ declare class LiteRateLimiter<TFn extends AnyFunction> {
|
|
|
143
141
|
* });
|
|
144
142
|
* ```
|
|
145
143
|
*/
|
|
146
|
-
declare function liteRateLimit<TFn extends AnyFunction>(fn: TFn, options: LiteRateLimiterOptions<TFn>): (...args: Parameters<TFn>) => boolean;
|
|
147
|
-
//#endregion
|
|
148
|
-
export { LiteRateLimiter, LiteRateLimiterOptions, liteRateLimit };
|
|
149
|
-
//# sourceMappingURL=lite-rate-limiter.d.ts.map
|
|
144
|
+
export declare function liteRateLimit<TFn extends AnyFunction>(fn: TFn, options: LiteRateLimiterOptions<TFn>): (...args: Parameters<TFn>) => boolean;
|
|
145
|
+
//#endregion
|
|
@@ -38,72 +38,99 @@
|
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
40
|
var LiteRateLimiter = class {
|
|
41
|
+
fn;
|
|
42
|
+
options;
|
|
43
|
+
executionTimes = [];
|
|
44
|
+
timeoutIds = /* @__PURE__ */ new Set();
|
|
41
45
|
constructor(fn, options) {
|
|
42
46
|
this.fn = fn;
|
|
43
47
|
this.options = options;
|
|
44
|
-
this.executionTimes = [];
|
|
45
|
-
this.timeoutIds = /* @__PURE__ */ new Set();
|
|
46
|
-
this.maybeExecute = (...args) => {
|
|
47
|
-
this.cleanupOldExecutions();
|
|
48
|
-
if (this.getExecutionTimesInWindow().length < this.options.limit) {
|
|
49
|
-
this.execute(...args);
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
this.options.onReject?.(this);
|
|
53
|
-
return false;
|
|
54
|
-
};
|
|
55
|
-
this.execute = (...args) => {
|
|
56
|
-
const now = Date.now();
|
|
57
|
-
this.fn(...args);
|
|
58
|
-
this.options.onExecute?.(args, this);
|
|
59
|
-
this.executionTimes.push(now);
|
|
60
|
-
this.setCleanupTimeout(now);
|
|
61
|
-
};
|
|
62
|
-
this.getExecutionTimesInWindow = () => {
|
|
63
|
-
if (this.options.windowType === "sliding") return this.executionTimes.filter((time) => time > Date.now() - this.options.window);
|
|
64
|
-
else {
|
|
65
|
-
if (this.executionTimes.length === 0) return [];
|
|
66
|
-
const windowStart = Math.min(...this.executionTimes);
|
|
67
|
-
const windowEnd = windowStart + this.options.window;
|
|
68
|
-
if (Date.now() > windowEnd) return [];
|
|
69
|
-
return this.executionTimes.filter((time) => time >= windowStart && time <= windowEnd);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
this.setCleanupTimeout = (executionTime) => {
|
|
73
|
-
if (this.options.windowType === "sliding" || this.timeoutIds.size === 0) {
|
|
74
|
-
const timeUntilExpiration = executionTime - Date.now() + this.options.window + 1;
|
|
75
|
-
const timeoutId = setTimeout(() => {
|
|
76
|
-
this.cleanupOldExecutions();
|
|
77
|
-
this.clearTimeout(timeoutId);
|
|
78
|
-
}, timeUntilExpiration);
|
|
79
|
-
this.timeoutIds.add(timeoutId);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
this.clearTimeout = (timeoutId) => {
|
|
83
|
-
clearTimeout(timeoutId);
|
|
84
|
-
this.timeoutIds.delete(timeoutId);
|
|
85
|
-
};
|
|
86
|
-
this.clearTimeouts = () => {
|
|
87
|
-
this.timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId));
|
|
88
|
-
this.timeoutIds.clear();
|
|
89
|
-
};
|
|
90
|
-
this.cleanupOldExecutions = () => {
|
|
91
|
-
this.executionTimes = this.getExecutionTimesInWindow();
|
|
92
|
-
};
|
|
93
|
-
this.getRemainingInWindow = () => {
|
|
94
|
-
const relevantExecutionTimes = this.getExecutionTimesInWindow();
|
|
95
|
-
return Math.max(0, this.options.limit - relevantExecutionTimes.length);
|
|
96
|
-
};
|
|
97
|
-
this.getMsUntilNextWindow = () => {
|
|
98
|
-
if (this.getRemainingInWindow() > 0) return 0;
|
|
99
|
-
return (this.executionTimes[0] ?? Infinity) + this.options.window - Date.now();
|
|
100
|
-
};
|
|
101
|
-
this.reset = () => {
|
|
102
|
-
this.executionTimes = [];
|
|
103
|
-
this.clearTimeouts();
|
|
104
|
-
};
|
|
105
48
|
if (this.options.windowType === void 0) this.options.windowType = "fixed";
|
|
106
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Attempts to execute the rate-limited function if within the configured limits.
|
|
52
|
+
* Returns true if executed, false if rejected due to rate limiting.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const rateLimiter = new LiteRateLimiter(fn, { limit: 5, window: 1000 });
|
|
57
|
+
*
|
|
58
|
+
* // First 5 calls return true
|
|
59
|
+
* rateLimiter.maybeExecute('arg1', 'arg2'); // true
|
|
60
|
+
*
|
|
61
|
+
* // Additional calls within the window return false
|
|
62
|
+
* rateLimiter.maybeExecute('arg1', 'arg2'); // false
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
maybeExecute = (...args) => {
|
|
66
|
+
this.cleanupOldExecutions();
|
|
67
|
+
if (this.getExecutionTimesInWindow().length < this.options.limit) {
|
|
68
|
+
this.execute(...args);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
this.options.onReject?.(this);
|
|
72
|
+
return false;
|
|
73
|
+
};
|
|
74
|
+
execute = (...args) => {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
this.fn(...args);
|
|
77
|
+
this.options.onExecute?.(args, this);
|
|
78
|
+
this.executionTimes.push(now);
|
|
79
|
+
this.setCleanupTimeout(now);
|
|
80
|
+
};
|
|
81
|
+
getExecutionTimesInWindow = () => {
|
|
82
|
+
if (this.options.windowType === "sliding") return this.executionTimes.filter((time) => time > Date.now() - this.options.window);
|
|
83
|
+
else {
|
|
84
|
+
if (this.executionTimes.length === 0) return [];
|
|
85
|
+
const windowStart = Math.min(...this.executionTimes);
|
|
86
|
+
const windowEnd = windowStart + this.options.window;
|
|
87
|
+
if (Date.now() > windowEnd) return [];
|
|
88
|
+
return this.executionTimes.filter((time) => time >= windowStart && time <= windowEnd);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
setCleanupTimeout = (executionTime) => {
|
|
92
|
+
if (this.options.windowType === "sliding" || this.timeoutIds.size === 0) {
|
|
93
|
+
const timeUntilExpiration = executionTime - Date.now() + this.options.window + 1;
|
|
94
|
+
const timeoutId = setTimeout(() => {
|
|
95
|
+
this.cleanupOldExecutions();
|
|
96
|
+
this.clearTimeout(timeoutId);
|
|
97
|
+
}, timeUntilExpiration);
|
|
98
|
+
this.timeoutIds.add(timeoutId);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
clearTimeout = (timeoutId) => {
|
|
102
|
+
clearTimeout(timeoutId);
|
|
103
|
+
this.timeoutIds.delete(timeoutId);
|
|
104
|
+
};
|
|
105
|
+
clearTimeouts = () => {
|
|
106
|
+
this.timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId));
|
|
107
|
+
this.timeoutIds.clear();
|
|
108
|
+
};
|
|
109
|
+
cleanupOldExecutions = () => {
|
|
110
|
+
this.executionTimes = this.getExecutionTimesInWindow();
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Returns the number of remaining executions allowed in the current window.
|
|
114
|
+
*/
|
|
115
|
+
getRemainingInWindow = () => {
|
|
116
|
+
const relevantExecutionTimes = this.getExecutionTimesInWindow();
|
|
117
|
+
return Math.max(0, this.options.limit - relevantExecutionTimes.length);
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Returns the number of milliseconds until the next execution will be possible.
|
|
121
|
+
* Returns 0 if executions are currently allowed.
|
|
122
|
+
*/
|
|
123
|
+
getMsUntilNextWindow = () => {
|
|
124
|
+
if (this.getRemainingInWindow() > 0) return 0;
|
|
125
|
+
return (this.executionTimes[0] ?? Infinity) + this.options.window - Date.now();
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Resets the rate limiter state, clearing all execution history.
|
|
129
|
+
*/
|
|
130
|
+
reset = () => {
|
|
131
|
+
this.executionTimes = [];
|
|
132
|
+
this.clearTimeouts();
|
|
133
|
+
};
|
|
107
134
|
};
|
|
108
135
|
/**
|
|
109
136
|
* Creates a lightweight rate-limited function that will execute the provided function up to a maximum number of times within a time window.
|
|
@@ -143,5 +170,4 @@ function liteRateLimit(fn, options) {
|
|
|
143
170
|
}
|
|
144
171
|
|
|
145
172
|
//#endregion
|
|
146
|
-
export { LiteRateLimiter, liteRateLimit };
|
|
147
|
-
//# sourceMappingURL=lite-rate-limiter.js.map
|
|
173
|
+
export { LiteRateLimiter, liteRateLimit };
|
package/dist/lite-throttler.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { AnyFunction } from "
|
|
2
|
-
|
|
1
|
+
import { AnyFunction } from "./pacer/dist/types.js";
|
|
3
2
|
//#region src/lite-throttler.d.ts
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Options for configuring a lite throttled function
|
|
7
5
|
*/
|
|
8
|
-
interface LiteThrottlerOptions<TFn extends AnyFunction = AnyFunction> {
|
|
6
|
+
export interface LiteThrottlerOptions<TFn extends AnyFunction = AnyFunction> {
|
|
9
7
|
/**
|
|
10
8
|
* Whether to execute on the leading edge of the timeout.
|
|
11
9
|
* Defaults to true.
|
|
@@ -65,7 +63,7 @@ interface LiteThrottlerOptions<TFn extends AnyFunction = AnyFunction> {
|
|
|
65
63
|
* });
|
|
66
64
|
* ```
|
|
67
65
|
*/
|
|
68
|
-
declare class LiteThrottler<TFn extends AnyFunction> {
|
|
66
|
+
export declare class LiteThrottler<TFn extends AnyFunction> {
|
|
69
67
|
fn: TFn;
|
|
70
68
|
options: LiteThrottlerOptions<TFn>;
|
|
71
69
|
private timeoutId;
|
|
@@ -128,7 +126,5 @@ declare class LiteThrottler<TFn extends AnyFunction> {
|
|
|
128
126
|
* }, { wait: 250, leading: true, trailing: false });
|
|
129
127
|
* ```
|
|
130
128
|
*/
|
|
131
|
-
declare function liteThrottle<TFn extends AnyFunction>(fn: TFn, options: LiteThrottlerOptions<TFn>): (...args: Parameters<TFn>) => void;
|
|
132
|
-
//#endregion
|
|
133
|
-
export { LiteThrottler, LiteThrottlerOptions, liteThrottle };
|
|
134
|
-
//# sourceMappingURL=lite-throttler.d.ts.map
|
|
129
|
+
export declare function liteThrottle<TFn extends AnyFunction>(fn: TFn, options: LiteThrottlerOptions<TFn>): (...args: Parameters<TFn>) => void;
|
|
130
|
+
//#endregion
|
package/dist/lite-throttler.js
CHANGED
|
@@ -40,52 +40,76 @@
|
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
42
|
var LiteThrottler = class {
|
|
43
|
+
fn;
|
|
44
|
+
options;
|
|
45
|
+
timeoutId;
|
|
46
|
+
lastArgs;
|
|
47
|
+
lastExecutionTime = 0;
|
|
48
|
+
isPending = false;
|
|
43
49
|
constructor(fn, options) {
|
|
44
50
|
this.fn = fn;
|
|
45
51
|
this.options = options;
|
|
46
|
-
this.lastExecutionTime = 0;
|
|
47
|
-
this.isPending = false;
|
|
48
|
-
this.maybeExecute = (...args) => {
|
|
49
|
-
const timeSinceLastExecution = Date.now() - this.lastExecutionTime;
|
|
50
|
-
if (this.options.leading && timeSinceLastExecution >= this.options.wait) this.execute(...args);
|
|
51
|
-
else {
|
|
52
|
-
this.lastArgs = args;
|
|
53
|
-
if (!this.timeoutId && this.options.trailing) {
|
|
54
|
-
const timeoutDuration = this.options.wait - timeSinceLastExecution;
|
|
55
|
-
this.isPending = true;
|
|
56
|
-
this.timeoutId = setTimeout(() => {
|
|
57
|
-
if (this.lastArgs !== void 0) this.execute(...this.lastArgs);
|
|
58
|
-
}, timeoutDuration);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
this.execute = (...args) => {
|
|
63
|
-
this.fn(...args);
|
|
64
|
-
this.options.onExecute?.(args, this);
|
|
65
|
-
this.lastExecutionTime = Date.now();
|
|
66
|
-
this.clearTimeout();
|
|
67
|
-
this.lastArgs = void 0;
|
|
68
|
-
this.isPending = false;
|
|
69
|
-
};
|
|
70
|
-
this.flush = () => {
|
|
71
|
-
if (this.isPending && this.lastArgs) this.execute(...this.lastArgs);
|
|
72
|
-
};
|
|
73
|
-
this.cancel = () => {
|
|
74
|
-
this.clearTimeout();
|
|
75
|
-
this.lastArgs = void 0;
|
|
76
|
-
this.isPending = false;
|
|
77
|
-
};
|
|
78
|
-
this.clearTimeout = () => {
|
|
79
|
-
if (this.timeoutId) {
|
|
80
|
-
clearTimeout(this.timeoutId);
|
|
81
|
-
this.timeoutId = void 0;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
52
|
if (this.options.leading === void 0 && this.options.trailing === void 0) {
|
|
85
53
|
this.options.leading = true;
|
|
86
54
|
this.options.trailing = true;
|
|
87
55
|
}
|
|
88
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Attempts to execute the throttled function. The execution behavior depends on the throttler options:
|
|
59
|
+
*
|
|
60
|
+
* - If enough time has passed since the last execution (>= wait period):
|
|
61
|
+
* - With leading=true: Executes immediately
|
|
62
|
+
* - With leading=false: Waits for the next trailing execution
|
|
63
|
+
*
|
|
64
|
+
* - If within the wait period:
|
|
65
|
+
* - With trailing=true: Schedules execution for end of wait period
|
|
66
|
+
* - With trailing=false: Drops the execution
|
|
67
|
+
*/
|
|
68
|
+
maybeExecute = (...args) => {
|
|
69
|
+
const timeSinceLastExecution = Date.now() - this.lastExecutionTime;
|
|
70
|
+
if (this.options.leading && timeSinceLastExecution >= this.options.wait) this.execute(...args);
|
|
71
|
+
else {
|
|
72
|
+
this.lastArgs = args;
|
|
73
|
+
if (!this.timeoutId && this.options.trailing) {
|
|
74
|
+
const timeoutDuration = this.options.wait - timeSinceLastExecution;
|
|
75
|
+
this.isPending = true;
|
|
76
|
+
this.timeoutId = setTimeout(() => {
|
|
77
|
+
if (this.lastArgs !== void 0) this.execute(...this.lastArgs);
|
|
78
|
+
}, timeoutDuration);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
execute = (...args) => {
|
|
83
|
+
this.fn(...args);
|
|
84
|
+
this.options.onExecute?.(args, this);
|
|
85
|
+
this.lastExecutionTime = Date.now();
|
|
86
|
+
this.clearTimeout();
|
|
87
|
+
this.lastArgs = void 0;
|
|
88
|
+
this.isPending = false;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Processes the current pending execution immediately.
|
|
92
|
+
* If there's a pending execution, it will be executed right away
|
|
93
|
+
* and the timeout will be cleared.
|
|
94
|
+
*/
|
|
95
|
+
flush = () => {
|
|
96
|
+
if (this.isPending && this.lastArgs) this.execute(...this.lastArgs);
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Cancels any pending trailing execution and clears internal state.
|
|
100
|
+
* If a trailing execution is scheduled, this will prevent that execution from occurring.
|
|
101
|
+
*/
|
|
102
|
+
cancel = () => {
|
|
103
|
+
this.clearTimeout();
|
|
104
|
+
this.lastArgs = void 0;
|
|
105
|
+
this.isPending = false;
|
|
106
|
+
};
|
|
107
|
+
clearTimeout = () => {
|
|
108
|
+
if (this.timeoutId) {
|
|
109
|
+
clearTimeout(this.timeoutId);
|
|
110
|
+
this.timeoutId = void 0;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
89
113
|
};
|
|
90
114
|
/**
|
|
91
115
|
* Creates a lightweight throttled function that limits how often the provided function can execute.
|
|
@@ -121,5 +145,4 @@ function liteThrottle(fn, options) {
|
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
//#endregion
|
|
124
|
-
export { LiteThrottler, liteThrottle };
|
|
125
|
-
//# sourceMappingURL=lite-throttler.js.map
|
|
148
|
+
export { LiteThrottler, liteThrottle };
|