@tanstack/pacer 0.15.3 → 0.16.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 (92) hide show
  1. package/dist/cjs/async-batcher.cjs +66 -4
  2. package/dist/cjs/async-batcher.cjs.map +1 -1
  3. package/dist/cjs/async-batcher.d.cts +97 -16
  4. package/dist/cjs/async-debouncer.cjs +53 -18
  5. package/dist/cjs/async-debouncer.cjs.map +1 -1
  6. package/dist/cjs/async-debouncer.d.cts +79 -9
  7. package/dist/cjs/async-queuer.cjs +58 -1
  8. package/dist/cjs/async-queuer.cjs.map +1 -1
  9. package/dist/cjs/async-queuer.d.cts +103 -9
  10. package/dist/cjs/async-rate-limiter.cjs +51 -1
  11. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  12. package/dist/cjs/async-rate-limiter.d.cts +94 -27
  13. package/dist/cjs/async-retryer.cjs +286 -0
  14. package/dist/cjs/async-retryer.cjs.map +1 -0
  15. package/dist/cjs/async-retryer.d.cts +312 -0
  16. package/dist/cjs/async-throttler.cjs +101 -52
  17. package/dist/cjs/async-throttler.cjs.map +1 -1
  18. package/dist/cjs/async-throttler.d.cts +85 -9
  19. package/dist/cjs/batcher.cjs +5 -0
  20. package/dist/cjs/batcher.cjs.map +1 -1
  21. package/dist/cjs/batcher.d.cts +13 -1
  22. package/dist/cjs/debouncer.cjs +5 -0
  23. package/dist/cjs/debouncer.cjs.map +1 -1
  24. package/dist/cjs/debouncer.d.cts +10 -2
  25. package/dist/cjs/event-client.cjs.map +1 -1
  26. package/dist/cjs/event-client.d.cts +3 -0
  27. package/dist/cjs/index.cjs +13 -0
  28. package/dist/cjs/index.cjs.map +1 -1
  29. package/dist/cjs/index.d.cts +1 -0
  30. package/dist/cjs/queuer.cjs +5 -0
  31. package/dist/cjs/queuer.cjs.map +1 -1
  32. package/dist/cjs/queuer.d.cts +11 -3
  33. package/dist/cjs/rate-limiter.cjs +5 -0
  34. package/dist/cjs/rate-limiter.cjs.map +1 -1
  35. package/dist/cjs/rate-limiter.d.cts +11 -0
  36. package/dist/cjs/throttler.cjs +5 -0
  37. package/dist/cjs/throttler.cjs.map +1 -1
  38. package/dist/cjs/throttler.d.cts +11 -0
  39. package/dist/cjs/utils.cjs.map +1 -1
  40. package/dist/esm/async-batcher.d.ts +97 -16
  41. package/dist/esm/async-batcher.js +67 -5
  42. package/dist/esm/async-batcher.js.map +1 -1
  43. package/dist/esm/async-debouncer.d.ts +79 -9
  44. package/dist/esm/async-debouncer.js +54 -19
  45. package/dist/esm/async-debouncer.js.map +1 -1
  46. package/dist/esm/async-queuer.d.ts +103 -9
  47. package/dist/esm/async-queuer.js +59 -2
  48. package/dist/esm/async-queuer.js.map +1 -1
  49. package/dist/esm/async-rate-limiter.d.ts +94 -27
  50. package/dist/esm/async-rate-limiter.js +52 -2
  51. package/dist/esm/async-rate-limiter.js.map +1 -1
  52. package/dist/esm/async-retryer.d.ts +312 -0
  53. package/dist/esm/async-retryer.js +286 -0
  54. package/dist/esm/async-retryer.js.map +1 -0
  55. package/dist/esm/async-throttler.d.ts +85 -9
  56. package/dist/esm/async-throttler.js +102 -53
  57. package/dist/esm/async-throttler.js.map +1 -1
  58. package/dist/esm/batcher.d.ts +13 -1
  59. package/dist/esm/batcher.js +5 -0
  60. package/dist/esm/batcher.js.map +1 -1
  61. package/dist/esm/debouncer.d.ts +10 -2
  62. package/dist/esm/debouncer.js +6 -1
  63. package/dist/esm/debouncer.js.map +1 -1
  64. package/dist/esm/event-client.d.ts +3 -0
  65. package/dist/esm/event-client.js.map +1 -1
  66. package/dist/esm/index.d.ts +1 -0
  67. package/dist/esm/index.js +23 -10
  68. package/dist/esm/index.js.map +1 -1
  69. package/dist/esm/queuer.d.ts +11 -3
  70. package/dist/esm/queuer.js +6 -1
  71. package/dist/esm/queuer.js.map +1 -1
  72. package/dist/esm/rate-limiter.d.ts +11 -0
  73. package/dist/esm/rate-limiter.js +6 -1
  74. package/dist/esm/rate-limiter.js.map +1 -1
  75. package/dist/esm/throttler.d.ts +11 -0
  76. package/dist/esm/throttler.js +6 -1
  77. package/dist/esm/throttler.js.map +1 -1
  78. package/dist/esm/utils.js.map +1 -1
  79. package/package.json +13 -3
  80. package/src/async-batcher.ts +146 -19
  81. package/src/async-debouncer.ts +120 -30
  82. package/src/async-queuer.ts +149 -11
  83. package/src/async-rate-limiter.ts +131 -30
  84. package/src/async-retryer.ts +669 -0
  85. package/src/async-throttler.ts +198 -72
  86. package/src/batcher.ts +18 -1
  87. package/src/debouncer.ts +19 -2
  88. package/src/event-client.ts +3 -0
  89. package/src/index.ts +2 -0
  90. package/src/queuer.ts +21 -3
  91. package/src/rate-limiter.ts +20 -0
  92. package/src/throttler.ts +20 -0
@@ -1,4 +1,5 @@
1
1
  import { Store } from "@tanstack/store";
2
+ import { AsyncRetryer } from "./async-retryer.js";
2
3
  import { parseFunctionOrValue, createKey } from "./utils.js";
3
4
  import { emitChange, pacerEventClient } from "./event-client.js";
4
5
  function getDefaultAsyncQueuerState() {
@@ -6,8 +7,10 @@ function getDefaultAsyncQueuerState() {
6
7
  activeItems: [],
7
8
  addItemCount: 0,
8
9
  errorCount: 0,
10
+ executeCount: 0,
9
11
  expirationCount: 0,
10
12
  isEmpty: true,
13
+ isExecuting: false,
11
14
  isFull: false,
12
15
  isIdle: true,
13
16
  isRunning: true,
@@ -22,8 +25,14 @@ function getDefaultAsyncQueuerState() {
22
25
  successCount: 0
23
26
  };
24
27
  }
28
+ function asyncQueuerOptions(options) {
29
+ return options;
30
+ }
25
31
  const defaultOptions = {
26
32
  addItemsTo: "back",
33
+ asyncRetryerOptions: {
34
+ maxAttempts: 1
35
+ },
27
36
  concurrency: 1,
28
37
  expirationDuration: Infinity,
29
38
  getIsExpired: () => false,
@@ -38,7 +47,9 @@ class AsyncQueuer {
38
47
  constructor(fn, initialOptions = {}) {
39
48
  this.fn = fn;
40
49
  this.store = new Store(getDefaultAsyncQueuerState());
50
+ this.asyncRetryers = /* @__PURE__ */ new Map();
41
51
  this.#timeoutIds = /* @__PURE__ */ new Set();
52
+ this._emit = () => emitChange("AsyncQueuer", this);
42
53
  this.setOptions = (newOptions) => {
43
54
  this.options = { ...this.options, ...newOptions };
44
55
  };
@@ -182,8 +193,18 @@ class AsyncQueuer {
182
193
  this.execute = async (position) => {
183
194
  const item = this.getNextItem(position);
184
195
  if (item !== void 0) {
196
+ const currentExecuteCount = this.store.state.executeCount + 1;
197
+ this.#setState({
198
+ executeCount: currentExecuteCount,
199
+ isExecuting: true
200
+ });
185
201
  try {
186
- const lastResult = await this.fn(item);
202
+ const currentAsyncRetryer = new AsyncRetryer(this.fn, {
203
+ ...this.options.asyncRetryerOptions,
204
+ key: `${this.key}-retryer-${currentExecuteCount}`
205
+ });
206
+ this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer);
207
+ const lastResult = await currentAsyncRetryer.execute(item);
187
208
  this.#setState({
188
209
  successCount: this.store.state.successCount + 1,
189
210
  lastResult
@@ -198,10 +219,12 @@ class AsyncQueuer {
198
219
  throw error;
199
220
  }
200
221
  } finally {
222
+ this.asyncRetryers.delete(currentExecuteCount);
201
223
  this.#setState({
202
224
  activeItems: this.store.state.activeItems.filter(
203
225
  (activeItem) => activeItem !== item
204
226
  ),
227
+ isExecuting: false,
205
228
  settledCount: this.store.state.settledCount + 1
206
229
  });
207
230
  this.options.onSettled?.(item, this);
@@ -289,9 +312,17 @@ class AsyncQueuer {
289
312
  this.#setState({ items: [], itemTimestamps: [] });
290
313
  this.options.onItemsChange?.(this);
291
314
  };
315
+ this.abort = () => {
316
+ this.asyncRetryers.forEach((retryer) => retryer.abort());
317
+ this.asyncRetryers.clear();
318
+ this.#setState({
319
+ isExecuting: false
320
+ });
321
+ };
292
322
  this.reset = () => {
293
323
  this.#setState(getDefaultAsyncQueuerState());
294
324
  this.options.onItemsChange?.(this);
325
+ this.asyncRetryers.forEach((retryer) => retryer.reset());
295
326
  };
296
327
  this.key = createKey(initialOptions.key);
297
328
  this.options = {
@@ -329,6 +360,31 @@ class AsyncQueuer {
329
360
  #getAllItems;
330
361
  #checkExpiredItems;
331
362
  #clearTimeouts;
363
+ /**
364
+ * Returns the AbortSignal for a specific execution.
365
+ * If no executeCount is provided, returns the signal for the most recent execution.
366
+ * Returns null if no execution is found or not currently executing.
367
+ *
368
+ * @param executeCount - Optional specific execution to get signal for
369
+ * @example
370
+ * ```typescript
371
+ * const queuer = new AsyncQueuer(
372
+ * async (item: string) => {
373
+ * const signal = queuer.getAbortSignal()
374
+ * if (signal) {
375
+ * const response = await fetch(`/api/process/${item}`, { signal })
376
+ * return response.json()
377
+ * }
378
+ * },
379
+ * { concurrency: 2 }
380
+ * )
381
+ * ```
382
+ */
383
+ getAbortSignal(executeCount) {
384
+ const count = executeCount ?? this.store.state.executeCount;
385
+ const retryer = this.asyncRetryers.get(count);
386
+ return retryer?.getAbortSignal() ?? null;
387
+ }
332
388
  }
333
389
  function asyncQueue(fn, initialOptions) {
334
390
  const asyncQueuer = new AsyncQueuer(fn, initialOptions);
@@ -336,6 +392,7 @@ function asyncQueue(fn, initialOptions) {
336
392
  }
337
393
  export {
338
394
  AsyncQueuer,
339
- asyncQueue
395
+ asyncQueue,
396
+ asyncQueuerOptions
340
397
  };
341
398
  //# sourceMappingURL=async-queuer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"async-queuer.js","sources":["../../src/async-queuer.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { createKey, parseFunctionOrValue } from './utils'\nimport { emitChange, pacerEventClient } from './event-client'\nimport type { OptionalKeys } from './types'\nimport type { QueuePosition } from './queuer'\n\nexport interface AsyncQueuerState<TValue> {\n /**\n * Items currently being processed by the queuer\n */\n activeItems: Array<TValue>\n /**\n * Number of times addItem has been called (for reduction calculations)\n */\n addItemCount: number\n /**\n * Number of task executions that have resulted in errors\n */\n errorCount: number\n /**\n * Number of items that have been removed from the queue due to expiration\n */\n expirationCount: number\n /**\n * Whether the queuer has no items to process (items array is empty)\n */\n isEmpty: boolean\n /**\n * Whether the queuer has reached its maximum capacity\n */\n isFull: boolean\n /**\n * Whether the queuer is not currently processing any items\n */\n isIdle: boolean\n /**\n * Whether the queuer is active and will process items automatically\n */\n isRunning: boolean\n /**\n * Array of items currently waiting to be processed\n */\n items: Array<TValue>\n /**\n * Timestamps when items were added to the queue for expiration tracking\n */\n itemTimestamps: Array<number>\n /**\n * The result from the most recent task execution\n */\n lastResult: any\n /**\n * Whether the queuer has a pending timeout for processing the next item\n */\n pendingTick: boolean\n /**\n * Number of items that have been rejected from being added to the queue\n */\n rejectionCount: number\n /**\n * Number of task executions that have completed (either successfully or with errors)\n */\n settledCount: number\n /**\n * Number of items currently in the queue\n */\n size: number\n /**\n * Current processing status - 'idle' when not processing, 'running' when active, 'stopped' when paused\n */\n status: 'idle' | 'running' | 'stopped'\n /**\n * Number of task executions that have completed successfully\n */\n successCount: number\n}\n\nfunction getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {\n return {\n activeItems: [],\n addItemCount: 0,\n errorCount: 0,\n expirationCount: 0,\n isEmpty: true,\n isFull: false,\n isIdle: true,\n isRunning: true,\n itemTimestamps: [],\n items: [],\n lastResult: null,\n pendingTick: false,\n rejectionCount: 0,\n settledCount: 0,\n size: 0,\n status: 'idle',\n successCount: 0,\n }\n}\n\nexport interface AsyncQueuerOptions<TValue> {\n /**\n * Default position to add items to the queuer\n * @default 'back'\n */\n addItemsTo?: QueuePosition\n /**\n * Maximum number of concurrent tasks to process.\n * Can be a number or a function that returns a number.\n * @default 1\n */\n concurrency?: number | ((queuer: AsyncQueuer<TValue>) => number)\n /**\n * Maximum time in milliseconds that an item can stay in the queue\n * If not provided, items will never expire\n */\n expirationDuration?: number\n /**\n * Function to determine if an item has expired\n * If provided, this overrides the expirationDuration behavior\n */\n getIsExpired?: (item: TValue, addedAt: number) => boolean\n /**\n * Default position to get items from during processing\n * @default 'front'\n */\n getItemsFrom?: QueuePosition\n /**\n * Function to determine priority of items in the queuer\n * Higher priority items will be processed first\n * If not provided, will use static priority values attached to tasks\n */\n getPriority?: (item: TValue) => number\n /**\n * Initial items to populate the queuer with\n */\n initialItems?: Array<TValue>\n /**\n * Initial state for the async queuer\n */\n initialState?: Partial<AsyncQueuerState<TValue>>\n /**\n * Optional key to identify this async queuer instance.\n * If provided, the async queuer will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Maximum number of items allowed in the queuer\n */\n maxSize?: number\n /**\n * Optional error handler for when a task throws.\n * If provided, the handler will be called with the error and queuer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (error: unknown, item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item expires in the queuer\n */\n onExpire?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item is added or removed from the queuer\n */\n onItemsChange?: (queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item is rejected from being added to the queuer\n */\n onReject?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Optional callback to call when a task is settled\n */\n onSettled?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Optional callback to call when a task succeeds\n */\n onSuccess?: (result: any, item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Whether the queuer should start processing tasks immediately or not.\n */\n started?: boolean\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 * Time in milliseconds to wait between processing items.\n * Can be a number or a function that returns a number.\n * @default 0\n */\n wait?: number | ((queuer: AsyncQueuer<TValue>) => number)\n}\n\ntype AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys<\n Required<AsyncQueuerOptions<any>>,\n | 'initialState'\n | 'throwOnError'\n | 'onSuccess'\n | 'onSettled'\n | 'onReject'\n | 'onItemsChange'\n | 'onExpire'\n | 'onError'\n | 'key'\n>\n\nconst defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = {\n addItemsTo: 'back',\n concurrency: 1,\n expirationDuration: Infinity,\n getIsExpired: () => false,\n getItemsFrom: 'front',\n getPriority: (item: any) => item?.priority ?? 0,\n initialItems: [],\n maxSize: Infinity,\n started: true,\n wait: 0,\n}\n\n/**\n * A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration.\n *\n * Features:\n * - Priority queue support via the getPriority option\n * - Configurable concurrency limit\n * - Callbacks for task success, error, completion, and queue state changes\n * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior\n * - Pause and resume processing\n * - Task cancellation\n * - Item expiration to remove stale items from the queue\n *\n * Tasks are processed concurrently up to the configured concurrency limit. When a task completes,\n * the next pending task is processed if the concurrency limit allows.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and queuer 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 AsyncQueuer 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 queuer\n * - Use `onSuccess` callback to react to successful task execution and implement custom logic\n * - Use `onError` callback to react to task execution errors and implement custom error handling\n * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the queue\n * - Use `onExpire` callback to react to items expiring and implement custom logic\n * - Use `onReject` callback to react to items being rejected when the queue is full\n * - The state includes error count, expiration count, rejection count, running status, and success/settle counts\n * - State can be accessed via `asyncQueuer.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncQueuer.state`\n *\n * Example usage:\n * ```ts\n * const asyncQueuer = new AsyncQueuer<string>(async (item) => {\n * // process item\n * return item.toUpperCase();\n * }, {\n * concurrency: 2,\n * onSuccess: (result) => {\n * console.log(result);\n * }\n * });\n *\n * asyncQueuer.addItem('hello');\n * asyncQueuer.start();\n * ```\n */\nexport class AsyncQueuer<TValue> {\n readonly store: Store<Readonly<AsyncQueuerState<TValue>>> = new Store<\n AsyncQueuerState<TValue>\n >(getDefaultAsyncQueuerState<TValue>())\n key: string\n options: AsyncQueuerOptions<TValue>\n #timeoutIds: Set<NodeJS.Timeout> = new Set()\n\n constructor(\n public fn: (item: TValue) => Promise<any>,\n initialOptions: AsyncQueuerOptions<TValue> = {},\n ) {\n this.key = createKey(initialOptions.key)\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n const isInitiallyRunning =\n this.options.initialState?.isRunning ?? this.options.started ?? true\n this.#setState({\n ...this.options.initialState,\n isRunning: isInitiallyRunning,\n })\n\n if (this.options.initialState?.items) {\n if (this.store.state.isRunning) {\n this.#tick()\n }\n } else {\n for (let i = 0; i < (this.options.initialItems?.length ?? 0); i++) {\n const item = this.options.initialItems![i]!\n const isLast = i === (this.options.initialItems?.length ?? 0) - 1\n this.addItem(item, this.options.addItemsTo ?? 'back', isLast)\n }\n }\n\n pacerEventClient.on('d-AsyncQueuer', (e) => {\n if (e.payload.key !== this.key) return\n this.#setState(e.payload.store.state)\n this.setOptions(e.payload.options)\n })\n }\n\n /**\n * Updates the queuer options. New options are merged with existing options.\n */\n setOptions = (newOptions: Partial<AsyncQueuerOptions<TValue>>): void => {\n this.options = { ...this.options, ...newOptions }\n }\n\n #setState = (newState: Partial<AsyncQueuerState<TValue>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n\n const { activeItems, items, isRunning } = combinedState\n\n const size = items.length\n const isFull = size >= (this.options.maxSize ?? Infinity)\n const isEmpty = size === 0\n const isIdle = isRunning && isEmpty && activeItems.length === 0\n\n const status = isIdle ? 'idle' : isRunning ? 'running' : 'stopped'\n\n return {\n ...combinedState,\n isEmpty,\n isFull,\n isIdle,\n size,\n status,\n }\n })\n emitChange('AsyncQueuer', this)\n }\n\n /**\n * Returns the current wait time (in milliseconds) between processing items.\n * If a function is provided, it is called with the queuer instance.\n */\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait ?? 0, this)\n }\n\n /**\n * Returns the current concurrency limit for processing items.\n * If a function is provided, it is called with the queuer instance.\n */\n #getConcurrency = (): number => {\n return parseFunctionOrValue(this.options.concurrency ?? 1, this)\n }\n\n /**\n * Processes items in the queue up to the concurrency limit. Internal use only.\n */\n #tick = () => {\n if (!this.store.state.isRunning) {\n this.#setState({ pendingTick: false })\n return\n }\n this.#setState({ pendingTick: true })\n\n // Check for expired items\n this.#checkExpiredItems()\n\n // Process items concurrently up to the concurrency limit\n const activeItems = this.store.state.activeItems\n while (\n activeItems.length < this.#getConcurrency() &&\n this.store.state.items.length > 0\n ) {\n const nextItem = this.peekNextItem()\n if (!nextItem) {\n break\n }\n activeItems.push(nextItem)\n this.#setState({\n activeItems,\n })\n ;(async () => {\n const result = await this.execute()\n this.#setState({ lastResult: result })\n\n const wait = this.#getWait()\n if (wait > 0) {\n const timeoutId = setTimeout(() => this.#tick(), wait)\n this.#timeoutIds.add(timeoutId)\n return\n }\n\n this.#tick()\n })()\n }\n\n this.#setState({ pendingTick: false })\n }\n\n /**\n * Adds an item to the queue. If the queue is full, the item is rejected and onReject is called.\n * Items can be inserted based on priority or at the front/back depending on configuration.\n *\n * @example\n * ```ts\n * queuer.addItem({ value: 'task', priority: 10 });\n * queuer.addItem('task2', 'front');\n * ```\n */\n addItem = (\n item: TValue,\n position: QueuePosition = this.options.addItemsTo ?? 'back',\n runOnItemsChange: boolean = true,\n ): boolean => {\n this.#setState({\n addItemCount: this.store.state.addItemCount + 1,\n })\n\n if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {\n this.#setState({\n rejectionCount: this.store.state.rejectionCount + 1,\n })\n this.options.onReject?.(item, this)\n return false\n }\n\n // Get priority either from the function or from getPriority option\n const priority =\n this.options.getPriority !== defaultOptions.getPriority\n ? this.options.getPriority!(item)\n : (item as any).priority\n\n const items = this.store.state.items\n const itemTimestamps = this.store.state.itemTimestamps\n\n if (priority !== undefined) {\n // Insert based on priority - higher priority items go to front\n const insertIndex = items.findIndex((existing) => {\n const existingPriority =\n this.options.getPriority !== defaultOptions.getPriority\n ? this.options.getPriority!(existing)\n : (existing as any).priority\n return existingPriority < priority\n })\n\n if (insertIndex === -1) {\n items.push(item)\n itemTimestamps.push(Date.now())\n } else {\n items.splice(insertIndex, 0, item)\n itemTimestamps.splice(insertIndex, 0, Date.now())\n }\n } else {\n if (position === 'front') {\n // Default FIFO/LIFO behavior\n items.unshift(item)\n itemTimestamps.unshift(Date.now())\n } else {\n // LIFO\n items.push(item)\n itemTimestamps.push(Date.now())\n }\n }\n\n this.#setState({\n items,\n itemTimestamps,\n })\n\n if (runOnItemsChange) {\n this.options.onItemsChange?.(this)\n }\n\n if (this.store.state.isRunning && !this.store.state.pendingTick) {\n this.#tick()\n }\n\n return true\n }\n\n /**\n * Removes and returns the next item from the queue without executing the task function.\n * Use for manual queue management. Normally, use execute() to process items.\n *\n * @example\n * ```ts\n * // FIFO\n * queuer.getNextItem();\n * // LIFO\n * queuer.getNextItem('back');\n * ```\n */\n getNextItem = (\n position: QueuePosition = this.options.getItemsFrom ?? 'front',\n ): TValue | undefined => {\n const { items, itemTimestamps } = this.store.state\n let item: TValue | undefined\n\n // When priority function is provided or position is 'front', always get from front (highest priority)\n // Priority takes precedence over FIFO/LIFO behavior\n if (\n this.options.getPriority !== defaultOptions.getPriority ||\n position === 'front'\n ) {\n item = items[0]\n if (item !== undefined) {\n this.#setState({\n items: items.slice(1),\n itemTimestamps: itemTimestamps.slice(1),\n })\n }\n } else {\n item = items[items.length - 1]\n if (item !== undefined) {\n this.#setState({\n items: items.slice(0, -1),\n itemTimestamps: itemTimestamps.slice(0, -1),\n })\n }\n }\n\n if (item !== undefined) {\n this.options.onItemsChange?.(this)\n }\n\n return item\n }\n\n #getAllItems = (): Array<TValue> => {\n const items = this.peekAllItems()\n this.clear()\n return items\n }\n\n /**\n * Removes and returns the next item from the queue and executes the task function with it.\n *\n * @example\n * ```ts\n * queuer.execute();\n * // LIFO\n * queuer.execute('back');\n * ```\n */\n execute = async (position?: QueuePosition): Promise<any> => {\n const item = this.getNextItem(position)\n if (item !== undefined) {\n try {\n const lastResult = await this.fn(item) // EXECUTE!\n this.#setState({\n successCount: this.store.state.successCount + 1,\n lastResult,\n })\n this.options.onSuccess?.(lastResult, item, this)\n } catch (error) {\n this.#setState({\n errorCount: this.store.state.errorCount + 1,\n })\n this.options.onError?.(error, item, this)\n if (this.options.throwOnError) {\n throw error\n }\n } finally {\n this.#setState({\n activeItems: this.store.state.activeItems.filter(\n (activeItem) => activeItem !== item,\n ),\n settledCount: this.store.state.settledCount + 1,\n })\n this.options.onSettled?.(item, this)\n }\n }\n return item\n }\n\n /**\n * Processes a specified number of items to execute immediately with no wait time\n * If no numberOfItems is provided, all items will be processed\n */\n flush = async (\n numberOfItems: number = this.store.state.items.length,\n position?: QueuePosition,\n ): Promise<void> => {\n this.#clearTimeouts() // clear any pending timeouts\n await Promise.all(\n Array.from({ length: numberOfItems }, () => this.execute(position)),\n )\n }\n\n /**\n * Processes all items in the queue as a batch using the provided function as an argument\n * The queue is cleared after processing\n */\n flushAsBatch = async (\n batchFunction: (items: Array<TValue>) => Promise<any>,\n ): Promise<void> => {\n this.#clearTimeouts() // clear any pending timeouts\n const items = this.#getAllItems()\n await batchFunction(items)\n }\n\n /**\n * Checks for expired items in the queue and removes them. Calls onExpire for each expired item.\n * Internal use only.\n */\n #checkExpiredItems = (): void => {\n if (\n (this.options.expirationDuration ?? Infinity) === Infinity &&\n this.options.getIsExpired === defaultOptions.getIsExpired\n ) {\n return\n }\n\n const now = Date.now()\n const expiredIndices: Array<number> = []\n\n // Find indices of expired items\n for (let i = 0; i < this.store.state.items.length; i++) {\n const timestamp = this.store.state.itemTimestamps[i]\n if (timestamp === undefined) continue\n\n const item = this.store.state.items[i]\n if (item === undefined) continue\n\n const isExpired =\n this.options.getIsExpired !== defaultOptions.getIsExpired\n ? this.options.getIsExpired!(item, timestamp)\n : now - timestamp > (this.options.expirationDuration ?? Infinity)\n\n if (isExpired) {\n expiredIndices.push(i)\n }\n }\n\n // Remove expired items from back to front to maintain indices\n for (let i = expiredIndices.length - 1; i >= 0; i--) {\n const index = expiredIndices[i]\n if (index === undefined) continue\n\n const expiredItem = this.store.state.items[index]\n if (expiredItem === undefined) continue\n\n const newItems = [...this.store.state.items]\n const newTimestamps = [...this.store.state.itemTimestamps]\n newItems.splice(index, 1)\n newTimestamps.splice(index, 1)\n this.#setState({\n items: newItems,\n itemTimestamps: newTimestamps,\n expirationCount: this.store.state.expirationCount + 1,\n })\n this.options.onExpire?.(expiredItem, this)\n }\n\n if (expiredIndices.length > 0) {\n this.options.onItemsChange?.(this)\n }\n }\n\n /**\n * Returns the next item in the queue without removing it.\n *\n * @example\n * ```ts\n * queuer.peekNextItem(); // front\n * queuer.peekNextItem('back'); // back\n * ```\n */\n peekNextItem = (position: QueuePosition = 'front'): TValue | undefined => {\n if (position === 'front') {\n return this.store.state.items[0]\n }\n return this.store.state.items[this.store.state.items.length - 1]\n }\n\n /**\n * Returns a copy of all items in the queue, including active and pending items.\n */\n peekAllItems = (): Array<TValue> => {\n return [...this.peekActiveItems(), ...this.peekPendingItems()]\n }\n\n /**\n * Returns the items currently being processed (active tasks).\n */\n peekActiveItems = (): Array<TValue> => {\n return [...this.store.state.activeItems]\n }\n\n /**\n * Returns the items waiting to be processed (pending tasks).\n */\n peekPendingItems = (): Array<TValue> => {\n return [...this.store.state.items]\n }\n\n /**\n * Starts processing items in the queue. If already running, does nothing.\n */\n start = (): void => {\n this.#setState({ isRunning: true })\n if (!this.store.state.pendingTick && this.store.state.items.length > 0) {\n this.#tick()\n }\n }\n\n /**\n * Stops processing items in the queue. Does not clear the queue.\n */\n stop = (): void => {\n this.#clearTimeouts()\n this.#setState({ isRunning: false, pendingTick: false })\n }\n\n #clearTimeouts = (): void => {\n this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId))\n this.#timeoutIds.clear()\n }\n\n /**\n * Removes all pending items from the queue. Does not affect active tasks.\n */\n clear = (): void => {\n this.#setState({ items: [], itemTimestamps: [] })\n this.options.onItemsChange?.(this)\n }\n\n /**\n * Resets the queuer state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncQueuerState<TValue>())\n this.options.onItemsChange?.(this)\n }\n}\n\n/**\n * Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks.\n * The queuer is started automatically and ready to process items.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and queuer 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 AsyncQueuer 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 queuer\n * - Use `onSuccess` callback to react to successful task execution and implement custom logic\n * - Use `onError` callback to react to task execution errors and implement custom error handling\n * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the queue\n * - Use `onExpire` callback to react to items expiring and implement custom logic\n * - Use `onReject` callback to react to items being rejected when the queue is full\n * - The state includes error count, expiration count, rejection count, running status, and success/settle counts\n * - State can be accessed via the underlying AsyncQueuer instance's `store.state` property\n * - When using framework adapters (React/Solid), state is accessed from the hook's state property\n *\n * Example usage:\n * ```ts\n * const enqueue = asyncQueue<string>(async (item) => {\n * return item.toUpperCase();\n * }, {...options});\n *\n * enqueue('hello');\n * ```\n */\nexport function asyncQueue<TValue>(\n fn: (value: TValue) => Promise<any>,\n initialOptions: AsyncQueuerOptions<TValue>,\n) {\n const asyncQueuer = new AsyncQueuer<TValue>(fn, initialOptions)\n return asyncQueuer.addItem\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"async-queuer.js","sources":["../../src/async-queuer.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { AsyncRetryer } from './async-retryer'\nimport { createKey, parseFunctionOrValue } from './utils'\nimport { emitChange, pacerEventClient } from './event-client'\nimport type { AsyncRetryerOptions } from './async-retryer'\nimport type { OptionalKeys } from './types'\nimport type { QueuePosition } from './queuer'\n\nexport interface AsyncQueuerState<TValue> {\n /**\n * Items currently being processed by the queuer\n */\n activeItems: Array<TValue>\n /**\n * Number of times addItem has been called (for reduction calculations)\n */\n addItemCount: number\n /**\n * Number of task executions that have resulted in errors\n */\n errorCount: number\n /**\n * Number of times execute has been called\n */\n executeCount: number\n /**\n * Number of items that have been removed from the queue due to expiration\n */\n expirationCount: number\n /**\n * Whether the queuer has no items to process (items array is empty)\n */\n isEmpty: boolean\n /**\n * Whether the queuer is currently executing\n */\n isExecuting: boolean\n /**\n * Whether the queuer has reached its maximum capacity\n */\n isFull: boolean\n /**\n * Whether the queuer is not currently processing any items\n */\n isIdle: boolean\n /**\n * Whether the queuer is active and will process items automatically\n */\n isRunning: boolean\n /**\n * Array of items currently waiting to be processed\n */\n items: Array<TValue>\n /**\n * Timestamps when items were added to the queue for expiration tracking\n */\n itemTimestamps: Array<number>\n /**\n * The result from the most recent task execution\n */\n lastResult: any\n /**\n * Whether the queuer has a pending timeout for processing the next item\n */\n pendingTick: boolean\n /**\n * Number of items that have been rejected from being added to the queue\n */\n rejectionCount: number\n /**\n * Number of task executions that have completed (either successfully or with errors)\n */\n settledCount: number\n /**\n * Number of items currently in the queue\n */\n size: number\n /**\n * Current processing status - 'idle' when not processing, 'running' when active, 'stopped' when paused\n */\n status: 'idle' | 'running' | 'stopped'\n /**\n * Number of task executions that have completed successfully\n */\n successCount: number\n}\n\nfunction getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {\n return {\n activeItems: [],\n addItemCount: 0,\n errorCount: 0,\n executeCount: 0,\n expirationCount: 0,\n isEmpty: true,\n isExecuting: false,\n isFull: false,\n isIdle: true,\n isRunning: true,\n itemTimestamps: [],\n items: [],\n lastResult: null,\n pendingTick: false,\n rejectionCount: 0,\n settledCount: 0,\n size: 0,\n status: 'idle',\n successCount: 0,\n }\n}\n\nexport interface AsyncQueuerOptions<TValue> {\n /**\n * Options for configuring the underlying async retryer\n */\n asyncRetryerOptions?: AsyncRetryerOptions<(item: TValue) => Promise<any>>\n /**\n * Default position to add items to the queuer\n * @default 'back'\n */\n addItemsTo?: QueuePosition\n /**\n * Maximum number of concurrent tasks to process.\n * Can be a number or a function that returns a number.\n * @default 1\n */\n concurrency?: number | ((queuer: AsyncQueuer<TValue>) => number)\n /**\n * Maximum time in milliseconds that an item can stay in the queue\n * If not provided, items will never expire\n */\n expirationDuration?: number\n /**\n * Function to determine if an item has expired\n * If provided, this overrides the expirationDuration behavior\n */\n getIsExpired?: (item: TValue, addedAt: number) => boolean\n /**\n * Default position to get items from during processing\n * @default 'front'\n */\n getItemsFrom?: QueuePosition\n /**\n * Function to determine priority of items in the queuer\n * Higher priority items will be processed first\n * If not provided, will use static priority values attached to tasks\n */\n getPriority?: (item: TValue) => number\n /**\n * Initial items to populate the queuer with\n */\n initialItems?: Array<TValue>\n /**\n * Initial state for the async queuer\n */\n initialState?: Partial<AsyncQueuerState<TValue>>\n /**\n * Optional key to identify this async queuer instance.\n * If provided, the async queuer will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Maximum number of items allowed in the queuer\n */\n maxSize?: number\n /**\n * Optional error handler for when a task throws.\n * If provided, the handler will be called with the error and queuer instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (error: Error, item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item expires in the queuer\n */\n onExpire?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item is added or removed from the queuer\n */\n onItemsChange?: (queuer: AsyncQueuer<TValue>) => void\n /**\n * Callback fired whenever an item is rejected from being added to the queuer\n */\n onReject?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Optional callback to call when a task is settled\n */\n onSettled?: (item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Optional callback to call when a task succeeds\n */\n onSuccess?: (result: any, item: TValue, queuer: AsyncQueuer<TValue>) => void\n /**\n * Whether the queuer should start processing tasks immediately or not.\n */\n started?: boolean\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 * Time in milliseconds to wait between processing items.\n * Can be a number or a function that returns a number.\n * @default 0\n */\n wait?: number | ((queuer: AsyncQueuer<TValue>) => number)\n}\n\n/**\n * Utility function for sharing common `AsyncQueuerOptions` options between different `AsyncQueuer` instances.\n */\nexport function asyncQueuerOptions<\n TValue = any,\n TOptions extends Partial<AsyncQueuerOptions<TValue>> = Partial<\n AsyncQueuerOptions<TValue>\n >,\n>(options: TOptions): TOptions {\n return options\n}\n\ntype AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys<\n Required<AsyncQueuerOptions<any>>,\n | 'initialState'\n | 'throwOnError'\n | 'onSuccess'\n | 'onSettled'\n | 'onReject'\n | 'onItemsChange'\n | 'onExpire'\n | 'onError'\n | 'key'\n>\n\nconst defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = {\n addItemsTo: 'back',\n asyncRetryerOptions: {\n maxAttempts: 1,\n },\n concurrency: 1,\n expirationDuration: Infinity,\n getIsExpired: () => false,\n getItemsFrom: 'front',\n getPriority: (item: any) => item?.priority ?? 0,\n initialItems: [],\n maxSize: Infinity,\n started: true,\n wait: 0,\n}\n\n/**\n * A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync Queuer:\n * - Returns promises that can be awaited for task results\n * - Built-in retry support via AsyncRetryer integration for each queued task\n * - Abort support to cancel in-flight task executions\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n * - Concurrent execution support (process multiple items simultaneously)\n *\n * The sync Queuer is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Queuing?\n * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.\n * Tasks are processed up to the configured concurrency limit. When a task completes,\n * the next pending task is processed if the concurrency limit allows.\n *\n * Key Features:\n * - Priority queue support via the getPriority option\n * - Configurable concurrency limit\n * - Callbacks for task success, error, completion, and queue state changes\n * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior\n * - Pause and resume processing\n * - Item expiration to remove stale items from the queue\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and queuer 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 AsyncQueuer 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 queuer\n * - Use `onSuccess` callback to react to successful task execution and implement custom logic\n * - Use `onError` callback to react to task execution errors and implement custom error handling\n * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the queue\n * - Use `onExpire` callback to react to items expiring and implement custom logic\n * - Use `onReject` callback to react to items being rejected when the queue is full\n * - The state includes error count, expiration count, rejection count, running status, and success/settle counts\n * - State can be accessed via `asyncQueuer.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncQueuer.state`\n *\n * Example usage:\n * ```ts\n * const asyncQueuer = new AsyncQueuer<string>(async (item) => {\n * // process item\n * return item.toUpperCase();\n * }, {\n * concurrency: 2,\n * onSuccess: (result) => {\n * console.log(result);\n * }\n * });\n *\n * asyncQueuer.addItem('hello');\n * asyncQueuer.start();\n * ```\n */\nexport class AsyncQueuer<TValue> {\n readonly store: Store<Readonly<AsyncQueuerState<TValue>>> = new Store<\n AsyncQueuerState<TValue>\n >(getDefaultAsyncQueuerState<TValue>())\n key: string\n options: AsyncQueuerOptions<TValue>\n asyncRetryers = new Map<\n number,\n AsyncRetryer<(item: TValue) => Promise<any>>\n >()\n #timeoutIds: Set<NodeJS.Timeout> = new Set()\n\n constructor(\n public fn: (item: TValue) => Promise<any>,\n initialOptions: AsyncQueuerOptions<TValue> = {},\n ) {\n this.key = createKey(initialOptions.key)\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n const isInitiallyRunning =\n this.options.initialState?.isRunning ?? this.options.started ?? true\n this.#setState({\n ...this.options.initialState,\n isRunning: isInitiallyRunning,\n })\n\n if (this.options.initialState?.items) {\n if (this.store.state.isRunning) {\n this.#tick()\n }\n } else {\n for (let i = 0; i < (this.options.initialItems?.length ?? 0); i++) {\n const item = this.options.initialItems![i]!\n const isLast = i === (this.options.initialItems?.length ?? 0) - 1\n this.addItem(item, this.options.addItemsTo ?? 'back', isLast)\n }\n }\n\n pacerEventClient.on('d-AsyncQueuer', (e) => {\n if (e.payload.key !== this.key) return\n this.#setState(e.payload.store.state)\n this.setOptions(e.payload.options)\n })\n }\n\n /**\n * Emits a change event for the async queuer instance. Mostly useful for devtools.\n */\n _emit = () => emitChange('AsyncQueuer', this)\n\n /**\n * Updates the queuer options. New options are merged with existing options.\n */\n setOptions = (newOptions: Partial<AsyncQueuerOptions<TValue>>): void => {\n this.options = { ...this.options, ...newOptions }\n }\n\n #setState = (newState: Partial<AsyncQueuerState<TValue>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n\n const { activeItems, items, isRunning } = combinedState\n\n const size = items.length\n const isFull = size >= (this.options.maxSize ?? Infinity)\n const isEmpty = size === 0\n const isIdle = isRunning && isEmpty && activeItems.length === 0\n\n const status = isIdle ? 'idle' : isRunning ? 'running' : 'stopped'\n\n return {\n ...combinedState,\n isEmpty,\n isFull,\n isIdle,\n size,\n status,\n }\n })\n emitChange('AsyncQueuer', this)\n }\n\n /**\n * Returns the current wait time (in milliseconds) between processing items.\n * If a function is provided, it is called with the queuer instance.\n */\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait ?? 0, this)\n }\n\n /**\n * Returns the current concurrency limit for processing items.\n * If a function is provided, it is called with the queuer instance.\n */\n #getConcurrency = (): number => {\n return parseFunctionOrValue(this.options.concurrency ?? 1, this)\n }\n\n /**\n * Processes items in the queue up to the concurrency limit. Internal use only.\n */\n #tick = () => {\n if (!this.store.state.isRunning) {\n this.#setState({ pendingTick: false })\n return\n }\n this.#setState({ pendingTick: true })\n\n // Check for expired items\n this.#checkExpiredItems()\n\n // Process items concurrently up to the concurrency limit\n const activeItems = this.store.state.activeItems\n while (\n activeItems.length < this.#getConcurrency() &&\n this.store.state.items.length > 0\n ) {\n const nextItem = this.peekNextItem()\n if (!nextItem) {\n break\n }\n activeItems.push(nextItem)\n this.#setState({\n activeItems,\n })\n ;(async () => {\n const result = await this.execute()\n this.#setState({ lastResult: result })\n\n const wait = this.#getWait()\n if (wait > 0) {\n const timeoutId = setTimeout(() => this.#tick(), wait)\n this.#timeoutIds.add(timeoutId)\n return\n }\n\n this.#tick()\n })()\n }\n\n this.#setState({ pendingTick: false })\n }\n\n /**\n * Adds an item to the queue. If the queue is full, the item is rejected and onReject is called.\n * Items can be inserted based on priority or at the front/back depending on configuration.\n *\n * @example\n * ```ts\n * queuer.addItem({ value: 'task', priority: 10 });\n * queuer.addItem('task2', 'front');\n * ```\n */\n addItem = (\n item: TValue,\n position: QueuePosition = this.options.addItemsTo ?? 'back',\n runOnItemsChange: boolean = true,\n ): boolean => {\n this.#setState({\n addItemCount: this.store.state.addItemCount + 1,\n })\n\n if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {\n this.#setState({\n rejectionCount: this.store.state.rejectionCount + 1,\n })\n this.options.onReject?.(item, this)\n return false\n }\n\n // Get priority either from the function or from getPriority option\n const priority =\n this.options.getPriority !== defaultOptions.getPriority\n ? this.options.getPriority!(item)\n : (item as any).priority\n\n const items = this.store.state.items\n const itemTimestamps = this.store.state.itemTimestamps\n\n if (priority !== undefined) {\n // Insert based on priority - higher priority items go to front\n const insertIndex = items.findIndex((existing) => {\n const existingPriority =\n this.options.getPriority !== defaultOptions.getPriority\n ? this.options.getPriority!(existing)\n : (existing as any).priority\n return existingPriority < priority\n })\n\n if (insertIndex === -1) {\n items.push(item)\n itemTimestamps.push(Date.now())\n } else {\n items.splice(insertIndex, 0, item)\n itemTimestamps.splice(insertIndex, 0, Date.now())\n }\n } else {\n if (position === 'front') {\n // Default FIFO/LIFO behavior\n items.unshift(item)\n itemTimestamps.unshift(Date.now())\n } else {\n // LIFO\n items.push(item)\n itemTimestamps.push(Date.now())\n }\n }\n\n this.#setState({\n items,\n itemTimestamps,\n })\n\n if (runOnItemsChange) {\n this.options.onItemsChange?.(this)\n }\n\n if (this.store.state.isRunning && !this.store.state.pendingTick) {\n this.#tick()\n }\n\n return true\n }\n\n /**\n * Removes and returns the next item from the queue without executing the task function.\n * Use for manual queue management. Normally, use execute() to process items.\n *\n * @example\n * ```ts\n * // FIFO\n * queuer.getNextItem();\n * // LIFO\n * queuer.getNextItem('back');\n * ```\n */\n getNextItem = (\n position: QueuePosition = this.options.getItemsFrom ?? 'front',\n ): TValue | undefined => {\n const { items, itemTimestamps } = this.store.state\n let item: TValue | undefined\n\n // When priority function is provided or position is 'front', always get from front (highest priority)\n // Priority takes precedence over FIFO/LIFO behavior\n if (\n this.options.getPriority !== defaultOptions.getPriority ||\n position === 'front'\n ) {\n item = items[0]\n if (item !== undefined) {\n this.#setState({\n items: items.slice(1),\n itemTimestamps: itemTimestamps.slice(1),\n })\n }\n } else {\n item = items[items.length - 1]\n if (item !== undefined) {\n this.#setState({\n items: items.slice(0, -1),\n itemTimestamps: itemTimestamps.slice(0, -1),\n })\n }\n }\n\n if (item !== undefined) {\n this.options.onItemsChange?.(this)\n }\n\n return item\n }\n\n #getAllItems = (): Array<TValue> => {\n const items = this.peekAllItems()\n this.clear()\n return items\n }\n\n /**\n * Removes and returns the next item from the queue and executes the task function with it.\n *\n * @example\n * ```ts\n * queuer.execute();\n * // LIFO\n * queuer.execute('back');\n * ```\n */\n execute = async (position?: QueuePosition): Promise<any> => {\n const item = this.getNextItem(position)\n\n if (item !== undefined) {\n const currentExecuteCount = this.store.state.executeCount + 1\n this.#setState({\n executeCount: currentExecuteCount,\n isExecuting: true,\n })\n try {\n const currentAsyncRetryer = new AsyncRetryer(this.fn, {\n ...this.options.asyncRetryerOptions,\n key: `${this.key}-retryer-${currentExecuteCount}`,\n })\n this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer)\n const lastResult = await currentAsyncRetryer.execute(item) // EXECUTE!\n this.#setState({\n successCount: this.store.state.successCount + 1,\n lastResult,\n })\n this.options.onSuccess?.(lastResult, item, this)\n } catch (error) {\n this.#setState({\n errorCount: this.store.state.errorCount + 1,\n })\n this.options.onError?.(error as Error, item, this)\n if (this.options.throwOnError) {\n throw error\n }\n } finally {\n this.asyncRetryers.delete(currentExecuteCount) // dispose retryer\n this.#setState({\n activeItems: this.store.state.activeItems.filter(\n (activeItem) => activeItem !== item,\n ),\n isExecuting: false,\n settledCount: this.store.state.settledCount + 1,\n })\n this.options.onSettled?.(item, this)\n }\n }\n return item\n }\n\n /**\n * Processes a specified number of items to execute immediately with no wait time\n * If no numberOfItems is provided, all items will be processed\n */\n flush = async (\n numberOfItems: number = this.store.state.items.length,\n position?: QueuePosition,\n ): Promise<void> => {\n this.#clearTimeouts() // clear any pending timeouts\n await Promise.all(\n Array.from({ length: numberOfItems }, () => this.execute(position)),\n )\n }\n\n /**\n * Processes all items in the queue as a batch using the provided function as an argument\n * The queue is cleared after processing\n */\n flushAsBatch = async (\n batchFunction: (items: Array<TValue>) => Promise<any>,\n ): Promise<void> => {\n this.#clearTimeouts() // clear any pending timeouts\n const items = this.#getAllItems()\n await batchFunction(items)\n }\n\n /**\n * Checks for expired items in the queue and removes them. Calls onExpire for each expired item.\n * Internal use only.\n */\n #checkExpiredItems = (): void => {\n if (\n (this.options.expirationDuration ?? Infinity) === Infinity &&\n this.options.getIsExpired === defaultOptions.getIsExpired\n ) {\n return\n }\n\n const now = Date.now()\n const expiredIndices: Array<number> = []\n\n // Find indices of expired items\n for (let i = 0; i < this.store.state.items.length; i++) {\n const timestamp = this.store.state.itemTimestamps[i]\n if (timestamp === undefined) continue\n\n const item = this.store.state.items[i]\n if (item === undefined) continue\n\n const isExpired =\n this.options.getIsExpired !== defaultOptions.getIsExpired\n ? this.options.getIsExpired!(item, timestamp)\n : now - timestamp > (this.options.expirationDuration ?? Infinity)\n\n if (isExpired) {\n expiredIndices.push(i)\n }\n }\n\n // Remove expired items from back to front to maintain indices\n for (let i = expiredIndices.length - 1; i >= 0; i--) {\n const index = expiredIndices[i]\n if (index === undefined) continue\n\n const expiredItem = this.store.state.items[index]\n if (expiredItem === undefined) continue\n\n const newItems = [...this.store.state.items]\n const newTimestamps = [...this.store.state.itemTimestamps]\n newItems.splice(index, 1)\n newTimestamps.splice(index, 1)\n this.#setState({\n items: newItems,\n itemTimestamps: newTimestamps,\n expirationCount: this.store.state.expirationCount + 1,\n })\n this.options.onExpire?.(expiredItem, this)\n }\n\n if (expiredIndices.length > 0) {\n this.options.onItemsChange?.(this)\n }\n }\n\n /**\n * Returns the next item in the queue without removing it.\n *\n * @example\n * ```ts\n * queuer.peekNextItem(); // front\n * queuer.peekNextItem('back'); // back\n * ```\n */\n peekNextItem = (position: QueuePosition = 'front'): TValue | undefined => {\n if (position === 'front') {\n return this.store.state.items[0]\n }\n return this.store.state.items[this.store.state.items.length - 1]\n }\n\n /**\n * Returns a copy of all items in the queue, including active and pending items.\n */\n peekAllItems = (): Array<TValue> => {\n return [...this.peekActiveItems(), ...this.peekPendingItems()]\n }\n\n /**\n * Returns the items currently being processed (active tasks).\n */\n peekActiveItems = (): Array<TValue> => {\n return [...this.store.state.activeItems]\n }\n\n /**\n * Returns the items waiting to be processed (pending tasks).\n */\n peekPendingItems = (): Array<TValue> => {\n return [...this.store.state.items]\n }\n\n /**\n * Starts processing items in the queue. If already running, does nothing.\n */\n start = (): void => {\n this.#setState({ isRunning: true })\n if (!this.store.state.pendingTick && this.store.state.items.length > 0) {\n this.#tick()\n }\n }\n\n /**\n * Stops processing items in the queue. Does not clear the queue.\n */\n stop = (): void => {\n this.#clearTimeouts()\n this.#setState({ isRunning: false, pendingTick: false })\n }\n\n #clearTimeouts = (): void => {\n this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId))\n this.#timeoutIds.clear()\n }\n\n /**\n * Removes all pending items from the queue.\n * Does NOT affect active tasks.\n */\n clear = (): void => {\n this.#setState({ items: [], itemTimestamps: [] })\n this.options.onItemsChange?.(this)\n }\n\n /**\n * Returns the AbortSignal for a specific execution.\n * If no executeCount 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 executeCount - Optional specific execution to get signal for\n * @example\n * ```typescript\n * const queuer = new AsyncQueuer(\n * async (item: string) => {\n * const signal = queuer.getAbortSignal()\n * if (signal) {\n * const response = await fetch(`/api/process/${item}`, { signal })\n * return response.json()\n * }\n * },\n * { concurrency: 2 }\n * )\n * ```\n */\n getAbortSignal(executeCount?: number): AbortSignal | null {\n const count = executeCount ?? this.store.state.executeCount\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 clear out the items.\n */\n abort = (): void => {\n this.asyncRetryers.forEach((retryer) => retryer.abort())\n this.asyncRetryers.clear()\n this.#setState({\n isExecuting: false,\n })\n }\n\n /**\n * Resets the queuer state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncQueuerState<TValue>())\n this.options.onItemsChange?.(this)\n this.asyncRetryers.forEach((retryer) => retryer.reset())\n }\n}\n\n/**\n * Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks.\n * The queuer is started automatically and ready to process items.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync queue function:\n * - Returns promises that can be awaited for task results\n * - Built-in retry support via AsyncRetryer integration for each queued task\n * - Abort support to cancel in-flight task executions\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n * - Concurrent execution support (process multiple items simultaneously)\n *\n * The sync queue function is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Queuing?\n * Queuing is a technique for managing and processing items sequentially or with controlled concurrency.\n * Tasks are processed up to the configured concurrency limit. When a task completes,\n * the next pending task is processed if the concurrency limit allows.\n *\n * Configuration Options:\n * - `concurrency`: Maximum number of concurrent tasks (default: 1)\n * - `wait`: Time to wait between processing items (default: 0)\n * - `maxSize`: Maximum number of items allowed in the queue (default: Infinity)\n * - `getPriority`: Function to determine item priority\n * - `addItemsTo`: Default position to add items ('back' or 'front', default: 'back')\n * - `getItemsFrom`: Default position to get items ('front' or 'back', default: 'front')\n * - `expirationDuration`: Maximum time items can stay in queue\n * - `started`: Whether to start processing immediately (default: true)\n * - `asyncRetryerOptions`: Configure retry behavior for task executions\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and queuer 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 AsyncQueuer 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 queuer\n * - Use `onSuccess` callback to react to successful task execution and implement custom logic\n * - Use `onError` callback to react to task execution errors and implement custom error handling\n * - Use `onSettled` callback to react to task execution completion (success or error) and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the queue\n * - Use `onExpire` callback to react to items expiring and implement custom logic\n * - Use `onReject` callback to react to items being rejected when the queue is full\n * - The state includes error count, expiration count, rejection count, running status, and success/settle counts\n * - State can be accessed via the underlying AsyncQueuer 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 enqueue = asyncQueue<string>(async (item) => {\n * return item.toUpperCase();\n * }, {\n * concurrency: 2,\n * wait: 100,\n * onSuccess: (result) => console.log('Processed:', result)\n * });\n *\n * enqueue('hello');\n * ```\n */\nexport function asyncQueue<TValue>(\n fn: (value: TValue) => Promise<any>,\n initialOptions: AsyncQueuerOptions<TValue>,\n) {\n const asyncQueuer = new AsyncQueuer<TValue>(fn, initialOptions)\n return asyncQueuer.addItem\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { Store } from '@tanstack/store';
2
+ import { AsyncRetryer, AsyncRetryerOptions } from './async-retryer.js';
2
3
  import { AnyAsyncFunction } from './types.js';
3
4
  export interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
4
5
  /**
@@ -46,6 +47,10 @@ export interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
46
47
  * Options for configuring an async rate-limited function
47
48
  */
48
49
  export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
50
+ /**
51
+ * Options for configuring the underlying async retryer
52
+ */
53
+ asyncRetryerOptions?: AsyncRetryerOptions<TFn>;
49
54
  /**
50
55
  * Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions.
51
56
  * Can be a boolean or a function that returns a boolean.
@@ -71,7 +76,7 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
71
76
  * If provided, the handler will be called with the error and rate limiter instance.
72
77
  * This can be used alongside throwOnError - the handler will be called before any error is thrown.
73
78
  */
74
- onError?: (error: unknown, args: Parameters<TFn>, rateLimiter: AsyncRateLimiter<TFn>) => void;
79
+ onError?: (error: Error, args: Parameters<TFn>, rateLimiter: AsyncRateLimiter<TFn>) => void;
75
80
  /**
76
81
  * Optional callback function that is called when an execution is rejected due to rate limiting
77
82
  */
@@ -103,29 +108,41 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
103
108
  */
104
109
  windowType?: 'fixed' | 'sliding';
105
110
  }
111
+ /**
112
+ * Utility function for sharing common `AsyncRateLimiterOptions` options between different `AsyncRateLimiter` instances.
113
+ */
114
+ export declare function asyncRateLimiterOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncRateLimiterOptions<TFn>> = Partial<AsyncRateLimiterOptions<TFn>>>(options: TOptions): TOptions;
106
115
  /**
107
116
  * A class that creates an async rate-limited function.
108
117
  *
109
- * Rate limiting is a simple approach that allows a function to execute up to a limit within a time window,
118
+ * Async vs Sync Versions:
119
+ * The async version provides advanced features over the sync RateLimiter:
120
+ * - Returns promises that can be awaited for rate-limited function results
121
+ * - Built-in retry support via AsyncRetryer integration
122
+ * - Abort support to cancel in-flight executions
123
+ * - Comprehensive error handling with onError callbacks and throwOnError control
124
+ * - Detailed execution tracking (success/error/settle counts, rejection counts)
125
+ * - More sophisticated window management with automatic cleanup
126
+ *
127
+ * The sync RateLimiter is lighter weight and simpler when you don't need async features,
128
+ * return values, or execution control.
129
+ *
130
+ * What is Rate Limiting?
131
+ * Rate limiting allows a function to execute up to a limit within a time window,
110
132
  * then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where
111
133
  * all executions happen immediately, followed by a complete block.
112
134
  *
113
- * The rate limiter supports two types of windows:
135
+ * Window Types:
114
136
  * - 'fixed': A strict window that resets after the window period. All executions within the window count
115
137
  * towards the limit, and the window resets completely after the period.
116
138
  * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
117
139
  * consistent rate of execution over time.
118
140
  *
119
- * Unlike the non-async RateLimiter, this async version supports returning values from the rate-limited function,
120
- * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
121
- * instead of setting the result on a state variable from within the rate-limited function.
122
- *
123
- * For smoother execution patterns, consider using:
124
- * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms)
125
- * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls)
126
- *
141
+ * When to Use Rate Limiting:
127
142
  * Rate limiting is best used for hard API limits or resource constraints. For UI updates or
128
143
  * smoothing out frequent events, throttling or debouncing usually provide better user experience.
144
+ * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms)
145
+ * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls)
129
146
  *
130
147
  * State Management:
131
148
  * - Uses TanStack Store for reactive state management
@@ -175,7 +192,12 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
175
192
  readonly store: Store<Readonly<AsyncRateLimiterState<TFn>>>;
176
193
  key: string;
177
194
  options: AsyncRateLimiterOptions<TFn>;
195
+ asyncRetryers: Map<number, AsyncRetryer<TFn>>;
178
196
  constructor(fn: TFn, initialOptions: AsyncRateLimiterOptions<TFn>);
197
+ /**
198
+ * Emits a change event for the async rate limiter instance. Mostly useful for devtools.
199
+ */
200
+ _emit: () => void;
179
201
  /**
180
202
  * Updates the async rate limiter options
181
203
  */
@@ -216,6 +238,32 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
216
238
  * For sliding windows, this is the time until the oldest execution expires
217
239
  */
218
240
  getMsUntilNextWindow: () => number;
241
+ /**
242
+ * Returns the AbortSignal for a specific execution.
243
+ * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
244
+ * Returns null if no execution is found or not currently executing.
245
+ *
246
+ * @param maybeExecuteCount - Optional specific execution to get signal for
247
+ * @example
248
+ * ```typescript
249
+ * const rateLimiter = new AsyncRateLimiter(
250
+ * async (userId: string) => {
251
+ * const signal = rateLimiter.getAbortSignal()
252
+ * if (signal) {
253
+ * const response = await fetch(`/api/users/${userId}`, { signal })
254
+ * return response.json()
255
+ * }
256
+ * },
257
+ * { limit: 5, window: 1000 }
258
+ * )
259
+ * ```
260
+ */
261
+ getAbortSignal(maybeExecuteCount?: number): AbortSignal | null;
262
+ /**
263
+ * Aborts all ongoing executions with the internal abort controllers.
264
+ * Does NOT clear out the execution times or reset the rate limiter.
265
+ */
266
+ abort: () => void;
219
267
  /**
220
268
  * Resets the rate limiter state
221
269
  */
@@ -224,21 +272,51 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
224
272
  /**
225
273
  * Creates an async rate-limited function that will execute the provided function up to a maximum number of times within a time window.
226
274
  *
227
- * Unlike the non-async rate limiter, this async version supports returning values from the rate-limited function,
228
- * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
229
- * instead of setting the result on a state variable from within the rate-limited function.
275
+ * Async vs Sync Versions:
276
+ * The async version provides advanced features over the sync rate limit function:
277
+ * - Returns promises that can be awaited for rate-limited function results
278
+ * - Built-in retry support via AsyncRetryer integration
279
+ * - Abort support to cancel in-flight executions
280
+ * - Comprehensive error handling with onError callbacks and throwOnError control
281
+ * - Detailed execution tracking (success/error/settle counts, rejection counts)
282
+ * - More sophisticated window management with automatic cleanup
230
283
  *
231
- * The rate limiter supports two types of windows:
284
+ * The sync rate limit function is lighter weight and simpler when you don't need async features,
285
+ * return values, or execution control.
286
+ *
287
+ * What is Rate Limiting?
288
+ * Rate limiting allows a function to execute up to a limit within a time window,
289
+ * then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where
290
+ * all executions happen immediately, followed by a complete block.
291
+ *
292
+ * Window Types:
232
293
  * - 'fixed': A strict window that resets after the window period. All executions within the window count
233
294
  * towards the limit, and the window resets completely after the period.
234
295
  * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
235
296
  * consistent rate of execution over time.
236
297
  *
237
- * Note that rate limiting is a simpler form of execution control compared to throttling or debouncing:
298
+ * Configuration Options:
299
+ * - `limit`: Maximum number of executions allowed within the window (required)
300
+ * - `window`: Time window in milliseconds (required)
301
+ * - `windowType`: 'fixed' or 'sliding' (default: 'fixed')
302
+ * - `enabled`: Whether the rate limiter is enabled (default: true)
303
+ * - `asyncRetryerOptions`: Configure retry behavior for executions
304
+ *
305
+ * When to Use Rate Limiting:
306
+ * Rate limiting is best used for hard API limits or resource constraints. For UI updates or
307
+ * smoothing out frequent events, throttling or debouncing usually provide better user experience.
238
308
  * - A rate limiter will allow all executions until the limit is reached, then block all subsequent calls until the window resets
239
309
  * - A throttler ensures even spacing between executions, which can be better for consistent performance
240
310
  * - A debouncer collapses multiple calls into one, which is better for handling bursts of events
241
311
  *
312
+ * Error Handling:
313
+ * - If an `onError` handler is provided, it will be called with the error and rate limiter instance
314
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
315
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
316
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
317
+ * - The error state can be checked using the underlying AsyncRateLimiter instance
318
+ * - Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler
319
+ *
242
320
  * State Management:
243
321
  * - Uses TanStack Store for reactive state management
244
322
  * - Use `initialState` to provide initial state values when creating the rate limiter
@@ -251,17 +329,6 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
251
329
  * - State can be accessed via the underlying AsyncRateLimiter instance's `store.state` property
252
330
  * - When using framework adapters (React/Solid), state is accessed from the hook's state property
253
331
  *
254
- * Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically
255
- * need to enforce a hard limit on the number of executions within a time period.
256
- *
257
- * Error Handling:
258
- * - If an `onError` handler is provided, it will be called with the error and rate limiter instance
259
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
260
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
261
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
262
- * - The error state can be checked using the underlying AsyncRateLimiter instance
263
- * - Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler
264
- *
265
332
  * @example
266
333
  * ```ts
267
334
  * // Rate limit to 5 calls per minute with a sliding window
@@ -1,4 +1,5 @@
1
1
  import { Store } from "@tanstack/store";
2
+ import { AsyncRetryer } from "./async-retryer.js";
2
3
  import { parseFunctionOrValue, createKey } from "./utils.js";
3
4
  import { emitChange, pacerEventClient } from "./event-client.js";
4
5
  function getDefaultAsyncRateLimiterState() {
@@ -15,7 +16,13 @@ function getDefaultAsyncRateLimiterState() {
15
16
  successCount: 0
16
17
  };
17
18
  }
19
+ function asyncRateLimiterOptions(options) {
20
+ return options;
21
+ }
18
22
  const defaultOptions = {
23
+ asyncRetryerOptions: {
24
+ maxAttempts: 1
25
+ },
19
26
  enabled: true,
20
27
  limit: 1,
21
28
  window: 0,
@@ -26,7 +33,9 @@ class AsyncRateLimiter {
26
33
  constructor(fn, initialOptions) {
27
34
  this.fn = fn;
28
35
  this.store = new Store(getDefaultAsyncRateLimiterState());
36
+ this.asyncRetryers = /* @__PURE__ */ new Map();
29
37
  this.#timeoutIds = /* @__PURE__ */ new Set();
38
+ this._emit = () => emitChange("AsyncRateLimiter", this);
30
39
  this.setOptions = (newOptions) => {
31
40
  this.options = { ...this.options, ...newOptions };
32
41
  };
@@ -73,6 +82,7 @@ class AsyncRateLimiter {
73
82
  };
74
83
  this.#execute = async (...args) => {
75
84
  if (!this.#getEnabled()) return;
85
+ const currentMaybeExecute = this.store.state.maybeExecuteCount;
76
86
  const now = Date.now();
77
87
  const executionTimes = [...this.store.state.executionTimes, now];
78
88
  this.#setState({
@@ -80,7 +90,12 @@ class AsyncRateLimiter {
80
90
  executionTimes
81
91
  });
82
92
  try {
83
- const result = await this.fn(...args);
93
+ const currentAsyncRetryer = new AsyncRetryer(this.fn, {
94
+ ...this.options.asyncRetryerOptions,
95
+ key: `${this.key}-retryer-${currentMaybeExecute}`
96
+ });
97
+ this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer);
98
+ const result = await currentAsyncRetryer.execute(...args);
84
99
  this.#setCleanupTimeout(now);
85
100
  this.#setState({
86
101
  successCount: this.store.state.successCount + 1,
@@ -96,6 +111,7 @@ class AsyncRateLimiter {
96
111
  throw error;
97
112
  }
98
113
  } finally {
114
+ this.asyncRetryers.delete(currentMaybeExecute);
99
115
  this.#setState({
100
116
  isExecuting: false,
101
117
  settleCount: this.store.state.settleCount + 1
@@ -160,9 +176,17 @@ class AsyncRateLimiter {
160
176
  const oldestExecution = this.store.state.executionTimes[0] ?? Infinity;
161
177
  return oldestExecution + this.#getWindow() - Date.now();
162
178
  };
179
+ this.abort = () => {
180
+ this.asyncRetryers.forEach((retryer) => retryer.abort());
181
+ this.asyncRetryers.clear();
182
+ this.#setState({
183
+ isExecuting: false
184
+ });
185
+ };
163
186
  this.reset = () => {
164
187
  this.#setState(getDefaultAsyncRateLimiterState());
165
188
  this.#clearTimeouts();
189
+ this.asyncRetryers.forEach((retryer) => retryer.reset());
166
190
  };
167
191
  this.key = createKey(initialOptions.key);
168
192
  this.options = {
@@ -191,6 +215,31 @@ class AsyncRateLimiter {
191
215
  #clearTimeout;
192
216
  #clearTimeouts;
193
217
  #cleanupOldExecutions;
218
+ /**
219
+ * Returns the AbortSignal for a specific execution.
220
+ * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
221
+ * Returns null if no execution is found or not currently executing.
222
+ *
223
+ * @param maybeExecuteCount - Optional specific execution to get signal for
224
+ * @example
225
+ * ```typescript
226
+ * const rateLimiter = new AsyncRateLimiter(
227
+ * async (userId: string) => {
228
+ * const signal = rateLimiter.getAbortSignal()
229
+ * if (signal) {
230
+ * const response = await fetch(`/api/users/${userId}`, { signal })
231
+ * return response.json()
232
+ * }
233
+ * },
234
+ * { limit: 5, window: 1000 }
235
+ * )
236
+ * ```
237
+ */
238
+ getAbortSignal(maybeExecuteCount) {
239
+ const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount;
240
+ const retryer = this.asyncRetryers.get(count);
241
+ return retryer?.getAbortSignal() ?? null;
242
+ }
194
243
  }
195
244
  function asyncRateLimit(fn, initialOptions) {
196
245
  const rateLimiter = new AsyncRateLimiter(fn, initialOptions);
@@ -198,6 +247,7 @@ function asyncRateLimit(fn, initialOptions) {
198
247
  }
199
248
  export {
200
249
  AsyncRateLimiter,
201
- asyncRateLimit
250
+ asyncRateLimit,
251
+ asyncRateLimiterOptions
202
252
  };
203
253
  //# sourceMappingURL=async-rate-limiter.js.map