@tanstack/pacer 0.8.0 → 0.9.1

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 (91) hide show
  1. package/dist/cjs/async-batcher.cjs +163 -0
  2. package/dist/cjs/async-batcher.cjs.map +1 -0
  3. package/dist/cjs/async-batcher.d.cts +273 -0
  4. package/dist/cjs/async-debouncer.cjs +149 -162
  5. package/dist/cjs/async-debouncer.cjs.map +1 -1
  6. package/dist/cjs/async-debouncer.d.cts +76 -57
  7. package/dist/cjs/async-queuer.cjs +282 -343
  8. package/dist/cjs/async-queuer.cjs.map +1 -1
  9. package/dist/cjs/async-queuer.d.cts +121 -100
  10. package/dist/cjs/async-rate-limiter.cjs +128 -185
  11. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  12. package/dist/cjs/async-rate-limiter.d.cts +72 -61
  13. package/dist/cjs/async-throttler.cjs +168 -178
  14. package/dist/cjs/async-throttler.cjs.map +1 -1
  15. package/dist/cjs/async-throttler.d.cts +97 -69
  16. package/dist/cjs/batcher.cjs +110 -119
  17. package/dist/cjs/batcher.cjs.map +1 -1
  18. package/dist/cjs/batcher.d.cts +76 -51
  19. package/dist/cjs/debouncer.cjs +97 -85
  20. package/dist/cjs/debouncer.cjs.map +1 -1
  21. package/dist/cjs/debouncer.d.cts +54 -26
  22. package/dist/cjs/index.cjs +3 -6
  23. package/dist/cjs/index.cjs.map +1 -1
  24. package/dist/cjs/index.d.cts +1 -1
  25. package/dist/cjs/queuer.cjs +247 -294
  26. package/dist/cjs/queuer.cjs.map +1 -1
  27. package/dist/cjs/queuer.d.cts +102 -81
  28. package/dist/cjs/rate-limiter.cjs +97 -130
  29. package/dist/cjs/rate-limiter.cjs.map +1 -1
  30. package/dist/cjs/rate-limiter.d.cts +50 -37
  31. package/dist/cjs/throttler.cjs +107 -123
  32. package/dist/cjs/throttler.cjs.map +1 -1
  33. package/dist/cjs/throttler.d.cts +59 -35
  34. package/dist/cjs/utils.cjs +0 -13
  35. package/dist/cjs/utils.cjs.map +1 -1
  36. package/dist/cjs/utils.d.cts +0 -1
  37. package/dist/esm/async-batcher.d.ts +273 -0
  38. package/dist/esm/async-batcher.js +163 -0
  39. package/dist/esm/async-batcher.js.map +1 -0
  40. package/dist/esm/async-debouncer.d.ts +76 -57
  41. package/dist/esm/async-debouncer.js +149 -162
  42. package/dist/esm/async-debouncer.js.map +1 -1
  43. package/dist/esm/async-queuer.d.ts +121 -100
  44. package/dist/esm/async-queuer.js +282 -343
  45. package/dist/esm/async-queuer.js.map +1 -1
  46. package/dist/esm/async-rate-limiter.d.ts +72 -61
  47. package/dist/esm/async-rate-limiter.js +128 -185
  48. package/dist/esm/async-rate-limiter.js.map +1 -1
  49. package/dist/esm/async-throttler.d.ts +97 -69
  50. package/dist/esm/async-throttler.js +168 -178
  51. package/dist/esm/async-throttler.js.map +1 -1
  52. package/dist/esm/batcher.d.ts +76 -51
  53. package/dist/esm/batcher.js +110 -119
  54. package/dist/esm/batcher.js.map +1 -1
  55. package/dist/esm/debouncer.d.ts +54 -26
  56. package/dist/esm/debouncer.js +97 -85
  57. package/dist/esm/debouncer.js.map +1 -1
  58. package/dist/esm/index.d.ts +1 -1
  59. package/dist/esm/index.js +4 -7
  60. package/dist/esm/queuer.d.ts +102 -81
  61. package/dist/esm/queuer.js +247 -294
  62. package/dist/esm/queuer.js.map +1 -1
  63. package/dist/esm/rate-limiter.d.ts +50 -37
  64. package/dist/esm/rate-limiter.js +97 -130
  65. package/dist/esm/rate-limiter.js.map +1 -1
  66. package/dist/esm/throttler.d.ts +59 -35
  67. package/dist/esm/throttler.js +107 -123
  68. package/dist/esm/throttler.js.map +1 -1
  69. package/dist/esm/utils.d.ts +0 -1
  70. package/dist/esm/utils.js +0 -13
  71. package/dist/esm/utils.js.map +1 -1
  72. package/package.json +14 -11
  73. package/src/async-batcher.ts +475 -0
  74. package/src/async-debouncer.ts +201 -121
  75. package/src/async-queuer.ts +337 -216
  76. package/src/async-rate-limiter.ts +176 -136
  77. package/src/async-throttler.ts +233 -139
  78. package/src/batcher.ts +158 -92
  79. package/src/debouncer.ts +135 -52
  80. package/src/index.ts +1 -1
  81. package/src/queuer.ts +349 -226
  82. package/src/rate-limiter.ts +125 -80
  83. package/src/throttler.ts +152 -78
  84. package/src/utils.ts +0 -15
  85. package/dist/cjs/compare.cjs +0 -72
  86. package/dist/cjs/compare.cjs.map +0 -1
  87. package/dist/cjs/compare.d.cts +0 -12
  88. package/dist/esm/compare.d.ts +0 -12
  89. package/dist/esm/compare.js +0 -72
  90. package/dist/esm/compare.js.map +0 -1
  91. package/src/compare.ts +0 -105
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const store = require("@tanstack/store");
4
+ const utils = require("./utils.cjs");
5
+ function getDefaultAsyncBatcherState() {
6
+ return {
7
+ errorCount: 0,
8
+ failedItems: [],
9
+ isEmpty: true,
10
+ isExecuting: false,
11
+ isPending: false,
12
+ isRunning: true,
13
+ items: [],
14
+ lastResult: void 0,
15
+ settleCount: 0,
16
+ size: 0,
17
+ status: "idle",
18
+ successCount: 0,
19
+ totalItemsProcessed: 0,
20
+ totalItemsFailed: 0
21
+ };
22
+ }
23
+ const defaultOptions = {
24
+ getShouldExecute: () => false,
25
+ maxSize: Infinity,
26
+ started: true,
27
+ throwOnError: true,
28
+ wait: Infinity
29
+ };
30
+ class AsyncBatcher {
31
+ constructor(fn, initialOptions) {
32
+ this.fn = fn;
33
+ this.store = new store.Store(
34
+ getDefaultAsyncBatcherState()
35
+ );
36
+ this.#timeoutId = null;
37
+ this.setOptions = (newOptions) => {
38
+ this.options = { ...this.options, ...newOptions };
39
+ };
40
+ this.#setState = (newState) => {
41
+ this.store.setState((state) => {
42
+ const combinedState = {
43
+ ...state,
44
+ ...newState
45
+ };
46
+ const { isExecuting, isPending, items } = combinedState;
47
+ const size = items.length;
48
+ const isEmpty = size === 0;
49
+ return {
50
+ ...combinedState,
51
+ isEmpty,
52
+ size,
53
+ status: isExecuting ? "executing" : isPending ? "pending" : isEmpty ? "idle" : "populated"
54
+ };
55
+ });
56
+ };
57
+ this.#getWait = () => {
58
+ return utils.parseFunctionOrValue(this.options.wait, this);
59
+ };
60
+ this.addItem = (item) => {
61
+ this.#setState({
62
+ items: [...this.store.state.items, item],
63
+ isPending: this.options.wait !== Infinity
64
+ });
65
+ this.options.onItemsChange?.(this);
66
+ const shouldProcess = this.store.state.items.length >= this.options.maxSize || this.options.getShouldExecute(this.store.state.items, this);
67
+ if (shouldProcess) {
68
+ this.#execute();
69
+ } else if (this.store.state.isRunning && this.options.wait !== Infinity) {
70
+ this.#clearTimeout();
71
+ this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait());
72
+ }
73
+ };
74
+ this.#execute = async () => {
75
+ if (this.store.state.items.length === 0) {
76
+ return void 0;
77
+ }
78
+ const batch = this.peekAllItems();
79
+ this.clear();
80
+ this.options.onItemsChange?.(this);
81
+ this.#setState({ isExecuting: true });
82
+ try {
83
+ const result = await this.fn(batch);
84
+ this.#setState({
85
+ totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length,
86
+ lastResult: result,
87
+ successCount: this.store.state.successCount + 1
88
+ });
89
+ this.options.onSuccess?.(result, this);
90
+ return result;
91
+ } catch (error) {
92
+ this.#setState({
93
+ errorCount: this.store.state.errorCount + 1,
94
+ failedItems: [...this.store.state.failedItems, ...batch],
95
+ totalItemsFailed: this.store.state.totalItemsFailed + batch.length
96
+ });
97
+ this.options.onError?.(error, batch, this);
98
+ if (this.options.throwOnError) {
99
+ throw error;
100
+ }
101
+ return void 0;
102
+ } finally {
103
+ this.#setState({
104
+ isExecuting: false,
105
+ settleCount: this.store.state.settleCount + 1
106
+ });
107
+ this.options.onSettled?.(this);
108
+ this.options.onExecute?.(this);
109
+ }
110
+ };
111
+ this.flush = async () => {
112
+ this.#clearTimeout();
113
+ return await this.#execute();
114
+ };
115
+ this.stop = () => {
116
+ this.#setState({ isRunning: false });
117
+ this.#clearTimeout();
118
+ };
119
+ this.start = () => {
120
+ this.#setState({ isRunning: true });
121
+ if (this.store.state.items.length > 0 && !this.#timeoutId) {
122
+ this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait());
123
+ }
124
+ };
125
+ this.peekAllItems = () => {
126
+ return [...this.store.state.items];
127
+ };
128
+ this.peekFailedItems = () => {
129
+ return [...this.store.state.failedItems];
130
+ };
131
+ this.#clearTimeout = () => {
132
+ if (this.#timeoutId) {
133
+ clearTimeout(this.#timeoutId);
134
+ this.#timeoutId = null;
135
+ }
136
+ };
137
+ this.clear = () => {
138
+ this.#setState({ items: [], failedItems: [], isPending: false });
139
+ };
140
+ this.reset = () => {
141
+ this.#setState(getDefaultAsyncBatcherState());
142
+ this.options.onItemsChange?.(this);
143
+ };
144
+ this.options = {
145
+ ...defaultOptions,
146
+ ...initialOptions,
147
+ throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
148
+ };
149
+ this.#setState(this.options.initialState ?? {});
150
+ }
151
+ #timeoutId;
152
+ #setState;
153
+ #getWait;
154
+ #execute;
155
+ #clearTimeout;
156
+ }
157
+ function asyncBatch(fn, options) {
158
+ const batcher = new AsyncBatcher(fn, options);
159
+ return batcher.addItem;
160
+ }
161
+ exports.AsyncBatcher = AsyncBatcher;
162
+ exports.asyncBatch = asyncBatch;
163
+ //# sourceMappingURL=async-batcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-batcher.cjs","sources":["../../src/async-batcher.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { parseFunctionOrValue } from './utils'\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 * 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 * Whether the batcher is active and will process items automatically\n */\n isRunning: 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 been processed across all batches\n */\n totalItemsProcessed: number\n /**\n * Total number of items that have failed processing across all batches\n */\n totalItemsFailed: number\n}\n\nfunction getDefaultAsyncBatcherState<TValue>(): AsyncBatcherState<TValue> {\n return {\n errorCount: 0,\n failedItems: [],\n isEmpty: true,\n isExecuting: false,\n isPending: false,\n isRunning: true,\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 * 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 * 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 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: unknown,\n failedItems: Array<TValue>,\n batcher: AsyncBatcher<TValue>,\n ) => void\n /**\n * Callback fired after a batch is processed\n */\n onExecute?: (batcher: AsyncBatcher<TValue>) => 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?: (batcher: AsyncBatcher<TValue>) => void\n /**\n * Optional callback to call when a batch succeeds\n */\n onSuccess?: (result: any, batcher: AsyncBatcher<TValue>) => 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\ntype AsyncBatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<\n Required<AsyncBatcherOptions<TValue>>,\n | 'initialState'\n | 'onError'\n | 'onExecute'\n | 'onItemsChange'\n | 'onSettled'\n | 'onSuccess'\n>\n\nconst defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks<any> = {\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 * This is the async version of the Batcher class. Unlike the sync version, this async batcher:\n * - Handles promises and returns results from batch executions\n * - Provides error handling with configurable error behavior\n * - Tracks success, error, and settle counts separately\n * - Has state tracking for when batches are executing\n * - Returns the result of the batch function execution\n *\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 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 options: AsyncBatcherOptionsWithOptionalCallbacks<TValue>\n #timeoutId: NodeJS.Timeout | null = null\n\n constructor(\n private fn: (items: Array<TValue>) => Promise<any>,\n initialOptions: AsyncBatcherOptions<TValue>,\n ) {\n this.options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n this.#setState(this.options.initialState ?? {})\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 }\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 addItem = (item: TValue): void => {\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 this.#execute()\n } else if (this.store.state.isRunning && 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 }\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\n */\n #execute = async (): Promise<any> => {\n if (this.store.state.items.length === 0) {\n return undefined\n }\n\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) // Call onItemsChange to notify listeners that the items have changed\n\n this.#setState({ isExecuting: true })\n\n try {\n const result = await this.fn(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, 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, batch, this)\n if (this.options.throwOnError) {\n throw error\n }\n return undefined\n } finally {\n this.#setState({\n isExecuting: false,\n settleCount: this.store.state.settleCount + 1,\n })\n this.options.onSettled?.(this)\n this.options.onExecute?.(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 * Stops the async batcher from processing batches\n */\n stop = (): void => {\n this.#setState({ isRunning: false })\n this.#clearTimeout()\n }\n\n /**\n * Starts the async batcher and processes any pending items\n */\n start = (): void => {\n this.#setState({ isRunning: true })\n if (this.store.state.items.length > 0 && !this.#timeoutId) {\n this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait())\n }\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 * Resets the async batcher state to its default values\n */\n reset = (): void => {\n this.#setState(getDefaultAsyncBatcherState<TValue>())\n this.options.onItemsChange?.(this)\n }\n}\n\n/**\n * Creates an async batcher that processes items in batches\n *\n * Unlike the sync batcher, this async version:\n * - Handles promises and returns results from batch executions\n * - Provides error handling with configurable error behavior\n * - Tracks success, error, and settle counts separately\n * - Has state tracking for when batches are executing\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error 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 `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 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"],"names":["Store","parseFunctionOrValue"],"mappings":"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"}
@@ -0,0 +1,273 @@
1
+ import { Store } from '@tanstack/store';
2
+ import { OptionalKeys } from './types.cjs';
3
+ export interface AsyncBatcherState<TValue> {
4
+ /**
5
+ * Number of batch executions that have resulted in errors
6
+ */
7
+ errorCount: number;
8
+ /**
9
+ * Array of items that failed during batch processing
10
+ */
11
+ failedItems: Array<TValue>;
12
+ /**
13
+ * Whether the batcher has no items to process (items array is empty)
14
+ */
15
+ isEmpty: boolean;
16
+ /**
17
+ * Whether a batch is currently being processed asynchronously
18
+ */
19
+ isExecuting: boolean;
20
+ /**
21
+ * Whether the batcher is waiting for the timeout to trigger batch processing
22
+ */
23
+ isPending: boolean;
24
+ /**
25
+ * Whether the batcher is active and will process items automatically
26
+ */
27
+ isRunning: boolean;
28
+ /**
29
+ * Array of items currently queued for batch processing
30
+ */
31
+ items: Array<TValue>;
32
+ /**
33
+ * The result from the most recent batch execution
34
+ */
35
+ lastResult: any;
36
+ /**
37
+ * Number of batch executions that have completed (either successfully or with errors)
38
+ */
39
+ settleCount: number;
40
+ /**
41
+ * Number of items currently in the batch queue
42
+ */
43
+ size: number;
44
+ /**
45
+ * 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
46
+ */
47
+ status: 'idle' | 'pending' | 'executing' | 'populated';
48
+ /**
49
+ * Number of batch executions that have completed successfully
50
+ */
51
+ successCount: number;
52
+ /**
53
+ * Total number of items that have been processed across all batches
54
+ */
55
+ totalItemsProcessed: number;
56
+ /**
57
+ * Total number of items that have failed processing across all batches
58
+ */
59
+ totalItemsFailed: number;
60
+ }
61
+ /**
62
+ * Options for configuring an AsyncBatcher instance
63
+ */
64
+ export interface AsyncBatcherOptions<TValue> {
65
+ /**
66
+ * Custom function to determine if a batch should be processed
67
+ * Return true to process the batch immediately
68
+ */
69
+ getShouldExecute?: (items: Array<TValue>, batcher: AsyncBatcher<TValue>) => boolean;
70
+ /**
71
+ * Initial state for the async batcher
72
+ */
73
+ initialState?: Partial<AsyncBatcherState<TValue>>;
74
+ /**
75
+ * Maximum number of items in a batch
76
+ * @default Infinity
77
+ */
78
+ maxSize?: number;
79
+ /**
80
+ * Optional error handler for when the batch function throws.
81
+ * If provided, the handler will be called with the error and batcher instance.
82
+ * This can be used alongside throwOnError - the handler will be called before any error is thrown.
83
+ */
84
+ onError?: (error: unknown, failedItems: Array<TValue>, batcher: AsyncBatcher<TValue>) => void;
85
+ /**
86
+ * Callback fired after a batch is processed
87
+ */
88
+ onExecute?: (batcher: AsyncBatcher<TValue>) => void;
89
+ /**
90
+ * Callback fired after items are added to the batcher
91
+ */
92
+ onItemsChange?: (batcher: AsyncBatcher<TValue>) => void;
93
+ /**
94
+ * Optional callback to call when a batch is settled (completed or failed)
95
+ */
96
+ onSettled?: (batcher: AsyncBatcher<TValue>) => void;
97
+ /**
98
+ * Optional callback to call when a batch succeeds
99
+ */
100
+ onSuccess?: (result: any, batcher: AsyncBatcher<TValue>) => void;
101
+ /**
102
+ * Whether the batcher should start processing immediately
103
+ * @default true
104
+ */
105
+ started?: boolean;
106
+ /**
107
+ * Whether to throw errors when they occur.
108
+ * Defaults to true if no onError handler is provided, false if an onError handler is provided.
109
+ * Can be explicitly set to override these defaults.
110
+ */
111
+ throwOnError?: boolean;
112
+ /**
113
+ * Maximum time in milliseconds to wait before processing a batch.
114
+ * If the wait duration has elapsed, the batch will be processed.
115
+ * If not provided, the batch will not be triggered by a timeout.
116
+ * @default Infinity
117
+ */
118
+ wait?: number | ((asyncBatcher: AsyncBatcher<TValue>) => number);
119
+ }
120
+ type AsyncBatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<Required<AsyncBatcherOptions<TValue>>, 'initialState' | 'onError' | 'onExecute' | 'onItemsChange' | 'onSettled' | 'onSuccess'>;
121
+ /**
122
+ * A class that collects items and processes them in batches asynchronously.
123
+ *
124
+ * This is the async version of the Batcher class. Unlike the sync version, this async batcher:
125
+ * - Handles promises and returns results from batch executions
126
+ * - Provides error handling with configurable error behavior
127
+ * - Tracks success, error, and settle counts separately
128
+ * - Has state tracking for when batches are executing
129
+ * - Returns the result of the batch function execution
130
+ *
131
+ * Batching is a technique for grouping multiple operations together to be processed as a single unit.
132
+ *
133
+ * The AsyncBatcher provides a flexible way to implement async batching with configurable:
134
+ * - Maximum batch size (number of items per batch)
135
+ * - Time-based batching (process after X milliseconds)
136
+ * - Custom batch processing logic via getShouldExecute
137
+ * - Event callbacks for monitoring batch operations
138
+ * - Error handling for failed batch operations
139
+ *
140
+ * Error Handling:
141
+ * - If an `onError` handler is provided, it will be called with the error and batcher instance
142
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
143
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
144
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
145
+ * - The error state can be checked using the AsyncBatcher instance
146
+ *
147
+ * State Management:
148
+ * - Uses TanStack Store for reactive state management
149
+ * - Use `initialState` to provide initial state values when creating the async batcher
150
+ * - Use `onSuccess` callback to react to successful batch execution and implement custom logic
151
+ * - Use `onError` callback to react to batch execution errors and implement custom error handling
152
+ * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic
153
+ * - Use `onExecute` callback to react to batch execution and implement custom logic
154
+ * - Use `onItemsChange` callback to react to items being added or removed from the batcher
155
+ * - The state includes total items processed, success/error counts, and execution status
156
+ * - State can be accessed via `asyncBatcher.store.state` when using the class directly
157
+ * - When using framework adapters (React/Solid), state is accessed from `asyncBatcher.state`
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const batcher = new AsyncBatcher<number>(
162
+ * async (items) => {
163
+ * const result = await processItems(items);
164
+ * console.log('Processing batch:', items);
165
+ * return result;
166
+ * },
167
+ * {
168
+ * maxSize: 5,
169
+ * wait: 2000,
170
+ * onSuccess: (result) => console.log('Batch succeeded:', result),
171
+ * onError: (error) => console.error('Batch failed:', error)
172
+ * }
173
+ * );
174
+ *
175
+ * batcher.addItem(1);
176
+ * batcher.addItem(2);
177
+ * // After 2 seconds or when 5 items are added, whichever comes first,
178
+ * // the batch will be processed and the result will be available
179
+ * // batcher.execute() // manually trigger a batch
180
+ * ```
181
+ */
182
+ export declare class AsyncBatcher<TValue> {
183
+ #private;
184
+ private fn;
185
+ readonly store: Store<Readonly<AsyncBatcherState<TValue>>>;
186
+ options: AsyncBatcherOptionsWithOptionalCallbacks<TValue>;
187
+ constructor(fn: (items: Array<TValue>) => Promise<any>, initialOptions: AsyncBatcherOptions<TValue>);
188
+ /**
189
+ * Updates the async batcher options
190
+ */
191
+ setOptions: (newOptions: Partial<AsyncBatcherOptions<TValue>>) => void;
192
+ /**
193
+ * Adds an item to the async batcher
194
+ * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed
195
+ */
196
+ addItem: (item: TValue) => void;
197
+ /**
198
+ * Processes the current batch of items immediately
199
+ */
200
+ flush: () => Promise<any>;
201
+ /**
202
+ * Stops the async batcher from processing batches
203
+ */
204
+ stop: () => void;
205
+ /**
206
+ * Starts the async batcher and processes any pending items
207
+ */
208
+ start: () => void;
209
+ /**
210
+ * Returns a copy of all items in the async batcher
211
+ */
212
+ peekAllItems: () => Array<TValue>;
213
+ peekFailedItems: () => Array<TValue>;
214
+ /**
215
+ * Removes all items from the async batcher
216
+ */
217
+ clear: () => void;
218
+ /**
219
+ * Resets the async batcher state to its default values
220
+ */
221
+ reset: () => void;
222
+ }
223
+ /**
224
+ * Creates an async batcher that processes items in batches
225
+ *
226
+ * Unlike the sync batcher, this async version:
227
+ * - Handles promises and returns results from batch executions
228
+ * - Provides error handling with configurable error behavior
229
+ * - Tracks success, error, and settle counts separately
230
+ * - Has state tracking for when batches are executing
231
+ *
232
+ * Error Handling:
233
+ * - If an `onError` handler is provided, it will be called with the error and batcher instance
234
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
235
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
236
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
237
+ * - The error state can be checked using the underlying AsyncBatcher instance
238
+ *
239
+ * State Management:
240
+ * - Uses TanStack Store for reactive state management
241
+ * - Use `initialState` to provide initial state values when creating the async batcher
242
+ * - Use `onSuccess` callback to react to successful batch execution and implement custom logic
243
+ * - Use `onError` callback to react to batch execution errors and implement custom error handling
244
+ * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic
245
+ * - Use `onExecute` callback to react to batch execution and implement custom logic
246
+ * - Use `onItemsChange` callback to react to items being added or removed from the batcher
247
+ * - The state includes total items processed, success/error counts, and execution status
248
+ * - State can be accessed via the underlying AsyncBatcher instance's `store.state` property
249
+ * - When using framework adapters (React/Solid), state is accessed from the hook's state property
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * const batchItems = asyncBatch<number>(
254
+ * async (items) => {
255
+ * const result = await processApiCall(items);
256
+ * console.log('Processing:', items);
257
+ * return result;
258
+ * },
259
+ * {
260
+ * maxSize: 3,
261
+ * wait: 1000,
262
+ * onSuccess: (result) => console.log('Batch succeeded:', result),
263
+ * onError: (error) => console.error('Batch failed:', error)
264
+ * }
265
+ * );
266
+ *
267
+ * batchItems(1);
268
+ * batchItems(2);
269
+ * batchItems(3); // Triggers batch processing
270
+ * ```
271
+ */
272
+ export declare function asyncBatch<TValue>(fn: (items: Array<TValue>) => Promise<any>, options: AsyncBatcherOptions<TValue>): (item: TValue) => void;
273
+ export {};