@tanstack/pacer 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +4 -4
  2. package/dist/async-batcher.d.ts +11 -13
  3. package/dist/async-batcher.js +185 -131
  4. package/dist/async-debouncer.d.ts +6 -8
  5. package/dist/async-debouncer.js +186 -142
  6. package/dist/async-queuer.d.ts +11 -13
  7. package/dist/async-queuer.js +383 -290
  8. package/dist/async-rate-limiter.d.ts +6 -8
  9. package/dist/async-rate-limiter.js +197 -143
  10. package/dist/async-retryer.d.ts +6 -8
  11. package/dist/async-retryer.js +209 -179
  12. package/dist/async-throttler.d.ts +6 -8
  13. package/dist/async-throttler.js +214 -157
  14. package/dist/batcher.d.ts +5 -7
  15. package/dist/batcher.js +107 -87
  16. package/dist/debouncer.d.ts +6 -8
  17. package/dist/debouncer.js +99 -86
  18. package/dist/event-client.d.ts +8 -10
  19. package/dist/event-client.js +1 -2
  20. package/dist/queuer.d.ts +7 -9
  21. package/dist/queuer.js +277 -212
  22. package/dist/rate-limiter.d.ts +6 -8
  23. package/dist/rate-limiter.js +127 -109
  24. package/dist/throttler.d.ts +6 -8
  25. package/dist/throttler.js +127 -90
  26. package/dist/types.d.ts +4 -6
  27. package/dist/utils.d.ts +3 -5
  28. package/dist/utils.js +1 -2
  29. package/package.json +21 -68
  30. package/dist/async-batcher.cjs +0 -337
  31. package/dist/async-batcher.cjs.map +0 -1
  32. package/dist/async-batcher.d.cts +0 -343
  33. package/dist/async-batcher.js.map +0 -1
  34. package/dist/async-debouncer.cjs +0 -327
  35. package/dist/async-debouncer.cjs.map +0 -1
  36. package/dist/async-debouncer.d.cts +0 -299
  37. package/dist/async-debouncer.js.map +0 -1
  38. package/dist/async-queuer.cjs +0 -516
  39. package/dist/async-queuer.cjs.map +0 -1
  40. package/dist/async-queuer.d.cts +0 -440
  41. package/dist/async-queuer.js.map +0 -1
  42. package/dist/async-rate-limiter.cjs +0 -370
  43. package/dist/async-rate-limiter.cjs.map +0 -1
  44. package/dist/async-rate-limiter.d.cts +0 -356
  45. package/dist/async-rate-limiter.js.map +0 -1
  46. package/dist/async-retryer.cjs +0 -365
  47. package/dist/async-retryer.cjs.map +0 -1
  48. package/dist/async-retryer.d.cts +0 -321
  49. package/dist/async-retryer.js.map +0 -1
  50. package/dist/async-throttler.cjs +0 -344
  51. package/dist/async-throttler.cjs.map +0 -1
  52. package/dist/async-throttler.d.cts +0 -319
  53. package/dist/async-throttler.js.map +0 -1
  54. package/dist/batcher.cjs +0 -200
  55. package/dist/batcher.cjs.map +0 -1
  56. package/dist/batcher.d.cts +0 -179
  57. package/dist/batcher.js.map +0 -1
  58. package/dist/debouncer.cjs +0 -203
  59. package/dist/debouncer.cjs.map +0 -1
  60. package/dist/debouncer.d.cts +0 -166
  61. package/dist/debouncer.js.map +0 -1
  62. package/dist/event-client.cjs +0 -64
  63. package/dist/event-client.cjs.map +0 -1
  64. package/dist/event-client.d.cts +0 -65
  65. package/dist/event-client.js.map +0 -1
  66. package/dist/index.cjs +0 -52
  67. package/dist/index.d.cts +0 -15
  68. package/dist/queuer.cjs +0 -406
  69. package/dist/queuer.cjs.map +0 -1
  70. package/dist/queuer.d.cts +0 -345
  71. package/dist/queuer.js.map +0 -1
  72. package/dist/rate-limiter.cjs +0 -263
  73. package/dist/rate-limiter.cjs.map +0 -1
  74. package/dist/rate-limiter.d.cts +0 -214
  75. package/dist/rate-limiter.js.map +0 -1
  76. package/dist/throttler.cjs +0 -215
  77. package/dist/throttler.cjs.map +0 -1
  78. package/dist/throttler.d.cts +0 -206
  79. package/dist/throttler.js.map +0 -1
  80. package/dist/types.cjs +0 -0
  81. package/dist/types.d.cts +0 -13
  82. package/dist/utils.cjs +0 -13
  83. package/dist/utils.cjs.map +0 -1
  84. package/dist/utils.d.cts +0 -7
  85. package/dist/utils.js.map +0 -1
  86. package/src/async-batcher.ts +0 -594
  87. package/src/async-debouncer.ts +0 -566
  88. package/src/async-queuer.ts +0 -988
  89. package/src/async-rate-limiter.ts +0 -648
  90. package/src/async-retryer.ts +0 -673
  91. package/src/async-throttler.ts +0 -634
  92. package/src/batcher.ts +0 -329
  93. package/src/debouncer.ts +0 -334
  94. package/src/event-client.ts +0 -129
  95. package/src/index.ts +0 -24
  96. package/src/queuer.ts +0 -751
  97. package/src/rate-limiter.ts +0 -429
  98. package/src/throttler.ts +0 -380
  99. package/src/types.ts +0 -12
  100. package/src/utils.ts +0 -12
@@ -1,594 +0,0 @@
1
- import { Store } from '@tanstack/store'
2
- import { AsyncRetryer } from './async-retryer'
3
- import { parseFunctionOrValue } from './utils'
4
- import { emitChange, pacerEventClient } from './event-client'
5
- import type { AsyncRetryerOptions } from './async-retryer'
6
- import type { OptionalKeys } from './types'
7
-
8
- export interface AsyncBatcherState<TValue> {
9
- /**
10
- * Number of batch executions that have resulted in errors
11
- */
12
- errorCount: number
13
- /**
14
- * Number of batch executions that have been executed
15
- */
16
- executeCount: number
17
- /**
18
- * Array of items that failed during batch processing
19
- */
20
- failedItems: Array<TValue>
21
- /**
22
- * Whether the batcher has no items to process (items array is empty)
23
- */
24
- isEmpty: boolean
25
- /**
26
- * Whether a batch is currently being processed asynchronously
27
- */
28
- isExecuting: boolean
29
- /**
30
- * Whether the batcher is waiting for the timeout to trigger batch processing
31
- */
32
- isPending: boolean
33
- /**
34
- * Array of items currently queued for batch processing
35
- */
36
- items: Array<TValue>
37
- /**
38
- * The result from the most recent batch execution
39
- */
40
- lastResult: any
41
- /**
42
- * Number of batch executions that have completed (either successfully or with errors)
43
- */
44
- settleCount: number
45
- /**
46
- * Number of items currently in the batch queue
47
- */
48
- size: number
49
- /**
50
- * Current processing status - 'idle' when not processing, 'pending' when waiting for timeout, 'executing' when processing, 'populated' when items are present, but no wait is configured
51
- */
52
- status: 'idle' | 'pending' | 'executing' | 'populated'
53
- /**
54
- * Number of batch executions that have completed successfully
55
- */
56
- successCount: number
57
- /**
58
- * Total number of items that have failed processing across all batches
59
- */
60
- totalItemsFailed: number
61
- /**
62
- * Total number of items that have been processed across all batches
63
- */
64
- totalItemsProcessed: number
65
- }
66
-
67
- function getDefaultAsyncBatcherState<TValue>(): AsyncBatcherState<TValue> {
68
- return {
69
- errorCount: 0,
70
- executeCount: 0,
71
- failedItems: [],
72
- isEmpty: true,
73
- isExecuting: false,
74
- isPending: false,
75
- items: [],
76
- lastResult: undefined,
77
- settleCount: 0,
78
- size: 0,
79
- status: 'idle',
80
- successCount: 0,
81
- totalItemsProcessed: 0,
82
- totalItemsFailed: 0,
83
- }
84
- }
85
-
86
- /**
87
- * Options for configuring an AsyncBatcher instance
88
- */
89
- export interface AsyncBatcherOptions<TValue> {
90
- /**
91
- * Options for configuring the underlying async retryer
92
- */
93
- asyncRetryerOptions?: AsyncRetryerOptions<
94
- (items: Array<TValue>) => Promise<any>
95
- >
96
- /**
97
- * Custom function to determine if a batch should be processed
98
- * Return true to process the batch immediately
99
- */
100
- getShouldExecute?: (
101
- items: Array<TValue>,
102
- batcher: AsyncBatcher<TValue>,
103
- ) => boolean
104
- /**
105
- * Initial state for the async batcher
106
- */
107
- initialState?: Partial<AsyncBatcherState<TValue>>
108
- /**
109
- * Optional key to identify this async batcher instance.
110
- * If provided, the async batcher will be identified by this key in the devtools and PacerProvider if applicable.
111
- */
112
- key?: string
113
- /**
114
- * Maximum number of items in a batch
115
- * @default Infinity
116
- */
117
- maxSize?: number
118
- /**
119
- * Optional error handler for when the batch function throws.
120
- * If provided, the handler will be called with the error, the batch of items that failed, and batcher instance.
121
- * This can be used alongside throwOnError - the handler will be called before any error is thrown.
122
- */
123
- onError?: (
124
- error: Error,
125
- batch: Array<TValue>,
126
- batcher: AsyncBatcher<TValue>,
127
- ) => void
128
- /**
129
- * Callback fired after items are added to the batcher
130
- */
131
- onItemsChange?: (batcher: AsyncBatcher<TValue>) => void
132
- /**
133
- * Optional callback to call when a batch is settled (completed or failed)
134
- */
135
- onSettled?: (batch: Array<TValue>, batcher: AsyncBatcher<TValue>) => void
136
- /**
137
- * Optional callback to call when a batch succeeds
138
- */
139
- onSuccess?: (
140
- result: any,
141
- batch: Array<TValue>,
142
- batcher: AsyncBatcher<TValue>,
143
- ) => void
144
- /**
145
- * Whether the batcher should start processing immediately
146
- * @default true
147
- */
148
- started?: boolean
149
- /**
150
- * Whether to throw errors when they occur.
151
- * Defaults to true if no onError handler is provided, false if an onError handler is provided.
152
- * Can be explicitly set to override these defaults.
153
- */
154
- throwOnError?: boolean
155
- /**
156
- * Maximum time in milliseconds to wait before processing a batch.
157
- * If the wait duration has elapsed, the batch will be processed.
158
- * If not provided, the batch will not be triggered by a timeout.
159
- * @default Infinity
160
- */
161
- wait?: number | ((asyncBatcher: AsyncBatcher<TValue>) => number)
162
- }
163
-
164
- /**
165
- * Utility function for sharing common `AsyncBatcherOptions` options between different `AsyncBatcher` instances.
166
- *
167
- */
168
- export function asyncBatcherOptions<
169
- TValue = any,
170
- TOptions extends Partial<AsyncBatcherOptions<TValue>> = Partial<
171
- AsyncBatcherOptions<TValue>
172
- >,
173
- >(options: TOptions): TOptions {
174
- return options
175
- }
176
-
177
- type AsyncBatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<
178
- Required<AsyncBatcherOptions<TValue>>,
179
- | 'initialState'
180
- | 'onError'
181
- | 'onItemsChange'
182
- | 'onSettled'
183
- | 'onSuccess'
184
- | 'key'
185
- >
186
-
187
- const defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks<any> = {
188
- asyncRetryerOptions: {
189
- maxAttempts: 1,
190
- },
191
- getShouldExecute: () => false,
192
- maxSize: Infinity,
193
- started: true,
194
- throwOnError: true,
195
- wait: Infinity,
196
- }
197
-
198
- /**
199
- * A class that collects items and processes them in batches asynchronously.
200
- *
201
- * Async vs Sync Versions:
202
- * The async version provides advanced features over the sync Batcher:
203
- * - Returns promises that can be awaited for batch results
204
- * - Built-in retry support via AsyncRetryer integration
205
- * - Abort support to cancel in-flight batch executions
206
- * - Cancel support to prevent pending batches from starting
207
- * - Comprehensive error handling with onError callbacks and throwOnError control
208
- * - Detailed execution tracking (success/error/settle counts)
209
- *
210
- * The sync Batcher is lighter weight and simpler when you don't need async features,
211
- * return values, or execution control.
212
- *
213
- * What is Batching?
214
- * Batching is a technique for grouping multiple operations together to be processed as a single unit.
215
- *
216
- * The AsyncBatcher provides a flexible way to implement async batching with configurable:
217
- * - Maximum batch size (number of items per batch)
218
- * - Time-based batching (process after X milliseconds)
219
- * - Custom batch processing logic via getShouldExecute
220
- * - Event callbacks for monitoring batch operations
221
- * - Error handling for failed batch operations
222
- *
223
- * Error Handling:
224
- * - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher instance
225
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
226
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
227
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
228
- * - The error state can be checked using the AsyncBatcher instance
229
- *
230
- * State Management:
231
- * - Uses TanStack Store for reactive state management
232
- * - Use `initialState` to provide initial state values when creating the async batcher
233
- * - Use `onSuccess` callback to react to successful batch execution and implement custom logic
234
- * - Use `onError` callback to react to batch execution errors and implement custom error handling
235
- * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic
236
- * - Use `onExecute` callback to react to batch execution and implement custom logic
237
- * - Use `onItemsChange` callback to react to items being added or removed from the batcher
238
- * - The state includes total items processed, success/error counts, and execution status
239
- * - State can be accessed via `asyncBatcher.store.state` when using the class directly
240
- * - When using framework adapters (React/Solid), state is accessed from `asyncBatcher.state`
241
- *
242
- * @example
243
- * ```ts
244
- * const batcher = new AsyncBatcher<number>(
245
- * async (items) => {
246
- * const result = await processItems(items);
247
- * console.log('Processing batch:', items);
248
- * return result;
249
- * },
250
- * {
251
- * maxSize: 5,
252
- * wait: 2000,
253
- * onSuccess: (result) => console.log('Batch succeeded:', result),
254
- * onError: (error) => console.error('Batch failed:', error)
255
- * }
256
- * );
257
- *
258
- * batcher.addItem(1);
259
- * batcher.addItem(2);
260
- * // After 2 seconds or when 5 items are added, whichever comes first,
261
- * // the batch will be processed and the result will be available
262
- * // batcher.execute() // manually trigger a batch
263
- * ```
264
- */
265
- export class AsyncBatcher<TValue> {
266
- readonly store: Store<Readonly<AsyncBatcherState<TValue>>> = new Store(
267
- getDefaultAsyncBatcherState<TValue>(),
268
- )
269
- key: string | undefined
270
- options: AsyncBatcherOptionsWithOptionalCallbacks<TValue>
271
- asyncRetryers = new Map<
272
- number,
273
- AsyncRetryer<(items: Array<TValue>) => Promise<any>>
274
- >()
275
- #timeoutId: ReturnType<typeof setTimeout> | null = null
276
-
277
- constructor(
278
- public fn: (items: Array<TValue>) => Promise<any>,
279
- initialOptions: AsyncBatcherOptions<TValue>,
280
- ) {
281
- this.key = initialOptions.key
282
- this.options = {
283
- ...defaultOptions,
284
- ...initialOptions,
285
- throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
286
- }
287
- this.#setState(this.options.initialState ?? {})
288
-
289
- if (this.key) {
290
- pacerEventClient.on('d-AsyncBatcher', (event) => {
291
- if (event.payload.key !== this.key) return
292
- this.#setState(
293
- event.payload.store.state as Partial<AsyncBatcherState<TValue>>,
294
- )
295
- this.setOptions(
296
- event.payload.options as Partial<AsyncBatcherOptions<TValue>>,
297
- )
298
- })
299
- }
300
- }
301
-
302
- /**
303
- * Updates the async batcher options
304
- */
305
- setOptions = (newOptions: Partial<AsyncBatcherOptions<TValue>>): void => {
306
- this.options = { ...this.options, ...newOptions }
307
- }
308
-
309
- #setState = (newState: Partial<AsyncBatcherState<TValue>>): void => {
310
- this.store.setState((state) => {
311
- const combinedState = {
312
- ...state,
313
- ...newState,
314
- }
315
- const { isExecuting, isPending, items } = combinedState
316
- const size = items.length
317
- const isEmpty = size === 0
318
- return {
319
- ...combinedState,
320
- isEmpty,
321
- size,
322
- status: isExecuting
323
- ? 'executing'
324
- : isPending
325
- ? 'pending'
326
- : isEmpty
327
- ? 'idle'
328
- : 'populated',
329
- }
330
- })
331
- emitChange('AsyncBatcher', this)
332
- }
333
-
334
- #getWait = (): number => {
335
- return parseFunctionOrValue(this.options.wait, this)
336
- }
337
-
338
- /**
339
- * Adds an item to the async batcher
340
- * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed
341
- *
342
- * @returns The result from the batch function, or undefined if an error occurred and was handled by onError
343
- *
344
- * @throws The error from the batch function if no onError handler is configured or throwOnError is true
345
- */
346
- addItem = async (item: TValue): Promise<any> => {
347
- this.#setState({
348
- items: [...this.store.state.items, item],
349
- isPending: this.options.wait !== Infinity,
350
- })
351
- this.options.onItemsChange?.(this)
352
-
353
- const shouldProcess =
354
- this.store.state.items.length >= this.options.maxSize ||
355
- this.options.getShouldExecute(this.store.state.items, this)
356
-
357
- if (shouldProcess) {
358
- return await this.#execute()
359
- } else if (this.options.wait !== Infinity) {
360
- this.#clearTimeout() // clear any pending timeout to replace it with a new one
361
- this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait())
362
- await new Promise((resolve) => setTimeout(resolve, this.#getWait()))
363
- }
364
- }
365
-
366
- /**
367
- * Processes the current batch of items asynchronously.
368
- * This method will automatically be triggered if the batcher is running and any of these conditions are met:
369
- * - The number of items reaches maxSize
370
- * - The wait duration has elapsed
371
- * - The getShouldExecute function returns true upon adding an item
372
- *
373
- * You can also call this method manually to process the current batch at any time.
374
- *
375
- * @returns A promise that resolves with the result of the batch function, or undefined if an error occurred and was handled by onError
376
- * @throws The error from the batch function if no onError handler is configured or throwOnError is true
377
- */
378
- #execute = async (): Promise<any> => {
379
- if (this.store.state.items.length === 0) {
380
- return undefined
381
- }
382
-
383
- const currentExecuteCount = this.store.state.executeCount + 1
384
- const batch = this.peekAllItems() // copy of the items to be processed (to prevent race conditions)
385
- this.clear() // Clear items before processing to prevent race conditions
386
- this.options.onItemsChange?.(this)
387
-
388
- this.#setState({ isExecuting: true, executeCount: currentExecuteCount })
389
-
390
- try {
391
- const currentAsyncRetryer = new AsyncRetryer(
392
- this.fn,
393
- this.options.asyncRetryerOptions,
394
- )
395
- this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer)
396
- const result = await currentAsyncRetryer.execute(batch) // EXECUTE
397
- this.#setState({
398
- totalItemsProcessed:
399
- this.store.state.totalItemsProcessed + batch.length,
400
- lastResult: result,
401
- successCount: this.store.state.successCount + 1,
402
- })
403
- this.options.onSuccess?.(result, batch, this)
404
- return result
405
- } catch (error) {
406
- this.#setState({
407
- errorCount: this.store.state.errorCount + 1,
408
- failedItems: [...this.store.state.failedItems, ...batch],
409
- totalItemsFailed: this.store.state.totalItemsFailed + batch.length,
410
- })
411
- this.options.onError?.(error as Error, batch, this)
412
- if (this.options.throwOnError) {
413
- throw error
414
- }
415
- return undefined
416
- } finally {
417
- this.asyncRetryers.delete(currentExecuteCount) // dispose retryer
418
- this.#setState({
419
- isExecuting: false,
420
- settleCount: this.store.state.settleCount + 1,
421
- })
422
- this.options.onSettled?.(batch, this)
423
- }
424
- }
425
-
426
- /**
427
- * Processes the current batch of items immediately
428
- */
429
- flush = async (): Promise<any> => {
430
- this.#clearTimeout() // clear any pending timeout
431
- return await this.#execute()
432
- }
433
-
434
- /**
435
- * Returns a copy of all items in the async batcher
436
- */
437
- peekAllItems = (): Array<TValue> => {
438
- return [...this.store.state.items]
439
- }
440
-
441
- peekFailedItems = (): Array<TValue> => {
442
- return [...this.store.state.failedItems]
443
- }
444
-
445
- #clearTimeout = (): void => {
446
- if (this.#timeoutId) {
447
- clearTimeout(this.#timeoutId)
448
- this.#timeoutId = null
449
- }
450
- }
451
-
452
- /**
453
- * Removes all items from the async batcher
454
- */
455
- clear = (): void => {
456
- this.#setState({ items: [], failedItems: [], isPending: false })
457
- }
458
-
459
- /**
460
- * Returns the AbortSignal for a specific execution.
461
- * If no executeCount is provided, returns the signal for the most recent execution.
462
- * Returns null if no execution is found or not currently executing.
463
- *
464
- * @param executeCount - Optional specific execution to get signal for
465
- * @example
466
- * ```typescript
467
- * const batcher = new AsyncBatcher(
468
- * async (items: string[]) => {
469
- * const signal = batcher.getAbortSignal()
470
- * if (signal) {
471
- * const response = await fetch('/api/batch', {
472
- * method: 'POST',
473
- * body: JSON.stringify(items),
474
- * signal
475
- * })
476
- * return response.json()
477
- * }
478
- * },
479
- * { maxSize: 10, wait: 100 }
480
- * )
481
- * ```
482
- */
483
- getAbortSignal = (executeCount?: number): AbortSignal | null => {
484
- const count = executeCount ?? this.store.state.executeCount
485
- const retryer = this.asyncRetryers.get(count)
486
- return retryer?.getAbortSignal() ?? null
487
- }
488
-
489
- /**
490
- * Aborts all ongoing executions with the internal abort controllers.
491
- * Does NOT cancel any pending execution that have not started yet.
492
- * Does NOT clear out the items.
493
- */
494
- abort = (): void => {
495
- this.asyncRetryers.forEach((retryer) => retryer.abort())
496
- this.asyncRetryers.clear()
497
- this.#setState({
498
- isExecuting: false,
499
- })
500
- }
501
-
502
- /**
503
- * Cancels any pending execution that have not started yet.
504
- * Does NOT abort any execution already in progress.
505
- * Does NOT clear out the items.
506
- */
507
- cancel = (): void => {
508
- this.#clearTimeout()
509
- this.#setState({
510
- isPending: false,
511
- })
512
- }
513
-
514
- /**
515
- * Resets the async batcher state to its default values
516
- */
517
- reset = (): void => {
518
- this.#setState(getDefaultAsyncBatcherState<TValue>())
519
- this.options.onItemsChange?.(this)
520
- this.asyncRetryers.forEach((retryer) => retryer.reset())
521
- }
522
- }
523
-
524
- /**
525
- * Creates an async batcher that processes items in batches.
526
- *
527
- * Async vs Sync Versions:
528
- * The async version provides advanced features over the sync batch function:
529
- * - Returns promises that can be awaited for batch results
530
- * - Built-in retry support via AsyncRetryer integration
531
- * - Abort support to cancel in-flight batch executions
532
- * - Cancel support to prevent pending batches from starting
533
- * - Comprehensive error handling with onError callbacks and throwOnError control
534
- * - Detailed execution tracking (success/error/settle counts)
535
- *
536
- * The sync batch function is lighter weight and simpler when you don't need async features,
537
- * return values, or execution control.
538
- *
539
- * What is Batching?
540
- * Batching is a technique for grouping multiple operations together to be processed as a single unit.
541
- *
542
- * Configuration Options:
543
- * - `maxSize`: Maximum number of items per batch (default: Infinity)
544
- * - `wait`: Time to wait before processing batch (default: Infinity)
545
- * - `getShouldExecute`: Custom logic to trigger batch processing
546
- * - `asyncRetryerOptions`: Configure retry behavior for batch executions
547
- * - `started`: Whether to start processing immediately (default: true)
548
- *
549
- * Error Handling:
550
- * - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher instance
551
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
552
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
553
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
554
- * - The error state can be checked using the underlying AsyncBatcher instance
555
- *
556
- * State Management:
557
- * - Uses TanStack Store for reactive state management
558
- * - Use `initialState` to provide initial state values when creating the async batcher
559
- * - Use `onSuccess` callback to react to successful batch execution and implement custom logic
560
- * - Use `onError` callback to react to batch execution errors and implement custom error handling
561
- * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic
562
- * - Use `onItemsChange` callback to react to items being added or removed from the batcher
563
- * - The state includes total items processed, success/error counts, and execution status
564
- * - State can be accessed via the underlying AsyncBatcher instance's `store.state` property
565
- * - When using framework adapters (React/Solid), state is accessed from the hook's state property
566
- *
567
- * @example
568
- * ```ts
569
- * const batchItems = asyncBatch<number>(
570
- * async (items) => {
571
- * const result = await processApiCall(items);
572
- * console.log('Processing:', items);
573
- * return result;
574
- * },
575
- * {
576
- * maxSize: 3,
577
- * wait: 1000,
578
- * onSuccess: (result) => console.log('Batch succeeded:', result),
579
- * onError: (error) => console.error('Batch failed:', error)
580
- * }
581
- * );
582
- *
583
- * batchItems(1);
584
- * batchItems(2);
585
- * batchItems(3); // Triggers batch processing
586
- * ```
587
- */
588
- export function asyncBatch<TValue>(
589
- fn: (items: Array<TValue>) => Promise<any>,
590
- options: AsyncBatcherOptions<TValue>,
591
- ) {
592
- const batcher = new AsyncBatcher<TValue>(fn, options)
593
- return batcher.addItem
594
- }