@tanstack/pacer 0.16.3 → 0.17.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 (169) hide show
  1. package/README.md +165 -0
  2. package/dist/async-batcher.cjs +355 -0
  3. package/dist/async-batcher.cjs.map +1 -0
  4. package/dist/async-batcher.d.cts +344 -0
  5. package/dist/async-batcher.d.ts +344 -0
  6. package/dist/async-batcher.js +353 -0
  7. package/dist/async-batcher.js.map +1 -0
  8. package/dist/async-debouncer.cjs +340 -0
  9. package/dist/async-debouncer.cjs.map +1 -0
  10. package/dist/async-debouncer.d.cts +300 -0
  11. package/dist/async-debouncer.d.ts +300 -0
  12. package/dist/async-debouncer.js +338 -0
  13. package/dist/async-debouncer.js.map +1 -0
  14. package/dist/async-queuer.cjs +496 -0
  15. package/dist/async-queuer.cjs.map +1 -0
  16. package/dist/async-queuer.d.cts +440 -0
  17. package/dist/async-queuer.d.ts +440 -0
  18. package/dist/async-queuer.js +494 -0
  19. package/dist/async-queuer.js.map +1 -0
  20. package/dist/async-rate-limiter.cjs +381 -0
  21. package/dist/async-rate-limiter.cjs.map +1 -0
  22. package/dist/{cjs/async-rate-limiter.d.cts → async-rate-limiter.d.cts} +190 -185
  23. package/dist/{esm/async-rate-limiter.d.ts → async-rate-limiter.d.ts} +190 -185
  24. package/dist/async-rate-limiter.js +379 -0
  25. package/dist/async-rate-limiter.js.map +1 -0
  26. package/dist/async-retryer.cjs +378 -0
  27. package/dist/async-retryer.cjs.map +1 -0
  28. package/dist/async-retryer.d.cts +312 -0
  29. package/dist/async-retryer.d.ts +312 -0
  30. package/dist/async-retryer.js +376 -0
  31. package/dist/async-retryer.js.map +1 -0
  32. package/dist/async-throttler.cjs +362 -0
  33. package/dist/async-throttler.cjs.map +1 -0
  34. package/dist/async-throttler.d.cts +320 -0
  35. package/dist/async-throttler.d.ts +320 -0
  36. package/dist/async-throttler.js +360 -0
  37. package/dist/async-throttler.js.map +1 -0
  38. package/dist/batcher.cjs +194 -0
  39. package/dist/batcher.cjs.map +1 -0
  40. package/dist/batcher.d.cts +180 -0
  41. package/dist/batcher.d.ts +180 -0
  42. package/dist/batcher.js +193 -0
  43. package/dist/batcher.js.map +1 -0
  44. package/dist/debouncer.cjs +197 -0
  45. package/dist/debouncer.cjs.map +1 -0
  46. package/dist/debouncer.d.cts +167 -0
  47. package/dist/debouncer.d.ts +167 -0
  48. package/dist/debouncer.js +195 -0
  49. package/dist/debouncer.js.map +1 -0
  50. package/dist/event-client.cjs +20 -0
  51. package/dist/event-client.cjs.map +1 -0
  52. package/dist/event-client.d.cts +49 -0
  53. package/dist/event-client.d.ts +49 -0
  54. package/dist/event-client.js +19 -0
  55. package/dist/event-client.js.map +1 -0
  56. package/dist/index.cjs +49 -0
  57. package/dist/index.d.cts +15 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/{esm/index.js → index.js} +5 -41
  60. package/dist/queuer.cjs +393 -0
  61. package/dist/queuer.cjs.map +1 -0
  62. package/dist/queuer.d.cts +345 -0
  63. package/dist/queuer.d.ts +345 -0
  64. package/dist/queuer.js +391 -0
  65. package/dist/queuer.js.map +1 -0
  66. package/dist/rate-limiter.cjs +251 -0
  67. package/dist/rate-limiter.cjs.map +1 -0
  68. package/dist/{cjs/rate-limiter.d.cts → rate-limiter.d.cts} +113 -108
  69. package/dist/{esm/rate-limiter.d.ts → rate-limiter.d.ts} +113 -108
  70. package/dist/rate-limiter.js +249 -0
  71. package/dist/rate-limiter.js.map +1 -0
  72. package/dist/throttler.cjs +209 -0
  73. package/dist/throttler.cjs.map +1 -0
  74. package/dist/throttler.d.cts +207 -0
  75. package/dist/throttler.d.ts +207 -0
  76. package/dist/throttler.js +207 -0
  77. package/dist/throttler.js.map +1 -0
  78. package/dist/types.cjs +0 -0
  79. package/dist/types.d.cts +13 -0
  80. package/dist/types.d.ts +13 -0
  81. package/dist/types.js +1 -0
  82. package/dist/utils.cjs +13 -0
  83. package/dist/utils.cjs.map +1 -0
  84. package/dist/utils.d.cts +8 -0
  85. package/dist/utils.d.ts +8 -0
  86. package/dist/utils.js +11 -0
  87. package/dist/utils.js.map +1 -0
  88. package/package.json +39 -123
  89. package/dist/cjs/async-batcher.cjs +0 -222
  90. package/dist/cjs/async-batcher.cjs.map +0 -1
  91. package/dist/cjs/async-batcher.d.cts +0 -340
  92. package/dist/cjs/async-debouncer.cjs +0 -233
  93. package/dist/cjs/async-debouncer.cjs.map +0 -1
  94. package/dist/cjs/async-debouncer.d.cts +0 -295
  95. package/dist/cjs/async-queuer.cjs +0 -399
  96. package/dist/cjs/async-queuer.cjs.map +0 -1
  97. package/dist/cjs/async-queuer.d.cts +0 -435
  98. package/dist/cjs/async-rate-limiter.cjs +0 -254
  99. package/dist/cjs/async-rate-limiter.cjs.map +0 -1
  100. package/dist/cjs/async-retryer.cjs +0 -287
  101. package/dist/cjs/async-retryer.cjs.map +0 -1
  102. package/dist/cjs/async-retryer.d.cts +0 -308
  103. package/dist/cjs/async-throttler.cjs +0 -263
  104. package/dist/cjs/async-throttler.cjs.map +0 -1
  105. package/dist/cjs/async-throttler.d.cts +0 -315
  106. package/dist/cjs/batcher.cjs +0 -132
  107. package/dist/cjs/batcher.cjs.map +0 -1
  108. package/dist/cjs/batcher.d.cts +0 -176
  109. package/dist/cjs/debouncer.cjs +0 -139
  110. package/dist/cjs/debouncer.cjs.map +0 -1
  111. package/dist/cjs/debouncer.d.cts +0 -162
  112. package/dist/cjs/event-client.cjs +0 -20
  113. package/dist/cjs/event-client.cjs.map +0 -1
  114. package/dist/cjs/event-client.d.cts +0 -45
  115. package/dist/cjs/index.cjs +0 -51
  116. package/dist/cjs/index.cjs.map +0 -1
  117. package/dist/cjs/index.d.cts +0 -15
  118. package/dist/cjs/queuer.cjs +0 -308
  119. package/dist/cjs/queuer.cjs.map +0 -1
  120. package/dist/cjs/queuer.d.cts +0 -340
  121. package/dist/cjs/rate-limiter.cjs +0 -181
  122. package/dist/cjs/rate-limiter.cjs.map +0 -1
  123. package/dist/cjs/throttler.cjs +0 -153
  124. package/dist/cjs/throttler.cjs.map +0 -1
  125. package/dist/cjs/throttler.d.cts +0 -202
  126. package/dist/cjs/types.d.cts +0 -9
  127. package/dist/cjs/utils.cjs +0 -11
  128. package/dist/cjs/utils.cjs.map +0 -1
  129. package/dist/cjs/utils.d.cts +0 -3
  130. package/dist/esm/async-batcher.d.ts +0 -340
  131. package/dist/esm/async-batcher.js +0 -222
  132. package/dist/esm/async-batcher.js.map +0 -1
  133. package/dist/esm/async-debouncer.d.ts +0 -295
  134. package/dist/esm/async-debouncer.js +0 -233
  135. package/dist/esm/async-debouncer.js.map +0 -1
  136. package/dist/esm/async-queuer.d.ts +0 -435
  137. package/dist/esm/async-queuer.js +0 -399
  138. package/dist/esm/async-queuer.js.map +0 -1
  139. package/dist/esm/async-rate-limiter.js +0 -254
  140. package/dist/esm/async-rate-limiter.js.map +0 -1
  141. package/dist/esm/async-retryer.d.ts +0 -308
  142. package/dist/esm/async-retryer.js +0 -287
  143. package/dist/esm/async-retryer.js.map +0 -1
  144. package/dist/esm/async-throttler.d.ts +0 -315
  145. package/dist/esm/async-throttler.js +0 -263
  146. package/dist/esm/async-throttler.js.map +0 -1
  147. package/dist/esm/batcher.d.ts +0 -176
  148. package/dist/esm/batcher.js +0 -132
  149. package/dist/esm/batcher.js.map +0 -1
  150. package/dist/esm/debouncer.d.ts +0 -162
  151. package/dist/esm/debouncer.js +0 -139
  152. package/dist/esm/debouncer.js.map +0 -1
  153. package/dist/esm/event-client.d.ts +0 -45
  154. package/dist/esm/event-client.js +0 -20
  155. package/dist/esm/event-client.js.map +0 -1
  156. package/dist/esm/index.d.ts +0 -15
  157. package/dist/esm/index.js.map +0 -1
  158. package/dist/esm/queuer.d.ts +0 -340
  159. package/dist/esm/queuer.js +0 -308
  160. package/dist/esm/queuer.js.map +0 -1
  161. package/dist/esm/rate-limiter.js +0 -181
  162. package/dist/esm/rate-limiter.js.map +0 -1
  163. package/dist/esm/throttler.d.ts +0 -202
  164. package/dist/esm/throttler.js +0 -153
  165. package/dist/esm/throttler.js.map +0 -1
  166. package/dist/esm/types.d.ts +0 -9
  167. package/dist/esm/utils.d.ts +0 -3
  168. package/dist/esm/utils.js +0 -11
  169. package/dist/esm/utils.js.map +0 -1
@@ -1,308 +0,0 @@
1
- import { Store } from '@tanstack/store';
2
- import { AnyAsyncFunction } from './types.cjs';
3
- export interface AsyncRetryerState<TFn extends AnyAsyncFunction> {
4
- /**
5
- * The current retry attempt number (0 when not executing)
6
- */
7
- currentAttempt: number;
8
- /**
9
- * Total number of completed executions (successful or failed)
10
- */
11
- executionCount: number;
12
- /**
13
- * Whether the retryer is currently executing the function
14
- */
15
- isExecuting: boolean;
16
- /**
17
- * The most recent error encountered during execution
18
- */
19
- lastError: Error | undefined;
20
- /**
21
- * Timestamp of the last execution completion in milliseconds
22
- */
23
- lastExecutionTime: number;
24
- /**
25
- * The result from the most recent successful execution
26
- */
27
- lastResult: Awaited<ReturnType<TFn>> | undefined;
28
- /**
29
- * Current execution status - 'disabled' when not enabled, 'idle' when ready, 'executing' when running
30
- */
31
- status: 'disabled' | 'idle' | 'executing' | 'retrying';
32
- /**
33
- * Total time spent executing (including retries) in milliseconds
34
- */
35
- totalExecutionTime: number;
36
- }
37
- export interface AsyncRetryerOptions<TFn extends AnyAsyncFunction> {
38
- /**
39
- * The backoff strategy for retry delays:
40
- * - 'exponential': Wait time doubles with each attempt (1s, 2s, 4s, ...)
41
- * - 'linear': Wait time increases linearly (1s, 2s, 3s, ...)
42
- * - 'fixed': Same wait time for all attempts
43
- * @default 'exponential'
44
- */
45
- backoff?: 'linear' | 'exponential' | 'fixed';
46
- /**
47
- * Base wait time in milliseconds between retries, or a function that returns the wait time
48
- * @default 1000
49
- */
50
- baseWait?: number | ((retryer: AsyncRetryer<TFn>) => number);
51
- /**
52
- * Whether the retryer is enabled, or a function that determines if it's enabled
53
- * @default true
54
- */
55
- enabled?: boolean | ((retryer: AsyncRetryer<TFn>) => boolean);
56
- /**
57
- * Initial state to merge with the default state
58
- */
59
- initialState?: Partial<AsyncRetryerState<TFn>>;
60
- /**
61
- * Jitter percentage to add to retry delays (0-1). Adds randomness to prevent thundering herd.
62
- * @default 0
63
- */
64
- jitter?: number;
65
- /**
66
- * Optional key to identify this async retryer instance.
67
- * If provided, the async retryer will be identified by this key in the devtools and PacerProvider if applicable.
68
- */
69
- key?: string;
70
- /**
71
- * Maximum number of retry attempts, or a function that returns the max attempts
72
- * @default 3
73
- */
74
- maxAttempts?: number | ((retryer: AsyncRetryer<TFn>) => number);
75
- /**
76
- * Maximum execution time in milliseconds for a single function call before aborting
77
- * @default Infinity
78
- */
79
- maxExecutionTime?: number;
80
- /**
81
- * Maximum total execution time in milliseconds for the entire retry operation before aborting
82
- * @default Infinity
83
- */
84
- maxTotalExecutionTime?: number;
85
- /**
86
- * Callback invoked when the execution is aborted (manually or due to timeouts)
87
- */
88
- onAbort?: (reason: 'manual' | 'execution-timeout' | 'total-timeout' | 'new-execution', retryer: AsyncRetryer<TFn>) => void;
89
- /**
90
- * Callback invoked when any error occurs during execution (including retries)
91
- */
92
- onError?: (error: Error, args: Parameters<TFn>, retryer: AsyncRetryer<TFn>) => void;
93
- /**
94
- * Callback invoked when the final error occurs after all retries are exhausted
95
- */
96
- onLastError?: (error: Error, retryer: AsyncRetryer<TFn>) => void;
97
- /**
98
- * Callback invoked before each retry attempt
99
- */
100
- onRetry?: (attempt: number, error: Error, retryer: AsyncRetryer<TFn>) => void;
101
- /**
102
- * Callback invoked after execution completes (success or failure) of each attempt
103
- */
104
- onSettled?: (args: Parameters<TFn>, retryer: AsyncRetryer<TFn>) => void;
105
- /**
106
- * Callback invoked when execution succeeds
107
- */
108
- onSuccess?: (result: Awaited<ReturnType<TFn>>, args: Parameters<TFn>, retryer: AsyncRetryer<TFn>) => void;
109
- /**
110
- * Callback invoked when a single execution attempt times out (maxExecutionTime exceeded)
111
- */
112
- onExecutionTimeout?: (retryer: AsyncRetryer<TFn>) => void;
113
- /**
114
- * Callback invoked when the total execution time times out (maxTotalExecutionTime exceeded)
115
- */
116
- onTotalExecutionTimeout?: (retryer: AsyncRetryer<TFn>) => void;
117
- /**
118
- * Controls when errors are thrown:
119
- * - 'last': Only throw the final error after all retries are exhausted
120
- * - true: Throw every error immediately (disables retrying)
121
- * - false: Never throw errors, return undefined instead
122
- * @default 'last'
123
- */
124
- throwOnError?: boolean | 'last';
125
- }
126
- /**
127
- * Utility function for sharing common `AsyncRetryerOptions` options between different `AsyncRetryer` instances.
128
- */
129
- export declare function asyncRetryerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncRetryerOptions<TFn>> = Partial<AsyncRetryerOptions<TFn>>>(options: TOptions): TOptions;
130
- declare const defaultOptions: Omit<Required<AsyncRetryerOptions<any>>, 'initialState' | 'key' | 'onAbort' | 'onError' | 'onLastError' | 'onRetry' | 'onSettled' | 'onSuccess' | 'onExecutionTimeout' | 'onTotalExecutionTimeout'>;
131
- /**
132
- * Provides robust retry functionality for asynchronous functions, supporting configurable backoff strategies,
133
- * attempt limits, timeout controls, and detailed state management. The AsyncRetryer class is designed to help you reliably
134
- * execute async operations that may fail intermittently, such as network requests or database operations,
135
- * by automatically retrying them according to your chosen policy.
136
- *
137
- * ## Retrying Concepts
138
- *
139
- * - **Retrying**: Automatically re-executes a failed async function up to a specified number of attempts.
140
- * Useful for handling transient errors (e.g., network flakiness, rate limits, temporary server issues).
141
- * - **Backoff Strategies**: Controls the delay between retry attempts (default: `'exponential'`):
142
- * - `'exponential'`: Wait time doubles with each attempt (1s, 2s, 4s, ...) - **DEFAULT**
143
- * - `'linear'`: Wait time increases linearly (1s, 2s, 3s, ...)
144
- * - `'fixed'`: Waits a constant amount of time (`baseWait`) between each attempt
145
- * - **Jitter**: Adds randomness to retry delays to prevent thundering herd problems (default: `0`).
146
- * Set to a value between 0-1 to apply that percentage of random variation to each delay.
147
- * - **Timeout Controls**: Set limits on execution time to prevent hanging operations:
148
- * - `maxExecutionTime`: Maximum time for a single function call (default: `Infinity`)
149
- * - `maxTotalExecutionTime`: Maximum time for the entire retry operation (default: `Infinity`)
150
- * - **Abort & Cancellation**: Supports cancellation via an internal `AbortController`. Call `abort()` to stop retries.
151
- * Use `getAbortSignal()` to make your async function actually cancellable (e.g., with fetch requests).
152
- *
153
- * ## State Management
154
- *
155
- * Uses TanStack Store for fine-grained reactivity. State can be accessed via the `store.state` property.
156
- *
157
- * Available state properties:
158
- * - `currentAttempt`: The current retry attempt number (0 when not executing)
159
- * - `executionCount`: Total number of completed executions (successful or failed)
160
- * - `isExecuting`: Whether the retryer is currently executing the function
161
- * - `lastError`: The most recent error encountered during execution
162
- * - `lastExecutionTime`: Timestamp of the last execution completion in milliseconds
163
- * - `lastResult`: The result from the most recent successful execution
164
- * - `status`: Current execution status ('disabled' | 'idle' | 'executing' | 'retrying')
165
- * - `totalExecutionTime`: Total time spent executing (including retries) in milliseconds
166
- *
167
- * ## Error Handling
168
- *
169
- * The `throwOnError` option controls when errors are thrown (default: `'last'`):
170
- * - `'last'`: Only throws the final error after all retries are exhausted - **DEFAULT**
171
- * - `true`: Throws every error immediately (disables retrying)
172
- * - `false`: Never throws errors, returns `undefined` instead
173
- *
174
- * Callbacks for lifecycle management:
175
- * - `onAbort`: Called when execution is aborted (manually or due to timeouts)
176
- * - `onError`: Called for every error (including during retries)
177
- * - `onLastError`: Called only for the final error after all retries fail
178
- * - `onRetry`: Called before each retry attempt
179
- * - `onSettled`: Called after execution completes (success or failure) of each attempt
180
- * - `onSuccess`: Called when execution succeeds
181
- * - `onExecutionTimeout`: Called when a single execution attempt times out
182
- * - `onTotalExecutionTimeout`: Called when the total execution time times out
183
- *
184
- * ## Usage
185
- *
186
- * - Use for async operations that may fail transiently and benefit from retrying.
187
- * - Configure `maxAttempts`, `backoff`, `baseWait`, and `jitter` to control retry behavior.
188
- * - Set `maxExecutionTime` and `maxTotalExecutionTime` to prevent hanging operations.
189
- * - Use `onAbort`, `onError`, `onLastError`, `onRetry`, `onSettled`, `onSuccess`, `onExecutionTimeout`, and `onTotalExecutionTimeout` for custom side effects.
190
- * - Call `abort()` to cancel ongoing execution and pending retries.
191
- * - Call `reset()` to reset state and cancel execution.
192
- * - Use `getAbortSignal()` to make your async function cancellable.
193
- * - Use dynamic options (functions) for `maxAttempts`, `baseWait`, and `enabled` based on retryer state.
194
- *
195
- * **Important:** This class is designed for single-use execution. Calling `execute()` multiple times
196
- * on the same instance will abort previous executions. For multiple calls, create a new instance
197
- * each time.
198
- *
199
- * @example
200
- * ```typescript
201
- * // Retry a fetch operation up to 5 times with exponential backoff, jitter, and timeouts
202
- * const retryer = new AsyncRetryer(async (url: string) => {
203
- * const signal = retryer.getAbortSignal()
204
- * return await fetch(url, { signal })
205
- * }, {
206
- * maxAttempts: 5,
207
- * backoff: 'exponential',
208
- * baseWait: 1000,
209
- * jitter: 0.1, // Add 10% random variation to prevent thundering herd
210
- * maxExecutionTime: 5000, // Abort individual calls after 5 seconds
211
- * maxTotalExecutionTime: 30000, // Abort entire operation after 30 seconds
212
- * onRetry: (attempt, error) => console.log(`Retry attempt ${attempt} after error:`, error),
213
- * onSuccess: (result) => console.log('Success:', result),
214
- * onError: (error) => console.error('Error:', error),
215
- * onLastError: (error) => console.error('All retries failed:', error),
216
- * })
217
- *
218
- * const result = await retryer.execute('/api/data')
219
- * ```
220
- *
221
- * @template TFn The async function type to be retried.
222
- */
223
- export declare class AsyncRetryer<TFn extends AnyAsyncFunction> {
224
- #private;
225
- fn: TFn;
226
- readonly store: Store<Readonly<AsyncRetryerState<TFn>>>;
227
- key: string | undefined;
228
- options: AsyncRetryerOptions<TFn> & typeof defaultOptions;
229
- /**
230
- * Creates a new AsyncRetryer instance
231
- * @param fn The async function to retry
232
- * @param initialOptions Configuration options for the retryer
233
- */
234
- constructor(fn: TFn, initialOptions?: AsyncRetryerOptions<TFn>);
235
- /**
236
- * Updates the retryer options
237
- * @param newOptions Partial options to merge with existing options
238
- */
239
- setOptions: (newOptions: Partial<AsyncRetryerOptions<TFn>>) => void;
240
- /**
241
- * Executes the function with retry logic
242
- * @param args Arguments to pass to the function
243
- * @returns The function result, or undefined if disabled or all retries failed (when throwOnError is false)
244
- * @throws The last error if throwOnError is true and all retries fail
245
- */
246
- execute: (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
247
- /**
248
- * Returns the current AbortSignal for the executing operation.
249
- * Use this signal in your async function to make it cancellable.
250
- * Returns null when not currently executing.
251
- *
252
- * @example
253
- * ```typescript
254
- * const retryer = new AsyncRetryer(async (userId: string) => {
255
- * const signal = retryer.getAbortSignal()
256
- * if (signal) {
257
- * return fetch(`/api/users/${userId}`, { signal })
258
- * }
259
- * return fetch(`/api/users/${userId}`)
260
- * })
261
- *
262
- * // Abort will now actually cancel the fetch
263
- * retryer.abort()
264
- * ```
265
- */
266
- getAbortSignal(): AbortSignal | null;
267
- /**
268
- * Cancels the current execution and any pending retries
269
- * @param reason The reason for the abort (defaults to 'manual')
270
- */
271
- abort: (reason?: "manual" | "execution-timeout" | "total-timeout" | "new-execution") => void;
272
- /**
273
- * Resets the retryer to its initial state
274
- */
275
- reset: () => void;
276
- }
277
- /**
278
- * Creates a retry-enabled version of an async function. This is a convenience wrapper
279
- * around the AsyncRetryer class that returns the execute method.
280
- *
281
- * @param fn The async function to add retry functionality to
282
- * @param initialOptions Configuration options for the retry behavior
283
- * @returns A new function that executes the original with retry logic
284
- *
285
- * @example
286
- * ```typescript
287
- * // Define your async function normally
288
- * async function fetchData(url: string) {
289
- * const response = await fetch(url)
290
- * if (!response.ok) throw new Error('Request failed')
291
- * return response.json()
292
- * }
293
- *
294
- * // Create retry-enabled function
295
- * const fetchWithRetry = asyncRetry(fetchData, {
296
- * maxAttempts: 3,
297
- * backoff: 'exponential',
298
- * baseWait: 1000,
299
- * jitter: 0.1
300
- * })
301
- *
302
- * // Call it multiple times
303
- * const data1 = await fetchWithRetry('/api/data1')
304
- * const data2 = await fetchWithRetry('/api/data2')
305
- * ```
306
- */
307
- export declare function asyncRetry<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions?: AsyncRetryerOptions<TFn>): (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
308
- export {};
@@ -1,263 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const store = require("@tanstack/store");
4
- const asyncRetryer = require("./async-retryer.cjs");
5
- const utils = require("./utils.cjs");
6
- const eventClient = require("./event-client.cjs");
7
- function getDefaultAsyncThrottlerState() {
8
- return {
9
- errorCount: 0,
10
- isExecuting: false,
11
- isPending: false,
12
- lastArgs: void 0,
13
- lastExecutionTime: 0,
14
- lastResult: void 0,
15
- maybeExecuteCount: 0,
16
- nextExecutionTime: void 0,
17
- settleCount: 0,
18
- status: "idle",
19
- successCount: 0
20
- };
21
- }
22
- function asyncThrottlerOptions(options) {
23
- return options;
24
- }
25
- const defaultOptions = {
26
- asyncRetryerOptions: {
27
- maxAttempts: 1
28
- },
29
- enabled: true,
30
- leading: true,
31
- trailing: true,
32
- wait: 0
33
- };
34
- class AsyncThrottler {
35
- constructor(fn, initialOptions) {
36
- this.fn = fn;
37
- this.store = new store.Store(getDefaultAsyncThrottlerState());
38
- this.asyncRetryers = /* @__PURE__ */ new Map();
39
- this.#timeoutId = null;
40
- this.#resolvePreviousPromise = null;
41
- this.setOptions = (newOptions) => {
42
- this.options = { ...this.options, ...newOptions };
43
- if (!this.#getEnabled()) {
44
- this.cancel();
45
- }
46
- };
47
- this.#setState = (newState) => {
48
- this.store.setState((state) => {
49
- const combinedState = {
50
- ...state,
51
- ...newState
52
- };
53
- const { isPending, isExecuting, settleCount } = combinedState;
54
- return {
55
- ...combinedState,
56
- status: !this.#getEnabled() ? "disabled" : isPending ? "pending" : isExecuting ? "executing" : settleCount > 0 ? "settled" : "idle"
57
- };
58
- });
59
- eventClient.emitChange("AsyncThrottler", this);
60
- };
61
- this.#getEnabled = () => {
62
- return !!utils.parseFunctionOrValue(this.options.enabled, this);
63
- };
64
- this.#getWait = () => {
65
- return utils.parseFunctionOrValue(this.options.wait, this);
66
- };
67
- this.maybeExecute = async (...args) => {
68
- if (!this.#getEnabled()) return void 0;
69
- this.#resolvePreviousPromiseInternal();
70
- this.#setState({
71
- maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
72
- lastArgs: args
73
- // store the arguments for potential trailing execution
74
- });
75
- const wait = this.#getWait();
76
- const thisMaybeExecuteNumber = this.store.state.maybeExecuteCount;
77
- for (let maxNumIterations = wait / 10; this.store.state.isExecuting && maxNumIterations > 0; maxNumIterations--) {
78
- await new Promise((resolve) => setTimeout(resolve, 10));
79
- if (this.store.state.maybeExecuteCount !== thisMaybeExecuteNumber) {
80
- return this.store.state.lastResult;
81
- }
82
- }
83
- const now = Date.now();
84
- const timeSinceLastExecution = now - this.store.state.lastExecutionTime;
85
- if (this.options.leading && !this.store.state.isPending && timeSinceLastExecution >= wait) {
86
- await this.#execute(...args);
87
- } else if (this.options.trailing) {
88
- this.cancel();
89
- this.#setState({
90
- isPending: true
91
- });
92
- return new Promise((resolve, reject) => {
93
- this.#resolvePreviousPromise = resolve;
94
- const newTimeSinceLastExecution = this.store.state.lastExecutionTime ? now - this.store.state.lastExecutionTime : 0;
95
- const timeoutDuration = Math.max(0, wait - newTimeSinceLastExecution);
96
- this.#timeoutId = setTimeout(async () => {
97
- this.#clearTimeout();
98
- if (this.store.state.lastArgs !== void 0) {
99
- try {
100
- await this.#execute(...this.store.state.lastArgs);
101
- } catch (error) {
102
- reject(error);
103
- }
104
- }
105
- this.#resolvePreviousPromise = null;
106
- resolve(this.store.state.lastResult);
107
- }, timeoutDuration);
108
- });
109
- }
110
- return this.store.state.lastResult;
111
- };
112
- this.#execute = async (...args) => {
113
- if (!this.#getEnabled()) return void 0;
114
- const currentMaybeExecute = this.store.state.maybeExecuteCount;
115
- try {
116
- this.#setState({ isExecuting: true });
117
- const currentAsyncRetryer = new asyncRetryer.AsyncRetryer(this.fn, {
118
- ...this.options.asyncRetryerOptions,
119
- key: `${this.key}-retryer-${currentMaybeExecute}`
120
- });
121
- this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer);
122
- const result = await currentAsyncRetryer.execute(...args);
123
- this.#setState({
124
- lastResult: result,
125
- successCount: this.store.state.successCount + 1
126
- });
127
- this.options.onSuccess?.(result, args, this);
128
- } catch (error) {
129
- this.#setState({
130
- errorCount: this.store.state.errorCount + 1
131
- });
132
- this.options.onError?.(error, args, this);
133
- if (this.options.throwOnError) {
134
- throw error;
135
- }
136
- } finally {
137
- this.asyncRetryers.delete(currentMaybeExecute);
138
- const lastExecutionTime = Date.now();
139
- const wait = this.#getWait();
140
- const nextExecutionTime = lastExecutionTime + wait;
141
- this.#setState({
142
- isExecuting: false,
143
- isPending: !!this.#timeoutId,
144
- settleCount: this.store.state.settleCount + 1,
145
- lastExecutionTime,
146
- nextExecutionTime
147
- });
148
- this.options.onSettled?.(args, this);
149
- setTimeout(() => {
150
- if (!this.store.state.isPending) {
151
- this.#setState({ nextExecutionTime: void 0 });
152
- }
153
- }, wait);
154
- }
155
- return this.store.state.lastResult;
156
- };
157
- this.flush = async () => {
158
- if (this.store.state.isPending && this.store.state.lastArgs) {
159
- const resolvePromise = this.#resolvePreviousPromise;
160
- this.#clearTimeout();
161
- this.#setState({
162
- isPending: false
163
- });
164
- const result = await this.#execute(...this.store.state.lastArgs);
165
- if (resolvePromise) {
166
- resolvePromise(result);
167
- }
168
- return result;
169
- }
170
- return void 0;
171
- };
172
- this.#resolvePreviousPromiseInternal = () => {
173
- if (this.#resolvePreviousPromise) {
174
- this.#resolvePreviousPromise(this.store.state.lastResult);
175
- this.#resolvePreviousPromise = null;
176
- }
177
- };
178
- this.#clearTimeout = () => {
179
- if (this.#timeoutId) {
180
- clearTimeout(this.#timeoutId);
181
- this.#timeoutId = null;
182
- }
183
- };
184
- this.abort = () => {
185
- this.asyncRetryers.forEach((retryer) => retryer.abort());
186
- this.asyncRetryers.clear();
187
- this.#setState({ isExecuting: false });
188
- };
189
- this.cancel = () => {
190
- this.#clearTimeout();
191
- if (this.#resolvePreviousPromise) {
192
- this.#resolvePreviousPromiseInternal();
193
- this.#resolvePreviousPromise = null;
194
- }
195
- this.#setState({
196
- isPending: false
197
- });
198
- };
199
- this.reset = () => {
200
- this.#setState(getDefaultAsyncThrottlerState());
201
- this.asyncRetryers.forEach((retryer) => retryer.reset());
202
- };
203
- this.key = initialOptions.key;
204
- this.options = {
205
- ...defaultOptions,
206
- ...initialOptions,
207
- throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
208
- };
209
- this.#setState(this.options.initialState ?? {});
210
- if (this.key) {
211
- eventClient.pacerEventClient.on("d-AsyncThrottler", (event) => {
212
- if (event.payload.key !== this.key) return;
213
- this.#setState(event.payload.store.state);
214
- this.setOptions(event.payload.options);
215
- });
216
- }
217
- }
218
- #timeoutId;
219
- #resolvePreviousPromise;
220
- #setState;
221
- #getEnabled;
222
- #getWait;
223
- #execute;
224
- #resolvePreviousPromiseInternal;
225
- #clearTimeout;
226
- /**
227
- * Returns the AbortSignal for a specific execution.
228
- * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
229
- * Returns null if no execution is found or not currently executing.
230
- *
231
- * @param maybeExecuteCount - Optional specific execution to get signal for
232
- * @example
233
- * ```typescript
234
- * const throttler = new AsyncThrottler(
235
- * async (data: string) => {
236
- * const signal = throttler.getAbortSignal()
237
- * if (signal) {
238
- * const response = await fetch('/api/save', {
239
- * method: 'POST',
240
- * body: data,
241
- * signal
242
- * })
243
- * return response.json()
244
- * }
245
- * },
246
- * { wait: 1000 }
247
- * )
248
- * ```
249
- */
250
- getAbortSignal(maybeExecuteCount) {
251
- const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount;
252
- const retryer = this.asyncRetryers.get(count);
253
- return retryer?.getAbortSignal() ?? null;
254
- }
255
- }
256
- function asyncThrottle(fn, initialOptions) {
257
- const asyncThrottler = new AsyncThrottler(fn, initialOptions);
258
- return asyncThrottler.maybeExecute;
259
- }
260
- exports.AsyncThrottler = AsyncThrottler;
261
- exports.asyncThrottle = asyncThrottle;
262
- exports.asyncThrottlerOptions = asyncThrottlerOptions;
263
- //# sourceMappingURL=async-throttler.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"async-throttler.cjs","sources":["../../src/async-throttler.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 AsyncThrottlerState<TFn extends AnyAsyncFunction> {\n /**\n * Number of function executions that have resulted in errors\n */\n errorCount: number\n /**\n * Whether the throttled function is currently executing asynchronously\n */\n isExecuting: boolean\n /**\n * Whether the throttler 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 * Timestamp of the last function execution in milliseconds\n */\n lastExecutionTime: number\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 * Timestamp when the next execution can occur in milliseconds\n */\n nextExecutionTime: number | undefined\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 getDefaultAsyncThrottlerState<\n TFn extends AnyAsyncFunction,\n>(): AsyncThrottlerState<TFn> {\n return {\n errorCount: 0,\n isExecuting: false,\n isPending: false,\n lastArgs: undefined,\n lastExecutionTime: 0,\n lastResult: undefined,\n maybeExecuteCount: 0,\n nextExecutionTime: undefined,\n settleCount: 0,\n status: 'idle',\n successCount: 0,\n }\n}\n\n/**\n * Options for configuring an async throttled function\n */\nexport interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Options for configuring the underlying async retryer\n */\n asyncRetryerOptions?: AsyncRetryerOptions<TFn>\n /**\n * Whether the throttler 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 | ((throttler: AsyncThrottler<TFn>) => boolean)\n /**\n * Initial state for the async throttler\n */\n initialState?: Partial<AsyncThrottlerState<TFn>>\n /**\n * Optional key to identify this async throttler instance.\n * If provided, the async throttler will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Whether to execute the function immediately when called\n * Defaults to true\n */\n leading?: boolean\n /**\n * Optional error handler for when the throttled function throws.\n * If provided, the handler will be called with the error and throttler 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 asyncThrottler: AsyncThrottler<TFn>,\n ) => void\n /**\n * Optional function to call when the throttled function is executed\n */\n onSettled?: (\n args: Parameters<TFn>,\n asyncThrottler: AsyncThrottler<TFn>,\n ) => void\n /**\n * Optional function to call when the throttled function is executed\n */\n onSuccess?: (\n result: ReturnType<TFn>,\n args: Parameters<TFn>,\n asyncThrottler: AsyncThrottler<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 the function on the trailing edge of the wait period\n * Defaults to true\n */\n trailing?: boolean\n /**\n * Time window in milliseconds during which the function can only be executed once.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((throttler: AsyncThrottler<TFn>) => number)\n}\n\n/**\n * Utility function for sharing common `AsyncThrottlerOptions` options between different `AsyncThrottler` instances.\n */\nexport function asyncThrottlerOptions<\n TFn extends AnyAsyncFunction = AnyAsyncFunction,\n TOptions extends Partial<AsyncThrottlerOptions<TFn>> = Partial<\n AsyncThrottlerOptions<TFn>\n >,\n>(options: TOptions): TOptions {\n return options\n}\n\ntype AsyncThrottlerOptionsWithOptionalCallbacks = OptionalKeys<\n AsyncThrottlerOptions<any>,\n 'initialState' | 'onError' | 'onSettled' | 'onSuccess'\n>\n\nconst defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = {\n asyncRetryerOptions: {\n maxAttempts: 1,\n },\n enabled: true,\n leading: true,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async throttled function.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync Throttler:\n * - Returns promises that can be awaited for throttled 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 * - Waits for ongoing executions to complete before scheduling the next one\n *\n * The sync Throttler is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Throttling?\n * Throttling limits how often a function can be executed, allowing only one execution within a specified time window.\n * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a\n * regular interval regardless of how often it's called.\n *\n * This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to\n * ensure a maximum execution frequency.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and throttler 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 AsyncThrottler instance\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the async throttler\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 error count, execution status, last execution time, and success/settle counts\n * - State can be accessed via `asyncThrottler.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncThrottler.state`\n *\n * @example\n * ```ts\n * const throttler = new AsyncThrottler(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 * });\n *\n * // Will only execute once per second no matter how often called\n * // Returns the API response directly\n * const result = await throttler.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncThrottler<TFn extends AnyAsyncFunction> {\n readonly store: Store<Readonly<AsyncThrottlerState<TFn>>> = new Store<\n AsyncThrottlerState<TFn>\n >(getDefaultAsyncThrottlerState<TFn>())\n key: string | undefined\n options: AsyncThrottlerOptions<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: AsyncThrottlerOptions<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 if (this.key) {\n pacerEventClient.on('d-AsyncThrottler', (event) => {\n if (event.payload.key !== this.key) return\n this.#setState(event.payload.store.state as AsyncThrottlerState<TFn>)\n this.setOptions(event.payload.options)\n })\n }\n }\n\n /**\n * Updates the async throttler options\n */\n setOptions = (newOptions: Partial<AsyncThrottlerOptions<TFn>>): void => {\n this.options = { ...this.options, ...newOptions }\n\n // End the pending state if the throttler is disabled\n if (!this.#getEnabled()) {\n this.cancel()\n }\n }\n\n #setState = (newState: Partial<AsyncThrottlerState<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('AsyncThrottler', this)\n }\n\n /**\n * Returns the current enabled state of the async throttler\n */\n #getEnabled = (): boolean => {\n return !!parseFunctionOrValue(this.options.enabled, this)\n }\n\n /**\n * Returns the current wait time in milliseconds\n */\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait, this)\n }\n\n /**\n * Attempts to execute the throttled function. The execution behavior depends on the throttler options:\n *\n * - If enough time has passed since the last execution (>= wait period):\n * - With leading=true: Executes immediately\n * - With leading=false: Waits for the next trailing execution\n *\n * - If within the wait period:\n * - With trailing=true: Schedules execution for end of wait period\n * - With trailing=false: Drops the execution\n *\n * @example\n * ```ts\n * const throttled = new AsyncThrottler(fn, { wait: 1000 });\n *\n * // First call executes immediately\n * await throttled.maybeExecute('a', 'b');\n *\n * // Call during wait period - gets throttled\n * await throttled.maybeExecute('c', 'd');\n * ```\n */\n maybeExecute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n\n this.#resolvePreviousPromiseInternal()\n\n this.#setState({\n maybeExecuteCount: this.store.state.maybeExecuteCount + 1,\n lastArgs: args, // store the arguments for potential trailing execution\n })\n\n const wait = this.#getWait()\n const thisMaybeExecuteNumber = this.store.state.maybeExecuteCount\n\n // Wait for the wait period for the previous execution to complete if it's still running\n for (\n let maxNumIterations = wait / 10;\n this.store.state.isExecuting && maxNumIterations > 0;\n maxNumIterations--\n ) {\n await new Promise((resolve) => setTimeout(resolve, 10))\n if (this.store.state.maybeExecuteCount !== thisMaybeExecuteNumber) {\n // cancel the current maybeExecute loop because a new maybeExecute call was made\n return this.store.state.lastResult\n }\n }\n\n const now = Date.now()\n const timeSinceLastExecution = now - this.store.state.lastExecutionTime\n\n if (\n this.options.leading &&\n !this.store.state.isPending &&\n timeSinceLastExecution >= wait\n ) {\n await this.#execute(...args) // Leading EXECUTE!\n } else if (this.options.trailing) {\n // replace old pending execution with a new one\n this.cancel()\n this.#setState({\n isPending: true,\n })\n\n // Set up new trailing execution\n return new Promise((resolve, reject) => {\n this.#resolvePreviousPromise = resolve\n\n const newTimeSinceLastExecution = this.store.state.lastExecutionTime\n ? now - this.store.state.lastExecutionTime\n : 0\n const timeoutDuration = Math.max(0, wait - newTimeSinceLastExecution)\n\n this.#timeoutId = setTimeout(async () => {\n this.#clearTimeout()\n if (this.store.state.lastArgs !== undefined) {\n try {\n await this.#execute(...this.store.state.lastArgs) // Trailing EXECUTE!\n } catch (error) {\n reject(error)\n }\n }\n this.#resolvePreviousPromise = null\n resolve(this.store.state.lastResult)\n }, timeoutDuration)\n })\n }\n return this.store.state.lastResult\n }\n\n #execute = async (\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> => {\n if (!this.#getEnabled()) return undefined\n\n const currentMaybeExecute = this.store.state.maybeExecuteCount\n\n try {\n this.#setState({ isExecuting: true })\n const currentAsyncRetryer = new AsyncRetryer(this.fn, {\n ...this.options.asyncRetryerOptions,\n key: `${this.key}-retryer-${currentMaybeExecute}`,\n })\n this.asyncRetryers.set(currentMaybeExecute, 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(currentMaybeExecute) // dispose retryer\n const lastExecutionTime = Date.now()\n const wait = this.#getWait()\n const nextExecutionTime = lastExecutionTime + wait\n this.#setState({\n isExecuting: false,\n isPending: !!this.#timeoutId,\n settleCount: this.store.state.settleCount + 1,\n lastExecutionTime,\n nextExecutionTime,\n })\n this.options.onSettled?.(args, this)\n setTimeout(() => {\n if (!this.store.state.isPending) {\n // clear nextExecutionTime if there is no pending execution\n this.#setState({ nextExecutionTime: undefined })\n }\n }, wait)\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 // Store the pending promise resolver before clearing timeout\n const resolvePromise = this.#resolvePreviousPromise\n\n // Clear timeout and state without resolving the promise\n this.#clearTimeout()\n this.#setState({\n isPending: false,\n })\n\n const result = await this.#execute(...this.store.state.lastArgs)\n\n // Resolve the pending promise with the result\n if (resolvePromise) {\n resolvePromise(result)\n }\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 /**\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 throttler = new AsyncThrottler(\n * async (data: string) => {\n * const signal = throttler.getAbortSignal()\n * if (signal) {\n * const response = await fetch('/api/save', {\n * method: 'POST',\n * body: data,\n * signal\n * })\n * return response.json()\n * }\n * },\n * { wait: 1000 }\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({ isExecuting: false })\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.#clearTimeout()\n if (this.#resolvePreviousPromise) {\n this.#resolvePreviousPromiseInternal()\n this.#resolvePreviousPromise = null\n }\n this.#setState({\n isPending: false,\n })\n }\n\n /**\n * Resets the debouncer state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncThrottlerState<TFn>())\n this.asyncRetryers.forEach((retryer) => retryer.reset())\n }\n}\n\n/**\n * Creates an async throttled function that limits how often the function can execute.\n * The throttled function will execute at most once per wait period, even if called multiple times.\n * If called while executing, it will wait until execution completes before scheduling the next call.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync throttle function:\n * - Returns promises that can be awaited for throttled 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 * - Waits for ongoing executions to complete before scheduling the next one\n *\n * The sync throttle function is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Throttling?\n * Throttling limits how often a function can be executed, allowing only one execution within a specified time window.\n * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a\n * regular interval regardless of how often it's called.\n *\n * Configuration Options:\n * - `wait`: Time window in milliseconds during which the function can only execute once (required)\n * - `leading`: Execute immediately when called (default: true)\n * - `trailing`: Execute on the trailing edge of the wait period (default: true)\n * - `enabled`: Whether the throttler 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 throttler 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 AsyncThrottler instance\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - Use `initialState` to provide initial state values when creating the async throttler\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 error count, execution status, last execution time, and success/settle counts\n * - State can be accessed via the underlying AsyncThrottler instance's `store.state` property\n * - When using framework adapters (React/Solid), state is accessed from the hook's state property\n *\n * @example\n * ```ts\n * const throttled = asyncThrottle(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 * });\n *\n * // This will execute at most once per second\n * // Returns the API response directly\n * const result = await throttled(inputElement.value);\n * ```\n */\nexport function asyncThrottle<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncThrottlerOptions<TFn>,\n) {\n const asyncThrottler = new AsyncThrottler(fn, initialOptions)\n return asyncThrottler.maybeExecute\n}\n"],"names":["Store","emitChange","parseFunctionOrValue","AsyncRetryer","pacerEventClient"],"mappings":";;;;;;AAsDA,SAAS,gCAEqB;AAC5B,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,WAAW;AAAA,IACX,UAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,cAAc;AAAA,EAAA;AAElB;AA6EO,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;AA4DO,MAAM,eAA6C;AAAA,EAYxD,YACS,IACP,gBACA;AAFO,SAAA,KAAA;AAZT,SAAS,QAAmD,IAAIA,MAAAA,MAE9D,8BAAA,CAAoC;AAGtC,SAAA,oCAAoB,IAAA;AACpB,SAAA,aAAoC;AACpC,SAAA,0BAEW;AA0BX,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;AACDC,kBAAAA,WAAW,kBAAkB,IAAI;AAAA,IACnC;AAKA,SAAA,cAAc,MAAe;AAC3B,aAAO,CAAC,CAACC,MAAAA,qBAAqB,KAAK,QAAQ,SAAS,IAAI;AAAA,IAC1D;AAKA,SAAA,WAAW,MAAc;AACvB,aAAOA,MAAAA,qBAAqB,KAAK,QAAQ,MAAM,IAAI;AAAA,IACrD;AAwBA,SAAA,eAAe,UACV,SACsC;AACzC,UAAI,CAAC,KAAK,YAAA,EAAe,QAAO;AAEhC,WAAK,gCAAA;AAEL,WAAK,UAAU;AAAA,QACb,mBAAmB,KAAK,MAAM,MAAM,oBAAoB;AAAA,QACxD,UAAU;AAAA;AAAA,MAAA,CACX;AAED,YAAM,OAAO,KAAK,SAAA;AAClB,YAAM,yBAAyB,KAAK,MAAM,MAAM;AAGhD,eACM,mBAAmB,OAAO,IAC9B,KAAK,MAAM,MAAM,eAAe,mBAAmB,GACnD,oBACA;AACA,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACtD,YAAI,KAAK,MAAM,MAAM,sBAAsB,wBAAwB;AAEjE,iBAAO,KAAK,MAAM,MAAM;AAAA,QAC1B;AAAA,MACF;AAEA,YAAM,MAAM,KAAK,IAAA;AACjB,YAAM,yBAAyB,MAAM,KAAK,MAAM,MAAM;AAEtD,UACE,KAAK,QAAQ,WACb,CAAC,KAAK,MAAM,MAAM,aAClB,0BAA0B,MAC1B;AACA,cAAM,KAAK,SAAS,GAAG,IAAI;AAAA,MAC7B,WAAW,KAAK,QAAQ,UAAU;AAEhC,aAAK,OAAA;AACL,aAAK,UAAU;AAAA,UACb,WAAW;AAAA,QAAA,CACZ;AAGD,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,eAAK,0BAA0B;AAE/B,gBAAM,4BAA4B,KAAK,MAAM,MAAM,oBAC/C,MAAM,KAAK,MAAM,MAAM,oBACvB;AACJ,gBAAM,kBAAkB,KAAK,IAAI,GAAG,OAAO,yBAAyB;AAEpE,eAAK,aAAa,WAAW,YAAY;AACvC,iBAAK,cAAA;AACL,gBAAI,KAAK,MAAM,MAAM,aAAa,QAAW;AAC3C,kBAAI;AACF,sBAAM,KAAK,SAAS,GAAG,KAAK,MAAM,MAAM,QAAQ;AAAA,cAClD,SAAS,OAAO;AACd,uBAAO,KAAK;AAAA,cACd;AAAA,YACF;AACA,iBAAK,0BAA0B;AAC/B,oBAAQ,KAAK,MAAM,MAAM,UAAU;AAAA,UACrC,GAAG,eAAe;AAAA,QACpB,CAAC;AAAA,MACH;AACA,aAAO,KAAK,MAAM,MAAM;AAAA,IAC1B;AAEA,SAAA,WAAW,UACN,SACsC;AACzC,UAAI,CAAC,KAAK,YAAA,EAAe,QAAO;AAEhC,YAAM,sBAAsB,KAAK,MAAM,MAAM;AAE7C,UAAI;AACF,aAAK,UAAU,EAAE,aAAa,KAAA,CAAM;AACpC,cAAM,sBAAsB,IAAIC,0BAAa,KAAK,IAAI;AAAA,UACpD,GAAG,KAAK,QAAQ;AAAA,UAChB,KAAK,GAAG,KAAK,GAAG,YAAY,mBAAmB;AAAA,QAAA,CAChD;AACD,aAAK,cAAc,IAAI,qBAAqB,mBAAmB;AAC/D,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,mBAAmB;AAC7C,cAAM,oBAAoB,KAAK,IAAA;AAC/B,cAAM,OAAO,KAAK,SAAA;AAClB,cAAM,oBAAoB,oBAAoB;AAC9C,aAAK,UAAU;AAAA,UACb,aAAa;AAAA,UACb,WAAW,CAAC,CAAC,KAAK;AAAA,UAClB,aAAa,KAAK,MAAM,MAAM,cAAc;AAAA,UAC5C;AAAA,UACA;AAAA,QAAA,CACD;AACD,aAAK,QAAQ,YAAY,MAAM,IAAI;AACnC,mBAAW,MAAM;AACf,cAAI,CAAC,KAAK,MAAM,MAAM,WAAW;AAE/B,iBAAK,UAAU,EAAE,mBAAmB,OAAA,CAAW;AAAA,UACjD;AAAA,QACF,GAAG,IAAI;AAAA,MACT;AACA,aAAO,KAAK,MAAM,MAAM;AAAA,IAC1B;AAKA,SAAA,QAAQ,YAAkD;AACxD,UAAI,KAAK,MAAM,MAAM,aAAa,KAAK,MAAM,MAAM,UAAU;AAE3D,cAAM,iBAAiB,KAAK;AAG5B,aAAK,cAAA;AACL,aAAK,UAAU;AAAA,UACb,WAAW;AAAA,QAAA,CACZ;AAED,cAAM,SAAS,MAAM,KAAK,SAAS,GAAG,KAAK,MAAM,MAAM,QAAQ;AAG/D,YAAI,gBAAgB;AAClB,yBAAe,MAAM;AAAA,QACvB;AAEA,eAAO;AAAA,MACT;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;AAoCA,SAAA,QAAQ,MAAY;AAClB,WAAK,cAAc,QAAQ,CAAC,YAAY,QAAQ,OAAO;AACvD,WAAK,cAAc,MAAA;AACnB,WAAK,UAAU,EAAE,aAAa,MAAA,CAAO;AAAA,IACvC;AAMA,SAAA,SAAS,MAAY;AACnB,WAAK,cAAA;AACL,UAAI,KAAK,yBAAyB;AAChC,aAAK,gCAAA;AACL,aAAK,0BAA0B;AAAA,MACjC;AACA,WAAK,UAAU;AAAA,QACb,WAAW;AAAA,MAAA,CACZ;AAAA,IACH;AAKA,SAAA,QAAQ,MAAY;AAClB,WAAK,UAAU,+BAAoC;AACnD,WAAK,cAAc,QAAQ,CAAC,YAAY,QAAQ,OAAO;AAAA,IACzD;AAtTE,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,QAAI,KAAK,KAAK;AACZC,kBAAAA,iBAAiB,GAAG,oBAAoB,CAAC,UAAU;AACjD,YAAI,MAAM,QAAQ,QAAQ,KAAK,IAAK;AACpC,aAAK,UAAU,MAAM,QAAQ,MAAM,KAAiC;AACpE,aAAK,WAAW,MAAM,QAAQ,OAAO;AAAA,MACvC,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAxBA;AAAA,EACA;AAAA,EAqCA;AAAA,EA0BA;AAAA,EAOA;AAAA,EAgGA;AAAA,EA6EA;AAAA,EAOA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,eAAe,mBAAgD;AAC7D,UAAM,QAAQ,qBAAqB,KAAK,MAAM,MAAM;AACpD,UAAM,UAAU,KAAK,cAAc,IAAI,KAAK;AAC5C,WAAO,SAAS,oBAAoB;AAAA,EACtC;AAkCF;AAkEO,SAAS,cACd,IACA,gBACA;AACA,QAAM,iBAAiB,IAAI,eAAe,IAAI,cAAc;AAC5D,SAAO,eAAe;AACxB;;;;"}