@tanstack/pacer 0.16.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +165 -0
  2. package/dist/async-batcher.cjs +355 -0
  3. package/dist/async-batcher.cjs.map +1 -0
  4. package/dist/async-batcher.d.cts +344 -0
  5. package/dist/async-batcher.d.ts +344 -0
  6. package/dist/async-batcher.js +353 -0
  7. package/dist/async-batcher.js.map +1 -0
  8. package/dist/async-debouncer.cjs +340 -0
  9. package/dist/async-debouncer.cjs.map +1 -0
  10. package/dist/async-debouncer.d.cts +300 -0
  11. package/dist/async-debouncer.d.ts +300 -0
  12. package/dist/async-debouncer.js +338 -0
  13. package/dist/async-debouncer.js.map +1 -0
  14. package/dist/async-queuer.cjs +496 -0
  15. package/dist/async-queuer.cjs.map +1 -0
  16. package/dist/async-queuer.d.cts +440 -0
  17. package/dist/async-queuer.d.ts +440 -0
  18. package/dist/async-queuer.js +494 -0
  19. package/dist/async-queuer.js.map +1 -0
  20. package/dist/async-rate-limiter.cjs +381 -0
  21. package/dist/async-rate-limiter.cjs.map +1 -0
  22. package/dist/{cjs/async-rate-limiter.d.cts → async-rate-limiter.d.cts} +190 -185
  23. package/dist/{esm/async-rate-limiter.d.ts → async-rate-limiter.d.ts} +190 -185
  24. package/dist/async-rate-limiter.js +379 -0
  25. package/dist/async-rate-limiter.js.map +1 -0
  26. package/dist/async-retryer.cjs +378 -0
  27. package/dist/async-retryer.cjs.map +1 -0
  28. package/dist/async-retryer.d.cts +312 -0
  29. package/dist/async-retryer.d.ts +312 -0
  30. package/dist/async-retryer.js +376 -0
  31. package/dist/async-retryer.js.map +1 -0
  32. package/dist/async-throttler.cjs +362 -0
  33. package/dist/async-throttler.cjs.map +1 -0
  34. package/dist/async-throttler.d.cts +320 -0
  35. package/dist/async-throttler.d.ts +320 -0
  36. package/dist/async-throttler.js +360 -0
  37. package/dist/async-throttler.js.map +1 -0
  38. package/dist/batcher.cjs +194 -0
  39. package/dist/batcher.cjs.map +1 -0
  40. package/dist/batcher.d.cts +180 -0
  41. package/dist/batcher.d.ts +180 -0
  42. package/dist/batcher.js +193 -0
  43. package/dist/batcher.js.map +1 -0
  44. package/dist/debouncer.cjs +197 -0
  45. package/dist/debouncer.cjs.map +1 -0
  46. package/dist/debouncer.d.cts +167 -0
  47. package/dist/debouncer.d.ts +167 -0
  48. package/dist/debouncer.js +195 -0
  49. package/dist/debouncer.js.map +1 -0
  50. package/dist/event-client.cjs +20 -0
  51. package/dist/event-client.cjs.map +1 -0
  52. package/dist/event-client.d.cts +49 -0
  53. package/dist/event-client.d.ts +49 -0
  54. package/dist/event-client.js +19 -0
  55. package/dist/event-client.js.map +1 -0
  56. package/dist/index.cjs +49 -0
  57. package/dist/index.d.cts +15 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/{esm/index.js → index.js} +5 -41
  60. package/dist/queuer.cjs +393 -0
  61. package/dist/queuer.cjs.map +1 -0
  62. package/dist/queuer.d.cts +345 -0
  63. package/dist/queuer.d.ts +345 -0
  64. package/dist/queuer.js +391 -0
  65. package/dist/queuer.js.map +1 -0
  66. package/dist/rate-limiter.cjs +251 -0
  67. package/dist/rate-limiter.cjs.map +1 -0
  68. package/dist/{cjs/rate-limiter.d.cts → rate-limiter.d.cts} +113 -108
  69. package/dist/{esm/rate-limiter.d.ts → rate-limiter.d.ts} +113 -108
  70. package/dist/rate-limiter.js +249 -0
  71. package/dist/rate-limiter.js.map +1 -0
  72. package/dist/throttler.cjs +209 -0
  73. package/dist/throttler.cjs.map +1 -0
  74. package/dist/throttler.d.cts +207 -0
  75. package/dist/throttler.d.ts +207 -0
  76. package/dist/throttler.js +207 -0
  77. package/dist/throttler.js.map +1 -0
  78. package/dist/types.cjs +0 -0
  79. package/dist/types.d.cts +13 -0
  80. package/dist/types.d.ts +13 -0
  81. package/dist/types.js +1 -0
  82. package/dist/utils.cjs +13 -0
  83. package/dist/utils.cjs.map +1 -0
  84. package/dist/utils.d.cts +8 -0
  85. package/dist/utils.d.ts +8 -0
  86. package/dist/utils.js +11 -0
  87. package/dist/utils.js.map +1 -0
  88. package/package.json +39 -123
  89. package/dist/cjs/async-batcher.cjs +0 -222
  90. package/dist/cjs/async-batcher.cjs.map +0 -1
  91. package/dist/cjs/async-batcher.d.cts +0 -340
  92. package/dist/cjs/async-debouncer.cjs +0 -233
  93. package/dist/cjs/async-debouncer.cjs.map +0 -1
  94. package/dist/cjs/async-debouncer.d.cts +0 -295
  95. package/dist/cjs/async-queuer.cjs +0 -399
  96. package/dist/cjs/async-queuer.cjs.map +0 -1
  97. package/dist/cjs/async-queuer.d.cts +0 -435
  98. package/dist/cjs/async-rate-limiter.cjs +0 -254
  99. package/dist/cjs/async-rate-limiter.cjs.map +0 -1
  100. package/dist/cjs/async-retryer.cjs +0 -287
  101. package/dist/cjs/async-retryer.cjs.map +0 -1
  102. package/dist/cjs/async-retryer.d.cts +0 -308
  103. package/dist/cjs/async-throttler.cjs +0 -263
  104. package/dist/cjs/async-throttler.cjs.map +0 -1
  105. package/dist/cjs/async-throttler.d.cts +0 -315
  106. package/dist/cjs/batcher.cjs +0 -132
  107. package/dist/cjs/batcher.cjs.map +0 -1
  108. package/dist/cjs/batcher.d.cts +0 -176
  109. package/dist/cjs/debouncer.cjs +0 -139
  110. package/dist/cjs/debouncer.cjs.map +0 -1
  111. package/dist/cjs/debouncer.d.cts +0 -162
  112. package/dist/cjs/event-client.cjs +0 -20
  113. package/dist/cjs/event-client.cjs.map +0 -1
  114. package/dist/cjs/event-client.d.cts +0 -45
  115. package/dist/cjs/index.cjs +0 -51
  116. package/dist/cjs/index.cjs.map +0 -1
  117. package/dist/cjs/index.d.cts +0 -15
  118. package/dist/cjs/queuer.cjs +0 -308
  119. package/dist/cjs/queuer.cjs.map +0 -1
  120. package/dist/cjs/queuer.d.cts +0 -340
  121. package/dist/cjs/rate-limiter.cjs +0 -181
  122. package/dist/cjs/rate-limiter.cjs.map +0 -1
  123. package/dist/cjs/throttler.cjs +0 -153
  124. package/dist/cjs/throttler.cjs.map +0 -1
  125. package/dist/cjs/throttler.d.cts +0 -202
  126. package/dist/cjs/types.d.cts +0 -9
  127. package/dist/cjs/utils.cjs +0 -11
  128. package/dist/cjs/utils.cjs.map +0 -1
  129. package/dist/cjs/utils.d.cts +0 -3
  130. package/dist/esm/async-batcher.d.ts +0 -340
  131. package/dist/esm/async-batcher.js +0 -222
  132. package/dist/esm/async-batcher.js.map +0 -1
  133. package/dist/esm/async-debouncer.d.ts +0 -295
  134. package/dist/esm/async-debouncer.js +0 -233
  135. package/dist/esm/async-debouncer.js.map +0 -1
  136. package/dist/esm/async-queuer.d.ts +0 -435
  137. package/dist/esm/async-queuer.js +0 -399
  138. package/dist/esm/async-queuer.js.map +0 -1
  139. package/dist/esm/async-rate-limiter.js +0 -254
  140. package/dist/esm/async-rate-limiter.js.map +0 -1
  141. package/dist/esm/async-retryer.d.ts +0 -308
  142. package/dist/esm/async-retryer.js +0 -287
  143. package/dist/esm/async-retryer.js.map +0 -1
  144. package/dist/esm/async-throttler.d.ts +0 -315
  145. package/dist/esm/async-throttler.js +0 -263
  146. package/dist/esm/async-throttler.js.map +0 -1
  147. package/dist/esm/batcher.d.ts +0 -176
  148. package/dist/esm/batcher.js +0 -132
  149. package/dist/esm/batcher.js.map +0 -1
  150. package/dist/esm/debouncer.d.ts +0 -162
  151. package/dist/esm/debouncer.js +0 -139
  152. package/dist/esm/debouncer.js.map +0 -1
  153. package/dist/esm/event-client.d.ts +0 -45
  154. package/dist/esm/event-client.js +0 -20
  155. package/dist/esm/event-client.js.map +0 -1
  156. package/dist/esm/index.d.ts +0 -15
  157. package/dist/esm/index.js.map +0 -1
  158. package/dist/esm/queuer.d.ts +0 -340
  159. package/dist/esm/queuer.js +0 -308
  160. package/dist/esm/queuer.js.map +0 -1
  161. package/dist/esm/rate-limiter.js +0 -181
  162. package/dist/esm/rate-limiter.js.map +0 -1
  163. package/dist/esm/throttler.d.ts +0 -202
  164. package/dist/esm/throttler.js +0 -153
  165. package/dist/esm/throttler.js.map +0 -1
  166. package/dist/esm/types.d.ts +0 -9
  167. package/dist/esm/utils.d.ts +0 -3
  168. package/dist/esm/utils.js +0 -11
  169. package/dist/esm/utils.js.map +0 -1
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ <div align="center">
2
+ <img src="./media/header_pacer.png" >
3
+ </div>
4
+
5
+ <br />
6
+
7
+ <div align="center">
8
+ <a href="https://www.npmjs.com/package/@tanstack/pacer" target="\_parent">
9
+ <img alt="" src="https://img.shields.io/npm/dm/@tanstack/pacer.svg" alt="npm downloads" />
10
+ </a>
11
+ - <a href="https://github.com/TanStack/pacer" target="\_parent">
12
+ <img alt="" src="https://img.shields.io/github/stars/TanStack/pacer.svg?style=social&label=Star" alt="GitHub stars" />
13
+ </a>
14
+ <a href="https://bundlephobia.com/result?p=@tanstack/react-pacer@latest" target="\_parent">
15
+ <img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/react-pacer@latest" alt="Bundle size" />
16
+ </a>
17
+ </div>
18
+
19
+ <div align="center">
20
+ <a href="#badge">
21
+ <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
22
+ </a>
23
+ <a href="#badge">
24
+ <img src="https://img.shields.io/github/v/release/tanstack/pacer" alt="Release"/>
25
+ </a>
26
+ <a href="https://twitter.com/tan_stack">
27
+ <img src="https://img.shields.io/twitter/follow/tan_stack.svg?style=social" alt="Follow @TanStack"/>
28
+ </a>
29
+ </div>
30
+
31
+ <div align="center">
32
+
33
+ ### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
34
+ </div>
35
+
36
+ # TanStack Pacer
37
+
38
+ A lightweight timing and scheduling library for debouncing, throttling, rate limiting, queuing, and batching.
39
+
40
+ > [!NOTE]
41
+ > TanStack Pacer is currently mostly a client-side only library, but it is being designed to be able to potentially be used on the server-side as well.
42
+
43
+ - **Debouncing**
44
+ - Delay execution until after a period of inactivity for when you only care about the last execution in a sequence.
45
+ - Synchronous or Asynchronous Debounce utilities with promise support and error handling
46
+ - Control of leading, trailing, and enabled options
47
+ - **Throttling**
48
+ - Smoothly limit the rate at which a function can fire
49
+ - Synchronous or Asynchronous Throttle utilities with promise support and error handling
50
+ - Control of leading, trailing, and enabled options.
51
+ - **Rate Limiting**
52
+ - Limit the rate at which a function can fire over a period of time
53
+ - Synchronous or Asynchronous Rate Limiting utilities with promise support and error handling
54
+ - Fixed or Sliding Window variations of Rate Limiting
55
+ - **Queuing**
56
+ - Queue functions to be executed in a specific order
57
+ - Choose from FIFO, LIFO, and Priority queue implementations
58
+ - Control processing speed with configurable wait times or concurrency limits
59
+ - Manage queue execution with start/stop capabilities
60
+ - Expire items from the queue after a configurable duration
61
+ - **Batching**
62
+ - Chunk up multiple operations into larger batches to reduce total back-and-forth operations
63
+ - Batch by time period, batch size, whichever comes first, or a custom condition to trigger batch executions
64
+ - **Async or Sync Variations**
65
+ - Choose between synchronous and asynchronous versions of each utility
66
+ - Optional error, success, and settled handling for async variations
67
+ - Retry and Abort support for async variations
68
+ - **State Management**
69
+ - Uses TanStack Store under the hood for state management with fine-grained reactivity
70
+ - Easily integrate with your own state management library of choice
71
+ - Persist state to local or session storage for some utilities like rate limiting and queuing
72
+ - **Convenient Hooks**
73
+ - Reduce boilerplate code with pre-built hooks like `useDebouncedCallback`, `useThrottledValue`, and `useQueuedState`, and more.
74
+ - Multiple layers of abstraction to choose from depending on your use case.
75
+ - Works with each framework's default state management solutions, or with whatever custom state management library that you prefer.
76
+ - **Type Safety**
77
+ - Full type safety with TypeScript that makes sure that your functions will always be called with the correct arguments
78
+ - Generics for flexible and reusable utilities
79
+ - **Framework Adapters**
80
+ - React, Solid, and more
81
+ - **Tree Shaking**
82
+ - We, of course, get tree-shaking right for your applications by default, but we also provide extra deep imports for each utility, making it easier to embed these utilities into your libraries without increasing the bundle-phobia reports of your library.
83
+
84
+ ### <a href="https://tanstack.com/pacer">Read the docs →</b></a>
85
+
86
+ <br />
87
+
88
+ > [!NOTE]
89
+ > You may know **TanSack Pacer** by our adapter names, too!
90
+ >
91
+ > - [**React Pacer**](https://tanstack.com/pacer/latest/docs/framework/react/react-pacer)
92
+ > - [**Preact Pacer**](https://tanstack.com/pacer/latest/docs/framework/preact/preact-pacer)
93
+ > - [**Solid Pacer**](https://tanstack.com/pacer/latest/docs/framework/solid/solid-pacer)
94
+ > - Angular Pacer - needs a contributor!
95
+ > - Svelte Pacer - needs a contributor!
96
+ > - Vue Pacer - needs a contributor!
97
+
98
+ ## Get Involved
99
+
100
+ - We welcome issues and pull requests!
101
+ - Participate in [GitHub discussions](https://github.com/TanStack/pacer/discussions)
102
+ - Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ)
103
+ - See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions
104
+
105
+ ## Partners
106
+
107
+ <table align="center">
108
+ <tr>
109
+ <td>
110
+ <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
111
+ <picture>
112
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
113
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
114
+ <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
115
+ </picture>
116
+ </a>
117
+ </td>
118
+ <td>
119
+ <a href="https://www.cloudflare.com?utm_source=tanstack">
120
+ <picture>
121
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
122
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
123
+ <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
124
+ </picture>
125
+ </a>
126
+ </td>
127
+ <td>
128
+ <a href="https://www.unkey.com/?utm_source=tanstack">
129
+ <picture>
130
+ <source media="(prefers-color-scheme: dark)" srcset="./media/unkey_dark.svg" height="60" />
131
+ <source media="(prefers-color-scheme: light)" srcset="./media/unkey_logo.svg" height="60" />
132
+ <img src="./media/unkey_logo.svg" height="60" alt="Unkey"/>
133
+ </picture>
134
+ </a>
135
+ </td>
136
+ </tr>
137
+ </table>
138
+
139
+ <div align="center">
140
+ <img src="./media/partner_logo.svg" alt="Pacer & you?" height="65">
141
+ <p>
142
+ We're looking for TanStack Pacer Partners to join our mission! Partner with us to push the boundaries of TanStack Pacer and build amazing things together.
143
+ </p>
144
+ <a href="mailto:partners@tanstack.com?subject=TanStack Pacer Partnership"><b>LET'S CHAT</b></a>
145
+ </div>
146
+
147
+ </div>
148
+
149
+ ## Explore the TanStack Ecosystem
150
+
151
+ - <a href="https://github.com/tanstack/config"><b>TanStack Config</b></a> – Tooling for JS/TS packages
152
+ - <a href="https://github.com/tanstack/db"><b>TanStack DB</b></a> – Reactive sync client store
153
+ - <a href="https://github.com/tanstack/devtools"><b>TanStack DevTools</b></a> – Unified devtools panel
154
+ - <a href="https://github.com/tanstack/form"><b>TanStack Form</b></a> – Type‑safe form state
155
+ - <a href="https://github.com/tanstack/query"><b>TanStack Query</b></a> – Async state & caching
156
+ - <a href="https://github.com/tanstack/ranger"><b>TanStack Ranger</b></a> – Range & slider primitives
157
+ - <a href="https://github.com/tanstack/router"><b>TanStack Router</b></a> – Type‑safe routing, caching & URL state
158
+ - <a href="https://github.com/tanstack/router"><b>TanStack Start</b></a> – Full‑stack SSR & streaming
159
+ - <a href="https://github.com/tanstack/store"><b>TanStack Store</b></a> – Reactive data store
160
+ - <a href="https://github.com/tanstack/table"><b>TanStack Table</b></a> – Headless datagrids
161
+ - <a href="https://github.com/tanstack/virtual"><b>TanStack Virtual</b></a> – Virtualized rendering
162
+
163
+ … and more at <a href="https://tanstack.com"><b>TanStack.com »</b></a>
164
+
165
+ <!-- USE THE FORCE LUKE -->
@@ -0,0 +1,355 @@
1
+ const require_utils = require('./utils.cjs');
2
+ const require_event_client = require('./event-client.cjs');
3
+ const require_async_retryer = require('./async-retryer.cjs');
4
+ let __tanstack_store = require("@tanstack/store");
5
+
6
+ //#region src/async-batcher.ts
7
+ function getDefaultAsyncBatcherState() {
8
+ return {
9
+ errorCount: 0,
10
+ executeCount: 0,
11
+ failedItems: [],
12
+ isEmpty: true,
13
+ isExecuting: false,
14
+ isPending: false,
15
+ items: [],
16
+ lastResult: void 0,
17
+ settleCount: 0,
18
+ size: 0,
19
+ status: "idle",
20
+ successCount: 0,
21
+ totalItemsProcessed: 0,
22
+ totalItemsFailed: 0
23
+ };
24
+ }
25
+ /**
26
+ * Utility function for sharing common `AsyncBatcherOptions` options between different `AsyncBatcher` instances.
27
+ *
28
+ */
29
+ function asyncBatcherOptions(options) {
30
+ return options;
31
+ }
32
+ const defaultOptions = {
33
+ asyncRetryerOptions: { maxAttempts: 1 },
34
+ getShouldExecute: () => false,
35
+ maxSize: Infinity,
36
+ started: true,
37
+ throwOnError: true,
38
+ wait: Infinity
39
+ };
40
+ /**
41
+ * A class that collects items and processes them in batches asynchronously.
42
+ *
43
+ * Async vs Sync Versions:
44
+ * The async version provides advanced features over the sync Batcher:
45
+ * - Returns promises that can be awaited for batch results
46
+ * - Built-in retry support via AsyncRetryer integration
47
+ * - Abort support to cancel in-flight batch executions
48
+ * - Cancel support to prevent pending batches from starting
49
+ * - Comprehensive error handling with onError callbacks and throwOnError control
50
+ * - Detailed execution tracking (success/error/settle counts)
51
+ *
52
+ * The sync Batcher is lighter weight and simpler when you don't need async features,
53
+ * return values, or execution control.
54
+ *
55
+ * What is Batching?
56
+ * Batching is a technique for grouping multiple operations together to be processed as a single unit.
57
+ *
58
+ * The AsyncBatcher provides a flexible way to implement async batching with configurable:
59
+ * - Maximum batch size (number of items per batch)
60
+ * - Time-based batching (process after X milliseconds)
61
+ * - Custom batch processing logic via getShouldExecute
62
+ * - Event callbacks for monitoring batch operations
63
+ * - Error handling for failed batch operations
64
+ *
65
+ * Error Handling:
66
+ * - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher instance
67
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
68
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
69
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
70
+ * - The error state can be checked using the AsyncBatcher instance
71
+ *
72
+ * State Management:
73
+ * - Uses TanStack Store for reactive state management
74
+ * - Use `initialState` to provide initial state values when creating the async batcher
75
+ * - Use `onSuccess` callback to react to successful batch execution and implement custom logic
76
+ * - Use `onError` callback to react to batch execution errors and implement custom error handling
77
+ * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic
78
+ * - Use `onExecute` callback to react to batch execution and implement custom logic
79
+ * - Use `onItemsChange` callback to react to items being added or removed from the batcher
80
+ * - The state includes total items processed, success/error counts, and execution status
81
+ * - State can be accessed via `asyncBatcher.store.state` when using the class directly
82
+ * - When using framework adapters (React/Solid), state is accessed from `asyncBatcher.state`
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const batcher = new AsyncBatcher<number>(
87
+ * async (items) => {
88
+ * const result = await processItems(items);
89
+ * console.log('Processing batch:', items);
90
+ * return result;
91
+ * },
92
+ * {
93
+ * maxSize: 5,
94
+ * wait: 2000,
95
+ * onSuccess: (result) => console.log('Batch succeeded:', result),
96
+ * onError: (error) => console.error('Batch failed:', error)
97
+ * }
98
+ * );
99
+ *
100
+ * batcher.addItem(1);
101
+ * batcher.addItem(2);
102
+ * // After 2 seconds or when 5 items are added, whichever comes first,
103
+ * // the batch will be processed and the result will be available
104
+ * // batcher.execute() // manually trigger a batch
105
+ * ```
106
+ */
107
+ var AsyncBatcher = class {
108
+ #timeoutId = null;
109
+ constructor(fn, initialOptions) {
110
+ this.fn = fn;
111
+ this.store = new __tanstack_store.Store(getDefaultAsyncBatcherState());
112
+ this.asyncRetryers = /* @__PURE__ */ new Map();
113
+ this.setOptions = (newOptions) => {
114
+ this.options = {
115
+ ...this.options,
116
+ ...newOptions
117
+ };
118
+ };
119
+ this.addItem = async (item) => {
120
+ this.#setState({
121
+ items: [...this.store.state.items, item],
122
+ isPending: this.options.wait !== Infinity
123
+ });
124
+ this.options.onItemsChange?.(this);
125
+ if (this.store.state.items.length >= this.options.maxSize || this.options.getShouldExecute(this.store.state.items, this)) return await this.#execute();
126
+ else if (this.options.wait !== Infinity) {
127
+ this.#clearTimeout();
128
+ this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait());
129
+ await new Promise((resolve) => setTimeout(resolve, this.#getWait()));
130
+ }
131
+ };
132
+ this.flush = async () => {
133
+ this.#clearTimeout();
134
+ return await this.#execute();
135
+ };
136
+ this.peekAllItems = () => {
137
+ return [...this.store.state.items];
138
+ };
139
+ this.peekFailedItems = () => {
140
+ return [...this.store.state.failedItems];
141
+ };
142
+ this.clear = () => {
143
+ this.#setState({
144
+ items: [],
145
+ failedItems: [],
146
+ isPending: false
147
+ });
148
+ };
149
+ this.abort = () => {
150
+ this.asyncRetryers.forEach((retryer) => retryer.abort());
151
+ this.asyncRetryers.clear();
152
+ this.#setState({ isExecuting: false });
153
+ };
154
+ this.cancel = () => {
155
+ this.#clearTimeout();
156
+ this.#setState({ isPending: false });
157
+ };
158
+ this.reset = () => {
159
+ this.#setState(getDefaultAsyncBatcherState());
160
+ this.options.onItemsChange?.(this);
161
+ this.asyncRetryers.forEach((retryer) => retryer.reset());
162
+ };
163
+ this.key = initialOptions.key;
164
+ this.options = {
165
+ ...defaultOptions,
166
+ ...initialOptions,
167
+ throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
168
+ };
169
+ this.#setState(this.options.initialState ?? {});
170
+ if (this.key) require_event_client.pacerEventClient.on("d-AsyncBatcher", (event) => {
171
+ if (event.payload.key !== this.key) return;
172
+ this.#setState(event.payload.store.state);
173
+ this.setOptions(event.payload.options);
174
+ });
175
+ }
176
+ #setState = (newState) => {
177
+ this.store.setState((state) => {
178
+ const combinedState = {
179
+ ...state,
180
+ ...newState
181
+ };
182
+ const { isExecuting, isPending, items } = combinedState;
183
+ const size = items.length;
184
+ const isEmpty = size === 0;
185
+ return {
186
+ ...combinedState,
187
+ isEmpty,
188
+ size,
189
+ status: isExecuting ? "executing" : isPending ? "pending" : isEmpty ? "idle" : "populated"
190
+ };
191
+ });
192
+ require_event_client.emitChange("AsyncBatcher", this);
193
+ };
194
+ #getWait = () => {
195
+ return require_utils.parseFunctionOrValue(this.options.wait, this);
196
+ };
197
+ /**
198
+ * Processes the current batch of items asynchronously.
199
+ * This method will automatically be triggered if the batcher is running and any of these conditions are met:
200
+ * - The number of items reaches maxSize
201
+ * - The wait duration has elapsed
202
+ * - The getShouldExecute function returns true upon adding an item
203
+ *
204
+ * You can also call this method manually to process the current batch at any time.
205
+ *
206
+ * @returns A promise that resolves with the result of the batch function, or undefined if an error occurred and was handled by onError
207
+ * @throws The error from the batch function if no onError handler is configured or throwOnError is true
208
+ */
209
+ #execute = async () => {
210
+ if (this.store.state.items.length === 0) return;
211
+ const currentExecuteCount = this.store.state.executeCount + 1;
212
+ const batch = this.peekAllItems();
213
+ this.clear();
214
+ this.options.onItemsChange?.(this);
215
+ this.#setState({
216
+ isExecuting: true,
217
+ executeCount: currentExecuteCount
218
+ });
219
+ try {
220
+ const currentAsyncRetryer = new require_async_retryer.AsyncRetryer(this.fn, this.options.asyncRetryerOptions);
221
+ this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer);
222
+ const result = await currentAsyncRetryer.execute(batch);
223
+ this.#setState({
224
+ totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length,
225
+ lastResult: result,
226
+ successCount: this.store.state.successCount + 1
227
+ });
228
+ this.options.onSuccess?.(result, batch, this);
229
+ return result;
230
+ } catch (error) {
231
+ this.#setState({
232
+ errorCount: this.store.state.errorCount + 1,
233
+ failedItems: [...this.store.state.failedItems, ...batch],
234
+ totalItemsFailed: this.store.state.totalItemsFailed + batch.length
235
+ });
236
+ this.options.onError?.(error, batch, this);
237
+ if (this.options.throwOnError) throw error;
238
+ return;
239
+ } finally {
240
+ this.asyncRetryers.delete(currentExecuteCount);
241
+ this.#setState({
242
+ isExecuting: false,
243
+ settleCount: this.store.state.settleCount + 1
244
+ });
245
+ this.options.onSettled?.(batch, this);
246
+ }
247
+ };
248
+ #clearTimeout = () => {
249
+ if (this.#timeoutId) {
250
+ clearTimeout(this.#timeoutId);
251
+ this.#timeoutId = null;
252
+ }
253
+ };
254
+ /**
255
+ * Returns the AbortSignal for a specific execution.
256
+ * If no executeCount is provided, returns the signal for the most recent execution.
257
+ * Returns null if no execution is found or not currently executing.
258
+ *
259
+ * @param executeCount - Optional specific execution to get signal for
260
+ * @example
261
+ * ```typescript
262
+ * const batcher = new AsyncBatcher(
263
+ * async (items: string[]) => {
264
+ * const signal = batcher.getAbortSignal()
265
+ * if (signal) {
266
+ * const response = await fetch('/api/batch', {
267
+ * method: 'POST',
268
+ * body: JSON.stringify(items),
269
+ * signal
270
+ * })
271
+ * return response.json()
272
+ * }
273
+ * },
274
+ * { maxSize: 10, wait: 100 }
275
+ * )
276
+ * ```
277
+ */
278
+ getAbortSignal(executeCount) {
279
+ const count = executeCount ?? this.store.state.executeCount;
280
+ return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
281
+ }
282
+ };
283
+ /**
284
+ * Creates an async batcher that processes items in batches.
285
+ *
286
+ * Async vs Sync Versions:
287
+ * The async version provides advanced features over the sync batch function:
288
+ * - Returns promises that can be awaited for batch results
289
+ * - Built-in retry support via AsyncRetryer integration
290
+ * - Abort support to cancel in-flight batch executions
291
+ * - Cancel support to prevent pending batches from starting
292
+ * - Comprehensive error handling with onError callbacks and throwOnError control
293
+ * - Detailed execution tracking (success/error/settle counts)
294
+ *
295
+ * The sync batch function is lighter weight and simpler when you don't need async features,
296
+ * return values, or execution control.
297
+ *
298
+ * What is Batching?
299
+ * Batching is a technique for grouping multiple operations together to be processed as a single unit.
300
+ *
301
+ * Configuration Options:
302
+ * - `maxSize`: Maximum number of items per batch (default: Infinity)
303
+ * - `wait`: Time to wait before processing batch (default: Infinity)
304
+ * - `getShouldExecute`: Custom logic to trigger batch processing
305
+ * - `asyncRetryerOptions`: Configure retry behavior for batch executions
306
+ * - `started`: Whether to start processing immediately (default: true)
307
+ *
308
+ * Error Handling:
309
+ * - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher instance
310
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
311
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
312
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
313
+ * - The error state can be checked using the underlying AsyncBatcher instance
314
+ *
315
+ * State Management:
316
+ * - Uses TanStack Store for reactive state management
317
+ * - Use `initialState` to provide initial state values when creating the async batcher
318
+ * - Use `onSuccess` callback to react to successful batch execution and implement custom logic
319
+ * - Use `onError` callback to react to batch execution errors and implement custom error handling
320
+ * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic
321
+ * - Use `onItemsChange` callback to react to items being added or removed from the batcher
322
+ * - The state includes total items processed, success/error counts, and execution status
323
+ * - State can be accessed via the underlying AsyncBatcher instance's `store.state` property
324
+ * - When using framework adapters (React/Solid), state is accessed from the hook's state property
325
+ *
326
+ * @example
327
+ * ```ts
328
+ * const batchItems = asyncBatch<number>(
329
+ * async (items) => {
330
+ * const result = await processApiCall(items);
331
+ * console.log('Processing:', items);
332
+ * return result;
333
+ * },
334
+ * {
335
+ * maxSize: 3,
336
+ * wait: 1000,
337
+ * onSuccess: (result) => console.log('Batch succeeded:', result),
338
+ * onError: (error) => console.error('Batch failed:', error)
339
+ * }
340
+ * );
341
+ *
342
+ * batchItems(1);
343
+ * batchItems(2);
344
+ * batchItems(3); // Triggers batch processing
345
+ * ```
346
+ */
347
+ function asyncBatch(fn, options) {
348
+ return new AsyncBatcher(fn, options).addItem;
349
+ }
350
+
351
+ //#endregion
352
+ exports.AsyncBatcher = AsyncBatcher;
353
+ exports.asyncBatch = asyncBatch;
354
+ exports.asyncBatcherOptions = asyncBatcherOptions;
355
+ //# sourceMappingURL=async-batcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-batcher.cjs","names":["defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks<any>","fn: (items: Array<TValue>) => Promise<any>","Store","#setState","#execute","#clearTimeout","#timeoutId","#getWait","parseFunctionOrValue","AsyncRetryer"],"sources":["../src/async-batcher.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { AsyncRetryer } from './async-retryer'\nimport { parseFunctionOrValue } from './utils'\nimport { emitChange, pacerEventClient } from './event-client'\nimport type { AsyncRetryerOptions } from './async-retryer'\nimport type { OptionalKeys } from './types'\n\nexport interface AsyncBatcherState<TValue> {\n /**\n * Number of batch executions that have resulted in errors\n */\n errorCount: number\n /**\n * Number of batch executions that have been executed\n */\n executeCount: number\n /**\n * Array of items that failed during batch processing\n */\n failedItems: Array<TValue>\n /**\n * Whether the batcher has no items to process (items array is empty)\n */\n isEmpty: boolean\n /**\n * Whether a batch is currently being processed asynchronously\n */\n isExecuting: boolean\n /**\n * Whether the batcher is waiting for the timeout to trigger batch processing\n */\n isPending: boolean\n /**\n * Array of items currently queued for batch processing\n */\n items: Array<TValue>\n /**\n * The result from the most recent batch execution\n */\n lastResult: any\n /**\n * Number of batch executions that have completed (either successfully or with errors)\n */\n settleCount: number\n /**\n * Number of items currently in the batch queue\n */\n size: number\n /**\n * Current processing status - 'idle' when not processing, 'pending' when waiting for timeout, 'executing' when processing, 'populated' when items are present, but no wait is configured\n */\n status: 'idle' | 'pending' | 'executing' | 'populated'\n /**\n * Number of batch executions that have completed successfully\n */\n successCount: number\n /**\n * Total number of items that have failed processing across all batches\n */\n totalItemsFailed: number\n /**\n * Total number of items that have been processed across all batches\n */\n totalItemsProcessed: number\n}\n\nfunction getDefaultAsyncBatcherState<TValue>(): AsyncBatcherState<TValue> {\n return {\n errorCount: 0,\n executeCount: 0,\n failedItems: [],\n isEmpty: true,\n isExecuting: false,\n isPending: false,\n items: [],\n lastResult: undefined,\n settleCount: 0,\n size: 0,\n status: 'idle',\n successCount: 0,\n totalItemsProcessed: 0,\n totalItemsFailed: 0,\n }\n}\n\n/**\n * Options for configuring an AsyncBatcher instance\n */\nexport interface AsyncBatcherOptions<TValue> {\n /**\n * Options for configuring the underlying async retryer\n */\n asyncRetryerOptions?: AsyncRetryerOptions<\n (items: Array<TValue>) => Promise<any>\n >\n /**\n * Custom function to determine if a batch should be processed\n * Return true to process the batch immediately\n */\n getShouldExecute?: (\n items: Array<TValue>,\n batcher: AsyncBatcher<TValue>,\n ) => boolean\n /**\n * Initial state for the async batcher\n */\n initialState?: Partial<AsyncBatcherState<TValue>>\n /**\n * Optional key to identify this async batcher instance.\n * If provided, the async batcher will be identified by this key in the devtools and PacerProvider if applicable.\n */\n key?: string\n /**\n * Maximum number of items in a batch\n * @default Infinity\n */\n maxSize?: number\n /**\n * Optional error handler for when the batch function throws.\n * If provided, the handler will be called with the error, the batch of items that failed, and batcher instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (\n error: Error,\n batch: Array<TValue>,\n batcher: AsyncBatcher<TValue>,\n ) => void\n /**\n * Callback fired after items are added to the batcher\n */\n onItemsChange?: (batcher: AsyncBatcher<TValue>) => void\n /**\n * Optional callback to call when a batch is settled (completed or failed)\n */\n onSettled?: (batch: Array<TValue>, batcher: AsyncBatcher<TValue>) => void\n /**\n * Optional callback to call when a batch succeeds\n */\n onSuccess?: (\n result: any,\n batch: Array<TValue>,\n batcher: AsyncBatcher<TValue>,\n ) => void\n /**\n * Whether the batcher should start processing immediately\n * @default true\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 * Maximum time in milliseconds to wait before processing a batch.\n * If the wait duration has elapsed, the batch will be processed.\n * If not provided, the batch will not be triggered by a timeout.\n * @default Infinity\n */\n wait?: number | ((asyncBatcher: AsyncBatcher<TValue>) => number)\n}\n\n/**\n * Utility function for sharing common `AsyncBatcherOptions` options between different `AsyncBatcher` instances.\n *\n */\nexport function asyncBatcherOptions<\n TValue = any,\n TOptions extends Partial<AsyncBatcherOptions<TValue>> = Partial<\n AsyncBatcherOptions<TValue>\n >,\n>(options: TOptions): TOptions {\n return options\n}\n\ntype AsyncBatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<\n Required<AsyncBatcherOptions<TValue>>,\n | 'initialState'\n | 'onError'\n | 'onItemsChange'\n | 'onSettled'\n | 'onSuccess'\n | 'key'\n>\n\nconst defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks<any> = {\n asyncRetryerOptions: {\n maxAttempts: 1,\n },\n getShouldExecute: () => false,\n maxSize: Infinity,\n started: true,\n throwOnError: true,\n wait: Infinity,\n}\n\n/**\n * A class that collects items and processes them in batches asynchronously.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync Batcher:\n * - Returns promises that can be awaited for batch results\n * - Built-in retry support via AsyncRetryer integration\n * - Abort support to cancel in-flight batch executions\n * - Cancel support to prevent pending batches from starting\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n *\n * The sync Batcher is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Batching?\n * Batching is a technique for grouping multiple operations together to be processed as a single unit.\n *\n * The AsyncBatcher provides a flexible way to implement async batching with configurable:\n * - Maximum batch size (number of items per batch)\n * - Time-based batching (process after X milliseconds)\n * - Custom batch processing logic via getShouldExecute\n * - Event callbacks for monitoring batch operations\n * - Error handling for failed batch operations\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher 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 AsyncBatcher 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 batcher\n * - Use `onSuccess` callback to react to successful batch execution and implement custom logic\n * - Use `onError` callback to react to batch execution errors and implement custom error handling\n * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic\n * - Use `onExecute` callback to react to batch execution and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the batcher\n * - The state includes total items processed, success/error counts, and execution status\n * - State can be accessed via `asyncBatcher.store.state` when using the class directly\n * - When using framework adapters (React/Solid), state is accessed from `asyncBatcher.state`\n *\n * @example\n * ```ts\n * const batcher = new AsyncBatcher<number>(\n * async (items) => {\n * const result = await processItems(items);\n * console.log('Processing batch:', items);\n * return result;\n * },\n * {\n * maxSize: 5,\n * wait: 2000,\n * onSuccess: (result) => console.log('Batch succeeded:', result),\n * onError: (error) => console.error('Batch failed:', error)\n * }\n * );\n *\n * batcher.addItem(1);\n * batcher.addItem(2);\n * // After 2 seconds or when 5 items are added, whichever comes first,\n * // the batch will be processed and the result will be available\n * // batcher.execute() // manually trigger a batch\n * ```\n */\nexport class AsyncBatcher<TValue> {\n readonly store: Store<Readonly<AsyncBatcherState<TValue>>> = new Store(\n getDefaultAsyncBatcherState<TValue>(),\n )\n key: string | undefined\n options: AsyncBatcherOptionsWithOptionalCallbacks<TValue>\n asyncRetryers = new Map<\n number,\n AsyncRetryer<(items: Array<TValue>) => Promise<any>>\n >()\n #timeoutId: NodeJS.Timeout | null = null\n\n constructor(\n public fn: (items: Array<TValue>) => Promise<any>,\n initialOptions: AsyncBatcherOptions<TValue>,\n ) {\n this.key = initialOptions.key\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n this.#setState(this.options.initialState ?? {})\n\n if (this.key) {\n pacerEventClient.on('d-AsyncBatcher', (event) => {\n if (event.payload.key !== this.key) return\n this.#setState(event.payload.store.state)\n this.setOptions(event.payload.options)\n })\n }\n }\n\n /**\n * Updates the async batcher options\n */\n setOptions = (newOptions: Partial<AsyncBatcherOptions<TValue>>): void => {\n this.options = { ...this.options, ...newOptions }\n }\n\n #setState = (newState: Partial<AsyncBatcherState<TValue>>): void => {\n this.store.setState((state) => {\n const combinedState = {\n ...state,\n ...newState,\n }\n const { isExecuting, isPending, items } = combinedState\n const size = items.length\n const isEmpty = size === 0\n return {\n ...combinedState,\n isEmpty,\n size,\n status: isExecuting\n ? 'executing'\n : isPending\n ? 'pending'\n : isEmpty\n ? 'idle'\n : 'populated',\n }\n })\n emitChange('AsyncBatcher', this)\n }\n\n #getWait = (): number => {\n return parseFunctionOrValue(this.options.wait, this)\n }\n\n /**\n * Adds an item to the async batcher\n * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed\n *\n * @returns The result from the batch function, or undefined if an error occurred and was handled by onError\n *\n * @throws The error from the batch function if no onError handler is configured or throwOnError is true\n */\n addItem = async (item: TValue): Promise<any> => {\n this.#setState({\n items: [...this.store.state.items, item],\n isPending: this.options.wait !== Infinity,\n })\n this.options.onItemsChange?.(this)\n\n const shouldProcess =\n this.store.state.items.length >= this.options.maxSize ||\n this.options.getShouldExecute(this.store.state.items, this)\n\n if (shouldProcess) {\n return await this.#execute()\n } else if (this.options.wait !== Infinity) {\n this.#clearTimeout() // clear any pending timeout to replace it with a new one\n this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait())\n await new Promise((resolve) => setTimeout(resolve, this.#getWait()))\n }\n }\n\n /**\n * Processes the current batch of items asynchronously.\n * This method will automatically be triggered if the batcher is running and any of these conditions are met:\n * - The number of items reaches maxSize\n * - The wait duration has elapsed\n * - The getShouldExecute function returns true upon adding an item\n *\n * You can also call this method manually to process the current batch at any time.\n *\n * @returns A promise that resolves with the result of the batch function, or undefined if an error occurred and was handled by onError\n * @throws The error from the batch function if no onError handler is configured or throwOnError is true\n */\n #execute = async (): Promise<any> => {\n if (this.store.state.items.length === 0) {\n return undefined\n }\n\n const currentExecuteCount = this.store.state.executeCount + 1\n const batch = this.peekAllItems() // copy of the items to be processed (to prevent race conditions)\n this.clear() // Clear items before processing to prevent race conditions\n this.options.onItemsChange?.(this)\n\n this.#setState({ isExecuting: true, executeCount: currentExecuteCount })\n\n try {\n const currentAsyncRetryer = new AsyncRetryer(\n this.fn,\n this.options.asyncRetryerOptions,\n )\n this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer)\n const result = await currentAsyncRetryer.execute(batch) // EXECUTE\n this.#setState({\n totalItemsProcessed:\n this.store.state.totalItemsProcessed + batch.length,\n lastResult: result,\n successCount: this.store.state.successCount + 1,\n })\n this.options.onSuccess?.(result, batch, this)\n return result\n } catch (error) {\n this.#setState({\n errorCount: this.store.state.errorCount + 1,\n failedItems: [...this.store.state.failedItems, ...batch],\n totalItemsFailed: this.store.state.totalItemsFailed + batch.length,\n })\n this.options.onError?.(error as Error, batch, this)\n if (this.options.throwOnError) {\n throw error\n }\n return undefined\n } finally {\n this.asyncRetryers.delete(currentExecuteCount) // dispose retryer\n this.#setState({\n isExecuting: false,\n settleCount: this.store.state.settleCount + 1,\n })\n this.options.onSettled?.(batch, this)\n }\n }\n\n /**\n * Processes the current batch of items immediately\n */\n flush = async (): Promise<any> => {\n this.#clearTimeout() // clear any pending timeout\n return await this.#execute()\n }\n\n /**\n * Returns a copy of all items in the async batcher\n */\n peekAllItems = (): Array<TValue> => {\n return [...this.store.state.items]\n }\n\n peekFailedItems = (): Array<TValue> => {\n return [...this.store.state.failedItems]\n }\n\n #clearTimeout = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId)\n this.#timeoutId = null\n }\n }\n\n /**\n * Removes all items from the async batcher\n */\n clear = (): void => {\n this.#setState({ items: [], failedItems: [], isPending: false })\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 batcher = new AsyncBatcher(\n * async (items: string[]) => {\n * const signal = batcher.getAbortSignal()\n * if (signal) {\n * const response = await fetch('/api/batch', {\n * method: 'POST',\n * body: JSON.stringify(items),\n * signal\n * })\n * return response.json()\n * }\n * },\n * { maxSize: 10, wait: 100 }\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 cancel any pending execution that have not started yet.\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 * Cancels any pending execution that have not started yet.\n * Does NOT abort any execution already in progress.\n * Does NOT clear out the items.\n */\n cancel = (): void => {\n this.#clearTimeout()\n this.#setState({\n isPending: false,\n })\n }\n\n /**\n * Resets the async batcher state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncBatcherState<TValue>())\n this.options.onItemsChange?.(this)\n this.asyncRetryers.forEach((retryer) => retryer.reset())\n }\n}\n\n/**\n * Creates an async batcher that processes items in batches.\n *\n * Async vs Sync Versions:\n * The async version provides advanced features over the sync batch function:\n * - Returns promises that can be awaited for batch results\n * - Built-in retry support via AsyncRetryer integration\n * - Abort support to cancel in-flight batch executions\n * - Cancel support to prevent pending batches from starting\n * - Comprehensive error handling with onError callbacks and throwOnError control\n * - Detailed execution tracking (success/error/settle counts)\n *\n * The sync batch function is lighter weight and simpler when you don't need async features,\n * return values, or execution control.\n *\n * What is Batching?\n * Batching is a technique for grouping multiple operations together to be processed as a single unit.\n *\n * Configuration Options:\n * - `maxSize`: Maximum number of items per batch (default: Infinity)\n * - `wait`: Time to wait before processing batch (default: Infinity)\n * - `getShouldExecute`: Custom logic to trigger batch processing\n * - `asyncRetryerOptions`: Configure retry behavior for batch executions\n * - `started`: Whether to start processing immediately (default: true)\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher 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 AsyncBatcher 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 batcher\n * - Use `onSuccess` callback to react to successful batch execution and implement custom logic\n * - Use `onError` callback to react to batch execution errors and implement custom error handling\n * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic\n * - Use `onItemsChange` callback to react to items being added or removed from the batcher\n * - The state includes total items processed, success/error counts, and execution status\n * - State can be accessed via the underlying AsyncBatcher 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 batchItems = asyncBatch<number>(\n * async (items) => {\n * const result = await processApiCall(items);\n * console.log('Processing:', items);\n * return result;\n * },\n * {\n * maxSize: 3,\n * wait: 1000,\n * onSuccess: (result) => console.log('Batch succeeded:', result),\n * onError: (error) => console.error('Batch failed:', error)\n * }\n * );\n *\n * batchItems(1);\n * batchItems(2);\n * batchItems(3); // Triggers batch processing\n * ```\n */\nexport function asyncBatch<TValue>(\n fn: (items: Array<TValue>) => Promise<any>,\n options: AsyncBatcherOptions<TValue>,\n) {\n const batcher = new AsyncBatcher<TValue>(fn, options)\n return batcher.addItem\n}\n"],"mappings":";;;;;;AAkEA,SAAS,8BAAiE;AACxE,QAAO;EACL,YAAY;EACZ,cAAc;EACd,aAAa,EAAE;EACf,SAAS;EACT,aAAa;EACb,WAAW;EACX,OAAO,EAAE;EACT,YAAY;EACZ,aAAa;EACb,MAAM;EACN,QAAQ;EACR,cAAc;EACd,qBAAqB;EACrB,kBAAkB;EACnB;;;;;;AAqFH,SAAgB,oBAKd,SAA6B;AAC7B,QAAO;;AAaT,MAAMA,iBAAgE;CACpE,qBAAqB,EACnB,aAAa,GACd;CACD,wBAAwB;CACxB,SAAS;CACT,SAAS;CACT,cAAc;CACd,MAAM;CACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqED,IAAa,eAAb,MAAkC;CAUhC,aAAoC;CAEpC,YACE,AAAOC,IACP,gBACA;EAFO;eAZoD,IAAIC,uBAC/D,6BAAqC,CACtC;uCAGe,IAAI,KAGjB;qBA2BW,eAA2D;AACvE,QAAK,UAAU;IAAE,GAAG,KAAK;IAAS,GAAG;IAAY;;iBAwCzC,OAAO,SAA+B;AAC9C,SAAKC,SAAU;IACb,OAAO,CAAC,GAAG,KAAK,MAAM,MAAM,OAAO,KAAK;IACxC,WAAW,KAAK,QAAQ,SAAS;IAClC,CAAC;AACF,QAAK,QAAQ,gBAAgB,KAAK;AAMlC,OAHE,KAAK,MAAM,MAAM,MAAM,UAAU,KAAK,QAAQ,WAC9C,KAAK,QAAQ,iBAAiB,KAAK,MAAM,MAAM,OAAO,KAAK,CAG3D,QAAO,MAAM,MAAKC,SAAU;YACnB,KAAK,QAAQ,SAAS,UAAU;AACzC,UAAKC,cAAe;AACpB,UAAKC,YAAa,iBAAiB,MAAKF,SAAU,EAAE,MAAKG,SAAU,CAAC;AACpE,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAKA,SAAU,CAAC,CAAC;;;eAmEhE,YAA0B;AAChC,SAAKF,cAAe;AACpB,UAAO,MAAM,MAAKD,SAAU;;4BAMM;AAClC,UAAO,CAAC,GAAG,KAAK,MAAM,MAAM,MAAM;;+BAGG;AACrC,UAAO,CAAC,GAAG,KAAK,MAAM,MAAM,YAAY;;qBAatB;AAClB,SAAKD,SAAU;IAAE,OAAO,EAAE;IAAE,aAAa,EAAE;IAAE,WAAW;IAAO,CAAC;;qBAsC9C;AAClB,QAAK,cAAc,SAAS,YAAY,QAAQ,OAAO,CAAC;AACxD,QAAK,cAAc,OAAO;AAC1B,SAAKA,SAAU,EACb,aAAa,OACd,CAAC;;sBAQiB;AACnB,SAAKE,cAAe;AACpB,SAAKF,SAAU,EACb,WAAW,OACZ,CAAC;;qBAMgB;AAClB,SAAKA,SAAU,6BAAqC,CAAC;AACrD,QAAK,QAAQ,gBAAgB,KAAK;AAClC,QAAK,cAAc,SAAS,YAAY,QAAQ,OAAO,CAAC;;AA3OxD,OAAK,MAAM,eAAe;AAC1B,OAAK,UAAU;GACb,GAAG;GACH,GAAG;GACH,cAAc,eAAe,gBAAgB,CAAC,eAAe;GAC9D;AACD,QAAKA,SAAU,KAAK,QAAQ,gBAAgB,EAAE,CAAC;AAE/C,MAAI,KAAK,IACP,uCAAiB,GAAG,mBAAmB,UAAU;AAC/C,OAAI,MAAM,QAAQ,QAAQ,KAAK,IAAK;AACpC,SAAKA,SAAU,MAAM,QAAQ,MAAM,MAAM;AACzC,QAAK,WAAW,MAAM,QAAQ,QAAQ;IACtC;;CAWN,aAAa,aAAuD;AAClE,OAAK,MAAM,UAAU,UAAU;GAC7B,MAAM,gBAAgB;IACpB,GAAG;IACH,GAAG;IACJ;GACD,MAAM,EAAE,aAAa,WAAW,UAAU;GAC1C,MAAM,OAAO,MAAM;GACnB,MAAM,UAAU,SAAS;AACzB,UAAO;IACL,GAAG;IACH;IACA;IACA,QAAQ,cACJ,cACA,YACE,YACA,UACE,SACA;IACT;IACD;AACF,kCAAW,gBAAgB,KAAK;;CAGlC,iBAAyB;AACvB,SAAOK,mCAAqB,KAAK,QAAQ,MAAM,KAAK;;;;;;;;;;;;;;CA2CtD,WAAW,YAA0B;AACnC,MAAI,KAAK,MAAM,MAAM,MAAM,WAAW,EACpC;EAGF,MAAM,sBAAsB,KAAK,MAAM,MAAM,eAAe;EAC5D,MAAM,QAAQ,KAAK,cAAc;AACjC,OAAK,OAAO;AACZ,OAAK,QAAQ,gBAAgB,KAAK;AAElC,QAAKL,SAAU;GAAE,aAAa;GAAM,cAAc;GAAqB,CAAC;AAExE,MAAI;GACF,MAAM,sBAAsB,IAAIM,mCAC9B,KAAK,IACL,KAAK,QAAQ,oBACd;AACD,QAAK,cAAc,IAAI,qBAAqB,oBAAoB;GAChE,MAAM,SAAS,MAAM,oBAAoB,QAAQ,MAAM;AACvD,SAAKN,SAAU;IACb,qBACE,KAAK,MAAM,MAAM,sBAAsB,MAAM;IAC/C,YAAY;IACZ,cAAc,KAAK,MAAM,MAAM,eAAe;IAC/C,CAAC;AACF,QAAK,QAAQ,YAAY,QAAQ,OAAO,KAAK;AAC7C,UAAO;WACA,OAAO;AACd,SAAKA,SAAU;IACb,YAAY,KAAK,MAAM,MAAM,aAAa;IAC1C,aAAa,CAAC,GAAG,KAAK,MAAM,MAAM,aAAa,GAAG,MAAM;IACxD,kBAAkB,KAAK,MAAM,MAAM,mBAAmB,MAAM;IAC7D,CAAC;AACF,QAAK,QAAQ,UAAU,OAAgB,OAAO,KAAK;AACnD,OAAI,KAAK,QAAQ,aACf,OAAM;AAER;YACQ;AACR,QAAK,cAAc,OAAO,oBAAoB;AAC9C,SAAKA,SAAU;IACb,aAAa;IACb,aAAa,KAAK,MAAM,MAAM,cAAc;IAC7C,CAAC;AACF,QAAK,QAAQ,YAAY,OAAO,KAAK;;;CAuBzC,sBAA4B;AAC1B,MAAI,MAAKG,WAAY;AACnB,gBAAa,MAAKA,UAAW;AAC7B,SAAKA,YAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCtB,eAAe,cAA2C;EACxD,MAAM,QAAQ,gBAAgB,KAAK,MAAM,MAAM;AAE/C,SADgB,KAAK,cAAc,IAAI,MAAM,EAC7B,gBAAgB,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsGxC,SAAgB,WACd,IACA,SACA;AAEA,QADgB,IAAI,aAAqB,IAAI,QAAQ,CACtC"}