@tanstack/pacer 0.21.1 → 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.
Files changed (101) hide show
  1. package/README.md +21 -6
  2. package/dist/async-batcher.d.ts +11 -14
  3. package/dist/async-batcher.js +186 -132
  4. package/dist/async-debouncer.d.ts +10 -13
  5. package/dist/async-debouncer.js +187 -146
  6. package/dist/async-queuer.d.ts +12 -14
  7. package/dist/async-queuer.js +384 -259
  8. package/dist/async-rate-limiter.d.ts +9 -12
  9. package/dist/async-rate-limiter.js +198 -147
  10. package/dist/async-retryer.d.ts +10 -10
  11. package/dist/async-retryer.js +209 -188
  12. package/dist/async-throttler.d.ts +10 -13
  13. package/dist/async-throttler.js +215 -161
  14. package/dist/batcher.d.ts +5 -8
  15. package/dist/batcher.js +107 -87
  16. package/dist/debouncer.d.ts +6 -9
  17. package/dist/debouncer.js +99 -86
  18. package/dist/event-client.d.ts +8 -11
  19. package/dist/event-client.js +1 -2
  20. package/dist/index.js +1 -1
  21. package/dist/queuer.d.ts +8 -10
  22. package/dist/queuer.js +277 -207
  23. package/dist/rate-limiter.d.ts +6 -9
  24. package/dist/rate-limiter.js +127 -109
  25. package/dist/throttler.d.ts +6 -9
  26. package/dist/throttler.js +127 -90
  27. package/dist/types.d.ts +4 -6
  28. package/dist/utils.d.ts +3 -6
  29. package/dist/utils.js +1 -2
  30. package/package.json +23 -70
  31. package/dist/async-batcher.cjs +0 -337
  32. package/dist/async-batcher.cjs.map +0 -1
  33. package/dist/async-batcher.d.cts +0 -344
  34. package/dist/async-batcher.js.map +0 -1
  35. package/dist/async-debouncer.cjs +0 -330
  36. package/dist/async-debouncer.cjs.map +0 -1
  37. package/dist/async-debouncer.d.cts +0 -300
  38. package/dist/async-debouncer.js.map +0 -1
  39. package/dist/async-queuer.cjs +0 -484
  40. package/dist/async-queuer.cjs.map +0 -1
  41. package/dist/async-queuer.d.cts +0 -440
  42. package/dist/async-queuer.js.map +0 -1
  43. package/dist/async-rate-limiter.cjs +0 -373
  44. package/dist/async-rate-limiter.cjs.map +0 -1
  45. package/dist/async-rate-limiter.d.cts +0 -357
  46. package/dist/async-rate-limiter.js.map +0 -1
  47. package/dist/async-retryer.cjs +0 -374
  48. package/dist/async-retryer.cjs.map +0 -1
  49. package/dist/async-retryer.d.cts +0 -319
  50. package/dist/async-retryer.js.map +0 -1
  51. package/dist/async-throttler.cjs +0 -347
  52. package/dist/async-throttler.cjs.map +0 -1
  53. package/dist/async-throttler.d.cts +0 -320
  54. package/dist/async-throttler.js.map +0 -1
  55. package/dist/batcher.cjs +0 -200
  56. package/dist/batcher.cjs.map +0 -1
  57. package/dist/batcher.d.cts +0 -180
  58. package/dist/batcher.js.map +0 -1
  59. package/dist/debouncer.cjs +0 -203
  60. package/dist/debouncer.cjs.map +0 -1
  61. package/dist/debouncer.d.cts +0 -167
  62. package/dist/debouncer.js.map +0 -1
  63. package/dist/event-client.cjs +0 -64
  64. package/dist/event-client.cjs.map +0 -1
  65. package/dist/event-client.d.cts +0 -66
  66. package/dist/event-client.js.map +0 -1
  67. package/dist/index.cjs +0 -52
  68. package/dist/index.d.cts +0 -15
  69. package/dist/queuer.cjs +0 -401
  70. package/dist/queuer.cjs.map +0 -1
  71. package/dist/queuer.d.cts +0 -345
  72. package/dist/queuer.js.map +0 -1
  73. package/dist/rate-limiter.cjs +0 -263
  74. package/dist/rate-limiter.cjs.map +0 -1
  75. package/dist/rate-limiter.d.cts +0 -215
  76. package/dist/rate-limiter.js.map +0 -1
  77. package/dist/throttler.cjs +0 -215
  78. package/dist/throttler.cjs.map +0 -1
  79. package/dist/throttler.d.cts +0 -207
  80. package/dist/throttler.js.map +0 -1
  81. package/dist/types.cjs +0 -0
  82. package/dist/types.d.cts +0 -13
  83. package/dist/utils.cjs +0 -14
  84. package/dist/utils.cjs.map +0 -1
  85. package/dist/utils.d.cts +0 -8
  86. package/dist/utils.js.map +0 -1
  87. package/src/async-batcher.ts +0 -594
  88. package/src/async-debouncer.ts +0 -565
  89. package/src/async-queuer.ts +0 -925
  90. package/src/async-rate-limiter.ts +0 -647
  91. package/src/async-retryer.ts +0 -684
  92. package/src/async-throttler.ts +0 -633
  93. package/src/batcher.ts +0 -329
  94. package/src/debouncer.ts +0 -334
  95. package/src/event-client.ts +0 -129
  96. package/src/index.ts +0 -24
  97. package/src/queuer.ts +0 -740
  98. package/src/rate-limiter.ts +0 -429
  99. package/src/throttler.ts +0 -380
  100. package/src/types.ts +0 -12
  101. package/src/utils.ts +0 -12
@@ -1,484 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- const require_utils = require('./utils.cjs');
3
- const require_event_client = require('./event-client.cjs');
4
- const require_async_retryer = require('./async-retryer.cjs');
5
- let _tanstack_store = require("@tanstack/store");
6
-
7
- //#region src/async-queuer.ts
8
- function getDefaultAsyncQueuerState() {
9
- return {
10
- activeItems: [],
11
- addItemCount: 0,
12
- errorCount: 0,
13
- executeCount: 0,
14
- expirationCount: 0,
15
- isEmpty: true,
16
- isExecuting: false,
17
- isFull: false,
18
- isIdle: true,
19
- isRunning: true,
20
- itemTimestamps: [],
21
- items: [],
22
- lastResult: null,
23
- pendingTick: false,
24
- rejectionCount: 0,
25
- settledCount: 0,
26
- size: 0,
27
- status: "idle",
28
- successCount: 0
29
- };
30
- }
31
- /**
32
- * Utility function for sharing common `AsyncQueuerOptions` options between different `AsyncQueuer` instances.
33
- */
34
- function asyncQueuerOptions(options) {
35
- return options;
36
- }
37
- const defaultOptions = {
38
- addItemsTo: "back",
39
- asyncRetryerOptions: { maxAttempts: 1 },
40
- concurrency: 1,
41
- expirationDuration: Infinity,
42
- getIsExpired: () => false,
43
- getItemsFrom: "front",
44
- getPriority: (item) => item?.priority ?? 0,
45
- initialItems: [],
46
- maxSize: Infinity,
47
- started: true,
48
- wait: 0
49
- };
50
- /**
51
- * A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration.
52
- *
53
- * Async vs Sync Versions:
54
- * The async version provides advanced features over the sync Queuer:
55
- * - Returns promises that can be awaited for task results
56
- * - Built-in retry support via AsyncRetryer integration for each queued task
57
- * - Abort support to cancel in-flight task executions
58
- * - Comprehensive error handling with onError callbacks and throwOnError control
59
- * - Detailed execution tracking (success/error/settle counts)
60
- * - Concurrent execution support (process multiple items simultaneously)
61
- *
62
- * The sync Queuer is lighter weight and simpler when you don't need async features,
63
- * return values, or execution control.
64
- *
65
- * What is Queuing?
66
- * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.
67
- * Tasks are processed up to the configured concurrency limit. When a task completes,
68
- * the next pending task is processed if the concurrency limit allows.
69
- *
70
- * Key Features:
71
- * - Priority queue support via the getPriority option
72
- * - Configurable concurrency limit
73
- * - Callbacks for task success, error, completion, and queue state changes
74
- * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior
75
- * - Pause and resume processing
76
- * - Item expiration to remove stale items from the queue
77
- *
78
- * Error Handling:
79
- * - If an `onError` handler is provided, it will be called with the error and queuer instance
80
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
81
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
82
- * - Both onError and throwOnError can be used together; the handler will be called before any error is thrown
83
- * - The error state can be checked using the AsyncQueuer instance
84
- *
85
- * State Management:
86
- * - Uses TanStack Store for reactive state management
87
- * - Use `initialState` to provide initial state values when creating the async queuer
88
- * - Use `onSuccess` callback to react to successful task execution and implement custom logic
89
- * - Use `onError` callback to react to task execution errors and implement custom error handling
90
- * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic
91
- * - Use `onItemsChange` callback to react to items being added or removed from the queue
92
- * - Use `onExpire` callback to react to items expiring and implement custom logic
93
- * - Use `onReject` callback to react to items being rejected when the queue is full
94
- * - The state includes error count, expiration count, rejection count, running status, and success/settle counts
95
- * - State can be accessed via `asyncQueuer.store.state` when using the class directly
96
- * - When using framework adapters (React/Solid), state is accessed from `asyncQueuer.state`
97
- *
98
- * Example usage:
99
- * ```ts
100
- * const asyncQueuer = new AsyncQueuer<string>(async (item) => {
101
- * // process item
102
- * return item.toUpperCase();
103
- * }, {
104
- * concurrency: 2,
105
- * onSuccess: (result) => {
106
- * console.log(result);
107
- * }
108
- * });
109
- *
110
- * asyncQueuer.addItem('hello');
111
- * asyncQueuer.start();
112
- * ```
113
- */
114
- var AsyncQueuer = class {
115
- #timeoutIds;
116
- constructor(fn, initialOptions = {}) {
117
- this.fn = fn;
118
- this.store = new _tanstack_store.Store(getDefaultAsyncQueuerState());
119
- this.asyncRetryers = /* @__PURE__ */ new Map();
120
- this.#timeoutIds = /* @__PURE__ */ new Set();
121
- this.setOptions = (newOptions) => {
122
- this.options = {
123
- ...this.options,
124
- ...newOptions
125
- };
126
- };
127
- this.#setState = (newState) => {
128
- this.store.setState((state) => {
129
- const combinedState = {
130
- ...state,
131
- ...newState
132
- };
133
- const { activeItems, items, isRunning } = combinedState;
134
- const size = items.length;
135
- const isFull = size >= (this.options.maxSize ?? Infinity);
136
- const isEmpty = size === 0;
137
- const isIdle = isRunning && isEmpty && activeItems.length === 0;
138
- const status = isIdle ? "idle" : isRunning ? "running" : "stopped";
139
- return {
140
- ...combinedState,
141
- isEmpty,
142
- isFull,
143
- isIdle,
144
- size,
145
- status
146
- };
147
- });
148
- require_event_client.emitChange("AsyncQueuer", this);
149
- };
150
- this.#getWait = () => {
151
- return require_utils.parseFunctionOrValue(this.options.wait ?? 0, this);
152
- };
153
- this.#getConcurrency = () => {
154
- return require_utils.parseFunctionOrValue(this.options.concurrency ?? 1, this);
155
- };
156
- this.#tick = () => {
157
- if (!this.store.state.isRunning) {
158
- this.#setState({ pendingTick: false });
159
- return;
160
- }
161
- this.#setState({ pendingTick: true });
162
- this.#checkExpiredItems();
163
- const activeItems = this.store.state.activeItems;
164
- while (activeItems.length < this.#getConcurrency() && this.store.state.items.length > 0) {
165
- const nextItem = this.peekNextItem();
166
- if (!nextItem) break;
167
- activeItems.push(nextItem);
168
- this.#setState({ activeItems });
169
- (async () => {
170
- await this.execute();
171
- const wait = this.#getWait();
172
- if (wait > 0) {
173
- const timeoutId = setTimeout(() => this.#tick(), wait);
174
- this.#timeoutIds.add(timeoutId);
175
- return;
176
- }
177
- this.#tick();
178
- })();
179
- }
180
- this.#setState({ pendingTick: false });
181
- };
182
- this.addItem = (item, position = this.options.addItemsTo ?? "back", runOnItemsChange = true) => {
183
- this.#setState({ addItemCount: this.store.state.addItemCount + 1 });
184
- if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {
185
- this.#setState({ rejectionCount: this.store.state.rejectionCount + 1 });
186
- this.options.onReject?.(item, this);
187
- return false;
188
- }
189
- const priority = this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(item) : item.priority;
190
- const items = this.store.state.items;
191
- const itemTimestamps = this.store.state.itemTimestamps;
192
- if (priority !== void 0) {
193
- const insertIndex = items.findIndex((existing) => {
194
- return (this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(existing) : existing.priority) < priority;
195
- });
196
- if (insertIndex === -1) {
197
- items.push(item);
198
- itemTimestamps.push(Date.now());
199
- } else {
200
- items.splice(insertIndex, 0, item);
201
- itemTimestamps.splice(insertIndex, 0, Date.now());
202
- }
203
- } else if (position === "front") {
204
- items.unshift(item);
205
- itemTimestamps.unshift(Date.now());
206
- } else {
207
- items.push(item);
208
- itemTimestamps.push(Date.now());
209
- }
210
- this.#setState({
211
- items,
212
- itemTimestamps
213
- });
214
- if (runOnItemsChange) this.options.onItemsChange?.(this);
215
- if (this.store.state.isRunning && !this.store.state.pendingTick) this.#tick();
216
- return true;
217
- };
218
- this.getNextItem = (position = this.options.getItemsFrom ?? "front") => {
219
- const { items, itemTimestamps } = this.store.state;
220
- let item;
221
- if (this.options.getPriority !== defaultOptions.getPriority || position === "front") {
222
- item = items[0];
223
- if (item !== void 0) this.#setState({
224
- items: items.slice(1),
225
- itemTimestamps: itemTimestamps.slice(1)
226
- });
227
- } else {
228
- item = items[items.length - 1];
229
- if (item !== void 0) this.#setState({
230
- items: items.slice(0, -1),
231
- itemTimestamps: itemTimestamps.slice(0, -1)
232
- });
233
- }
234
- if (item !== void 0) this.options.onItemsChange?.(this);
235
- return item;
236
- };
237
- this.#getAllItems = () => {
238
- const items = this.peekAllItems();
239
- this.clear();
240
- return items;
241
- };
242
- this.execute = async (position) => {
243
- const item = this.getNextItem(position);
244
- if (item !== void 0) {
245
- const currentExecuteCount = this.store.state.executeCount + 1;
246
- this.#setState({
247
- executeCount: currentExecuteCount,
248
- isExecuting: true
249
- });
250
- try {
251
- const currentAsyncRetryer = new require_async_retryer.AsyncRetryer(this.fn, {
252
- ...this.options.asyncRetryerOptions,
253
- key: `${this.key}-retryer-${currentExecuteCount}`
254
- });
255
- this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer);
256
- const lastResult = await currentAsyncRetryer.execute(item);
257
- this.#setState({
258
- successCount: this.store.state.successCount + 1,
259
- lastResult
260
- });
261
- this.options.onSuccess?.(lastResult, item, this);
262
- } catch (error) {
263
- this.#setState({ errorCount: this.store.state.errorCount + 1 });
264
- this.options.onError?.(error, item, this);
265
- if (this.options.throwOnError) throw error;
266
- } finally {
267
- this.asyncRetryers.delete(currentExecuteCount);
268
- this.#setState({
269
- activeItems: this.store.state.activeItems.filter((activeItem) => activeItem !== item),
270
- isExecuting: false,
271
- settledCount: this.store.state.settledCount + 1
272
- });
273
- this.options.onSettled?.(item, this);
274
- }
275
- }
276
- return item;
277
- };
278
- this.flush = async (numberOfItems = this.store.state.items.length, position) => {
279
- this.#clearTimeouts();
280
- await Promise.all(Array.from({ length: numberOfItems }, () => this.execute(position)));
281
- };
282
- this.flushAsBatch = async (batchFunction) => {
283
- this.#clearTimeouts();
284
- await batchFunction(this.#getAllItems());
285
- };
286
- this.#checkExpiredItems = () => {
287
- if ((this.options.expirationDuration ?? Infinity) === Infinity && this.options.getIsExpired === defaultOptions.getIsExpired) return;
288
- const now = Date.now();
289
- const expiredIndices = [];
290
- for (let i = 0; i < this.store.state.items.length; i++) {
291
- const timestamp = this.store.state.itemTimestamps[i];
292
- if (timestamp === void 0) continue;
293
- const item = this.store.state.items[i];
294
- if (item === void 0) continue;
295
- if (this.options.getIsExpired !== defaultOptions.getIsExpired ? this.options.getIsExpired(item, timestamp) : now - timestamp > (this.options.expirationDuration ?? Infinity)) expiredIndices.push(i);
296
- }
297
- for (let i = expiredIndices.length - 1; i >= 0; i--) {
298
- const index = expiredIndices[i];
299
- if (index === void 0) continue;
300
- const expiredItem = this.store.state.items[index];
301
- if (expiredItem === void 0) continue;
302
- const newItems = [...this.store.state.items];
303
- const newTimestamps = [...this.store.state.itemTimestamps];
304
- newItems.splice(index, 1);
305
- newTimestamps.splice(index, 1);
306
- this.#setState({
307
- items: newItems,
308
- itemTimestamps: newTimestamps,
309
- expirationCount: this.store.state.expirationCount + 1
310
- });
311
- this.options.onExpire?.(expiredItem, this);
312
- }
313
- if (expiredIndices.length > 0) this.options.onItemsChange?.(this);
314
- };
315
- this.peekNextItem = (position = "front") => {
316
- if (position === "front") return this.store.state.items[0];
317
- return this.store.state.items[this.store.state.items.length - 1];
318
- };
319
- this.peekAllItems = () => {
320
- return [...this.peekActiveItems(), ...this.peekPendingItems()];
321
- };
322
- this.peekActiveItems = () => {
323
- return [...this.store.state.activeItems];
324
- };
325
- this.peekPendingItems = () => {
326
- return [...this.store.state.items];
327
- };
328
- this.start = () => {
329
- this.#setState({ isRunning: true });
330
- if (!this.store.state.pendingTick && this.store.state.items.length > 0) this.#tick();
331
- };
332
- this.stop = () => {
333
- this.#clearTimeouts();
334
- this.#setState({
335
- isRunning: false,
336
- pendingTick: false
337
- });
338
- };
339
- this.#clearTimeouts = () => {
340
- this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId));
341
- this.#timeoutIds.clear();
342
- };
343
- this.clear = () => {
344
- this.#setState({
345
- items: [],
346
- itemTimestamps: []
347
- });
348
- this.options.onItemsChange?.(this);
349
- };
350
- this.getAbortSignal = (executeCount) => {
351
- const count = executeCount ?? this.store.state.executeCount;
352
- return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
353
- };
354
- this.abort = () => {
355
- this.asyncRetryers.forEach((retryer) => retryer.abort());
356
- this.asyncRetryers.clear();
357
- this.#setState({ isExecuting: false });
358
- };
359
- this.reset = () => {
360
- this.#setState(getDefaultAsyncQueuerState());
361
- this.options.onItemsChange?.(this);
362
- this.asyncRetryers.forEach((retryer) => retryer.reset());
363
- };
364
- this.key = initialOptions.key;
365
- this.options = {
366
- ...defaultOptions,
367
- ...initialOptions,
368
- throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
369
- };
370
- const isInitiallyRunning = this.options.initialState?.isRunning ?? this.options.started ?? true;
371
- this.#setState({
372
- ...this.options.initialState,
373
- isRunning: isInitiallyRunning
374
- });
375
- if (this.options.initialState?.items) {
376
- if (this.store.state.isRunning) this.#tick();
377
- } else for (let i = 0; i < (this.options.initialItems?.length ?? 0); i++) {
378
- const item = this.options.initialItems[i];
379
- const isLast = i === (this.options.initialItems?.length ?? 0) - 1;
380
- this.addItem(item, this.options.addItemsTo ?? "back", isLast);
381
- }
382
- if (this.key) require_event_client.pacerEventClient.on("d-AsyncQueuer", (e) => {
383
- if (e.payload.key !== this.key) return;
384
- this.#setState(e.payload.store.state);
385
- this.setOptions(e.payload.options);
386
- });
387
- }
388
- #setState;
389
- /**
390
- * Returns the current wait time (in milliseconds) between processing items.
391
- * If a function is provided, it is called with the queuer instance.
392
- */
393
- #getWait;
394
- /**
395
- * Returns the current concurrency limit for processing items.
396
- * If a function is provided, it is called with the queuer instance.
397
- */
398
- #getConcurrency;
399
- /**
400
- * Processes items in the queue up to the concurrency limit. Internal use only.
401
- */
402
- #tick;
403
- #getAllItems;
404
- /**
405
- * Checks for expired items in the queue and removes them. Calls onExpire for each expired item.
406
- * Internal use only.
407
- */
408
- #checkExpiredItems;
409
- #clearTimeouts;
410
- };
411
- /**
412
- * Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks.
413
- * The queuer is started automatically and ready to process items.
414
- *
415
- * Async vs Sync Versions:
416
- * The async version provides advanced features over the sync queue function:
417
- * - Returns promises that can be awaited for task results
418
- * - Built-in retry support via AsyncRetryer integration for each queued task
419
- * - Abort support to cancel in-flight task executions
420
- * - Comprehensive error handling with onError callbacks and throwOnError control
421
- * - Detailed execution tracking (success/error/settle counts)
422
- * - Concurrent execution support (process multiple items simultaneously)
423
- *
424
- * The sync queue function is lighter weight and simpler when you don't need async features,
425
- * return values, or execution control.
426
- *
427
- * What is Queuing?
428
- * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.
429
- * Tasks are processed up to the configured concurrency limit. When a task completes,
430
- * the next pending task is processed if the concurrency limit allows.
431
- *
432
- * Configuration Options:
433
- * - `concurrency`: Maximum number of concurrent tasks (default: 1)
434
- * - `wait`: Time to wait between processing items (default: 0)
435
- * - `maxSize`: Maximum number of items allowed in the queue (default: Infinity)
436
- * - `getPriority`: Function to determine item priority
437
- * - `addItemsTo`: Default position to add items ('back' or 'front', default: 'back')
438
- * - `getItemsFrom`: Default position to get items ('front' or 'back', default: 'front')
439
- * - `expirationDuration`: Maximum time items can stay in queue
440
- * - `started`: Whether to start processing immediately (default: true)
441
- * - `asyncRetryerOptions`: Configure retry behavior for task executions
442
- *
443
- * Error Handling:
444
- * - If an `onError` handler is provided, it will be called with the error and queuer instance
445
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
446
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
447
- * - Both onError and throwOnError can be used together; the handler will be called before any error is thrown
448
- * - The error state can be checked using the underlying AsyncQueuer instance
449
- *
450
- * State Management:
451
- * - Uses TanStack Store for reactive state management
452
- * - Use `initialState` to provide initial state values when creating the async queuer
453
- * - Use `onSuccess` callback to react to successful task execution and implement custom logic
454
- * - Use `onError` callback to react to task execution errors and implement custom error handling
455
- * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic
456
- * - Use `onItemsChange` callback to react to items being added or removed from the queue
457
- * - Use `onExpire` callback to react to items expiring and implement custom logic
458
- * - Use `onReject` callback to react to items being rejected when the queue is full
459
- * - The state includes error count, expiration count, rejection count, running status, and success/settle counts
460
- * - State can be accessed via the underlying AsyncQueuer instance's `store.state` property
461
- * - When using framework adapters (React/Solid), state is accessed from the hook's state property
462
- *
463
- * @example
464
- * ```ts
465
- * const enqueue = asyncQueue<string>(async (item) => {
466
- * return item.toUpperCase();
467
- * }, {
468
- * concurrency: 2,
469
- * wait: 100,
470
- * onSuccess: (result) => console.log('Processed:', result)
471
- * });
472
- *
473
- * enqueue('hello');
474
- * ```
475
- */
476
- function asyncQueue(fn, initialOptions) {
477
- return new AsyncQueuer(fn, initialOptions).addItem;
478
- }
479
-
480
- //#endregion
481
- exports.AsyncQueuer = AsyncQueuer;
482
- exports.asyncQueue = asyncQueue;
483
- exports.asyncQueuerOptions = asyncQueuerOptions;
484
- //# sourceMappingURL=async-queuer.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"async-queuer.cjs","names":["Store","parseFunctionOrValue","#setState","#checkExpiredItems","#getConcurrency","#getWait","#tick","#timeoutIds","AsyncRetryer","#clearTimeouts","#getAllItems"],"sources":["../src/async-queuer.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { AsyncRetryer } from './async-retryer'\nimport { parseFunctionOrValue } from './utils'\nimport { emitChange, pacerEventClient } from './event-client'\nimport type { AsyncRetryerOptions } from './async-retryer'\nimport type { OptionalKeys } from './types'\nimport type { QueuePosition } from './queuer'\n\nexport interface AsyncQueuerState<TValue> {\n /**\n * Items currently being processed by the queuer\n */\n activeItems: Array<TValue>\n /**\n * Number of times addItem has been called (for reduction calculations)\n */\n addItemCount: number\n /**\n * Number of task executions that have resulted in errors\n */\n errorCount: number\n /**\n * Number of times execute has been called\n */\n executeCount: number\n /**\n * Number of items that have been removed from the queue due to expiration\n */\n expirationCount: number\n /**\n * Whether the queuer has no items to process (items array is empty)\n */\n isEmpty: boolean\n /**\n * Whether the queuer is currently executing\n */\n isExecuting: boolean\n /**\n * Whether the queuer has reached its maximum capacity\n */\n isFull: boolean\n /**\n * Whether the queuer is not currently processing any items\n */\n isIdle: boolean\n /**\n * Whether the queuer is active and will process items automatically\n */\n isRunning: boolean\n /**\n * Array of items currently waiting to be processed\n */\n items: Array<TValue>\n /**\n * Timestamps when items were added to the queue for expiration tracking\n */\n itemTimestamps: Array<number>\n /**\n * The result from the most recent task execution\n */\n lastResult: any\n /**\n * Whether the queuer has a pending timeout for processing the next item\n */\n pendingTick: boolean\n /**\n * Number of items that have been rejected from being added to the queue\n */\n rejectionCount: number\n /**\n * Number of task executions that have completed (either successfully or with errors)\n */\n settledCount: number\n /**\n * Number of items currently in the queue\n */\n size: number\n /**\n * Current processing status - 'idle' when not processing, 'running' when active, 'stopped' when paused\n */\n status: 'idle' | 'running' | 'stopped'\n /**\n * Number of task executions that have completed successfully\n */\n successCount: number\n}\n\nfunction getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {\n return {\n activeItems: [],\n addItemCount: 0,\n errorCount: 0,\n executeCount: 0,\n expirationCount: 0,\n isEmpty: true,\n isExecuting: false,\n isFull: false,\n isIdle: true,\n isRunning: true,\n itemTimestamps: [],\n items: [],\n lastResult: null,\n pendingTick: false,\n rejectionCount: 0,\n settledCount: 0,\n size: 0,\n status: 'idle',\n successCount: 0,\n }\n}\n\nexport interface AsyncQueuerOptions<TValue> {\n /**\n * Options for configuring the underlying async retryer\n */\n asyncRetryerOptions?: AsyncRetryerOptions<(item: TValue) => Promise<any>>\n /**\n * Default position to add items to the queuer\n * @default 'back'\n */\n addItemsTo?: QueuePosition\n /**\n * Maximum number of concurrent tasks to process.\n * Can be a number or a function that returns a number.\n * @default 1\n */\n concurrency?: number | ((queuer: AsyncQueuer<TValue>) => number)\n /**\n * Maximum time in milliseconds that an item can stay in the queue\n * If not provided, items will never expire\n */\n expirationDuration?: number\n /**\n * Function to determine if an item has expired\n * If provided, this overrides the expirationDuration behavior\n */\n getIsExpired?: (item: TValue, addedAt: number) => boolean\n /**\n * Default position to get items from during processing\n * @default 'front'\n */\n getItemsFrom?: QueuePosition\n /**\n * Function to determine priority of items in the queuer\n * Higher priority items will be processed first\n * If not provided, will use static priority values attached to tasks\n */\n getPriority?: (item: TValue) => number\n /**\n * Initial items to populate the queuer with\n */\n initialItems?: Array<TValue>\n /**\n * Initial state for the async queuer\n */\n initialState?: Partial<AsyncQueuerState<TValue>>\n /**\n * Optional key to identify this async queuer instance.\n * If provided, the async queuer will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Maximum number of items allowed in the queuer\n */\n maxSize?: number\n /**\n * Optional error handler for when a task throws.\n * If provided, the handler will be called with the error and queuer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (error: Error, item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item expires in the queuer\n */\n onExpire?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item is added or removed from the queuer\n */\n onItemsChange?: (queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item is rejected from being added to the queuer\n */\n onReject?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Optional callback to call when a task is settled\n */\n onSettled?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Optional callback to call when a task succeeds\n */\n onSuccess?: (result: any, item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Whether the queuer should start processing tasks immediately or not.\n */\n started?: boolean\n /**\n * Whether to throw errors when they occur.\n * Defaults to true if no onError handler is provided, false if an onError handler is provided.\n * Can be explicitly set to override these defaults.\n */\n throwOnError?: boolean\n /**\n * Time in milliseconds to wait between processing items.\n * Can be a number or a function that returns a number.\n * @default 0\n */\n wait?: number | ((queuer: AsyncQueuer<TValue>) => number)\n}\n\n/**\n * Utility function for sharing common `AsyncQueuerOptions` options between different `AsyncQueuer` instances.\n */\nexport function asyncQueuerOptions<\n TValue = any,\n TOptions extends Partial<AsyncQueuerOptions<TValue>> = Partial<\n AsyncQueuerOptions<TValue>\n >,\n>(options: TOptions): TOptions {\n return options\n}\n\ntype AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys<\n Required<AsyncQueuerOptions<any>>,\n | 'initialState'\n | 'throwOnError'\n | 'onSuccess'\n | 'onSettled'\n | 'onReject'\n | 'onItemsChange'\n | 'onExpire'\n | 'onError'\n | 'key'\n>\n\nconst defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = {\n addItemsTo: 'back',\n asyncRetryerOptions: {\n maxAttempts: 1,\n },\n concurrency: 1,\n expirationDuration: Infinity,\n getIsExpired: () => false,\n getItemsFrom: 'front',\n getPriority: (item: any) => item?.priority ?? 0,\n initialItems: [],\n maxSize: Infinity,\n started: true,\n wait: 0,\n}\n\n/**\n * A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync Queuer:\n * - Returns promises that can be awaited for task results\n * - Built-in retry support via AsyncRetryer integration for each queued task\n * - Abort support to cancel in-flight task executions\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n * - Concurrent execution support (process multiple items simultaneously)\n *\n * The sync Queuer is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Queuing?\n * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.\n * Tasks are processed up to the configured concurrency limit. When a task completes,\n * the next pending task is processed if the concurrency limit allows.\n *\n * Key Features:\n * - Priority queue support via the getPriority option\n * - Configurable concurrency limit\n * - Callbacks for task success, error, completion, and queue state changes\n * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior\n * - Pause and resume processing\n * - Item expiration to remove stale items from the queue\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and queuer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together; the handler will be called before any error is thrown\n * - The error state can be checked using the AsyncQueuer instance\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the async queuer\n * - Use `onSuccess` callback to react to successful task execution and implement custom logic\n * - Use `onError` callback to react to task execution errors and implement custom error handling\n * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the queue\n * - Use `onExpire` callback to react to items expiring and implement custom logic\n * - Use `onReject` callback to react to items being rejected when the queue is full\n * - The state includes error count, expiration count, rejection count, running status, and success/settle counts\n * - State can be accessed via `asyncQueuer.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncQueuer.state`\n *\n * Example usage:\n * ```ts\n * const asyncQueuer = new AsyncQueuer<string>(async (item) => {\n * // process item\n * return item.toUpperCase();\n * }, {\n * concurrency: 2,\n * onSuccess: (result) => {\n * console.log(result);\n * }\n * });\n *\n * asyncQueuer.addItem('hello');\n * asyncQueuer.start();\n * ```\n */\nexport class AsyncQueuer<TValue> {\n readonly store: Store<Readonly<AsyncQueuerState<TValue>>> = new Store<\n AsyncQueuerState<TValue>\n >(getDefaultAsyncQueuerState<TValue>())\n key: string | undefined\n options: AsyncQueuerOptions<TValue>\n asyncRetryers = new Map<\n number,\n AsyncRetryer<(item: TValue) => Promise<any>>\n >()\n #timeoutIds: Set<ReturnType<typeof setTimeout>> = new Set()\n\n constructor(\n public fn: (item: TValue) => Promise<any>,\n initialOptions: AsyncQueuerOptions<TValue> = {},\n ) {\n this.key = initialOptions.key\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n const isInitiallyRunning =\n this.options.initialState?.isRunning ?? this.options.started ?? true\n this.#setState({\n ...this.options.initialState,\n isRunning: isInitiallyRunning,\n })\n\n if (this.options.initialState?.items) {\n if (this.store.state.isRunning) {\n this.#tick()\n }\n } else {\n for (let i = 0; i < (this.options.initialItems?.length ?? 0); i++) {\n const item = this.options.initialItems![i]!\n const isLast = i === (this.options.initialItems?.length ?? 0) - 1\n this.addItem(item, this.options.addItemsTo ?? 'back', isLast)\n }\n }\n\n if (this.key) {\n pacerEventClient.on('d-AsyncQueuer', (e) => {\n if (e.payload.key !== this.key) return\n this.#setState(\n e.payload.store.state as Partial<AsyncQueuerState<TValue>>,\n )\n this.setOptions(\n e.payload.options as Partial<AsyncQueuerOptions<TValue>>,\n )\n })\n }\n }\n\n /**\n * Updates the queuer options. New options are merged with existing options.\n */\n setOptions = (newOptions: Partial<AsyncQueuerOptions<TValue>>): void => {\n this.options = { ...this.options, ...newOptions }\n }\n\n #setState = (newState: Partial<AsyncQueuerState<TValue>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n\n const { activeItems, items, isRunning } = combinedState\n\n const size = items.length\n const isFull = size >= (this.options.maxSize ?? Infinity)\n const isEmpty = size === 0\n const isIdle = isRunning && isEmpty && activeItems.length === 0\n\n const status = isIdle ? 'idle' : isRunning ? 'running' : 'stopped'\n\n return {\n ...combinedState,\n isEmpty,\n isFull,\n isIdle,\n size,\n status,\n }\n })\n emitChange('AsyncQueuer', this)\n }\n\n /**\n * Returns the current wait time (in milliseconds) between processing items.\n * If a function is provided, it is called with the queuer instance.\n */\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait ?? 0, this)\n }\n\n /**\n * Returns the current concurrency limit for processing items.\n * If a function is provided, it is called with the queuer instance.\n */\n #getConcurrency = (): number => {\n return parseFunctionOrValue(this.options.concurrency ?? 1, this)\n }\n\n /**\n * Processes items in the queue up to the concurrency limit. Internal use only.\n */\n #tick = () => {\n if (!this.store.state.isRunning) {\n this.#setState({ pendingTick: false })\n return\n }\n this.#setState({ pendingTick: true })\n\n // Check for expired items\n this.#checkExpiredItems()\n\n // Process items concurrently up to the concurrency limit\n const activeItems = this.store.state.activeItems\n while (\n activeItems.length < this.#getConcurrency() &&\n this.store.state.items.length > 0\n ) {\n const nextItem = this.peekNextItem()\n if (!nextItem) {\n break\n }\n activeItems.push(nextItem)\n this.#setState({\n activeItems,\n })\n ;(async () => {\n await this.execute()\n\n const wait = this.#getWait()\n if (wait > 0) {\n const timeoutId = setTimeout(() => this.#tick(), wait)\n this.#timeoutIds.add(timeoutId)\n return\n }\n\n this.#tick()\n })()\n }\n\n this.#setState({ pendingTick: false })\n }\n\n /**\n * Adds an item to the queue. If the queue is full, the item is rejected and onReject is called.\n * Items can be inserted based on priority or at the front/back depending on configuration.\n *\n * @example\n * ```ts\n * queuer.addItem({ value: 'task', priority: 10 });\n * queuer.addItem('task2', 'front');\n * ```\n */\n addItem = (\n item: TValue,\n position: QueuePosition = this.options.addItemsTo ?? 'back',\n runOnItemsChange: boolean = true,\n ): boolean => {\n this.#setState({\n addItemCount: this.store.state.addItemCount + 1,\n })\n\n if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {\n this.#setState({\n rejectionCount: this.store.state.rejectionCount + 1,\n })\n this.options.onReject?.(item, this)\n return false\n }\n\n // Get priority either from the function or from getPriority option\n const priority =\n this.options.getPriority !== defaultOptions.getPriority\n ? this.options.getPriority!(item)\n : (item as any).priority\n\n const items = this.store.state.items\n const itemTimestamps = this.store.state.itemTimestamps\n\n if (priority !== undefined) {\n // Insert based on priority - higher priority items go to front\n const insertIndex = items.findIndex((existing) => {\n const existingPriority =\n this.options.getPriority !== defaultOptions.getPriority\n ? this.options.getPriority!(existing)\n : (existing as any).priority\n return existingPriority < priority\n })\n\n if (insertIndex === -1) {\n items.push(item)\n itemTimestamps.push(Date.now())\n } else {\n items.splice(insertIndex, 0, item)\n itemTimestamps.splice(insertIndex, 0, Date.now())\n }\n } else {\n if (position === 'front') {\n // Default FIFO/LIFO behavior\n items.unshift(item)\n itemTimestamps.unshift(Date.now())\n } else {\n // LIFO\n items.push(item)\n itemTimestamps.push(Date.now())\n }\n }\n\n this.#setState({\n items,\n itemTimestamps,\n })\n\n if (runOnItemsChange) {\n this.options.onItemsChange?.(this)\n }\n\n if (this.store.state.isRunning && !this.store.state.pendingTick) {\n this.#tick()\n }\n\n return true\n }\n\n /**\n * Removes and returns the next item from the queue without executing the task function.\n * Use for manual queue management. Normally, use execute() to process items.\n *\n * @example\n * ```ts\n * // FIFO\n * queuer.getNextItem();\n * // LIFO\n * queuer.getNextItem('back');\n * ```\n */\n getNextItem = (\n position: QueuePosition = this.options.getItemsFrom ?? 'front',\n ): TValue | undefined => {\n const { items, itemTimestamps } = this.store.state\n let item: TValue | undefined\n\n // When priority function is provided or position is 'front', always get from front (highest priority)\n // Priority takes precedence over FIFO/LIFO behavior\n if (\n this.options.getPriority !== defaultOptions.getPriority ||\n position === 'front'\n ) {\n item = items[0]\n if (item !== undefined) {\n this.#setState({\n items: items.slice(1),\n itemTimestamps: itemTimestamps.slice(1),\n })\n }\n } else {\n item = items[items.length - 1]\n if (item !== undefined) {\n this.#setState({\n items: items.slice(0, -1),\n itemTimestamps: itemTimestamps.slice(0, -1),\n })\n }\n }\n\n if (item !== undefined) {\n this.options.onItemsChange?.(this)\n }\n\n return item\n }\n\n #getAllItems = (): Array<TValue> => {\n const items = this.peekAllItems()\n this.clear()\n return items\n }\n\n /**\n * Removes and returns the next item from the queue and executes the task function with it.\n *\n * @example\n * ```ts\n * queuer.execute();\n * // LIFO\n * queuer.execute('back');\n * ```\n */\n execute = async (position?: QueuePosition): Promise<any> => {\n const item = this.getNextItem(position)\n\n if (item !== undefined) {\n const currentExecuteCount = this.store.state.executeCount + 1\n this.#setState({\n executeCount: currentExecuteCount,\n isExecuting: true,\n })\n try {\n const currentAsyncRetryer = new AsyncRetryer(this.fn, {\n ...this.options.asyncRetryerOptions,\n key: `${this.key}-retryer-${currentExecuteCount}`,\n })\n this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer)\n const lastResult = await currentAsyncRetryer.execute(item) // EXECUTE!\n this.#setState({\n successCount: this.store.state.successCount + 1,\n lastResult,\n })\n this.options.onSuccess?.(lastResult, item, this)\n } catch (error) {\n this.#setState({\n errorCount: this.store.state.errorCount + 1,\n })\n this.options.onError?.(error as Error, item, this)\n if (this.options.throwOnError) {\n throw error\n }\n } finally {\n this.asyncRetryers.delete(currentExecuteCount) // dispose retryer\n this.#setState({\n activeItems: this.store.state.activeItems.filter(\n (activeItem) => activeItem !== item,\n ),\n isExecuting: false,\n settledCount: this.store.state.settledCount + 1,\n })\n this.options.onSettled?.(item, this)\n }\n }\n return item\n }\n\n /**\n * Processes a specified number of items to execute immediately with no wait time\n * If no numberOfItems is provided, all items will be processed\n */\n flush = async (\n numberOfItems: number = this.store.state.items.length,\n position?: QueuePosition,\n ): Promise<void> => {\n this.#clearTimeouts() // clear any pending timeouts\n await Promise.all(\n Array.from({ length: numberOfItems }, () => this.execute(position)),\n )\n }\n\n /**\n * Processes all items in the queue as a batch using the provided function as an argument\n * The queue is cleared after processing\n */\n flushAsBatch = async (\n batchFunction: (items: Array<TValue>) => Promise<any>,\n ): Promise<void> => {\n this.#clearTimeouts() // clear any pending timeouts\n const items = this.#getAllItems()\n await batchFunction(items)\n }\n\n /**\n * Checks for expired items in the queue and removes them. Calls onExpire for each expired item.\n * Internal use only.\n */\n #checkExpiredItems = (): void => {\n if (\n (this.options.expirationDuration ?? Infinity) === Infinity &&\n this.options.getIsExpired === defaultOptions.getIsExpired\n ) {\n return\n }\n\n const now = Date.now()\n const expiredIndices: Array<number> = []\n\n // Find indices of expired items\n for (let i = 0; i < this.store.state.items.length; i++) {\n const timestamp = this.store.state.itemTimestamps[i]\n if (timestamp === undefined) continue\n\n const item = this.store.state.items[i]\n if (item === undefined) continue\n\n const isExpired =\n this.options.getIsExpired !== defaultOptions.getIsExpired\n ? this.options.getIsExpired!(item, timestamp)\n : now - timestamp > (this.options.expirationDuration ?? Infinity)\n\n if (isExpired) {\n expiredIndices.push(i)\n }\n }\n\n // Remove expired items from back to front to maintain indices\n for (let i = expiredIndices.length - 1; i >= 0; i--) {\n const index = expiredIndices[i]\n if (index === undefined) continue\n\n const expiredItem = this.store.state.items[index]\n if (expiredItem === undefined) continue\n\n const newItems = [...this.store.state.items]\n const newTimestamps = [...this.store.state.itemTimestamps]\n newItems.splice(index, 1)\n newTimestamps.splice(index, 1)\n this.#setState({\n items: newItems,\n itemTimestamps: newTimestamps,\n expirationCount: this.store.state.expirationCount + 1,\n })\n this.options.onExpire?.(expiredItem, this)\n }\n\n if (expiredIndices.length > 0) {\n this.options.onItemsChange?.(this)\n }\n }\n\n /**\n * Returns the next item in the queue without removing it.\n *\n * @example\n * ```ts\n * queuer.peekNextItem(); // front\n * queuer.peekNextItem('back'); // back\n * ```\n */\n peekNextItem = (position: QueuePosition = 'front'): TValue | undefined => {\n if (position === 'front') {\n return this.store.state.items[0]\n }\n return this.store.state.items[this.store.state.items.length - 1]\n }\n\n /**\n * Returns a copy of all items in the queue, including active and pending items.\n */\n peekAllItems = (): Array<TValue> => {\n return [...this.peekActiveItems(), ...this.peekPendingItems()]\n }\n\n /**\n * Returns the items currently being processed (active tasks).\n */\n peekActiveItems = (): Array<TValue> => {\n return [...this.store.state.activeItems]\n }\n\n /**\n * Returns the items waiting to be processed (pending tasks).\n */\n peekPendingItems = (): Array<TValue> => {\n return [...this.store.state.items]\n }\n\n /**\n * Starts processing items in the queue. If already running, does nothing.\n */\n start = (): void => {\n this.#setState({ isRunning: true })\n if (!this.store.state.pendingTick && this.store.state.items.length > 0) {\n this.#tick()\n }\n }\n\n /**\n * Stops processing items in the queue. Does not clear the queue.\n */\n stop = (): void => {\n this.#clearTimeouts()\n this.#setState({ isRunning: false, pendingTick: false })\n }\n\n #clearTimeouts = (): void => {\n this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId))\n this.#timeoutIds.clear()\n }\n\n /**\n * Removes all pending items from the queue.\n * Does NOT affect active tasks.\n */\n clear = (): void => {\n this.#setState({ items: [], itemTimestamps: [] })\n this.options.onItemsChange?.(this)\n }\n\n /**\n * Returns the AbortSignal for a specific execution.\n * If no executeCount is provided, returns the signal for the most recent execution.\n * Returns null if no execution is found or not currently executing.\n *\n * @param executeCount - Optional specific execution to get signal for\n * @example\n * ```typescript\n * const queuer = new AsyncQueuer(\n * async (item: string) => {\n * const signal = queuer.getAbortSignal()\n * if (signal) {\n * const response = await fetch(`/api/process/${item}`, { signal })\n * return response.json()\n * }\n * },\n * { concurrency: 2 }\n * )\n * ```\n */\n getAbortSignal = (executeCount?: number): AbortSignal | null => {\n const count = executeCount ?? this.store.state.executeCount\n const retryer = this.asyncRetryers.get(count)\n return retryer?.getAbortSignal() ?? null\n }\n\n /**\n * Aborts all ongoing executions with the internal abort controllers.\n * Does NOT clear out the items.\n */\n abort = (): void => {\n this.asyncRetryers.forEach((retryer) => retryer.abort())\n this.asyncRetryers.clear()\n this.#setState({\n isExecuting: false,\n })\n }\n\n /**\n * Resets the queuer state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncQueuerState<TValue>())\n this.options.onItemsChange?.(this)\n this.asyncRetryers.forEach((retryer) => retryer.reset())\n }\n}\n\n/**\n * Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks.\n * The queuer is started automatically and ready to process items.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync queue function:\n * - Returns promises that can be awaited for task results\n * - Built-in retry support via AsyncRetryer integration for each queued task\n * - Abort support to cancel in-flight task executions\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n * - Concurrent execution support (process multiple items simultaneously)\n *\n * The sync queue function is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Queuing?\n * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.\n * Tasks are processed up to the configured concurrency limit. When a task completes,\n * the next pending task is processed if the concurrency limit allows.\n *\n * Configuration Options:\n * - `concurrency`: Maximum number of concurrent tasks (default: 1)\n * - `wait`: Time to wait between processing items (default: 0)\n * - `maxSize`: Maximum number of items allowed in the queue (default: Infinity)\n * - `getPriority`: Function to determine item priority\n * - `addItemsTo`: Default position to add items ('back' or 'front', default: 'back')\n * - `getItemsFrom`: Default position to get items ('front' or 'back', default: 'front')\n * - `expirationDuration`: Maximum time items can stay in queue\n * - `started`: Whether to start processing immediately (default: true)\n * - `asyncRetryerOptions`: Configure retry behavior for task executions\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and queuer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together; the handler will be called before any error is thrown\n * - The error state can be checked using the underlying AsyncQueuer instance\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the async queuer\n * - Use `onSuccess` callback to react to successful task execution and implement custom logic\n * - Use `onError` callback to react to task execution errors and implement custom error handling\n * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the queue\n * - Use `onExpire` callback to react to items expiring and implement custom logic\n * - Use `onReject` callback to react to items being rejected when the queue is full\n * - The state includes error count, expiration count, rejection count, running status, and success/settle counts\n * - State can be accessed via the underlying AsyncQueuer instance's `store.state` property\n * - When using framework adapters (React/Solid), state is accessed from the hook's state property\n *\n * @example\n * ```ts\n * const enqueue = asyncQueue<string>(async (item) => {\n * return item.toUpperCase();\n * }, {\n * concurrency: 2,\n * wait: 100,\n * onSuccess: (result) => console.log('Processed:', result)\n * });\n *\n * enqueue('hello');\n * ```\n */\nexport function asyncQueue<TValue>(\n fn: (value: TValue) => Promise<any>,\n initialOptions: AsyncQueuerOptions<TValue>,\n) {\n const asyncQueuer = new AsyncQueuer<TValue>(fn, initialOptions)\n return asyncQueuer.addItem\n}\n"],"mappings":";;;;;;;AAuFA,SAAS,6BAA+D;CACtE,OAAO;EACL,aAAa,EAAE;EACf,cAAc;EACd,YAAY;EACZ,cAAc;EACd,iBAAiB;EACjB,SAAS;EACT,aAAa;EACb,QAAQ;EACR,QAAQ;EACR,WAAW;EACX,gBAAgB,EAAE;EAClB,OAAO,EAAE;EACT,YAAY;EACZ,aAAa;EACb,gBAAgB;EAChB,cAAc;EACd,MAAM;EACN,QAAQ;EACR,cAAc;EACf;;;;;AAwGH,SAAgB,mBAKd,SAA6B;CAC7B,OAAO;;AAgBT,MAAM,iBAA0D;CAC9D,YAAY;CACZ,qBAAqB,EACnB,aAAa,GACd;CACD,aAAa;CACb,oBAAoB;CACpB,oBAAoB;CACpB,cAAc;CACd,cAAc,SAAc,MAAM,YAAY;CAC9C,cAAc,EAAE;CAChB,SAAS;CACT,SAAS;CACT,MAAM;CACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkED,IAAa,cAAb,MAAiC;CAU/B;CAEA,YACE,AAAO,IACP,iBAA6C,EAAE,EAC/C;EAFO;eAZmD,IAAIA,sBAE9D,4BAAoC,CAAC;uCAGvB,IAAI,KAGjB;qCAC+C,IAAI,KAAK;qBA+C7C,eAA0D;GACtE,KAAK,UAAU;IAAE,GAAG,KAAK;IAAS,GAAG;IAAY;;oBAGtC,aAAsD;GACjE,KAAK,MAAM,UAAU,UAAU;IAC7B,MAAM,gBAAgB;KACpB,GAAG;KACH,GAAG;KACJ;IAED,MAAM,EAAE,aAAa,OAAO,cAAc;IAE1C,MAAM,OAAO,MAAM;IACnB,MAAM,SAAS,SAAS,KAAK,QAAQ,WAAW;IAChD,MAAM,UAAU,SAAS;IACzB,MAAM,SAAS,aAAa,WAAW,YAAY,WAAW;IAE9D,MAAM,SAAS,SAAS,SAAS,YAAY,YAAY;IAEzD,OAAO;KACL,GAAG;KACH;KACA;KACA;KACA;KACA;KACD;KACD;GACF,gCAAW,eAAe,KAAK;;wBAOR;GACvB,OAAOC,mCAAqB,KAAK,QAAQ,QAAQ,GAAG,KAAK;;+BAO3B;GAC9B,OAAOA,mCAAqB,KAAK,QAAQ,eAAe,GAAG,KAAK;;qBAMpD;GACZ,IAAI,CAAC,KAAK,MAAM,MAAM,WAAW;IAC/B,KAAKC,UAAU,EAAE,aAAa,OAAO,CAAC;IACtC;;GAEF,KAAKA,UAAU,EAAE,aAAa,MAAM,CAAC;GAGrC,KAAKC,oBAAoB;GAGzB,MAAM,cAAc,KAAK,MAAM,MAAM;GACrC,OACE,YAAY,SAAS,KAAKC,iBAAiB,IAC3C,KAAK,MAAM,MAAM,MAAM,SAAS,GAChC;IACA,MAAM,WAAW,KAAK,cAAc;IACpC,IAAI,CAAC,UACH;IAEF,YAAY,KAAK,SAAS;IAC1B,KAAKF,UAAU,EACb,aACD,CAAC;IACD,CAAC,YAAY;KACZ,MAAM,KAAK,SAAS;KAEpB,MAAM,OAAO,KAAKG,UAAU;KAC5B,IAAI,OAAO,GAAG;MACZ,MAAM,YAAY,iBAAiB,KAAKC,OAAO,EAAE,KAAK;MACtD,KAAKC,YAAY,IAAI,UAAU;MAC/B;;KAGF,KAAKD,OAAO;QACV;;GAGN,KAAKJ,UAAU,EAAE,aAAa,OAAO,CAAC;;kBActC,MACA,WAA0B,KAAK,QAAQ,cAAc,QACrD,mBAA4B,SAChB;GACZ,KAAKA,UAAU,EACb,cAAc,KAAK,MAAM,MAAM,eAAe,GAC/C,CAAC;GAEF,IAAI,KAAK,MAAM,MAAM,MAAM,WAAW,KAAK,QAAQ,WAAW,WAAW;IACvE,KAAKA,UAAU,EACb,gBAAgB,KAAK,MAAM,MAAM,iBAAiB,GACnD,CAAC;IACF,KAAK,QAAQ,WAAW,MAAM,KAAK;IACnC,OAAO;;GAIT,MAAM,WACJ,KAAK,QAAQ,gBAAgB,eAAe,cACxC,KAAK,QAAQ,YAAa,KAAK,GAC9B,KAAa;GAEpB,MAAM,QAAQ,KAAK,MAAM,MAAM;GAC/B,MAAM,iBAAiB,KAAK,MAAM,MAAM;GAExC,IAAI,aAAa,QAAW;IAE1B,MAAM,cAAc,MAAM,WAAW,aAAa;KAKhD,QAHE,KAAK,QAAQ,gBAAgB,eAAe,cACxC,KAAK,QAAQ,YAAa,SAAS,GAClC,SAAiB,YACE;MAC1B;IAEF,IAAI,gBAAgB,IAAI;KACtB,MAAM,KAAK,KAAK;KAChB,eAAe,KAAK,KAAK,KAAK,CAAC;WAC1B;KACL,MAAM,OAAO,aAAa,GAAG,KAAK;KAClC,eAAe,OAAO,aAAa,GAAG,KAAK,KAAK,CAAC;;UAGnD,IAAI,aAAa,SAAS;IAExB,MAAM,QAAQ,KAAK;IACnB,eAAe,QAAQ,KAAK,KAAK,CAAC;UAC7B;IAEL,MAAM,KAAK,KAAK;IAChB,eAAe,KAAK,KAAK,KAAK,CAAC;;GAInC,KAAKA,UAAU;IACb;IACA;IACD,CAAC;GAEF,IAAI,kBACF,KAAK,QAAQ,gBAAgB,KAAK;GAGpC,IAAI,KAAK,MAAM,MAAM,aAAa,CAAC,KAAK,MAAM,MAAM,aAClD,KAAKI,OAAO;GAGd,OAAO;;sBAgBP,WAA0B,KAAK,QAAQ,gBAAgB,YAChC;GACvB,MAAM,EAAE,OAAO,mBAAmB,KAAK,MAAM;GAC7C,IAAI;GAIJ,IACE,KAAK,QAAQ,gBAAgB,eAAe,eAC5C,aAAa,SACb;IACA,OAAO,MAAM;IACb,IAAI,SAAS,QACX,KAAKJ,UAAU;KACb,OAAO,MAAM,MAAM,EAAE;KACrB,gBAAgB,eAAe,MAAM,EAAE;KACxC,CAAC;UAEC;IACL,OAAO,MAAM,MAAM,SAAS;IAC5B,IAAI,SAAS,QACX,KAAKA,UAAU;KACb,OAAO,MAAM,MAAM,GAAG,GAAG;KACzB,gBAAgB,eAAe,MAAM,GAAG,GAAG;KAC5C,CAAC;;GAIN,IAAI,SAAS,QACX,KAAK,QAAQ,gBAAgB,KAAK;GAGpC,OAAO;;4BAG2B;GAClC,MAAM,QAAQ,KAAK,cAAc;GACjC,KAAK,OAAO;GACZ,OAAO;;iBAaC,OAAO,aAA2C;GAC1D,MAAM,OAAO,KAAK,YAAY,SAAS;GAEvC,IAAI,SAAS,QAAW;IACtB,MAAM,sBAAsB,KAAK,MAAM,MAAM,eAAe;IAC5D,KAAKA,UAAU;KACb,cAAc;KACd,aAAa;KACd,CAAC;IACF,IAAI;KACF,MAAM,sBAAsB,IAAIM,mCAAa,KAAK,IAAI;MACpD,GAAG,KAAK,QAAQ;MAChB,KAAK,GAAG,KAAK,IAAI,WAAW;MAC7B,CAAC;KACF,KAAK,cAAc,IAAI,qBAAqB,oBAAoB;KAChE,MAAM,aAAa,MAAM,oBAAoB,QAAQ,KAAK;KAC1D,KAAKN,UAAU;MACb,cAAc,KAAK,MAAM,MAAM,eAAe;MAC9C;MACD,CAAC;KACF,KAAK,QAAQ,YAAY,YAAY,MAAM,KAAK;aACzC,OAAO;KACd,KAAKA,UAAU,EACb,YAAY,KAAK,MAAM,MAAM,aAAa,GAC3C,CAAC;KACF,KAAK,QAAQ,UAAU,OAAgB,MAAM,KAAK;KAClD,IAAI,KAAK,QAAQ,cACf,MAAM;cAEA;KACR,KAAK,cAAc,OAAO,oBAAoB;KAC9C,KAAKA,UAAU;MACb,aAAa,KAAK,MAAM,MAAM,YAAY,QACvC,eAAe,eAAe,KAChC;MACD,aAAa;MACb,cAAc,KAAK,MAAM,MAAM,eAAe;MAC/C,CAAC;KACF,KAAK,QAAQ,YAAY,MAAM,KAAK;;;GAGxC,OAAO;;eAOD,OACN,gBAAwB,KAAK,MAAM,MAAM,MAAM,QAC/C,aACkB;GAClB,KAAKO,gBAAgB;GACrB,MAAM,QAAQ,IACZ,MAAM,KAAK,EAAE,QAAQ,eAAe,QAAQ,KAAK,QAAQ,SAAS,CAAC,CACpE;;sBAOY,OACb,kBACkB;GAClB,KAAKA,gBAAgB;GAErB,MAAM,cADQ,KAAKC,cACM,CAAC;;kCAOK;GAC/B,KACG,KAAK,QAAQ,sBAAsB,cAAc,YAClD,KAAK,QAAQ,iBAAiB,eAAe,cAE7C;GAGF,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,iBAAgC,EAAE;GAGxC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,MAAM,MAAM,MAAM,QAAQ,KAAK;IACtD,MAAM,YAAY,KAAK,MAAM,MAAM,eAAe;IAClD,IAAI,cAAc,QAAW;IAE7B,MAAM,OAAO,KAAK,MAAM,MAAM,MAAM;IACpC,IAAI,SAAS,QAAW;IAOxB,IAJE,KAAK,QAAQ,iBAAiB,eAAe,eACzC,KAAK,QAAQ,aAAc,MAAM,UAAU,GAC3C,MAAM,aAAa,KAAK,QAAQ,sBAAsB,WAG1D,eAAe,KAAK,EAAE;;GAK1B,KAAK,IAAI,IAAI,eAAe,SAAS,GAAG,KAAK,GAAG,KAAK;IACnD,MAAM,QAAQ,eAAe;IAC7B,IAAI,UAAU,QAAW;IAEzB,MAAM,cAAc,KAAK,MAAM,MAAM,MAAM;IAC3C,IAAI,gBAAgB,QAAW;IAE/B,MAAM,WAAW,CAAC,GAAG,KAAK,MAAM,MAAM,MAAM;IAC5C,MAAM,gBAAgB,CAAC,GAAG,KAAK,MAAM,MAAM,eAAe;IAC1D,SAAS,OAAO,OAAO,EAAE;IACzB,cAAc,OAAO,OAAO,EAAE;IAC9B,KAAKR,UAAU;KACb,OAAO;KACP,gBAAgB;KAChB,iBAAiB,KAAK,MAAM,MAAM,kBAAkB;KACrD,CAAC;IACF,KAAK,QAAQ,WAAW,aAAa,KAAK;;GAG5C,IAAI,eAAe,SAAS,GAC1B,KAAK,QAAQ,gBAAgB,KAAK;;uBAatB,WAA0B,YAAgC;GACxE,IAAI,aAAa,SACf,OAAO,KAAK,MAAM,MAAM,MAAM;GAEhC,OAAO,KAAK,MAAM,MAAM,MAAM,KAAK,MAAM,MAAM,MAAM,SAAS;;4BAM5B;GAClC,OAAO,CAAC,GAAG,KAAK,iBAAiB,EAAE,GAAG,KAAK,kBAAkB,CAAC;;+BAMzB;GACrC,OAAO,CAAC,GAAG,KAAK,MAAM,MAAM,YAAY;;gCAMF;GACtC,OAAO,CAAC,GAAG,KAAK,MAAM,MAAM,MAAM;;qBAMhB;GAClB,KAAKA,UAAU,EAAE,WAAW,MAAM,CAAC;GACnC,IAAI,CAAC,KAAK,MAAM,MAAM,eAAe,KAAK,MAAM,MAAM,MAAM,SAAS,GACnE,KAAKI,OAAO;;oBAOG;GACjB,KAAKG,gBAAgB;GACrB,KAAKP,UAAU;IAAE,WAAW;IAAO,aAAa;IAAO,CAAC;;8BAG7B;GAC3B,KAAKK,YAAY,SAAS,cAAc,aAAa,UAAU,CAAC;GAChE,KAAKA,YAAY,OAAO;;qBAON;GAClB,KAAKL,UAAU;IAAE,OAAO,EAAE;IAAE,gBAAgB,EAAE;IAAE,CAAC;GACjD,KAAK,QAAQ,gBAAgB,KAAK;;yBAuBlB,iBAA8C;GAC9D,MAAM,QAAQ,gBAAgB,KAAK,MAAM,MAAM;GAE/C,OADgB,KAAK,cAAc,IAAI,MACzB,EAAE,gBAAgB,IAAI;;qBAOlB;GAClB,KAAK,cAAc,SAAS,YAAY,QAAQ,OAAO,CAAC;GACxD,KAAK,cAAc,OAAO;GAC1B,KAAKA,UAAU,EACb,aAAa,OACd,CAAC;;qBAMgB;GAClB,KAAKA,UAAU,4BAAoC,CAAC;GACpD,KAAK,QAAQ,gBAAgB,KAAK;GAClC,KAAK,cAAc,SAAS,YAAY,QAAQ,OAAO,CAAC;;EAvgBxD,KAAK,MAAM,eAAe;EAC1B,KAAK,UAAU;GACb,GAAG;GACH,GAAG;GACH,cAAc,eAAe,gBAAgB,CAAC,eAAe;GAC9D;EACD,MAAM,qBACJ,KAAK,QAAQ,cAAc,aAAa,KAAK,QAAQ,WAAW;EAClE,KAAKA,UAAU;GACb,GAAG,KAAK,QAAQ;GAChB,WAAW;GACZ,CAAC;EAEF,IAAI,KAAK,QAAQ,cAAc,OAC7B;OAAI,KAAK,MAAM,MAAM,WACnB,KAAKI,OAAO;SAGd,KAAK,IAAI,IAAI,GAAG,KAAK,KAAK,QAAQ,cAAc,UAAU,IAAI,KAAK;GACjE,MAAM,OAAO,KAAK,QAAQ,aAAc;GACxC,MAAM,SAAS,OAAO,KAAK,QAAQ,cAAc,UAAU,KAAK;GAChE,KAAK,QAAQ,MAAM,KAAK,QAAQ,cAAc,QAAQ,OAAO;;EAIjE,IAAI,KAAK,KACP,sCAAiB,GAAG,kBAAkB,MAAM;GAC1C,IAAI,EAAE,QAAQ,QAAQ,KAAK,KAAK;GAChC,KAAKJ,UACH,EAAE,QAAQ,MAAM,MACjB;GACD,KAAK,WACH,EAAE,QAAQ,QACX;IACD;;CAWN;;;;;CAgCA;;;;;CAQA;;;;CAOA;CA0KA;;;;;CA0FA;CA6GA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+HF,SAAgB,WACd,IACA,gBACA;CAEA,OAAO,IADiB,YAAoB,IAAI,eAC9B,CAAC"}