@tanstack/pacer 0.14.0 → 0.15.1
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 +7 -1
- package/dist/cjs/async-debouncer.cjs +15 -3
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +10 -0
- package/dist/cjs/async-queuer.cjs +13 -1
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +10 -0
- 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 +10 -0
- package/dist/cjs/async-throttler.cjs +13 -1
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +10 -0
- package/dist/cjs/batcher.cjs +8 -0
- package/dist/cjs/batcher.cjs.map +1 -1
- package/dist/cjs/batcher.d.cts +7 -1
- package/dist/cjs/debouncer.cjs +13 -1
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +10 -0
- 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 +14 -2
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +10 -0
- package/dist/cjs/rate-limiter.cjs +13 -1
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +10 -0
- package/dist/cjs/throttler.cjs +13 -1
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +10 -0
- 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 +7 -1
- package/dist/esm/async-batcher.js +9 -1
- package/dist/esm/async-batcher.js.map +1 -1
- package/dist/esm/async-debouncer.d.ts +10 -0
- package/dist/esm/async-debouncer.js +16 -4
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +10 -0
- package/dist/esm/async-queuer.js +14 -2
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +10 -0
- 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 +10 -0
- package/dist/esm/async-throttler.js +14 -2
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/batcher.d.ts +7 -1
- package/dist/esm/batcher.js +9 -1
- package/dist/esm/batcher.js.map +1 -1
- package/dist/esm/debouncer.d.ts +10 -0
- package/dist/esm/debouncer.js +14 -2
- 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 +10 -0
- package/dist/esm/queuer.js +15 -3
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +10 -0
- package/dist/esm/rate-limiter.js +14 -2
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +10 -0
- package/dist/esm/throttler.js +14 -2
- 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 +22 -2
- package/src/async-debouncer.ts +27 -4
- package/src/async-queuer.ts +32 -2
- package/src/async-rate-limiter.ts +27 -3
- package/src/async-throttler.ts +24 -2
- package/src/batcher.ts +17 -2
- package/src/debouncer.ts +27 -2
- package/src/event-client.ts +63 -0
- package/src/index.ts +2 -0
- package/src/queuer.ts +31 -2
- package/src/rate-limiter.ts +26 -2
- package/src/throttler.ts +26 -2
- package/src/utils.ts +15 -0
|
@@ -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,6 +217,7 @@ 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
|
|
|
@@ -213,6 +225,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
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
|
*/
|
|
@@ -55,6 +60,7 @@ function getDefaultAsyncThrottlerState<
|
|
|
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',
|
|
@@ -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
|
|
@@ -200,12 +212,18 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
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,6 +145,7 @@ 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
|
|
|
@@ -146,11 +153,18 @@ export class Batcher<TValue> {
|
|
|
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
|
*/
|
|
@@ -34,6 +39,7 @@ function getDefaultDebouncerState<
|
|
|
34
39
|
isPending: false,
|
|
35
40
|
lastArgs: undefined,
|
|
36
41
|
status: 'idle',
|
|
42
|
+
maybeExecuteCount: 0,
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -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,6 +130,7 @@ 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
|
|
|
@@ -126,11 +138,18 @@ export class Debouncer<TFn extends AnyFunction> {
|
|
|
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()
|
package/src/index.ts
CHANGED
package/src/queuer.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
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
|
|
|
4
5
|
export interface QueuerState<TValue> {
|
|
6
|
+
/**
|
|
7
|
+
* Number of times addItem has been called (for reduction calculations)
|
|
8
|
+
*/
|
|
9
|
+
addItemCount: number
|
|
5
10
|
/**
|
|
6
11
|
* Number of items that have been processed by the queuer
|
|
7
12
|
*/
|
|
@@ -66,6 +71,7 @@ function getDefaultQueuerState<TValue>(): QueuerState<TValue> {
|
|
|
66
71
|
rejectionCount: 0,
|
|
67
72
|
size: 0,
|
|
68
73
|
status: 'idle',
|
|
74
|
+
addItemCount: 0,
|
|
69
75
|
}
|
|
70
76
|
}
|
|
71
77
|
|
|
@@ -108,6 +114,11 @@ export interface QueuerOptions<TValue> {
|
|
|
108
114
|
* Initial state for the queuer
|
|
109
115
|
*/
|
|
110
116
|
initialState?: Partial<QueuerState<TValue>>
|
|
117
|
+
/**
|
|
118
|
+
* Optional key to identify this queuer instance.
|
|
119
|
+
* If provided, the queuer will be identified by this key in the devtools and PacerProvider if applicable.
|
|
120
|
+
*/
|
|
121
|
+
key?: string
|
|
111
122
|
/**
|
|
112
123
|
* Maximum number of items allowed in the queuer
|
|
113
124
|
*/
|
|
@@ -148,6 +159,7 @@ const defaultOptions: Omit<
|
|
|
148
159
|
| 'onItemsChange'
|
|
149
160
|
| 'onReject'
|
|
150
161
|
| 'onExpire'
|
|
162
|
+
| 'key'
|
|
151
163
|
> = {
|
|
152
164
|
addItemsTo: 'back',
|
|
153
165
|
getItemsFrom: 'front',
|
|
@@ -244,6 +256,7 @@ export class Queuer<TValue> {
|
|
|
244
256
|
readonly store: Store<Readonly<QueuerState<TValue>>> = new Store(
|
|
245
257
|
getDefaultQueuerState<TValue>(),
|
|
246
258
|
)
|
|
259
|
+
key: string
|
|
247
260
|
options: QueuerOptions<TValue>
|
|
248
261
|
#timeoutId: NodeJS.Timeout | null = null
|
|
249
262
|
|
|
@@ -251,6 +264,7 @@ export class Queuer<TValue> {
|
|
|
251
264
|
public fn: (item: TValue) => void,
|
|
252
265
|
initialOptions: QueuerOptions<TValue> = {},
|
|
253
266
|
) {
|
|
267
|
+
this.key = createKey(initialOptions.key)
|
|
254
268
|
this.options = {
|
|
255
269
|
...defaultOptions,
|
|
256
270
|
...initialOptions,
|
|
@@ -273,6 +287,11 @@ export class Queuer<TValue> {
|
|
|
273
287
|
this.addItem(item, this.options.addItemsTo ?? 'back', isLast)
|
|
274
288
|
}
|
|
275
289
|
}
|
|
290
|
+
pacerEventClient.on('d-Queuer', (event) => {
|
|
291
|
+
if (event.payload.key !== this.key) return
|
|
292
|
+
this.#setState(event.payload.store.state)
|
|
293
|
+
this.setOptions(event.payload.options)
|
|
294
|
+
})
|
|
276
295
|
}
|
|
277
296
|
|
|
278
297
|
/**
|
|
@@ -307,6 +326,7 @@ export class Queuer<TValue> {
|
|
|
307
326
|
status,
|
|
308
327
|
}
|
|
309
328
|
})
|
|
329
|
+
emitChange('Queuer', this)
|
|
310
330
|
}
|
|
311
331
|
|
|
312
332
|
/**
|
|
@@ -366,6 +386,10 @@ export class Queuer<TValue> {
|
|
|
366
386
|
position: QueuePosition = this.options.addItemsTo ?? 'back',
|
|
367
387
|
runOnItemsChange: boolean = true,
|
|
368
388
|
): boolean => {
|
|
389
|
+
this.#setState({
|
|
390
|
+
addItemCount: this.store.state.addItemCount + 1,
|
|
391
|
+
})
|
|
392
|
+
|
|
369
393
|
if (this.store.state.items.length >= (this.options.maxSize ?? Infinity)) {
|
|
370
394
|
this.#setState({
|
|
371
395
|
rejectionCount: this.store.state.rejectionCount + 1,
|
|
@@ -447,7 +471,12 @@ export class Queuer<TValue> {
|
|
|
447
471
|
const { items, itemTimestamps } = this.store.state
|
|
448
472
|
let item: TValue | undefined
|
|
449
473
|
|
|
450
|
-
|
|
474
|
+
// When priority function is provided, always get from front (highest priority)
|
|
475
|
+
// Priority takes precedence over FIFO/LIFO behavior
|
|
476
|
+
if (
|
|
477
|
+
this.options.getPriority !== defaultOptions.getPriority ||
|
|
478
|
+
position === 'front'
|
|
479
|
+
) {
|
|
451
480
|
item = items[0]
|
|
452
481
|
if (item !== undefined) {
|
|
453
482
|
this.#setState({
|
package/src/rate-limiter.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 RateLimiterState {
|
|
@@ -15,6 +16,10 @@ export interface RateLimiterState {
|
|
|
15
16
|
* Whether the rate limiter has exceeded the limit
|
|
16
17
|
*/
|
|
17
18
|
isExceeded: boolean
|
|
19
|
+
/**
|
|
20
|
+
* Number of times maybeExecute has been called (for reduction calculations)
|
|
21
|
+
*/
|
|
22
|
+
maybeExecuteCount: number
|
|
18
23
|
/**
|
|
19
24
|
* Number of function executions that have been rejected due to rate limiting
|
|
20
25
|
*/
|
|
@@ -32,6 +37,7 @@ function getDefaultRateLimiterState(): RateLimiterState {
|
|
|
32
37
|
isExceeded: false,
|
|
33
38
|
rejectionCount: 0,
|
|
34
39
|
status: 'idle',
|
|
40
|
+
maybeExecuteCount: 0,
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -48,6 +54,11 @@ export interface RateLimiterOptions<TFn extends AnyFunction> {
|
|
|
48
54
|
* Initial state for the rate limiter
|
|
49
55
|
*/
|
|
50
56
|
initialState?: Partial<RateLimiterState>
|
|
57
|
+
/**
|
|
58
|
+
* Optional key to identify this rate limiter instance.
|
|
59
|
+
* If provided, the rate limiter will be identified by this key in the devtools and PacerProvider if applicable.
|
|
60
|
+
*/
|
|
61
|
+
key?: string
|
|
51
62
|
/**
|
|
52
63
|
* Maximum number of executions allowed within the time window.
|
|
53
64
|
* Can be a number or a callback function that receives the rate limiter instance and returns a number.
|
|
@@ -77,7 +88,7 @@ export interface RateLimiterOptions<TFn extends AnyFunction> {
|
|
|
77
88
|
|
|
78
89
|
const defaultOptions: Omit<
|
|
79
90
|
Required<RateLimiterOptions<any>>,
|
|
80
|
-
'initialState' | 'onExecute' | 'onReject'
|
|
91
|
+
'initialState' | 'onExecute' | 'onReject' | 'key'
|
|
81
92
|
> = {
|
|
82
93
|
enabled: true,
|
|
83
94
|
limit: 1,
|
|
@@ -132,6 +143,7 @@ const defaultOptions: Omit<
|
|
|
132
143
|
export class RateLimiter<TFn extends AnyFunction> {
|
|
133
144
|
readonly store: Store<Readonly<RateLimiterState>> =
|
|
134
145
|
new Store<RateLimiterState>(getDefaultRateLimiterState())
|
|
146
|
+
key: string
|
|
135
147
|
options: RateLimiterOptions<TFn>
|
|
136
148
|
#timeoutIds: Set<NodeJS.Timeout> = new Set()
|
|
137
149
|
|
|
@@ -139,6 +151,7 @@ export class RateLimiter<TFn extends AnyFunction> {
|
|
|
139
151
|
public fn: TFn,
|
|
140
152
|
initialOptions: RateLimiterOptions<TFn>,
|
|
141
153
|
) {
|
|
154
|
+
this.key = createKey(initialOptions.key)
|
|
142
155
|
this.options = {
|
|
143
156
|
...defaultOptions,
|
|
144
157
|
...initialOptions,
|
|
@@ -147,6 +160,12 @@ export class RateLimiter<TFn extends AnyFunction> {
|
|
|
147
160
|
for (const executionTime of this.#getExecutionTimesInWindow()) {
|
|
148
161
|
this.#setCleanupTimeout(executionTime)
|
|
149
162
|
}
|
|
163
|
+
|
|
164
|
+
pacerEventClient.on('d-RateLimiter', (event) => {
|
|
165
|
+
if (event.payload.key !== this.key) return
|
|
166
|
+
this.#setState(event.payload.store.state)
|
|
167
|
+
this.setOptions(event.payload.options)
|
|
168
|
+
})
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
/**
|
|
@@ -174,6 +193,7 @@ export class RateLimiter<TFn extends AnyFunction> {
|
|
|
174
193
|
status,
|
|
175
194
|
}
|
|
176
195
|
})
|
|
196
|
+
emitChange('RateLimiter', this)
|
|
177
197
|
}
|
|
178
198
|
|
|
179
199
|
/**
|
|
@@ -213,6 +233,10 @@ export class RateLimiter<TFn extends AnyFunction> {
|
|
|
213
233
|
* ```
|
|
214
234
|
*/
|
|
215
235
|
maybeExecute = (...args: Parameters<TFn>): boolean => {
|
|
236
|
+
this.#setState({
|
|
237
|
+
maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
|
|
238
|
+
})
|
|
239
|
+
|
|
216
240
|
this.#cleanupOldExecutions()
|
|
217
241
|
|
|
218
242
|
const relevantExecutionTimes = this.#getExecutionTimesInWindow()
|
package/src/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 { AnyFunction } from './types'
|
|
4
5
|
|
|
5
6
|
export interface ThrottlerState<TFn extends AnyFunction> {
|
|
@@ -19,6 +20,10 @@ export interface ThrottlerState<TFn extends AnyFunction> {
|
|
|
19
20
|
* Timestamp of the last function execution in milliseconds
|
|
20
21
|
*/
|
|
21
22
|
lastExecutionTime: number
|
|
23
|
+
/**
|
|
24
|
+
* Number of times maybeExecute has been called (for reduction calculations)
|
|
25
|
+
*/
|
|
26
|
+
maybeExecuteCount: number
|
|
22
27
|
/**
|
|
23
28
|
* Timestamp when the next execution can occur in milliseconds
|
|
24
29
|
*/
|
|
@@ -39,6 +44,7 @@ function getDefaultThrottlerState<
|
|
|
39
44
|
lastExecutionTime: 0,
|
|
40
45
|
nextExecutionTime: 0,
|
|
41
46
|
status: 'idle',
|
|
47
|
+
maybeExecuteCount: 0,
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -56,6 +62,11 @@ export interface ThrottlerOptions<TFn extends AnyFunction> {
|
|
|
56
62
|
* Initial state for the throttler
|
|
57
63
|
*/
|
|
58
64
|
initialState?: Partial<ThrottlerState<TFn>>
|
|
65
|
+
/**
|
|
66
|
+
* A key to identify the throttler.
|
|
67
|
+
* If provided, the throttler will be identified by this key in the devtools and PacerProvider if applicable.
|
|
68
|
+
*/
|
|
69
|
+
key?: string
|
|
59
70
|
/**
|
|
60
71
|
* Whether to execute on the leading edge of the timeout.
|
|
61
72
|
* Defaults to true.
|
|
@@ -80,7 +91,7 @@ export interface ThrottlerOptions<TFn extends AnyFunction> {
|
|
|
80
91
|
|
|
81
92
|
const defaultOptions: Omit<
|
|
82
93
|
Required<ThrottlerOptions<any>>,
|
|
83
|
-
'initialState' | 'onExecute'
|
|
94
|
+
'initialState' | 'onExecute' | 'key'
|
|
84
95
|
> = {
|
|
85
96
|
enabled: true,
|
|
86
97
|
leading: true,
|
|
@@ -127,6 +138,7 @@ export class Throttler<TFn extends AnyFunction> {
|
|
|
127
138
|
readonly store: Store<Readonly<ThrottlerState<TFn>>> = new Store(
|
|
128
139
|
getDefaultThrottlerState(),
|
|
129
140
|
)
|
|
141
|
+
key: string | undefined
|
|
130
142
|
options: ThrottlerOptions<TFn>
|
|
131
143
|
#timeoutId: NodeJS.Timeout | undefined
|
|
132
144
|
|
|
@@ -134,11 +146,18 @@ export class Throttler<TFn extends AnyFunction> {
|
|
|
134
146
|
public fn: TFn,
|
|
135
147
|
initialOptions: ThrottlerOptions<TFn>,
|
|
136
148
|
) {
|
|
149
|
+
this.key = createKey(initialOptions.key)
|
|
137
150
|
this.options = {
|
|
138
151
|
...defaultOptions,
|
|
139
152
|
...initialOptions,
|
|
140
153
|
}
|
|
141
154
|
this.#setState(this.options.initialState ?? {})
|
|
155
|
+
|
|
156
|
+
pacerEventClient.on('d-Throttler', (event) => {
|
|
157
|
+
if (event.payload.key !== this.key) return
|
|
158
|
+
this.#setState(event.payload.store.state as ThrottlerState<TFn>)
|
|
159
|
+
this.setOptions(event.payload.options)
|
|
160
|
+
})
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
/**
|
|
@@ -169,6 +188,7 @@ export class Throttler<TFn extends AnyFunction> {
|
|
|
169
188
|
: 'idle',
|
|
170
189
|
}
|
|
171
190
|
})
|
|
191
|
+
emitChange('Throttler', this)
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
#getEnabled = (): boolean => {
|
|
@@ -202,6 +222,10 @@ export class Throttler<TFn extends AnyFunction> {
|
|
|
202
222
|
* ```
|
|
203
223
|
*/
|
|
204
224
|
maybeExecute = (...args: Parameters<TFn>): void => {
|
|
225
|
+
this.#setState({
|
|
226
|
+
maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
|
|
227
|
+
})
|
|
228
|
+
|
|
205
229
|
const now = Date.now()
|
|
206
230
|
const timeSinceLastExecution = now - this.store.state.lastExecutionTime
|
|
207
231
|
const wait = this.#getWait()
|