@tanstack/pacer 0.13.0 → 0.15.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-batcher.cjs +8 -0
- package/dist/cjs/async-batcher.cjs.map +1 -1
- package/dist/cjs/async-batcher.d.cts +8 -2
- package/dist/cjs/async-debouncer.cjs +17 -5
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +11 -1
- package/dist/cjs/async-queuer.cjs +14 -2
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +11 -1
- package/dist/cjs/async-rate-limiter.cjs +14 -2
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +11 -1
- package/dist/cjs/async-throttler.cjs +15 -3
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +11 -1
- package/dist/cjs/batcher.cjs +8 -0
- package/dist/cjs/batcher.cjs.map +1 -1
- package/dist/cjs/batcher.d.cts +8 -2
- package/dist/cjs/debouncer.cjs +15 -3
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +11 -1
- package/dist/cjs/event-client.cjs +18 -0
- package/dist/cjs/event-client.cjs.map +1 -0
- package/dist/cjs/event-client.d.cts +42 -0
- package/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/queuer.cjs +13 -1
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +11 -1
- package/dist/cjs/rate-limiter.cjs +15 -3
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +11 -1
- package/dist/cjs/throttler.cjs +15 -3
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +11 -1
- package/dist/cjs/utils.cjs +10 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/async-batcher.d.ts +8 -2
- package/dist/esm/async-batcher.js +9 -1
- package/dist/esm/async-batcher.js.map +1 -1
- package/dist/esm/async-debouncer.d.ts +11 -1
- package/dist/esm/async-debouncer.js +18 -6
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +11 -1
- package/dist/esm/async-queuer.js +15 -3
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +11 -1
- package/dist/esm/async-rate-limiter.js +15 -3
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +11 -1
- package/dist/esm/async-throttler.js +16 -4
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/batcher.d.ts +8 -2
- package/dist/esm/batcher.js +9 -1
- package/dist/esm/batcher.js.map +1 -1
- package/dist/esm/debouncer.d.ts +11 -1
- package/dist/esm/debouncer.js +16 -4
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/event-client.d.ts +42 -0
- package/dist/esm/event-client.js +18 -0
- package/dist/esm/event-client.js.map +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/queuer.d.ts +11 -1
- package/dist/esm/queuer.js +14 -2
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +11 -1
- package/dist/esm/rate-limiter.js +16 -4
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +11 -1
- package/dist/esm/throttler.js +16 -4
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +10 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +12 -1
- package/src/async-batcher.ts +23 -3
- package/src/async-debouncer.ts +30 -7
- package/src/async-queuer.ts +29 -4
- package/src/async-rate-limiter.ts +28 -4
- package/src/async-throttler.ts +27 -5
- package/src/batcher.ts +18 -3
- package/src/debouncer.ts +30 -5
- package/src/event-client.ts +63 -0
- package/src/index.ts +2 -0
- package/src/queuer.ts +26 -2
- package/src/rate-limiter.ts +29 -5
- package/src/throttler.ts +29 -5
- package/src/utils.ts +15 -0
package/src/async-debouncer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
|
-
import { parseFunctionOrValue } from './utils'
|
|
2
|
+
import { createKey, parseFunctionOrValue } from './utils'
|
|
3
|
+
import { emitChange, pacerEventClient } from './event-client'
|
|
3
4
|
import type { AnyAsyncFunction, OptionalKeys } from './types'
|
|
4
5
|
|
|
5
6
|
export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
|
|
@@ -27,6 +28,10 @@ export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
|
|
|
27
28
|
* The result from the most recent successful function execution
|
|
28
29
|
*/
|
|
29
30
|
lastResult: ReturnType<TFn> | undefined
|
|
31
|
+
/**
|
|
32
|
+
* Number of times maybeExecute has been called (for reduction calculations)
|
|
33
|
+
*/
|
|
34
|
+
maybeExecuteCount: number
|
|
30
35
|
/**
|
|
31
36
|
* Number of function executions that have completed (either successfully or with errors)
|
|
32
37
|
*/
|
|
@@ -44,17 +49,18 @@ export interface AsyncDebouncerState<TFn extends AnyAsyncFunction> {
|
|
|
44
49
|
function getDefaultAsyncDebouncerState<
|
|
45
50
|
TFn extends AnyAsyncFunction,
|
|
46
51
|
>(): AsyncDebouncerState<TFn> {
|
|
47
|
-
return
|
|
52
|
+
return {
|
|
48
53
|
canLeadingExecute: true,
|
|
49
54
|
errorCount: 0,
|
|
50
55
|
isExecuting: false,
|
|
51
56
|
isPending: false,
|
|
52
57
|
lastArgs: undefined,
|
|
53
58
|
lastResult: undefined,
|
|
59
|
+
maybeExecuteCount: 0,
|
|
54
60
|
settleCount: 0,
|
|
55
|
-
successCount: 0,
|
|
56
61
|
status: 'idle',
|
|
57
|
-
|
|
62
|
+
successCount: 0,
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/**
|
|
@@ -71,6 +77,11 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
|
71
77
|
* Initial state for the async debouncer
|
|
72
78
|
*/
|
|
73
79
|
initialState?: Partial<AsyncDebouncerState<TFn>>
|
|
80
|
+
/**
|
|
81
|
+
* Optional key to identify this async debouncer instance.
|
|
82
|
+
* If provided, the async debouncer will be identified by this key in the devtools and PacerProvider if applicable.
|
|
83
|
+
*/
|
|
84
|
+
key?: string
|
|
74
85
|
/**
|
|
75
86
|
* Whether to execute on the leading edge of the timeout.
|
|
76
87
|
* Defaults to false.
|
|
@@ -119,7 +130,7 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
|
|
|
119
130
|
|
|
120
131
|
type AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys<
|
|
121
132
|
AsyncDebouncerOptions<any>,
|
|
122
|
-
'initialState' | 'onError' | 'onSettled' | 'onSuccess'
|
|
133
|
+
'initialState' | 'onError' | 'onSettled' | 'onSuccess' | 'key'
|
|
123
134
|
>
|
|
124
135
|
|
|
125
136
|
const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = {
|
|
@@ -178,6 +189,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
178
189
|
readonly store: Store<Readonly<AsyncDebouncerState<TFn>>> = new Store<
|
|
179
190
|
AsyncDebouncerState<TFn>
|
|
180
191
|
>(getDefaultAsyncDebouncerState<TFn>())
|
|
192
|
+
key: string
|
|
181
193
|
options: AsyncDebouncerOptions<TFn>
|
|
182
194
|
#abortController: AbortController | null = null
|
|
183
195
|
#timeoutId: NodeJS.Timeout | null = null
|
|
@@ -186,15 +198,22 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
186
198
|
| null = null
|
|
187
199
|
|
|
188
200
|
constructor(
|
|
189
|
-
|
|
201
|
+
public fn: TFn,
|
|
190
202
|
initialOptions: AsyncDebouncerOptions<TFn>,
|
|
191
203
|
) {
|
|
204
|
+
this.key = createKey(initialOptions.key)
|
|
192
205
|
this.options = {
|
|
193
206
|
...defaultOptions,
|
|
194
207
|
...initialOptions,
|
|
195
208
|
throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
|
|
196
209
|
}
|
|
197
210
|
this.#setState(this.options.initialState ?? {})
|
|
211
|
+
|
|
212
|
+
pacerEventClient.on('d-AsyncDebouncer', (event) => {
|
|
213
|
+
if (event.payload.key !== this.key) return
|
|
214
|
+
this.#setState(event.payload.store.state as AsyncDebouncerState<TFn>)
|
|
215
|
+
this.setOptions(event.payload.options)
|
|
216
|
+
})
|
|
198
217
|
}
|
|
199
218
|
|
|
200
219
|
/**
|
|
@@ -229,6 +248,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
229
248
|
: 'idle',
|
|
230
249
|
}
|
|
231
250
|
})
|
|
251
|
+
emitChange('AsyncDebouncer', this)
|
|
232
252
|
}
|
|
233
253
|
|
|
234
254
|
/**
|
|
@@ -264,7 +284,10 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
|
|
|
264
284
|
): Promise<ReturnType<TFn> | undefined> => {
|
|
265
285
|
if (!this.#getEnabled()) return undefined
|
|
266
286
|
this.#cancelPendingExecution()
|
|
267
|
-
this.#setState({
|
|
287
|
+
this.#setState({
|
|
288
|
+
lastArgs: args,
|
|
289
|
+
maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
|
|
290
|
+
})
|
|
268
291
|
|
|
269
292
|
// Handle leading execution
|
|
270
293
|
if (this.options.leading && this.store.state.canLeadingExecute) {
|
package/src/async-queuer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
|
-
import { parseFunctionOrValue } from './utils'
|
|
2
|
+
import { createKey, parseFunctionOrValue } from './utils'
|
|
3
|
+
import { emitChange, pacerEventClient } from './event-client'
|
|
3
4
|
import type { OptionalKeys } from './types'
|
|
4
5
|
import type { QueuePosition } from './queuer'
|
|
5
6
|
|
|
@@ -8,6 +9,10 @@ export interface AsyncQueuerState<TValue> {
|
|
|
8
9
|
* Items currently being processed by the queuer
|
|
9
10
|
*/
|
|
10
11
|
activeItems: Array<TValue>
|
|
12
|
+
/**
|
|
13
|
+
* Number of times addItem has been called (for reduction calculations)
|
|
14
|
+
*/
|
|
15
|
+
addItemCount: number
|
|
11
16
|
/**
|
|
12
17
|
* Number of task executions that have resulted in errors
|
|
13
18
|
*/
|
|
@@ -71,8 +76,9 @@ export interface AsyncQueuerState<TValue> {
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
function getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {
|
|
74
|
-
return
|
|
79
|
+
return {
|
|
75
80
|
activeItems: [],
|
|
81
|
+
addItemCount: 0,
|
|
76
82
|
errorCount: 0,
|
|
77
83
|
expirationCount: 0,
|
|
78
84
|
isEmpty: true,
|
|
@@ -88,7 +94,7 @@ function getDefaultAsyncQueuerState<TValue>(): AsyncQueuerState<TValue> {
|
|
|
88
94
|
size: 0,
|
|
89
95
|
status: 'idle',
|
|
90
96
|
successCount: 0,
|
|
91
|
-
}
|
|
97
|
+
}
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
export interface AsyncQueuerOptions<TValue> {
|
|
@@ -132,6 +138,11 @@ export interface AsyncQueuerOptions<TValue> {
|
|
|
132
138
|
* Initial state for the async queuer
|
|
133
139
|
*/
|
|
134
140
|
initialState?: Partial<AsyncQueuerState<TValue>>
|
|
141
|
+
/**
|
|
142
|
+
* Optional key to identify this async queuer instance.
|
|
143
|
+
* If provided, the async queuer will be identified by this key in the devtools and PacerProvider if applicable.
|
|
144
|
+
*/
|
|
145
|
+
key?: string
|
|
135
146
|
/**
|
|
136
147
|
* Maximum number of items allowed in the queuer
|
|
137
148
|
*/
|
|
@@ -190,6 +201,7 @@ type AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys<
|
|
|
190
201
|
| 'onItemsChange'
|
|
191
202
|
| 'onExpire'
|
|
192
203
|
| 'onError'
|
|
204
|
+
| 'key'
|
|
193
205
|
>
|
|
194
206
|
|
|
195
207
|
const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = {
|
|
@@ -260,13 +272,15 @@ export class AsyncQueuer<TValue> {
|
|
|
260
272
|
readonly store: Store<Readonly<AsyncQueuerState<TValue>>> = new Store<
|
|
261
273
|
AsyncQueuerState<TValue>
|
|
262
274
|
>(getDefaultAsyncQueuerState<TValue>())
|
|
275
|
+
key: string
|
|
263
276
|
options: AsyncQueuerOptions<TValue>
|
|
264
277
|
#timeoutIds: Set<NodeJS.Timeout> = new Set()
|
|
265
278
|
|
|
266
279
|
constructor(
|
|
267
|
-
|
|
280
|
+
public fn: (item: TValue) => Promise<any>,
|
|
268
281
|
initialOptions: AsyncQueuerOptions<TValue> = {},
|
|
269
282
|
) {
|
|
283
|
+
this.key = createKey(initialOptions.key)
|
|
270
284
|
this.options = {
|
|
271
285
|
...defaultOptions,
|
|
272
286
|
...initialOptions,
|
|
@@ -290,6 +304,12 @@ export class AsyncQueuer<TValue> {
|
|
|
290
304
|
this.addItem(item, this.options.addItemsTo ?? 'back', isLast)
|
|
291
305
|
}
|
|
292
306
|
}
|
|
307
|
+
|
|
308
|
+
pacerEventClient.on('d-AsyncQueuer', (e) => {
|
|
309
|
+
if (e.payload.key !== this.key) return
|
|
310
|
+
this.#setState(e.payload.store.state)
|
|
311
|
+
this.setOptions(e.payload.options)
|
|
312
|
+
})
|
|
293
313
|
}
|
|
294
314
|
|
|
295
315
|
/**
|
|
@@ -324,6 +344,7 @@ export class AsyncQueuer<TValue> {
|
|
|
324
344
|
status,
|
|
325
345
|
}
|
|
326
346
|
})
|
|
347
|
+
emitChange('AsyncQueuer', this)
|
|
327
348
|
}
|
|
328
349
|
|
|
329
350
|
/**
|
|
@@ -402,6 +423,10 @@ export class AsyncQueuer<TValue> {
|
|
|
402
423
|
position: QueuePosition = this.options.addItemsTo ?? 'back',
|
|
403
424
|
runOnItemsChange: boolean = true,
|
|
404
425
|
): boolean => {
|
|
426
|
+
this.#setState({
|
|
427
|
+
addItemCount: this.store.state.addItemCount + 1,
|
|
428
|
+
})
|
|
429
|
+
|
|
405
430
|
if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {
|
|
406
431
|
this.#setState({
|
|
407
432
|
rejectionCount: this.store.state.rejectionCount + 1,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
|
-
import { parseFunctionOrValue } from './utils'
|
|
2
|
+
import { createKey, parseFunctionOrValue } from './utils'
|
|
3
|
+
import { emitChange, pacerEventClient } from './event-client'
|
|
3
4
|
import type { AnyAsyncFunction } from './types'
|
|
4
5
|
|
|
5
6
|
export interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
|
|
@@ -39,6 +40,10 @@ export interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
|
|
|
39
40
|
* Number of function executions that have completed successfully
|
|
40
41
|
*/
|
|
41
42
|
successCount: number
|
|
43
|
+
/**
|
|
44
|
+
* Number of times maybeExecute has been called (for reduction calculations)
|
|
45
|
+
*/
|
|
46
|
+
maybeExecuteCount: number
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
function getDefaultAsyncRateLimiterState<
|
|
@@ -50,10 +55,11 @@ function getDefaultAsyncRateLimiterState<
|
|
|
50
55
|
isExceeded: false,
|
|
51
56
|
isExecuting: false,
|
|
52
57
|
lastResult: undefined,
|
|
58
|
+
maybeExecuteCount: 0,
|
|
53
59
|
rejectionCount: 0,
|
|
54
60
|
settleCount: 0,
|
|
55
|
-
successCount: 0,
|
|
56
61
|
status: 'idle',
|
|
62
|
+
successCount: 0,
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -71,6 +77,11 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
|
|
|
71
77
|
* Initial state for the rate limiter
|
|
72
78
|
*/
|
|
73
79
|
initialState?: Partial<AsyncRateLimiterState<TFn>>
|
|
80
|
+
/**
|
|
81
|
+
* Optional key to identify this async rate limiter instance.
|
|
82
|
+
* If provided, the async rate limiter will be identified by this key in the devtools and PacerProvider if applicable.
|
|
83
|
+
*/
|
|
84
|
+
key?: string
|
|
74
85
|
/**
|
|
75
86
|
* Maximum number of executions allowed within the time window.
|
|
76
87
|
* Can be a number or a function that returns a number.
|
|
@@ -127,7 +138,7 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
|
|
|
127
138
|
|
|
128
139
|
const defaultOptions: Omit<
|
|
129
140
|
Required<AsyncRateLimiterOptions<any>>,
|
|
130
|
-
'initialState' | 'onError' | 'onReject' | 'onSettled' | 'onSuccess'
|
|
141
|
+
'initialState' | 'onError' | 'onReject' | 'onSettled' | 'onSuccess' | 'key'
|
|
131
142
|
> = {
|
|
132
143
|
enabled: true,
|
|
133
144
|
limit: 1,
|
|
@@ -206,13 +217,15 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
206
217
|
readonly store: Store<Readonly<AsyncRateLimiterState<TFn>>> = new Store<
|
|
207
218
|
AsyncRateLimiterState<TFn>
|
|
208
219
|
>(getDefaultAsyncRateLimiterState<TFn>())
|
|
220
|
+
key: string
|
|
209
221
|
options: AsyncRateLimiterOptions<TFn>
|
|
210
222
|
#timeoutIds: Set<NodeJS.Timeout> = new Set()
|
|
211
223
|
|
|
212
224
|
constructor(
|
|
213
|
-
|
|
225
|
+
public fn: TFn,
|
|
214
226
|
initialOptions: AsyncRateLimiterOptions<TFn>,
|
|
215
227
|
) {
|
|
228
|
+
this.key = createKey(initialOptions.key)
|
|
216
229
|
this.options = {
|
|
217
230
|
...defaultOptions,
|
|
218
231
|
...initialOptions,
|
|
@@ -222,6 +235,12 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
222
235
|
for (const executionTime of this.#getExecutionTimesInWindow()) {
|
|
223
236
|
this.#setCleanupTimeout(executionTime)
|
|
224
237
|
}
|
|
238
|
+
|
|
239
|
+
pacerEventClient.on('d-AsyncRateLimiter', (event) => {
|
|
240
|
+
if (event.payload.key !== this.key) return
|
|
241
|
+
this.#setState(event.payload.store.state)
|
|
242
|
+
this.setOptions(event.payload.options)
|
|
243
|
+
})
|
|
225
244
|
}
|
|
226
245
|
|
|
227
246
|
/**
|
|
@@ -251,6 +270,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
251
270
|
status,
|
|
252
271
|
}
|
|
253
272
|
})
|
|
273
|
+
emitChange('AsyncRateLimiter', this)
|
|
254
274
|
}
|
|
255
275
|
|
|
256
276
|
/**
|
|
@@ -302,6 +322,10 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
302
322
|
maybeExecute = async (
|
|
303
323
|
...args: Parameters<TFn>
|
|
304
324
|
): Promise<ReturnType<TFn> | undefined> => {
|
|
325
|
+
this.#setState({
|
|
326
|
+
maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
|
|
327
|
+
})
|
|
328
|
+
|
|
305
329
|
this.#cleanupOldExecutions()
|
|
306
330
|
|
|
307
331
|
const relevantExecutionTimes = this.#getExecutionTimesInWindow()
|
package/src/async-throttler.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
|
-
import { parseFunctionOrValue } from './utils'
|
|
2
|
+
import { createKey, parseFunctionOrValue } from './utils'
|
|
3
|
+
import { emitChange, pacerEventClient } from './event-client'
|
|
3
4
|
import type { AnyAsyncFunction, OptionalKeys } from './types'
|
|
4
5
|
|
|
5
6
|
export interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
@@ -27,6 +28,10 @@ export interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
|
27
28
|
* The result from the most recent successful function execution
|
|
28
29
|
*/
|
|
29
30
|
lastResult: ReturnType<TFn> | undefined
|
|
31
|
+
/**
|
|
32
|
+
* Number of times maybeExecute has been called (for reduction calculations)
|
|
33
|
+
*/
|
|
34
|
+
maybeExecuteCount: number
|
|
30
35
|
/**
|
|
31
36
|
* Timestamp when the next execution can occur in milliseconds
|
|
32
37
|
*/
|
|
@@ -48,18 +53,19 @@ export interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
|
48
53
|
function getDefaultAsyncThrottlerState<
|
|
49
54
|
TFn extends AnyAsyncFunction,
|
|
50
55
|
>(): AsyncThrottlerState<TFn> {
|
|
51
|
-
return
|
|
56
|
+
return {
|
|
52
57
|
errorCount: 0,
|
|
53
58
|
isExecuting: false,
|
|
54
59
|
isPending: false,
|
|
55
60
|
lastArgs: undefined,
|
|
56
61
|
lastExecutionTime: 0,
|
|
57
62
|
lastResult: undefined,
|
|
63
|
+
maybeExecuteCount: 0,
|
|
58
64
|
nextExecutionTime: undefined,
|
|
59
65
|
settleCount: 0,
|
|
60
66
|
status: 'idle',
|
|
61
67
|
successCount: 0,
|
|
62
|
-
}
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
/**
|
|
@@ -76,6 +82,11 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
76
82
|
* Initial state for the async throttler
|
|
77
83
|
*/
|
|
78
84
|
initialState?: Partial<AsyncThrottlerState<TFn>>
|
|
85
|
+
/**
|
|
86
|
+
* Optional key to identify this async throttler instance.
|
|
87
|
+
* If provided, the async throttler will be identified by this key in the devtools and PacerProvider if applicable.
|
|
88
|
+
*/
|
|
89
|
+
key?: string
|
|
79
90
|
/**
|
|
80
91
|
* Whether to execute the function immediately when called
|
|
81
92
|
* Defaults to true
|
|
@@ -189,6 +200,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
189
200
|
readonly store: Store<Readonly<AsyncThrottlerState<TFn>>> = new Store<
|
|
190
201
|
AsyncThrottlerState<TFn>
|
|
191
202
|
>(getDefaultAsyncThrottlerState<TFn>())
|
|
203
|
+
key: string
|
|
192
204
|
options: AsyncThrottlerOptions<TFn>
|
|
193
205
|
#abortController: AbortController | null = null
|
|
194
206
|
#timeoutId: NodeJS.Timeout | null = null
|
|
@@ -197,15 +209,21 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
197
209
|
| null = null
|
|
198
210
|
|
|
199
211
|
constructor(
|
|
200
|
-
|
|
212
|
+
public fn: TFn,
|
|
201
213
|
initialOptions: AsyncThrottlerOptions<TFn>,
|
|
202
214
|
) {
|
|
215
|
+
this.key = createKey(initialOptions.key)
|
|
203
216
|
this.options = {
|
|
204
217
|
...defaultOptions,
|
|
205
218
|
...initialOptions,
|
|
206
219
|
throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
|
|
207
220
|
}
|
|
208
221
|
this.#setState(this.options.initialState ?? {})
|
|
222
|
+
pacerEventClient.on('d-AsyncThrottler', (event) => {
|
|
223
|
+
if (event.payload.key !== this.key) return
|
|
224
|
+
this.#setState(event.payload.store.state as AsyncThrottlerState<TFn>)
|
|
225
|
+
this.setOptions(event.payload.options)
|
|
226
|
+
})
|
|
209
227
|
}
|
|
210
228
|
|
|
211
229
|
/**
|
|
@@ -240,6 +258,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
240
258
|
: 'idle',
|
|
241
259
|
}
|
|
242
260
|
})
|
|
261
|
+
emitChange('AsyncThrottler', this)
|
|
243
262
|
}
|
|
244
263
|
|
|
245
264
|
/**
|
|
@@ -286,7 +305,10 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
286
305
|
const timeSinceLastExecution = now - this.store.state.lastExecutionTime
|
|
287
306
|
const wait = this.#getWait()
|
|
288
307
|
// Store the most recent arguments for potential trailing execution
|
|
289
|
-
this.#setState({
|
|
308
|
+
this.#setState({
|
|
309
|
+
lastArgs: args,
|
|
310
|
+
maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
|
|
311
|
+
})
|
|
290
312
|
|
|
291
313
|
this.#resolvePreviousPromiseInternal()
|
|
292
314
|
|
package/src/batcher.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
|
-
import { parseFunctionOrValue } from './utils'
|
|
2
|
+
import { createKey, parseFunctionOrValue } from './utils'
|
|
3
|
+
import { emitChange, pacerEventClient } from './event-client'
|
|
3
4
|
import type { OptionalKeys } from './types'
|
|
4
5
|
|
|
5
6
|
export interface BatcherState<TValue> {
|
|
@@ -58,6 +59,11 @@ export interface BatcherOptions<TValue> {
|
|
|
58
59
|
* Initial state for the batcher
|
|
59
60
|
*/
|
|
60
61
|
initialState?: Partial<BatcherState<TValue>>
|
|
62
|
+
/**
|
|
63
|
+
* Optional key to identify this batcher instance.
|
|
64
|
+
* If provided, the batcher will be identified by this key in the devtools and PacerProvider if applicable.
|
|
65
|
+
*/
|
|
66
|
+
key?: string
|
|
61
67
|
/**
|
|
62
68
|
* Maximum number of items in a batch
|
|
63
69
|
* @default Infinity
|
|
@@ -87,7 +93,7 @@ export interface BatcherOptions<TValue> {
|
|
|
87
93
|
|
|
88
94
|
type BatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<
|
|
89
95
|
Required<BatcherOptions<TValue>>,
|
|
90
|
-
'initialState' | 'onExecute' | 'onItemsChange'
|
|
96
|
+
'initialState' | 'onExecute' | 'onItemsChange' | 'key'
|
|
91
97
|
>
|
|
92
98
|
|
|
93
99
|
const defaultOptions: BatcherOptionsWithOptionalCallbacks<any> = {
|
|
@@ -139,18 +145,26 @@ export class Batcher<TValue> {
|
|
|
139
145
|
readonly store: Store<Readonly<BatcherState<TValue>>> = new Store(
|
|
140
146
|
getDefaultBatcherState<TValue>(),
|
|
141
147
|
)
|
|
148
|
+
key: string
|
|
142
149
|
options: BatcherOptionsWithOptionalCallbacks<TValue>
|
|
143
150
|
#timeoutId: NodeJS.Timeout | null = null
|
|
144
151
|
|
|
145
152
|
constructor(
|
|
146
|
-
|
|
153
|
+
public fn: (items: Array<TValue>) => void,
|
|
147
154
|
initialOptions: BatcherOptions<TValue>,
|
|
148
155
|
) {
|
|
156
|
+
this.key = createKey(initialOptions.key)
|
|
149
157
|
this.options = {
|
|
150
158
|
...defaultOptions,
|
|
151
159
|
...initialOptions,
|
|
152
160
|
}
|
|
153
161
|
this.#setState(this.options.initialState ?? {})
|
|
162
|
+
|
|
163
|
+
pacerEventClient.on('d-Batcher', (event) => {
|
|
164
|
+
if (event.payload.key !== this.key) return
|
|
165
|
+
this.#setState(event.payload.store.state)
|
|
166
|
+
this.setOptions(event.payload.options)
|
|
167
|
+
})
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
/**
|
|
@@ -176,6 +190,7 @@ export class Batcher<TValue> {
|
|
|
176
190
|
status: isPending ? 'pending' : 'idle',
|
|
177
191
|
}
|
|
178
192
|
})
|
|
193
|
+
emitChange('Batcher', this)
|
|
179
194
|
}
|
|
180
195
|
|
|
181
196
|
#getWait = (): number => {
|
package/src/debouncer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Store } from '@tanstack/store'
|
|
2
|
-
import { parseFunctionOrValue } from './utils'
|
|
2
|
+
import { createKey, parseFunctionOrValue } from './utils'
|
|
3
|
+
import { emitChange, pacerEventClient } from './event-client'
|
|
3
4
|
import type { AnyFunction } from './types'
|
|
4
5
|
|
|
5
6
|
export interface DebouncerState<TFn extends AnyFunction> {
|
|
@@ -19,6 +20,10 @@ export interface DebouncerState<TFn extends AnyFunction> {
|
|
|
19
20
|
* The arguments from the most recent call to maybeExecute
|
|
20
21
|
*/
|
|
21
22
|
lastArgs: Parameters<TFn> | undefined
|
|
23
|
+
/**
|
|
24
|
+
* Number of times maybeExecute has been called (for reduction calculations)
|
|
25
|
+
*/
|
|
26
|
+
maybeExecuteCount: number
|
|
22
27
|
/**
|
|
23
28
|
* Current execution status - 'idle' when not active, 'pending' when waiting for timeout
|
|
24
29
|
*/
|
|
@@ -28,13 +33,14 @@ export interface DebouncerState<TFn extends AnyFunction> {
|
|
|
28
33
|
function getDefaultDebouncerState<
|
|
29
34
|
TFn extends AnyFunction,
|
|
30
35
|
>(): DebouncerState<TFn> {
|
|
31
|
-
return
|
|
36
|
+
return {
|
|
32
37
|
canLeadingExecute: true,
|
|
33
38
|
executionCount: 0,
|
|
34
39
|
isPending: false,
|
|
35
40
|
lastArgs: undefined,
|
|
36
41
|
status: 'idle',
|
|
37
|
-
|
|
42
|
+
maybeExecuteCount: 0,
|
|
43
|
+
}
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
/**
|
|
@@ -51,6 +57,11 @@ export interface DebouncerOptions<TFn extends AnyFunction> {
|
|
|
51
57
|
* Initial state for the debouncer
|
|
52
58
|
*/
|
|
53
59
|
initialState?: Partial<DebouncerState<TFn>>
|
|
60
|
+
/**
|
|
61
|
+
* A key to identify the debouncer.
|
|
62
|
+
* If provided, the debouncer will be identified by this key in the devtools and PacerProvider if applicable.
|
|
63
|
+
*/
|
|
64
|
+
key?: string
|
|
54
65
|
/**
|
|
55
66
|
* Whether to execute on the leading edge of the timeout.
|
|
56
67
|
* The first call will execute immediately and the rest will wait the delay.
|
|
@@ -76,7 +87,7 @@ export interface DebouncerOptions<TFn extends AnyFunction> {
|
|
|
76
87
|
|
|
77
88
|
const defaultOptions: Omit<
|
|
78
89
|
Required<DebouncerOptions<any>>,
|
|
79
|
-
'initialState' | 'onExecute'
|
|
90
|
+
'initialState' | 'onExecute' | 'key'
|
|
80
91
|
> = {
|
|
81
92
|
enabled: true,
|
|
82
93
|
leading: false,
|
|
@@ -119,18 +130,26 @@ export class Debouncer<TFn extends AnyFunction> {
|
|
|
119
130
|
readonly store: Store<Readonly<DebouncerState<TFn>>> = new Store(
|
|
120
131
|
getDefaultDebouncerState<TFn>(),
|
|
121
132
|
)
|
|
133
|
+
key: string
|
|
122
134
|
options: DebouncerOptions<TFn>
|
|
123
135
|
#timeoutId: NodeJS.Timeout | undefined
|
|
124
136
|
|
|
125
137
|
constructor(
|
|
126
|
-
|
|
138
|
+
public fn: TFn,
|
|
127
139
|
initialOptions: DebouncerOptions<TFn>,
|
|
128
140
|
) {
|
|
141
|
+
this.key = createKey(initialOptions.key)
|
|
129
142
|
this.options = {
|
|
130
143
|
...defaultOptions,
|
|
131
144
|
...initialOptions,
|
|
132
145
|
}
|
|
133
146
|
this.#setState(this.options.initialState ?? {})
|
|
147
|
+
|
|
148
|
+
pacerEventClient.on('d-Debouncer', (event) => {
|
|
149
|
+
if (event.payload.key !== this.key) return
|
|
150
|
+
this.#setState(event.payload.store.state as DebouncerState<TFn>)
|
|
151
|
+
this.setOptions(event.payload.options)
|
|
152
|
+
})
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
/**
|
|
@@ -161,6 +180,7 @@ export class Debouncer<TFn extends AnyFunction> {
|
|
|
161
180
|
: 'idle',
|
|
162
181
|
}
|
|
163
182
|
})
|
|
183
|
+
emitChange('Debouncer', this)
|
|
164
184
|
}
|
|
165
185
|
|
|
166
186
|
/**
|
|
@@ -183,6 +203,11 @@ export class Debouncer<TFn extends AnyFunction> {
|
|
|
183
203
|
*/
|
|
184
204
|
maybeExecute = (...args: Parameters<TFn>): void => {
|
|
185
205
|
if (!this.#getEnabled()) return undefined
|
|
206
|
+
|
|
207
|
+
this.#setState({
|
|
208
|
+
maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
|
|
209
|
+
})
|
|
210
|
+
|
|
186
211
|
let _didLeadingExecute = false
|
|
187
212
|
|
|
188
213
|
// Handle leading execution
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { EventClient } from '@tanstack/devtools-event-client'
|
|
2
|
+
import type { AsyncBatcher } from './async-batcher'
|
|
3
|
+
import type { AsyncDebouncer } from './async-debouncer'
|
|
4
|
+
import type { AsyncQueuer } from './async-queuer'
|
|
5
|
+
import type { AsyncRateLimiter } from './async-rate-limiter'
|
|
6
|
+
import type { AsyncThrottler } from './async-throttler'
|
|
7
|
+
import type { Debouncer } from './debouncer'
|
|
8
|
+
import type { Batcher } from './batcher'
|
|
9
|
+
import type { Queuer } from './queuer'
|
|
10
|
+
import type { RateLimiter } from './rate-limiter'
|
|
11
|
+
import type { Throttler } from './throttler'
|
|
12
|
+
|
|
13
|
+
export interface PacerEventMap {
|
|
14
|
+
'pacer:d-AsyncBatcher': AsyncBatcher<any>
|
|
15
|
+
'pacer:d-AsyncDebouncer': AsyncDebouncer<any>
|
|
16
|
+
'pacer:d-AsyncQueuer': AsyncQueuer<any>
|
|
17
|
+
'pacer:d-AsyncRateLimiter': AsyncRateLimiter<any>
|
|
18
|
+
'pacer:d-AsyncThrottler': AsyncThrottler<any>
|
|
19
|
+
'pacer:d-Batcher': Batcher<any>
|
|
20
|
+
'pacer:d-Debouncer': Debouncer<any>
|
|
21
|
+
'pacer:d-Queuer': Queuer<any>
|
|
22
|
+
'pacer:d-RateLimiter': RateLimiter<any>
|
|
23
|
+
'pacer:d-Throttler': Throttler<any>
|
|
24
|
+
'pacer:AsyncBatcher': AsyncBatcher<any>
|
|
25
|
+
'pacer:AsyncDebouncer': AsyncDebouncer<any>
|
|
26
|
+
'pacer:AsyncQueuer': AsyncQueuer<any>
|
|
27
|
+
'pacer:AsyncRateLimiter': AsyncRateLimiter<any>
|
|
28
|
+
'pacer:AsyncThrottler': AsyncThrottler<any>
|
|
29
|
+
'pacer:Batcher': Batcher<any>
|
|
30
|
+
'pacer:Debouncer': Debouncer<any>
|
|
31
|
+
'pacer:Queuer': Queuer<any>
|
|
32
|
+
'pacer:RateLimiter': RateLimiter<any>
|
|
33
|
+
'pacer:Throttler': Throttler<any>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PacerEventName = keyof PacerEventMap extends `pacer:${infer T}`
|
|
37
|
+
? T
|
|
38
|
+
: never
|
|
39
|
+
|
|
40
|
+
class PacerEventClient extends EventClient<PacerEventMap> {
|
|
41
|
+
constructor(props?: { debug: boolean }) {
|
|
42
|
+
super({
|
|
43
|
+
pluginId: 'pacer',
|
|
44
|
+
debug: props?.debug,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const emitChange = <
|
|
50
|
+
TSuffix extends Extract<
|
|
51
|
+
keyof PacerEventMap,
|
|
52
|
+
`${string}:${string}`
|
|
53
|
+
> extends `${string}:${infer S}`
|
|
54
|
+
? S
|
|
55
|
+
: never,
|
|
56
|
+
>(
|
|
57
|
+
event: TSuffix,
|
|
58
|
+
payload: PacerEventMap[`pacer:${TSuffix}`],
|
|
59
|
+
) => {
|
|
60
|
+
pacerEventClient.emit(event, payload)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const pacerEventClient = new PacerEventClient()
|