@tanstack/pacer 0.22.0 → 0.23.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 +4 -4
- package/dist/async-batcher.d.ts +11 -13
- package/dist/async-batcher.js +185 -131
- package/dist/async-debouncer.d.ts +6 -8
- package/dist/async-debouncer.js +186 -142
- package/dist/async-queuer.d.ts +11 -13
- package/dist/async-queuer.js +383 -290
- package/dist/async-rate-limiter.d.ts +6 -8
- package/dist/async-rate-limiter.js +197 -143
- package/dist/async-retryer.d.ts +6 -8
- package/dist/async-retryer.js +209 -179
- package/dist/async-throttler.d.ts +6 -8
- package/dist/async-throttler.js +214 -157
- package/dist/batcher.d.ts +5 -7
- package/dist/batcher.js +107 -87
- package/dist/debouncer.d.ts +6 -8
- package/dist/debouncer.js +99 -86
- package/dist/event-client.d.ts +8 -10
- package/dist/event-client.js +1 -2
- package/dist/queuer.d.ts +7 -9
- package/dist/queuer.js +277 -212
- package/dist/rate-limiter.d.ts +6 -8
- package/dist/rate-limiter.js +127 -109
- package/dist/throttler.d.ts +6 -8
- package/dist/throttler.js +127 -90
- package/dist/types.d.ts +4 -6
- package/dist/utils.d.ts +3 -5
- package/dist/utils.js +1 -2
- package/package.json +21 -68
- package/dist/async-batcher.cjs +0 -337
- package/dist/async-batcher.cjs.map +0 -1
- package/dist/async-batcher.d.cts +0 -343
- package/dist/async-batcher.js.map +0 -1
- package/dist/async-debouncer.cjs +0 -327
- package/dist/async-debouncer.cjs.map +0 -1
- package/dist/async-debouncer.d.cts +0 -299
- package/dist/async-debouncer.js.map +0 -1
- package/dist/async-queuer.cjs +0 -516
- package/dist/async-queuer.cjs.map +0 -1
- package/dist/async-queuer.d.cts +0 -440
- package/dist/async-queuer.js.map +0 -1
- package/dist/async-rate-limiter.cjs +0 -370
- package/dist/async-rate-limiter.cjs.map +0 -1
- package/dist/async-rate-limiter.d.cts +0 -356
- package/dist/async-rate-limiter.js.map +0 -1
- package/dist/async-retryer.cjs +0 -365
- package/dist/async-retryer.cjs.map +0 -1
- package/dist/async-retryer.d.cts +0 -321
- package/dist/async-retryer.js.map +0 -1
- package/dist/async-throttler.cjs +0 -344
- package/dist/async-throttler.cjs.map +0 -1
- package/dist/async-throttler.d.cts +0 -319
- package/dist/async-throttler.js.map +0 -1
- package/dist/batcher.cjs +0 -200
- package/dist/batcher.cjs.map +0 -1
- package/dist/batcher.d.cts +0 -179
- package/dist/batcher.js.map +0 -1
- package/dist/debouncer.cjs +0 -203
- package/dist/debouncer.cjs.map +0 -1
- package/dist/debouncer.d.cts +0 -166
- package/dist/debouncer.js.map +0 -1
- package/dist/event-client.cjs +0 -64
- package/dist/event-client.cjs.map +0 -1
- package/dist/event-client.d.cts +0 -65
- package/dist/event-client.js.map +0 -1
- package/dist/index.cjs +0 -52
- package/dist/index.d.cts +0 -15
- package/dist/queuer.cjs +0 -406
- package/dist/queuer.cjs.map +0 -1
- package/dist/queuer.d.cts +0 -345
- package/dist/queuer.js.map +0 -1
- package/dist/rate-limiter.cjs +0 -263
- package/dist/rate-limiter.cjs.map +0 -1
- package/dist/rate-limiter.d.cts +0 -214
- package/dist/rate-limiter.js.map +0 -1
- package/dist/throttler.cjs +0 -215
- package/dist/throttler.cjs.map +0 -1
- package/dist/throttler.d.cts +0 -206
- package/dist/throttler.js.map +0 -1
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +0 -13
- package/dist/utils.cjs +0 -13
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.d.cts +0 -7
- package/dist/utils.js.map +0 -1
- package/src/async-batcher.ts +0 -594
- package/src/async-debouncer.ts +0 -566
- package/src/async-queuer.ts +0 -988
- package/src/async-rate-limiter.ts +0 -648
- package/src/async-retryer.ts +0 -673
- package/src/async-throttler.ts +0 -634
- package/src/batcher.ts +0 -329
- package/src/debouncer.ts +0 -334
- package/src/event-client.ts +0 -129
- package/src/index.ts +0 -24
- package/src/queuer.ts +0 -751
- package/src/rate-limiter.ts +0 -429
- package/src/throttler.ts +0 -380
- package/src/types.ts +0 -12
- package/src/utils.ts +0 -12
package/dist/queuer.js
CHANGED
|
@@ -112,212 +112,13 @@ const defaultOptions = {
|
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
114
|
var Queuer = class {
|
|
115
|
-
|
|
115
|
+
fn;
|
|
116
|
+
store = new Store(getDefaultQueuerState());
|
|
117
|
+
key;
|
|
118
|
+
options;
|
|
119
|
+
#timeoutId = null;
|
|
116
120
|
constructor(fn, initialOptions = {}) {
|
|
117
121
|
this.fn = fn;
|
|
118
|
-
this.store = new Store(getDefaultQueuerState());
|
|
119
|
-
this.#timeoutId = null;
|
|
120
|
-
this.setOptions = (newOptions) => {
|
|
121
|
-
this.options = {
|
|
122
|
-
...this.options,
|
|
123
|
-
...newOptions
|
|
124
|
-
};
|
|
125
|
-
};
|
|
126
|
-
this.#setState = (newState) => {
|
|
127
|
-
this.store.setState((state) => {
|
|
128
|
-
const combinedState = {
|
|
129
|
-
...state,
|
|
130
|
-
...newState
|
|
131
|
-
};
|
|
132
|
-
const { items, isRunning } = combinedState;
|
|
133
|
-
const size = items.length;
|
|
134
|
-
const isFull = size >= (this.options.maxSize ?? Infinity);
|
|
135
|
-
const isEmpty = size === 0;
|
|
136
|
-
const isIdle = isRunning && isEmpty;
|
|
137
|
-
const status = isIdle ? "idle" : isRunning ? "running" : "stopped";
|
|
138
|
-
return {
|
|
139
|
-
...combinedState,
|
|
140
|
-
isEmpty,
|
|
141
|
-
isFull,
|
|
142
|
-
isIdle,
|
|
143
|
-
size,
|
|
144
|
-
status
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
emitChange("Queuer", this);
|
|
148
|
-
};
|
|
149
|
-
this.#getWait = () => {
|
|
150
|
-
return parseFunctionOrValue(this.options.wait ?? 0, this);
|
|
151
|
-
};
|
|
152
|
-
this.#tick = () => {
|
|
153
|
-
if (!this.store.state.isRunning) {
|
|
154
|
-
this.#setState({ pendingTick: false });
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
this.#setState({ pendingTick: true });
|
|
158
|
-
this.#checkExpiredItems();
|
|
159
|
-
while (this.store.state.items.length > 0) {
|
|
160
|
-
if (this.execute(this.options.getItemsFrom ?? "front") === void 0) break;
|
|
161
|
-
const wait = this.#getWait();
|
|
162
|
-
if (wait > 0) {
|
|
163
|
-
this.#timeoutId = setTimeout(() => this.#tick(), wait);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
this.#tick();
|
|
167
|
-
}
|
|
168
|
-
this.#setState({ pendingTick: false });
|
|
169
|
-
};
|
|
170
|
-
this.addItem = (item, position = this.options.addItemsTo ?? "back", runOnItemsChange = true) => {
|
|
171
|
-
this.#setState({ addItemCount: this.store.state.addItemCount + 1 });
|
|
172
|
-
if (item === void 0) {
|
|
173
|
-
this.#setState({ rejectionCount: this.store.state.rejectionCount + 1 });
|
|
174
|
-
this.options.onReject?.(item, this);
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {
|
|
178
|
-
this.#setState({ rejectionCount: this.store.state.rejectionCount + 1 });
|
|
179
|
-
this.options.onReject?.(item, this);
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
const priority = this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(item) : item?.priority;
|
|
183
|
-
const items = this.store.state.items;
|
|
184
|
-
const itemTimestamps = this.store.state.itemTimestamps;
|
|
185
|
-
if (priority !== void 0) {
|
|
186
|
-
const insertIndex = items.findIndex((existing) => {
|
|
187
|
-
return (this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(existing) : existing?.priority) < priority;
|
|
188
|
-
});
|
|
189
|
-
if (insertIndex === -1) {
|
|
190
|
-
items.push(item);
|
|
191
|
-
itemTimestamps.push(Date.now());
|
|
192
|
-
} else {
|
|
193
|
-
items.splice(insertIndex, 0, item);
|
|
194
|
-
itemTimestamps.splice(insertIndex, 0, Date.now());
|
|
195
|
-
}
|
|
196
|
-
} else if (position === "front") {
|
|
197
|
-
items.unshift(item);
|
|
198
|
-
itemTimestamps.unshift(Date.now());
|
|
199
|
-
} else {
|
|
200
|
-
items.push(item);
|
|
201
|
-
itemTimestamps.push(Date.now());
|
|
202
|
-
}
|
|
203
|
-
this.#setState({
|
|
204
|
-
items,
|
|
205
|
-
itemTimestamps
|
|
206
|
-
});
|
|
207
|
-
if (runOnItemsChange) this.options.onItemsChange?.(this);
|
|
208
|
-
if (this.store.state.isRunning && !this.store.state.pendingTick) {
|
|
209
|
-
this.#setState({ pendingTick: true });
|
|
210
|
-
this.#tick();
|
|
211
|
-
}
|
|
212
|
-
return true;
|
|
213
|
-
};
|
|
214
|
-
this.getNextItem = (position = this.options.getItemsFrom ?? "front") => {
|
|
215
|
-
const { items, itemTimestamps } = this.store.state;
|
|
216
|
-
let item;
|
|
217
|
-
if (this.options.getPriority !== defaultOptions.getPriority || position === "front") {
|
|
218
|
-
item = items[0];
|
|
219
|
-
if (item !== void 0) this.#setState({
|
|
220
|
-
items: items.slice(1),
|
|
221
|
-
itemTimestamps: itemTimestamps.slice(1)
|
|
222
|
-
});
|
|
223
|
-
} else {
|
|
224
|
-
item = items[items.length - 1];
|
|
225
|
-
if (item !== void 0) this.#setState({
|
|
226
|
-
items: items.slice(0, -1),
|
|
227
|
-
itemTimestamps: itemTimestamps.slice(0, -1)
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
if (item !== void 0) this.options.onItemsChange?.(this);
|
|
231
|
-
return item;
|
|
232
|
-
};
|
|
233
|
-
this.#getAllItems = () => {
|
|
234
|
-
const items = this.peekAllItems();
|
|
235
|
-
this.clear();
|
|
236
|
-
return items;
|
|
237
|
-
};
|
|
238
|
-
this.execute = (position) => {
|
|
239
|
-
const item = this.getNextItem(position);
|
|
240
|
-
if (item !== void 0) {
|
|
241
|
-
this.fn(item);
|
|
242
|
-
this.#setState({ executionCount: this.store.state.executionCount + 1 });
|
|
243
|
-
this.options.onExecute?.(item, this);
|
|
244
|
-
}
|
|
245
|
-
return item;
|
|
246
|
-
};
|
|
247
|
-
this.flush = (numberOfItems = this.store.state.items.length, position) => {
|
|
248
|
-
this.#clearTimeout();
|
|
249
|
-
for (let i = 0; i < numberOfItems; i++) this.execute(position);
|
|
250
|
-
this.#tick();
|
|
251
|
-
};
|
|
252
|
-
this.flushAsBatch = (batchFunction) => {
|
|
253
|
-
const items = this.#getAllItems();
|
|
254
|
-
this.clear();
|
|
255
|
-
batchFunction(items);
|
|
256
|
-
};
|
|
257
|
-
this.#checkExpiredItems = () => {
|
|
258
|
-
if ((this.options.expirationDuration ?? Infinity) === Infinity && this.options.getIsExpired === defaultOptions.getIsExpired) return;
|
|
259
|
-
const now = Date.now();
|
|
260
|
-
const expiredIndices = [];
|
|
261
|
-
for (let i = 0; i < this.store.state.items.length; i++) {
|
|
262
|
-
const timestamp = this.store.state.itemTimestamps[i];
|
|
263
|
-
if (timestamp === void 0) continue;
|
|
264
|
-
const item = this.store.state.items[i];
|
|
265
|
-
if (item === void 0) continue;
|
|
266
|
-
if (this.options.getIsExpired !== defaultOptions.getIsExpired ? this.options.getIsExpired(item, timestamp) : now - timestamp > (this.options.expirationDuration ?? Infinity)) expiredIndices.push(i);
|
|
267
|
-
}
|
|
268
|
-
for (let i = expiredIndices.length - 1; i >= 0; i--) {
|
|
269
|
-
const index = expiredIndices[i];
|
|
270
|
-
if (index === void 0) continue;
|
|
271
|
-
const expiredItem = this.store.state.items[index];
|
|
272
|
-
if (expiredItem === void 0) continue;
|
|
273
|
-
const newItems = [...this.store.state.items];
|
|
274
|
-
const newTimestamps = [...this.store.state.itemTimestamps];
|
|
275
|
-
newItems.splice(index, 1);
|
|
276
|
-
newTimestamps.splice(index, 1);
|
|
277
|
-
this.#setState({
|
|
278
|
-
items: newItems,
|
|
279
|
-
itemTimestamps: newTimestamps,
|
|
280
|
-
expirationCount: this.store.state.expirationCount + 1
|
|
281
|
-
});
|
|
282
|
-
this.options.onExpire?.(expiredItem, this);
|
|
283
|
-
}
|
|
284
|
-
if (expiredIndices.length > 0) this.options.onItemsChange?.(this);
|
|
285
|
-
};
|
|
286
|
-
this.peekNextItem = (position = "front") => {
|
|
287
|
-
if (position === "front") return this.store.state.items[0];
|
|
288
|
-
return this.store.state.items[this.store.state.items.length - 1];
|
|
289
|
-
};
|
|
290
|
-
this.peekAllItems = () => {
|
|
291
|
-
return [...this.store.state.items];
|
|
292
|
-
};
|
|
293
|
-
this.start = () => {
|
|
294
|
-
this.#setState({ isRunning: true });
|
|
295
|
-
if (!this.store.state.pendingTick && this.store.state.items.length > 0) this.#tick();
|
|
296
|
-
};
|
|
297
|
-
this.stop = () => {
|
|
298
|
-
this.#clearTimeout();
|
|
299
|
-
this.#setState({
|
|
300
|
-
isRunning: false,
|
|
301
|
-
pendingTick: false
|
|
302
|
-
});
|
|
303
|
-
};
|
|
304
|
-
this.#clearTimeout = () => {
|
|
305
|
-
if (this.#timeoutId) {
|
|
306
|
-
clearTimeout(this.#timeoutId);
|
|
307
|
-
this.#timeoutId = null;
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
this.clear = () => {
|
|
311
|
-
this.#setState({
|
|
312
|
-
items: [],
|
|
313
|
-
itemTimestamps: []
|
|
314
|
-
});
|
|
315
|
-
this.options.onItemsChange?.(this);
|
|
316
|
-
};
|
|
317
|
-
this.reset = () => {
|
|
318
|
-
this.#setState(getDefaultQueuerState());
|
|
319
|
-
this.options.onItemsChange?.(this);
|
|
320
|
-
};
|
|
321
122
|
this.key = initialOptions.key;
|
|
322
123
|
this.options = {
|
|
323
124
|
...defaultOptions,
|
|
@@ -341,23 +142,288 @@ var Queuer = class {
|
|
|
341
142
|
this.setOptions(event.payload.options);
|
|
342
143
|
});
|
|
343
144
|
}
|
|
344
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Updates the queuer options. New options are merged with existing options.
|
|
147
|
+
*/
|
|
148
|
+
setOptions = (newOptions) => {
|
|
149
|
+
this.options = {
|
|
150
|
+
...this.options,
|
|
151
|
+
...newOptions
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
#setState = (newState) => {
|
|
155
|
+
this.store.setState((state) => {
|
|
156
|
+
const combinedState = {
|
|
157
|
+
...state,
|
|
158
|
+
...newState
|
|
159
|
+
};
|
|
160
|
+
const { items, isRunning } = combinedState;
|
|
161
|
+
const size = items.length;
|
|
162
|
+
const isFull = size >= (this.options.maxSize ?? Infinity);
|
|
163
|
+
const isEmpty = size === 0;
|
|
164
|
+
const isIdle = isRunning && isEmpty;
|
|
165
|
+
const status = isIdle ? "idle" : isRunning ? "running" : "stopped";
|
|
166
|
+
return {
|
|
167
|
+
...combinedState,
|
|
168
|
+
isEmpty,
|
|
169
|
+
isFull,
|
|
170
|
+
isIdle,
|
|
171
|
+
size,
|
|
172
|
+
status
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
emitChange("Queuer", this);
|
|
176
|
+
};
|
|
345
177
|
/**
|
|
346
178
|
* Returns the current wait time (in milliseconds) between processing items.
|
|
347
179
|
* If a function is provided, it is called with the queuer instance.
|
|
348
180
|
*/
|
|
349
|
-
#getWait
|
|
181
|
+
#getWait = () => {
|
|
182
|
+
return parseFunctionOrValue(this.options.wait ?? 0, this);
|
|
183
|
+
};
|
|
350
184
|
/**
|
|
351
185
|
* Processes items in the queue up to the wait interval. Internal use only.
|
|
352
186
|
*/
|
|
353
|
-
#tick
|
|
354
|
-
|
|
187
|
+
#tick = () => {
|
|
188
|
+
if (!this.store.state.isRunning) {
|
|
189
|
+
this.#setState({ pendingTick: false });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.#setState({ pendingTick: true });
|
|
193
|
+
this.#checkExpiredItems();
|
|
194
|
+
while (this.store.state.items.length > 0) {
|
|
195
|
+
if (this.execute(this.options.getItemsFrom ?? "front") === void 0) break;
|
|
196
|
+
const wait = this.#getWait();
|
|
197
|
+
if (wait > 0) {
|
|
198
|
+
this.#timeoutId = setTimeout(() => this.#tick(), wait);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.#tick();
|
|
202
|
+
}
|
|
203
|
+
this.#setState({ pendingTick: false });
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Adds an item to the queue. If the queue is full, the item is rejected and onReject is called.
|
|
207
|
+
* `undefined` cannot be queued (it is the internal "no item" sentinel) and is always rejected.
|
|
208
|
+
* Items can be inserted based on priority or at the front/back depending on configuration.
|
|
209
|
+
*
|
|
210
|
+
* Returns true if the item was added, false if the queue is full.
|
|
211
|
+
*
|
|
212
|
+
* Example usage:
|
|
213
|
+
* ```ts
|
|
214
|
+
* queuer.addItem('task');
|
|
215
|
+
* queuer.addItem('task2', 'front');
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
addItem = (item, position = this.options.addItemsTo ?? "back", runOnItemsChange = true) => {
|
|
219
|
+
this.#setState({ addItemCount: this.store.state.addItemCount + 1 });
|
|
220
|
+
if (item === void 0) {
|
|
221
|
+
this.#setState({ rejectionCount: this.store.state.rejectionCount + 1 });
|
|
222
|
+
this.options.onReject?.(item, this);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {
|
|
226
|
+
this.#setState({ rejectionCount: this.store.state.rejectionCount + 1 });
|
|
227
|
+
this.options.onReject?.(item, this);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const priority = this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(item) : item?.priority;
|
|
231
|
+
const items = this.store.state.items;
|
|
232
|
+
const itemTimestamps = this.store.state.itemTimestamps;
|
|
233
|
+
if (priority !== void 0) {
|
|
234
|
+
const insertIndex = items.findIndex((existing) => {
|
|
235
|
+
return (this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(existing) : existing?.priority) < priority;
|
|
236
|
+
});
|
|
237
|
+
if (insertIndex === -1) {
|
|
238
|
+
items.push(item);
|
|
239
|
+
itemTimestamps.push(Date.now());
|
|
240
|
+
} else {
|
|
241
|
+
items.splice(insertIndex, 0, item);
|
|
242
|
+
itemTimestamps.splice(insertIndex, 0, Date.now());
|
|
243
|
+
}
|
|
244
|
+
} else if (position === "front") {
|
|
245
|
+
items.unshift(item);
|
|
246
|
+
itemTimestamps.unshift(Date.now());
|
|
247
|
+
} else {
|
|
248
|
+
items.push(item);
|
|
249
|
+
itemTimestamps.push(Date.now());
|
|
250
|
+
}
|
|
251
|
+
this.#setState({
|
|
252
|
+
items,
|
|
253
|
+
itemTimestamps
|
|
254
|
+
});
|
|
255
|
+
if (runOnItemsChange) this.options.onItemsChange?.(this);
|
|
256
|
+
if (this.store.state.isRunning && !this.store.state.pendingTick) {
|
|
257
|
+
this.#setState({ pendingTick: true });
|
|
258
|
+
this.#tick();
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Removes and returns the next item from the queue without executing the function.
|
|
264
|
+
* Use for manual queue management. Normally, use execute() to process items.
|
|
265
|
+
*
|
|
266
|
+
* Example usage:
|
|
267
|
+
* ```ts
|
|
268
|
+
* // FIFO
|
|
269
|
+
* queuer.getNextItem();
|
|
270
|
+
* // LIFO
|
|
271
|
+
* queuer.getNextItem('back');
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
getNextItem = (position = this.options.getItemsFrom ?? "front") => {
|
|
275
|
+
const { items, itemTimestamps } = this.store.state;
|
|
276
|
+
let item;
|
|
277
|
+
if (this.options.getPriority !== defaultOptions.getPriority || position === "front") {
|
|
278
|
+
item = items[0];
|
|
279
|
+
if (item !== void 0) this.#setState({
|
|
280
|
+
items: items.slice(1),
|
|
281
|
+
itemTimestamps: itemTimestamps.slice(1)
|
|
282
|
+
});
|
|
283
|
+
} else {
|
|
284
|
+
item = items[items.length - 1];
|
|
285
|
+
if (item !== void 0) this.#setState({
|
|
286
|
+
items: items.slice(0, -1),
|
|
287
|
+
itemTimestamps: itemTimestamps.slice(0, -1)
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (item !== void 0) this.options.onItemsChange?.(this);
|
|
291
|
+
return item;
|
|
292
|
+
};
|
|
293
|
+
#getAllItems = () => {
|
|
294
|
+
const items = this.peekAllItems();
|
|
295
|
+
this.clear();
|
|
296
|
+
return items;
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Removes and returns the next item from the queue and processes it using the provided function.
|
|
300
|
+
*
|
|
301
|
+
* Example usage:
|
|
302
|
+
* ```ts
|
|
303
|
+
* queuer.execute();
|
|
304
|
+
* // LIFO
|
|
305
|
+
* queuer.execute('back');
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
execute = (position) => {
|
|
309
|
+
const item = this.getNextItem(position);
|
|
310
|
+
if (item !== void 0) {
|
|
311
|
+
this.fn(item);
|
|
312
|
+
this.#setState({ executionCount: this.store.state.executionCount + 1 });
|
|
313
|
+
this.options.onExecute?.(item, this);
|
|
314
|
+
}
|
|
315
|
+
return item;
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Processes a specified number of items to execute immediately with no wait time
|
|
319
|
+
* If no numberOfItems is provided, all items will be processed
|
|
320
|
+
*/
|
|
321
|
+
flush = (numberOfItems = this.store.state.items.length, position) => {
|
|
322
|
+
this.#clearTimeout();
|
|
323
|
+
for (let i = 0; i < numberOfItems; i++) this.execute(position);
|
|
324
|
+
this.#tick();
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* Processes all items in the queue as a batch using the provided function as an argument
|
|
328
|
+
* The queue is cleared after processing
|
|
329
|
+
*/
|
|
330
|
+
flushAsBatch = (batchFunction) => {
|
|
331
|
+
const items = this.#getAllItems();
|
|
332
|
+
this.clear();
|
|
333
|
+
batchFunction(items);
|
|
334
|
+
};
|
|
355
335
|
/**
|
|
356
336
|
* Checks for expired items in the queue and removes them. Calls onExpire for each expired item.
|
|
357
337
|
* Internal use only.
|
|
358
338
|
*/
|
|
359
|
-
#checkExpiredItems
|
|
360
|
-
|
|
339
|
+
#checkExpiredItems = () => {
|
|
340
|
+
if ((this.options.expirationDuration ?? Infinity) === Infinity && this.options.getIsExpired === defaultOptions.getIsExpired) return;
|
|
341
|
+
const now = Date.now();
|
|
342
|
+
const expiredIndices = [];
|
|
343
|
+
for (let i = 0; i < this.store.state.items.length; i++) {
|
|
344
|
+
const timestamp = this.store.state.itemTimestamps[i];
|
|
345
|
+
if (timestamp === void 0) continue;
|
|
346
|
+
const item = this.store.state.items[i];
|
|
347
|
+
if (item === void 0) continue;
|
|
348
|
+
if (this.options.getIsExpired !== defaultOptions.getIsExpired ? this.options.getIsExpired(item, timestamp) : now - timestamp > (this.options.expirationDuration ?? Infinity)) expiredIndices.push(i);
|
|
349
|
+
}
|
|
350
|
+
for (let i = expiredIndices.length - 1; i >= 0; i--) {
|
|
351
|
+
const index = expiredIndices[i];
|
|
352
|
+
if (index === void 0) continue;
|
|
353
|
+
const expiredItem = this.store.state.items[index];
|
|
354
|
+
if (expiredItem === void 0) continue;
|
|
355
|
+
const newItems = [...this.store.state.items];
|
|
356
|
+
const newTimestamps = [...this.store.state.itemTimestamps];
|
|
357
|
+
newItems.splice(index, 1);
|
|
358
|
+
newTimestamps.splice(index, 1);
|
|
359
|
+
this.#setState({
|
|
360
|
+
items: newItems,
|
|
361
|
+
itemTimestamps: newTimestamps,
|
|
362
|
+
expirationCount: this.store.state.expirationCount + 1
|
|
363
|
+
});
|
|
364
|
+
this.options.onExpire?.(expiredItem, this);
|
|
365
|
+
}
|
|
366
|
+
if (expiredIndices.length > 0) this.options.onItemsChange?.(this);
|
|
367
|
+
};
|
|
368
|
+
/**
|
|
369
|
+
* Returns the next item in the queue without removing it.
|
|
370
|
+
*
|
|
371
|
+
* Example usage:
|
|
372
|
+
* ```ts
|
|
373
|
+
* queuer.peekNextItem(); // front
|
|
374
|
+
* queuer.peekNextItem('back'); // back
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
peekNextItem = (position = "front") => {
|
|
378
|
+
if (position === "front") return this.store.state.items[0];
|
|
379
|
+
return this.store.state.items[this.store.state.items.length - 1];
|
|
380
|
+
};
|
|
381
|
+
/**
|
|
382
|
+
* Returns a copy of all items in the queue.
|
|
383
|
+
*/
|
|
384
|
+
peekAllItems = () => {
|
|
385
|
+
return [...this.store.state.items];
|
|
386
|
+
};
|
|
387
|
+
/**
|
|
388
|
+
* Starts processing items in the queue. If already isRunning, does nothing.
|
|
389
|
+
*/
|
|
390
|
+
start = () => {
|
|
391
|
+
this.#setState({ isRunning: true });
|
|
392
|
+
if (!this.store.state.pendingTick && this.store.state.items.length > 0) this.#tick();
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Stops processing items in the queue. Does not clear the queue.
|
|
396
|
+
*/
|
|
397
|
+
stop = () => {
|
|
398
|
+
this.#clearTimeout();
|
|
399
|
+
this.#setState({
|
|
400
|
+
isRunning: false,
|
|
401
|
+
pendingTick: false
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
#clearTimeout = () => {
|
|
405
|
+
if (this.#timeoutId) {
|
|
406
|
+
clearTimeout(this.#timeoutId);
|
|
407
|
+
this.#timeoutId = null;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Removes all pending items from the queue. Does not affect items being processed.
|
|
412
|
+
*/
|
|
413
|
+
clear = () => {
|
|
414
|
+
this.#setState({
|
|
415
|
+
items: [],
|
|
416
|
+
itemTimestamps: []
|
|
417
|
+
});
|
|
418
|
+
this.options.onItemsChange?.(this);
|
|
419
|
+
};
|
|
420
|
+
/**
|
|
421
|
+
* Resets the queuer state to its default values
|
|
422
|
+
*/
|
|
423
|
+
reset = () => {
|
|
424
|
+
this.#setState(getDefaultQueuerState());
|
|
425
|
+
this.options.onItemsChange?.(this);
|
|
426
|
+
};
|
|
361
427
|
};
|
|
362
428
|
/**
|
|
363
429
|
* Creates a queue that processes items immediately upon addition.
|
|
@@ -399,5 +465,4 @@ function queue(fn, initialOptions) {
|
|
|
399
465
|
}
|
|
400
466
|
|
|
401
467
|
//#endregion
|
|
402
|
-
export { Queuer, queue, queuerOptions };
|
|
403
|
-
//# sourceMappingURL=queuer.js.map
|
|
468
|
+
export { Queuer, queue, queuerOptions };
|
package/dist/rate-limiter.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AnyFunction } from "./types.js";
|
|
2
2
|
import { Store } from "@tanstack/store";
|
|
3
3
|
//#region src/rate-limiter.d.ts
|
|
4
|
-
interface RateLimiterState {
|
|
4
|
+
export interface RateLimiterState {
|
|
5
5
|
/**
|
|
6
6
|
* Number of function executions that have been completed
|
|
7
7
|
*/
|
|
@@ -30,7 +30,7 @@ interface RateLimiterState {
|
|
|
30
30
|
/**
|
|
31
31
|
* Options for configuring a rate-limited function
|
|
32
32
|
*/
|
|
33
|
-
interface RateLimiterOptions<TFn extends AnyFunction> {
|
|
33
|
+
export interface RateLimiterOptions<TFn extends AnyFunction> {
|
|
34
34
|
/**
|
|
35
35
|
* Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions.
|
|
36
36
|
* Defaults to true.
|
|
@@ -74,7 +74,7 @@ interface RateLimiterOptions<TFn extends AnyFunction> {
|
|
|
74
74
|
/**
|
|
75
75
|
* Utility function for sharing common `RateLimiterOptions` options between different `RateLimiter` instances.
|
|
76
76
|
*/
|
|
77
|
-
declare function rateLimiterOptions<TFn extends AnyFunction = AnyFunction, TOptions extends Partial<RateLimiterOptions<TFn>> = Partial<RateLimiterOptions<TFn>>>(options: TOptions): TOptions;
|
|
77
|
+
export declare function rateLimiterOptions<TFn extends AnyFunction = AnyFunction, TOptions extends Partial<RateLimiterOptions<TFn>> = Partial<RateLimiterOptions<TFn>>>(options: TOptions): TOptions;
|
|
78
78
|
/**
|
|
79
79
|
* A class that creates a rate-limited function.
|
|
80
80
|
*
|
|
@@ -120,7 +120,7 @@ declare function rateLimiterOptions<TFn extends AnyFunction = AnyFunction, TOpti
|
|
|
120
120
|
* rateLimiter.maybeExecute('123');
|
|
121
121
|
* ```
|
|
122
122
|
*/
|
|
123
|
-
declare class RateLimiter<TFn extends AnyFunction> {
|
|
123
|
+
export declare class RateLimiter<TFn extends AnyFunction> {
|
|
124
124
|
#private;
|
|
125
125
|
fn: TFn;
|
|
126
126
|
readonly store: Store<Readonly<RateLimiterState>>;
|
|
@@ -208,7 +208,5 @@ declare class RateLimiter<TFn extends AnyFunction> {
|
|
|
208
208
|
* const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds
|
|
209
209
|
* ```
|
|
210
210
|
*/
|
|
211
|
-
declare function rateLimit<TFn extends AnyFunction>(fn: TFn, initialOptions: RateLimiterOptions<TFn>): (...args: Parameters<TFn>) => boolean;
|
|
212
|
-
//#endregion
|
|
213
|
-
export { RateLimiter, RateLimiterOptions, RateLimiterState, rateLimit, rateLimiterOptions };
|
|
214
|
-
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
211
|
+
export declare function rateLimit<TFn extends AnyFunction>(fn: TFn, initialOptions: RateLimiterOptions<TFn>): (...args: Parameters<TFn>) => boolean;
|
|
212
|
+
//#endregion
|