@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.
- package/README.md +4 -4
- package/dist/async-batcher.d.ts +11 -13
- package/dist/async-batcher.js +185 -131
- package/dist/async-debouncer.d.ts +6 -8
- package/dist/async-debouncer.js +186 -142
- package/dist/async-queuer.d.ts +11 -13
- package/dist/async-queuer.js +383 -290
- package/dist/async-rate-limiter.d.ts +6 -8
- package/dist/async-rate-limiter.js +197 -143
- package/dist/async-retryer.d.ts +6 -8
- package/dist/async-retryer.js +209 -179
- package/dist/async-throttler.d.ts +6 -8
- package/dist/async-throttler.js +214 -157
- package/dist/batcher.d.ts +5 -7
- package/dist/batcher.js +107 -87
- package/dist/debouncer.d.ts +6 -8
- package/dist/debouncer.js +99 -86
- package/dist/event-client.d.ts +8 -10
- package/dist/event-client.js +1 -2
- package/dist/queuer.d.ts +7 -9
- package/dist/queuer.js +277 -212
- package/dist/rate-limiter.d.ts +6 -8
- package/dist/rate-limiter.js +127 -109
- package/dist/throttler.d.ts +6 -8
- package/dist/throttler.js +127 -90
- package/dist/types.d.ts +4 -6
- package/dist/utils.d.ts +3 -5
- package/dist/utils.js +1 -2
- package/package.json +21 -68
- package/dist/async-batcher.cjs +0 -337
- package/dist/async-batcher.cjs.map +0 -1
- package/dist/async-batcher.d.cts +0 -343
- package/dist/async-batcher.js.map +0 -1
- package/dist/async-debouncer.cjs +0 -327
- package/dist/async-debouncer.cjs.map +0 -1
- package/dist/async-debouncer.d.cts +0 -299
- package/dist/async-debouncer.js.map +0 -1
- package/dist/async-queuer.cjs +0 -516
- 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 -370
- package/dist/async-rate-limiter.cjs.map +0 -1
- package/dist/async-rate-limiter.d.cts +0 -356
- package/dist/async-rate-limiter.js.map +0 -1
- package/dist/async-retryer.cjs +0 -365
- package/dist/async-retryer.cjs.map +0 -1
- package/dist/async-retryer.d.cts +0 -321
- package/dist/async-retryer.js.map +0 -1
- package/dist/async-throttler.cjs +0 -344
- package/dist/async-throttler.cjs.map +0 -1
- package/dist/async-throttler.d.cts +0 -319
- 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 -179
- 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 -166
- 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 -65
- 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 -406
- 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 -214
- 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 -206
- 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 -13
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.d.cts +0 -7
- package/dist/utils.js.map +0 -1
- package/src/async-batcher.ts +0 -594
- package/src/async-debouncer.ts +0 -566
- package/src/async-queuer.ts +0 -988
- package/src/async-rate-limiter.ts +0 -648
- package/src/async-retryer.ts +0 -673
- package/src/async-throttler.ts +0 -634
- 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 -751
- 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/dist/async-throttler.js
CHANGED
|
@@ -91,157 +91,15 @@ const defaultOptions = {
|
|
|
91
91
|
* ```
|
|
92
92
|
*/
|
|
93
93
|
var AsyncThrottler = class {
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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 };
|
package/dist/debouncer.d.ts
CHANGED
|
@@ -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
|