@tanstack/pacer 0.21.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +21 -6
  2. package/dist/async-batcher.d.ts +11 -14
  3. package/dist/async-batcher.js +186 -132
  4. package/dist/async-debouncer.d.ts +10 -13
  5. package/dist/async-debouncer.js +187 -146
  6. package/dist/async-queuer.d.ts +12 -14
  7. package/dist/async-queuer.js +384 -259
  8. package/dist/async-rate-limiter.d.ts +9 -12
  9. package/dist/async-rate-limiter.js +198 -147
  10. package/dist/async-retryer.d.ts +10 -10
  11. package/dist/async-retryer.js +209 -188
  12. package/dist/async-throttler.d.ts +10 -13
  13. package/dist/async-throttler.js +215 -161
  14. package/dist/batcher.d.ts +5 -8
  15. package/dist/batcher.js +107 -87
  16. package/dist/debouncer.d.ts +6 -9
  17. package/dist/debouncer.js +99 -86
  18. package/dist/event-client.d.ts +8 -11
  19. package/dist/event-client.js +1 -2
  20. package/dist/index.js +1 -1
  21. package/dist/queuer.d.ts +8 -10
  22. package/dist/queuer.js +277 -207
  23. package/dist/rate-limiter.d.ts +6 -9
  24. package/dist/rate-limiter.js +127 -109
  25. package/dist/throttler.d.ts +6 -9
  26. package/dist/throttler.js +127 -90
  27. package/dist/types.d.ts +4 -6
  28. package/dist/utils.d.ts +3 -6
  29. package/dist/utils.js +1 -2
  30. package/package.json +23 -70
  31. package/dist/async-batcher.cjs +0 -337
  32. package/dist/async-batcher.cjs.map +0 -1
  33. package/dist/async-batcher.d.cts +0 -344
  34. package/dist/async-batcher.js.map +0 -1
  35. package/dist/async-debouncer.cjs +0 -330
  36. package/dist/async-debouncer.cjs.map +0 -1
  37. package/dist/async-debouncer.d.cts +0 -300
  38. package/dist/async-debouncer.js.map +0 -1
  39. package/dist/async-queuer.cjs +0 -484
  40. package/dist/async-queuer.cjs.map +0 -1
  41. package/dist/async-queuer.d.cts +0 -440
  42. package/dist/async-queuer.js.map +0 -1
  43. package/dist/async-rate-limiter.cjs +0 -373
  44. package/dist/async-rate-limiter.cjs.map +0 -1
  45. package/dist/async-rate-limiter.d.cts +0 -357
  46. package/dist/async-rate-limiter.js.map +0 -1
  47. package/dist/async-retryer.cjs +0 -374
  48. package/dist/async-retryer.cjs.map +0 -1
  49. package/dist/async-retryer.d.cts +0 -319
  50. package/dist/async-retryer.js.map +0 -1
  51. package/dist/async-throttler.cjs +0 -347
  52. package/dist/async-throttler.cjs.map +0 -1
  53. package/dist/async-throttler.d.cts +0 -320
  54. package/dist/async-throttler.js.map +0 -1
  55. package/dist/batcher.cjs +0 -200
  56. package/dist/batcher.cjs.map +0 -1
  57. package/dist/batcher.d.cts +0 -180
  58. package/dist/batcher.js.map +0 -1
  59. package/dist/debouncer.cjs +0 -203
  60. package/dist/debouncer.cjs.map +0 -1
  61. package/dist/debouncer.d.cts +0 -167
  62. package/dist/debouncer.js.map +0 -1
  63. package/dist/event-client.cjs +0 -64
  64. package/dist/event-client.cjs.map +0 -1
  65. package/dist/event-client.d.cts +0 -66
  66. package/dist/event-client.js.map +0 -1
  67. package/dist/index.cjs +0 -52
  68. package/dist/index.d.cts +0 -15
  69. package/dist/queuer.cjs +0 -401
  70. package/dist/queuer.cjs.map +0 -1
  71. package/dist/queuer.d.cts +0 -345
  72. package/dist/queuer.js.map +0 -1
  73. package/dist/rate-limiter.cjs +0 -263
  74. package/dist/rate-limiter.cjs.map +0 -1
  75. package/dist/rate-limiter.d.cts +0 -215
  76. package/dist/rate-limiter.js.map +0 -1
  77. package/dist/throttler.cjs +0 -215
  78. package/dist/throttler.cjs.map +0 -1
  79. package/dist/throttler.d.cts +0 -207
  80. package/dist/throttler.js.map +0 -1
  81. package/dist/types.cjs +0 -0
  82. package/dist/types.d.cts +0 -13
  83. package/dist/utils.cjs +0 -14
  84. package/dist/utils.cjs.map +0 -1
  85. package/dist/utils.d.cts +0 -8
  86. package/dist/utils.js.map +0 -1
  87. package/src/async-batcher.ts +0 -594
  88. package/src/async-debouncer.ts +0 -565
  89. package/src/async-queuer.ts +0 -925
  90. package/src/async-rate-limiter.ts +0 -647
  91. package/src/async-retryer.ts +0 -684
  92. package/src/async-throttler.ts +0 -633
  93. package/src/batcher.ts +0 -329
  94. package/src/debouncer.ts +0 -334
  95. package/src/event-client.ts +0 -129
  96. package/src/index.ts +0 -24
  97. package/src/queuer.ts +0 -740
  98. package/src/rate-limiter.ts +0 -429
  99. package/src/throttler.ts +0 -380
  100. package/src/types.ts +0 -12
  101. package/src/utils.ts +0 -12
package/src/throttler.ts DELETED
@@ -1,380 +0,0 @@
1
- import { Store } from '@tanstack/store'
2
- import { parseFunctionOrValue } from './utils'
3
- import { emitChange, pacerEventClient } from './event-client'
4
- import type { AnyFunction } from './types'
5
-
6
- export interface ThrottlerState<TFn extends AnyFunction> {
7
- /**
8
- * Number of function executions that have been completed
9
- */
10
- executionCount: number
11
- /**
12
- * Whether the throttler is waiting for the timeout to trigger execution
13
- */
14
- isPending: boolean
15
- /**
16
- * The arguments from the most recent call to maybeExecute
17
- */
18
- lastArgs: Parameters<TFn> | undefined
19
- /**
20
- * Timestamp of the last function execution in milliseconds
21
- */
22
- lastExecutionTime: number
23
- /**
24
- * Number of times maybeExecute has been called (for reduction calculations)
25
- */
26
- maybeExecuteCount: number
27
- /**
28
- * Timestamp when the next execution can occur in milliseconds
29
- */
30
- nextExecutionTime: number | undefined
31
- /**
32
- * Current execution status - 'idle' when not active, 'pending' when waiting for timeout
33
- */
34
- status: 'disabled' | 'idle' | 'pending'
35
- }
36
-
37
- function getDefaultThrottlerState<
38
- TFn extends AnyFunction,
39
- >(): ThrottlerState<TFn> {
40
- return {
41
- executionCount: 0,
42
- isPending: false,
43
- lastArgs: undefined,
44
- lastExecutionTime: 0,
45
- nextExecutionTime: 0,
46
- status: 'idle',
47
- maybeExecuteCount: 0,
48
- }
49
- }
50
-
51
- /**
52
- * Options for configuring a throttled function
53
- */
54
- export interface ThrottlerOptions<TFn extends AnyFunction> {
55
- /**
56
- * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions.
57
- * Can be a boolean or a function that returns a boolean.
58
- * Defaults to true.
59
- */
60
- enabled?: boolean | ((throttler: Throttler<TFn>) => boolean)
61
- /**
62
- * Initial state for the throttler
63
- */
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
70
- /**
71
- * Whether to execute on the leading edge of the timeout.
72
- * Defaults to true.
73
- */
74
- leading?: boolean
75
- /**
76
- * Callback function that is called after the function is executed
77
- */
78
- onExecute?: (args: Parameters<TFn>, throttler: Throttler<TFn>) => void
79
- /**
80
- * Whether to execute on the trailing edge of the timeout.
81
- * Defaults to true.
82
- */
83
- trailing?: boolean
84
- /**
85
- * Time window in milliseconds during which the function can only be executed once.
86
- * Can be a number or a function that returns a number.
87
- * Defaults to 0ms
88
- */
89
- wait: number | ((throttler: Throttler<TFn>) => number)
90
- }
91
-
92
- /**
93
- * Utility function for sharing common `ThrottlerOptions` options between different `Throttler` instances.
94
- */
95
- export function throttlerOptions<
96
- TFn extends AnyFunction = AnyFunction,
97
- TOptions extends Partial<ThrottlerOptions<TFn>> = Partial<
98
- ThrottlerOptions<TFn>
99
- >,
100
- >(options: TOptions): TOptions {
101
- return options
102
- }
103
-
104
- const defaultOptions: Omit<
105
- Required<ThrottlerOptions<any>>,
106
- 'initialState' | 'onExecute' | 'key'
107
- > = {
108
- enabled: true,
109
- leading: true,
110
- trailing: true,
111
- wait: 0,
112
- }
113
-
114
- /**
115
- * A class that creates a throttled function.
116
- *
117
- * Throttling ensures a function is called at most once within a specified time window.
118
- * Unlike debouncing which waits for a pause in calls, throttling guarantees consistent
119
- * execution timing regardless of call frequency.
120
- * This synchronous version is lighter weight and often all you need - upgrade to AsyncThrottler when you need promises, retry support, abort/cancel capabilities, or advanced error handling.
121
- *
122
- * Supports both leading and trailing edge execution:
123
- * - Leading: Execute immediately on first call (default: true)
124
- * - Trailing: Execute after wait period if called during throttle (default: true)
125
- *
126
- * For collapsing rapid-fire events where you only care about the last call, consider using Debouncer.
127
- *
128
- * State Management:
129
- * - Uses TanStack Store for reactive state management
130
- * - Use `initialState` to provide initial state values when creating the throttler
131
- * - Use `onExecute` callback to react to function execution and implement custom logic
132
- * - The state includes execution count, last execution time, pending status, and more
133
- * - State can be accessed via `throttler.store.state` when using the class directly
134
- * - When using framework adapters (React/Solid), state is accessed from `throttler.state`
135
- *
136
- * @example
137
- * ```ts
138
- * const throttler = new Throttler(
139
- * (id: string) => api.getData(id),
140
- * { wait: 1000 } // Execute at most once per second
141
- * );
142
- *
143
- * // First call executes immediately
144
- * throttler.maybeExecute('123');
145
- *
146
- * // Subsequent calls within 1000ms are throttled
147
- * throttler.maybeExecute('123'); // Throttled
148
- * ```
149
- */
150
- export class Throttler<TFn extends AnyFunction> {
151
- readonly store: Store<Readonly<ThrottlerState<TFn>>> = new Store(
152
- getDefaultThrottlerState<TFn>(),
153
- )
154
- key: string | undefined
155
- options: ThrottlerOptions<TFn>
156
- #timeoutId: ReturnType<typeof setTimeout> | undefined
157
-
158
- constructor(
159
- public fn: TFn,
160
- initialOptions: ThrottlerOptions<TFn>,
161
- ) {
162
- this.key = initialOptions.key
163
- this.options = {
164
- ...defaultOptions,
165
- ...initialOptions,
166
- }
167
- this.#setState(this.options.initialState ?? {})
168
-
169
- if (this.key) {
170
- pacerEventClient.on('d-Throttler', (event) => {
171
- if (event.payload.key !== this.key) return
172
- this.#setState(
173
- event.payload.store.state as Partial<ThrottlerState<TFn>>,
174
- )
175
- this.setOptions(event.payload.options as Partial<ThrottlerOptions<TFn>>)
176
- })
177
- }
178
- }
179
-
180
- /**
181
- * Updates the throttler options
182
- */
183
- setOptions = (newOptions: Partial<ThrottlerOptions<TFn>>): void => {
184
- this.options = { ...this.options, ...newOptions }
185
-
186
- // Cancel pending execution if the throttler is disabled
187
- if (!this.#getEnabled()) {
188
- this.cancel()
189
- }
190
- }
191
-
192
- #setState = (newState: Partial<ThrottlerState<TFn>>): void => {
193
- this.store.setState((state) => {
194
- const combinedState = {
195
- ...state,
196
- ...newState,
197
- }
198
- const { isPending } = combinedState
199
- return {
200
- ...combinedState,
201
- status: !this.#getEnabled()
202
- ? 'disabled'
203
- : isPending
204
- ? 'pending'
205
- : 'idle',
206
- }
207
- })
208
- emitChange('Throttler', this)
209
- }
210
-
211
- #getEnabled = (): boolean => {
212
- return !!parseFunctionOrValue(this.options.enabled, this)
213
- }
214
-
215
- #getWait = (): number => {
216
- return parseFunctionOrValue(this.options.wait, this)
217
- }
218
-
219
- /**
220
- * Attempts to execute the throttled function. The execution behavior depends on the throttler options:
221
- *
222
- * - If enough time has passed since the last execution (>= wait period):
223
- * - With leading=true: Executes immediately
224
- * - With leading=false: Waits for the next trailing execution
225
- *
226
- * - If within the wait period:
227
- * - With trailing=true: Schedules execution for end of wait period
228
- * - With trailing=false: Drops the execution
229
- *
230
- * @example
231
- * ```ts
232
- * const throttled = new Throttler(fn, { wait: 1000 });
233
- *
234
- * // First call executes immediately
235
- * throttled.maybeExecute('a', 'b');
236
- *
237
- * // Call during wait period - gets throttled
238
- * throttled.maybeExecute('c', 'd');
239
- * ```
240
- */
241
- maybeExecute = (...args: Parameters<TFn>): void => {
242
- this.#setState({
243
- maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
244
- })
245
-
246
- const now = Date.now()
247
- const timeSinceLastExecution = now - this.store.state.lastExecutionTime
248
- const wait = this.#getWait()
249
-
250
- // Handle leading execution
251
- if (this.options.leading && timeSinceLastExecution >= wait) {
252
- this.#execute(...args)
253
- } else {
254
- // Store the most recent arguments for potential trailing execution
255
- this.#setState({
256
- lastArgs: args,
257
- })
258
- // Set up trailing execution if not already scheduled
259
- if (!this.#timeoutId && this.options.trailing) {
260
- // prevent large number if lastExecutionTime is undefined
261
- const _timeSinceLastExecution = this.store.state.lastExecutionTime
262
- ? now - this.store.state.lastExecutionTime
263
- : 0
264
- const timeoutDuration = wait - _timeSinceLastExecution
265
- this.#setState({ isPending: true })
266
- this.#timeoutId = setTimeout(() => {
267
- const { lastArgs } = this.store.state
268
- if (lastArgs !== undefined) {
269
- this.#execute(...lastArgs)
270
- }
271
- }, timeoutDuration)
272
- }
273
- }
274
- }
275
-
276
- #execute = (...args: Parameters<TFn>): void => {
277
- if (!this.#getEnabled()) return
278
- this.fn(...args) // EXECUTE!
279
- const lastExecutionTime = Date.now()
280
- const nextExecutionTime = lastExecutionTime + this.#getWait()
281
- this.#clearTimeout()
282
- this.#setState({
283
- executionCount: this.store.state.executionCount + 1,
284
- lastExecutionTime,
285
- nextExecutionTime,
286
- isPending: false,
287
- lastArgs: undefined,
288
- })
289
- this.options.onExecute?.(args, this)
290
- setTimeout(() => {
291
- if (!this.store.state.isPending) {
292
- this.#setState({ nextExecutionTime: undefined })
293
- }
294
- }, this.#getWait())
295
- }
296
-
297
- /**
298
- * Processes the current pending execution immediately
299
- */
300
- flush = (): void => {
301
- if (this.store.state.isPending && this.store.state.lastArgs) {
302
- this.#execute(...this.store.state.lastArgs)
303
- }
304
- }
305
-
306
- #clearTimeout = (): void => {
307
- if (this.#timeoutId) {
308
- clearTimeout(this.#timeoutId)
309
- this.#timeoutId = undefined
310
- }
311
- }
312
-
313
- /**
314
- * Cancels any pending trailing execution and clears internal state.
315
- *
316
- * If a trailing execution is scheduled (due to throttling with trailing=true),
317
- * this will prevent that execution from occurring. The internal timeout and
318
- * stored arguments will be cleared.
319
- *
320
- * Has no effect if there is no pending execution.
321
- */
322
- cancel = (): void => {
323
- this.#clearTimeout()
324
- this.#setState({
325
- lastArgs: undefined,
326
- isPending: false,
327
- })
328
- }
329
-
330
- /**
331
- * Resets the throttler state to its default values
332
- */
333
- reset = (): void => {
334
- this.#setState(getDefaultThrottlerState<TFn>())
335
- }
336
- }
337
-
338
- /**
339
- * Creates a throttled function that limits how often the provided function can execute.
340
- *
341
- * This synchronous version is lighter weight and often all you need - upgrade to asyncThrottle when you need promises, retry support, abort/cancel capabilities, or advanced error handling.
342
- *
343
- * Throttling ensures a function executes at most once within a specified time window,
344
- * regardless of how many times it is called. This is useful for rate-limiting
345
- * expensive operations or UI updates.
346
- *
347
- * The throttled function can be configured to execute on the leading and/or trailing
348
- * edge of the throttle window via options.
349
- *
350
- * For handling bursts of events, consider using debounce() instead. For hard execution
351
- * limits, consider using rateLimit().
352
- *
353
- * State Management:
354
- * - Uses TanStack Store for reactive state management
355
- * - Use `initialState` to provide initial state values when creating the throttler
356
- * - Use `onExecute` callback to react to function execution and implement custom logic
357
- * - The state includes execution count, last execution time, pending status, and more
358
- * - State can be accessed via the underlying Throttler instance's `store.state` property
359
- * - When using framework adapters (React/Solid), state is accessed from the hook's state property
360
- *
361
- * @example
362
- * ```ts
363
- * // Basic throttling - max once per second
364
- * const throttled = throttle(updateUI, { wait: 1000 });
365
- *
366
- * // Configure leading/trailing execution
367
- * const throttled = throttle(saveData, {
368
- * wait: 2000,
369
- * leading: true, // Execute immediately on first call
370
- * trailing: true // Execute again after delay if called during wait
371
- * });
372
- * ```
373
- */
374
- export function throttle<TFn extends AnyFunction>(
375
- fn: TFn,
376
- initialOptions: ThrottlerOptions<TFn>,
377
- ) {
378
- const throttler = new Throttler(fn, initialOptions)
379
- return throttler.maybeExecute
380
- }
package/src/types.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Represents a function that can be called with any arguments and returns any value.
3
- */
4
- export type AnyFunction = (...args: Array<any>) => any
5
-
6
- /**
7
- * Represents an asynchronous function that can be called with any arguments and returns a promise.
8
- */
9
- export type AnyAsyncFunction = (...args: Array<any>) => Promise<any>
10
-
11
- export type OptionalKeys<T, TKey extends keyof T> = Omit<T, TKey> &
12
- Partial<Pick<T, TKey>>
package/src/utils.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { AnyFunction } from './types'
2
-
3
- export function isFunction<T extends AnyFunction>(value: any): value is T {
4
- return typeof value === 'function'
5
- }
6
-
7
- export function parseFunctionOrValue<T, TArgs extends Array<any>>(
8
- value: T | ((...args: TArgs) => T),
9
- ...args: TArgs
10
- ): T {
11
- return isFunction(value) ? value(...args) : value
12
- }