@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,566 +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 { AnyAsyncFunction, OptionalKeys } from './types'
7
-
8
- export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
9
- /**
10
- * Whether the debouncer can execute on the leading edge of the timeout
11
- */
12
- canLeadingExecute: boolean
13
- /**
14
- * Number of function executions that have resulted in errors
15
- */
16
- errorCount: number
17
- /**
18
- * Whether the debounced function is currently executing asynchronously
19
- */
20
- isExecuting: boolean
21
- /**
22
- * Whether the debouncer is waiting for the timeout to trigger execution
23
- */
24
- isPending: boolean
25
- /**
26
- * The arguments from the most recent call to maybeExecute
27
- */
28
- lastArgs: Parameters<TFn> | undefined
29
- /**
30
- * The result from the most recent successful function execution
31
- */
32
- lastResult: Awaited<ReturnType<TFn>> | undefined
33
- /**
34
- * Number of times maybeExecute has been called (for reduction calculations)
35
- */
36
- maybeExecuteCount: number
37
- /**
38
- * Number of function executions that have completed (either successfully or with errors)
39
- */
40
- settleCount: number
41
- /**
42
- * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed
43
- */
44
- status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled'
45
- /**
46
- * Number of function executions that have completed successfully
47
- */
48
- successCount: number
49
- }
50
-
51
- function getDefaultAsyncDebouncerState<
52
- TFn extends AnyAsyncFunction,
53
- >(): AsyncDebouncerState<TFn> {
54
- return {
55
- canLeadingExecute: true,
56
- errorCount: 0,
57
- isExecuting: false,
58
- isPending: false,
59
- lastArgs: undefined,
60
- lastResult: undefined,
61
- maybeExecuteCount: 0,
62
- settleCount: 0,
63
- status: 'idle',
64
- successCount: 0,
65
- }
66
- }
67
-
68
- /**
69
- * Options for configuring an async debounced function
70
- */
71
- export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
72
- /**
73
- * Options for configuring the underlying async retryer
74
- */
75
- asyncRetryerOptions?: AsyncRetryerOptions<TFn>
76
- /**
77
- * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
78
- * Can be a boolean or a function that returns a boolean.
79
- * Defaults to true.
80
- */
81
- enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean)
82
- /**
83
- * Initial state for the async debouncer
84
- */
85
- initialState?: Partial<AsyncDebouncerState<TFn>>
86
- /**
87
- * Optional key to identify this async debouncer instance.
88
- * If provided, the async debouncer will be identified by this key in the devtools and PacerProvider if applicable.
89
- */
90
- key?: string
91
- /**
92
- * Whether to execute on the leading edge of the timeout.
93
- * Defaults to false.
94
- */
95
- leading?: boolean
96
- /**
97
- * Optional error handler for when the debounced function throws.
98
- * If provided, the handler will be called with the error and debouncer instance.
99
- * This can be used alongside throwOnError - the handler will be called before any error is thrown.
100
- */
101
- onError?: (
102
- error: Error,
103
- args: Parameters<TFn>,
104
- debouncer: AsyncDebouncer<TFn>,
105
- ) => void
106
- /**
107
- * Optional callback to call when the debounced function is executed
108
- */
109
- onSettled?: (args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void
110
- /**
111
- * Optional callback to call when the debounced function is executed
112
- */
113
- onSuccess?: (
114
- result: Awaited<ReturnType<TFn>>,
115
- args: Parameters<TFn>,
116
- debouncer: AsyncDebouncer<TFn>,
117
- ) => void
118
- /**
119
- * Whether to throw errors when they occur.
120
- * Defaults to true if no onError handler is provided, false if an onError handler is provided.
121
- * Can be explicitly set to override these defaults.
122
- */
123
- throwOnError?: boolean
124
- /**
125
- * Whether to execute on the trailing edge of the timeout.
126
- * Defaults to true.
127
- */
128
- trailing?: boolean
129
- /**
130
- * Delay in milliseconds to wait after the last call before executing.
131
- * Can be a number or a function that returns a number.
132
- * Defaults to 0ms
133
- */
134
- wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)
135
- }
136
-
137
- /**
138
- * Utility function for sharing common `AsyncDebouncerOptions` options between different `AsyncDebouncer` instances.
139
- */
140
- export function asyncDebouncerOptions<
141
- TFn extends AnyAsyncFunction = AnyAsyncFunction,
142
- TOptions extends Partial<AsyncDebouncerOptions<TFn>> = Partial<
143
- AsyncDebouncerOptions<TFn>
144
- >,
145
- >(options: TOptions): TOptions {
146
- return options
147
- }
148
-
149
- type AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<
150
- AsyncDebouncerOptions<any>,
151
- 'initialState' | 'onError' | 'onSettled' | 'onSuccess' | 'key'
152
- >
153
-
154
- const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {
155
- asyncRetryerOptions: {
156
- maxAttempts: 1,
157
- },
158
- enabled: true,
159
- leading: false,
160
- trailing: true,
161
- wait: 0,
162
- }
163
-
164
- /**
165
- * A class that creates an async debounced function.
166
- *
167
- * Async vs Sync Versions:
168
- * The async version provides advanced features over the sync Debouncer:
169
- * - Returns promises that can be awaited for debounced function results
170
- * - Built-in retry support via AsyncRetryer integration
171
- * - Abort support to cancel in-flight executions
172
- * - Cancel support to prevent pending executions from starting
173
- * - Comprehensive error handling with onError callbacks and throwOnError control
174
- * - Detailed execution tracking (success/error/settle counts)
175
- *
176
- * The sync Debouncer is lighter weight and simpler when you don't need async features,
177
- * return values, or execution control.
178
- *
179
- * What is Debouncing?
180
- * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.
181
- * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing
182
- * or input changes where you only want to execute the handler after the events have stopped occurring.
183
- *
184
- * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until
185
- * the function stops being called for the specified delay period.
186
- *
187
- * Error Handling:
188
- * - If an `onError` handler is provided, it will be called with the error and debouncer instance
189
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
190
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
191
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
192
- * - The error state can be checked using the underlying store
193
- *
194
- * State Management:
195
- * - The debouncer uses a reactive store for state management
196
- * - Use `initialState` to provide initial state values when creating the async debouncer
197
- * - The state includes canLeadingExecute, error count, execution status, and success/settle counts
198
- * - State can be accessed via the `store` property and its `state` getter
199
- * - The store is reactive and will notify subscribers of state changes
200
- *
201
- * @example
202
- * ```ts
203
- * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {
204
- * const results = await searchAPI(value);
205
- * return results; // Return value is preserved
206
- * }, {
207
- * wait: 500,
208
- * onError: (error) => {
209
- * console.error('Search failed:', error);
210
- * }
211
- * });
212
- *
213
- * // Called on each keystroke but only executes after 500ms of no typing
214
- * // Returns the API response directly
215
- * const results = await asyncDebouncer.maybeExecute(inputElement.value);
216
- * ```
217
- */
218
- export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
219
- readonly store: Store<Readonly<AsyncDebouncerState<TFn>>> = new Store<
220
- AsyncDebouncerState<TFn>
221
- >(getDefaultAsyncDebouncerState<TFn>())
222
- key: string | undefined
223
- options: AsyncDebouncerOptions<TFn>
224
- asyncRetryers = new Map<number, AsyncRetryer<TFn>>()
225
- #timeoutId: ReturnType<typeof setTimeout> | null = null
226
- #resolvePreviousPromise:
227
- ((value?: Awaited<ReturnType<TFn>> | undefined) => void) | null = null
228
-
229
- constructor(
230
- public fn: TFn,
231
- initialOptions: AsyncDebouncerOptions<TFn>,
232
- ) {
233
- this.key = initialOptions.key
234
- this.options = {
235
- ...defaultOptions,
236
- ...initialOptions,
237
- throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
238
- }
239
- this.#setState(this.options.initialState ?? {})
240
-
241
- if (this.key) {
242
- pacerEventClient.on('d-AsyncDebouncer', (event) => {
243
- if (event.payload.key !== this.key) return
244
- this.#setState(
245
- event.payload.store.state as Partial<AsyncDebouncerState<TFn>>,
246
- )
247
- this.setOptions(
248
- event.payload.options as Partial<AsyncDebouncerOptions<TFn>>,
249
- )
250
- })
251
- }
252
- }
253
-
254
- /**
255
- * Updates the async debouncer options
256
- */
257
- setOptions = (newOptions: Partial<AsyncDebouncerOptions<TFn>>): void => {
258
- this.options = { ...this.options, ...newOptions }
259
-
260
- // Cancel pending execution if the debouncer is disabled
261
- if (!this.#getEnabled()) {
262
- this.cancel()
263
- }
264
- }
265
-
266
- #setState = (newState: Partial<AsyncDebouncerState<TFn>>): void => {
267
- this.store.setState((state) => {
268
- const combinedState = {
269
- ...state,
270
- ...newState,
271
- }
272
- const { isPending, isExecuting, settleCount } = combinedState
273
- return {
274
- ...combinedState,
275
- status: !this.#getEnabled()
276
- ? 'disabled'
277
- : isPending
278
- ? 'pending'
279
- : isExecuting
280
- ? 'executing'
281
- : settleCount > 0
282
- ? 'settled'
283
- : 'idle',
284
- }
285
- })
286
- emitChange('AsyncDebouncer', this)
287
- }
288
-
289
- /**
290
- * Returns the current debouncer enabled state
291
- */
292
- #getEnabled = (): boolean => {
293
- return !!parseFunctionOrValue(this.options.enabled, this)
294
- }
295
-
296
- /**
297
- * Returns the current debouncer wait state
298
- */
299
- #getWait = (): number => {
300
- return parseFunctionOrValue(this.options.wait, this)
301
- }
302
-
303
- /**
304
- * Attempts to execute the debounced function.
305
- * If a call is already in progress, it will be queued.
306
- *
307
- * Error Handling:
308
- * - If the debounced function throws and no `onError` handler is configured,
309
- * the error will be thrown from this method.
310
- * - If an `onError` handler is configured, errors will be caught and passed to the handler,
311
- * and this method will return undefined.
312
- * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
313
- *
314
- * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
315
- * @throws The error from the debounced function if no onError handler is configured
316
- */
317
- maybeExecute = async (
318
- ...args: Parameters<TFn>
319
- ): Promise<Awaited<ReturnType<TFn>> | undefined> => {
320
- if (!this.#getEnabled()) return undefined
321
- this.#cancelPendingExecution()
322
- this.#setState({
323
- lastArgs: args,
324
- maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
325
- })
326
-
327
- // Handle leading execution
328
- if (this.options.leading && this.store.state.canLeadingExecute) {
329
- this.#setState({ canLeadingExecute: false })
330
- await this.#execute(...args)
331
- return this.store.state.lastResult
332
- }
333
-
334
- // Handle trailing execution
335
- if (this.options.trailing && this.#getEnabled()) {
336
- this.#setState({ isPending: true })
337
- }
338
-
339
- return new Promise<Awaited<ReturnType<TFn>> | undefined>(
340
- (resolve, reject) => {
341
- this.#resolvePreviousPromise = resolve
342
- // this.#rejectPreviousPromise = reject
343
- this.#timeoutId = setTimeout(async () => {
344
- // Execute trailing if enabled
345
- if (this.options.trailing && this.store.state.lastArgs) {
346
- try {
347
- await this.#execute(...this.store.state.lastArgs)
348
- } catch (error) {
349
- reject(error)
350
- }
351
- }
352
-
353
- // Reset state and resolve
354
- this.#setState({ canLeadingExecute: true })
355
- this.#resolvePreviousPromise = null
356
- resolve(this.store.state.lastResult)
357
- }, this.#getWait())
358
- },
359
- )
360
- }
361
-
362
- #execute = async (
363
- ...args: Parameters<TFn>
364
- ): Promise<Awaited<ReturnType<TFn>> | undefined> => {
365
- if (!this.#getEnabled()) return undefined
366
- const currentMaybeExecuteCount = this.store.state.maybeExecuteCount + 1
367
-
368
- try {
369
- this.#setState({ isExecuting: true })
370
- const currentAsyncRetryer = new AsyncRetryer(
371
- this.fn,
372
- this.options.asyncRetryerOptions,
373
- )
374
- this.asyncRetryers.set(currentMaybeExecuteCount, currentAsyncRetryer)
375
- const result = await currentAsyncRetryer.execute(...args) // EXECUTE!
376
- this.#setState({
377
- lastResult: result,
378
- successCount: this.store.state.successCount + 1,
379
- })
380
- this.options.onSuccess?.(result as Awaited<ReturnType<TFn>>, args, this)
381
- } catch (error) {
382
- this.#setState({
383
- errorCount: this.store.state.errorCount + 1,
384
- })
385
- this.options.onError?.(error as Error, args, this)
386
- if (this.options.throwOnError) {
387
- throw error
388
- }
389
- } finally {
390
- this.asyncRetryers.delete(currentMaybeExecuteCount) // dispose retryer
391
- this.#setState({
392
- isExecuting: false,
393
- isPending: false,
394
- lastArgs: undefined,
395
- settleCount: this.store.state.settleCount + 1,
396
- })
397
- this.options.onSettled?.(args, this)
398
- }
399
- return this.store.state.lastResult
400
- }
401
-
402
- /**
403
- * Processes the current pending execution immediately
404
- */
405
- flush = async (): Promise<Awaited<ReturnType<TFn>> | undefined> => {
406
- if (this.store.state.isPending && this.store.state.lastArgs) {
407
- const { lastArgs } = this.store.state
408
- this.#cancelPendingExecution()
409
- return await this.#execute(...lastArgs)
410
- }
411
- return undefined
412
- }
413
-
414
- #resolvePreviousPromiseInternal = (): void => {
415
- if (this.#resolvePreviousPromise) {
416
- this.#resolvePreviousPromise(this.store.state.lastResult)
417
- this.#resolvePreviousPromise = null
418
- }
419
- }
420
-
421
- #clearTimeout = (): void => {
422
- if (this.#timeoutId) {
423
- clearTimeout(this.#timeoutId)
424
- this.#timeoutId = null
425
- }
426
- }
427
-
428
- /**
429
- * Internal cancel without resetting the leading execute state
430
- */
431
- #cancelPendingExecution = (): void => {
432
- this.#clearTimeout()
433
- this.#resolvePreviousPromiseInternal()
434
- this.#setState({
435
- isPending: false,
436
- lastArgs: undefined,
437
- })
438
- }
439
-
440
- /**
441
- * Returns the AbortSignal for a specific execution.
442
- * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
443
- * Returns null if no execution is found or not currently executing.
444
- *
445
- * @param maybeExecuteCount - Optional specific execution to get signal for
446
- * @example
447
- * ```typescript
448
- * const debouncer = new AsyncDebouncer(
449
- * async (searchTerm: string) => {
450
- * const signal = debouncer.getAbortSignal()
451
- * if (signal) {
452
- * const response = await fetch(`/api/search?q=${searchTerm}`, { signal })
453
- * return response.json()
454
- * }
455
- * },
456
- * { wait: 300 }
457
- * )
458
- * ```
459
- */
460
- getAbortSignal = (maybeExecuteCount?: number): AbortSignal | null => {
461
- const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount
462
- const retryer = this.asyncRetryers.get(count)
463
- return retryer?.getAbortSignal() ?? null
464
- }
465
-
466
- /**
467
- * Aborts all ongoing executions with the internal abort controllers.
468
- * Does NOT cancel any pending execution that have not started yet.
469
- */
470
- abort = (): void => {
471
- this.asyncRetryers.forEach((retryer) => retryer.abort())
472
- this.asyncRetryers.clear()
473
- this.#setState({
474
- isExecuting: false,
475
- })
476
- }
477
-
478
- /**
479
- * Cancels any pending execution that have not started yet.
480
- * Does NOT abort any execution already in progress.
481
- */
482
- cancel = (): void => {
483
- this.#cancelPendingExecution()
484
- this.#setState({ canLeadingExecute: true })
485
- }
486
-
487
- /**
488
- * Resets the debouncer state to its default values
489
- */
490
- reset = (): void => {
491
- this.#setState(getDefaultAsyncDebouncerState<TFn>())
492
- this.asyncRetryers.forEach((retryer) => retryer.reset())
493
- }
494
- }
495
-
496
- /**
497
- * Creates an async debounced function that delays execution until after a specified wait time.
498
- * The debounced function will only execute once the wait period has elapsed without any new calls.
499
- * If called again during the wait period, the timer resets and a new wait period begins.
500
- *
501
- * Async vs Sync Versions:
502
- * The async version provides advanced features over the sync debounce function:
503
- * - Returns promises that can be awaited for debounced function results
504
- * - Built-in retry support via AsyncRetryer integration
505
- * - Abort support to cancel in-flight executions
506
- * - Cancel support to prevent pending executions from starting
507
- * - Comprehensive error handling with onError callbacks and throwOnError control
508
- * - Detailed execution tracking (success/error/settle counts)
509
- *
510
- * The sync debounce function is lighter weight and simpler when you don't need async features,
511
- * return values, or execution control.
512
- *
513
- * What is Debouncing?
514
- * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.
515
- * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing
516
- * or input changes where you only want to execute the handler after the events have stopped occurring.
517
- *
518
- * Configuration Options:
519
- * - `wait`: Delay in milliseconds to wait after the last call (required)
520
- * - `leading`: Execute on the leading edge of the timeout (default: false)
521
- * - `trailing`: Execute on the trailing edge of the timeout (default: true)
522
- * - `enabled`: Whether the debouncer is enabled (default: true)
523
- * - `asyncRetryerOptions`: Configure retry behavior for executions
524
- *
525
- * Error Handling:
526
- * - If an `onError` handler is provided, it will be called with the error and debouncer instance
527
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
528
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
529
- * - The error state can be checked using the underlying AsyncDebouncer instance
530
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
531
- *
532
- * State Management:
533
- * - Uses TanStack Store for reactive state management
534
- * - Use `initialState` to provide initial state values when creating the async debouncer
535
- * - Use `onSuccess` callback to react to successful function execution and implement custom logic
536
- * - Use `onError` callback to react to function execution errors and implement custom error handling
537
- * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
538
- * - The state includes canLeadingExecute, error count, execution status, and success/settle counts
539
- * - State can be accessed via `asyncDebouncer.store.state` when using the class directly
540
- * - When using framework adapters (React/Solid), state is accessed from `asyncDebouncer.state`
541
- *
542
- * @example
543
- * ```ts
544
- * const debounced = asyncDebounce(async (value: string) => {
545
- * const result = await saveToAPI(value);
546
- * return result; // Return value is preserved
547
- * }, {
548
- * wait: 1000,
549
- * onError: (error) => {
550
- * console.error('API call failed:', error);
551
- * },
552
- * throwOnError: true // Will both log the error and throw it
553
- * });
554
- *
555
- * // Will only execute once, 1 second after the last call
556
- * // Returns the API response directly
557
- * const result = await debounced("third");
558
- * ```
559
- */
560
- export function asyncDebounce<TFn extends AnyAsyncFunction>(
561
- fn: TFn,
562
- initialOptions: AsyncDebouncerOptions<TFn>,
563
- ) {
564
- const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)
565
- return asyncDebouncer.maybeExecute
566
- }