@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
package/src/queuer.ts DELETED
@@ -1,740 +0,0 @@
1
- import { Store } from '@tanstack/store'
2
- import { parseFunctionOrValue } from './utils'
3
- import { emitChange, pacerEventClient } from './event-client'
4
-
5
- export interface QueuerState<TValue> {
6
- /**
7
- * Number of times addItem has been called (for reduction calculations)
8
- */
9
- addItemCount: number
10
- /**
11
- * Number of items that have been processed by the queuer
12
- */
13
- executionCount: number
14
- /**
15
- * Number of items that have been removed from the queue due to expiration
16
- */
17
- expirationCount: number
18
- /**
19
- * Whether the queuer has no items to process (items array is empty)
20
- */
21
- isEmpty: boolean
22
- /**
23
- * Whether the queuer has reached its maximum capacity
24
- */
25
- isFull: boolean
26
- /**
27
- * Whether the queuer is not currently processing any items
28
- */
29
- isIdle: boolean
30
- /**
31
- * Whether the queuer is active and will process items automatically
32
- */
33
- isRunning: boolean
34
- /**
35
- * Array of items currently waiting to be processed
36
- */
37
- items: Array<TValue>
38
- /**
39
- * Timestamps when items were added to the queue for expiration tracking
40
- */
41
- itemTimestamps: Array<number>
42
- /**
43
- * Whether the queuer has a pending timeout for processing the next item
44
- */
45
- pendingTick: boolean
46
- /**
47
- * Number of items that have been rejected from being added to the queue
48
- */
49
- rejectionCount: number
50
- /**
51
- * Number of items currently in the queue
52
- */
53
- size: number
54
- /**
55
- * Current processing status - 'idle' when not processing, 'running' when active, 'stopped' when paused
56
- */
57
- status: 'idle' | 'running' | 'stopped'
58
- }
59
-
60
- function getDefaultQueuerState<TValue>(): QueuerState<TValue> {
61
- return {
62
- executionCount: 0,
63
- expirationCount: 0,
64
- isEmpty: true,
65
- isFull: false,
66
- isIdle: true,
67
- isRunning: true,
68
- itemTimestamps: [],
69
- items: [],
70
- pendingTick: false,
71
- rejectionCount: 0,
72
- size: 0,
73
- status: 'idle',
74
- addItemCount: 0,
75
- }
76
- }
77
-
78
- /**
79
- * Options for configuring a Queuer instance.
80
- *
81
- * These options control queue behavior, item expiration, callbacks, and more.
82
- */
83
- export interface QueuerOptions<TValue> {
84
- /**
85
- * Default position to add items to the queuer
86
- * @default 'back'
87
- */
88
- addItemsTo?: QueuePosition
89
- /**
90
- * Maximum time in milliseconds that an item can stay in the queue
91
- * If not provided, items will never expire
92
- */
93
- expirationDuration?: number
94
- /**
95
- * Function to determine if an item has expired
96
- * If provided, this overrides the expirationDuration behavior
97
- */
98
- getIsExpired?: (item: TValue, addedAt: number) => boolean
99
- /**
100
- * Default position to get items from during processing
101
- * @default 'front'
102
- */
103
- getItemsFrom?: QueuePosition
104
- /**
105
- * Function to determine priority of items in the queuer
106
- * Higher priority items will be processed first
107
- */
108
- getPriority?: (item: TValue) => number
109
- /**
110
- * Initial items to populate the queuer with
111
- */
112
- initialItems?: Array<TValue>
113
- /**
114
- * Initial state for the queuer
115
- */
116
- initialState?: Partial<QueuerState<TValue>>
117
- /**
118
- * Optional key to identify this queuer instance.
119
- * If provided, the queuer will be identified by this key in the devtools and PacerProvider if applicable.
120
- */
121
- key?: string
122
- /**
123
- * Maximum number of items allowed in the queuer
124
- */
125
- maxSize?: number
126
- /**
127
- * Callback fired whenever an item is removed from the queuer
128
- */
129
- onExecute?: (item: TValue, queuer: Queuer<TValue>) => void
130
- /**
131
- * Callback fired whenever an item expires in the queuer
132
- */
133
- onExpire?: (item: TValue, queuer: Queuer<TValue>) => void
134
- /**
135
- * Callback fired whenever an item is added or removed from the queuer
136
- */
137
- onItemsChange?: (queuer: Queuer<TValue>) => void
138
- /**
139
- * Callback fired whenever an item is rejected from being added to the queuer
140
- */
141
- onReject?: (item: TValue, queuer: Queuer<TValue>) => void
142
- /**
143
- * Whether the queuer should start processing tasks immediately
144
- */
145
- started?: boolean
146
- /**
147
- * Time in milliseconds to wait between processing items.
148
- * Can be a number or a function that returns a number.
149
- * @default 0
150
- */
151
- wait?: number | ((queuer: Queuer<TValue>) => number)
152
- }
153
-
154
- /**
155
- * Utility function for sharing common `QueuerOptions` options between different `Queuer` instances.
156
- */
157
- export function queuerOptions<
158
- TValue = any,
159
- TOptions extends Partial<QueuerOptions<TValue>> = Partial<
160
- QueuerOptions<TValue>
161
- >,
162
- >(options: TOptions): TOptions {
163
- return options
164
- }
165
-
166
- const defaultOptions: Omit<
167
- Required<QueuerOptions<any>>,
168
- | 'initialState'
169
- | 'onExecute'
170
- | 'onIsRunningChange'
171
- | 'onItemsChange'
172
- | 'onReject'
173
- | 'onExpire'
174
- | 'key'
175
- > = {
176
- addItemsTo: 'back',
177
- getItemsFrom: 'front',
178
- getPriority: (item) => item?.priority ?? 0,
179
- getIsExpired: () => false,
180
- expirationDuration: Infinity,
181
- initialItems: [],
182
- maxSize: Infinity,
183
- started: true,
184
- wait: 0,
185
- }
186
-
187
- /**
188
- * Position type for addItem and getNextItem operations.
189
- *
190
- * - 'front': Operate on the front of the queue (FIFO)
191
- * - 'back': Operate on the back of the queue (LIFO)
192
- */
193
- export type QueuePosition = 'front' | 'back'
194
-
195
- /**
196
- * A flexible queue that processes items with configurable wait times, expiration, and priority.
197
- *
198
- * This synchronous version is lighter weight and often all you need - upgrade to AsyncQueuer when you need promises, retry support, abort capabilities, concurrent execution, or advanced error handling.
199
- *
200
- * Features:
201
- * - Automatic or manual processing of items
202
- * - FIFO (First In First Out), LIFO (Last In First Out), or double-ended queue behavior
203
- * - Priority-based ordering when getPriority is provided
204
- * - Item expiration and removal of stale items
205
- * - Callbacks for queue state changes, execution, rejection, and expiration
206
- *
207
- * Running behavior:
208
- * - `start()`: Begins automatically processing items in the queue (defaults to isRunning)
209
- * - `stop()`: Pauses processing but maintains queue state
210
- * - `wait`: Configurable delay between processing items
211
- * - `onItemsChange`/`onExecute`: Callbacks for monitoring queue state
212
- *
213
- * Manual processing is also supported when automatic processing is disabled:
214
- * - `execute()`: Processes the next item using the provided function
215
- * - `getNextItem()`: Removes and returns the next item without processing
216
- *
217
- * Queue behavior defaults to FIFO:
218
- * - `addItem(item)`: Adds to the back of the queue
219
- * - Items processed from the front of the queue
220
- *
221
- * Priority queue:
222
- * - Provide a `getPriority` function; higher values are processed first
223
- *
224
- * Stack (LIFO):
225
- * - `addItem(item, 'back')`: Adds to the back
226
- * - `getNextItem('back')`: Removes from the back
227
- *
228
- * Double-ended queue:
229
- * - `addItem(item, position)`: Adds to specified position ('front'/'back')
230
- * - `getNextItem(position)`: Removes from specified position
231
- *
232
- * Item expiration:
233
- * - `expirationDuration`: Maximum time items can stay in the queue
234
- * - `getIsExpired`: Function to override default expiration
235
- * - `onExpire`: Callback for expired items
236
- *
237
- * State Management:
238
- * - Uses TanStack Store for reactive state management
239
- * - Use `initialState` to provide initial state values when creating the queuer
240
- * - Use `onExecute` callback to react to item execution and implement custom logic
241
- * - Use `onItemsChange` callback to react to items being added or removed from the queue
242
- * - Use `onExpire` callback to react to items expiring and implement custom logic
243
- * - Use `onReject` callback to react to items being rejected when the queue is full
244
- * - The state includes execution count, expiration count, rejection count, and isRunning status
245
- * - State can be accessed via `queuer.store.state` when using the class directly
246
- * - When using framework adapters (React/Solid), state is accessed from `queuer.state`
247
- *
248
- * Example usage:
249
- * ```ts
250
- * // Auto-processing queue with wait time
251
- * const autoQueue = new Queuer<number>((n) => console.log(n), {
252
- * started: true, // Begin processing immediately
253
- * wait: 1000, // Wait 1s between items
254
- * onExecute: (item, queuer) => console.log(`Processed ${item}`)
255
- * });
256
- * autoQueue.addItem(1); // Will process after 1s
257
- * autoQueue.addItem(2); // Will process 1s after first item
258
- *
259
- * // Manual processing queue
260
- * const manualQueue = new Queuer<number>((n) => console.log(n), {
261
- * started: false
262
- * });
263
- * manualQueue.addItem(1); // [1]
264
- * manualQueue.addItem(2); // [1, 2]
265
- * manualQueue.execute(); // logs 1, queue is [2]
266
- * manualQueue.getNextItem(); // returns 2, queue is empty
267
- * ```
268
- */
269
- export class Queuer<TValue> {
270
- readonly store: Store<Readonly<QueuerState<TValue>>> = new Store(
271
- getDefaultQueuerState<TValue>(),
272
- )
273
- key: string | undefined
274
- options: QueuerOptions<TValue>
275
- #timeoutId: ReturnType<typeof setTimeout> | null = null
276
-
277
- constructor(
278
- public fn: (item: TValue) => void,
279
- initialOptions: QueuerOptions<TValue> = {},
280
- ) {
281
- this.key = initialOptions.key
282
- this.options = {
283
- ...defaultOptions,
284
- ...initialOptions,
285
- }
286
- const isInitiallyRunning =
287
- this.options.initialState?.isRunning ?? this.options.started ?? true
288
- this.#setState({
289
- ...this.options.initialState,
290
- isRunning: isInitiallyRunning,
291
- })
292
-
293
- if (this.options.initialState?.items) {
294
- if (this.store.state.isRunning) {
295
- this.#tick()
296
- }
297
- } else {
298
- for (let i = 0; i < (this.options.initialItems?.length ?? 0); i++) {
299
- const item = this.options.initialItems![i]!
300
- const isLast = i === (this.options.initialItems?.length ?? 0) - 1
301
- this.addItem(item, this.options.addItemsTo ?? 'back', isLast)
302
- }
303
- }
304
-
305
- if (this.key) {
306
- pacerEventClient.on('d-Queuer', (event) => {
307
- if (event.payload.key !== this.key) return
308
- this.#setState(
309
- event.payload.store.state as Partial<QueuerState<TValue>>,
310
- )
311
- this.setOptions(event.payload.options as Partial<QueuerOptions<TValue>>)
312
- })
313
- }
314
- }
315
-
316
- /**
317
- * Updates the queuer options. New options are merged with existing options.
318
- */
319
- setOptions = (newOptions: Partial<QueuerOptions<TValue>>): void => {
320
- this.options = { ...this.options, ...newOptions }
321
- }
322
-
323
- #setState = (newState: Partial<QueuerState<TValue>>): void => {
324
- this.store.setState((state) => {
325
- const combinedState = {
326
- ...state,
327
- ...newState,
328
- }
329
-
330
- const { items, isRunning } = combinedState
331
-
332
- const size = items.length
333
- const isFull = size >= (this.options.maxSize ?? Infinity)
334
- const isEmpty = size === 0
335
- const isIdle = isRunning && isEmpty
336
-
337
- const status = isIdle ? 'idle' : isRunning ? 'running' : 'stopped'
338
-
339
- return {
340
- ...combinedState,
341
- isEmpty,
342
- isFull,
343
- isIdle,
344
- size,
345
- status,
346
- }
347
- })
348
- emitChange('Queuer', this)
349
- }
350
-
351
- /**
352
- * Returns the current wait time (in milliseconds) between processing items.
353
- * If a function is provided, it is called with the queuer instance.
354
- */
355
- #getWait = (): number => {
356
- return parseFunctionOrValue(this.options.wait ?? 0, this)
357
- }
358
-
359
- /**
360
- * Processes items in the queue up to the wait interval. Internal use only.
361
- */
362
- #tick = () => {
363
- if (!this.store.state.isRunning) {
364
- this.#setState({ pendingTick: false })
365
- return
366
- }
367
-
368
- this.#setState({ pendingTick: true })
369
-
370
- // Check for expired items
371
- this.#checkExpiredItems()
372
-
373
- while (this.store.state.items.length > 0) {
374
- const nextItem = this.execute(this.options.getItemsFrom ?? 'front')
375
- if (nextItem === undefined) {
376
- break
377
- }
378
-
379
- const wait = this.#getWait()
380
- if (wait > 0) {
381
- // Use setTimeout to wait before processing next item
382
- this.#timeoutId = setTimeout(() => this.#tick(), wait)
383
- return
384
- }
385
-
386
- this.#tick()
387
- }
388
- this.#setState({ pendingTick: false })
389
- }
390
-
391
- /**
392
- * Adds an item to the queue. If the queue is full, the item is rejected and onReject is called.
393
- * Items can be inserted based on priority or at the front/back depending on configuration.
394
- *
395
- * Returns true if the item was added, false if the queue is full.
396
- *
397
- * Example usage:
398
- * ```ts
399
- * queuer.addItem('task');
400
- * queuer.addItem('task2', 'front');
401
- * ```
402
- */
403
- addItem = (
404
- item: TValue,
405
- position: QueuePosition = this.options.addItemsTo ?? 'back',
406
- runOnItemsChange: boolean = true,
407
- ): boolean => {
408
- this.#setState({
409
- addItemCount: this.store.state.addItemCount + 1,
410
- })
411
-
412
- if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {
413
- this.#setState({
414
- rejectionCount: this.store.state.rejectionCount + 1,
415
- })
416
- this.options.onReject?.(item, this)
417
- return false
418
- }
419
-
420
- // Get priority either from the function or from getPriority option
421
- const priority =
422
- this.options.getPriority !== defaultOptions.getPriority
423
- ? this.options.getPriority!(item)
424
- : (item as any).priority
425
-
426
- const items = this.store.state.items
427
- const itemTimestamps = this.store.state.itemTimestamps
428
-
429
- if (priority !== undefined) {
430
- // Insert based on priority - higher priority items go to front
431
- const insertIndex = items.findIndex((existing) => {
432
- const existingPriority: number =
433
- this.options.getPriority !== defaultOptions.getPriority
434
- ? this.options.getPriority!(existing)
435
- : (existing as any).priority
436
- return existingPriority < priority
437
- })
438
-
439
- if (insertIndex === -1) {
440
- items.push(item)
441
- itemTimestamps.push(Date.now())
442
- } else {
443
- items.splice(insertIndex, 0, item)
444
- itemTimestamps.splice(insertIndex, 0, Date.now())
445
- }
446
- } else {
447
- if (position === 'front') {
448
- // Default FIFO/LIFO behavior
449
- items.unshift(item)
450
- itemTimestamps.unshift(Date.now())
451
- } else {
452
- // LIFO
453
- items.push(item)
454
- itemTimestamps.push(Date.now())
455
- }
456
- }
457
-
458
- this.#setState({
459
- items,
460
- itemTimestamps,
461
- })
462
-
463
- if (runOnItemsChange) {
464
- this.options.onItemsChange?.(this)
465
- }
466
-
467
- if (this.store.state.isRunning && !this.store.state.pendingTick) {
468
- this.#setState({ pendingTick: true })
469
- this.#tick()
470
- }
471
-
472
- return true
473
- }
474
-
475
- /**
476
- * Removes and returns the next item from the queue without executing the function.
477
- * Use for manual queue management. Normally, use execute() to process items.
478
- *
479
- * Example usage:
480
- * ```ts
481
- * // FIFO
482
- * queuer.getNextItem();
483
- * // LIFO
484
- * queuer.getNextItem('back');
485
- * ```
486
- */
487
- getNextItem = (
488
- position: QueuePosition = this.options.getItemsFrom ?? 'front',
489
- ): TValue | undefined => {
490
- const { items, itemTimestamps } = this.store.state
491
- let item: TValue | undefined
492
-
493
- // When priority function is provided, always get from front (highest priority)
494
- // Priority takes precedence over FIFO/LIFO behavior
495
- if (
496
- this.options.getPriority !== defaultOptions.getPriority ||
497
- position === 'front'
498
- ) {
499
- item = items[0]
500
- if (item !== undefined) {
501
- this.#setState({
502
- items: items.slice(1),
503
- itemTimestamps: itemTimestamps.slice(1),
504
- })
505
- }
506
- } else {
507
- item = items[items.length - 1]
508
- if (item !== undefined) {
509
- this.#setState({
510
- items: items.slice(0, -1),
511
- itemTimestamps: itemTimestamps.slice(0, -1),
512
- })
513
- }
514
- }
515
-
516
- if (item !== undefined) {
517
- this.options.onItemsChange?.(this)
518
- }
519
-
520
- return item
521
- }
522
-
523
- #getAllItems = (): Array<TValue> => {
524
- const items = this.peekAllItems()
525
- this.clear()
526
- return items
527
- }
528
-
529
- /**
530
- * Removes and returns the next item from the queue and processes it using the provided function.
531
- *
532
- * Example usage:
533
- * ```ts
534
- * queuer.execute();
535
- * // LIFO
536
- * queuer.execute('back');
537
- * ```
538
- */
539
- execute = (position?: QueuePosition): TValue | undefined => {
540
- const item = this.getNextItem(position)
541
- if (item !== undefined) {
542
- this.fn(item)
543
- this.#setState({
544
- executionCount: this.store.state.executionCount + 1,
545
- })
546
- this.options.onExecute?.(item, this)
547
- }
548
- return item
549
- }
550
-
551
- /**
552
- * Processes a specified number of items to execute immediately with no wait time
553
- * If no numberOfItems is provided, all items will be processed
554
- */
555
- flush = (
556
- numberOfItems: number = this.store.state.items.length,
557
- position?: QueuePosition,
558
- ): void => {
559
- this.#clearTimeout() // clear any pending timeout
560
- for (let i = 0; i < numberOfItems; i++) {
561
- this.execute(position)
562
- }
563
- this.#tick()
564
- }
565
-
566
- /**
567
- * Processes all items in the queue as a batch using the provided function as an argument
568
- * The queue is cleared after processing
569
- */
570
- flushAsBatch = (batchFunction: (items: Array<TValue>) => void): void => {
571
- const items = this.#getAllItems()
572
- this.clear()
573
- batchFunction(items)
574
- }
575
-
576
- /**
577
- * Checks for expired items in the queue and removes them. Calls onExpire for each expired item.
578
- * Internal use only.
579
- */
580
- #checkExpiredItems = (): void => {
581
- if (
582
- (this.options.expirationDuration ?? Infinity) === Infinity &&
583
- this.options.getIsExpired === defaultOptions.getIsExpired
584
- ) {
585
- return
586
- }
587
-
588
- const now = Date.now()
589
- const expiredIndices: Array<number> = []
590
-
591
- // Find indices of expired items
592
- for (let i = 0; i < this.store.state.items.length; i++) {
593
- const timestamp = this.store.state.itemTimestamps[i]
594
- if (timestamp === undefined) continue
595
-
596
- const item = this.store.state.items[i]
597
- if (item === undefined) continue
598
-
599
- const isExpired =
600
- this.options.getIsExpired !== defaultOptions.getIsExpired
601
- ? this.options.getIsExpired!(item, timestamp)
602
- : now - timestamp > (this.options.expirationDuration ?? Infinity)
603
-
604
- if (isExpired) {
605
- expiredIndices.push(i)
606
- }
607
- }
608
-
609
- // Remove expired items from back to front to maintain indices
610
- for (let i = expiredIndices.length - 1; i >= 0; i--) {
611
- const index = expiredIndices[i]
612
- if (index === undefined) continue
613
-
614
- const expiredItem = this.store.state.items[index]
615
- if (expiredItem === undefined) continue
616
-
617
- const newItems = [...this.store.state.items]
618
- const newTimestamps = [...this.store.state.itemTimestamps]
619
- newItems.splice(index, 1)
620
- newTimestamps.splice(index, 1)
621
- this.#setState({
622
- items: newItems,
623
- itemTimestamps: newTimestamps,
624
- expirationCount: this.store.state.expirationCount + 1,
625
- })
626
- this.options.onExpire?.(expiredItem, this)
627
- }
628
-
629
- if (expiredIndices.length > 0) {
630
- this.options.onItemsChange?.(this)
631
- }
632
- }
633
-
634
- /**
635
- * Returns the next item in the queue without removing it.
636
- *
637
- * Example usage:
638
- * ```ts
639
- * queuer.peekNextItem(); // front
640
- * queuer.peekNextItem('back'); // back
641
- * ```
642
- */
643
- peekNextItem = (position: QueuePosition = 'front'): TValue | undefined => {
644
- if (position === 'front') {
645
- return this.store.state.items[0]
646
- }
647
- return this.store.state.items[this.store.state.items.length - 1]
648
- }
649
-
650
- /**
651
- * Returns a copy of all items in the queue.
652
- */
653
- peekAllItems = (): Array<TValue> => {
654
- return [...this.store.state.items]
655
- }
656
-
657
- /**
658
- * Starts processing items in the queue. If already isRunning, does nothing.
659
- */
660
- start = () => {
661
- this.#setState({ isRunning: true })
662
- if (!this.store.state.pendingTick && this.store.state.items.length > 0) {
663
- this.#tick()
664
- }
665
- }
666
-
667
- /**
668
- * Stops processing items in the queue. Does not clear the queue.
669
- */
670
- stop = () => {
671
- this.#clearTimeout()
672
- this.#setState({ isRunning: false, pendingTick: false })
673
- }
674
-
675
- #clearTimeout = (): void => {
676
- if (this.#timeoutId) {
677
- clearTimeout(this.#timeoutId)
678
- this.#timeoutId = null
679
- }
680
- }
681
-
682
- /**
683
- * Removes all pending items from the queue. Does not affect items being processed.
684
- */
685
- clear = (): void => {
686
- this.#setState({ items: [], itemTimestamps: [] })
687
- this.options.onItemsChange?.(this)
688
- }
689
-
690
- /**
691
- * Resets the queuer state to its default values
692
- */
693
- reset = (): void => {
694
- this.#setState(getDefaultQueuerState<TValue>())
695
- this.options.onItemsChange?.(this)
696
- }
697
- }
698
-
699
- /**
700
- * Creates a queue that processes items immediately upon addition.
701
- * Items are processed sequentially in FIFO order by default.
702
- *
703
- * This synchronous version is lighter weight and often all you need - upgrade to asyncQueue when you need promises, retry support, abort capabilities, concurrent execution, or advanced error handling.
704
- *
705
- * State Management:
706
- * - Uses TanStack Store for reactive state management
707
- * - Use `initialState` to provide initial state values when creating the queuer
708
- * - Use `onExecute` callback to react to item execution and implement custom logic
709
- * - Use `onItemsChange` callback to react to items being added or removed from the queue
710
- * - Use `onExpire` callback to react to items expiring and implement custom logic
711
- * - Use `onReject` callback to react to items being rejected when the queue is full
712
- * - The state includes execution count, expiration count, rejection count, and isRunning status
713
- * - State can be accessed via the underlying Queuer instance's `store.state` property
714
- * - When using framework adapters (React/Solid), state is accessed from the hook's state property
715
- *
716
- * Example usage:
717
- * ```ts
718
- * // Basic sequential processing
719
- * const processItems = queue<number>((n) => console.log(n), {
720
- * wait: 1000,
721
- * onItemsChange: (queuer) => console.log(queuer.peekAllItems())
722
- * });
723
- * processItems(1); // Logs: 1
724
- * processItems(2); // Logs: 2 after 1 completes
725
- *
726
- * // Priority queue
727
- * const processPriority = queue<number>((n) => console.log(n), {
728
- * getPriority: n => n // Higher numbers processed first
729
- * });
730
- * processPriority(1);
731
- * processPriority(3); // Processed before 1
732
- * ```
733
- */
734
- export function queue<TValue>(
735
- fn: (item: TValue) => void,
736
- initialOptions: QueuerOptions<TValue>,
737
- ) {
738
- const queuer = new Queuer<TValue>(fn, initialOptions)
739
- return queuer.addItem
740
- }