@tanstack/pacer 0.16.2 → 0.16.4

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 (180) hide show
  1. package/README.md +165 -0
  2. package/dist/async-batcher.cjs +355 -0
  3. package/dist/async-batcher.cjs.map +1 -0
  4. package/dist/async-batcher.d.cts +344 -0
  5. package/dist/async-batcher.d.ts +344 -0
  6. package/dist/async-batcher.js +353 -0
  7. package/dist/async-batcher.js.map +1 -0
  8. package/dist/async-debouncer.cjs +340 -0
  9. package/dist/async-debouncer.cjs.map +1 -0
  10. package/dist/async-debouncer.d.cts +300 -0
  11. package/dist/async-debouncer.d.ts +300 -0
  12. package/dist/async-debouncer.js +338 -0
  13. package/dist/async-debouncer.js.map +1 -0
  14. package/dist/async-queuer.cjs +496 -0
  15. package/dist/async-queuer.cjs.map +1 -0
  16. package/dist/async-queuer.d.cts +440 -0
  17. package/dist/async-queuer.d.ts +440 -0
  18. package/dist/async-queuer.js +494 -0
  19. package/dist/async-queuer.js.map +1 -0
  20. package/dist/async-rate-limiter.cjs +381 -0
  21. package/dist/async-rate-limiter.cjs.map +1 -0
  22. package/dist/{cjs/async-rate-limiter.d.cts → async-rate-limiter.d.cts} +190 -185
  23. package/dist/{esm/async-rate-limiter.d.ts → async-rate-limiter.d.ts} +190 -185
  24. package/dist/async-rate-limiter.js +379 -0
  25. package/dist/async-rate-limiter.js.map +1 -0
  26. package/dist/async-retryer.cjs +378 -0
  27. package/dist/async-retryer.cjs.map +1 -0
  28. package/dist/async-retryer.d.cts +312 -0
  29. package/dist/async-retryer.d.ts +312 -0
  30. package/dist/async-retryer.js +376 -0
  31. package/dist/async-retryer.js.map +1 -0
  32. package/dist/async-throttler.cjs +362 -0
  33. package/dist/async-throttler.cjs.map +1 -0
  34. package/dist/async-throttler.d.cts +320 -0
  35. package/dist/async-throttler.d.ts +320 -0
  36. package/dist/async-throttler.js +360 -0
  37. package/dist/async-throttler.js.map +1 -0
  38. package/dist/batcher.cjs +194 -0
  39. package/dist/batcher.cjs.map +1 -0
  40. package/dist/batcher.d.cts +180 -0
  41. package/dist/batcher.d.ts +180 -0
  42. package/dist/batcher.js +193 -0
  43. package/dist/batcher.js.map +1 -0
  44. package/dist/debouncer.cjs +197 -0
  45. package/dist/debouncer.cjs.map +1 -0
  46. package/dist/debouncer.d.cts +167 -0
  47. package/dist/debouncer.d.ts +167 -0
  48. package/dist/debouncer.js +195 -0
  49. package/dist/debouncer.js.map +1 -0
  50. package/dist/event-client.cjs +20 -0
  51. package/dist/event-client.cjs.map +1 -0
  52. package/dist/event-client.d.cts +49 -0
  53. package/dist/event-client.d.ts +49 -0
  54. package/dist/event-client.js +19 -0
  55. package/dist/event-client.js.map +1 -0
  56. package/dist/index.cjs +49 -0
  57. package/dist/index.d.cts +15 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/{esm/index.js → index.js} +5 -41
  60. package/dist/queuer.cjs +393 -0
  61. package/dist/queuer.cjs.map +1 -0
  62. package/dist/queuer.d.cts +345 -0
  63. package/dist/queuer.d.ts +345 -0
  64. package/dist/queuer.js +391 -0
  65. package/dist/queuer.js.map +1 -0
  66. package/dist/rate-limiter.cjs +251 -0
  67. package/dist/rate-limiter.cjs.map +1 -0
  68. package/dist/{cjs/rate-limiter.d.cts → rate-limiter.d.cts} +113 -108
  69. package/dist/{esm/rate-limiter.d.ts → rate-limiter.d.ts} +113 -108
  70. package/dist/rate-limiter.js +249 -0
  71. package/dist/rate-limiter.js.map +1 -0
  72. package/dist/throttler.cjs +209 -0
  73. package/dist/throttler.cjs.map +1 -0
  74. package/dist/throttler.d.cts +207 -0
  75. package/dist/throttler.d.ts +207 -0
  76. package/dist/throttler.js +207 -0
  77. package/dist/throttler.js.map +1 -0
  78. package/dist/types.cjs +0 -0
  79. package/dist/types.d.cts +13 -0
  80. package/dist/types.d.ts +13 -0
  81. package/dist/types.js +1 -0
  82. package/dist/utils.cjs +13 -0
  83. package/dist/utils.cjs.map +1 -0
  84. package/dist/utils.d.cts +8 -0
  85. package/dist/utils.d.ts +8 -0
  86. package/dist/utils.js +11 -0
  87. package/dist/utils.js.map +1 -0
  88. package/package.json +38 -122
  89. package/src/async-batcher.ts +7 -5
  90. package/src/async-debouncer.ts +7 -5
  91. package/src/async-queuer.ts +7 -5
  92. package/src/async-rate-limiter.ts +7 -5
  93. package/src/async-retryer.ts +7 -5
  94. package/src/async-throttler.ts +7 -5
  95. package/src/batcher.ts +7 -5
  96. package/src/debouncer.ts +7 -5
  97. package/src/queuer.ts +7 -5
  98. package/src/rate-limiter.ts +7 -5
  99. package/src/throttler.ts +7 -5
  100. package/dist/cjs/async-batcher.cjs +0 -220
  101. package/dist/cjs/async-batcher.cjs.map +0 -1
  102. package/dist/cjs/async-batcher.d.cts +0 -340
  103. package/dist/cjs/async-debouncer.cjs +0 -231
  104. package/dist/cjs/async-debouncer.cjs.map +0 -1
  105. package/dist/cjs/async-debouncer.d.cts +0 -295
  106. package/dist/cjs/async-queuer.cjs +0 -397
  107. package/dist/cjs/async-queuer.cjs.map +0 -1
  108. package/dist/cjs/async-queuer.d.cts +0 -435
  109. package/dist/cjs/async-rate-limiter.cjs +0 -252
  110. package/dist/cjs/async-rate-limiter.cjs.map +0 -1
  111. package/dist/cjs/async-retryer.cjs +0 -285
  112. package/dist/cjs/async-retryer.cjs.map +0 -1
  113. package/dist/cjs/async-retryer.d.cts +0 -308
  114. package/dist/cjs/async-throttler.cjs +0 -261
  115. package/dist/cjs/async-throttler.cjs.map +0 -1
  116. package/dist/cjs/async-throttler.d.cts +0 -315
  117. package/dist/cjs/batcher.cjs +0 -130
  118. package/dist/cjs/batcher.cjs.map +0 -1
  119. package/dist/cjs/batcher.d.cts +0 -176
  120. package/dist/cjs/debouncer.cjs +0 -137
  121. package/dist/cjs/debouncer.cjs.map +0 -1
  122. package/dist/cjs/debouncer.d.cts +0 -162
  123. package/dist/cjs/event-client.cjs +0 -20
  124. package/dist/cjs/event-client.cjs.map +0 -1
  125. package/dist/cjs/event-client.d.cts +0 -45
  126. package/dist/cjs/index.cjs +0 -51
  127. package/dist/cjs/index.cjs.map +0 -1
  128. package/dist/cjs/index.d.cts +0 -15
  129. package/dist/cjs/queuer.cjs +0 -306
  130. package/dist/cjs/queuer.cjs.map +0 -1
  131. package/dist/cjs/queuer.d.cts +0 -340
  132. package/dist/cjs/rate-limiter.cjs +0 -179
  133. package/dist/cjs/rate-limiter.cjs.map +0 -1
  134. package/dist/cjs/throttler.cjs +0 -151
  135. package/dist/cjs/throttler.cjs.map +0 -1
  136. package/dist/cjs/throttler.d.cts +0 -202
  137. package/dist/cjs/types.d.cts +0 -9
  138. package/dist/cjs/utils.cjs +0 -11
  139. package/dist/cjs/utils.cjs.map +0 -1
  140. package/dist/cjs/utils.d.cts +0 -3
  141. package/dist/esm/async-batcher.d.ts +0 -340
  142. package/dist/esm/async-batcher.js +0 -220
  143. package/dist/esm/async-batcher.js.map +0 -1
  144. package/dist/esm/async-debouncer.d.ts +0 -295
  145. package/dist/esm/async-debouncer.js +0 -231
  146. package/dist/esm/async-debouncer.js.map +0 -1
  147. package/dist/esm/async-queuer.d.ts +0 -435
  148. package/dist/esm/async-queuer.js +0 -397
  149. package/dist/esm/async-queuer.js.map +0 -1
  150. package/dist/esm/async-rate-limiter.js +0 -252
  151. package/dist/esm/async-rate-limiter.js.map +0 -1
  152. package/dist/esm/async-retryer.d.ts +0 -308
  153. package/dist/esm/async-retryer.js +0 -285
  154. package/dist/esm/async-retryer.js.map +0 -1
  155. package/dist/esm/async-throttler.d.ts +0 -315
  156. package/dist/esm/async-throttler.js +0 -261
  157. package/dist/esm/async-throttler.js.map +0 -1
  158. package/dist/esm/batcher.d.ts +0 -176
  159. package/dist/esm/batcher.js +0 -130
  160. package/dist/esm/batcher.js.map +0 -1
  161. package/dist/esm/debouncer.d.ts +0 -162
  162. package/dist/esm/debouncer.js +0 -137
  163. package/dist/esm/debouncer.js.map +0 -1
  164. package/dist/esm/event-client.d.ts +0 -45
  165. package/dist/esm/event-client.js +0 -20
  166. package/dist/esm/event-client.js.map +0 -1
  167. package/dist/esm/index.d.ts +0 -15
  168. package/dist/esm/index.js.map +0 -1
  169. package/dist/esm/queuer.d.ts +0 -340
  170. package/dist/esm/queuer.js +0 -306
  171. package/dist/esm/queuer.js.map +0 -1
  172. package/dist/esm/rate-limiter.js +0 -179
  173. package/dist/esm/rate-limiter.js.map +0 -1
  174. package/dist/esm/throttler.d.ts +0 -202
  175. package/dist/esm/throttler.js +0 -151
  176. package/dist/esm/throttler.js.map +0 -1
  177. package/dist/esm/types.d.ts +0 -9
  178. package/dist/esm/utils.d.ts +0 -3
  179. package/dist/esm/utils.js +0 -11
  180. package/dist/esm/utils.js.map +0 -1
@@ -1,295 +0,0 @@
1
- import { Store } from '@tanstack/store';
2
- import { AsyncRetryer, AsyncRetryerOptions } from './async-retryer.cjs';
3
- import { AnyAsyncFunction } from './types.cjs';
4
- export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
5
- /**
6
- * Whether the debouncer can execute on the leading edge of the timeout
7
- */
8
- canLeadingExecute: boolean;
9
- /**
10
- * Number of function executions that have resulted in errors
11
- */
12
- errorCount: number;
13
- /**
14
- * Whether the debounced function is currently executing asynchronously
15
- */
16
- isExecuting: boolean;
17
- /**
18
- * Whether the debouncer is waiting for the timeout to trigger execution
19
- */
20
- isPending: boolean;
21
- /**
22
- * The arguments from the most recent call to maybeExecute
23
- */
24
- lastArgs: Parameters<TFn> | undefined;
25
- /**
26
- * The result from the most recent successful function execution
27
- */
28
- lastResult: ReturnType<TFn> | undefined;
29
- /**
30
- * Number of times maybeExecute has been called (for reduction calculations)
31
- */
32
- maybeExecuteCount: number;
33
- /**
34
- * Number of function executions that have completed (either successfully or with errors)
35
- */
36
- settleCount: number;
37
- /**
38
- * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed
39
- */
40
- status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled';
41
- /**
42
- * Number of function executions that have completed successfully
43
- */
44
- successCount: number;
45
- }
46
- /**
47
- * Options for configuring an async debounced function
48
- */
49
- export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
50
- /**
51
- * Options for configuring the underlying async retryer
52
- */
53
- asyncRetryerOptions?: AsyncRetryerOptions<TFn>;
54
- /**
55
- * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
56
- * Can be a boolean or a function that returns a boolean.
57
- * Defaults to true.
58
- */
59
- enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean);
60
- /**
61
- * Initial state for the async debouncer
62
- */
63
- initialState?: Partial<AsyncDebouncerState<TFn>>;
64
- /**
65
- * Optional key to identify this async debouncer instance.
66
- * If provided, the async debouncer will be identified by this key in the devtools and PacerProvider if applicable.
67
- */
68
- key?: string;
69
- /**
70
- * Whether to execute on the leading edge of the timeout.
71
- * Defaults to false.
72
- */
73
- leading?: boolean;
74
- /**
75
- * Optional error handler for when the debounced function throws.
76
- * If provided, the handler will be called with the error and debouncer instance.
77
- * This can be used alongside throwOnError - the handler will be called before any error is thrown.
78
- */
79
- onError?: (error: Error, args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void;
80
- /**
81
- * Optional callback to call when the debounced function is executed
82
- */
83
- onSettled?: (args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void;
84
- /**
85
- * Optional callback to call when the debounced function is executed
86
- */
87
- onSuccess?: (result: ReturnType<TFn>, args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void;
88
- /**
89
- * Whether to throw errors when they occur.
90
- * Defaults to true if no onError handler is provided, false if an onError handler is provided.
91
- * Can be explicitly set to override these defaults.
92
- */
93
- throwOnError?: boolean;
94
- /**
95
- * Whether to execute on the trailing edge of the timeout.
96
- * Defaults to true.
97
- */
98
- trailing?: boolean;
99
- /**
100
- * Delay in milliseconds to wait after the last call before executing.
101
- * Can be a number or a function that returns a number.
102
- * Defaults to 0ms
103
- */
104
- wait: number | ((debouncer: AsyncDebouncer<TFn>) => number);
105
- }
106
- /**
107
- * Utility function for sharing common `AsyncDebouncerOptions` options between different `AsyncDebouncer` instances.
108
- */
109
- export declare function asyncDebouncerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncDebouncerOptions<TFn>> = Partial<AsyncDebouncerOptions<TFn>>>(options: TOptions): TOptions;
110
- /**
111
- * A class that creates an async debounced function.
112
- *
113
- * Async vs Sync Versions:
114
- * The async version provides advanced features over the sync Debouncer:
115
- * - Returns promises that can be awaited for debounced function results
116
- * - Built-in retry support via AsyncRetryer integration
117
- * - Abort support to cancel in-flight executions
118
- * - Cancel support to prevent pending executions from starting
119
- * - Comprehensive error handling with onError callbacks and throwOnError control
120
- * - Detailed execution tracking (success/error/settle counts)
121
- *
122
- * The sync Debouncer is lighter weight and simpler when you don't need async features,
123
- * return values, or execution control.
124
- *
125
- * What is Debouncing?
126
- * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.
127
- * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing
128
- * or input changes where you only want to execute the handler after the events have stopped occurring.
129
- *
130
- * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until
131
- * the function stops being called for the specified delay period.
132
- *
133
- * Error Handling:
134
- * - If an `onError` handler is provided, it will be called with the error and debouncer instance
135
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
136
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
137
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
138
- * - The error state can be checked using the underlying store
139
- *
140
- * State Management:
141
- * - The debouncer uses a reactive store for state management
142
- * - Use `initialState` to provide initial state values when creating the async debouncer
143
- * - The state includes canLeadingExecute, error count, execution status, and success/settle counts
144
- * - State can be accessed via the `store` property and its `state` getter
145
- * - The store is reactive and will notify subscribers of state changes
146
- *
147
- * @example
148
- * ```ts
149
- * const asyncDebouncer = new AsyncDebouncer(async (value: string) => {
150
- * const results = await searchAPI(value);
151
- * return results; // Return value is preserved
152
- * }, {
153
- * wait: 500,
154
- * onError: (error) => {
155
- * console.error('Search failed:', error);
156
- * }
157
- * });
158
- *
159
- * // Called on each keystroke but only executes after 500ms of no typing
160
- * // Returns the API response directly
161
- * const results = await asyncDebouncer.maybeExecute(inputElement.value);
162
- * ```
163
- */
164
- export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
165
- #private;
166
- fn: TFn;
167
- readonly store: Store<Readonly<AsyncDebouncerState<TFn>>>;
168
- key: string | undefined;
169
- options: AsyncDebouncerOptions<TFn>;
170
- asyncRetryers: Map<number, AsyncRetryer<TFn>>;
171
- constructor(fn: TFn, initialOptions: AsyncDebouncerOptions<TFn>);
172
- /**
173
- * Updates the async debouncer options
174
- */
175
- setOptions: (newOptions: Partial<AsyncDebouncerOptions<TFn>>) => void;
176
- /**
177
- * Attempts to execute the debounced function.
178
- * If a call is already in progress, it will be queued.
179
- *
180
- * Error Handling:
181
- * - If the debounced function throws and no `onError` handler is configured,
182
- * the error will be thrown from this method.
183
- * - If an `onError` handler is configured, errors will be caught and passed to the handler,
184
- * and this method will return undefined.
185
- * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
186
- *
187
- * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
188
- * @throws The error from the debounced function if no onError handler is configured
189
- */
190
- maybeExecute: (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;
191
- /**
192
- * Processes the current pending execution immediately
193
- */
194
- flush: () => Promise<ReturnType<TFn> | undefined>;
195
- /**
196
- * Returns the AbortSignal for a specific execution.
197
- * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
198
- * Returns null if no execution is found or not currently executing.
199
- *
200
- * @param maybeExecuteCount - Optional specific execution to get signal for
201
- * @example
202
- * ```typescript
203
- * const debouncer = new AsyncDebouncer(
204
- * async (searchTerm: string) => {
205
- * const signal = debouncer.getAbortSignal()
206
- * if (signal) {
207
- * const response = await fetch(`/api/search?q=${searchTerm}`, { signal })
208
- * return response.json()
209
- * }
210
- * },
211
- * { wait: 300 }
212
- * )
213
- * ```
214
- */
215
- getAbortSignal(maybeExecuteCount?: number): AbortSignal | null;
216
- /**
217
- * Aborts all ongoing executions with the internal abort controllers.
218
- * Does NOT cancel any pending execution that have not started yet.
219
- */
220
- abort: () => void;
221
- /**
222
- * Cancels any pending execution that have not started yet.
223
- * Does NOT abort any execution already in progress.
224
- */
225
- cancel: () => void;
226
- /**
227
- * Resets the debouncer state to its default values
228
- */
229
- reset: () => void;
230
- }
231
- /**
232
- * Creates an async debounced function that delays execution until after a specified wait time.
233
- * The debounced function will only execute once the wait period has elapsed without any new calls.
234
- * If called again during the wait period, the timer resets and a new wait period begins.
235
- *
236
- * Async vs Sync Versions:
237
- * The async version provides advanced features over the sync debounce function:
238
- * - Returns promises that can be awaited for debounced function results
239
- * - Built-in retry support via AsyncRetryer integration
240
- * - Abort support to cancel in-flight executions
241
- * - Cancel support to prevent pending executions from starting
242
- * - Comprehensive error handling with onError callbacks and throwOnError control
243
- * - Detailed execution tracking (success/error/settle counts)
244
- *
245
- * The sync debounce function is lighter weight and simpler when you don't need async features,
246
- * return values, or execution control.
247
- *
248
- * What is Debouncing?
249
- * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation.
250
- * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing
251
- * or input changes where you only want to execute the handler after the events have stopped occurring.
252
- *
253
- * Configuration Options:
254
- * - `wait`: Delay in milliseconds to wait after the last call (required)
255
- * - `leading`: Execute on the leading edge of the timeout (default: false)
256
- * - `trailing`: Execute on the trailing edge of the timeout (default: true)
257
- * - `enabled`: Whether the debouncer is enabled (default: true)
258
- * - `asyncRetryerOptions`: Configure retry behavior for executions
259
- *
260
- * Error Handling:
261
- * - If an `onError` handler is provided, it will be called with the error and debouncer instance
262
- * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
263
- * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
264
- * - The error state can be checked using the underlying AsyncDebouncer instance
265
- * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
266
- *
267
- * State Management:
268
- * - Uses TanStack Store for reactive state management
269
- * - Use `initialState` to provide initial state values when creating the async debouncer
270
- * - Use `onSuccess` callback to react to successful function execution and implement custom logic
271
- * - Use `onError` callback to react to function execution errors and implement custom error handling
272
- * - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
273
- * - The state includes canLeadingExecute, error count, execution status, and success/settle counts
274
- * - State can be accessed via `asyncDebouncer.store.state` when using the class directly
275
- * - When using framework adapters (React/Solid), state is accessed from `asyncDebouncer.state`
276
- *
277
- * @example
278
- * ```ts
279
- * const debounced = asyncDebounce(async (value: string) => {
280
- * const result = await saveToAPI(value);
281
- * return result; // Return value is preserved
282
- * }, {
283
- * wait: 1000,
284
- * onError: (error) => {
285
- * console.error('API call failed:', error);
286
- * },
287
- * throwOnError: true // Will both log the error and throw it
288
- * });
289
- *
290
- * // Will only execute once, 1 second after the last call
291
- * // Returns the API response directly
292
- * const result = await debounced("third");
293
- * ```
294
- */
295
- export declare function asyncDebounce<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncDebouncerOptions<TFn>): (...args: Parameters<TFn>) => Promise<ReturnType<TFn> | undefined>;
@@ -1,397 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const store = require("@tanstack/store");
4
- const asyncRetryer = require("./async-retryer.cjs");
5
- const utils = require("./utils.cjs");
6
- const eventClient = require("./event-client.cjs");
7
- function getDefaultAsyncQueuerState() {
8
- return {
9
- activeItems: [],
10
- addItemCount: 0,
11
- errorCount: 0,
12
- executeCount: 0,
13
- expirationCount: 0,
14
- isEmpty: true,
15
- isExecuting: false,
16
- isFull: false,
17
- isIdle: true,
18
- isRunning: true,
19
- itemTimestamps: [],
20
- items: [],
21
- lastResult: null,
22
- pendingTick: false,
23
- rejectionCount: 0,
24
- settledCount: 0,
25
- size: 0,
26
- status: "idle",
27
- successCount: 0
28
- };
29
- }
30
- function asyncQueuerOptions(options) {
31
- return options;
32
- }
33
- const defaultOptions = {
34
- addItemsTo: "back",
35
- asyncRetryerOptions: {
36
- maxAttempts: 1
37
- },
38
- concurrency: 1,
39
- expirationDuration: Infinity,
40
- getIsExpired: () => false,
41
- getItemsFrom: "front",
42
- getPriority: (item) => item?.priority ?? 0,
43
- initialItems: [],
44
- maxSize: Infinity,
45
- started: true,
46
- wait: 0
47
- };
48
- class AsyncQueuer {
49
- constructor(fn, initialOptions = {}) {
50
- this.fn = fn;
51
- this.store = new store.Store(getDefaultAsyncQueuerState());
52
- this.asyncRetryers = /* @__PURE__ */ new Map();
53
- this.#timeoutIds = /* @__PURE__ */ new Set();
54
- this.setOptions = (newOptions) => {
55
- this.options = { ...this.options, ...newOptions };
56
- };
57
- this.#setState = (newState) => {
58
- this.store.setState((state) => {
59
- const combinedState = {
60
- ...state,
61
- ...newState
62
- };
63
- const { activeItems, items, isRunning } = combinedState;
64
- const size = items.length;
65
- const isFull = size >= (this.options.maxSize ?? Infinity);
66
- const isEmpty = size === 0;
67
- const isIdle = isRunning && isEmpty && activeItems.length === 0;
68
- const status = isIdle ? "idle" : isRunning ? "running" : "stopped";
69
- return {
70
- ...combinedState,
71
- isEmpty,
72
- isFull,
73
- isIdle,
74
- size,
75
- status
76
- };
77
- });
78
- eventClient.emitChange("AsyncQueuer", this);
79
- };
80
- this.#getWait = () => {
81
- return utils.parseFunctionOrValue(this.options.wait ?? 0, this);
82
- };
83
- this.#getConcurrency = () => {
84
- return utils.parseFunctionOrValue(this.options.concurrency ?? 1, this);
85
- };
86
- this.#tick = () => {
87
- if (!this.store.state.isRunning) {
88
- this.#setState({ pendingTick: false });
89
- return;
90
- }
91
- this.#setState({ pendingTick: true });
92
- this.#checkExpiredItems();
93
- const activeItems = this.store.state.activeItems;
94
- while (activeItems.length < this.#getConcurrency() && this.store.state.items.length > 0) {
95
- const nextItem = this.peekNextItem();
96
- if (!nextItem) {
97
- break;
98
- }
99
- activeItems.push(nextItem);
100
- this.#setState({
101
- activeItems
102
- });
103
- (async () => {
104
- const result = await this.execute();
105
- this.#setState({ lastResult: result });
106
- const wait = this.#getWait();
107
- if (wait > 0) {
108
- const timeoutId = setTimeout(() => this.#tick(), wait);
109
- this.#timeoutIds.add(timeoutId);
110
- return;
111
- }
112
- this.#tick();
113
- })();
114
- }
115
- this.#setState({ pendingTick: false });
116
- };
117
- this.addItem = (item, position = this.options.addItemsTo ?? "back", runOnItemsChange = true) => {
118
- this.#setState({
119
- addItemCount: this.store.state.addItemCount + 1
120
- });
121
- if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {
122
- this.#setState({
123
- rejectionCount: this.store.state.rejectionCount + 1
124
- });
125
- this.options.onReject?.(item, this);
126
- return false;
127
- }
128
- const priority = this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(item) : item.priority;
129
- const items = this.store.state.items;
130
- const itemTimestamps = this.store.state.itemTimestamps;
131
- if (priority !== void 0) {
132
- const insertIndex = items.findIndex((existing) => {
133
- const existingPriority = this.options.getPriority !== defaultOptions.getPriority ? this.options.getPriority(existing) : existing.priority;
134
- return existingPriority < priority;
135
- });
136
- if (insertIndex === -1) {
137
- items.push(item);
138
- itemTimestamps.push(Date.now());
139
- } else {
140
- items.splice(insertIndex, 0, item);
141
- itemTimestamps.splice(insertIndex, 0, Date.now());
142
- }
143
- } else {
144
- if (position === "front") {
145
- items.unshift(item);
146
- itemTimestamps.unshift(Date.now());
147
- } else {
148
- items.push(item);
149
- itemTimestamps.push(Date.now());
150
- }
151
- }
152
- this.#setState({
153
- items,
154
- itemTimestamps
155
- });
156
- if (runOnItemsChange) {
157
- this.options.onItemsChange?.(this);
158
- }
159
- if (this.store.state.isRunning && !this.store.state.pendingTick) {
160
- this.#tick();
161
- }
162
- return true;
163
- };
164
- this.getNextItem = (position = this.options.getItemsFrom ?? "front") => {
165
- const { items, itemTimestamps } = this.store.state;
166
- let item;
167
- if (this.options.getPriority !== defaultOptions.getPriority || position === "front") {
168
- item = items[0];
169
- if (item !== void 0) {
170
- this.#setState({
171
- items: items.slice(1),
172
- itemTimestamps: itemTimestamps.slice(1)
173
- });
174
- }
175
- } else {
176
- item = items[items.length - 1];
177
- if (item !== void 0) {
178
- this.#setState({
179
- items: items.slice(0, -1),
180
- itemTimestamps: itemTimestamps.slice(0, -1)
181
- });
182
- }
183
- }
184
- if (item !== void 0) {
185
- this.options.onItemsChange?.(this);
186
- }
187
- return item;
188
- };
189
- this.#getAllItems = () => {
190
- const items = this.peekAllItems();
191
- this.clear();
192
- return items;
193
- };
194
- this.execute = async (position) => {
195
- const item = this.getNextItem(position);
196
- if (item !== void 0) {
197
- const currentExecuteCount = this.store.state.executeCount + 1;
198
- this.#setState({
199
- executeCount: currentExecuteCount,
200
- isExecuting: true
201
- });
202
- try {
203
- const currentAsyncRetryer = new asyncRetryer.AsyncRetryer(this.fn, {
204
- ...this.options.asyncRetryerOptions,
205
- key: `${this.key}-retryer-${currentExecuteCount}`
206
- });
207
- this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer);
208
- const lastResult = await currentAsyncRetryer.execute(item);
209
- this.#setState({
210
- successCount: this.store.state.successCount + 1,
211
- lastResult
212
- });
213
- this.options.onSuccess?.(lastResult, item, this);
214
- } catch (error) {
215
- this.#setState({
216
- errorCount: this.store.state.errorCount + 1
217
- });
218
- this.options.onError?.(error, item, this);
219
- if (this.options.throwOnError) {
220
- throw error;
221
- }
222
- } finally {
223
- this.asyncRetryers.delete(currentExecuteCount);
224
- this.#setState({
225
- activeItems: this.store.state.activeItems.filter(
226
- (activeItem) => activeItem !== item
227
- ),
228
- isExecuting: false,
229
- settledCount: this.store.state.settledCount + 1
230
- });
231
- this.options.onSettled?.(item, this);
232
- }
233
- }
234
- return item;
235
- };
236
- this.flush = async (numberOfItems = this.store.state.items.length, position) => {
237
- this.#clearTimeouts();
238
- await Promise.all(
239
- Array.from({ length: numberOfItems }, () => this.execute(position))
240
- );
241
- };
242
- this.flushAsBatch = async (batchFunction) => {
243
- this.#clearTimeouts();
244
- const items = this.#getAllItems();
245
- await batchFunction(items);
246
- };
247
- this.#checkExpiredItems = () => {
248
- if ((this.options.expirationDuration ?? Infinity) === Infinity && this.options.getIsExpired === defaultOptions.getIsExpired) {
249
- return;
250
- }
251
- const now = Date.now();
252
- const expiredIndices = [];
253
- for (let i = 0; i < this.store.state.items.length; i++) {
254
- const timestamp = this.store.state.itemTimestamps[i];
255
- if (timestamp === void 0) continue;
256
- const item = this.store.state.items[i];
257
- if (item === void 0) continue;
258
- const isExpired = this.options.getIsExpired !== defaultOptions.getIsExpired ? this.options.getIsExpired(item, timestamp) : now - timestamp > (this.options.expirationDuration ?? Infinity);
259
- if (isExpired) {
260
- expiredIndices.push(i);
261
- }
262
- }
263
- for (let i = expiredIndices.length - 1; i >= 0; i--) {
264
- const index = expiredIndices[i];
265
- if (index === void 0) continue;
266
- const expiredItem = this.store.state.items[index];
267
- if (expiredItem === void 0) continue;
268
- const newItems = [...this.store.state.items];
269
- const newTimestamps = [...this.store.state.itemTimestamps];
270
- newItems.splice(index, 1);
271
- newTimestamps.splice(index, 1);
272
- this.#setState({
273
- items: newItems,
274
- itemTimestamps: newTimestamps,
275
- expirationCount: this.store.state.expirationCount + 1
276
- });
277
- this.options.onExpire?.(expiredItem, this);
278
- }
279
- if (expiredIndices.length > 0) {
280
- this.options.onItemsChange?.(this);
281
- }
282
- };
283
- this.peekNextItem = (position = "front") => {
284
- if (position === "front") {
285
- return this.store.state.items[0];
286
- }
287
- return this.store.state.items[this.store.state.items.length - 1];
288
- };
289
- this.peekAllItems = () => {
290
- return [...this.peekActiveItems(), ...this.peekPendingItems()];
291
- };
292
- this.peekActiveItems = () => {
293
- return [...this.store.state.activeItems];
294
- };
295
- this.peekPendingItems = () => {
296
- return [...this.store.state.items];
297
- };
298
- this.start = () => {
299
- this.#setState({ isRunning: true });
300
- if (!this.store.state.pendingTick && this.store.state.items.length > 0) {
301
- this.#tick();
302
- }
303
- };
304
- this.stop = () => {
305
- this.#clearTimeouts();
306
- this.#setState({ isRunning: false, pendingTick: false });
307
- };
308
- this.#clearTimeouts = () => {
309
- this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId));
310
- this.#timeoutIds.clear();
311
- };
312
- this.clear = () => {
313
- this.#setState({ items: [], itemTimestamps: [] });
314
- this.options.onItemsChange?.(this);
315
- };
316
- this.abort = () => {
317
- this.asyncRetryers.forEach((retryer) => retryer.abort());
318
- this.asyncRetryers.clear();
319
- this.#setState({
320
- isExecuting: false
321
- });
322
- };
323
- this.reset = () => {
324
- this.#setState(getDefaultAsyncQueuerState());
325
- this.options.onItemsChange?.(this);
326
- this.asyncRetryers.forEach((retryer) => retryer.reset());
327
- };
328
- this.key = initialOptions.key;
329
- this.options = {
330
- ...defaultOptions,
331
- ...initialOptions,
332
- throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
333
- };
334
- const isInitiallyRunning = this.options.initialState?.isRunning ?? this.options.started ?? true;
335
- this.#setState({
336
- ...this.options.initialState,
337
- isRunning: isInitiallyRunning
338
- });
339
- if (this.options.initialState?.items) {
340
- if (this.store.state.isRunning) {
341
- this.#tick();
342
- }
343
- } else {
344
- for (let i = 0; i < (this.options.initialItems?.length ?? 0); i++) {
345
- const item = this.options.initialItems[i];
346
- const isLast = i === (this.options.initialItems?.length ?? 0) - 1;
347
- this.addItem(item, this.options.addItemsTo ?? "back", isLast);
348
- }
349
- }
350
- eventClient.pacerEventClient.on("d-AsyncQueuer", (e) => {
351
- if (e.payload.key !== this.key) return;
352
- this.#setState(e.payload.store.state);
353
- this.setOptions(e.payload.options);
354
- });
355
- }
356
- #timeoutIds;
357
- #setState;
358
- #getWait;
359
- #getConcurrency;
360
- #tick;
361
- #getAllItems;
362
- #checkExpiredItems;
363
- #clearTimeouts;
364
- /**
365
- * Returns the AbortSignal for a specific execution.
366
- * If no executeCount is provided, returns the signal for the most recent execution.
367
- * Returns null if no execution is found or not currently executing.
368
- *
369
- * @param executeCount - Optional specific execution to get signal for
370
- * @example
371
- * ```typescript
372
- * const queuer = new AsyncQueuer(
373
- * async (item: string) => {
374
- * const signal = queuer.getAbortSignal()
375
- * if (signal) {
376
- * const response = await fetch(`/api/process/${item}`, { signal })
377
- * return response.json()
378
- * }
379
- * },
380
- * { concurrency: 2 }
381
- * )
382
- * ```
383
- */
384
- getAbortSignal(executeCount) {
385
- const count = executeCount ?? this.store.state.executeCount;
386
- const retryer = this.asyncRetryers.get(count);
387
- return retryer?.getAbortSignal() ?? null;
388
- }
389
- }
390
- function asyncQueue(fn, initialOptions) {
391
- const asyncQueuer = new AsyncQueuer(fn, initialOptions);
392
- return asyncQueuer.addItem;
393
- }
394
- exports.AsyncQueuer = AsyncQueuer;
395
- exports.asyncQueue = asyncQueue;
396
- exports.asyncQueuerOptions = asyncQueuerOptions;
397
- //# sourceMappingURL=async-queuer.cjs.map