@tanstack/pacer 0.22.0 → 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.
Files changed (100) hide show
  1. package/README.md +4 -4
  2. package/dist/async-batcher.d.ts +11 -13
  3. package/dist/async-batcher.js +185 -131
  4. package/dist/async-debouncer.d.ts +6 -8
  5. package/dist/async-debouncer.js +186 -142
  6. package/dist/async-queuer.d.ts +11 -13
  7. package/dist/async-queuer.js +383 -290
  8. package/dist/async-rate-limiter.d.ts +6 -8
  9. package/dist/async-rate-limiter.js +197 -143
  10. package/dist/async-retryer.d.ts +6 -8
  11. package/dist/async-retryer.js +209 -179
  12. package/dist/async-throttler.d.ts +6 -8
  13. package/dist/async-throttler.js +214 -157
  14. package/dist/batcher.d.ts +5 -7
  15. package/dist/batcher.js +107 -87
  16. package/dist/debouncer.d.ts +6 -8
  17. package/dist/debouncer.js +99 -86
  18. package/dist/event-client.d.ts +8 -10
  19. package/dist/event-client.js +1 -2
  20. package/dist/queuer.d.ts +7 -9
  21. package/dist/queuer.js +277 -212
  22. package/dist/rate-limiter.d.ts +6 -8
  23. package/dist/rate-limiter.js +127 -109
  24. package/dist/throttler.d.ts +6 -8
  25. package/dist/throttler.js +127 -90
  26. package/dist/types.d.ts +4 -6
  27. package/dist/utils.d.ts +3 -5
  28. package/dist/utils.js +1 -2
  29. package/package.json +21 -68
  30. package/dist/async-batcher.cjs +0 -337
  31. package/dist/async-batcher.cjs.map +0 -1
  32. package/dist/async-batcher.d.cts +0 -343
  33. package/dist/async-batcher.js.map +0 -1
  34. package/dist/async-debouncer.cjs +0 -327
  35. package/dist/async-debouncer.cjs.map +0 -1
  36. package/dist/async-debouncer.d.cts +0 -299
  37. package/dist/async-debouncer.js.map +0 -1
  38. package/dist/async-queuer.cjs +0 -516
  39. package/dist/async-queuer.cjs.map +0 -1
  40. package/dist/async-queuer.d.cts +0 -440
  41. package/dist/async-queuer.js.map +0 -1
  42. package/dist/async-rate-limiter.cjs +0 -370
  43. package/dist/async-rate-limiter.cjs.map +0 -1
  44. package/dist/async-rate-limiter.d.cts +0 -356
  45. package/dist/async-rate-limiter.js.map +0 -1
  46. package/dist/async-retryer.cjs +0 -365
  47. package/dist/async-retryer.cjs.map +0 -1
  48. package/dist/async-retryer.d.cts +0 -321
  49. package/dist/async-retryer.js.map +0 -1
  50. package/dist/async-throttler.cjs +0 -344
  51. package/dist/async-throttler.cjs.map +0 -1
  52. package/dist/async-throttler.d.cts +0 -319
  53. package/dist/async-throttler.js.map +0 -1
  54. package/dist/batcher.cjs +0 -200
  55. package/dist/batcher.cjs.map +0 -1
  56. package/dist/batcher.d.cts +0 -179
  57. package/dist/batcher.js.map +0 -1
  58. package/dist/debouncer.cjs +0 -203
  59. package/dist/debouncer.cjs.map +0 -1
  60. package/dist/debouncer.d.cts +0 -166
  61. package/dist/debouncer.js.map +0 -1
  62. package/dist/event-client.cjs +0 -64
  63. package/dist/event-client.cjs.map +0 -1
  64. package/dist/event-client.d.cts +0 -65
  65. package/dist/event-client.js.map +0 -1
  66. package/dist/index.cjs +0 -52
  67. package/dist/index.d.cts +0 -15
  68. package/dist/queuer.cjs +0 -406
  69. package/dist/queuer.cjs.map +0 -1
  70. package/dist/queuer.d.cts +0 -345
  71. package/dist/queuer.js.map +0 -1
  72. package/dist/rate-limiter.cjs +0 -263
  73. package/dist/rate-limiter.cjs.map +0 -1
  74. package/dist/rate-limiter.d.cts +0 -214
  75. package/dist/rate-limiter.js.map +0 -1
  76. package/dist/throttler.cjs +0 -215
  77. package/dist/throttler.cjs.map +0 -1
  78. package/dist/throttler.d.cts +0 -206
  79. package/dist/throttler.js.map +0 -1
  80. package/dist/types.cjs +0 -0
  81. package/dist/types.d.cts +0 -13
  82. package/dist/utils.cjs +0 -13
  83. package/dist/utils.cjs.map +0 -1
  84. package/dist/utils.d.cts +0 -7
  85. package/dist/utils.js.map +0 -1
  86. package/src/async-batcher.ts +0 -594
  87. package/src/async-debouncer.ts +0 -566
  88. package/src/async-queuer.ts +0 -988
  89. package/src/async-rate-limiter.ts +0 -648
  90. package/src/async-retryer.ts +0 -673
  91. package/src/async-throttler.ts +0 -634
  92. package/src/batcher.ts +0 -329
  93. package/src/debouncer.ts +0 -334
  94. package/src/event-client.ts +0 -129
  95. package/src/index.ts +0 -24
  96. package/src/queuer.ts +0 -751
  97. package/src/rate-limiter.ts +0 -429
  98. package/src/throttler.ts +0 -380
  99. package/src/types.ts +0 -12
  100. package/src/utils.ts +0 -12
@@ -91,157 +91,15 @@ const defaultOptions = {
91
91
  * ```
92
92
  */
93
93
  var AsyncThrottler = class {
94
- #timeoutId;
95
- #resolvePreviousPromise;
94
+ fn;
95
+ store = new Store(getDefaultAsyncThrottlerState());
96
+ key;
97
+ options;
98
+ asyncRetryers = /* @__PURE__ */ new Map();
99
+ #timeoutId = null;
100
+ #resolvePreviousPromise = null;
96
101
  constructor(fn, initialOptions) {
97
102
  this.fn = fn;
98
- this.store = new Store(getDefaultAsyncThrottlerState());
99
- this.asyncRetryers = /* @__PURE__ */ new Map();
100
- this.#timeoutId = null;
101
- this.#resolvePreviousPromise = null;
102
- this.setOptions = (newOptions) => {
103
- this.options = {
104
- ...this.options,
105
- ...newOptions
106
- };
107
- if (!this.#getEnabled()) this.cancel();
108
- };
109
- this.#setState = (newState) => {
110
- this.store.setState((state) => {
111
- const combinedState = {
112
- ...state,
113
- ...newState
114
- };
115
- const { isPending, isExecuting, settleCount } = combinedState;
116
- return {
117
- ...combinedState,
118
- status: !this.#getEnabled() ? "disabled" : isPending ? "pending" : isExecuting ? "executing" : settleCount > 0 ? "settled" : "idle"
119
- };
120
- });
121
- emitChange("AsyncThrottler", this);
122
- };
123
- this.#getEnabled = () => {
124
- return !!parseFunctionOrValue(this.options.enabled, this);
125
- };
126
- this.#getWait = () => {
127
- return parseFunctionOrValue(this.options.wait, this);
128
- };
129
- this.maybeExecute = async (...args) => {
130
- if (!this.#getEnabled()) return void 0;
131
- this.#resolvePreviousPromiseInternal();
132
- this.#setState({
133
- maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
134
- lastArgs: args
135
- });
136
- const wait = this.#getWait();
137
- const thisMaybeExecuteNumber = this.store.state.maybeExecuteCount;
138
- for (let maxNumIterations = wait / 10; this.store.state.isExecuting && maxNumIterations > 0; maxNumIterations--) {
139
- await new Promise((resolve) => setTimeout(resolve, 10));
140
- if (this.store.state.maybeExecuteCount !== thisMaybeExecuteNumber) return this.store.state.lastResult;
141
- }
142
- const now = Date.now();
143
- const timeSinceLastExecution = now - this.store.state.lastExecutionTime;
144
- if (this.options.leading && !this.store.state.isPending && timeSinceLastExecution >= wait) await this.#execute(...args);
145
- else if (this.options.trailing) {
146
- this.cancel();
147
- this.#setState({ isPending: true });
148
- return new Promise((resolve, reject) => {
149
- this.#resolvePreviousPromise = resolve;
150
- const newTimeSinceLastExecution = this.store.state.lastExecutionTime ? now - this.store.state.lastExecutionTime : 0;
151
- const timeoutDuration = Math.max(0, wait - newTimeSinceLastExecution);
152
- this.#timeoutId = setTimeout(async () => {
153
- this.#clearTimeout();
154
- if (this.store.state.lastArgs !== void 0) try {
155
- await this.#execute(...this.store.state.lastArgs);
156
- } catch (error) {
157
- reject(error);
158
- }
159
- this.#resolvePreviousPromise = null;
160
- resolve(this.store.state.lastResult);
161
- }, timeoutDuration);
162
- });
163
- }
164
- return this.store.state.lastResult;
165
- };
166
- this.#execute = async (...args) => {
167
- if (!this.#getEnabled()) return void 0;
168
- const currentMaybeExecute = this.store.state.maybeExecuteCount;
169
- try {
170
- this.#setState({ isExecuting: true });
171
- const currentAsyncRetryer = new AsyncRetryer(this.fn, this.options.asyncRetryerOptions);
172
- this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer);
173
- const result = await currentAsyncRetryer.execute(...args);
174
- this.#setState({
175
- lastResult: result,
176
- successCount: this.store.state.successCount + 1
177
- });
178
- this.options.onSuccess?.(result, args, this);
179
- } catch (error) {
180
- this.#setState({ errorCount: this.store.state.errorCount + 1 });
181
- this.options.onError?.(error, args, this);
182
- if (this.options.throwOnError) throw error;
183
- } finally {
184
- this.asyncRetryers.delete(currentMaybeExecute);
185
- const lastExecutionTime = Date.now();
186
- const wait = this.#getWait();
187
- const nextExecutionTime = lastExecutionTime + wait;
188
- this.#setState({
189
- isExecuting: false,
190
- isPending: !!this.#timeoutId,
191
- settleCount: this.store.state.settleCount + 1,
192
- lastExecutionTime,
193
- nextExecutionTime
194
- });
195
- this.options.onSettled?.(args, this);
196
- setTimeout(() => {
197
- if (!this.store.state.isPending) this.#setState({ nextExecutionTime: void 0 });
198
- }, wait);
199
- }
200
- return this.store.state.lastResult;
201
- };
202
- this.flush = async () => {
203
- if (this.store.state.isPending && this.store.state.lastArgs) {
204
- const resolvePromise = this.#resolvePreviousPromise;
205
- this.#clearTimeout();
206
- this.#setState({ isPending: false });
207
- const result = await this.#execute(...this.store.state.lastArgs);
208
- if (resolvePromise) resolvePromise(result);
209
- return result;
210
- }
211
- };
212
- this.#resolvePreviousPromiseInternal = () => {
213
- if (this.#resolvePreviousPromise) {
214
- this.#resolvePreviousPromise(this.store.state.lastResult);
215
- this.#resolvePreviousPromise = null;
216
- }
217
- };
218
- this.#clearTimeout = () => {
219
- if (this.#timeoutId) {
220
- clearTimeout(this.#timeoutId);
221
- this.#timeoutId = null;
222
- }
223
- };
224
- this.getAbortSignal = (maybeExecuteCount) => {
225
- const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount;
226
- return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
227
- };
228
- this.abort = () => {
229
- this.asyncRetryers.forEach((retryer) => retryer.abort());
230
- this.asyncRetryers.clear();
231
- this.#setState({ isExecuting: false });
232
- };
233
- this.cancel = () => {
234
- this.#clearTimeout();
235
- if (this.#resolvePreviousPromise) {
236
- this.#resolvePreviousPromiseInternal();
237
- this.#resolvePreviousPromise = null;
238
- }
239
- this.#setState({ isPending: false });
240
- };
241
- this.reset = () => {
242
- this.#setState(getDefaultAsyncThrottlerState());
243
- this.asyncRetryers.forEach((retryer) => retryer.reset());
244
- };
245
103
  this.key = initialOptions.key;
246
104
  this.options = {
247
105
  ...defaultOptions,
@@ -255,18 +113,218 @@ var AsyncThrottler = class {
255
113
  this.setOptions(event.payload.options);
256
114
  });
257
115
  }
258
- #setState;
116
+ /**
117
+ * Updates the async throttler options
118
+ */
119
+ setOptions = (newOptions) => {
120
+ this.options = {
121
+ ...this.options,
122
+ ...newOptions
123
+ };
124
+ if (!this.#getEnabled()) this.cancel();
125
+ };
126
+ #setState = (newState) => {
127
+ this.store.setState((state) => {
128
+ const combinedState = {
129
+ ...state,
130
+ ...newState
131
+ };
132
+ const { isPending, isExecuting, settleCount } = combinedState;
133
+ return {
134
+ ...combinedState,
135
+ status: !this.#getEnabled() ? "disabled" : isPending ? "pending" : isExecuting ? "executing" : settleCount > 0 ? "settled" : "idle"
136
+ };
137
+ });
138
+ emitChange("AsyncThrottler", this);
139
+ };
259
140
  /**
260
141
  * Returns the current enabled state of the async throttler
261
142
  */
262
- #getEnabled;
143
+ #getEnabled = () => {
144
+ return !!parseFunctionOrValue(this.options.enabled, this);
145
+ };
263
146
  /**
264
147
  * Returns the current wait time in milliseconds
265
148
  */
266
- #getWait;
267
- #execute;
268
- #resolvePreviousPromiseInternal;
269
- #clearTimeout;
149
+ #getWait = () => {
150
+ return parseFunctionOrValue(this.options.wait, this);
151
+ };
152
+ /**
153
+ * Attempts to execute the throttled function. The execution behavior depends on the throttler options:
154
+ *
155
+ * - If enough time has passed since the last execution (>= wait period):
156
+ * - With leading=true: Executes immediately
157
+ * - With leading=false: Waits for the next trailing execution
158
+ *
159
+ * - If within the wait period:
160
+ * - With trailing=true: Schedules execution for end of wait period
161
+ * - With trailing=false: Drops the execution
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * const throttled = new AsyncThrottler(fn, { wait: 1000 });
166
+ *
167
+ * // First call executes immediately
168
+ * await throttled.maybeExecute('a', 'b');
169
+ *
170
+ * // Call during wait period - gets throttled
171
+ * await throttled.maybeExecute('c', 'd');
172
+ * ```
173
+ */
174
+ maybeExecute = async (...args) => {
175
+ if (!this.#getEnabled()) return void 0;
176
+ this.#resolvePreviousPromiseInternal();
177
+ this.#setState({
178
+ maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
179
+ lastArgs: args
180
+ });
181
+ const wait = this.#getWait();
182
+ const thisMaybeExecuteNumber = this.store.state.maybeExecuteCount;
183
+ for (let maxNumIterations = wait / 10; this.store.state.isExecuting && maxNumIterations > 0; maxNumIterations--) {
184
+ await new Promise((resolve) => setTimeout(resolve, 10));
185
+ if (this.store.state.maybeExecuteCount !== thisMaybeExecuteNumber) return this.store.state.lastResult;
186
+ }
187
+ const now = Date.now();
188
+ const timeSinceLastExecution = now - this.store.state.lastExecutionTime;
189
+ if (this.options.leading && !this.store.state.isPending && timeSinceLastExecution >= wait) await this.#execute(...args);
190
+ else if (this.options.trailing) {
191
+ this.cancel();
192
+ this.#setState({ isPending: true });
193
+ return new Promise((resolve, reject) => {
194
+ this.#resolvePreviousPromise = resolve;
195
+ const newTimeSinceLastExecution = this.store.state.lastExecutionTime ? now - this.store.state.lastExecutionTime : 0;
196
+ const timeoutDuration = Math.max(0, wait - newTimeSinceLastExecution);
197
+ this.#timeoutId = setTimeout(async () => {
198
+ this.#clearTimeout();
199
+ if (this.store.state.lastArgs !== void 0) try {
200
+ await this.#execute(...this.store.state.lastArgs);
201
+ } catch (error) {
202
+ reject(error);
203
+ }
204
+ this.#resolvePreviousPromise = null;
205
+ resolve(this.store.state.lastResult);
206
+ }, timeoutDuration);
207
+ });
208
+ }
209
+ return this.store.state.lastResult;
210
+ };
211
+ #execute = async (...args) => {
212
+ if (!this.#getEnabled()) return void 0;
213
+ const currentMaybeExecute = this.store.state.maybeExecuteCount;
214
+ try {
215
+ this.#setState({ isExecuting: true });
216
+ const currentAsyncRetryer = new AsyncRetryer(this.fn, this.options.asyncRetryerOptions);
217
+ this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer);
218
+ const result = await currentAsyncRetryer.execute(...args);
219
+ this.#setState({
220
+ lastResult: result,
221
+ successCount: this.store.state.successCount + 1
222
+ });
223
+ this.options.onSuccess?.(result, args, this);
224
+ } catch (error) {
225
+ this.#setState({ errorCount: this.store.state.errorCount + 1 });
226
+ this.options.onError?.(error, args, this);
227
+ if (this.options.throwOnError) throw error;
228
+ } finally {
229
+ this.asyncRetryers.delete(currentMaybeExecute);
230
+ const lastExecutionTime = Date.now();
231
+ const wait = this.#getWait();
232
+ const nextExecutionTime = lastExecutionTime + wait;
233
+ this.#setState({
234
+ isExecuting: false,
235
+ isPending: !!this.#timeoutId,
236
+ settleCount: this.store.state.settleCount + 1,
237
+ lastExecutionTime,
238
+ nextExecutionTime
239
+ });
240
+ this.options.onSettled?.(args, this);
241
+ setTimeout(() => {
242
+ if (!this.store.state.isPending) this.#setState({ nextExecutionTime: void 0 });
243
+ }, wait);
244
+ }
245
+ return this.store.state.lastResult;
246
+ };
247
+ /**
248
+ * Processes the current pending execution immediately
249
+ */
250
+ flush = async () => {
251
+ if (this.store.state.isPending && this.store.state.lastArgs) {
252
+ const resolvePromise = this.#resolvePreviousPromise;
253
+ this.#clearTimeout();
254
+ this.#setState({ isPending: false });
255
+ const result = await this.#execute(...this.store.state.lastArgs);
256
+ if (resolvePromise) resolvePromise(result);
257
+ return result;
258
+ }
259
+ };
260
+ #resolvePreviousPromiseInternal = () => {
261
+ if (this.#resolvePreviousPromise) {
262
+ this.#resolvePreviousPromise(this.store.state.lastResult);
263
+ this.#resolvePreviousPromise = null;
264
+ }
265
+ };
266
+ #clearTimeout = () => {
267
+ if (this.#timeoutId) {
268
+ clearTimeout(this.#timeoutId);
269
+ this.#timeoutId = null;
270
+ }
271
+ };
272
+ /**
273
+ * Returns the AbortSignal for a specific execution.
274
+ * If no maybeExecuteCount is provided, returns the signal for the most recent execution.
275
+ * Returns null if no execution is found or not currently executing.
276
+ *
277
+ * @param maybeExecuteCount - Optional specific execution to get signal for
278
+ * @example
279
+ * ```typescript
280
+ * const throttler = new AsyncThrottler(
281
+ * async (data: string) => {
282
+ * const signal = throttler.getAbortSignal()
283
+ * if (signal) {
284
+ * const response = await fetch('/api/save', {
285
+ * method: 'POST',
286
+ * body: data,
287
+ * signal
288
+ * })
289
+ * return response.json()
290
+ * }
291
+ * },
292
+ * { wait: 1000 }
293
+ * )
294
+ * ```
295
+ */
296
+ getAbortSignal = (maybeExecuteCount) => {
297
+ const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount;
298
+ return this.asyncRetryers.get(count)?.getAbortSignal() ?? null;
299
+ };
300
+ /**
301
+ * Aborts all ongoing executions with the internal abort controllers.
302
+ * Does NOT cancel any pending execution that have not started yet.
303
+ */
304
+ abort = () => {
305
+ this.asyncRetryers.forEach((retryer) => retryer.abort());
306
+ this.asyncRetryers.clear();
307
+ this.#setState({ isExecuting: false });
308
+ };
309
+ /**
310
+ * Cancels any pending execution that have not started yet.
311
+ * Does NOT abort any execution already in progress.
312
+ */
313
+ cancel = () => {
314
+ this.#clearTimeout();
315
+ if (this.#resolvePreviousPromise) {
316
+ this.#resolvePreviousPromiseInternal();
317
+ this.#resolvePreviousPromise = null;
318
+ }
319
+ this.#setState({ isPending: false });
320
+ };
321
+ /**
322
+ * Resets the debouncer state to its default values
323
+ */
324
+ reset = () => {
325
+ this.#setState(getDefaultAsyncThrottlerState());
326
+ this.asyncRetryers.forEach((retryer) => retryer.reset());
327
+ };
270
328
  };
271
329
  /**
272
330
  * Creates an async throttled function that limits how often the function can execute.
@@ -337,5 +395,4 @@ function asyncThrottle(fn, initialOptions) {
337
395
  }
338
396
 
339
397
  //#endregion
340
- export { AsyncThrottler, asyncThrottle, asyncThrottlerOptions };
341
- //# sourceMappingURL=async-throttler.js.map
398
+ export { AsyncThrottler, asyncThrottle, asyncThrottlerOptions };
package/dist/batcher.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { OptionalKeys } from "./types.js";
2
2
  import { Store } from "@tanstack/store";
3
3
  //#region src/batcher.d.ts
4
- interface BatcherState<TValue> {
4
+ export interface BatcherState<TValue> {
5
5
  /**
6
6
  * Number of batch executions that have been completed
7
7
  */
@@ -34,7 +34,7 @@ interface BatcherState<TValue> {
34
34
  /**
35
35
  * Options for configuring a Batcher instance
36
36
  */
37
- interface BatcherOptions<TValue> {
37
+ export interface BatcherOptions<TValue> {
38
38
  /**
39
39
  * Custom function to determine if a batch should be processed
40
40
  * Return true to process the batch immediately
@@ -115,7 +115,7 @@ type BatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<Required<Batcher
115
115
  * // batcher.flush() // manually trigger a batch
116
116
  * ```
117
117
  */
118
- declare class Batcher<TValue> {
118
+ export declare class Batcher<TValue> {
119
119
  #private;
120
120
  fn: (items: Array<TValue>) => void;
121
121
  readonly store: Store<Readonly<BatcherState<TValue>>>;
@@ -173,7 +173,5 @@ declare class Batcher<TValue> {
173
173
  * batchItems(3); // Triggers batch processing
174
174
  * ```
175
175
  */
176
- declare function batch<TValue>(fn: (items: Array<TValue>) => void, options: BatcherOptions<TValue>): (item: TValue) => void;
177
- //#endregion
178
- export { Batcher, BatcherOptions, BatcherState, batch };
179
- //# sourceMappingURL=batcher.d.ts.map
176
+ export declare function batch<TValue>(fn: (items: Array<TValue>) => void, options: BatcherOptions<TValue>): (item: TValue) => void;
177
+ //#endregion
package/dist/batcher.js CHANGED
@@ -60,89 +60,13 @@ const defaultOptions = {
60
60
  * ```
61
61
  */
62
62
  var Batcher = class {
63
- #timeoutId;
63
+ fn;
64
+ store = new Store(getDefaultBatcherState());
65
+ key;
66
+ options;
67
+ #timeoutId = null;
64
68
  constructor(fn, initialOptions) {
65
69
  this.fn = fn;
66
- this.store = new Store(getDefaultBatcherState());
67
- this.#timeoutId = null;
68
- this.setOptions = (newOptions) => {
69
- this.options = {
70
- ...this.options,
71
- ...newOptions
72
- };
73
- };
74
- this.#setState = (newState) => {
75
- this.store.setState((state) => {
76
- const combinedState = {
77
- ...state,
78
- ...newState
79
- };
80
- const { isPending, items } = combinedState;
81
- const size = items.length;
82
- const isEmpty = size === 0;
83
- return {
84
- ...combinedState,
85
- isEmpty,
86
- size,
87
- status: isPending ? "pending" : "idle"
88
- };
89
- });
90
- emitChange("Batcher", this);
91
- };
92
- this.#getWait = () => {
93
- return parseFunctionOrValue(this.options.wait, this);
94
- };
95
- this.addItem = (item) => {
96
- this.#setState({
97
- items: [...this.store.state.items, item],
98
- isPending: this.options.wait !== Infinity
99
- });
100
- this.options.onItemsChange?.(this);
101
- if (this.store.state.items.length >= this.options.maxSize || this.options.getShouldExecute(this.store.state.items, this)) this.#execute();
102
- else if (this.options.wait !== Infinity) {
103
- this.#clearTimeout();
104
- this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait());
105
- }
106
- };
107
- this.#execute = () => {
108
- if (this.store.state.items.length === 0) return;
109
- const batch = this.peekAllItems();
110
- this.clear();
111
- this.options.onItemsChange?.(this);
112
- this.fn(batch);
113
- this.#setState({
114
- executionCount: this.store.state.executionCount + 1,
115
- totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length
116
- });
117
- this.options.onExecute?.(batch, this);
118
- };
119
- this.flush = () => {
120
- this.#clearTimeout();
121
- this.#execute();
122
- };
123
- this.peekAllItems = () => {
124
- return [...this.store.state.items];
125
- };
126
- this.#clearTimeout = () => {
127
- if (this.#timeoutId) {
128
- clearTimeout(this.#timeoutId);
129
- this.#timeoutId = null;
130
- }
131
- };
132
- this.clear = () => {
133
- this.#setState({
134
- items: [],
135
- isPending: false
136
- });
137
- };
138
- this.cancel = () => {
139
- this.#clearTimeout();
140
- this.#setState({ isPending: false });
141
- };
142
- this.reset = () => {
143
- this.#setState(getDefaultBatcherState());
144
- this.options.onItemsChange?.(this);
145
- };
146
70
  this.key = initialOptions.key;
147
71
  this.options = {
148
72
  ...defaultOptions,
@@ -155,8 +79,52 @@ var Batcher = class {
155
79
  this.setOptions(event.payload.options);
156
80
  });
157
81
  }
158
- #setState;
159
- #getWait;
82
+ /**
83
+ * Updates the batcher options
84
+ */
85
+ setOptions = (newOptions) => {
86
+ this.options = {
87
+ ...this.options,
88
+ ...newOptions
89
+ };
90
+ };
91
+ #setState = (newState) => {
92
+ this.store.setState((state) => {
93
+ const combinedState = {
94
+ ...state,
95
+ ...newState
96
+ };
97
+ const { isPending, items } = combinedState;
98
+ const size = items.length;
99
+ const isEmpty = size === 0;
100
+ return {
101
+ ...combinedState,
102
+ isEmpty,
103
+ size,
104
+ status: isPending ? "pending" : "idle"
105
+ };
106
+ });
107
+ emitChange("Batcher", this);
108
+ };
109
+ #getWait = () => {
110
+ return parseFunctionOrValue(this.options.wait, this);
111
+ };
112
+ /**
113
+ * Adds an item to the batcher
114
+ * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed
115
+ */
116
+ addItem = (item) => {
117
+ this.#setState({
118
+ items: [...this.store.state.items, item],
119
+ isPending: this.options.wait !== Infinity
120
+ });
121
+ this.options.onItemsChange?.(this);
122
+ if (this.store.state.items.length >= this.options.maxSize || this.options.getShouldExecute(this.store.state.items, this)) this.#execute();
123
+ else if (this.options.wait !== Infinity) {
124
+ this.#clearTimeout();
125
+ this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait());
126
+ }
127
+ };
160
128
  /**
161
129
  * Processes the current batch of items.
162
130
  * This method will automatically be triggered if the batcher is running and any of these conditions are met:
@@ -166,8 +134,61 @@ var Batcher = class {
166
134
  *
167
135
  * You can also call this method manually to process the current batch at any time.
168
136
  */
169
- #execute;
170
- #clearTimeout;
137
+ #execute = () => {
138
+ if (this.store.state.items.length === 0) return;
139
+ const batch = this.peekAllItems();
140
+ this.clear();
141
+ this.options.onItemsChange?.(this);
142
+ this.fn(batch);
143
+ this.#setState({
144
+ executionCount: this.store.state.executionCount + 1,
145
+ totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length
146
+ });
147
+ this.options.onExecute?.(batch, this);
148
+ };
149
+ /**
150
+ * Processes the current batch of items immediately
151
+ */
152
+ flush = () => {
153
+ this.#clearTimeout();
154
+ this.#execute();
155
+ };
156
+ /**
157
+ * Returns a copy of all items in the batcher
158
+ */
159
+ peekAllItems = () => {
160
+ return [...this.store.state.items];
161
+ };
162
+ #clearTimeout = () => {
163
+ if (this.#timeoutId) {
164
+ clearTimeout(this.#timeoutId);
165
+ this.#timeoutId = null;
166
+ }
167
+ };
168
+ /**
169
+ * Removes all items from the batcher
170
+ */
171
+ clear = () => {
172
+ this.#setState({
173
+ items: [],
174
+ isPending: false
175
+ });
176
+ };
177
+ /**
178
+ * Cancels any pending execution that was scheduled.
179
+ * Does NOT clear out the items.
180
+ */
181
+ cancel = () => {
182
+ this.#clearTimeout();
183
+ this.#setState({ isPending: false });
184
+ };
185
+ /**
186
+ * Resets the batcher state to its default values
187
+ */
188
+ reset = () => {
189
+ this.#setState(getDefaultBatcherState());
190
+ this.options.onItemsChange?.(this);
191
+ };
171
192
  };
172
193
  /**
173
194
  * Creates a batcher that processes items in batches.
@@ -194,5 +215,4 @@ function batch(fn, options) {
194
215
  }
195
216
 
196
217
  //#endregion
197
- export { Batcher, batch };
198
- //# sourceMappingURL=batcher.js.map
218
+ export { Batcher, batch };
@@ -1,7 +1,7 @@
1
1
  import { AnyFunction } from "./types.js";
2
2
  import { Store } from "@tanstack/store";
3
3
  //#region src/debouncer.d.ts
4
- interface DebouncerState<TFn extends AnyFunction> {
4
+ export interface DebouncerState<TFn extends AnyFunction> {
5
5
  /**
6
6
  * Whether the debouncer can execute on the leading edge of the timeout
7
7
  */
@@ -30,7 +30,7 @@ interface DebouncerState<TFn extends AnyFunction> {
30
30
  /**
31
31
  * Options for configuring a debounced function
32
32
  */
33
- interface DebouncerOptions<TFn extends AnyFunction> {
33
+ export interface DebouncerOptions<TFn extends AnyFunction> {
34
34
  /**
35
35
  * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
36
36
  * Can be a boolean or a function that returns a boolean.
@@ -71,7 +71,7 @@ interface DebouncerOptions<TFn extends AnyFunction> {
71
71
  /**
72
72
  * Utility function for sharing common `DebouncerOptions` options between different `Debouncer` instances.
73
73
  */
74
- declare function debouncerOptions<TFn extends AnyFunction = AnyFunction, TOptions extends Partial<DebouncerOptions<TFn>> = Partial<DebouncerOptions<TFn>>>(options: TOptions): TOptions;
74
+ export declare function debouncerOptions<TFn extends AnyFunction = AnyFunction, TOptions extends Partial<DebouncerOptions<TFn>> = Partial<DebouncerOptions<TFn>>>(options: TOptions): TOptions;
75
75
  /**
76
76
  * A class that creates a debounced function.
77
77
  *
@@ -104,7 +104,7 @@ declare function debouncerOptions<TFn extends AnyFunction = AnyFunction, TOption
104
104
  * });
105
105
  * ```
106
106
  */
107
- declare class Debouncer<TFn extends AnyFunction> {
107
+ export declare class Debouncer<TFn extends AnyFunction> {
108
108
  #private;
109
109
  fn: TFn;
110
110
  readonly store: Store<Readonly<DebouncerState<TFn>>>;
@@ -160,7 +160,5 @@ declare class Debouncer<TFn extends AnyFunction> {
160
160
  * inputElement.addEventListener('input', debounced);
161
161
  * ```
162
162
  */
163
- declare function debounce<TFn extends AnyFunction>(fn: TFn, initialOptions: DebouncerOptions<TFn>): (...args: Parameters<TFn>) => void;
164
- //#endregion
165
- export { Debouncer, DebouncerOptions, DebouncerState, debounce, debouncerOptions };
166
- //# sourceMappingURL=debouncer.d.ts.map
163
+ export declare function debounce<TFn extends AnyFunction>(fn: TFn, initialOptions: DebouncerOptions<TFn>): (...args: Parameters<TFn>) => void;
164
+ //#endregion