@tanstack/pacer 0.4.0 → 0.6.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/dist/cjs/async-debouncer.cjs +39 -15
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +63 -11
- package/dist/cjs/async-queuer.cjs +102 -119
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +94 -52
- package/dist/cjs/async-rate-limiter.cjs +48 -16
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +74 -9
- package/dist/cjs/async-throttler.cjs +42 -17
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +62 -10
- package/dist/cjs/debouncer.cjs +16 -3
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +14 -4
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/queuer.cjs +13 -5
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +9 -3
- package/dist/cjs/rate-limiter.cjs +26 -7
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +20 -6
- package/dist/cjs/throttler.cjs +19 -5
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +15 -4
- package/dist/cjs/types.d.cts +1 -0
- package/dist/cjs/utils.cjs +18 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +3 -0
- package/dist/esm/async-debouncer.d.ts +63 -11
- package/dist/esm/async-debouncer.js +39 -15
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +94 -52
- package/dist/esm/async-queuer.js +102 -119
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +74 -9
- package/dist/esm/async-rate-limiter.js +48 -16
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +62 -10
- package/dist/esm/async-throttler.js +42 -17
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/debouncer.d.ts +14 -4
- package/dist/esm/debouncer.js +16 -3
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/queuer.d.ts +9 -3
- package/dist/esm/queuer.js +13 -5
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +20 -6
- package/dist/esm/rate-limiter.js +26 -7
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +15 -4
- package/dist/esm/throttler.js +19 -5
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +19 -8
- package/dist/esm/utils.js.map +1 -1
- package/package.json +9 -3
- package/src/async-debouncer.ts +89 -22
- package/src/async-queuer.ts +205 -175
- package/src/async-rate-limiter.ts +111 -25
- package/src/async-throttler.ts +92 -24
- package/src/debouncer.ts +24 -7
- package/src/queuer.ts +19 -7
- package/src/rate-limiter.ts +37 -13
- package/src/throttler.ts +28 -9
- package/src/types.ts +3 -0
- package/src/utils.ts +19 -5
package/dist/esm/utils.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
+
function isFunction(value) {
|
|
2
|
+
return typeof value === "function";
|
|
3
|
+
}
|
|
4
|
+
function parseFunctionOrValue(value, ...args) {
|
|
5
|
+
return isFunction(value) ? value(...args) : value;
|
|
6
|
+
}
|
|
1
7
|
function bindInstanceMethods(instance) {
|
|
2
|
-
return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).reduce(
|
|
9
|
+
(acc, key) => {
|
|
10
|
+
const method = instance[key];
|
|
11
|
+
if (isFunction(method)) {
|
|
12
|
+
acc[key] = method.bind(instance);
|
|
13
|
+
}
|
|
14
|
+
return acc;
|
|
15
|
+
},
|
|
16
|
+
instance
|
|
17
|
+
);
|
|
9
18
|
}
|
|
10
19
|
export {
|
|
11
|
-
bindInstanceMethods
|
|
20
|
+
bindInstanceMethods,
|
|
21
|
+
isFunction,
|
|
22
|
+
parseFunctionOrValue
|
|
12
23
|
};
|
|
13
24
|
//# sourceMappingURL=utils.js.map
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type { AnyFunction } from './types'\n\nexport function isFunction<T extends AnyFunction>(value: any): value is T {\n return typeof value === 'function'\n}\n\nexport function parseFunctionOrValue<T, TArgs extends Array<any>>(\n value: T | ((...args: TArgs) => T),\n ...args: TArgs\n): T {\n return isFunction(value) ? value(...args) : value\n}\n\nexport function bindInstanceMethods<T extends Record<string, any>>(\n instance: T,\n): T {\n return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).reduce(\n (acc: any, key) => {\n const method = instance[key as keyof T]\n if (isFunction(method)) {\n acc[key] = method.bind(instance)\n }\n return acc\n },\n instance,\n )\n}\n"],"names":[],"mappings":"AAEO,SAAS,WAAkC,OAAwB;AACxE,SAAO,OAAO,UAAU;AAC1B;AAEgB,SAAA,qBACd,UACG,MACA;AACH,SAAO,WAAW,KAAK,IAAI,MAAM,GAAG,IAAI,IAAI;AAC9C;AAEO,SAAS,oBACd,UACG;AACH,SAAO,OAAO,oBAAoB,OAAO,eAAe,QAAQ,CAAC,EAAE;AAAA,IACjE,CAAC,KAAU,QAAQ;AACX,YAAA,SAAS,SAAS,GAAc;AAClC,UAAA,WAAW,MAAM,GAAG;AACtB,YAAI,GAAG,IAAI,OAAO,KAAK,QAAQ;AAAA,MAAA;AAE1B,aAAA;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/pacer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Utilities for debouncing, throttling, rate-limiting, queuing, and more.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -131,8 +131,14 @@
|
|
|
131
131
|
"types": "./dist/esm/types.d.ts"
|
|
132
132
|
},
|
|
133
133
|
"./utils": {
|
|
134
|
-
"
|
|
135
|
-
|
|
134
|
+
"import": {
|
|
135
|
+
"types": "./dist/esm/utils.d.ts",
|
|
136
|
+
"default": "./dist/esm/utils.js"
|
|
137
|
+
},
|
|
138
|
+
"require": {
|
|
139
|
+
"types": "./dist/cjs/utils.d.cts",
|
|
140
|
+
"default": "./dist/cjs/utils.cjs"
|
|
141
|
+
}
|
|
136
142
|
},
|
|
137
143
|
"./package.json": "./package.json"
|
|
138
144
|
},
|
package/src/async-debouncer.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { parseFunctionOrValue } from './utils'
|
|
2
|
+
import type { AnyAsyncFunction, OptionalKeys } from './types'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Options for configuring an async debounced function
|
|
@@ -6,16 +7,19 @@ import type { AnyAsyncFunction } from './types'
|
|
|
6
7
|
export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
7
8
|
/**
|
|
8
9
|
* Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
|
|
10
|
+
* Can be a boolean or a function that returns a boolean.
|
|
9
11
|
* Defaults to true.
|
|
10
12
|
*/
|
|
11
|
-
enabled?: boolean
|
|
13
|
+
enabled?: boolean | ((debouncer: AsyncDebouncer<TFn>) => boolean)
|
|
12
14
|
/**
|
|
13
15
|
* Whether to execute on the leading edge of the timeout.
|
|
14
16
|
* Defaults to false.
|
|
15
17
|
*/
|
|
16
18
|
leading?: boolean
|
|
17
19
|
/**
|
|
18
|
-
* Optional error handler for when the debounced function throws
|
|
20
|
+
* Optional error handler for when the debounced function throws.
|
|
21
|
+
* If provided, the handler will be called with the error and debouncer instance.
|
|
22
|
+
* This can be used alongside throwOnError - the handler will be called before any error is thrown.
|
|
19
23
|
*/
|
|
20
24
|
onError?: (error: unknown, debouncer: AsyncDebouncer<TFn>) => void
|
|
21
25
|
/**
|
|
@@ -26,24 +30,33 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
|
26
30
|
* Optional callback to call when the debounced function is executed
|
|
27
31
|
*/
|
|
28
32
|
onSuccess?: (result: ReturnType<TFn>, debouncer: AsyncDebouncer<TFn>) => void
|
|
33
|
+
/**
|
|
34
|
+
* Whether to throw errors when they occur.
|
|
35
|
+
* Defaults to true if no onError handler is provided, false if an onError handler is provided.
|
|
36
|
+
* Can be explicitly set to override these defaults.
|
|
37
|
+
*/
|
|
38
|
+
throwOnError?: boolean
|
|
29
39
|
/**
|
|
30
40
|
* Whether to execute on the trailing edge of the timeout.
|
|
31
41
|
* Defaults to true.
|
|
32
42
|
*/
|
|
33
43
|
trailing?: boolean
|
|
34
44
|
/**
|
|
35
|
-
* Delay in milliseconds to wait after the last call before executing
|
|
45
|
+
* Delay in milliseconds to wait after the last call before executing.
|
|
46
|
+
* Can be a number or a function that returns a number.
|
|
36
47
|
* Defaults to 0ms
|
|
37
48
|
*/
|
|
38
|
-
wait: number
|
|
49
|
+
wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)
|
|
39
50
|
}
|
|
40
51
|
|
|
41
|
-
|
|
52
|
+
type AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<
|
|
53
|
+
AsyncDebouncerOptions<any>,
|
|
54
|
+
'onError' | 'onSettled' | 'onSuccess'
|
|
55
|
+
>
|
|
56
|
+
|
|
57
|
+
const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {
|
|
42
58
|
enabled: true,
|
|
43
59
|
leading: false,
|
|
44
|
-
onError: () => {},
|
|
45
|
-
onSettled: () => {},
|
|
46
|
-
onSuccess: () => {},
|
|
47
60
|
trailing: true,
|
|
48
61
|
wait: 0,
|
|
49
62
|
}
|
|
@@ -62,12 +75,23 @@ const defaultOptions: Required<AsyncDebouncerOptions<any>> = {
|
|
|
62
75
|
* making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
|
|
63
76
|
* instead of setting the result on a state variable from within the debounced function.
|
|
64
77
|
*
|
|
78
|
+
* Error Handling:
|
|
79
|
+
* - If an error occurs during execution and no `onError` handler is provided, the error will be thrown and propagate up to the caller.
|
|
80
|
+
* - If an `onError` handler is provided, errors will be caught and passed to the handler instead of being thrown.
|
|
81
|
+
* - The error count can be tracked using `getErrorCount()`.
|
|
82
|
+
* - The debouncer maintains its state and can continue to be used after an error occurs.
|
|
83
|
+
*
|
|
65
84
|
* @example
|
|
66
85
|
* ```ts
|
|
67
86
|
* const asyncDebouncer = new AsyncDebouncer(async (value: string) => {
|
|
68
87
|
* const results = await searchAPI(value);
|
|
69
88
|
* return results; // Return value is preserved
|
|
70
|
-
* }, {
|
|
89
|
+
* }, {
|
|
90
|
+
* wait: 500,
|
|
91
|
+
* onError: (error) => {
|
|
92
|
+
* console.error('Search failed:', error);
|
|
93
|
+
* }
|
|
94
|
+
* });
|
|
71
95
|
*
|
|
72
96
|
* // Called on each keystroke but only executes after 500ms of no typing
|
|
73
97
|
* // Returns the API response directly
|
|
@@ -75,6 +99,7 @@ const defaultOptions: Required<AsyncDebouncerOptions<any>> = {
|
|
|
75
99
|
* ```
|
|
76
100
|
*/
|
|
77
101
|
export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
102
|
+
private _options: AsyncDebouncerOptionsWithOptionalCallbacks
|
|
78
103
|
private _abortController: AbortController | null = null
|
|
79
104
|
private _canLeadingExecute = true
|
|
80
105
|
private _errorCount = 0
|
|
@@ -82,7 +107,6 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
82
107
|
private _isPending = false
|
|
83
108
|
private _lastArgs: Parameters<TFn> | undefined
|
|
84
109
|
private _lastResult: ReturnType<TFn> | undefined
|
|
85
|
-
private _options: Required<AsyncDebouncerOptions<TFn>>
|
|
86
110
|
private _settleCount = 0
|
|
87
111
|
private _successCount = 0
|
|
88
112
|
private _timeoutId: NodeJS.Timeout | null = null
|
|
@@ -94,6 +118,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
94
118
|
this._options = {
|
|
95
119
|
...defaultOptions,
|
|
96
120
|
...initialOptions,
|
|
121
|
+
throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
|
|
97
122
|
}
|
|
98
123
|
}
|
|
99
124
|
|
|
@@ -113,13 +138,37 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
113
138
|
/**
|
|
114
139
|
* Returns the current debouncer options
|
|
115
140
|
*/
|
|
116
|
-
getOptions():
|
|
141
|
+
getOptions(): AsyncDebouncerOptions<TFn> {
|
|
117
142
|
return this._options
|
|
118
143
|
}
|
|
119
144
|
|
|
120
145
|
/**
|
|
121
|
-
*
|
|
122
|
-
|
|
146
|
+
* Returns the current debouncer enabled state
|
|
147
|
+
*/
|
|
148
|
+
getEnabled(): boolean {
|
|
149
|
+
return !!parseFunctionOrValue(this._options.enabled, this)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Returns the current debouncer wait state
|
|
154
|
+
*/
|
|
155
|
+
getWait(): number {
|
|
156
|
+
return parseFunctionOrValue(this._options.wait, this)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Attempts to execute the debounced function.
|
|
161
|
+
* If a call is already in progress, it will be queued.
|
|
162
|
+
*
|
|
163
|
+
* Error Handling:
|
|
164
|
+
* - If the debounced function throws and no `onError` handler is configured,
|
|
165
|
+
* the error will be thrown from this method.
|
|
166
|
+
* - If an `onError` handler is configured, errors will be caught and passed to the handler,
|
|
167
|
+
* and this method will return undefined.
|
|
168
|
+
* - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
|
|
169
|
+
*
|
|
170
|
+
* @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
|
|
171
|
+
* @throws The error from the debounced function if no onError handler is configured
|
|
123
172
|
*/
|
|
124
173
|
async maybeExecute(
|
|
125
174
|
...args: Parameters<TFn>
|
|
@@ -149,29 +198,34 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
149
198
|
// Reset state and resolve
|
|
150
199
|
this._canLeadingExecute = true
|
|
151
200
|
resolve(this._lastResult)
|
|
152
|
-
}, this.
|
|
201
|
+
}, this.getWait())
|
|
153
202
|
})
|
|
154
203
|
}
|
|
155
204
|
|
|
156
205
|
private async executeFunction(
|
|
157
206
|
...args: Parameters<TFn>
|
|
158
207
|
): Promise<ReturnType<TFn> | undefined> {
|
|
159
|
-
if (!this.
|
|
208
|
+
if (!this.getEnabled()) return undefined
|
|
160
209
|
this._abortController = new AbortController()
|
|
161
210
|
try {
|
|
162
211
|
this._isExecuting = true
|
|
163
212
|
this._lastResult = await this.fn(...args) // EXECUTE!
|
|
164
213
|
this._successCount++
|
|
165
|
-
this._options.onSuccess(this._lastResult!, this)
|
|
214
|
+
this._options.onSuccess?.(this._lastResult!, this)
|
|
166
215
|
} catch (error) {
|
|
167
216
|
this._errorCount++
|
|
168
|
-
this._options.onError(error, this)
|
|
217
|
+
this._options.onError?.(error, this)
|
|
218
|
+
if (this._options.throwOnError) {
|
|
219
|
+
throw error
|
|
220
|
+
} else {
|
|
221
|
+
console.error(error)
|
|
222
|
+
}
|
|
169
223
|
} finally {
|
|
170
224
|
this._isExecuting = false
|
|
171
225
|
this._isPending = false
|
|
172
226
|
this._settleCount++
|
|
173
227
|
this._abortController = null
|
|
174
|
-
this._options.onSettled(this)
|
|
228
|
+
this._options.onSettled?.(this)
|
|
175
229
|
}
|
|
176
230
|
return this._lastResult
|
|
177
231
|
}
|
|
@@ -233,7 +287,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
233
287
|
* Returns `true` if there is a pending execution queued up for trailing execution
|
|
234
288
|
*/
|
|
235
289
|
getIsPending(): boolean {
|
|
236
|
-
return this.
|
|
290
|
+
return this.getEnabled() && this._isPending
|
|
237
291
|
}
|
|
238
292
|
|
|
239
293
|
/**
|
|
@@ -253,12 +307,25 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
253
307
|
* making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
|
|
254
308
|
* instead of setting the result on a state variable from within the debounced function.
|
|
255
309
|
*
|
|
310
|
+
* Error Handling:
|
|
311
|
+
* - If an `onError` handler is provided, it will be called with the error and debouncer instance
|
|
312
|
+
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
313
|
+
* - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
|
|
314
|
+
* - The error state can be checked using the underlying AsyncDebouncer instance
|
|
315
|
+
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
316
|
+
*
|
|
256
317
|
* @example
|
|
257
318
|
* ```ts
|
|
258
319
|
* const debounced = asyncDebounce(async (value: string) => {
|
|
259
320
|
* const result = await saveToAPI(value);
|
|
260
321
|
* return result; // Return value is preserved
|
|
261
|
-
* }, {
|
|
322
|
+
* }, {
|
|
323
|
+
* wait: 1000,
|
|
324
|
+
* onError: (error) => {
|
|
325
|
+
* console.error('API call failed:', error);
|
|
326
|
+
* },
|
|
327
|
+
* throwOnError: true // Will both log the error and throw it
|
|
328
|
+
* });
|
|
262
329
|
*
|
|
263
330
|
* // Will only execute once, 1 second after the last call
|
|
264
331
|
* // Returns the API response directly
|
|
@@ -267,7 +334,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
267
334
|
*/
|
|
268
335
|
export function asyncDebounce<TFn extends AnyAsyncFunction>(
|
|
269
336
|
fn: TFn,
|
|
270
|
-
initialOptions:
|
|
337
|
+
initialOptions: AsyncDebouncerOptions<TFn>,
|
|
271
338
|
) {
|
|
272
339
|
const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)
|
|
273
340
|
return asyncDebouncer.maybeExecute.bind(asyncDebouncer)
|