@tanstack/pacer 0.15.4 → 0.16.1

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 (99) hide show
  1. package/dist/cjs/async-batcher.cjs +66 -5
  2. package/dist/cjs/async-batcher.cjs.map +1 -1
  3. package/dist/cjs/async-batcher.d.cts +94 -17
  4. package/dist/cjs/async-debouncer.cjs +53 -19
  5. package/dist/cjs/async-debouncer.cjs.map +1 -1
  6. package/dist/cjs/async-debouncer.d.cts +76 -10
  7. package/dist/cjs/async-queuer.cjs +58 -2
  8. package/dist/cjs/async-queuer.cjs.map +1 -1
  9. package/dist/cjs/async-queuer.d.cts +100 -10
  10. package/dist/cjs/async-rate-limiter.cjs +51 -2
  11. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  12. package/dist/cjs/async-rate-limiter.d.cts +91 -28
  13. package/dist/cjs/async-retryer.cjs +285 -0
  14. package/dist/cjs/async-retryer.cjs.map +1 -0
  15. package/dist/cjs/async-retryer.d.cts +308 -0
  16. package/dist/cjs/async-throttler.cjs +101 -53
  17. package/dist/cjs/async-throttler.cjs.map +1 -1
  18. package/dist/cjs/async-throttler.d.cts +82 -10
  19. package/dist/cjs/batcher.cjs +5 -1
  20. package/dist/cjs/batcher.cjs.map +1 -1
  21. package/dist/cjs/batcher.d.cts +10 -2
  22. package/dist/cjs/debouncer.cjs +5 -1
  23. package/dist/cjs/debouncer.cjs.map +1 -1
  24. package/dist/cjs/debouncer.d.cts +7 -3
  25. package/dist/cjs/event-client.cjs +3 -1
  26. package/dist/cjs/event-client.cjs.map +1 -1
  27. package/dist/cjs/event-client.d.cts +3 -0
  28. package/dist/cjs/index.cjs +13 -1
  29. package/dist/cjs/index.cjs.map +1 -1
  30. package/dist/cjs/index.d.cts +1 -0
  31. package/dist/cjs/queuer.cjs +5 -1
  32. package/dist/cjs/queuer.cjs.map +1 -1
  33. package/dist/cjs/queuer.d.cts +8 -4
  34. package/dist/cjs/rate-limiter.cjs +5 -1
  35. package/dist/cjs/rate-limiter.cjs.map +1 -1
  36. package/dist/cjs/rate-limiter.d.cts +8 -1
  37. package/dist/cjs/throttler.cjs +5 -1
  38. package/dist/cjs/throttler.cjs.map +1 -1
  39. package/dist/cjs/throttler.d.cts +7 -0
  40. package/dist/cjs/utils.cjs +0 -10
  41. package/dist/cjs/utils.cjs.map +1 -1
  42. package/dist/cjs/utils.d.cts +0 -1
  43. package/dist/esm/async-batcher.d.ts +94 -17
  44. package/dist/esm/async-batcher.js +68 -7
  45. package/dist/esm/async-batcher.js.map +1 -1
  46. package/dist/esm/async-debouncer.d.ts +76 -10
  47. package/dist/esm/async-debouncer.js +55 -21
  48. package/dist/esm/async-debouncer.js.map +1 -1
  49. package/dist/esm/async-queuer.d.ts +100 -10
  50. package/dist/esm/async-queuer.js +60 -4
  51. package/dist/esm/async-queuer.js.map +1 -1
  52. package/dist/esm/async-rate-limiter.d.ts +91 -28
  53. package/dist/esm/async-rate-limiter.js +53 -4
  54. package/dist/esm/async-rate-limiter.js.map +1 -1
  55. package/dist/esm/async-retryer.d.ts +308 -0
  56. package/dist/esm/async-retryer.js +285 -0
  57. package/dist/esm/async-retryer.js.map +1 -0
  58. package/dist/esm/async-throttler.d.ts +82 -10
  59. package/dist/esm/async-throttler.js +103 -55
  60. package/dist/esm/async-throttler.js.map +1 -1
  61. package/dist/esm/batcher.d.ts +10 -2
  62. package/dist/esm/batcher.js +6 -2
  63. package/dist/esm/batcher.js.map +1 -1
  64. package/dist/esm/debouncer.d.ts +7 -3
  65. package/dist/esm/debouncer.js +7 -3
  66. package/dist/esm/debouncer.js.map +1 -1
  67. package/dist/esm/event-client.d.ts +3 -0
  68. package/dist/esm/event-client.js +3 -1
  69. package/dist/esm/event-client.js.map +1 -1
  70. package/dist/esm/index.d.ts +1 -0
  71. package/dist/esm/index.js +24 -12
  72. package/dist/esm/index.js.map +1 -1
  73. package/dist/esm/queuer.d.ts +8 -4
  74. package/dist/esm/queuer.js +7 -3
  75. package/dist/esm/queuer.js.map +1 -1
  76. package/dist/esm/rate-limiter.d.ts +8 -1
  77. package/dist/esm/rate-limiter.js +7 -3
  78. package/dist/esm/rate-limiter.js.map +1 -1
  79. package/dist/esm/throttler.d.ts +7 -0
  80. package/dist/esm/throttler.js +7 -3
  81. package/dist/esm/throttler.js.map +1 -1
  82. package/dist/esm/utils.d.ts +0 -1
  83. package/dist/esm/utils.js +0 -10
  84. package/dist/esm/utils.js.map +1 -1
  85. package/package.json +13 -3
  86. package/src/async-batcher.ts +144 -22
  87. package/src/async-debouncer.ts +118 -33
  88. package/src/async-queuer.ts +147 -14
  89. package/src/async-rate-limiter.ts +129 -33
  90. package/src/async-retryer.ts +664 -0
  91. package/src/async-throttler.ts +196 -75
  92. package/src/batcher.ts +16 -4
  93. package/src/debouncer.ts +17 -5
  94. package/src/event-client.ts +6 -1
  95. package/src/index.ts +2 -0
  96. package/src/queuer.ts +19 -6
  97. package/src/rate-limiter.ts +18 -3
  98. package/src/throttler.ts +17 -2
  99. package/src/utils.ts +0 -15
@@ -1,4 +1,5 @@
1
1
  import { Store } from '@tanstack/store';
2
+ import { AsyncRetryer, AsyncRetryerOptions } from './async-retryer.js';
2
3
  import { AnyAsyncFunction } from './types.js';
3
4
  export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
4
5
  /**
@@ -46,6 +47,10 @@ export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
46
47
  * Options for configuring an async debounced function
47
48
  */
48
49
  export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
50
+ /**
51
+ * Options for configuring the underlying async retryer
52
+ */
53
+ asyncRetryerOptions?: AsyncRetryerOptions<TFn>;
49
54
  /**
50
55
  * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
51
56
  * Can be a boolean or a function that returns a boolean.
@@ -71,7 +76,7 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
71
76
  * If provided, the handler will be called with the error and debouncer instance.
72
77
  * This can be used alongside throwOnError - the handler will be called before any error is thrown.
73
78
  */
74
- onError?: (error: unknown, args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void;
79
+ onError?: (error: Error, args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void;
75
80
  /**
76
81
  * Optional callback to call when the debounced function is executed
77
82
  */
@@ -98,9 +103,26 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
98
103
  */
99
104
  wait: number | ((debouncer: AsyncDebouncer<TFn>) => number);
100
105
  }
106
+ /**
107
+ * Utility function for sharing common `AsyncDebouncerOptions` options between different `AsyncDebouncer` instances.
108
+ */
109
+ export declare function asyncDebouncerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncDebouncerOptions<TFn>> = Partial<AsyncDebouncerOptions<TFn>>>(options: TOptions): TOptions;
101
110
  /**
102
111
  * A class that creates an async debounced function.
103
112
  *
113
+ * Async vs Sync Versions:
114
+ * The async version provides advanced features over the sync Debouncer:
115
+ * - Returns promises that can be awaited for debounced function results
116
+ * - Built-in retry support via AsyncRetryer integration
117
+ * - Abort support to cancel in-flight executions
118
+ * - Cancel support to prevent pending executions from starting
119
+ * - Comprehensive error handling with onError callbacks and throwOnError control
120
+ * - Detailed execution tracking (success/error/settle counts)
121
+ *
122
+ * The sync Debouncer is lighter weight and simpler when you don't need async features,
123
+ * return values, or execution control.
124
+ *
125
+ * What is Debouncing?
104
126
  * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.
105
127
  * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing
106
128
  * or input changes where you only want to execute the handler after the events have stopped occurring.
@@ -108,10 +130,6 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
108
130
  * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until
109
131
  * the function stops being called for the specified delay period.
110
132
  *
111
- * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,
112
- * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
113
- * instead of setting the result on a state variable from within the debounced function.
114
- *
115
133
  * Error Handling:
116
134
  * - If an `onError` handler is provided, it will be called with the error and debouncer instance
117
135
  * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
@@ -147,8 +165,9 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
147
165
  #private;
148
166
  fn: TFn;
149
167
  readonly store: Store<Readonly<AsyncDebouncerState<TFn>>>;
150
- key: string;
168
+ key: string | undefined;
151
169
  options: AsyncDebouncerOptions<TFn>;
170
+ asyncRetryers: Map<number, AsyncRetryer<TFn>>;
152
171
  constructor(fn: TFn, initialOptions: AsyncDebouncerOptions<TFn>);
153
172
  /**
154
173
  * Updates the async debouncer options
@@ -174,7 +193,34 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
174
193
  */
175
194
  flush: () => Promise<ReturnType<TFn> | undefined>;
176
195
  /**
177
- * Cancels any pending execution or aborts any execution in progress
196
+ * Returns the AbortSignal for a specific execution.
197
+ * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
198
+ * Returns null if no execution is found or not currently executing.
199
+ *
200
+ * @param maybeExecuteCount - Optional specific execution to get signal for
201
+ * @example
202
+ * ```typescript
203
+ * const debouncer = new AsyncDebouncer(
204
+ * async (searchTerm: string) => {
205
+ * const signal = debouncer.getAbortSignal()
206
+ * if (signal) {
207
+ * const response = await fetch(`/api/search?q=${searchTerm}`, { signal })
208
+ * return response.json()
209
+ * }
210
+ * },
211
+ * { wait: 300 }
212
+ * )
213
+ * ```
214
+ */
215
+ getAbortSignal(maybeExecuteCount?: number): AbortSignal | null;
216
+ /**
217
+ * Aborts all ongoing executions with the internal abort controllers.
218
+ * Does NOT cancel any pending execution that have not started yet.
219
+ */
220
+ abort: () => void;
221
+ /**
222
+ * Cancels any pending execution that have not started yet.
223
+ * Does NOT abort any execution already in progress.
178
224
  */
179
225
  cancel: () => void;
180
226
  /**
@@ -187,9 +233,29 @@ export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
187
233
  * The debounced function will only execute once the wait period has elapsed without any new calls.
188
234
  * If called again during the wait period, the timer resets and a new wait period begins.
189
235
  *
190
- * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,
191
- * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
192
- * instead of setting the result on a state variable from within the debounced function.
236
+ * Async vs Sync Versions:
237
+ * The async version provides advanced features over the sync debounce function:
238
+ * - Returns promises that can be awaited for debounced function results
239
+ * - Built-in retry support via AsyncRetryer integration
240
+ * - Abort support to cancel in-flight executions
241
+ * - Cancel support to prevent pending executions from starting
242
+ * - Comprehensive error handling with onError callbacks and throwOnError control
243
+ * - Detailed execution tracking (success/error/settle counts)
244
+ *
245
+ * The sync debounce function is lighter weight and simpler when you don't need async features,
246
+ * return values, or execution control.
247
+ *
248
+ * What is Debouncing?
249
+ * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.
250
+ * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing
251
+ * or input changes where you only want to execute the handler after the events have stopped occurring.
252
+ *
253
+ * Configuration Options:
254
+ * - `wait`: Delay in milliseconds to wait after the last call (required)
255
+ * - `leading`: Execute on the leading edge of the timeout (default: false)
256
+ * - `trailing`: Execute on the trailing edge of the timeout (default: true)
257
+ * - `enabled`: Whether the debouncer is enabled (default: true)
258
+ * - `asyncRetryerOptions`: Configure retry behavior for executions
193
259
  *
194
260
  * Error Handling:
195
261
  * - If an `onError` handler is provided, it will be called with the error and debouncer instance
@@ -1,5 +1,6 @@
1
1
  import { Store } from "@tanstack/store";
2
- import { parseFunctionOrValue, createKey } from "./utils.js";
2
+ import { AsyncRetryer } from "./async-retryer.js";
3
+ import { parseFunctionOrValue } from "./utils.js";
3
4
  import { emitChange, pacerEventClient } from "./event-client.js";
4
5
  function getDefaultAsyncDebouncerState() {
5
6
  return {
@@ -15,7 +16,13 @@ function getDefaultAsyncDebouncerState() {
15
16
  successCount: 0
16
17
  };
17
18
  }
19
+ function asyncDebouncerOptions(options) {
20
+ return options;
21
+ }
18
22
  const defaultOptions = {
23
+ asyncRetryerOptions: {
24
+ maxAttempts: 1
25
+ },
19
26
  enabled: true,
20
27
  leading: false,
21
28
  trailing: true,
@@ -25,7 +32,7 @@ class AsyncDebouncer {
25
32
  constructor(fn, initialOptions) {
26
33
  this.fn = fn;
27
34
  this.store = new Store(getDefaultAsyncDebouncerState());
28
- this.#abortController = null;
35
+ this.asyncRetryers = /* @__PURE__ */ new Map();
29
36
  this.#timeoutId = null;
30
37
  this.#resolvePreviousPromise = null;
31
38
  this.setOptions = (newOptions) => {
@@ -87,10 +94,15 @@ class AsyncDebouncer {
87
94
  };
88
95
  this.#execute = async (...args) => {
89
96
  if (!this.#getEnabled()) return void 0;
90
- this.#abortController = new AbortController();
97
+ const currentMaybeExecuteCount = this.store.state.maybeExecuteCount + 1;
91
98
  try {
92
99
  this.#setState({ isExecuting: true });
93
- const result = await this.fn(...args);
100
+ const currentAsyncRetryer = new AsyncRetryer(this.fn, {
101
+ ...this.options.asyncRetryerOptions,
102
+ key: `${this.key}-retryer-${currentMaybeExecuteCount}`
103
+ });
104
+ this.asyncRetryers.set(currentMaybeExecuteCount, currentAsyncRetryer);
105
+ const result = await currentAsyncRetryer.execute(...args);
94
106
  this.#setState({
95
107
  lastResult: result,
96
108
  successCount: this.store.state.successCount + 1
@@ -105,24 +117,22 @@ class AsyncDebouncer {
105
117
  throw error;
106
118
  }
107
119
  } finally {
120
+ this.asyncRetryers.delete(currentMaybeExecuteCount);
108
121
  this.#setState({
109
122
  isExecuting: false,
110
123
  isPending: false,
111
124
  lastArgs: void 0,
112
125
  settleCount: this.store.state.settleCount + 1
113
126
  });
114
- this.#abortController = null;
115
127
  this.options.onSettled?.(args, this);
116
128
  }
117
129
  return this.store.state.lastResult;
118
130
  };
119
131
  this.flush = async () => {
120
132
  if (this.store.state.isPending && this.store.state.lastArgs) {
121
- this.#abortExecution();
122
- this.#clearTimeout();
123
- const result = await this.#execute(...this.store.state.lastArgs);
124
- this.#resolvePreviousPromiseInternal();
125
- return result;
133
+ const { lastArgs } = this.store.state;
134
+ this.#cancelPendingExecution();
135
+ return await this.#execute(...lastArgs);
126
136
  }
127
137
  return void 0;
128
138
  };
@@ -143,25 +153,25 @@ class AsyncDebouncer {
143
153
  this.#resolvePreviousPromiseInternal();
144
154
  this.#setState({
145
155
  isPending: false,
146
- isExecuting: false,
147
156
  lastArgs: void 0
148
157
  });
149
158
  };
150
- this.#abortExecution = () => {
151
- if (this.#abortController) {
152
- this.#abortController.abort();
153
- this.#abortController = null;
154
- }
159
+ this.abort = () => {
160
+ this.asyncRetryers.forEach((retryer) => retryer.abort());
161
+ this.asyncRetryers.clear();
162
+ this.#setState({
163
+ isExecuting: false
164
+ });
155
165
  };
156
166
  this.cancel = () => {
157
167
  this.#cancelPendingExecution();
158
- this.#abortExecution();
159
168
  this.#setState({ canLeadingExecute: true });
160
169
  };
161
170
  this.reset = () => {
162
171
  this.#setState(getDefaultAsyncDebouncerState());
172
+ this.asyncRetryers.forEach((retryer) => retryer.reset());
163
173
  };
164
- this.key = createKey(initialOptions.key);
174
+ this.key = initialOptions.key;
165
175
  this.options = {
166
176
  ...defaultOptions,
167
177
  ...initialOptions,
@@ -174,7 +184,6 @@ class AsyncDebouncer {
174
184
  this.setOptions(event.payload.options);
175
185
  });
176
186
  }
177
- #abortController;
178
187
  #timeoutId;
179
188
  #resolvePreviousPromise;
180
189
  #setState;
@@ -184,7 +193,31 @@ class AsyncDebouncer {
184
193
  #resolvePreviousPromiseInternal;
185
194
  #clearTimeout;
186
195
  #cancelPendingExecution;
187
- #abortExecution;
196
+ /**
197
+ * Returns the AbortSignal for a specific execution.
198
+ * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
199
+ * Returns null if no execution is found or not currently executing.
200
+ *
201
+ * @param maybeExecuteCount - Optional specific execution to get signal for
202
+ * @example
203
+ * ```typescript
204
+ * const debouncer = new AsyncDebouncer(
205
+ * async (searchTerm: string) => {
206
+ * const signal = debouncer.getAbortSignal()
207
+ * if (signal) {
208
+ * const response = await fetch(`/api/search?q=${searchTerm}`, { signal })
209
+ * return response.json()
210
+ * }
211
+ * },
212
+ * { wait: 300 }
213
+ * )
214
+ * ```
215
+ */
216
+ getAbortSignal(maybeExecuteCount) {
217
+ const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount;
218
+ const retryer = this.asyncRetryers.get(count);
219
+ return retryer?.getAbortSignal() ?? null;
220
+ }
188
221
  }
189
222
  function asyncDebounce(fn, initialOptions) {
190
223
  const asyncDebouncer = new AsyncDebouncer(fn, initialOptions);
@@ -192,6 +225,7 @@ function asyncDebounce(fn, initialOptions) {
192
225
  }
193
226
  export {
194
227
  AsyncDebouncer,
195
- asyncDebounce
228
+ asyncDebounce,
229
+ asyncDebouncerOptions
196
230
  };
197
231
  //# sourceMappingURL=async-debouncer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"async-debouncer.js","sources":["../../src/async-debouncer.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { createKey, parseFunctionOrValue } from './utils'\nimport { emitChange, pacerEventClient } from './event-client'\nimport type { AnyAsyncFunction, OptionalKeys } from './types'\n\nexport interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer can execute on the leading edge of the timeout\n */\n canLeadingExecute: boolean\n /**\n * Number of function executions that have resulted in errors\n */\n errorCount: number\n /**\n * Whether the debounced function is currently executing asynchronously\n */\n isExecuting: boolean\n /**\n * Whether the debouncer is waiting for the timeout to trigger execution\n */\n isPending: boolean\n /**\n * The arguments from the most recent call to maybeExecute\n */\n lastArgs: Parameters<TFn> | undefined\n /**\n * The result from the most recent successful function execution\n */\n lastResult: ReturnType<TFn> | undefined\n /**\n * Number of times maybeExecute has been called (for reduction calculations)\n */\n maybeExecuteCount: number\n /**\n * Number of function executions that have completed (either successfully or with errors)\n */\n settleCount: number\n /**\n * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed\n */\n status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled'\n /**\n * Number of function executions that have completed successfully\n */\n successCount: number\n}\n\nfunction getDefaultAsyncDebouncerState<\n TFn extends AnyAsyncFunction,\n>(): AsyncDebouncerState<TFn> {\n return {\n canLeadingExecute: true,\n errorCount: 0,\n isExecuting: false,\n isPending: false,\n lastArgs: undefined,\n lastResult: undefined,\n maybeExecuteCount: 0,\n settleCount: 0,\n status: 'idle',\n successCount: 0,\n }\n}\n\n/**\n * Options for configuring an async debounced function\n */\nexport interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.\n * Can be a boolean or a function that returns a boolean.\n * Defaults to true.\n */\n enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean)\n /**\n * Initial state for the async debouncer\n */\n initialState?: Partial<AsyncDebouncerState<TFn>>\n /**\n * Optional key to identify this async debouncer instance.\n * If provided, the async debouncer will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to false.\n */\n leading?: boolean\n /**\n * Optional error handler for when the debounced function throws.\n * If provided, the handler will be called with the error and debouncer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (\n error: unknown,\n args: Parameters<TFn>,\n debouncer: AsyncDebouncer<TFn>,\n ) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSettled?: (args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSuccess?: (\n result: ReturnType<TFn>,\n args: Parameters<TFn>,\n debouncer: AsyncDebouncer<TFn>,\n ) => void\n /**\n * Whether to throw errors when they occur.\n * Defaults to true if no onError handler is provided, false if an onError handler is provided.\n * Can be explicitly set to override these defaults.\n */\n throwOnError?: boolean\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Delay in milliseconds to wait after the last call before executing.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)\n}\n\ntype AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<\n AsyncDebouncerOptions<any>,\n 'initialState' | 'onError' | 'onSettled' | 'onSuccess' | 'key'\n>\n\nconst defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {\n enabled: true,\n leading: false,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async debounced function.\n *\n * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.\n * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing\n * or input changes where you only want to execute the handler after the events have stopped occurring.\n *\n * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until\n * the function stops being called for the specified delay period.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n * - The error state can be checked using the underlying store\n *\n * State Management:\n * - The debouncer uses a reactive store for state management\n * - Use `initialState` to provide initial state values when creating the async debouncer\n * - The state includes canLeadingExecute, error count, execution status, and success/settle counts\n * - State can be accessed via the `store` property and its `state` getter\n * - The store is reactive and will notify subscribers of state changes\n *\n * @example\n * ```ts\n * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {\n * const results = await searchAPI(value);\n * return results; // Return value is preserved\n * }, {\n * wait: 500,\n * onError: (error) => {\n * console.error('Search failed:', error);\n * }\n * });\n *\n * // Called on each keystroke but only executes after 500ms of no typing\n * // Returns the API response directly\n * const results = await asyncDebouncer.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncDebouncer<TFn extends AnyAsyncFunction> {\n readonly store: Store<Readonly<AsyncDebouncerState<TFn>>> = new Store<\n AsyncDebouncerState<TFn>\n >(getDefaultAsyncDebouncerState<TFn>())\n key: string\n options: AsyncDebouncerOptions<TFn>\n #abortController: AbortController | null = null\n #timeoutId: NodeJS.Timeout | null = null\n #resolvePreviousPromise:\n | ((value?: ReturnType<TFn> | undefined) => void)\n | null = null\n\n constructor(\n public fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n ) {\n this.key = createKey(initialOptions.key)\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n this.#setState(this.options.initialState ?? {})\n\n pacerEventClient.on('d-AsyncDebouncer', (event) => {\n if (event.payload.key !== this.key) return\n this.#setState(event.payload.store.state as AsyncDebouncerState<TFn>)\n this.setOptions(event.payload.options)\n })\n }\n\n /**\n * Updates the async debouncer options\n */\n setOptions = (newOptions: Partial<AsyncDebouncerOptions<TFn>>): void => {\n this.options = { ...this.options, ...newOptions }\n\n // Cancel pending execution if the debouncer is disabled\n if (!this.#getEnabled()) {\n this.cancel()\n }\n }\n\n #setState = (newState: Partial<AsyncDebouncerState<TFn>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n const { isPending, isExecuting, settleCount } = combinedState\n return {\n ...combinedState,\n status: !this.#getEnabled()\n ? 'disabled'\n : isPending\n ? 'pending'\n : isExecuting\n ? 'executing'\n : settleCount > 0\n ? 'settled'\n : 'idle',\n }\n })\n emitChange('AsyncDebouncer', this)\n }\n\n /**\n * Returns the current debouncer enabled state\n */\n #getEnabled = (): boolean => {\n return !!parseFunctionOrValue(this.options.enabled, this)\n }\n\n /**\n * Returns the current debouncer wait state\n */\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait, this)\n }\n\n /**\n * Attempts to execute the debounced function.\n * If a call is already in progress, it will be queued.\n *\n * Error Handling:\n * - If the debounced function throws and no `onError` handler is configured,\n * the error will be thrown from this method.\n * - If an `onError` handler is configured, errors will be caught and passed to the handler,\n * and this method will return undefined.\n * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.\n *\n * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError\n * @throws The error from the debounced function if no onError handler is configured\n */\n maybeExecute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n this.#cancelPendingExecution()\n this.#setState({\n lastArgs: args,\n maybeExecuteCount: this.store.state.maybeExecuteCount + 1,\n })\n\n // Handle leading execution\n if (this.options.leading && this.store.state.canLeadingExecute) {\n this.#setState({ canLeadingExecute: false })\n await this.#execute(...args)\n return this.store.state.lastResult\n }\n\n // Handle trailing execution\n if (this.options.trailing && this.#getEnabled()) {\n this.#setState({ isPending: true })\n }\n\n return new Promise((resolve, reject) => {\n this.#resolvePreviousPromise = resolve\n // this.#rejectPreviousPromise = reject\n this.#timeoutId = setTimeout(async () => {\n // Execute trailing if enabled\n if (this.options.trailing && this.store.state.lastArgs) {\n try {\n await this.#execute(...this.store.state.lastArgs)\n } catch (error) {\n reject(error)\n }\n }\n\n // Reset state and resolve\n this.#setState({ canLeadingExecute: true })\n this.#resolvePreviousPromise = null\n resolve(this.store.state.lastResult)\n }, this.#getWait())\n })\n }\n\n #execute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n this.#abortController = new AbortController()\n try {\n this.#setState({ isExecuting: true })\n const result = await this.fn(...args) // EXECUTE!\n this.#setState({\n lastResult: result,\n successCount: this.store.state.successCount + 1,\n })\n this.options.onSuccess?.(result, args, this)\n } catch (error) {\n this.#setState({\n errorCount: this.store.state.errorCount + 1,\n })\n this.options.onError?.(error, args, this)\n if (this.options.throwOnError) {\n throw error\n }\n } finally {\n this.#setState({\n isExecuting: false,\n isPending: false,\n lastArgs: undefined,\n settleCount: this.store.state.settleCount + 1,\n })\n this.#abortController = null\n this.options.onSettled?.(args, this)\n }\n return this.store.state.lastResult\n }\n\n /**\n * Processes the current pending execution immediately\n */\n flush = async (): Promise<ReturnType<TFn> | undefined> => {\n if (this.store.state.isPending && this.store.state.lastArgs) {\n this.#abortExecution() // abort any current execution\n this.#clearTimeout() // clear any existing timeout\n const result = await this.#execute(...this.store.state.lastArgs)\n\n // Resolve any pending promise from maybeExecute\n this.#resolvePreviousPromiseInternal()\n\n return result\n }\n return undefined\n }\n\n #resolvePreviousPromiseInternal = (): void => {\n if (this.#resolvePreviousPromise) {\n this.#resolvePreviousPromise(this.store.state.lastResult)\n this.#resolvePreviousPromise = null\n }\n }\n\n #clearTimeout = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId)\n this.#timeoutId = null\n }\n }\n\n #cancelPendingExecution = (): void => {\n this.#clearTimeout()\n this.#resolvePreviousPromiseInternal()\n this.#setState({\n isPending: false,\n isExecuting: false,\n lastArgs: undefined,\n })\n }\n\n #abortExecution = (): void => {\n if (this.#abortController) {\n this.#abortController.abort()\n this.#abortController = null\n }\n }\n\n /**\n * Cancels any pending execution or aborts any execution in progress\n */\n cancel = (): void => {\n this.#cancelPendingExecution()\n this.#abortExecution()\n this.#setState({ canLeadingExecute: true })\n }\n\n /**\n * Resets the debouncer state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncDebouncerState<TFn>())\n }\n}\n\n/**\n * Creates an async debounced function that delays execution until after a specified wait time.\n * The debounced function will only execute once the wait period has elapsed without any new calls.\n * If called again during the wait period, the timer resets and a new wait period begins.\n *\n * Unlike the non-async Debouncer, this async version supports returning values from the debounced function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the debounced function.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - The error state can be checked using the underlying AsyncDebouncer instance\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the async debouncer\n * - Use `onSuccess` callback to react to successful function execution and implement custom logic\n * - Use `onError` callback to react to function execution errors and implement custom error handling\n * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic\n * - The state includes canLeadingExecute, error count, execution status, and success/settle counts\n * - State can be accessed via `asyncDebouncer.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncDebouncer.state`\n *\n * @example\n * ```ts\n * const debounced = asyncDebounce(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, {\n * wait: 1000,\n * onError: (error) => {\n * console.error('API call failed:', error);\n * },\n * throwOnError: true // Will both log the error and throw it\n * });\n *\n * // Will only execute once, 1 second after the last call\n * // Returns the API response directly\n * const result = await debounced(\"third\");\n * ```\n */\nexport function asyncDebounce<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n) {\n const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)\n return asyncDebouncer.maybeExecute\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"async-debouncer.js","sources":["../../src/async-debouncer.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { AsyncRetryer } from './async-retryer'\nimport { parseFunctionOrValue } from './utils'\nimport { emitChange, pacerEventClient } from './event-client'\nimport type { AsyncRetryerOptions } from './async-retryer'\nimport type { AnyAsyncFunction, OptionalKeys } from './types'\n\nexport interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {\n /**\n * Whether the debouncer can execute on the leading edge of the timeout\n */\n canLeadingExecute: boolean\n /**\n * Number of function executions that have resulted in errors\n */\n errorCount: number\n /**\n * Whether the debounced function is currently executing asynchronously\n */\n isExecuting: boolean\n /**\n * Whether the debouncer is waiting for the timeout to trigger execution\n */\n isPending: boolean\n /**\n * The arguments from the most recent call to maybeExecute\n */\n lastArgs: Parameters<TFn> | undefined\n /**\n * The result from the most recent successful function execution\n */\n lastResult: ReturnType<TFn> | undefined\n /**\n * Number of times maybeExecute has been called (for reduction calculations)\n */\n maybeExecuteCount: number\n /**\n * Number of function executions that have completed (either successfully or with errors)\n */\n settleCount: number\n /**\n * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed\n */\n status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled'\n /**\n * Number of function executions that have completed successfully\n */\n successCount: number\n}\n\nfunction getDefaultAsyncDebouncerState<\n TFn extends AnyAsyncFunction,\n>(): AsyncDebouncerState<TFn> {\n return {\n canLeadingExecute: true,\n errorCount: 0,\n isExecuting: false,\n isPending: false,\n lastArgs: undefined,\n lastResult: undefined,\n maybeExecuteCount: 0,\n settleCount: 0,\n status: 'idle',\n successCount: 0,\n }\n}\n\n/**\n * Options for configuring an async debounced function\n */\nexport interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Options for configuring the underlying async retryer\n */\n asyncRetryerOptions?: AsyncRetryerOptions<TFn>\n /**\n * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.\n * Can be a boolean or a function that returns a boolean.\n * Defaults to true.\n */\n enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean)\n /**\n * Initial state for the async debouncer\n */\n initialState?: Partial<AsyncDebouncerState<TFn>>\n /**\n * Optional key to identify this async debouncer instance.\n * If provided, the async debouncer will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Whether to execute on the leading edge of the timeout.\n * Defaults to false.\n */\n leading?: boolean\n /**\n * Optional error handler for when the debounced function throws.\n * If provided, the handler will be called with the error and debouncer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (\n error: Error,\n args: Parameters<TFn>,\n debouncer: AsyncDebouncer<TFn>,\n ) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSettled?: (args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void\n /**\n * Optional callback to call when the debounced function is executed\n */\n onSuccess?: (\n result: ReturnType<TFn>,\n args: Parameters<TFn>,\n debouncer: AsyncDebouncer<TFn>,\n ) => void\n /**\n * Whether to throw errors when they occur.\n * Defaults to true if no onError handler is provided, false if an onError handler is provided.\n * Can be explicitly set to override these defaults.\n */\n throwOnError?: boolean\n /**\n * Whether to execute on the trailing edge of the timeout.\n * Defaults to true.\n */\n trailing?: boolean\n /**\n * Delay in milliseconds to wait after the last call before executing.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)\n}\n\n/**\n * Utility function for sharing common `AsyncDebouncerOptions` options between different `AsyncDebouncer` instances.\n */\nexport function asyncDebouncerOptions<\n TFn extends AnyAsyncFunction = AnyAsyncFunction,\n TOptions extends Partial<AsyncDebouncerOptions<TFn>> = Partial<\n AsyncDebouncerOptions<TFn>\n >,\n>(options: TOptions): TOptions {\n return options\n}\n\ntype AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<\n AsyncDebouncerOptions<any>,\n 'initialState' | 'onError' | 'onSettled' | 'onSuccess' | 'key'\n>\n\nconst defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {\n asyncRetryerOptions: {\n maxAttempts: 1,\n },\n enabled: true,\n leading: false,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async debounced function.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync Debouncer:\n * - Returns promises that can be awaited for debounced function results\n * - Built-in retry support via AsyncRetryer integration\n * - Abort support to cancel in-flight executions\n * - Cancel support to prevent pending executions from starting\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n *\n * The sync Debouncer is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Debouncing?\n * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.\n * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing\n * or input changes where you only want to execute the handler after the events have stopped occurring.\n *\n * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until\n * the function stops being called for the specified delay period.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n * - The error state can be checked using the underlying store\n *\n * State Management:\n * - The debouncer uses a reactive store for state management\n * - Use `initialState` to provide initial state values when creating the async debouncer\n * - The state includes canLeadingExecute, error count, execution status, and success/settle counts\n * - State can be accessed via the `store` property and its `state` getter\n * - The store is reactive and will notify subscribers of state changes\n *\n * @example\n * ```ts\n * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {\n * const results = await searchAPI(value);\n * return results; // Return value is preserved\n * }, {\n * wait: 500,\n * onError: (error) => {\n * console.error('Search failed:', error);\n * }\n * });\n *\n * // Called on each keystroke but only executes after 500ms of no typing\n * // Returns the API response directly\n * const results = await asyncDebouncer.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncDebouncer<TFn extends AnyAsyncFunction> {\n readonly store: Store<Readonly<AsyncDebouncerState<TFn>>> = new Store<\n AsyncDebouncerState<TFn>\n >(getDefaultAsyncDebouncerState<TFn>())\n key: string | undefined\n options: AsyncDebouncerOptions<TFn>\n asyncRetryers = new Map<number, AsyncRetryer<TFn>>()\n #timeoutId: NodeJS.Timeout | null = null\n #resolvePreviousPromise:\n | ((value?: ReturnType<TFn> | undefined) => void)\n | null = null\n\n constructor(\n public fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n ) {\n this.key = initialOptions.key\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n this.#setState(this.options.initialState ?? {})\n\n pacerEventClient.on('d-AsyncDebouncer', (event) => {\n if (event.payload.key !== this.key) return\n this.#setState(event.payload.store.state as AsyncDebouncerState<TFn>)\n this.setOptions(event.payload.options)\n })\n }\n\n /**\n * Updates the async debouncer options\n */\n setOptions = (newOptions: Partial<AsyncDebouncerOptions<TFn>>): void => {\n this.options = { ...this.options, ...newOptions }\n\n // Cancel pending execution if the debouncer is disabled\n if (!this.#getEnabled()) {\n this.cancel()\n }\n }\n\n #setState = (newState: Partial<AsyncDebouncerState<TFn>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n const { isPending, isExecuting, settleCount } = combinedState\n return {\n ...combinedState,\n status: !this.#getEnabled()\n ? 'disabled'\n : isPending\n ? 'pending'\n : isExecuting\n ? 'executing'\n : settleCount > 0\n ? 'settled'\n : 'idle',\n }\n })\n emitChange('AsyncDebouncer', this)\n }\n\n /**\n * Returns the current debouncer enabled state\n */\n #getEnabled = (): boolean => {\n return !!parseFunctionOrValue(this.options.enabled, this)\n }\n\n /**\n * Returns the current debouncer wait state\n */\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait, this)\n }\n\n /**\n * Attempts to execute the debounced function.\n * If a call is already in progress, it will be queued.\n *\n * Error Handling:\n * - If the debounced function throws and no `onError` handler is configured,\n * the error will be thrown from this method.\n * - If an `onError` handler is configured, errors will be caught and passed to the handler,\n * and this method will return undefined.\n * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.\n *\n * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError\n * @throws The error from the debounced function if no onError handler is configured\n */\n maybeExecute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n this.#cancelPendingExecution()\n this.#setState({\n lastArgs: args,\n maybeExecuteCount: this.store.state.maybeExecuteCount + 1,\n })\n\n // Handle leading execution\n if (this.options.leading && this.store.state.canLeadingExecute) {\n this.#setState({ canLeadingExecute: false })\n await this.#execute(...args)\n return this.store.state.lastResult\n }\n\n // Handle trailing execution\n if (this.options.trailing && this.#getEnabled()) {\n this.#setState({ isPending: true })\n }\n\n return new Promise((resolve, reject) => {\n this.#resolvePreviousPromise = resolve\n // this.#rejectPreviousPromise = reject\n this.#timeoutId = setTimeout(async () => {\n // Execute trailing if enabled\n if (this.options.trailing && this.store.state.lastArgs) {\n try {\n await this.#execute(...this.store.state.lastArgs)\n } catch (error) {\n reject(error)\n }\n }\n\n // Reset state and resolve\n this.#setState({ canLeadingExecute: true })\n this.#resolvePreviousPromise = null\n resolve(this.store.state.lastResult)\n }, this.#getWait())\n })\n }\n\n #execute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n const currentMaybeExecuteCount = this.store.state.maybeExecuteCount + 1\n\n try {\n this.#setState({ isExecuting: true })\n const currentAsyncRetryer = new AsyncRetryer(this.fn, {\n ...this.options.asyncRetryerOptions,\n key: `${this.key}-retryer-${currentMaybeExecuteCount}`,\n })\n this.asyncRetryers.set(currentMaybeExecuteCount, currentAsyncRetryer)\n const result = await currentAsyncRetryer.execute(...args) // EXECUTE!\n this.#setState({\n lastResult: result,\n successCount: this.store.state.successCount + 1,\n })\n this.options.onSuccess?.(result as ReturnType<TFn>, args, this)\n } catch (error) {\n this.#setState({\n errorCount: this.store.state.errorCount + 1,\n })\n this.options.onError?.(error as Error, args, this)\n if (this.options.throwOnError) {\n throw error\n }\n } finally {\n this.asyncRetryers.delete(currentMaybeExecuteCount) // dispose retryer\n this.#setState({\n isExecuting: false,\n isPending: false,\n lastArgs: undefined,\n settleCount: this.store.state.settleCount + 1,\n })\n this.options.onSettled?.(args, this)\n }\n return this.store.state.lastResult\n }\n\n /**\n * Processes the current pending execution immediately\n */\n flush = async (): Promise<ReturnType<TFn> | undefined> => {\n if (this.store.state.isPending && this.store.state.lastArgs) {\n const { lastArgs } = this.store.state\n this.#cancelPendingExecution()\n return await this.#execute(...lastArgs)\n }\n return undefined\n }\n\n #resolvePreviousPromiseInternal = (): void => {\n if (this.#resolvePreviousPromise) {\n this.#resolvePreviousPromise(this.store.state.lastResult)\n this.#resolvePreviousPromise = null\n }\n }\n\n #clearTimeout = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId)\n this.#timeoutId = null\n }\n }\n\n /**\n * Internal cancel without resetting the leading execute state\n */\n #cancelPendingExecution = (): void => {\n this.#clearTimeout()\n this.#resolvePreviousPromiseInternal()\n this.#setState({\n isPending: false,\n lastArgs: undefined,\n })\n }\n\n /**\n * Returns the AbortSignal for a specific execution.\n * If no maybeExecuteCount is provided, returns the signal for the most recent execution.\n * Returns null if no execution is found or not currently executing.\n *\n * @param maybeExecuteCount - Optional specific execution to get signal for\n * @example\n * ```typescript\n * const debouncer = new AsyncDebouncer(\n * async (searchTerm: string) => {\n * const signal = debouncer.getAbortSignal()\n * if (signal) {\n * const response = await fetch(`/api/search?q=${searchTerm}`, { signal })\n * return response.json()\n * }\n * },\n * { wait: 300 }\n * )\n * ```\n */\n getAbortSignal(maybeExecuteCount?: number): AbortSignal | null {\n const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount\n const retryer = this.asyncRetryers.get(count)\n return retryer?.getAbortSignal() ?? null\n }\n\n /**\n * Aborts all ongoing executions with the internal abort controllers.\n * Does NOT cancel any pending execution that have not started yet.\n */\n abort = (): void => {\n this.asyncRetryers.forEach((retryer) => retryer.abort())\n this.asyncRetryers.clear()\n this.#setState({\n isExecuting: false,\n })\n }\n\n /**\n * Cancels any pending execution that have not started yet.\n * Does NOT abort any execution already in progress.\n */\n cancel = (): void => {\n this.#cancelPendingExecution()\n this.#setState({ canLeadingExecute: true })\n }\n\n /**\n * Resets the debouncer state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncDebouncerState<TFn>())\n this.asyncRetryers.forEach((retryer) => retryer.reset())\n }\n}\n\n/**\n * Creates an async debounced function that delays execution until after a specified wait time.\n * The debounced function will only execute once the wait period has elapsed without any new calls.\n * If called again during the wait period, the timer resets and a new wait period begins.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync debounce function:\n * - Returns promises that can be awaited for debounced function results\n * - Built-in retry support via AsyncRetryer integration\n * - Abort support to cancel in-flight executions\n * - Cancel support to prevent pending executions from starting\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n *\n * The sync debounce function is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Debouncing?\n * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.\n * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing\n * or input changes where you only want to execute the handler after the events have stopped occurring.\n *\n * Configuration Options:\n * - `wait`: Delay in milliseconds to wait after the last call (required)\n * - `leading`: Execute on the leading edge of the timeout (default: false)\n * - `trailing`: Execute on the trailing edge of the timeout (default: true)\n * - `enabled`: Whether the debouncer is enabled (default: true)\n * - `asyncRetryerOptions`: Configure retry behavior for executions\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and debouncer instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - The error state can be checked using the underlying AsyncDebouncer instance\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the async debouncer\n * - Use `onSuccess` callback to react to successful function execution and implement custom logic\n * - Use `onError` callback to react to function execution errors and implement custom error handling\n * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic\n * - The state includes canLeadingExecute, error count, execution status, and success/settle counts\n * - State can be accessed via `asyncDebouncer.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncDebouncer.state`\n *\n * @example\n * ```ts\n * const debounced = asyncDebounce(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, {\n * wait: 1000,\n * onError: (error) => {\n * console.error('API call failed:', error);\n * },\n * throwOnError: true // Will both log the error and throw it\n * });\n *\n * // Will only execute once, 1 second after the last call\n * // Returns the API response directly\n * const result = await debounced(\"third\");\n * ```\n */\nexport function asyncDebounce<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncDebouncerOptions<TFn>,\n) {\n const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)\n return asyncDebouncer.maybeExecute\n}\n"],"names":[],"mappings":";;;;AAkDA,SAAS,gCAEqB;AAC5B,SAAO;AAAA,IACL,mBAAmB;AAAA,IACnB,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,cAAc;AAAA,EAAA;AAElB;AA0EO,SAAS,sBAKd,SAA6B;AAC7B,SAAO;AACT;AAOA,MAAM,iBAA6D;AAAA,EACjE,qBAAqB;AAAA,IACnB,aAAa;AAAA,EAAA;AAAA,EAEf,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AACR;AAwDO,MAAM,eAA6C;AAAA,EAYxD,YACS,IACP,gBACA;AAFO,SAAA,KAAA;AAZT,SAAS,QAAmD,IAAI,MAE9D,8BAAA,CAAoC;AAGtC,SAAA,oCAAoB,IAAA;AACpB,SAAA,aAAoC;AACpC,SAAA,0BAEW;AAwBX,SAAA,aAAa,CAAC,eAA0D;AACtE,WAAK,UAAU,EAAE,GAAG,KAAK,SAAS,GAAG,WAAA;AAGrC,UAAI,CAAC,KAAK,eAAe;AACvB,aAAK,OAAA;AAAA,MACP;AAAA,IACF;AAEA,SAAA,YAAY,CAAC,aAAsD;AACjE,WAAK,MAAM,SAAS,CAAC,UAAU;AAC7B,cAAM,gBAAgB;AAAA,UACpB,GAAG;AAAA,UACH,GAAG;AAAA,QAAA;AAEL,cAAM,EAAE,WAAW,aAAa,YAAA,IAAgB;AAChD,eAAO;AAAA,UACL,GAAG;AAAA,UACH,QAAQ,CAAC,KAAK,YAAA,IACV,aACA,YACE,YACA,cACE,cACA,cAAc,IACZ,YACA;AAAA,QAAA;AAAA,MAEd,CAAC;AACD,iBAAW,kBAAkB,IAAI;AAAA,IACnC;AAKA,SAAA,cAAc,MAAe;AAC3B,aAAO,CAAC,CAAC,qBAAqB,KAAK,QAAQ,SAAS,IAAI;AAAA,IAC1D;AAKA,SAAA,WAAW,MAAc;AACvB,aAAO,qBAAqB,KAAK,QAAQ,MAAM,IAAI;AAAA,IACrD;AAgBA,SAAA,eAAe,UACV,SACsC;AACzC,UAAI,CAAC,KAAK,YAAA,EAAe,QAAO;AAChC,WAAK,wBAAA;AACL,WAAK,UAAU;AAAA,QACb,UAAU;AAAA,QACV,mBAAmB,KAAK,MAAM,MAAM,oBAAoB;AAAA,MAAA,CACzD;AAGD,UAAI,KAAK,QAAQ,WAAW,KAAK,MAAM,MAAM,mBAAmB;AAC9D,aAAK,UAAU,EAAE,mBAAmB,MAAA,CAAO;AAC3C,cAAM,KAAK,SAAS,GAAG,IAAI;AAC3B,eAAO,KAAK,MAAM,MAAM;AAAA,MAC1B;AAGA,UAAI,KAAK,QAAQ,YAAY,KAAK,eAAe;AAC/C,aAAK,UAAU,EAAE,WAAW,KAAA,CAAM;AAAA,MACpC;AAEA,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,aAAK,0BAA0B;AAE/B,aAAK,aAAa,WAAW,YAAY;AAEvC,cAAI,KAAK,QAAQ,YAAY,KAAK,MAAM,MAAM,UAAU;AACtD,gBAAI;AACF,oBAAM,KAAK,SAAS,GAAG,KAAK,MAAM,MAAM,QAAQ;AAAA,YAClD,SAAS,OAAO;AACd,qBAAO,KAAK;AAAA,YACd;AAAA,UACF;AAGA,eAAK,UAAU,EAAE,mBAAmB,KAAA,CAAM;AAC1C,eAAK,0BAA0B;AAC/B,kBAAQ,KAAK,MAAM,MAAM,UAAU;AAAA,QACrC,GAAG,KAAK,UAAU;AAAA,MACpB,CAAC;AAAA,IACH;AAEA,SAAA,WAAW,UACN,SACsC;AACzC,UAAI,CAAC,KAAK,YAAA,EAAe,QAAO;AAChC,YAAM,2BAA2B,KAAK,MAAM,MAAM,oBAAoB;AAEtE,UAAI;AACF,aAAK,UAAU,EAAE,aAAa,KAAA,CAAM;AACpC,cAAM,sBAAsB,IAAI,aAAa,KAAK,IAAI;AAAA,UACpD,GAAG,KAAK,QAAQ;AAAA,UAChB,KAAK,GAAG,KAAK,GAAG,YAAY,wBAAwB;AAAA,QAAA,CACrD;AACD,aAAK,cAAc,IAAI,0BAA0B,mBAAmB;AACpE,cAAM,SAAS,MAAM,oBAAoB,QAAQ,GAAG,IAAI;AACxD,aAAK,UAAU;AAAA,UACb,YAAY;AAAA,UACZ,cAAc,KAAK,MAAM,MAAM,eAAe;AAAA,QAAA,CAC/C;AACD,aAAK,QAAQ,YAAY,QAA2B,MAAM,IAAI;AAAA,MAChE,SAAS,OAAO;AACd,aAAK,UAAU;AAAA,UACb,YAAY,KAAK,MAAM,MAAM,aAAa;AAAA,QAAA,CAC3C;AACD,aAAK,QAAQ,UAAU,OAAgB,MAAM,IAAI;AACjD,YAAI,KAAK,QAAQ,cAAc;AAC7B,gBAAM;AAAA,QACR;AAAA,MACF,UAAA;AACE,aAAK,cAAc,OAAO,wBAAwB;AAClD,aAAK,UAAU;AAAA,UACb,aAAa;AAAA,UACb,WAAW;AAAA,UACX,UAAU;AAAA,UACV,aAAa,KAAK,MAAM,MAAM,cAAc;AAAA,QAAA,CAC7C;AACD,aAAK,QAAQ,YAAY,MAAM,IAAI;AAAA,MACrC;AACA,aAAO,KAAK,MAAM,MAAM;AAAA,IAC1B;AAKA,SAAA,QAAQ,YAAkD;AACxD,UAAI,KAAK,MAAM,MAAM,aAAa,KAAK,MAAM,MAAM,UAAU;AAC3D,cAAM,EAAE,SAAA,IAAa,KAAK,MAAM;AAChC,aAAK,wBAAA;AACL,eAAO,MAAM,KAAK,SAAS,GAAG,QAAQ;AAAA,MACxC;AACA,aAAO;AAAA,IACT;AAEA,SAAA,kCAAkC,MAAY;AAC5C,UAAI,KAAK,yBAAyB;AAChC,aAAK,wBAAwB,KAAK,MAAM,MAAM,UAAU;AACxD,aAAK,0BAA0B;AAAA,MACjC;AAAA,IACF;AAEA,SAAA,gBAAgB,MAAY;AAC1B,UAAI,KAAK,YAAY;AACnB,qBAAa,KAAK,UAAU;AAC5B,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AAKA,SAAA,0BAA0B,MAAY;AACpC,WAAK,cAAA;AACL,WAAK,gCAAA;AACL,WAAK,UAAU;AAAA,QACb,WAAW;AAAA,QACX,UAAU;AAAA,MAAA,CACX;AAAA,IACH;AAgCA,SAAA,QAAQ,MAAY;AAClB,WAAK,cAAc,QAAQ,CAAC,YAAY,QAAQ,OAAO;AACvD,WAAK,cAAc,MAAA;AACnB,WAAK,UAAU;AAAA,QACb,aAAa;AAAA,MAAA,CACd;AAAA,IACH;AAMA,SAAA,SAAS,MAAY;AACnB,WAAK,wBAAA;AACL,WAAK,UAAU,EAAE,mBAAmB,KAAA,CAAM;AAAA,IAC5C;AAKA,SAAA,QAAQ,MAAY;AAClB,WAAK,UAAU,+BAAoC;AACnD,WAAK,cAAc,QAAQ,CAAC,YAAY,QAAQ,OAAO;AAAA,IACzD;AA5PE,SAAK,MAAM,eAAe;AAC1B,SAAK,UAAU;AAAA,MACb,GAAG;AAAA,MACH,GAAG;AAAA,MACH,cAAc,eAAe,gBAAgB,CAAC,eAAe;AAAA,IAAA;AAE/D,SAAK,UAAU,KAAK,QAAQ,gBAAgB,CAAA,CAAE;AAE9C,qBAAiB,GAAG,oBAAoB,CAAC,UAAU;AACjD,UAAI,MAAM,QAAQ,QAAQ,KAAK,IAAK;AACpC,WAAK,UAAU,MAAM,QAAQ,MAAM,KAAiC;AACpE,WAAK,WAAW,MAAM,QAAQ,OAAO;AAAA,IACvC,CAAC;AAAA,EACH;AAAA,EAtBA;AAAA,EACA;AAAA,EAmCA;AAAA,EA0BA;AAAA,EAOA;AAAA,EA6DA;AAAA,EAoDA;AAAA,EAOA;AAAA,EAUA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6BA,eAAe,mBAAgD;AAC7D,UAAM,QAAQ,qBAAqB,KAAK,MAAM,MAAM;AACpD,UAAM,UAAU,KAAK,cAAc,IAAI,KAAK;AAC5C,WAAO,SAAS,oBAAoB;AAAA,EACtC;AA8BF;AAkEO,SAAS,cACd,IACA,gBACA;AACA,QAAM,iBAAiB,IAAI,eAAe,IAAI,cAAc;AAC5D,SAAO,eAAe;AACxB;"}
@@ -1,4 +1,5 @@
1
1
  import { Store } from '@tanstack/store';
2
+ import { AsyncRetryer, AsyncRetryerOptions } from './async-retryer.js';
2
3
  import { QueuePosition } from './queuer.js';
3
4
  export interface AsyncQueuerState<TValue> {
4
5
  /**
@@ -13,6 +14,10 @@ export interface AsyncQueuerState<TValue> {
13
14
  * Number of task executions that have resulted in errors
14
15
  */
15
16
  errorCount: number;
17
+ /**
18
+ * Number of times execute has been called
19
+ */
20
+ executeCount: number;
16
21
  /**
17
22
  * Number of items that have been removed from the queue due to expiration
18
23
  */
@@ -21,6 +26,10 @@ export interface AsyncQueuerState<TValue> {
21
26
  * Whether the queuer has no items to process (items array is empty)
22
27
  */
23
28
  isEmpty: boolean;
29
+ /**
30
+ * Whether the queuer is currently executing
31
+ */
32
+ isExecuting: boolean;
24
33
  /**
25
34
  * Whether the queuer has reached its maximum capacity
26
35
  */
@@ -71,6 +80,10 @@ export interface AsyncQueuerState<TValue> {
71
80
  successCount: number;
72
81
  }
73
82
  export interface AsyncQueuerOptions<TValue> {
83
+ /**
84
+ * Options for configuring the underlying async retryer
85
+ */
86
+ asyncRetryerOptions?: AsyncRetryerOptions<(item: TValue) => Promise<any>>;
74
87
  /**
75
88
  * Default position to add items to the queuer
76
89
  * @default 'back'
@@ -125,7 +138,7 @@ export interface AsyncQueuerOptions<TValue> {
125
138
  * If provided, the handler will be called with the error and queuer instance.
126
139
  * This can be used alongside throwOnError - the handler will be called before any error is thrown.
127
140
  */
128
- onError?: (error: unknown, item: TValue, queuer: AsyncQueuer<TValue>) => void;
141
+ onError?: (error: Error, item: TValue, queuer: AsyncQueuer<TValue>) => void;
129
142
  /**
130
143
  * Callback fired whenever an item expires in the queuer
131
144
  */
@@ -163,21 +176,38 @@ export interface AsyncQueuerOptions<TValue> {
163
176
  */
164
177
  wait?: number | ((queuer: AsyncQueuer<TValue>) => number);
165
178
  }
179
+ /**
180
+ * Utility function for sharing common `AsyncQueuerOptions` options between different `AsyncQueuer` instances.
181
+ */
182
+ export declare function asyncQueuerOptions<TValue = any, TOptions extends Partial<AsyncQueuerOptions<TValue>> = Partial<AsyncQueuerOptions<TValue>>>(options: TOptions): TOptions;
166
183
  /**
167
184
  * A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration.
168
185
  *
169
- * Features:
186
+ * Async vs Sync Versions:
187
+ * The async version provides advanced features over the sync Queuer:
188
+ * - Returns promises that can be awaited for task results
189
+ * - Built-in retry support via AsyncRetryer integration for each queued task
190
+ * - Abort support to cancel in-flight task executions
191
+ * - Comprehensive error handling with onError callbacks and throwOnError control
192
+ * - Detailed execution tracking (success/error/settle counts)
193
+ * - Concurrent execution support (process multiple items simultaneously)
194
+ *
195
+ * The sync Queuer is lighter weight and simpler when you don't need async features,
196
+ * return values, or execution control.
197
+ *
198
+ * What is Queuing?
199
+ * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.
200
+ * Tasks are processed up to the configured concurrency limit. When a task completes,
201
+ * the next pending task is processed if the concurrency limit allows.
202
+ *
203
+ * Key Features:
170
204
  * - Priority queue support via the getPriority option
171
205
  * - Configurable concurrency limit
172
206
  * - Callbacks for task success, error, completion, and queue state changes
173
207
  * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior
174
208
  * - Pause and resume processing
175
- * - Task cancellation
176
209
  * - Item expiration to remove stale items from the queue
177
210
  *
178
- * Tasks are processed concurrently up to the configured concurrency limit. When a task completes,
179
- * the next pending task is processed if the concurrency limit allows.
180
- *
181
211
  * Error Handling:
182
212
  * - If an `onError` handler is provided, it will be called with the error and queuer instance
183
213
  * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
@@ -218,8 +248,9 @@ export declare class AsyncQueuer<TValue> {
218
248
  #private;
219
249
  fn: (item: TValue) => Promise<any>;
220
250
  readonly store: Store<Readonly<AsyncQueuerState<TValue>>>;
221
- key: string;
251
+ key: string | undefined;
222
252
  options: AsyncQueuerOptions<TValue>;
253
+ asyncRetryers: Map<number, AsyncRetryer<(item: TValue) => Promise<any>>>;
223
254
  constructor(fn: (item: TValue) => Promise<any>, initialOptions?: AsyncQueuerOptions<TValue>);
224
255
  /**
225
256
  * Updates the queuer options. New options are merged with existing options.
@@ -301,9 +332,36 @@ export declare class AsyncQueuer<TValue> {
301
332
  */
302
333
  stop: () => void;
303
334
  /**
304
- * Removes all pending items from the queue. Does not affect active tasks.
335
+ * Removes all pending items from the queue.
336
+ * Does NOT affect active tasks.
305
337
  */
306
338
  clear: () => void;
339
+ /**
340
+ * Returns the AbortSignal for a specific execution.
341
+ * If no executeCount is provided, returns the signal for the most recent execution.
342
+ * Returns null if no execution is found or not currently executing.
343
+ *
344
+ * @param executeCount - Optional specific execution to get signal for
345
+ * @example
346
+ * ```typescript
347
+ * const queuer = new AsyncQueuer(
348
+ * async (item: string) => {
349
+ * const signal = queuer.getAbortSignal()
350
+ * if (signal) {
351
+ * const response = await fetch(`/api/process/${item}`, { signal })
352
+ * return response.json()
353
+ * }
354
+ * },
355
+ * { concurrency: 2 }
356
+ * )
357
+ * ```
358
+ */
359
+ getAbortSignal(executeCount?: number): AbortSignal | null;
360
+ /**
361
+ * Aborts all ongoing executions with the internal abort controllers.
362
+ * Does NOT clear out the items.
363
+ */
364
+ abort: () => void;
307
365
  /**
308
366
  * Resets the queuer state to its default values
309
367
  */
@@ -313,6 +371,34 @@ export declare class AsyncQueuer<TValue> {
313
371
  * Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks.
314
372
  * The queuer is started automatically and ready to process items.
315
373
  *
374
+ * Async vs Sync Versions:
375
+ * The async version provides advanced features over the sync queue function:
376
+ * - Returns promises that can be awaited for task results
377
+ * - Built-in retry support via AsyncRetryer integration for each queued task
378
+ * - Abort support to cancel in-flight task executions
379
+ * - Comprehensive error handling with onError callbacks and throwOnError control
380
+ * - Detailed execution tracking (success/error/settle counts)
381
+ * - Concurrent execution support (process multiple items simultaneously)
382
+ *
383
+ * The sync queue function is lighter weight and simpler when you don't need async features,
384
+ * return values, or execution control.
385
+ *
386
+ * What is Queuing?
387
+ * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.
388
+ * Tasks are processed up to the configured concurrency limit. When a task completes,
389
+ * the next pending task is processed if the concurrency limit allows.
390
+ *
391
+ * Configuration Options:
392
+ * - `concurrency`: Maximum number of concurrent tasks (default: 1)
393
+ * - `wait`: Time to wait between processing items (default: 0)
394
+ * - `maxSize`: Maximum number of items allowed in the queue (default: Infinity)
395
+ * - `getPriority`: Function to determine item priority
396
+ * - `addItemsTo`: Default position to add items ('back' or 'front', default: 'back')
397
+ * - `getItemsFrom`: Default position to get items ('front' or 'back', default: 'front')
398
+ * - `expirationDuration`: Maximum time items can stay in queue
399
+ * - `started`: Whether to start processing immediately (default: true)
400
+ * - `asyncRetryerOptions`: Configure retry behavior for task executions
401
+ *
316
402
  * Error Handling:
317
403
  * - If an `onError` handler is provided, it will be called with the error and queuer instance
318
404
  * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
@@ -333,11 +419,15 @@ export declare class AsyncQueuer<TValue> {
333
419
  * - State can be accessed via the underlying AsyncQueuer instance's `store.state` property
334
420
  * - When using framework adapters (React/Solid), state is accessed from the hook's state property
335
421
  *
336
- * Example usage:
422
+ * @example
337
423
  * ```ts
338
424
  * const enqueue = asyncQueue<string>(async (item) => {
339
425
  * return item.toUpperCase();
340
- * }, {...options});
426
+ * }, {
427
+ * concurrency: 2,
428
+ * wait: 100,
429
+ * onSuccess: (result) => console.log('Processed:', result)
430
+ * });
341
431
  *
342
432
  * enqueue('hello');
343
433
  * ```