@tanstack/pacer 0.21.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +21 -6
  2. package/dist/async-batcher.d.ts +11 -14
  3. package/dist/async-batcher.js +186 -132
  4. package/dist/async-debouncer.d.ts +10 -13
  5. package/dist/async-debouncer.js +187 -146
  6. package/dist/async-queuer.d.ts +12 -14
  7. package/dist/async-queuer.js +384 -259
  8. package/dist/async-rate-limiter.d.ts +9 -12
  9. package/dist/async-rate-limiter.js +198 -147
  10. package/dist/async-retryer.d.ts +10 -10
  11. package/dist/async-retryer.js +209 -188
  12. package/dist/async-throttler.d.ts +10 -13
  13. package/dist/async-throttler.js +215 -161
  14. package/dist/batcher.d.ts +5 -8
  15. package/dist/batcher.js +107 -87
  16. package/dist/debouncer.d.ts +6 -9
  17. package/dist/debouncer.js +99 -86
  18. package/dist/event-client.d.ts +8 -11
  19. package/dist/event-client.js +1 -2
  20. package/dist/index.js +1 -1
  21. package/dist/queuer.d.ts +8 -10
  22. package/dist/queuer.js +277 -207
  23. package/dist/rate-limiter.d.ts +6 -9
  24. package/dist/rate-limiter.js +127 -109
  25. package/dist/throttler.d.ts +6 -9
  26. package/dist/throttler.js +127 -90
  27. package/dist/types.d.ts +4 -6
  28. package/dist/utils.d.ts +3 -6
  29. package/dist/utils.js +1 -2
  30. package/package.json +23 -70
  31. package/dist/async-batcher.cjs +0 -337
  32. package/dist/async-batcher.cjs.map +0 -1
  33. package/dist/async-batcher.d.cts +0 -344
  34. package/dist/async-batcher.js.map +0 -1
  35. package/dist/async-debouncer.cjs +0 -330
  36. package/dist/async-debouncer.cjs.map +0 -1
  37. package/dist/async-debouncer.d.cts +0 -300
  38. package/dist/async-debouncer.js.map +0 -1
  39. package/dist/async-queuer.cjs +0 -484
  40. package/dist/async-queuer.cjs.map +0 -1
  41. package/dist/async-queuer.d.cts +0 -440
  42. package/dist/async-queuer.js.map +0 -1
  43. package/dist/async-rate-limiter.cjs +0 -373
  44. package/dist/async-rate-limiter.cjs.map +0 -1
  45. package/dist/async-rate-limiter.d.cts +0 -357
  46. package/dist/async-rate-limiter.js.map +0 -1
  47. package/dist/async-retryer.cjs +0 -374
  48. package/dist/async-retryer.cjs.map +0 -1
  49. package/dist/async-retryer.d.cts +0 -319
  50. package/dist/async-retryer.js.map +0 -1
  51. package/dist/async-throttler.cjs +0 -347
  52. package/dist/async-throttler.cjs.map +0 -1
  53. package/dist/async-throttler.d.cts +0 -320
  54. package/dist/async-throttler.js.map +0 -1
  55. package/dist/batcher.cjs +0 -200
  56. package/dist/batcher.cjs.map +0 -1
  57. package/dist/batcher.d.cts +0 -180
  58. package/dist/batcher.js.map +0 -1
  59. package/dist/debouncer.cjs +0 -203
  60. package/dist/debouncer.cjs.map +0 -1
  61. package/dist/debouncer.d.cts +0 -167
  62. package/dist/debouncer.js.map +0 -1
  63. package/dist/event-client.cjs +0 -64
  64. package/dist/event-client.cjs.map +0 -1
  65. package/dist/event-client.d.cts +0 -66
  66. package/dist/event-client.js.map +0 -1
  67. package/dist/index.cjs +0 -52
  68. package/dist/index.d.cts +0 -15
  69. package/dist/queuer.cjs +0 -401
  70. package/dist/queuer.cjs.map +0 -1
  71. package/dist/queuer.d.cts +0 -345
  72. package/dist/queuer.js.map +0 -1
  73. package/dist/rate-limiter.cjs +0 -263
  74. package/dist/rate-limiter.cjs.map +0 -1
  75. package/dist/rate-limiter.d.cts +0 -215
  76. package/dist/rate-limiter.js.map +0 -1
  77. package/dist/throttler.cjs +0 -215
  78. package/dist/throttler.cjs.map +0 -1
  79. package/dist/throttler.d.cts +0 -207
  80. package/dist/throttler.js.map +0 -1
  81. package/dist/types.cjs +0 -0
  82. package/dist/types.d.cts +0 -13
  83. package/dist/utils.cjs +0 -14
  84. package/dist/utils.cjs.map +0 -1
  85. package/dist/utils.d.cts +0 -8
  86. package/dist/utils.js.map +0 -1
  87. package/src/async-batcher.ts +0 -594
  88. package/src/async-debouncer.ts +0 -565
  89. package/src/async-queuer.ts +0 -925
  90. package/src/async-rate-limiter.ts +0 -647
  91. package/src/async-retryer.ts +0 -684
  92. package/src/async-throttler.ts +0 -633
  93. package/src/batcher.ts +0 -329
  94. package/src/debouncer.ts +0 -334
  95. package/src/event-client.ts +0 -129
  96. package/src/index.ts +0 -24
  97. package/src/queuer.ts +0 -740
  98. package/src/rate-limiter.ts +0 -429
  99. package/src/throttler.ts +0 -380
  100. package/src/types.ts +0 -12
  101. package/src/utils.ts +0 -12
@@ -1,9 +1,8 @@
1
1
  import { AnyAsyncFunction } from "./types.js";
2
2
  import { AsyncRetryer, AsyncRetryerOptions } from "./async-retryer.js";
3
3
  import { Store } from "@tanstack/store";
4
-
5
4
  //#region src/async-rate-limiter.d.ts
6
- interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
5
+ export interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
7
6
  /**
8
7
  * Number of function executions that have resulted in errors
9
8
  */
@@ -23,7 +22,7 @@ interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
23
22
  /**
24
23
  * The result from the most recent successful function execution
25
24
  */
26
- lastResult: ReturnType<TFn> | undefined;
25
+ lastResult: Awaited<ReturnType<TFn>> | undefined;
27
26
  /**
28
27
  * Number of function executions that have been rejected due to rate limiting
29
28
  */
@@ -48,7 +47,7 @@ interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
48
47
  /**
49
48
  * Options for configuring an async rate-limited function
50
49
  */
51
- interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
50
+ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
52
51
  /**
53
52
  * Options for configuring the underlying async retryer
54
53
  */
@@ -90,7 +89,7 @@ interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
90
89
  /**
91
90
  * Optional function to call when the rate-limited function is executed
92
91
  */
93
- onSuccess?: (result: ReturnType<TFn>, args: Parameters<TFn>, rateLimiter: AsyncRateLimiter<TFn>) => void;
92
+ onSuccess?: (result: Awaited<ReturnType<TFn>>, args: Parameters<TFn>, rateLimiter: AsyncRateLimiter<TFn>) => void;
94
93
  /**
95
94
  * Whether to throw errors when they occur.
96
95
  * Defaults to true if no onError handler is provided, false if an onError handler is provided.
@@ -113,7 +112,7 @@ interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
113
112
  /**
114
113
  * Utility function for sharing common `AsyncRateLimiterOptions` options between different `AsyncRateLimiter` instances.
115
114
  */
116
- declare function asyncRateLimiterOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncRateLimiterOptions<TFn>> = Partial<AsyncRateLimiterOptions<TFn>>>(options: TOptions): TOptions;
115
+ export declare function asyncRateLimiterOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncRateLimiterOptions<TFn>> = Partial<AsyncRateLimiterOptions<TFn>>>(options: TOptions): TOptions;
117
116
  /**
118
117
  * A class that creates an async rate-limited function.
119
118
  *
@@ -188,7 +187,7 @@ declare function asyncRateLimiterOptions<TFn extends AnyAsyncFunction = AnyAsync
188
187
  * const data = await rateLimiter.maybeExecute('123');
189
188
  * ```
190
189
  */
191
- declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
190
+ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
192
191
  #private;
193
192
  fn: TFn;
194
193
  readonly store: Store<Readonly<AsyncRateLimiterState<TFn>>>;
@@ -225,7 +224,7 @@ declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
225
224
  * const result2 = await rateLimiter.maybeExecute('arg1', 'arg2'); // undefined
226
225
  * ```
227
226
  */
228
- maybeExecute: (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;
227
+ maybeExecute: (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
229
228
  /**
230
229
  * Returns the number of remaining executions allowed in the current window
231
230
  */
@@ -351,7 +350,5 @@ declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
351
350
  * const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds
352
351
  * ```
353
352
  */
354
- declare function asyncRateLimit<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncRateLimiterOptions<TFn>): (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;
355
- //#endregion
356
- export { AsyncRateLimiter, AsyncRateLimiterOptions, AsyncRateLimiterState, asyncRateLimit, asyncRateLimiterOptions };
357
- //# sourceMappingURL=async-rate-limiter.d.ts.map
353
+ export declare function asyncRateLimit<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncRateLimiterOptions<TFn>): (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
354
+ //#endregion
@@ -1,6 +1,6 @@
1
1
  import { parseFunctionOrValue } from "./utils.js";
2
- import { emitChange, pacerEventClient } from "./event-client.js";
3
2
  import { AsyncRetryer } from "./async-retryer.js";
3
+ import { emitChange, pacerEventClient } from "./event-client.js";
4
4
  import { Store } from "@tanstack/store";
5
5
 
6
6
  //#region src/async-rate-limiter.ts
@@ -107,142 +107,14 @@ const defaultOptions = {
107
107
  * ```
108
108
  */
109
109
  var AsyncRateLimiter = class {
110
- #timeoutIds;
110
+ fn;
111
+ store = new Store(getDefaultAsyncRateLimiterState());
112
+ key;
113
+ options;
114
+ asyncRetryers = /* @__PURE__ */ new Map();
115
+ #timeoutIds = /* @__PURE__ */ new Set();
111
116
  constructor(fn, initialOptions) {
112
117
  this.fn = fn;
113
- this.store = new Store(getDefaultAsyncRateLimiterState());
114
- this.asyncRetryers = /* @__PURE__ */ new Map();
115
- this.#timeoutIds = /* @__PURE__ */ new Set();
116
- this.setOptions = (newOptions) => {
117
- this.options = {
118
- ...this.options,
119
- ...newOptions
120
- };
121
- };
122
- this.#setState = (newState) => {
123
- this.store.setState((state) => {
124
- const combinedState = {
125
- ...state,
126
- ...newState
127
- };
128
- const isExceeded = combinedState.executionTimes.length >= this.#getLimit();
129
- const status = !this.#getEnabled() ? "disabled" : combinedState.isExecuting ? "executing" : isExceeded ? "exceeded" : "idle";
130
- return {
131
- ...combinedState,
132
- isExceeded,
133
- status
134
- };
135
- });
136
- emitChange("AsyncRateLimiter", this);
137
- };
138
- this.#getEnabled = () => {
139
- return !!parseFunctionOrValue(this.options.enabled, this);
140
- };
141
- this.#getLimit = () => {
142
- return parseFunctionOrValue(this.options.limit, this);
143
- };
144
- this.#getWindow = () => {
145
- return parseFunctionOrValue(this.options.window, this);
146
- };
147
- this.maybeExecute = async (...args) => {
148
- this.#setState({ maybeExecuteCount: this.store.state.maybeExecuteCount + 1 });
149
- this.#cleanupOldExecutions();
150
- if (this.#getExecutionTimesInWindow().length < this.#getLimit()) {
151
- await this.#execute(...args);
152
- return this.store.state.lastResult;
153
- }
154
- this.#setState({ rejectionCount: this.store.state.rejectionCount + 1 });
155
- this.options.onReject?.(args, this);
156
- };
157
- this.#execute = async (...args) => {
158
- if (!this.#getEnabled()) return;
159
- const currentMaybeExecute = this.store.state.maybeExecuteCount;
160
- const now = Date.now();
161
- const executionTimes = [...this.store.state.executionTimes, now];
162
- this.#setState({
163
- isExecuting: true,
164
- executionTimes
165
- });
166
- try {
167
- const currentAsyncRetryer = new AsyncRetryer(this.fn, {
168
- ...this.options.asyncRetryerOptions,
169
- key: `${this.key}-retryer-${currentMaybeExecute}`
170
- });
171
- this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer);
172
- const result = await currentAsyncRetryer.execute(...args);
173
- this.#setCleanupTimeout(now);
174
- this.#setState({
175
- successCount: this.store.state.successCount + 1,
176
- lastResult: result
177
- });
178
- this.options.onSuccess?.(result, args, this);
179
- } catch (error) {
180
- this.#setState({ errorCount: this.store.state.errorCount + 1 });
181
- this.options.onError?.(error, args, this);
182
- if (this.options.throwOnError) throw error;
183
- } finally {
184
- this.asyncRetryers.delete(currentMaybeExecute);
185
- this.#setState({
186
- isExecuting: false,
187
- settleCount: this.store.state.settleCount + 1
188
- });
189
- this.options.onSettled?.(args, this);
190
- }
191
- return this.store.state.lastResult;
192
- };
193
- this.#getExecutionTimesInWindow = () => {
194
- if (this.options.windowType === "sliding") return this.store.state.executionTimes.filter((time) => time > Date.now() - this.#getWindow());
195
- else {
196
- if (this.store.state.executionTimes.length === 0) return [];
197
- const windowStart = Math.min(...this.store.state.executionTimes);
198
- const windowEnd = windowStart + this.#getWindow();
199
- if (Date.now() > windowEnd) return [];
200
- return this.store.state.executionTimes.filter((time) => time >= windowStart && time <= windowEnd);
201
- }
202
- };
203
- this.#setCleanupTimeout = (executionTime) => {
204
- if (this.options.windowType === "sliding" || this.#timeoutIds.size === 0) {
205
- const timeUntilExpiration = executionTime - Date.now() + this.#getWindow() + 1;
206
- const timeoutId = setTimeout(() => {
207
- this.#cleanupOldExecutions();
208
- this.#clearTimeout(timeoutId);
209
- }, timeUntilExpiration);
210
- this.#timeoutIds.add(timeoutId);
211
- }
212
- };
213
- this.#clearTimeout = (timeoutId) => {
214
- clearTimeout(timeoutId);
215
- this.#timeoutIds.delete(timeoutId);
216
- };
217
- this.#clearTimeouts = () => {
218
- this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId));
219
- this.#timeoutIds.clear();
220
- };
221
- this.#cleanupOldExecutions = () => {
222
- this.#setState({ executionTimes: this.#getExecutionTimesInWindow() });
223
- };
224
- this.getRemainingInWindow = () => {
225
- const relevantExecutionTimes = this.#getExecutionTimesInWindow();
226
- return Math.max(0, this.#getLimit() - relevantExecutionTimes.length);
227
- };
228
- this.getMsUntilNextWindow = () => {
229
- if (this.getRemainingInWindow() > 0) return 0;
230
- return (this.store.state.executionTimes[0] ?? Infinity) + this.#getWindow() - Date.now();
231
- };
232
- this.getAbortSignal = (maybeExecuteCount) => {
233
- const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount;
234
- return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
235
- };
236
- this.abort = () => {
237
- this.asyncRetryers.forEach((retryer) => retryer.abort());
238
- this.asyncRetryers.clear();
239
- this.#setState({ isExecuting: false });
240
- };
241
- this.reset = () => {
242
- this.#setState(getDefaultAsyncRateLimiterState());
243
- this.#clearTimeouts();
244
- this.asyncRetryers.forEach((retryer) => retryer.reset());
245
- };
246
118
  this.key = initialOptions.key;
247
119
  this.options = {
248
120
  ...defaultOptions,
@@ -257,25 +129,205 @@ var AsyncRateLimiter = class {
257
129
  this.setOptions(event.payload.options);
258
130
  });
259
131
  }
260
- #setState;
132
+ /**
133
+ * Updates the async rate limiter options
134
+ */
135
+ setOptions = (newOptions) => {
136
+ this.options = {
137
+ ...this.options,
138
+ ...newOptions
139
+ };
140
+ };
141
+ #setState = (newState) => {
142
+ this.store.setState((state) => {
143
+ const combinedState = {
144
+ ...state,
145
+ ...newState
146
+ };
147
+ const isExceeded = combinedState.executionTimes.length >= this.#getLimit();
148
+ const status = !this.#getEnabled() ? "disabled" : combinedState.isExecuting ? "executing" : isExceeded ? "exceeded" : "idle";
149
+ return {
150
+ ...combinedState,
151
+ isExceeded,
152
+ status
153
+ };
154
+ });
155
+ emitChange("AsyncRateLimiter", this);
156
+ };
261
157
  /**
262
158
  * Returns the current enabled state of the async rate limiter
263
159
  */
264
- #getEnabled;
160
+ #getEnabled = () => {
161
+ return !!parseFunctionOrValue(this.options.enabled, this);
162
+ };
265
163
  /**
266
164
  * Returns the current limit of executions allowed within the time window
267
165
  */
268
- #getLimit;
166
+ #getLimit = () => {
167
+ return parseFunctionOrValue(this.options.limit, this);
168
+ };
269
169
  /**
270
170
  * Returns the current time window in milliseconds
271
171
  */
272
- #getWindow;
273
- #execute;
274
- #getExecutionTimesInWindow;
275
- #setCleanupTimeout;
276
- #clearTimeout;
277
- #clearTimeouts;
278
- #cleanupOldExecutions;
172
+ #getWindow = () => {
173
+ return parseFunctionOrValue(this.options.window, this);
174
+ };
175
+ /**
176
+ * Attempts to execute the rate-limited function if within the configured limits.
177
+ * Will reject execution if the number of calls in the current window exceeds the limit.
178
+ *
179
+ * Error Handling:
180
+ * - If the rate-limited function throws and no `onError` handler is configured,
181
+ * the error will be thrown from this method.
182
+ * - If an `onError` handler is configured, errors will be caught and passed to the handler,
183
+ * and this method will return undefined.
184
+ * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
185
+ *
186
+ * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
187
+ * @throws The error from the rate-limited function if no onError handler is configured
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 });
192
+ *
193
+ * // First 5 calls will return a promise that resolves with the result
194
+ * const result = await rateLimiter.maybeExecute('arg1', 'arg2');
195
+ *
196
+ * // Additional calls within the window will return undefined
197
+ * const result2 = await rateLimiter.maybeExecute('arg1', 'arg2'); // undefined
198
+ * ```
199
+ */
200
+ maybeExecute = async (...args) => {
201
+ this.#setState({ maybeExecuteCount: this.store.state.maybeExecuteCount + 1 });
202
+ this.#cleanupOldExecutions();
203
+ if (this.#getExecutionTimesInWindow().length < this.#getLimit()) {
204
+ await this.#execute(...args);
205
+ return this.store.state.lastResult;
206
+ }
207
+ this.#setState({ rejectionCount: this.store.state.rejectionCount + 1 });
208
+ this.options.onReject?.(args, this);
209
+ };
210
+ #execute = async (...args) => {
211
+ if (!this.#getEnabled()) return;
212
+ const currentMaybeExecute = this.store.state.maybeExecuteCount;
213
+ const now = Date.now();
214
+ const executionTimes = [...this.store.state.executionTimes, now];
215
+ this.#setState({
216
+ isExecuting: true,
217
+ executionTimes
218
+ });
219
+ try {
220
+ const currentAsyncRetryer = new AsyncRetryer(this.fn, this.options.asyncRetryerOptions);
221
+ this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer);
222
+ const result = await currentAsyncRetryer.execute(...args);
223
+ this.#setCleanupTimeout(now);
224
+ this.#setState({
225
+ successCount: this.store.state.successCount + 1,
226
+ lastResult: result
227
+ });
228
+ this.options.onSuccess?.(result, args, this);
229
+ } catch (error) {
230
+ this.#setState({ errorCount: this.store.state.errorCount + 1 });
231
+ this.options.onError?.(error, args, this);
232
+ if (this.options.throwOnError) throw error;
233
+ } finally {
234
+ this.asyncRetryers.delete(currentMaybeExecute);
235
+ this.#setState({
236
+ isExecuting: this.asyncRetryers.size > 0,
237
+ settleCount: this.store.state.settleCount + 1
238
+ });
239
+ this.options.onSettled?.(args, this);
240
+ }
241
+ return this.store.state.lastResult;
242
+ };
243
+ #getExecutionTimesInWindow = () => {
244
+ if (this.options.windowType === "sliding") return this.store.state.executionTimes.filter((time) => time > Date.now() - this.#getWindow());
245
+ else {
246
+ if (this.store.state.executionTimes.length === 0) return [];
247
+ const windowStart = Math.min(...this.store.state.executionTimes);
248
+ const windowEnd = windowStart + this.#getWindow();
249
+ if (Date.now() > windowEnd) return [];
250
+ return this.store.state.executionTimes.filter((time) => time >= windowStart && time <= windowEnd);
251
+ }
252
+ };
253
+ #setCleanupTimeout = (executionTime) => {
254
+ if (this.options.windowType === "sliding" || this.#timeoutIds.size === 0) {
255
+ const timeUntilExpiration = executionTime - Date.now() + this.#getWindow() + 1;
256
+ const timeoutId = setTimeout(() => {
257
+ this.#cleanupOldExecutions();
258
+ this.#clearTimeout(timeoutId);
259
+ }, timeUntilExpiration);
260
+ this.#timeoutIds.add(timeoutId);
261
+ }
262
+ };
263
+ #clearTimeout = (timeoutId) => {
264
+ clearTimeout(timeoutId);
265
+ this.#timeoutIds.delete(timeoutId);
266
+ };
267
+ #clearTimeouts = () => {
268
+ this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId));
269
+ this.#timeoutIds.clear();
270
+ };
271
+ #cleanupOldExecutions = () => {
272
+ this.#setState({ executionTimes: this.#getExecutionTimesInWindow() });
273
+ };
274
+ /**
275
+ * Returns the number of remaining executions allowed in the current window
276
+ */
277
+ getRemainingInWindow = () => {
278
+ const relevantExecutionTimes = this.#getExecutionTimesInWindow();
279
+ return Math.max(0, this.#getLimit() - relevantExecutionTimes.length);
280
+ };
281
+ /**
282
+ * Returns the number of milliseconds until the next execution will be possible
283
+ * For fixed windows, this is the time until the current window resets
284
+ * For sliding windows, this is the time until the oldest execution expires
285
+ */
286
+ getMsUntilNextWindow = () => {
287
+ if (this.getRemainingInWindow() > 0) return 0;
288
+ return (this.store.state.executionTimes[0] ?? Infinity) + this.#getWindow() - Date.now();
289
+ };
290
+ /**
291
+ * Returns the AbortSignal for a specific execution.
292
+ * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
293
+ * Returns null if no execution is found or not currently executing.
294
+ *
295
+ * @param maybeExecuteCount - Optional specific execution to get signal for
296
+ * @example
297
+ * ```typescript
298
+ * const rateLimiter = new AsyncRateLimiter(
299
+ * async (userId: string) => {
300
+ * const signal = rateLimiter.getAbortSignal()
301
+ * if (signal) {
302
+ * const response = await fetch(`/api/users/${userId}`, { signal })
303
+ * return response.json()
304
+ * }
305
+ * },
306
+ * { limit: 5, window: 1000 }
307
+ * )
308
+ * ```
309
+ */
310
+ getAbortSignal = (maybeExecuteCount) => {
311
+ const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount;
312
+ return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
313
+ };
314
+ /**
315
+ * Aborts all ongoing executions with the internal abort controllers.
316
+ * Does NOT clear out the execution times or reset the rate limiter.
317
+ */
318
+ abort = () => {
319
+ this.asyncRetryers.forEach((retryer) => retryer.abort());
320
+ this.asyncRetryers.clear();
321
+ this.#setState({ isExecuting: false });
322
+ };
323
+ /**
324
+ * Resets the rate limiter state
325
+ */
326
+ reset = () => {
327
+ this.#setState(getDefaultAsyncRateLimiterState());
328
+ this.#clearTimeouts();
329
+ this.asyncRetryers.forEach((retryer) => retryer.reset());
330
+ };
279
331
  };
280
332
  /**
281
333
  * Creates an async rate-limited function that will execute the provided function up to a maximum number of times within a time window.
@@ -366,5 +418,4 @@ function asyncRateLimit(fn, initialOptions) {
366
418
  }
367
419
 
368
420
  //#endregion
369
- export { AsyncRateLimiter, asyncRateLimit, asyncRateLimiterOptions };
370
- //# sourceMappingURL=async-rate-limiter.js.map
421
+ export { AsyncRateLimiter, asyncRateLimit, asyncRateLimiterOptions };
@@ -1,8 +1,7 @@
1
1
  import { AnyAsyncFunction } from "./types.js";
2
2
  import { Store } from "@tanstack/store";
3
-
4
3
  //#region src/async-retryer.d.ts
5
- interface AsyncRetryerState<TFn extends AnyAsyncFunction> {
4
+ export interface AsyncRetryerState<TFn extends AnyAsyncFunction> {
6
5
  /**
7
6
  * The current retry attempt number (0 when not executing)
8
7
  */
@@ -36,7 +35,7 @@ interface AsyncRetryerState<TFn extends AnyAsyncFunction> {
36
35
  */
37
36
  totalExecutionTime: number;
38
37
  }
39
- interface AsyncRetryerOptions<TFn extends AnyAsyncFunction> {
38
+ export interface AsyncRetryerOptions<TFn extends AnyAsyncFunction> {
40
39
  /**
41
40
  * The backoff strategy for retry delays:
42
41
  * - 'exponential': Wait time doubles with each attempt (1s, 2s, 4s, ...)
@@ -66,7 +65,10 @@ interface AsyncRetryerOptions<TFn extends AnyAsyncFunction> {
66
65
  jitter?: number;
67
66
  /**
68
67
  * Optional key to identify this async retryer instance.
69
- * If provided, the async retryer will be identified by this key in the devtools and PacerProvider if applicable.
68
+ * Note: async retryers are not currently surfaced in the devtools, so this key
69
+ * is only a plain identifier. Retryer instances are often created per-execution
70
+ * (including internally by the other async utilities), so they intentionally do
71
+ * not register with the devtools event bus.
70
72
  */
71
73
  key?: string;
72
74
  /**
@@ -133,7 +135,7 @@ interface AsyncRetryerOptions<TFn extends AnyAsyncFunction> {
133
135
  /**
134
136
  * Utility function for sharing common `AsyncRetryerOptions` options between different `AsyncRetryer` instances.
135
137
  */
136
- declare function asyncRetryerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncRetryerOptions<TFn>> = Partial<AsyncRetryerOptions<TFn>>>(options: TOptions): TOptions;
138
+ export declare function asyncRetryerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncRetryerOptions<TFn>> = Partial<AsyncRetryerOptions<TFn>>>(options: TOptions): TOptions;
137
139
  declare const defaultOptions: Omit<Required<AsyncRetryerOptions<any>>, 'initialState' | 'key' | 'onAbort' | 'onError' | 'onLastError' | 'onRetry' | 'onSettled' | 'onSuccess' | 'onExecutionTimeout' | 'onTotalExecutionTimeout'>;
138
140
  /**
139
141
  * Provides robust retry functionality for asynchronous functions, supporting configurable backoff strategies,
@@ -229,7 +231,7 @@ declare const defaultOptions: Omit<Required<AsyncRetryerOptions<any>>, 'initialS
229
231
  *
230
232
  * @template TFn The async function type to be retried.
231
233
  */
232
- declare class AsyncRetryer<TFn extends AnyAsyncFunction> {
234
+ export declare class AsyncRetryer<TFn extends AnyAsyncFunction> {
233
235
  #private;
234
236
  fn: TFn;
235
237
  readonly store: Store<Readonly<AsyncRetryerState<TFn>>>;
@@ -313,7 +315,5 @@ declare class AsyncRetryer<TFn extends AnyAsyncFunction> {
313
315
  * const data2 = await fetchWithRetry('/api/data2')
314
316
  * ```
315
317
  */
316
- declare function asyncRetry<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions?: AsyncRetryerOptions<TFn>): (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
317
- //#endregion
318
- export { AsyncRetryer, AsyncRetryerOptions, AsyncRetryerState, asyncRetry, asyncRetryerOptions };
319
- //# sourceMappingURL=async-retryer.d.ts.map
318
+ export declare function asyncRetry<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions?: AsyncRetryerOptions<TFn>): (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
319
+ //#endregion