@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,287 +0,0 @@
1
- import { Store } from "@tanstack/store";
2
- import { parseFunctionOrValue } from "./utils.js";
3
- import { emitChange, pacerEventClient } from "./event-client.js";
4
- function getDefaultAsyncRetryerState() {
5
- return structuredClone({
6
- currentAttempt: 0,
7
- executionCount: 0,
8
- isExecuting: false,
9
- lastError: void 0,
10
- lastExecutionTime: 0,
11
- lastResult: void 0,
12
- status: "idle",
13
- totalExecutionTime: 0
14
- });
15
- }
16
- function asyncRetryerOptions(options) {
17
- return options;
18
- }
19
- const defaultOptions = {
20
- backoff: "exponential",
21
- baseWait: 1e3,
22
- enabled: true,
23
- jitter: 0,
24
- maxAttempts: 3,
25
- maxExecutionTime: Infinity,
26
- maxTotalExecutionTime: Infinity,
27
- throwOnError: "last"
28
- };
29
- class AsyncRetryer {
30
- /**
31
- * Creates a new AsyncRetryer instance
32
- * @param fn The async function to retry
33
- * @param initialOptions Configuration options for the retryer
34
- */
35
- constructor(fn, initialOptions = {}) {
36
- this.fn = fn;
37
- this.store = new Store(
38
- getDefaultAsyncRetryerState()
39
- );
40
- this.#abortController = null;
41
- this.setOptions = (newOptions) => {
42
- this.options = { ...this.options, ...newOptions };
43
- };
44
- this.#setState = (newState) => {
45
- this.store.setState((state) => {
46
- const combinedState = {
47
- ...state,
48
- ...newState
49
- };
50
- const { isExecuting, currentAttempt } = combinedState;
51
- return {
52
- ...combinedState,
53
- status: !this.#getEnabled() ? "disabled" : isExecuting && currentAttempt === 1 ? "executing" : isExecuting && currentAttempt > 1 ? "retrying" : "idle"
54
- };
55
- });
56
- emitChange("AsyncRetryer", this);
57
- };
58
- this.#getEnabled = () => {
59
- return !!parseFunctionOrValue(this.options.enabled, this);
60
- };
61
- this.#getMaxAttempts = () => {
62
- return parseFunctionOrValue(this.options.maxAttempts, this);
63
- };
64
- this.#getBaseWait = () => {
65
- return parseFunctionOrValue(this.options.baseWait, this);
66
- };
67
- this.#calculateJitter = (waitTime) => {
68
- const jitterAmount = this.options.jitter;
69
- if (jitterAmount <= 0) return 0;
70
- try {
71
- const crypto = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
72
- if (crypto?.getRandomValues) {
73
- const array = new Uint32Array(1);
74
- crypto.getRandomValues(array);
75
- const randomFactor = array[0] / 4294967295 * 2 - 1;
76
- return Math.floor(waitTime * jitterAmount * randomFactor);
77
- }
78
- } catch {
79
- }
80
- return 0;
81
- };
82
- this.#calculateWait = (attempt) => {
83
- const baseWait = this.#getBaseWait();
84
- let waitTime;
85
- switch (this.options.backoff) {
86
- case "linear":
87
- waitTime = baseWait * attempt;
88
- break;
89
- case "exponential":
90
- waitTime = baseWait * Math.pow(2, attempt - 1);
91
- break;
92
- case "fixed":
93
- default:
94
- waitTime = baseWait;
95
- break;
96
- }
97
- const jitter = this.#calculateJitter(waitTime);
98
- return Math.max(0, waitTime + jitter);
99
- };
100
- this.execute = async (...args) => {
101
- if (!this.#getEnabled()) {
102
- return void 0;
103
- }
104
- this.abort("new-execution");
105
- const startTime = Date.now();
106
- let lastError;
107
- let result;
108
- this.#abortController = new AbortController();
109
- const signal = this.#abortController.signal;
110
- this.#setState({
111
- isExecuting: true,
112
- currentAttempt: 0,
113
- lastError: void 0
114
- });
115
- let totalTimeoutId;
116
- if (this.options.maxTotalExecutionTime !== Infinity) {
117
- totalTimeoutId = setTimeout(() => {
118
- this.options.onTotalExecutionTimeout?.(this);
119
- this.abort("total-timeout");
120
- }, this.options.maxTotalExecutionTime);
121
- }
122
- let isLastAttempt = false;
123
- for (let attempt = 1; attempt <= this.#getMaxAttempts(); attempt++) {
124
- isLastAttempt = attempt === this.#getMaxAttempts();
125
- this.#setState({ currentAttempt: attempt });
126
- try {
127
- if (signal.aborted) {
128
- return void 0;
129
- }
130
- const currentTotalTime = Date.now() - startTime;
131
- if (this.options.maxTotalExecutionTime !== Infinity && currentTotalTime >= this.options.maxTotalExecutionTime) {
132
- this.options.onTotalExecutionTimeout?.(this);
133
- this.abort("total-timeout");
134
- return void 0;
135
- }
136
- if (this.options.maxExecutionTime === Infinity) {
137
- result = await this.fn(...args);
138
- } else {
139
- result = await Promise.race([
140
- this.fn(...args),
141
- new Promise((_, reject) => {
142
- const timeout = setTimeout(() => {
143
- this.options.onExecutionTimeout?.(this);
144
- this.abort("execution-timeout");
145
- reject(
146
- new Error(
147
- `Execution timeout: ${this.options.maxExecutionTime}ms exceeded`
148
- )
149
- );
150
- }, this.options.maxExecutionTime);
151
- signal.addEventListener(
152
- "abort",
153
- () => {
154
- clearTimeout(timeout);
155
- reject(new Error("Aborted"));
156
- },
157
- { once: true }
158
- );
159
- })
160
- ]);
161
- }
162
- if (signal.aborted) {
163
- return void 0;
164
- }
165
- const totalTime = Date.now() - startTime;
166
- this.#setState({
167
- executionCount: this.store.state.executionCount + 1,
168
- isExecuting: false,
169
- lastExecutionTime: Date.now(),
170
- totalExecutionTime: totalTime,
171
- currentAttempt: 0,
172
- lastResult: result
173
- });
174
- this.options.onSuccess?.(result, args, this);
175
- return result;
176
- } catch (error) {
177
- if (error && typeof error === "object" && "name" in error && error.name === "AbortError") {
178
- return void 0;
179
- }
180
- lastError = error instanceof Error ? error : new Error(String(error));
181
- this.#setState({ lastError });
182
- this.options.onError?.(lastError, args, this);
183
- if (attempt < this.#getMaxAttempts()) {
184
- this.options.onRetry?.(attempt, lastError, this);
185
- const wait = this.#calculateWait(attempt);
186
- if (wait > 0) {
187
- this.#setState({ isExecuting: true, currentAttempt: attempt + 1 });
188
- await new Promise((resolve) => {
189
- const timeout = setTimeout(() => {
190
- signal.removeEventListener("abort", onAbort);
191
- resolve();
192
- }, wait);
193
- const onAbort = () => {
194
- clearTimeout(timeout);
195
- signal.removeEventListener("abort", onAbort);
196
- resolve();
197
- };
198
- signal.addEventListener("abort", onAbort);
199
- });
200
- if (signal.aborted) {
201
- return void 0;
202
- }
203
- }
204
- }
205
- } finally {
206
- this.options.onSettled?.(args, this);
207
- }
208
- }
209
- if (totalTimeoutId) {
210
- clearTimeout(totalTimeoutId);
211
- }
212
- this.#setState({ isExecuting: false });
213
- this.options.onLastError?.(lastError, this);
214
- this.options.onSettled?.(args, this);
215
- if (this.options.throwOnError === "last" && isLastAttempt || this.options.throwOnError === true) {
216
- throw lastError;
217
- }
218
- return void 0;
219
- };
220
- this.abort = (reason = "manual") => {
221
- if (this.#abortController) {
222
- this.#abortController.abort();
223
- this.#abortController = null;
224
- this.#setState({
225
- isExecuting: false
226
- });
227
- this.options.onAbort?.(reason, this);
228
- }
229
- };
230
- this.reset = () => {
231
- this.#setState(getDefaultAsyncRetryerState());
232
- };
233
- this.key = initialOptions.key;
234
- this.options = {
235
- ...defaultOptions,
236
- ...initialOptions,
237
- throwOnError: initialOptions.throwOnError ?? (initialOptions.onError ? false : defaultOptions.throwOnError)
238
- };
239
- this.#setState(this.options.initialState ?? {});
240
- if (this.key) {
241
- pacerEventClient.on("d-AsyncRetryer", (event) => {
242
- if (event.payload.key !== this.key) return;
243
- this.#setState(event.payload.store.state);
244
- this.setOptions(event.payload.options);
245
- });
246
- }
247
- }
248
- #abortController;
249
- #setState;
250
- #getEnabled;
251
- #getMaxAttempts;
252
- #getBaseWait;
253
- #calculateJitter;
254
- #calculateWait;
255
- /**
256
- * Returns the current AbortSignal for the executing operation.
257
- * Use this signal in your async function to make it cancellable.
258
- * Returns null when not currently executing.
259
- *
260
- * @example
261
- * ```typescript
262
- * const retryer = new AsyncRetryer(async (userId: string) => {
263
- * const signal = retryer.getAbortSignal()
264
- * if (signal) {
265
- * return fetch(`/api/users/${userId}`, { signal })
266
- * }
267
- * return fetch(`/api/users/${userId}`)
268
- * })
269
- *
270
- * // Abort will now actually cancel the fetch
271
- * retryer.abort()
272
- * ```
273
- */
274
- getAbortSignal() {
275
- return this.#abortController?.signal ?? null;
276
- }
277
- }
278
- function asyncRetry(fn, initialOptions = {}) {
279
- const retryer = new AsyncRetryer(fn, initialOptions);
280
- return retryer.execute;
281
- }
282
- export {
283
- AsyncRetryer,
284
- asyncRetry,
285
- asyncRetryerOptions
286
- };
287
- //# sourceMappingURL=async-retryer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"async-retryer.js","sources":["../../src/async-retryer.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { parseFunctionOrValue } from './utils'\nimport { emitChange, pacerEventClient } from './event-client'\nimport type { AnyAsyncFunction } from './types'\n\nexport interface AsyncRetryerState<TFn extends AnyAsyncFunction> {\n /**\n * The current retry attempt number (0 when not executing)\n */\n currentAttempt: number\n /**\n * Total number of completed executions (successful or failed)\n */\n executionCount: number\n /**\n * Whether the retryer is currently executing the function\n */\n isExecuting: boolean\n /**\n * The most recent error encountered during execution\n */\n lastError: Error | undefined\n /**\n * Timestamp of the last execution completion in milliseconds\n */\n lastExecutionTime: number\n /**\n * The result from the most recent successful execution\n */\n lastResult: Awaited<ReturnType<TFn>> | undefined\n /**\n * Current execution status - 'disabled' when not enabled, 'idle' when ready, 'executing' when running\n */\n status: 'disabled' | 'idle' | 'executing' | 'retrying'\n /**\n * Total time spent executing (including retries) in milliseconds\n */\n totalExecutionTime: number\n}\n\n/**\n * Creates the default initial state for an AsyncRetryer instance\n * @returns The default state with all values reset to initial values\n */\nfunction getDefaultAsyncRetryerState<\n TFn extends AnyAsyncFunction,\n>(): AsyncRetryerState<TFn> {\n return structuredClone({\n currentAttempt: 0,\n executionCount: 0,\n isExecuting: false,\n lastError: undefined,\n lastExecutionTime: 0,\n lastResult: undefined,\n status: 'idle',\n totalExecutionTime: 0,\n })\n}\n\nexport interface AsyncRetryerOptions<TFn extends AnyAsyncFunction> {\n /**\n * The backoff strategy for retry delays:\n * - 'exponential': Wait time doubles with each attempt (1s, 2s, 4s, ...)\n * - 'linear': Wait time increases linearly (1s, 2s, 3s, ...)\n * - 'fixed': Same wait time for all attempts\n * @default 'exponential'\n */\n backoff?: 'linear' | 'exponential' | 'fixed'\n /**\n * Base wait time in milliseconds between retries, or a function that returns the wait time\n * @default 1000\n */\n baseWait?: number | ((retryer: AsyncRetryer<TFn>) => number)\n /**\n * Whether the retryer is enabled, or a function that determines if it's enabled\n * @default true\n */\n enabled?: boolean | ((retryer: AsyncRetryer<TFn>) => boolean)\n /**\n * Initial state to merge with the default state\n */\n initialState?: Partial<AsyncRetryerState<TFn>>\n /**\n * Jitter percentage to add to retry delays (0-1). Adds randomness to prevent thundering herd.\n * @default 0\n */\n jitter?: number\n /**\n * Optional key to identify this async retryer instance.\n * If provided, the async retryer will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Maximum number of retry attempts, or a function that returns the max attempts\n * @default 3\n */\n maxAttempts?: number | ((retryer: AsyncRetryer<TFn>) => number)\n /**\n * Maximum execution time in milliseconds for a single function call before aborting\n * @default Infinity\n */\n maxExecutionTime?: number\n /**\n * Maximum total execution time in milliseconds for the entire retry operation before aborting\n * @default Infinity\n */\n maxTotalExecutionTime?: number\n /**\n * Callback invoked when the execution is aborted (manually or due to timeouts)\n */\n onAbort?: (\n reason: 'manual' | 'execution-timeout' | 'total-timeout' | 'new-execution',\n retryer: AsyncRetryer<TFn>,\n ) => void\n /**\n * Callback invoked when any error occurs during execution (including retries)\n */\n onError?: (\n error: Error,\n args: Parameters<TFn>,\n retryer: AsyncRetryer<TFn>,\n ) => void\n /**\n * Callback invoked when the final error occurs after all retries are exhausted\n */\n onLastError?: (error: Error, retryer: AsyncRetryer<TFn>) => void\n /**\n * Callback invoked before each retry attempt\n */\n onRetry?: (attempt: number, error: Error, retryer: AsyncRetryer<TFn>) => void\n /**\n * Callback invoked after execution completes (success or failure) of each attempt\n */\n onSettled?: (args: Parameters<TFn>, retryer: AsyncRetryer<TFn>) => void\n /**\n * Callback invoked when execution succeeds\n */\n onSuccess?: (\n result: Awaited<ReturnType<TFn>>,\n args: Parameters<TFn>,\n retryer: AsyncRetryer<TFn>,\n ) => void\n /**\n * Callback invoked when a single execution attempt times out (maxExecutionTime exceeded)\n */\n onExecutionTimeout?: (retryer: AsyncRetryer<TFn>) => void\n /**\n * Callback invoked when the total execution time times out (maxTotalExecutionTime exceeded)\n */\n onTotalExecutionTimeout?: (retryer: AsyncRetryer<TFn>) => void\n /**\n * Controls when errors are thrown:\n * - 'last': Only throw the final error after all retries are exhausted\n * - true: Throw every error immediately (disables retrying)\n * - false: Never throw errors, return undefined instead\n * @default 'last'\n */\n throwOnError?: boolean | 'last'\n}\n\n/**\n * Utility function for sharing common `AsyncRetryerOptions` options between different `AsyncRetryer` instances.\n */\nexport function asyncRetryerOptions<\n TFn extends AnyAsyncFunction = AnyAsyncFunction,\n TOptions extends Partial<AsyncRetryerOptions<TFn>> = Partial<\n AsyncRetryerOptions<TFn>\n >,\n>(options: TOptions): TOptions {\n return options\n}\n\nconst defaultOptions: Omit<\n Required<AsyncRetryerOptions<any>>,\n | 'initialState'\n | 'key'\n | 'onAbort'\n | 'onError'\n | 'onLastError'\n | 'onRetry'\n | 'onSettled'\n | 'onSuccess'\n | 'onExecutionTimeout'\n | 'onTotalExecutionTimeout'\n> = {\n backoff: 'exponential',\n baseWait: 1000,\n enabled: true,\n jitter: 0,\n maxAttempts: 3,\n maxExecutionTime: Infinity,\n maxTotalExecutionTime: Infinity,\n throwOnError: 'last',\n}\n\n/**\n * Provides robust retry functionality for asynchronous functions, supporting configurable backoff strategies,\n * attempt limits, timeout controls, and detailed state management. The AsyncRetryer class is designed to help you reliably\n * execute async operations that may fail intermittently, such as network requests or database operations,\n * by automatically retrying them according to your chosen policy.\n *\n * ## Retrying Concepts\n *\n * - **Retrying**: Automatically re-executes a failed async function up to a specified number of attempts.\n * Useful for handling transient errors (e.g., network flakiness, rate limits, temporary server issues).\n * - **Backoff Strategies**: Controls the delay between retry attempts (default: `'exponential'`):\n * - `'exponential'`: Wait time doubles with each attempt (1s, 2s, 4s, ...) - **DEFAULT**\n * - `'linear'`: Wait time increases linearly (1s, 2s, 3s, ...)\n * - `'fixed'`: Waits a constant amount of time (`baseWait`) between each attempt\n * - **Jitter**: Adds randomness to retry delays to prevent thundering herd problems (default: `0`).\n * Set to a value between 0-1 to apply that percentage of random variation to each delay.\n * - **Timeout Controls**: Set limits on execution time to prevent hanging operations:\n * - `maxExecutionTime`: Maximum time for a single function call (default: `Infinity`)\n * - `maxTotalExecutionTime`: Maximum time for the entire retry operation (default: `Infinity`)\n * - **Abort & Cancellation**: Supports cancellation via an internal `AbortController`. Call `abort()` to stop retries.\n * Use `getAbortSignal()` to make your async function actually cancellable (e.g., with fetch requests).\n *\n * ## State Management\n *\n * Uses TanStack Store for fine-grained reactivity. State can be accessed via the `store.state` property.\n *\n * Available state properties:\n * - `currentAttempt`: The current retry attempt number (0 when not executing)\n * - `executionCount`: Total number of completed executions (successful or failed)\n * - `isExecuting`: Whether the retryer is currently executing the function\n * - `lastError`: The most recent error encountered during execution\n * - `lastExecutionTime`: Timestamp of the last execution completion in milliseconds\n * - `lastResult`: The result from the most recent successful execution\n * - `status`: Current execution status ('disabled' | 'idle' | 'executing' | 'retrying')\n * - `totalExecutionTime`: Total time spent executing (including retries) in milliseconds\n *\n * ## Error Handling\n *\n * The `throwOnError` option controls when errors are thrown (default: `'last'`):\n * - `'last'`: Only throws the final error after all retries are exhausted - **DEFAULT**\n * - `true`: Throws every error immediately (disables retrying)\n * - `false`: Never throws errors, returns `undefined` instead\n *\n * Callbacks for lifecycle management:\n * - `onAbort`: Called when execution is aborted (manually or due to timeouts)\n * - `onError`: Called for every error (including during retries)\n * - `onLastError`: Called only for the final error after all retries fail\n * - `onRetry`: Called before each retry attempt\n * - `onSettled`: Called after execution completes (success or failure) of each attempt\n * - `onSuccess`: Called when execution succeeds\n * - `onExecutionTimeout`: Called when a single execution attempt times out\n * - `onTotalExecutionTimeout`: Called when the total execution time times out\n *\n * ## Usage\n *\n * - Use for async operations that may fail transiently and benefit from retrying.\n * - Configure `maxAttempts`, `backoff`, `baseWait`, and `jitter` to control retry behavior.\n * - Set `maxExecutionTime` and `maxTotalExecutionTime` to prevent hanging operations.\n * - Use `onAbort`, `onError`, `onLastError`, `onRetry`, `onSettled`, `onSuccess`, `onExecutionTimeout`, and `onTotalExecutionTimeout` for custom side effects.\n * - Call `abort()` to cancel ongoing execution and pending retries.\n * - Call `reset()` to reset state and cancel execution.\n * - Use `getAbortSignal()` to make your async function cancellable.\n * - Use dynamic options (functions) for `maxAttempts`, `baseWait`, and `enabled` based on retryer state.\n *\n * **Important:** This class is designed for single-use execution. Calling `execute()` multiple times\n * on the same instance will abort previous executions. For multiple calls, create a new instance\n * each time.\n *\n * @example\n * ```typescript\n * // Retry a fetch operation up to 5 times with exponential backoff, jitter, and timeouts\n * const retryer = new AsyncRetryer(async (url: string) => {\n * const signal = retryer.getAbortSignal()\n * return await fetch(url, { signal })\n * }, {\n * maxAttempts: 5,\n * backoff: 'exponential',\n * baseWait: 1000,\n * jitter: 0.1, // Add 10% random variation to prevent thundering herd\n * maxExecutionTime: 5000, // Abort individual calls after 5 seconds\n * maxTotalExecutionTime: 30000, // Abort entire operation after 30 seconds\n * onRetry: (attempt, error) => console.log(`Retry attempt ${attempt} after error:`, error),\n * onSuccess: (result) => console.log('Success:', result),\n * onError: (error) => console.error('Error:', error),\n * onLastError: (error) => console.error('All retries failed:', error),\n * })\n *\n * const result = await retryer.execute('/api/data')\n * ```\n *\n * @template TFn The async function type to be retried.\n */\nexport class AsyncRetryer<TFn extends AnyAsyncFunction> {\n readonly store: Store<Readonly<AsyncRetryerState<TFn>>> = new Store(\n getDefaultAsyncRetryerState<TFn>(),\n )\n key: string | undefined\n options: AsyncRetryerOptions<TFn> & typeof defaultOptions\n #abortController: AbortController | null = null\n\n /**\n * Creates a new AsyncRetryer instance\n * @param fn The async function to retry\n * @param initialOptions Configuration options for the retryer\n */\n constructor(\n public fn: TFn,\n initialOptions: AsyncRetryerOptions<TFn> = {},\n ) {\n this.key = initialOptions.key\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError:\n initialOptions.throwOnError ??\n (initialOptions.onError ? false : defaultOptions.throwOnError),\n }\n this.#setState(this.options.initialState ?? {})\n\n if (this.key) {\n pacerEventClient.on('d-AsyncRetryer', (event) => {\n if (event.payload.key !== this.key) return\n this.#setState(event.payload.store.state as AsyncRetryerState<TFn>)\n this.setOptions(event.payload.options)\n })\n }\n }\n\n /**\n * Updates the retryer options\n * @param newOptions Partial options to merge with existing options\n */\n setOptions = (newOptions: Partial<AsyncRetryerOptions<TFn>>): void => {\n this.options = { ...this.options, ...newOptions }\n }\n\n #setState = (newState: Partial<AsyncRetryerState<TFn>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n const { isExecuting, currentAttempt } = combinedState\n return {\n ...combinedState,\n status: !this.#getEnabled()\n ? 'disabled'\n : isExecuting && currentAttempt === 1\n ? 'executing'\n : isExecuting && currentAttempt > 1\n ? 'retrying'\n : 'idle',\n }\n })\n emitChange('AsyncRetryer', this)\n }\n\n #getEnabled = (): boolean => {\n return !!parseFunctionOrValue(this.options.enabled, this)\n }\n\n #getMaxAttempts = (): number => {\n return parseFunctionOrValue(this.options.maxAttempts, this)\n }\n\n #getBaseWait = (): number => {\n return parseFunctionOrValue(this.options.baseWait, this)\n }\n\n #calculateJitter = (waitTime: number): number => {\n const jitterAmount = this.options.jitter\n if (jitterAmount <= 0) return 0\n\n try {\n const crypto =\n typeof globalThis !== 'undefined' ? globalThis.crypto : undefined\n if (crypto?.getRandomValues) {\n const array = new Uint32Array(1)\n crypto.getRandomValues(array)\n // Convert to 0-1 range and apply jitter percentage\n const randomFactor = (array[0]! / 0xffffffff) * 2 - 1 // -1 to 1\n return Math.floor(waitTime * jitterAmount * randomFactor)\n }\n } catch {\n // No crypto available\n }\n return 0\n }\n\n #calculateWait = (attempt: number): number => {\n const baseWait = this.#getBaseWait()\n let waitTime: number\n\n switch (this.options.backoff) {\n case 'linear':\n waitTime = baseWait * attempt\n break\n case 'exponential':\n waitTime = baseWait * Math.pow(2, attempt - 1)\n break\n case 'fixed':\n default:\n waitTime = baseWait\n break\n }\n\n const jitter = this.#calculateJitter(waitTime)\n return Math.max(0, waitTime + jitter)\n }\n\n /**\n * Executes the function with retry logic\n * @param args Arguments to pass to the function\n * @returns The function result, or undefined if disabled or all retries failed (when throwOnError is false)\n * @throws The last error if throwOnError is true and all retries fail\n */\n execute = async (\n ...args: Parameters<TFn>\n ): Promise<Awaited<ReturnType<TFn>> | undefined> => {\n if (!this.#getEnabled()) {\n return undefined\n }\n\n // Cancel any existing execution\n this.abort('new-execution')\n\n const startTime = Date.now()\n let lastError: Error | undefined\n let result: Awaited<ReturnType<TFn>> | undefined\n\n this.#abortController = new AbortController()\n const signal = this.#abortController.signal\n\n this.#setState({\n isExecuting: true,\n currentAttempt: 0,\n lastError: undefined,\n })\n\n // Set up total execution timeout\n let totalTimeoutId: NodeJS.Timeout | undefined\n if (this.options.maxTotalExecutionTime !== Infinity) {\n totalTimeoutId = setTimeout(() => {\n this.options.onTotalExecutionTimeout?.(this)\n this.abort('total-timeout')\n }, this.options.maxTotalExecutionTime)\n }\n\n let isLastAttempt = false\n for (let attempt = 1; attempt <= this.#getMaxAttempts(); attempt++) {\n isLastAttempt = attempt === this.#getMaxAttempts()\n this.#setState({ currentAttempt: attempt })\n\n try {\n if (signal.aborted) {\n return undefined\n }\n\n // Check if total execution time has been exceeded\n const currentTotalTime = Date.now() - startTime\n if (\n this.options.maxTotalExecutionTime !== Infinity &&\n currentTotalTime >= this.options.maxTotalExecutionTime\n ) {\n this.options.onTotalExecutionTimeout?.(this)\n this.abort('total-timeout')\n return undefined\n }\n\n // Execute with individual timeout if specified\n if (this.options.maxExecutionTime === Infinity) {\n result = (await this.fn(...args)) as Awaited<ReturnType<TFn>>\n } else {\n result = (await Promise.race([\n this.fn(...args),\n new Promise<never>((_, reject) => {\n const timeout = setTimeout(() => {\n this.options.onExecutionTimeout?.(this)\n this.abort('execution-timeout')\n reject(\n new Error(\n `Execution timeout: ${this.options.maxExecutionTime}ms exceeded`,\n ),\n )\n }, this.options.maxExecutionTime)\n\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(timeout)\n reject(new Error('Aborted'))\n },\n { once: true },\n )\n }),\n ])) as Awaited<ReturnType<TFn>>\n }\n\n // Check if cancelled during execution\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (signal.aborted) {\n return undefined\n }\n\n const totalTime = Date.now() - startTime\n this.#setState({\n executionCount: this.store.state.executionCount + 1,\n isExecuting: false,\n lastExecutionTime: Date.now(),\n totalExecutionTime: totalTime,\n currentAttempt: 0,\n lastResult: result,\n })\n\n this.options.onSuccess?.(result as Awaited<ReturnType<TFn>>, args, this)\n\n return result\n } catch (error) {\n // Treat abort as a non-error cancellation outcome\n if (\n error &&\n typeof error === 'object' &&\n 'name' in error &&\n (error as Error).name === 'AbortError'\n ) {\n return undefined\n }\n lastError = error instanceof Error ? error : new Error(String(error))\n this.#setState({ lastError })\n\n // Call onError for every error (including during retries)\n this.options.onError?.(lastError, args, this)\n\n if (attempt < this.#getMaxAttempts()) {\n this.options.onRetry?.(attempt, lastError, this)\n\n const wait = this.#calculateWait(attempt)\n if (wait > 0) {\n // Eagerly reflect retrying status during the wait window\n this.#setState({ isExecuting: true, currentAttempt: attempt + 1 })\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, wait)\n const onAbort = () => {\n clearTimeout(timeout)\n signal.removeEventListener('abort', onAbort)\n resolve()\n }\n signal.addEventListener('abort', onAbort)\n })\n if (signal.aborted) {\n return undefined\n }\n }\n }\n } finally {\n this.options.onSettled?.(args, this)\n }\n }\n\n // Clean up total timeout\n if (totalTimeoutId) {\n clearTimeout(totalTimeoutId)\n }\n\n // Exhausted retries - finalize state\n this.#setState({ isExecuting: false })\n this.options.onLastError?.(lastError as Error, this)\n this.options.onSettled?.(args, this)\n\n if (\n (this.options.throwOnError === 'last' && isLastAttempt) ||\n this.options.throwOnError === true\n ) {\n throw lastError\n }\n\n return undefined\n }\n\n /**\n * Returns the current AbortSignal for the executing operation.\n * Use this signal in your async function to make it cancellable.\n * Returns null when not currently executing.\n *\n * @example\n * ```typescript\n * const retryer = new AsyncRetryer(async (userId: string) => {\n * const signal = retryer.getAbortSignal()\n * if (signal) {\n * return fetch(`/api/users/${userId}`, { signal })\n * }\n * return fetch(`/api/users/${userId}`)\n * })\n *\n * // Abort will now actually cancel the fetch\n * retryer.abort()\n * ```\n */\n getAbortSignal(): AbortSignal | null {\n return this.#abortController?.signal ?? null\n }\n\n /**\n * Cancels the current execution and any pending retries\n * @param reason The reason for the abort (defaults to 'manual')\n */\n abort = (\n reason:\n | 'manual'\n | 'execution-timeout'\n | 'total-timeout'\n | 'new-execution' = 'manual',\n ): void => {\n if (this.#abortController) {\n this.#abortController.abort()\n this.#abortController = null\n this.#setState({\n isExecuting: false,\n })\n this.options.onAbort?.(reason, this)\n }\n }\n\n /**\n * Resets the retryer to its initial state\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncRetryerState<TFn>())\n }\n}\n\n/**\n * Creates a retry-enabled version of an async function. This is a convenience wrapper\n * around the AsyncRetryer class that returns the execute method.\n *\n * @param fn The async function to add retry functionality to\n * @param initialOptions Configuration options for the retry behavior\n * @returns A new function that executes the original with retry logic\n *\n * @example\n * ```typescript\n * // Define your async function normally\n * async function fetchData(url: string) {\n * const response = await fetch(url)\n * if (!response.ok) throw new Error('Request failed')\n * return response.json()\n * }\n *\n * // Create retry-enabled function\n * const fetchWithRetry = asyncRetry(fetchData, {\n * maxAttempts: 3,\n * backoff: 'exponential',\n * baseWait: 1000,\n * jitter: 0.1\n * })\n *\n * // Call it multiple times\n * const data1 = await fetchWithRetry('/api/data1')\n * const data2 = await fetchWithRetry('/api/data2')\n * ```\n */\nexport function asyncRetry<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncRetryerOptions<TFn> = {},\n): (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined> {\n const retryer = new AsyncRetryer(fn, initialOptions)\n return retryer.execute\n}\n"],"names":[],"mappings":";;;AA4CA,SAAS,8BAEmB;AAC1B,SAAO,gBAAgB;AAAA,IACrB,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,aAAa;AAAA,IACb,WAAW;AAAA,IACX,mBAAmB;AAAA,IACnB,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,oBAAoB;AAAA,EAAA,CACrB;AACH;AA0GO,SAAS,oBAKd,SAA6B;AAC7B,SAAO;AACT;AAEA,MAAM,iBAYF;AAAA,EACF,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,cAAc;AAChB;AA8FO,MAAM,aAA2C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAatD,YACS,IACP,iBAA2C,IAC3C;AAFO,SAAA,KAAA;AAbT,SAAS,QAAiD,IAAI;AAAA,MAC5D,4BAAA;AAAA,IAAiC;AAInC,SAAA,mBAA2C;AAkC3C,SAAA,aAAa,CAAC,eAAwD;AACpE,WAAK,UAAU,EAAE,GAAG,KAAK,SAAS,GAAG,WAAA;AAAA,IACvC;AAEA,SAAA,YAAY,CAAC,aAAoD;AAC/D,WAAK,MAAM,SAAS,CAAC,UAAU;AAC7B,cAAM,gBAAgB;AAAA,UACpB,GAAG;AAAA,UACH,GAAG;AAAA,QAAA;AAEL,cAAM,EAAE,aAAa,eAAA,IAAmB;AACxC,eAAO;AAAA,UACL,GAAG;AAAA,UACH,QAAQ,CAAC,KAAK,YAAA,IACV,aACA,eAAe,mBAAmB,IAChC,cACA,eAAe,iBAAiB,IAC9B,aACA;AAAA,QAAA;AAAA,MAEZ,CAAC;AACD,iBAAW,gBAAgB,IAAI;AAAA,IACjC;AAEA,SAAA,cAAc,MAAe;AAC3B,aAAO,CAAC,CAAC,qBAAqB,KAAK,QAAQ,SAAS,IAAI;AAAA,IAC1D;AAEA,SAAA,kBAAkB,MAAc;AAC9B,aAAO,qBAAqB,KAAK,QAAQ,aAAa,IAAI;AAAA,IAC5D;AAEA,SAAA,eAAe,MAAc;AAC3B,aAAO,qBAAqB,KAAK,QAAQ,UAAU,IAAI;AAAA,IACzD;AAEA,SAAA,mBAAmB,CAAC,aAA6B;AAC/C,YAAM,eAAe,KAAK,QAAQ;AAClC,UAAI,gBAAgB,EAAG,QAAO;AAE9B,UAAI;AACF,cAAM,SACJ,OAAO,eAAe,cAAc,WAAW,SAAS;AAC1D,YAAI,QAAQ,iBAAiB;AAC3B,gBAAM,QAAQ,IAAI,YAAY,CAAC;AAC/B,iBAAO,gBAAgB,KAAK;AAE5B,gBAAM,eAAgB,MAAM,CAAC,IAAK,aAAc,IAAI;AACpD,iBAAO,KAAK,MAAM,WAAW,eAAe,YAAY;AAAA,QAC1D;AAAA,MACF,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,IACT;AAEA,SAAA,iBAAiB,CAAC,YAA4B;AAC5C,YAAM,WAAW,KAAK,aAAA;AACtB,UAAI;AAEJ,cAAQ,KAAK,QAAQ,SAAA;AAAA,QACnB,KAAK;AACH,qBAAW,WAAW;AACtB;AAAA,QACF,KAAK;AACH,qBAAW,WAAW,KAAK,IAAI,GAAG,UAAU,CAAC;AAC7C;AAAA,QACF,KAAK;AAAA,QACL;AACE,qBAAW;AACX;AAAA,MAAA;AAGJ,YAAM,SAAS,KAAK,iBAAiB,QAAQ;AAC7C,aAAO,KAAK,IAAI,GAAG,WAAW,MAAM;AAAA,IACtC;AAQA,SAAA,UAAU,UACL,SAC+C;AAClD,UAAI,CAAC,KAAK,eAAe;AACvB,eAAO;AAAA,MACT;AAGA,WAAK,MAAM,eAAe;AAE1B,YAAM,YAAY,KAAK,IAAA;AACvB,UAAI;AACJ,UAAI;AAEJ,WAAK,mBAAmB,IAAI,gBAAA;AAC5B,YAAM,SAAS,KAAK,iBAAiB;AAErC,WAAK,UAAU;AAAA,QACb,aAAa;AAAA,QACb,gBAAgB;AAAA,QAChB,WAAW;AAAA,MAAA,CACZ;AAGD,UAAI;AACJ,UAAI,KAAK,QAAQ,0BAA0B,UAAU;AACnD,yBAAiB,WAAW,MAAM;AAChC,eAAK,QAAQ,0BAA0B,IAAI;AAC3C,eAAK,MAAM,eAAe;AAAA,QAC5B,GAAG,KAAK,QAAQ,qBAAqB;AAAA,MACvC;AAEA,UAAI,gBAAgB;AACpB,eAAS,UAAU,GAAG,WAAW,KAAK,gBAAA,GAAmB,WAAW;AAClE,wBAAgB,YAAY,KAAK,gBAAA;AACjC,aAAK,UAAU,EAAE,gBAAgB,QAAA,CAAS;AAE1C,YAAI;AACF,cAAI,OAAO,SAAS;AAClB,mBAAO;AAAA,UACT;AAGA,gBAAM,mBAAmB,KAAK,IAAA,IAAQ;AACtC,cACE,KAAK,QAAQ,0BAA0B,YACvC,oBAAoB,KAAK,QAAQ,uBACjC;AACA,iBAAK,QAAQ,0BAA0B,IAAI;AAC3C,iBAAK,MAAM,eAAe;AAC1B,mBAAO;AAAA,UACT;AAGA,cAAI,KAAK,QAAQ,qBAAqB,UAAU;AAC9C,qBAAU,MAAM,KAAK,GAAG,GAAG,IAAI;AAAA,UACjC,OAAO;AACL,qBAAU,MAAM,QAAQ,KAAK;AAAA,cAC3B,KAAK,GAAG,GAAG,IAAI;AAAA,cACf,IAAI,QAAe,CAAC,GAAG,WAAW;AAChC,sBAAM,UAAU,WAAW,MAAM;AAC/B,uBAAK,QAAQ,qBAAqB,IAAI;AACtC,uBAAK,MAAM,mBAAmB;AAC9B;AAAA,oBACE,IAAI;AAAA,sBACF,sBAAsB,KAAK,QAAQ,gBAAgB;AAAA,oBAAA;AAAA,kBACrD;AAAA,gBAEJ,GAAG,KAAK,QAAQ,gBAAgB;AAEhC,uBAAO;AAAA,kBACL;AAAA,kBACA,MAAM;AACJ,iCAAa,OAAO;AACpB,2BAAO,IAAI,MAAM,SAAS,CAAC;AAAA,kBAC7B;AAAA,kBACA,EAAE,MAAM,KAAA;AAAA,gBAAK;AAAA,cAEjB,CAAC;AAAA,YAAA,CACF;AAAA,UACH;AAIA,cAAI,OAAO,SAAS;AAClB,mBAAO;AAAA,UACT;AAEA,gBAAM,YAAY,KAAK,IAAA,IAAQ;AAC/B,eAAK,UAAU;AAAA,YACb,gBAAgB,KAAK,MAAM,MAAM,iBAAiB;AAAA,YAClD,aAAa;AAAA,YACb,mBAAmB,KAAK,IAAA;AAAA,YACxB,oBAAoB;AAAA,YACpB,gBAAgB;AAAA,YAChB,YAAY;AAAA,UAAA,CACb;AAED,eAAK,QAAQ,YAAY,QAAoC,MAAM,IAAI;AAEvE,iBAAO;AAAA,QACT,SAAS,OAAO;AAEd,cACE,SACA,OAAO,UAAU,YACjB,UAAU,SACT,MAAgB,SAAS,cAC1B;AACA,mBAAO;AAAA,UACT;AACA,sBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,eAAK,UAAU,EAAE,WAAW;AAG5B,eAAK,QAAQ,UAAU,WAAW,MAAM,IAAI;AAE5C,cAAI,UAAU,KAAK,mBAAmB;AACpC,iBAAK,QAAQ,UAAU,SAAS,WAAW,IAAI;AAE/C,kBAAM,OAAO,KAAK,eAAe,OAAO;AACxC,gBAAI,OAAO,GAAG;AAEZ,mBAAK,UAAU,EAAE,aAAa,MAAM,gBAAgB,UAAU,GAAG;AACjE,oBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,sBAAM,UAAU,WAAW,MAAM;AAC/B,yBAAO,oBAAoB,SAAS,OAAO;AAC3C,0BAAA;AAAA,gBACF,GAAG,IAAI;AACP,sBAAM,UAAU,MAAM;AACpB,+BAAa,OAAO;AACpB,yBAAO,oBAAoB,SAAS,OAAO;AAC3C,0BAAA;AAAA,gBACF;AACA,uBAAO,iBAAiB,SAAS,OAAO;AAAA,cAC1C,CAAC;AACD,kBAAI,OAAO,SAAS;AAClB,uBAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AAAA,QACF,UAAA;AACE,eAAK,QAAQ,YAAY,MAAM,IAAI;AAAA,QACrC;AAAA,MACF;AAGA,UAAI,gBAAgB;AAClB,qBAAa,cAAc;AAAA,MAC7B;AAGA,WAAK,UAAU,EAAE,aAAa,MAAA,CAAO;AACrC,WAAK,QAAQ,cAAc,WAAoB,IAAI;AACnD,WAAK,QAAQ,YAAY,MAAM,IAAI;AAEnC,UACG,KAAK,QAAQ,iBAAiB,UAAU,iBACzC,KAAK,QAAQ,iBAAiB,MAC9B;AACA,cAAM;AAAA,MACR;AAEA,aAAO;AAAA,IACT;AA6BA,SAAA,QAAQ,CACN,SAIsB,aACb;AACT,UAAI,KAAK,kBAAkB;AACzB,aAAK,iBAAiB,MAAA;AACtB,aAAK,mBAAmB;AACxB,aAAK,UAAU;AAAA,UACb,aAAa;AAAA,QAAA,CACd;AACD,aAAK,QAAQ,UAAU,QAAQ,IAAI;AAAA,MACrC;AAAA,IACF;AAKA,SAAA,QAAQ,MAAY;AAClB,WAAK,UAAU,6BAAkC;AAAA,IACnD;AAlUE,SAAK,MAAM,eAAe;AAC1B,SAAK,UAAU;AAAA,MACb,GAAG;AAAA,MACH,GAAG;AAAA,MACH,cACE,eAAe,iBACd,eAAe,UAAU,QAAQ,eAAe;AAAA,IAAA;AAErD,SAAK,UAAU,KAAK,QAAQ,gBAAgB,CAAA,CAAE;AAE9C,QAAI,KAAK,KAAK;AACZ,uBAAiB,GAAG,kBAAkB,CAAC,UAAU;AAC/C,YAAI,MAAM,QAAQ,QAAQ,KAAK,IAAK;AACpC,aAAK,UAAU,MAAM,QAAQ,MAAM,KAA+B;AAClE,aAAK,WAAW,MAAM,QAAQ,OAAO;AAAA,MACvC,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EA5BA;AAAA,EAsCA;AAAA,EAqBA;AAAA,EAIA;AAAA,EAIA;AAAA,EAIA;AAAA,EAoBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoNA,iBAAqC;AACnC,WAAO,KAAK,kBAAkB,UAAU;AAAA,EAC1C;AA6BF;AAgCO,SAAS,WACd,IACA,iBAA2C,IACkC;AAC7E,QAAM,UAAU,IAAI,aAAa,IAAI,cAAc;AACnD,SAAO,QAAQ;AACjB;"}
@@ -1,315 +0,0 @@
1
- import { Store } from '@tanstack/store';
2
- import { AsyncRetryer, AsyncRetryerOptions } from './async-retryer.js';
3
- import { AnyAsyncFunction } from './types.js';
4
- export interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
5
- /**
6
- * Number of function executions that have resulted in errors
7
- */
8
- errorCount: number;
9
- /**
10
- * Whether the throttled function is currently executing asynchronously
11
- */
12
- isExecuting: boolean;
13
- /**
14
- * Whether the throttler is waiting for the timeout to trigger execution
15
- */
16
- isPending: boolean;
17
- /**
18
- * The arguments from the most recent call to maybeExecute
19
- */
20
- lastArgs: Parameters<TFn> | undefined;
21
- /**
22
- * Timestamp of the last function execution in milliseconds
23
- */
24
- lastExecutionTime: number;
25
- /**
26
- * The result from the most recent successful function execution
27
- */
28
- lastResult: ReturnType<TFn> | undefined;
29
- /**
30
- * Number of times maybeExecute has been called (for reduction calculations)
31
- */
32
- maybeExecuteCount: number;
33
- /**
34
- * Timestamp when the next execution can occur in milliseconds
35
- */
36
- nextExecutionTime: number | undefined;
37
- /**
38
- * Number of function executions that have completed (either successfully or with errors)
39
- */
40
- settleCount: number;
41
- /**
42
- * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed
43
- */
44
- status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled';
45
- /**
46
- * Number of function executions that have completed successfully
47
- */
48
- successCount: number;
49
- }
50
- /**
51
- * Options for configuring an async throttled function
52
- */
53
- export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
54
- /**
55
- * Options for configuring the underlying async retryer
56
- */
57
- asyncRetryerOptions?: AsyncRetryerOptions<TFn>;
58
- /**
59
- * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions.
60
- * Can be a boolean or a function that returns a boolean.
61
- * Defaults to true.
62
- */
63
- enabled?: boolean | ((throttler: AsyncThrottler<TFn>) => boolean);
64
- /**
65
- * Initial state for the async throttler
66
- */
67
- initialState?: Partial<AsyncThrottlerState<TFn>>;
68
- /**
69
- * Optional key to identify this async throttler instance.
70
- * If provided, the async throttler will be identified by this key in the devtools and PacerProvider if applicable.
71
- */
72
- key?: string;
73
- /**
74
- * Whether to execute the function immediately when called
75
- * Defaults to true
76
- */
77
- leading?: boolean;
78
- /**
79
- * Optional error handler for when the throttled function throws.
80
- * If provided, the handler will be called with the error and throttler instance.
81
- * This can be used alongside throwOnError - the handler will be called before any error is thrown.
82
- */
83
- onError?: (error: Error, args: Parameters<TFn>, asyncThrottler: AsyncThrottler<TFn>) => void;
84
- /**
85
- * Optional function to call when the throttled function is executed
86
- */
87
- onSettled?: (args: Parameters<TFn>, asyncThrottler: AsyncThrottler<TFn>) => void;
88
- /**
89
- * Optional function to call when the throttled function is executed
90
- */
91
- onSuccess?: (result: ReturnType<TFn>, args: Parameters<TFn>, asyncThrottler: AsyncThrottler<TFn>) => void;
92
- /**
93
- * Whether to throw errors when they occur.
94
- * Defaults to true if no onError handler is provided, false if an onError handler is provided.
95
- * Can be explicitly set to override these defaults.
96
- */
97
- throwOnError?: boolean;
98
- /**
99
- * Whether to execute the function on the trailing edge of the wait period
100
- * Defaults to true
101
- */
102
- trailing?: boolean;
103
- /**
104
- * Time window in milliseconds during which the function can only be executed once.
105
- * Can be a number or a function that returns a number.
106
- * Defaults to 0ms
107
- */
108
- wait: number | ((throttler: AsyncThrottler<TFn>) => number);
109
- }
110
- /**
111
- * Utility function for sharing common `AsyncThrottlerOptions` options between different `AsyncThrottler` instances.
112
- */
113
- export declare function asyncThrottlerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncThrottlerOptions<TFn>> = Partial<AsyncThrottlerOptions<TFn>>>(options: TOptions): TOptions;
114
- /**
115
- * A class that creates an async throttled function.
116
- *
117
- * Async vs Sync Versions:
118
- * The async version provides advanced features over the sync Throttler:
119
- * - Returns promises that can be awaited for throttled function results
120
- * - Built-in retry support via AsyncRetryer integration
121
- * - Abort support to cancel in-flight executions
122
- * - Cancel support to prevent pending executions from starting
123
- * - Comprehensive error handling with onError callbacks and throwOnError control
124
- * - Detailed execution tracking (success/error/settle counts)
125
- * - Waits for ongoing executions to complete before scheduling the next one
126
- *
127
- * The sync Throttler is lighter weight and simpler when you don't need async features,
128
- * return values, or execution control.
129
- *
130
- * What is Throttling?
131
- * Throttling limits how often a function can be executed, allowing only one execution within a specified time window.
132
- * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a
133
- * regular interval regardless of how often it's called.
134
- *
135
- * This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to
136
- * ensure a maximum execution frequency.
137
- *
138
- * Error Handling:
139
- * - If an `onError` handler is provided, it will be called with the error and throttler instance
140
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
141
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
142
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
143
- * - The error state can be checked using the underlying AsyncThrottler instance
144
- *
145
- * State Management:
146
- * - Uses TanStack Store for reactive state management
147
- * - Use `initialState` to provide initial state values when creating the async throttler
148
- * - Use `onSuccess` callback to react to successful function execution and implement custom logic
149
- * - Use `onError` callback to react to function execution errors and implement custom error handling
150
- * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
151
- * - The state includes error count, execution status, last execution time, and success/settle counts
152
- * - State can be accessed via `asyncThrottler.store.state` when using the class directly
153
- * - When using framework adapters (React/Solid), state is accessed from `asyncThrottler.state`
154
- *
155
- * @example
156
- * ```ts
157
- * const throttler = new AsyncThrottler(async (value: string) => {
158
- * const result = await saveToAPI(value);
159
- * return result; // Return value is preserved
160
- * }, {
161
- * wait: 1000,
162
- * onError: (error) => {
163
- * console.error('API call failed:', error);
164
- * }
165
- * });
166
- *
167
- * // Will only execute once per second no matter how often called
168
- * // Returns the API response directly
169
- * const result = await throttler.maybeExecute(inputElement.value);
170
- * ```
171
- */
172
- export declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
173
- #private;
174
- fn: TFn;
175
- readonly store: Store<Readonly<AsyncThrottlerState<TFn>>>;
176
- key: string | undefined;
177
- options: AsyncThrottlerOptions<TFn>;
178
- asyncRetryers: Map<number, AsyncRetryer<TFn>>;
179
- constructor(fn: TFn, initialOptions: AsyncThrottlerOptions<TFn>);
180
- /**
181
- * Updates the async throttler options
182
- */
183
- setOptions: (newOptions: Partial<AsyncThrottlerOptions<TFn>>) => void;
184
- /**
185
- * Attempts to execute the throttled function. The execution behavior depends on the throttler options:
186
- *
187
- * - If enough time has passed since the last execution (>= wait period):
188
- * - With leading=true: Executes immediately
189
- * - With leading=false: Waits for the next trailing execution
190
- *
191
- * - If within the wait period:
192
- * - With trailing=true: Schedules execution for end of wait period
193
- * - With trailing=false: Drops the execution
194
- *
195
- * @example
196
- * ```ts
197
- * const throttled = new AsyncThrottler(fn, { wait: 1000 });
198
- *
199
- * // First call executes immediately
200
- * await throttled.maybeExecute('a', 'b');
201
- *
202
- * // Call during wait period - gets throttled
203
- * await throttled.maybeExecute('c', 'd');
204
- * ```
205
- */
206
- maybeExecute: (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;
207
- /**
208
- * Processes the current pending execution immediately
209
- */
210
- flush: () => Promise<ReturnType<TFn> | undefined>;
211
- /**
212
- * Returns the AbortSignal for a specific execution.
213
- * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
214
- * Returns null if no execution is found or not currently executing.
215
- *
216
- * @param maybeExecuteCount - Optional specific execution to get signal for
217
- * @example
218
- * ```typescript
219
- * const throttler = new AsyncThrottler(
220
- * async (data: string) => {
221
- * const signal = throttler.getAbortSignal()
222
- * if (signal) {
223
- * const response = await fetch('/api/save', {
224
- * method: 'POST',
225
- * body: data,
226
- * signal
227
- * })
228
- * return response.json()
229
- * }
230
- * },
231
- * { wait: 1000 }
232
- * )
233
- * ```
234
- */
235
- getAbortSignal(maybeExecuteCount?: number): AbortSignal | null;
236
- /**
237
- * Aborts all ongoing executions with the internal abort controllers.
238
- * Does NOT cancel any pending execution that have not started yet.
239
- */
240
- abort: () => void;
241
- /**
242
- * Cancels any pending execution that have not started yet.
243
- * Does NOT abort any execution already in progress.
244
- */
245
- cancel: () => void;
246
- /**
247
- * Resets the debouncer state to its default values
248
- */
249
- reset: () => void;
250
- }
251
- /**
252
- * Creates an async throttled function that limits how often the function can execute.
253
- * The throttled function will execute at most once per wait period, even if called multiple times.
254
- * If called while executing, it will wait until execution completes before scheduling the next call.
255
- *
256
- * Async vs Sync Versions:
257
- * The async version provides advanced features over the sync throttle function:
258
- * - Returns promises that can be awaited for throttled function results
259
- * - Built-in retry support via AsyncRetryer integration
260
- * - Abort support to cancel in-flight executions
261
- * - Cancel support to prevent pending executions from starting
262
- * - Comprehensive error handling with onError callbacks and throwOnError control
263
- * - Detailed execution tracking (success/error/settle counts)
264
- * - Waits for ongoing executions to complete before scheduling the next one
265
- *
266
- * The sync throttle function is lighter weight and simpler when you don't need async features,
267
- * return values, or execution control.
268
- *
269
- * What is Throttling?
270
- * Throttling limits how often a function can be executed, allowing only one execution within a specified time window.
271
- * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a
272
- * regular interval regardless of how often it's called.
273
- *
274
- * Configuration Options:
275
- * - `wait`: Time window in milliseconds during which the function can only execute once (required)
276
- * - `leading`: Execute immediately when called (default: true)
277
- * - `trailing`: Execute on the trailing edge of the wait period (default: true)
278
- * - `enabled`: Whether the throttler is enabled (default: true)
279
- * - `asyncRetryerOptions`: Configure retry behavior for executions
280
- *
281
- * Error Handling:
282
- * - If an `onError` handler is provided, it will be called with the error and throttler instance
283
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
284
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
285
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
286
- * - The error state can be checked using the underlying AsyncThrottler instance
287
- *
288
- * State Management:
289
- * - Uses TanStack Store for reactive state management
290
- * - Use `initialState` to provide initial state values when creating the async throttler
291
- * - Use `onSuccess` callback to react to successful function execution and implement custom logic
292
- * - Use `onError` callback to react to function execution errors and implement custom error handling
293
- * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
294
- * - The state includes error count, execution status, last execution time, and success/settle counts
295
- * - State can be accessed via the underlying AsyncThrottler instance's `store.state` property
296
- * - When using framework adapters (React/Solid), state is accessed from the hook's state property
297
- *
298
- * @example
299
- * ```ts
300
- * const throttled = asyncThrottle(async (value: string) => {
301
- * const result = await saveToAPI(value);
302
- * return result; // Return value is preserved
303
- * }, {
304
- * wait: 1000,
305
- * onError: (error) => {
306
- * console.error('API call failed:', error);
307
- * }
308
- * });
309
- *
310
- * // This will execute at most once per second
311
- * // Returns the API response directly
312
- * const result = await throttled(inputElement.value);
313
- * ```
314
- */
315
- export declare function asyncThrottle<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncThrottlerOptions<TFn>): (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;