@tanstack/pacer 0.1.0 → 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.
Files changed (74) hide show
  1. package/dist/cjs/async-debouncer.cjs +112 -63
  2. package/dist/cjs/async-debouncer.cjs.map +1 -1
  3. package/dist/cjs/async-debouncer.d.cts +66 -25
  4. package/dist/cjs/async-queuer.cjs +198 -124
  5. package/dist/cjs/async-queuer.cjs.map +1 -1
  6. package/dist/cjs/async-queuer.d.cts +91 -49
  7. package/dist/cjs/async-rate-limiter.cjs +83 -55
  8. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  9. package/dist/cjs/async-rate-limiter.d.cts +55 -28
  10. package/dist/cjs/async-throttler.cjs +121 -70
  11. package/dist/cjs/async-throttler.cjs.map +1 -1
  12. package/dist/cjs/async-throttler.d.cts +75 -25
  13. package/dist/cjs/debouncer.cjs +45 -23
  14. package/dist/cjs/debouncer.cjs.map +1 -1
  15. package/dist/cjs/debouncer.d.cts +27 -12
  16. package/dist/cjs/index.cjs +2 -0
  17. package/dist/cjs/index.cjs.map +1 -1
  18. package/dist/cjs/index.d.cts +2 -0
  19. package/dist/cjs/queuer.cjs +161 -101
  20. package/dist/cjs/queuer.cjs.map +1 -1
  21. package/dist/cjs/queuer.d.cts +80 -38
  22. package/dist/cjs/rate-limiter.cjs +52 -44
  23. package/dist/cjs/rate-limiter.cjs.map +1 -1
  24. package/dist/cjs/rate-limiter.d.cts +38 -46
  25. package/dist/cjs/throttler.cjs +57 -44
  26. package/dist/cjs/throttler.cjs.map +1 -1
  27. package/dist/cjs/throttler.d.cts +35 -23
  28. package/dist/cjs/types.d.cts +8 -0
  29. package/dist/cjs/utils.cjs +13 -0
  30. package/dist/cjs/utils.cjs.map +1 -0
  31. package/dist/cjs/utils.d.cts +1 -0
  32. package/dist/esm/async-debouncer.d.ts +66 -25
  33. package/dist/esm/async-debouncer.js +112 -63
  34. package/dist/esm/async-debouncer.js.map +1 -1
  35. package/dist/esm/async-queuer.d.ts +91 -49
  36. package/dist/esm/async-queuer.js +198 -124
  37. package/dist/esm/async-queuer.js.map +1 -1
  38. package/dist/esm/async-rate-limiter.d.ts +55 -28
  39. package/dist/esm/async-rate-limiter.js +83 -55
  40. package/dist/esm/async-rate-limiter.js.map +1 -1
  41. package/dist/esm/async-throttler.d.ts +75 -25
  42. package/dist/esm/async-throttler.js +121 -70
  43. package/dist/esm/async-throttler.js.map +1 -1
  44. package/dist/esm/debouncer.d.ts +27 -12
  45. package/dist/esm/debouncer.js +45 -23
  46. package/dist/esm/debouncer.js.map +1 -1
  47. package/dist/esm/index.d.ts +2 -0
  48. package/dist/esm/index.js +2 -0
  49. package/dist/esm/index.js.map +1 -1
  50. package/dist/esm/queuer.d.ts +80 -38
  51. package/dist/esm/queuer.js +161 -101
  52. package/dist/esm/queuer.js.map +1 -1
  53. package/dist/esm/rate-limiter.d.ts +38 -46
  54. package/dist/esm/rate-limiter.js +52 -44
  55. package/dist/esm/rate-limiter.js.map +1 -1
  56. package/dist/esm/throttler.d.ts +35 -23
  57. package/dist/esm/throttler.js +57 -44
  58. package/dist/esm/throttler.js.map +1 -1
  59. package/dist/esm/types.d.ts +8 -0
  60. package/dist/esm/utils.d.ts +1 -0
  61. package/dist/esm/utils.js +13 -0
  62. package/dist/esm/utils.js.map +1 -0
  63. package/package.json +8 -1
  64. package/src/async-debouncer.ts +157 -88
  65. package/src/async-queuer.ts +266 -148
  66. package/src/async-rate-limiter.ts +123 -83
  67. package/src/async-throttler.ts +173 -89
  68. package/src/debouncer.ts +71 -42
  69. package/src/index.ts +2 -0
  70. package/src/queuer.ts +219 -114
  71. package/src/rate-limiter.ts +74 -88
  72. package/src/throttler.ts +83 -65
  73. package/src/types.ts +9 -0
  74. package/src/utils.ts +13 -0
@@ -10,6 +10,16 @@ export interface AsyncQueuerOptions<TValue> {
10
10
  * Maximum number of concurrent tasks to process
11
11
  */
12
12
  concurrency?: number
13
+ /**
14
+ * Maximum time in milliseconds that an item can stay in the queue
15
+ * If not provided, items will never expire
16
+ */
17
+ expirationDuration?: number
18
+ /**
19
+ * Function to determine if an item has expired
20
+ * If provided, this overrides the expirationDuration behavior
21
+ */
22
+ getIsExpired?: (item: () => Promise<TValue>, addedAt: number) => boolean
13
23
  /**
14
24
  * Default position to get items from during processing
15
25
  * @default 'front'
@@ -36,12 +46,24 @@ export interface AsyncQueuerOptions<TValue> {
36
46
  item: () => Promise<TValue>,
37
47
  queuer: AsyncQueuer<TValue>,
38
48
  ) => void
49
+ /**
50
+ * Callback fired whenever the queuer's running state changes
51
+ */
52
+ onIsRunningChange?: (queuer: AsyncQueuer<TValue>) => void
39
53
  /**
40
54
  * Callback fired whenever an item is added or removed from the queuer
41
55
  */
42
- onUpdate?: (queuer: AsyncQueuer<TValue>) => void
56
+ onItemsChange?: (queuer: AsyncQueuer<TValue>) => void
57
+ /**
58
+ * Callback fired whenever an item is rejected from being added to the queuer
59
+ */
60
+ onReject?: (item: () => Promise<TValue>, queuer: AsyncQueuer<TValue>) => void
43
61
  /**
44
- * Whether the queuer should start processing tasks immediately
62
+ * Callback fired whenever an item expires in the queuer
63
+ */
64
+ onExpire?: (item: () => Promise<TValue>, queuer: AsyncQueuer<TValue>) => void
65
+ /**
66
+ * Whether the queuer should start processing tasks immediately or not.
45
67
  */
46
68
  started?: boolean
47
69
  /**
@@ -53,64 +75,73 @@ export interface AsyncQueuerOptions<TValue> {
53
75
  const defaultOptions: Required<AsyncQueuerOptions<any>> = {
54
76
  addItemsTo: 'back',
55
77
  concurrency: 1,
78
+ expirationDuration: Infinity,
79
+ getIsExpired: () => false,
56
80
  getItemsFrom: 'front',
57
- getPriority: (item) => (item as any).priority ?? 0,
81
+ getPriority: (item) => (item as any)?.priority ?? 0,
58
82
  initialItems: [],
59
83
  maxSize: Infinity,
60
84
  onGetNextItem: () => {},
61
- onUpdate: () => {},
62
- started: false,
85
+ onIsRunningChange: () => {},
86
+ onItemsChange: () => {},
87
+ onReject: () => {},
88
+ onExpire: () => {},
89
+ started: true,
63
90
  wait: 0,
64
91
  }
65
92
 
66
93
  /**
67
- * A flexible asynchronous queuer that processes tasks with configurable concurrency control.
94
+ * A flexible asynchronous queue that processes tasks with configurable concurrency control.
68
95
  *
69
96
  * Features:
70
- * - Priority queuer support via getPriority option
97
+ * - Priority queue support via getPriority option
71
98
  * - Configurable concurrency limit
72
99
  * - Task success/error/completion callbacks
73
- * - FIFO (First In First Out) or LIFO (Last In First Out) queuer behavior
100
+ * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior
74
101
  * - Pause/resume task processing
75
102
  * - Task cancellation
103
+ * - Item expiration to clear stale items from the queue
76
104
  *
77
105
  * Tasks are processed concurrently up to the configured concurrency limit. When a task completes,
78
106
  * the next pending task is processed if below the concurrency limit.
79
107
  *
80
108
  * @example
81
109
  * ```ts
82
- * const queuer = new AsyncQueuer<string>({ concurrency: 2 });
110
+ * const asyncQueuer = new AsyncQueuer<string>({ concurrency: 2 });
83
111
  *
84
- * queuer.addItem(async () => {
112
+ * asyncQueuer.addItem(async () => {
85
113
  * return 'Hello';
86
114
  * });
87
115
  *
88
- * queuer.start();
116
+ * asyncQueuer.start();
89
117
  *
90
- * queuer.onSuccess((result) => {
118
+ * asyncQueuer.onSuccess((result) => {
91
119
  * console.log(result); // 'Hello'
92
120
  * });
93
121
  * ```
94
122
  */
95
123
  export class AsyncQueuer<TValue> {
96
- protected options: Required<AsyncQueuerOptions<TValue>>
97
- private items: Array<() => Promise<TValue>> = []
98
- private activeItems: Set<() => Promise<TValue>> = new Set()
99
- private onSuccessCallbacks: Array<(result: TValue) => void> = []
100
- private onErrorCallbacks: Array<(error: Error) => void> = []
101
- private onSettledCallbacks: Array<(result: TValue | Error) => void> = []
102
- private running: boolean
103
- private pendingTick = false
104
- private executionCount = 0
124
+ private _options: Required<AsyncQueuerOptions<TValue>>
125
+ private _activeItems: Set<() => Promise<TValue>> = new Set()
126
+ private _executionCount = 0
127
+ private _rejectionCount = 0
128
+ private _expirationCount = 0
129
+ private _items: Array<() => Promise<TValue>> = []
130
+ private _itemTimestamps: Array<number> = []
131
+ private _onErrorCallbacks: Array<(error: Error) => void> = []
132
+ private _onSettledCallbacks: Array<(result: TValue | Error) => void> = []
133
+ private _onSuccessCallbacks: Array<(result: TValue) => void> = []
134
+ private _pendingTick = false
135
+ private _running: boolean
105
136
 
106
137
  constructor(initialOptions: AsyncQueuerOptions<TValue> = defaultOptions) {
107
- this.options = { ...defaultOptions, ...initialOptions }
108
- this.running = this.options.started
138
+ this._options = { ...defaultOptions, ...initialOptions }
139
+ this._running = this._options.started
109
140
 
110
- for (let i = 0; i < this.options.initialItems.length; i++) {
111
- const item = this.options.initialItems[i]!
112
- const isLast = i === this.options.initialItems.length - 1
113
- this.addItem(item, this.options.addItemsTo, isLast)
141
+ for (let i = 0; i < this._options.initialItems.length; i++) {
142
+ const item = this._options.initialItems[i]!
143
+ const isLast = i === this._options.initialItems.length - 1
144
+ this.addItem(item, this._options.addItemsTo, isLast)
114
145
  }
115
146
  }
116
147
 
@@ -118,32 +149,39 @@ export class AsyncQueuer<TValue> {
118
149
  * Updates the queuer options
119
150
  * Returns the new options state
120
151
  */
121
- setOptions(
122
- newOptions: Partial<AsyncQueuerOptions<TValue>>,
123
- ): AsyncQueuerOptions<TValue> {
124
- this.options = { ...this.options, ...newOptions }
125
- return this.options
152
+ setOptions(newOptions: Partial<AsyncQueuerOptions<TValue>>): void {
153
+ this._options = { ...this._options, ...newOptions }
154
+ }
155
+
156
+ /**
157
+ * Returns the current queuer options
158
+ */
159
+ getOptions(): Required<AsyncQueuerOptions<TValue>> {
160
+ return this._options
126
161
  }
127
162
 
128
163
  /**
129
164
  * Processes items in the queuer
130
165
  */
131
- protected tick() {
132
- if (!this.running) {
133
- this.pendingTick = false
166
+ private tick() {
167
+ if (!this._running) {
168
+ this._pendingTick = false
134
169
  return
135
170
  }
136
171
 
172
+ // Check for expired items
173
+ this.checkExpiredItems()
174
+
137
175
  while (
138
- this.activeItems.size < this.options.concurrency &&
139
- !this.isEmpty()
176
+ this._activeItems.size < this._options.concurrency &&
177
+ !this.getIsEmpty()
140
178
  ) {
141
179
  const nextFn = this.getNextItem()
142
180
  if (!nextFn) {
143
181
  break
144
182
  }
145
-
146
- this.activeItems.add(nextFn)
183
+ this._activeItems.add(nextFn)
184
+ this._options.onItemsChange(this)
147
185
  ;(async () => {
148
186
  let success = false
149
187
  let res!: TValue
@@ -155,19 +193,19 @@ export class AsyncQueuer<TValue> {
155
193
  } catch (e) {
156
194
  error = e as Error
157
195
  } finally {
158
- this.options.onUpdate(this)
196
+ this._activeItems.delete(nextFn)
197
+ this._options.onItemsChange(this)
159
198
  }
160
199
 
161
- this.activeItems.delete(nextFn)
162
200
  if (success) {
163
- this.onSuccessCallbacks.forEach((cb) => cb(res))
201
+ this._onSuccessCallbacks.forEach((cb) => cb(res))
164
202
  } else {
165
- this.onErrorCallbacks.forEach((cb) => cb(error!))
203
+ this._onErrorCallbacks.forEach((cb) => cb(error!))
166
204
  }
167
- this.onSettledCallbacks.forEach((cb) => cb(success ? res : error!))
205
+ this._onSettledCallbacks.forEach((cb) => cb(success ? res : error!))
168
206
 
169
- if (this.options.wait > 0) {
170
- setTimeout(() => this.tick(), this.options.wait)
207
+ if (this._options.wait > 0) {
208
+ setTimeout(() => this.tick(), this._options.wait)
171
209
  return
172
210
  }
173
211
 
@@ -175,7 +213,109 @@ export class AsyncQueuer<TValue> {
175
213
  })()
176
214
  }
177
215
 
178
- this.pendingTick = false
216
+ this._pendingTick = false
217
+ }
218
+
219
+ /**
220
+ * Checks for and removes expired items from the queuer
221
+ */
222
+ private checkExpiredItems() {
223
+ if (
224
+ this._options.expirationDuration === Infinity &&
225
+ this._options.getIsExpired === defaultOptions.getIsExpired
226
+ )
227
+ return
228
+
229
+ const now = Date.now()
230
+ const expiredIndices: Array<number> = []
231
+
232
+ // Find indices of expired items
233
+ for (let i = 0; i < this._items.length; i++) {
234
+ const timestamp = this._itemTimestamps[i]
235
+ if (timestamp === undefined) continue
236
+
237
+ const item = this._items[i]
238
+ if (item === undefined) continue
239
+
240
+ const isExpired =
241
+ this._options.getIsExpired !== defaultOptions.getIsExpired
242
+ ? this._options.getIsExpired(item, timestamp)
243
+ : now - timestamp > this._options.expirationDuration
244
+
245
+ if (isExpired) {
246
+ expiredIndices.push(i)
247
+ }
248
+ }
249
+
250
+ // Remove expired items from back to front to maintain indices
251
+ for (let i = expiredIndices.length - 1; i >= 0; i--) {
252
+ const index = expiredIndices[i]
253
+ if (index === undefined) continue
254
+
255
+ const expiredItem = this._items[index]
256
+ if (expiredItem === undefined) continue
257
+
258
+ this._items.splice(index, 1)
259
+ this._itemTimestamps.splice(index, 1)
260
+ this._expirationCount++
261
+ this._options.onExpire(expiredItem, this)
262
+ }
263
+
264
+ if (expiredIndices.length > 0) {
265
+ this._options.onItemsChange(this)
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Starts the queuer and processes items
271
+ */
272
+ start(): Promise<void> {
273
+ this._running = true
274
+ if (!this._pendingTick && !this.getIsEmpty()) {
275
+ this._pendingTick = true
276
+ this.tick()
277
+ }
278
+ this._options.onIsRunningChange(this)
279
+
280
+ return new Promise<void>((resolve) => {
281
+ const checkIdle = () => {
282
+ if (this.getIsIdle()) {
283
+ resolve()
284
+ } else {
285
+ setTimeout(checkIdle, 100)
286
+ }
287
+ }
288
+ checkIdle()
289
+ })
290
+ }
291
+
292
+ /**
293
+ * Stops the queuer from processing items
294
+ */
295
+ stop(): void {
296
+ this._running = false
297
+ this._pendingTick = false
298
+ this._options.onIsRunningChange(this)
299
+ }
300
+
301
+ /**
302
+ * Removes all items from the queuer
303
+ */
304
+ clear(): void {
305
+ this._items = []
306
+ this._options.onItemsChange(this)
307
+ }
308
+
309
+ /**
310
+ * Resets the queuer to its initial state
311
+ */
312
+ reset(withInitialItems?: boolean): void {
313
+ this.clear()
314
+ this._executionCount = 0
315
+ if (withInitialItems) {
316
+ this._items = [...this._options.initialItems]
317
+ }
318
+ this._running = this._options.started
179
319
  }
180
320
 
181
321
  /**
@@ -183,10 +323,12 @@ export class AsyncQueuer<TValue> {
183
323
  */
184
324
  addItem(
185
325
  fn: (() => Promise<TValue>) & { priority?: number },
186
- position: QueuePosition = this.options.addItemsTo,
326
+ position: QueuePosition = this._options.addItemsTo,
187
327
  runOnUpdate: boolean = true,
188
328
  ): Promise<TValue> {
189
- if (this.isFull()) {
329
+ if (this.getIsFull()) {
330
+ this._rejectionCount++
331
+ this._options.onReject(fn, this)
190
332
  return Promise.reject(new Error('Queuer is full'))
191
333
  }
192
334
 
@@ -207,40 +349,44 @@ export class AsyncQueuer<TValue> {
207
349
 
208
350
  // Get priority either from the function or from getPriority option
209
351
  const priority =
210
- this.options.getPriority !== defaultOptions.getPriority
211
- ? this.options.getPriority(task)
352
+ this._options.getPriority !== defaultOptions.getPriority
353
+ ? this._options.getPriority(task)
212
354
  : task.priority
213
355
 
214
356
  if (priority !== undefined) {
215
357
  // Insert based on priority
216
- const insertIndex = this.items.findIndex((existing) => {
358
+ const insertIndex = this._items.findIndex((existing) => {
217
359
  const existingPriority =
218
- this.options.getPriority !== defaultOptions.getPriority
219
- ? this.options.getPriority(existing)
360
+ this._options.getPriority !== defaultOptions.getPriority
361
+ ? this._options.getPriority(existing)
220
362
  : (existing as any).priority
221
363
  return existingPriority > priority
222
364
  })
223
365
 
224
366
  if (insertIndex === -1) {
225
- this.items.push(task)
367
+ this._items.push(task)
368
+ this._itemTimestamps.push(Date.now())
226
369
  } else {
227
- this.items.splice(insertIndex, 0, task)
370
+ this._items.splice(insertIndex, 0, task)
371
+ this._itemTimestamps.splice(insertIndex, 0, Date.now())
228
372
  }
229
373
  } else {
230
374
  // Default FIFO/LIFO behavior
231
375
  if (position === 'front') {
232
- this.items.unshift(task)
376
+ this._items.unshift(task)
377
+ this._itemTimestamps.unshift(Date.now())
233
378
  } else {
234
- this.items.push(task)
379
+ this._items.push(task)
380
+ this._itemTimestamps.push(Date.now())
235
381
  }
236
382
  }
237
383
 
238
384
  if (runOnUpdate) {
239
- this.options.onUpdate(this)
385
+ this._options.onItemsChange(this)
240
386
  }
241
387
 
242
- if (this.running && !this.pendingTick) {
243
- this.pendingTick = true
388
+ if (this._running && !this._pendingTick) {
389
+ this._pendingTick = true
244
390
  this.tick()
245
391
  }
246
392
  })
@@ -250,20 +396,22 @@ export class AsyncQueuer<TValue> {
250
396
  * Removes and returns an item from the queuer
251
397
  */
252
398
  getNextItem(
253
- position: QueuePosition = this.options.getItemsFrom,
399
+ position: QueuePosition = this._options.getItemsFrom,
254
400
  ): (() => Promise<TValue>) | undefined {
255
401
  let item: (() => Promise<TValue>) | undefined
256
402
 
257
403
  if (position === 'front') {
258
- item = this.items.shift()
404
+ item = this._items.shift()
405
+ this._itemTimestamps.shift()
259
406
  } else {
260
- item = this.items.pop()
407
+ item = this._items.pop()
408
+ this._itemTimestamps.pop()
261
409
  }
262
410
 
263
411
  if (item !== undefined) {
264
- this.executionCount++
265
- this.options.onUpdate(this)
266
- this.options.onGetNextItem(item, this)
412
+ this._executionCount++
413
+ this._options.onItemsChange(this)
414
+ this._options.onGetNextItem(item, this)
267
415
  }
268
416
  return item
269
417
  }
@@ -271,89 +419,94 @@ export class AsyncQueuer<TValue> {
271
419
  /**
272
420
  * Returns an item without removing it
273
421
  */
274
- peek(position: QueuePosition = 'front'): (() => Promise<TValue>) | undefined {
422
+ getPeek(
423
+ position: QueuePosition = 'front',
424
+ ): (() => Promise<TValue>) | undefined {
275
425
  if (position === 'front') {
276
- return this.items[0]
426
+ return this._items[0]
277
427
  }
278
- return this.items[this.items.length - 1]
428
+ return this._items[this._items.length - 1]
279
429
  }
280
430
 
281
431
  /**
282
432
  * Returns true if the queuer is empty
283
433
  */
284
- isEmpty(): boolean {
285
- return this.items.length === 0
434
+ getIsEmpty(): boolean {
435
+ return this._items.length === 0
286
436
  }
287
437
 
288
438
  /**
289
439
  * Returns true if the queuer is full
290
440
  */
291
- isFull(): boolean {
292
- return this.items.length >= this.options.maxSize
441
+ getIsFull(): boolean {
442
+ return this._items.length >= this._options.maxSize
293
443
  }
294
444
 
295
445
  /**
296
446
  * Returns the current size of the queuer
297
447
  */
298
- size(): number {
299
- return this.items.length
448
+ getSize(): number {
449
+ return this._items.length
300
450
  }
301
451
 
302
452
  /**
303
- * Removes all items from the queuer
453
+ * Returns a copy of all items in the queuer
304
454
  */
305
- clear(): void {
306
- this.items = []
307
- this.options.onUpdate(this)
455
+ getAllItems(): Array<() => Promise<TValue>> {
456
+ return [...this.getActiveItems(), ...this.getPendingItems()]
308
457
  }
309
458
 
310
459
  /**
311
- * Resets the queuer to its initial state
460
+ * Returns the active items
312
461
  */
313
- reset(withInitialItems?: boolean): void {
314
- this.clear()
315
- this.executionCount = 0
316
- if (withInitialItems) {
317
- this.items = [...this.options.initialItems]
318
- }
319
- this.running = this.options.started
462
+ getActiveItems(): Array<() => Promise<TValue>> {
463
+ return Array.from(this._activeItems)
320
464
  }
321
465
 
322
466
  /**
323
- * Returns a copy of all items in the queuer
467
+ * Returns the pending items
324
468
  */
325
- getAllItems(): Array<() => Promise<TValue>> {
326
- return [...this.items]
469
+ getPendingItems(): Array<() => Promise<TValue>> {
470
+ return [...this._items]
327
471
  }
328
472
 
329
473
  /**
330
474
  * Returns the number of items that have been removed from the queuer
331
475
  */
332
476
  getExecutionCount(): number {
333
- return this.executionCount
477
+ return this._executionCount
334
478
  }
335
479
 
336
480
  /**
337
- * Returns the active items
481
+ * Returns the number of items that have been rejected from the queuer
338
482
  */
339
- getActiveItems(): Array<() => Promise<TValue>> {
340
- return Array.from(this.activeItems)
483
+ getRejectionCount(): number {
484
+ return this._rejectionCount
341
485
  }
342
486
 
343
487
  /**
344
- * Returns the pending items
488
+ * Returns true if the queuer is running
345
489
  */
346
- getPendingItems(): Array<() => Promise<TValue>> {
347
- return this.getAllItems()
490
+ getIsRunning(): boolean {
491
+ return this._running
492
+ }
493
+
494
+ /**
495
+ * Returns true if the queuer is running but has no items to process
496
+ */
497
+ getIsIdle(): boolean {
498
+ return this._running && this.getIsEmpty() && this._activeItems.size === 0
348
499
  }
349
500
 
350
501
  /**
351
502
  * Adds a callback to be called when a task succeeds
352
503
  */
353
504
  onSuccess(cb: (result: TValue) => void) {
354
- this.onSuccessCallbacks.push(cb)
505
+ this._onSuccessCallbacks.push(cb)
355
506
  return () => {
356
- this.onSuccessCallbacks = this.onSuccessCallbacks.filter((d) => d !== cb)
507
+ this._onSuccessCallbacks = this._onSuccessCallbacks.filter(
508
+ (d) => d !== cb,
509
+ )
357
510
  }
358
511
  }
359
512
 
@@ -361,9 +514,9 @@ export class AsyncQueuer<TValue> {
361
514
  * Adds a callback to be called when a task errors
362
515
  */
363
516
  onError(cb: (error: Error) => void) {
364
- this.onErrorCallbacks.push(cb)
517
+ this._onErrorCallbacks.push(cb)
365
518
  return () => {
366
- this.onErrorCallbacks = this.onErrorCallbacks.filter((d) => d !== cb)
519
+ this._onErrorCallbacks = this._onErrorCallbacks.filter((d) => d !== cb)
367
520
  }
368
521
  }
369
522
 
@@ -371,56 +524,19 @@ export class AsyncQueuer<TValue> {
371
524
  * Adds a callback to be called when a task is settled
372
525
  */
373
526
  onSettled(cb: (result: TValue | Error) => void) {
374
- this.onSettledCallbacks.push(cb)
527
+ this._onSettledCallbacks.push(cb)
375
528
  return () => {
376
- this.onSettledCallbacks = this.onSettledCallbacks.filter((d) => d !== cb)
377
- }
378
- }
379
-
380
- /**
381
- * Starts the queuer and processes items
382
- */
383
- start(): Promise<void> {
384
- this.running = true
385
- if (!this.pendingTick && !this.isEmpty()) {
386
- this.pendingTick = true
387
- this.tick()
529
+ this._onSettledCallbacks = this._onSettledCallbacks.filter(
530
+ (d) => d !== cb,
531
+ )
388
532
  }
389
- this.options.onUpdate(this)
390
-
391
- return new Promise<void>((resolve) => {
392
- const checkIdle = () => {
393
- if (this.isIdle()) {
394
- resolve()
395
- } else {
396
- setTimeout(checkIdle, 100)
397
- }
398
- }
399
- checkIdle()
400
- })
401
- }
402
-
403
- /**
404
- * Stops the queuer from processing items
405
- */
406
- stop(): void {
407
- this.running = false
408
- this.pendingTick = false
409
- this.options.onUpdate(this)
410
- }
411
-
412
- /**
413
- * Returns true if the queuer is running
414
- */
415
- isRunning(): boolean {
416
- return this.running
417
533
  }
418
534
 
419
535
  /**
420
- * Returns true if the queuer is running but has no items to process
536
+ * Returns the number of items that have expired from the queuer
421
537
  */
422
- isIdle(): boolean {
423
- return this.running && this.isEmpty() && this.activeItems.size === 0
538
+ getExpirationCount(): number {
539
+ return this._expirationCount
424
540
  }
425
541
  }
426
542
 
@@ -441,7 +557,9 @@ export class AsyncQueuer<TValue> {
441
557
  * @param options - Configuration options for the AsyncQueuer
442
558
  * @returns A bound addItem function that can be used to add tasks to the queuer
443
559
  */
444
- export function asyncQueue<TValue>(options: AsyncQueuerOptions<TValue> = {}) {
445
- const queuer = new AsyncQueuer<TValue>({ ...options, started: true })
560
+ export function asyncQueue<TValue>(
561
+ options: Omit<AsyncQueuerOptions<TValue>, 'started'> = {},
562
+ ) {
563
+ const queuer = new AsyncQueuer<TValue>(options)
446
564
  return queuer.addItem.bind(queuer)
447
565
  }