@tanstack/pacer 0.21.1 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -6
- package/dist/async-batcher.d.ts +11 -14
- package/dist/async-batcher.js +186 -132
- package/dist/async-debouncer.d.ts +10 -13
- package/dist/async-debouncer.js +187 -146
- package/dist/async-queuer.d.ts +12 -14
- package/dist/async-queuer.js +384 -259
- package/dist/async-rate-limiter.d.ts +9 -12
- package/dist/async-rate-limiter.js +198 -147
- package/dist/async-retryer.d.ts +10 -10
- package/dist/async-retryer.js +209 -188
- package/dist/async-throttler.d.ts +10 -13
- package/dist/async-throttler.js +215 -161
- package/dist/batcher.d.ts +5 -8
- package/dist/batcher.js +107 -87
- package/dist/debouncer.d.ts +6 -9
- package/dist/debouncer.js +99 -86
- package/dist/event-client.d.ts +8 -11
- package/dist/event-client.js +1 -2
- package/dist/index.js +1 -1
- package/dist/queuer.d.ts +8 -10
- package/dist/queuer.js +277 -207
- package/dist/rate-limiter.d.ts +6 -9
- package/dist/rate-limiter.js +127 -109
- package/dist/throttler.d.ts +6 -9
- package/dist/throttler.js +127 -90
- package/dist/types.d.ts +4 -6
- package/dist/utils.d.ts +3 -6
- package/dist/utils.js +1 -2
- package/package.json +23 -70
- package/dist/async-batcher.cjs +0 -337
- package/dist/async-batcher.cjs.map +0 -1
- package/dist/async-batcher.d.cts +0 -344
- package/dist/async-batcher.js.map +0 -1
- package/dist/async-debouncer.cjs +0 -330
- package/dist/async-debouncer.cjs.map +0 -1
- package/dist/async-debouncer.d.cts +0 -300
- package/dist/async-debouncer.js.map +0 -1
- package/dist/async-queuer.cjs +0 -484
- package/dist/async-queuer.cjs.map +0 -1
- package/dist/async-queuer.d.cts +0 -440
- package/dist/async-queuer.js.map +0 -1
- package/dist/async-rate-limiter.cjs +0 -373
- package/dist/async-rate-limiter.cjs.map +0 -1
- package/dist/async-rate-limiter.d.cts +0 -357
- package/dist/async-rate-limiter.js.map +0 -1
- package/dist/async-retryer.cjs +0 -374
- package/dist/async-retryer.cjs.map +0 -1
- package/dist/async-retryer.d.cts +0 -319
- package/dist/async-retryer.js.map +0 -1
- package/dist/async-throttler.cjs +0 -347
- package/dist/async-throttler.cjs.map +0 -1
- package/dist/async-throttler.d.cts +0 -320
- package/dist/async-throttler.js.map +0 -1
- package/dist/batcher.cjs +0 -200
- package/dist/batcher.cjs.map +0 -1
- package/dist/batcher.d.cts +0 -180
- package/dist/batcher.js.map +0 -1
- package/dist/debouncer.cjs +0 -203
- package/dist/debouncer.cjs.map +0 -1
- package/dist/debouncer.d.cts +0 -167
- package/dist/debouncer.js.map +0 -1
- package/dist/event-client.cjs +0 -64
- package/dist/event-client.cjs.map +0 -1
- package/dist/event-client.d.cts +0 -66
- package/dist/event-client.js.map +0 -1
- package/dist/index.cjs +0 -52
- package/dist/index.d.cts +0 -15
- package/dist/queuer.cjs +0 -401
- package/dist/queuer.cjs.map +0 -1
- package/dist/queuer.d.cts +0 -345
- package/dist/queuer.js.map +0 -1
- package/dist/rate-limiter.cjs +0 -263
- package/dist/rate-limiter.cjs.map +0 -1
- package/dist/rate-limiter.d.cts +0 -215
- package/dist/rate-limiter.js.map +0 -1
- package/dist/throttler.cjs +0 -215
- package/dist/throttler.cjs.map +0 -1
- package/dist/throttler.d.cts +0 -207
- package/dist/throttler.js.map +0 -1
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +0 -13
- package/dist/utils.cjs +0 -14
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.d.cts +0 -8
- package/dist/utils.js.map +0 -1
- package/src/async-batcher.ts +0 -594
- package/src/async-debouncer.ts +0 -565
- package/src/async-queuer.ts +0 -925
- package/src/async-rate-limiter.ts +0 -647
- package/src/async-retryer.ts +0 -684
- package/src/async-throttler.ts +0 -633
- package/src/batcher.ts +0 -329
- package/src/debouncer.ts +0 -334
- package/src/event-client.ts +0 -129
- package/src/index.ts +0 -24
- package/src/queuer.ts +0 -740
- package/src/rate-limiter.ts +0 -429
- package/src/throttler.ts +0 -380
- package/src/types.ts +0 -12
- package/src/utils.ts +0 -12
package/README.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<
|
|
2
|
+
<picture>
|
|
3
|
+
<source
|
|
4
|
+
media="(prefers-color-scheme: dark)"
|
|
5
|
+
srcset="https://tanstack.com/api/readme/pacer.png?theme=dark"
|
|
6
|
+
/>
|
|
7
|
+
<source
|
|
8
|
+
media="(prefers-color-scheme: light)"
|
|
9
|
+
srcset="https://tanstack.com/api/readme/pacer.png"
|
|
10
|
+
/>
|
|
11
|
+
<img
|
|
12
|
+
src="https://tanstack.com/api/readme/pacer.png"
|
|
13
|
+
alt="TanStack Pacer"
|
|
14
|
+
width="900"
|
|
15
|
+
/>
|
|
16
|
+
</picture>
|
|
3
17
|
</div>
|
|
4
18
|
|
|
5
19
|
<br />
|
|
@@ -29,8 +43,9 @@
|
|
|
29
43
|
</div>
|
|
30
44
|
|
|
31
45
|
<div align="center">
|
|
32
|
-
|
|
46
|
+
|
|
33
47
|
### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/)
|
|
48
|
+
|
|
34
49
|
</div>
|
|
35
50
|
|
|
36
51
|
# TanStack Pacer
|
|
@@ -88,10 +103,10 @@ A lightweight timing and scheduling library for debouncing, throttling, rate lim
|
|
|
88
103
|
> [!NOTE]
|
|
89
104
|
> You may know **TanStack Pacer** by our adapter names, too!
|
|
90
105
|
>
|
|
91
|
-
> - [**React Pacer**](https://tanstack.com/pacer/latest/docs/framework/react
|
|
92
|
-
> - [**Preact Pacer**](https://tanstack.com/pacer/latest/docs/framework/preact
|
|
93
|
-
> - [**Solid Pacer**](https://tanstack.com/pacer/latest/docs/framework/solid
|
|
94
|
-
> - [**Angular Pacer**](https://tanstack.com/pacer/latest/docs/framework/angular
|
|
106
|
+
> - [**React Pacer**](https://tanstack.com/pacer/latest/docs/framework/react)
|
|
107
|
+
> - [**Preact Pacer**](https://tanstack.com/pacer/latest/docs/framework/preact)
|
|
108
|
+
> - [**Solid Pacer**](https://tanstack.com/pacer/latest/docs/framework/solid)
|
|
109
|
+
> - [**Angular Pacer**](https://tanstack.com/pacer/latest/docs/framework/angular)
|
|
95
110
|
> - Svelte Pacer - needs a contributor!
|
|
96
111
|
> - Vue Pacer - needs a contributor!
|
|
97
112
|
|
package/dist/async-batcher.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { OptionalKeys } from "./types.js";
|
|
2
2
|
import { AsyncRetryer, AsyncRetryerOptions } from "./async-retryer.js";
|
|
3
3
|
import { Store } from "@tanstack/store";
|
|
4
|
-
|
|
5
4
|
//#region src/async-batcher.d.ts
|
|
6
|
-
interface AsyncBatcherState<TValue> {
|
|
5
|
+
export interface AsyncBatcherState<TValue> {
|
|
7
6
|
/**
|
|
8
7
|
* Number of batch executions that have resulted in errors
|
|
9
8
|
*/
|
|
10
9
|
errorCount: number;
|
|
11
10
|
/**
|
|
12
|
-
* Number of batch executions that have been
|
|
11
|
+
* Number of batch executions that have been started
|
|
13
12
|
*/
|
|
14
|
-
|
|
13
|
+
executionCount: number;
|
|
15
14
|
/**
|
|
16
15
|
* Array of items that failed during batch processing
|
|
17
16
|
*/
|
|
@@ -64,7 +63,7 @@ interface AsyncBatcherState<TValue> {
|
|
|
64
63
|
/**
|
|
65
64
|
* Options for configuring an AsyncBatcher instance
|
|
66
65
|
*/
|
|
67
|
-
interface AsyncBatcherOptions<TValue> {
|
|
66
|
+
export interface AsyncBatcherOptions<TValue> {
|
|
68
67
|
/**
|
|
69
68
|
* Options for configuring the underlying async retryer
|
|
70
69
|
*/
|
|
@@ -129,7 +128,7 @@ interface AsyncBatcherOptions<TValue> {
|
|
|
129
128
|
* Utility function for sharing common `AsyncBatcherOptions` options between different `AsyncBatcher` instances.
|
|
130
129
|
*
|
|
131
130
|
*/
|
|
132
|
-
declare function asyncBatcherOptions<TValue = any, TOptions extends Partial<AsyncBatcherOptions<TValue>> = Partial<AsyncBatcherOptions<TValue>>>(options: TOptions): TOptions;
|
|
131
|
+
export declare function asyncBatcherOptions<TValue = any, TOptions extends Partial<AsyncBatcherOptions<TValue>> = Partial<AsyncBatcherOptions<TValue>>>(options: TOptions): TOptions;
|
|
133
132
|
type AsyncBatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<Required<AsyncBatcherOptions<TValue>>, 'initialState' | 'onError' | 'onItemsChange' | 'onSettled' | 'onSuccess' | 'key'>;
|
|
134
133
|
/**
|
|
135
134
|
* A class that collects items and processes them in batches asynchronously.
|
|
@@ -198,7 +197,7 @@ type AsyncBatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<Required<As
|
|
|
198
197
|
* // batcher.execute() // manually trigger a batch
|
|
199
198
|
* ```
|
|
200
199
|
*/
|
|
201
|
-
declare class AsyncBatcher<TValue> {
|
|
200
|
+
export declare class AsyncBatcher<TValue> {
|
|
202
201
|
#private;
|
|
203
202
|
fn: (items: Array<TValue>) => Promise<any>;
|
|
204
203
|
readonly store: Store<Readonly<AsyncBatcherState<TValue>>>;
|
|
@@ -234,10 +233,10 @@ declare class AsyncBatcher<TValue> {
|
|
|
234
233
|
clear: () => void;
|
|
235
234
|
/**
|
|
236
235
|
* Returns the AbortSignal for a specific execution.
|
|
237
|
-
* If no
|
|
236
|
+
* If no executionCount is provided, returns the signal for the most recent execution.
|
|
238
237
|
* Returns null if no execution is found or not currently executing.
|
|
239
238
|
*
|
|
240
|
-
* @param
|
|
239
|
+
* @param executionCount - Optional specific execution to get signal for
|
|
241
240
|
* @example
|
|
242
241
|
* ```typescript
|
|
243
242
|
* const batcher = new AsyncBatcher(
|
|
@@ -256,7 +255,7 @@ declare class AsyncBatcher<TValue> {
|
|
|
256
255
|
* )
|
|
257
256
|
* ```
|
|
258
257
|
*/
|
|
259
|
-
getAbortSignal: (
|
|
258
|
+
getAbortSignal: (executionCount?: number) => AbortSignal | null;
|
|
260
259
|
/**
|
|
261
260
|
* Aborts all ongoing executions with the internal abort controllers.
|
|
262
261
|
* Does NOT cancel any pending execution that have not started yet.
|
|
@@ -338,7 +337,5 @@ declare class AsyncBatcher<TValue> {
|
|
|
338
337
|
* batchItems(3); // Triggers batch processing
|
|
339
338
|
* ```
|
|
340
339
|
*/
|
|
341
|
-
declare function asyncBatch<TValue>(fn: (items: Array<TValue>) => Promise<any>, options: AsyncBatcherOptions<TValue>): (item: TValue) => Promise<any>;
|
|
342
|
-
//#endregion
|
|
343
|
-
export { AsyncBatcher, AsyncBatcherOptions, AsyncBatcherState, asyncBatch, asyncBatcherOptions };
|
|
344
|
-
//# sourceMappingURL=async-batcher.d.ts.map
|
|
340
|
+
export declare function asyncBatch<TValue>(fn: (items: Array<TValue>) => Promise<any>, options: AsyncBatcherOptions<TValue>): (item: TValue) => Promise<any>;
|
|
341
|
+
//#endregion
|
package/dist/async-batcher.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { parseFunctionOrValue } from "./utils.js";
|
|
2
|
-
import { emitChange, pacerEventClient } from "./event-client.js";
|
|
3
2
|
import { AsyncRetryer } from "./async-retryer.js";
|
|
3
|
+
import { emitChange, pacerEventClient } from "./event-client.js";
|
|
4
4
|
import { Store } from "@tanstack/store";
|
|
5
5
|
|
|
6
6
|
//#region src/async-batcher.ts
|
|
7
7
|
function getDefaultAsyncBatcherState() {
|
|
8
8
|
return {
|
|
9
9
|
errorCount: 0,
|
|
10
|
-
|
|
10
|
+
executionCount: 0,
|
|
11
11
|
failedItems: [],
|
|
12
12
|
isEmpty: true,
|
|
13
13
|
isExecuting: false,
|
|
@@ -105,132 +105,14 @@ const defaultOptions = {
|
|
|
105
105
|
* ```
|
|
106
106
|
*/
|
|
107
107
|
var AsyncBatcher = class {
|
|
108
|
-
|
|
108
|
+
fn;
|
|
109
|
+
store = new Store(getDefaultAsyncBatcherState());
|
|
110
|
+
key;
|
|
111
|
+
options;
|
|
112
|
+
asyncRetryers = /* @__PURE__ */ new Map();
|
|
113
|
+
#timeoutId = null;
|
|
109
114
|
constructor(fn, initialOptions) {
|
|
110
115
|
this.fn = fn;
|
|
111
|
-
this.store = new Store(getDefaultAsyncBatcherState());
|
|
112
|
-
this.asyncRetryers = /* @__PURE__ */ new Map();
|
|
113
|
-
this.#timeoutId = null;
|
|
114
|
-
this.setOptions = (newOptions) => {
|
|
115
|
-
this.options = {
|
|
116
|
-
...this.options,
|
|
117
|
-
...newOptions
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
this.#setState = (newState) => {
|
|
121
|
-
this.store.setState((state) => {
|
|
122
|
-
const combinedState = {
|
|
123
|
-
...state,
|
|
124
|
-
...newState
|
|
125
|
-
};
|
|
126
|
-
const { isExecuting, isPending, items } = combinedState;
|
|
127
|
-
const size = items.length;
|
|
128
|
-
const isEmpty = size === 0;
|
|
129
|
-
return {
|
|
130
|
-
...combinedState,
|
|
131
|
-
isEmpty,
|
|
132
|
-
size,
|
|
133
|
-
status: isExecuting ? "executing" : isPending ? "pending" : isEmpty ? "idle" : "populated"
|
|
134
|
-
};
|
|
135
|
-
});
|
|
136
|
-
emitChange("AsyncBatcher", this);
|
|
137
|
-
};
|
|
138
|
-
this.#getWait = () => {
|
|
139
|
-
return parseFunctionOrValue(this.options.wait, this);
|
|
140
|
-
};
|
|
141
|
-
this.addItem = async (item) => {
|
|
142
|
-
this.#setState({
|
|
143
|
-
items: [...this.store.state.items, item],
|
|
144
|
-
isPending: this.options.wait !== Infinity
|
|
145
|
-
});
|
|
146
|
-
this.options.onItemsChange?.(this);
|
|
147
|
-
if (this.store.state.items.length >= this.options.maxSize || this.options.getShouldExecute(this.store.state.items, this)) return await this.#execute();
|
|
148
|
-
else if (this.options.wait !== Infinity) {
|
|
149
|
-
this.#clearTimeout();
|
|
150
|
-
this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait());
|
|
151
|
-
await new Promise((resolve) => setTimeout(resolve, this.#getWait()));
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
this.#execute = async () => {
|
|
155
|
-
if (this.store.state.items.length === 0) return;
|
|
156
|
-
const currentExecuteCount = this.store.state.executeCount + 1;
|
|
157
|
-
const batch = this.peekAllItems();
|
|
158
|
-
this.clear();
|
|
159
|
-
this.options.onItemsChange?.(this);
|
|
160
|
-
this.#setState({
|
|
161
|
-
isExecuting: true,
|
|
162
|
-
executeCount: currentExecuteCount
|
|
163
|
-
});
|
|
164
|
-
try {
|
|
165
|
-
const currentAsyncRetryer = new AsyncRetryer(this.fn, this.options.asyncRetryerOptions);
|
|
166
|
-
this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer);
|
|
167
|
-
const result = await currentAsyncRetryer.execute(batch);
|
|
168
|
-
this.#setState({
|
|
169
|
-
totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length,
|
|
170
|
-
lastResult: result,
|
|
171
|
-
successCount: this.store.state.successCount + 1
|
|
172
|
-
});
|
|
173
|
-
this.options.onSuccess?.(result, batch, this);
|
|
174
|
-
return result;
|
|
175
|
-
} catch (error) {
|
|
176
|
-
this.#setState({
|
|
177
|
-
errorCount: this.store.state.errorCount + 1,
|
|
178
|
-
failedItems: [...this.store.state.failedItems, ...batch],
|
|
179
|
-
totalItemsFailed: this.store.state.totalItemsFailed + batch.length
|
|
180
|
-
});
|
|
181
|
-
this.options.onError?.(error, batch, this);
|
|
182
|
-
if (this.options.throwOnError) throw error;
|
|
183
|
-
return;
|
|
184
|
-
} finally {
|
|
185
|
-
this.asyncRetryers.delete(currentExecuteCount);
|
|
186
|
-
this.#setState({
|
|
187
|
-
isExecuting: false,
|
|
188
|
-
settleCount: this.store.state.settleCount + 1
|
|
189
|
-
});
|
|
190
|
-
this.options.onSettled?.(batch, this);
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
this.flush = async () => {
|
|
194
|
-
this.#clearTimeout();
|
|
195
|
-
return await this.#execute();
|
|
196
|
-
};
|
|
197
|
-
this.peekAllItems = () => {
|
|
198
|
-
return [...this.store.state.items];
|
|
199
|
-
};
|
|
200
|
-
this.peekFailedItems = () => {
|
|
201
|
-
return [...this.store.state.failedItems];
|
|
202
|
-
};
|
|
203
|
-
this.#clearTimeout = () => {
|
|
204
|
-
if (this.#timeoutId) {
|
|
205
|
-
clearTimeout(this.#timeoutId);
|
|
206
|
-
this.#timeoutId = null;
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
this.clear = () => {
|
|
210
|
-
this.#setState({
|
|
211
|
-
items: [],
|
|
212
|
-
failedItems: [],
|
|
213
|
-
isPending: false
|
|
214
|
-
});
|
|
215
|
-
};
|
|
216
|
-
this.getAbortSignal = (executeCount) => {
|
|
217
|
-
const count = executeCount ?? this.store.state.executeCount;
|
|
218
|
-
return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
|
|
219
|
-
};
|
|
220
|
-
this.abort = () => {
|
|
221
|
-
this.asyncRetryers.forEach((retryer) => retryer.abort());
|
|
222
|
-
this.asyncRetryers.clear();
|
|
223
|
-
this.#setState({ isExecuting: false });
|
|
224
|
-
};
|
|
225
|
-
this.cancel = () => {
|
|
226
|
-
this.#clearTimeout();
|
|
227
|
-
this.#setState({ isPending: false });
|
|
228
|
-
};
|
|
229
|
-
this.reset = () => {
|
|
230
|
-
this.#setState(getDefaultAsyncBatcherState());
|
|
231
|
-
this.options.onItemsChange?.(this);
|
|
232
|
-
this.asyncRetryers.forEach((retryer) => retryer.reset());
|
|
233
|
-
};
|
|
234
116
|
this.key = initialOptions.key;
|
|
235
117
|
this.options = {
|
|
236
118
|
...defaultOptions,
|
|
@@ -244,8 +126,57 @@ var AsyncBatcher = class {
|
|
|
244
126
|
this.setOptions(event.payload.options);
|
|
245
127
|
});
|
|
246
128
|
}
|
|
247
|
-
|
|
248
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Updates the async batcher options
|
|
131
|
+
*/
|
|
132
|
+
setOptions = (newOptions) => {
|
|
133
|
+
this.options = {
|
|
134
|
+
...this.options,
|
|
135
|
+
...newOptions
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
#setState = (newState) => {
|
|
139
|
+
this.store.setState((state) => {
|
|
140
|
+
const combinedState = {
|
|
141
|
+
...state,
|
|
142
|
+
...newState
|
|
143
|
+
};
|
|
144
|
+
const { isExecuting, isPending, items } = combinedState;
|
|
145
|
+
const size = items.length;
|
|
146
|
+
const isEmpty = size === 0;
|
|
147
|
+
return {
|
|
148
|
+
...combinedState,
|
|
149
|
+
isEmpty,
|
|
150
|
+
size,
|
|
151
|
+
status: isExecuting ? "executing" : isPending ? "pending" : isEmpty ? "idle" : "populated"
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
emitChange("AsyncBatcher", this);
|
|
155
|
+
};
|
|
156
|
+
#getWait = () => {
|
|
157
|
+
return parseFunctionOrValue(this.options.wait, this);
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Adds an item to the async batcher
|
|
161
|
+
* If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed
|
|
162
|
+
*
|
|
163
|
+
* @returns The result from the batch function, or undefined if an error occurred and was handled by onError
|
|
164
|
+
*
|
|
165
|
+
* @throws The error from the batch function if no onError handler is configured or throwOnError is true
|
|
166
|
+
*/
|
|
167
|
+
addItem = async (item) => {
|
|
168
|
+
this.#setState({
|
|
169
|
+
items: [...this.store.state.items, item],
|
|
170
|
+
isPending: this.options.wait !== Infinity
|
|
171
|
+
});
|
|
172
|
+
this.options.onItemsChange?.(this);
|
|
173
|
+
if (this.store.state.items.length >= this.options.maxSize || this.options.getShouldExecute(this.store.state.items, this)) return await this.#execute();
|
|
174
|
+
else if (this.options.wait !== Infinity) {
|
|
175
|
+
this.#clearTimeout();
|
|
176
|
+
this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait());
|
|
177
|
+
await new Promise((resolve) => setTimeout(resolve, this.#getWait()));
|
|
178
|
+
}
|
|
179
|
+
};
|
|
249
180
|
/**
|
|
250
181
|
* Processes the current batch of items asynchronously.
|
|
251
182
|
* This method will automatically be triggered if the batcher is running and any of these conditions are met:
|
|
@@ -258,8 +189,132 @@ var AsyncBatcher = class {
|
|
|
258
189
|
* @returns A promise that resolves with the result of the batch function, or undefined if an error occurred and was handled by onError
|
|
259
190
|
* @throws The error from the batch function if no onError handler is configured or throwOnError is true
|
|
260
191
|
*/
|
|
261
|
-
#execute
|
|
262
|
-
|
|
192
|
+
#execute = async () => {
|
|
193
|
+
if (this.store.state.items.length === 0) return;
|
|
194
|
+
const currentExecutionCount = this.store.state.executionCount + 1;
|
|
195
|
+
const batch = this.peekAllItems();
|
|
196
|
+
this.clear();
|
|
197
|
+
this.options.onItemsChange?.(this);
|
|
198
|
+
this.#setState({
|
|
199
|
+
isExecuting: true,
|
|
200
|
+
executionCount: currentExecutionCount
|
|
201
|
+
});
|
|
202
|
+
try {
|
|
203
|
+
const currentAsyncRetryer = new AsyncRetryer(this.fn, this.options.asyncRetryerOptions);
|
|
204
|
+
this.asyncRetryers.set(currentExecutionCount, currentAsyncRetryer);
|
|
205
|
+
const result = await currentAsyncRetryer.execute(batch);
|
|
206
|
+
this.#setState({
|
|
207
|
+
totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length,
|
|
208
|
+
lastResult: result,
|
|
209
|
+
successCount: this.store.state.successCount + 1
|
|
210
|
+
});
|
|
211
|
+
this.options.onSuccess?.(result, batch, this);
|
|
212
|
+
return result;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
this.#setState({
|
|
215
|
+
errorCount: this.store.state.errorCount + 1,
|
|
216
|
+
failedItems: [...this.store.state.failedItems, ...batch],
|
|
217
|
+
totalItemsFailed: this.store.state.totalItemsFailed + batch.length
|
|
218
|
+
});
|
|
219
|
+
this.options.onError?.(error, batch, this);
|
|
220
|
+
if (this.options.throwOnError) throw error;
|
|
221
|
+
return;
|
|
222
|
+
} finally {
|
|
223
|
+
this.asyncRetryers.delete(currentExecutionCount);
|
|
224
|
+
this.#setState({
|
|
225
|
+
isExecuting: false,
|
|
226
|
+
settleCount: this.store.state.settleCount + 1
|
|
227
|
+
});
|
|
228
|
+
this.options.onSettled?.(batch, this);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Processes the current batch of items immediately
|
|
233
|
+
*/
|
|
234
|
+
flush = async () => {
|
|
235
|
+
this.#clearTimeout();
|
|
236
|
+
return await this.#execute();
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Returns a copy of all items in the async batcher
|
|
240
|
+
*/
|
|
241
|
+
peekAllItems = () => {
|
|
242
|
+
return [...this.store.state.items];
|
|
243
|
+
};
|
|
244
|
+
peekFailedItems = () => {
|
|
245
|
+
return [...this.store.state.failedItems];
|
|
246
|
+
};
|
|
247
|
+
#clearTimeout = () => {
|
|
248
|
+
if (this.#timeoutId) {
|
|
249
|
+
clearTimeout(this.#timeoutId);
|
|
250
|
+
this.#timeoutId = null;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Removes all items from the async batcher
|
|
255
|
+
*/
|
|
256
|
+
clear = () => {
|
|
257
|
+
this.#setState({
|
|
258
|
+
items: [],
|
|
259
|
+
failedItems: [],
|
|
260
|
+
isPending: false
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Returns the AbortSignal for a specific execution.
|
|
265
|
+
* If no executionCount is provided, returns the signal for the most recent execution.
|
|
266
|
+
* Returns null if no execution is found or not currently executing.
|
|
267
|
+
*
|
|
268
|
+
* @param executionCount - Optional specific execution to get signal for
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* const batcher = new AsyncBatcher(
|
|
272
|
+
* async (items: string[]) => {
|
|
273
|
+
* const signal = batcher.getAbortSignal()
|
|
274
|
+
* if (signal) {
|
|
275
|
+
* const response = await fetch('/api/batch', {
|
|
276
|
+
* method: 'POST',
|
|
277
|
+
* body: JSON.stringify(items),
|
|
278
|
+
* signal
|
|
279
|
+
* })
|
|
280
|
+
* return response.json()
|
|
281
|
+
* }
|
|
282
|
+
* },
|
|
283
|
+
* { maxSize: 10, wait: 100 }
|
|
284
|
+
* )
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
getAbortSignal = (executionCount) => {
|
|
288
|
+
const count = executionCount ?? this.store.state.executionCount;
|
|
289
|
+
return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* Aborts all ongoing executions with the internal abort controllers.
|
|
293
|
+
* Does NOT cancel any pending execution that have not started yet.
|
|
294
|
+
* Does NOT clear out the items.
|
|
295
|
+
*/
|
|
296
|
+
abort = () => {
|
|
297
|
+
this.asyncRetryers.forEach((retryer) => retryer.abort());
|
|
298
|
+
this.asyncRetryers.clear();
|
|
299
|
+
this.#setState({ isExecuting: false });
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Cancels any pending execution that have not started yet.
|
|
303
|
+
* Does NOT abort any execution already in progress.
|
|
304
|
+
* Does NOT clear out the items.
|
|
305
|
+
*/
|
|
306
|
+
cancel = () => {
|
|
307
|
+
this.#clearTimeout();
|
|
308
|
+
this.#setState({ isPending: false });
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Resets the async batcher state to its default values
|
|
312
|
+
*/
|
|
313
|
+
reset = () => {
|
|
314
|
+
this.#setState(getDefaultAsyncBatcherState());
|
|
315
|
+
this.options.onItemsChange?.(this);
|
|
316
|
+
this.asyncRetryers.forEach((retryer) => retryer.reset());
|
|
317
|
+
};
|
|
263
318
|
};
|
|
264
319
|
/**
|
|
265
320
|
* Creates an async batcher that processes items in batches.
|
|
@@ -330,5 +385,4 @@ function asyncBatch(fn, options) {
|
|
|
330
385
|
}
|
|
331
386
|
|
|
332
387
|
//#endregion
|
|
333
|
-
export { AsyncBatcher, asyncBatch, asyncBatcherOptions };
|
|
334
|
-
//# sourceMappingURL=async-batcher.js.map
|
|
388
|
+
export { AsyncBatcher, asyncBatch, asyncBatcherOptions };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { AnyAsyncFunction } from "./types.js";
|
|
2
2
|
import { AsyncRetryer, AsyncRetryerOptions } from "./async-retryer.js";
|
|
3
3
|
import { Store } from "@tanstack/store";
|
|
4
|
-
|
|
5
4
|
//#region src/async-debouncer.d.ts
|
|
6
|
-
interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
|
|
5
|
+
export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
|
|
7
6
|
/**
|
|
8
7
|
* Whether the debouncer can execute on the leading edge of the timeout
|
|
9
8
|
*/
|
|
@@ -27,7 +26,7 @@ interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
|
|
|
27
26
|
/**
|
|
28
27
|
* The result from the most recent successful function execution
|
|
29
28
|
*/
|
|
30
|
-
lastResult: ReturnType<TFn
|
|
29
|
+
lastResult: Awaited<ReturnType<TFn>> | undefined;
|
|
31
30
|
/**
|
|
32
31
|
* Number of times maybeExecute has been called (for reduction calculations)
|
|
33
32
|
*/
|
|
@@ -48,7 +47,7 @@ interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
|
|
|
48
47
|
/**
|
|
49
48
|
* Options for configuring an async debounced function
|
|
50
49
|
*/
|
|
51
|
-
interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
50
|
+
export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
52
51
|
/**
|
|
53
52
|
* Options for configuring the underlying async retryer
|
|
54
53
|
*/
|
|
@@ -86,7 +85,7 @@ interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
|
86
85
|
/**
|
|
87
86
|
* Optional callback to call when the debounced function is executed
|
|
88
87
|
*/
|
|
89
|
-
onSuccess?: (result: ReturnType<TFn
|
|
88
|
+
onSuccess?: (result: Awaited<ReturnType<TFn>>, args: Parameters<TFn>, debouncer: AsyncDebouncer<TFn>) => void;
|
|
90
89
|
/**
|
|
91
90
|
* Whether to throw errors when they occur.
|
|
92
91
|
* Defaults to true if no onError handler is provided, false if an onError handler is provided.
|
|
@@ -108,7 +107,7 @@ interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
|
108
107
|
/**
|
|
109
108
|
* Utility function for sharing common `AsyncDebouncerOptions` options between different `AsyncDebouncer` instances.
|
|
110
109
|
*/
|
|
111
|
-
declare function asyncDebouncerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncDebouncerOptions<TFn>> = Partial<AsyncDebouncerOptions<TFn>>>(options: TOptions): TOptions;
|
|
110
|
+
export declare function asyncDebouncerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncDebouncerOptions<TFn>> = Partial<AsyncDebouncerOptions<TFn>>>(options: TOptions): TOptions;
|
|
112
111
|
/**
|
|
113
112
|
* A class that creates an async debounced function.
|
|
114
113
|
*
|
|
@@ -163,7 +162,7 @@ declare function asyncDebouncerOptions<TFn extends AnyAsyncFunction = AnyAsyncFu
|
|
|
163
162
|
* const results = await asyncDebouncer.maybeExecute(inputElement.value);
|
|
164
163
|
* ```
|
|
165
164
|
*/
|
|
166
|
-
declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
165
|
+
export declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
167
166
|
#private;
|
|
168
167
|
fn: TFn;
|
|
169
168
|
readonly store: Store<Readonly<AsyncDebouncerState<TFn>>>;
|
|
@@ -189,11 +188,11 @@ declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
189
188
|
* @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
|
|
190
189
|
* @throws The error from the debounced function if no onError handler is configured
|
|
191
190
|
*/
|
|
192
|
-
maybeExecute: (...args: Parameters<TFn>) => Promise<ReturnType<TFn
|
|
191
|
+
maybeExecute: (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
|
|
193
192
|
/**
|
|
194
193
|
* Processes the current pending execution immediately
|
|
195
194
|
*/
|
|
196
|
-
flush: () => Promise<ReturnType<TFn
|
|
195
|
+
flush: () => Promise<Awaited<ReturnType<TFn>> | undefined>;
|
|
197
196
|
/**
|
|
198
197
|
* Returns the AbortSignal for a specific execution.
|
|
199
198
|
* If no maybeExecuteCount is provided, returns the signal for the most recent execution.
|
|
@@ -294,7 +293,5 @@ declare class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
294
293
|
* const result = await debounced("third");
|
|
295
294
|
* ```
|
|
296
295
|
*/
|
|
297
|
-
declare function asyncDebounce<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncDebouncerOptions<TFn>): (...args: Parameters<TFn>) => Promise<ReturnType<TFn
|
|
298
|
-
//#endregion
|
|
299
|
-
export { AsyncDebouncer, AsyncDebouncerOptions, AsyncDebouncerState, asyncDebounce, asyncDebouncerOptions };
|
|
300
|
-
//# sourceMappingURL=async-debouncer.d.ts.map
|
|
296
|
+
export declare function asyncDebounce<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncDebouncerOptions<TFn>): (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
|
|
297
|
+
//#endregion
|