@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/batcher.ts DELETED
@@ -1,329 +0,0 @@
1
- import { Store } from '@tanstack/store'
2
- import { parseFunctionOrValue } from './utils'
3
- import { emitChange, pacerEventClient } from './event-client'
4
- import type { OptionalKeys } from './types'
5
-
6
- export interface BatcherState<TValue> {
7
- /**
8
- * Number of batch executions that have been completed
9
- */
10
- executionCount: number
11
- /**
12
- * Whether the batcher has no items to process (items array is empty)
13
- */
14
- isEmpty: boolean
15
- /**
16
- * Whether the batcher is waiting for the timeout to trigger batch processing
17
- */
18
- isPending: boolean
19
- /**
20
- * Array of items currently queued for batch processing
21
- */
22
- items: Array<TValue>
23
- /**
24
- * Number of items currently in the batch queue
25
- */
26
- size: number
27
- /**
28
- * Current processing status - 'idle' when not processing, 'pending' when waiting for timeout
29
- */
30
- status: 'idle' | 'pending'
31
- /**
32
- * Total number of items that have been processed across all batches
33
- */
34
- totalItemsProcessed: number
35
- }
36
-
37
- function getDefaultBatcherState<TValue>(): BatcherState<TValue> {
38
- return {
39
- executionCount: 0,
40
- isEmpty: true,
41
- isPending: false,
42
- totalItemsProcessed: 0,
43
- items: [],
44
- size: 0,
45
- status: 'idle',
46
- }
47
- }
48
-
49
- /**
50
- * Options for configuring a Batcher instance
51
- */
52
- export interface BatcherOptions<TValue> {
53
- /**
54
- * Custom function to determine if a batch should be processed
55
- * Return true to process the batch immediately
56
- */
57
- getShouldExecute?: (items: Array<TValue>, batcher: Batcher<TValue>) => boolean
58
- /**
59
- * Initial state for the batcher
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
67
- /**
68
- * Maximum number of items in a batch
69
- * @default Infinity
70
- */
71
- maxSize?: number
72
- /**
73
- * Callback fired after a batch is processed
74
- */
75
- onExecute?: (batch: Array<TValue>, batcher: Batcher<TValue>) => void
76
- /**
77
- * Callback fired after items are added to the batcher
78
- */
79
- onItemsChange?: (batcher: Batcher<TValue>) => void
80
- /**
81
- * Whether the batcher should start processing immediately
82
- * @default true
83
- */
84
- started?: boolean
85
- /**
86
- * Maximum time in milliseconds to wait before processing a batch.
87
- * If the wait duration has elapsed, the batch will be processed.
88
- * If not provided, the batch will not be triggered by a timeout.
89
- * @default Infinity
90
- */
91
- wait?: number | ((batcher: Batcher<TValue>) => number)
92
- }
93
-
94
- type BatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<
95
- Required<BatcherOptions<TValue>>,
96
- 'initialState' | 'onExecute' | 'onItemsChange' | 'key'
97
- >
98
-
99
- const defaultOptions: BatcherOptionsWithOptionalCallbacks<any> = {
100
- getShouldExecute: () => false,
101
- maxSize: Infinity,
102
- started: true,
103
- wait: Infinity,
104
- }
105
-
106
- /**
107
- * A class that collects items and processes them in batches.
108
- *
109
- * Batching is a technique for grouping multiple operations together to be processed as a single unit.
110
- * This synchronous version is lighter weight and often all you need - upgrade to AsyncBatcher when you need promises, retry support, abort/cancel capabilities, or advanced error handling.
111
- *
112
- * The Batcher provides a flexible way to implement batching with configurable:
113
- * - Maximum batch size (number of items per batch)
114
- * - Time-based batching (process after X milliseconds)
115
- * - Custom batch processing logic via getShouldExecute
116
- * - Event callbacks for monitoring batch operations
117
- *
118
- * State Management:
119
- * - Uses TanStack Store for reactive state management
120
- * - Use `initialState` to provide initial state values when creating the batcher
121
- * - Use `onExecute` callback to react to batch execution and implement custom logic
122
- * - Use `onItemsChange` callback to react to items being added or removed from the batcher
123
- * - The state includes batch execution count, total items processed, items, and running status
124
- * - State can be accessed via `batcher.store.state` when using the class directly
125
- * - When using framework adapters (React/Solid), state is accessed from `batcher.state`
126
- *
127
- * @example
128
- * ```ts
129
- * const batcher = new Batcher<number>(
130
- * (items) => console.log('Processing batch:', items),
131
- * {
132
- * maxSize: 5,
133
- * wait: 2000,
134
- * onExecute: (batch, batcher) => console.log('Batch executed:', batch)
135
- * }
136
- * );
137
- *
138
- * batcher.addItem(1);
139
- * batcher.addItem(2);
140
- * // After 2 seconds or when 5 items are added, whichever comes first,
141
- * // the batch will be processed
142
- * // batcher.flush() // manually trigger a batch
143
- * ```
144
- */
145
- export class Batcher<TValue> {
146
- readonly store: Store<Readonly<BatcherState<TValue>>> = new Store(
147
- getDefaultBatcherState<TValue>(),
148
- )
149
- key: string | undefined
150
- options: BatcherOptionsWithOptionalCallbacks<TValue>
151
- #timeoutId: ReturnType<typeof setTimeout> | null = null
152
-
153
- constructor(
154
- public fn: (items: Array<TValue>) => void,
155
- initialOptions: BatcherOptions<TValue>,
156
- ) {
157
- this.key = initialOptions.key
158
- this.options = {
159
- ...defaultOptions,
160
- ...initialOptions,
161
- }
162
- this.#setState(this.options.initialState ?? {})
163
-
164
- if (this.key) {
165
- pacerEventClient.on('d-Batcher', (event) => {
166
- if (event.payload.key !== this.key) return
167
- this.#setState(
168
- event.payload.store.state as Partial<BatcherState<TValue>>,
169
- )
170
- this.setOptions(
171
- event.payload.options as Partial<BatcherOptions<TValue>>,
172
- )
173
- })
174
- }
175
- }
176
-
177
- /**
178
- * Updates the batcher options
179
- */
180
- setOptions = (newOptions: Partial<BatcherOptions<TValue>>): void => {
181
- this.options = { ...this.options, ...newOptions }
182
- }
183
-
184
- #setState = (newState: Partial<BatcherState<TValue>>): void => {
185
- this.store.setState((state) => {
186
- const combinedState = {
187
- ...state,
188
- ...newState,
189
- }
190
- const { isPending, items } = combinedState
191
- const size = items.length
192
- const isEmpty = size === 0
193
- return {
194
- ...combinedState,
195
- isEmpty,
196
- size,
197
- status: isPending ? 'pending' : 'idle',
198
- }
199
- })
200
- emitChange('Batcher', this)
201
- }
202
-
203
- #getWait = (): number => {
204
- return parseFunctionOrValue(this.options.wait, this)
205
- }
206
-
207
- /**
208
- * Adds an item to the batcher
209
- * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed
210
- */
211
- addItem = (item: TValue): void => {
212
- this.#setState({
213
- items: [...this.store.state.items, item],
214
- isPending: this.options.wait !== Infinity,
215
- })
216
- this.options.onItemsChange?.(this)
217
-
218
- const shouldProcess =
219
- this.store.state.items.length >= this.options.maxSize ||
220
- this.options.getShouldExecute(this.store.state.items, this)
221
-
222
- if (shouldProcess) {
223
- this.#execute()
224
- } else if (this.options.wait !== Infinity) {
225
- this.#clearTimeout() // clear any pending timeout to replace it with a new one
226
- this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait())
227
- }
228
- }
229
-
230
- /**
231
- * Processes the current batch of items.
232
- * This method will automatically be triggered if the batcher is running and any of these conditions are met:
233
- * - The number of items reaches batchSize
234
- * - The wait duration has elapsed
235
- * - The getShouldExecute function returns true upon adding an item
236
- *
237
- * You can also call this method manually to process the current batch at any time.
238
- */
239
- #execute = (): void => {
240
- if (this.store.state.items.length === 0) {
241
- return
242
- }
243
-
244
- const batch = this.peekAllItems() // copy of the items to be processed (to prevent race conditions)
245
- this.clear() // Clear items before processing to prevent race conditions
246
- this.options.onItemsChange?.(this) // Call onItemsChange to notify listeners that the items have changed
247
-
248
- this.fn(batch) // EXECUTE
249
- this.#setState({
250
- executionCount: this.store.state.executionCount + 1,
251
- totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length,
252
- })
253
- this.options.onExecute?.(batch, this)
254
- }
255
-
256
- /**
257
- * Processes the current batch of items immediately
258
- */
259
- flush = (): void => {
260
- this.#clearTimeout() // clear any pending timeout
261
- this.#execute() // execute immediately
262
- }
263
-
264
- /**
265
- * Returns a copy of all items in the batcher
266
- */
267
- peekAllItems = (): Array<TValue> => {
268
- return [...this.store.state.items]
269
- }
270
-
271
- #clearTimeout = (): void => {
272
- if (this.#timeoutId) {
273
- clearTimeout(this.#timeoutId)
274
- this.#timeoutId = null
275
- }
276
- }
277
-
278
- /**
279
- * Removes all items from the batcher
280
- */
281
- clear = (): void => {
282
- this.#setState({ items: [], isPending: false })
283
- }
284
-
285
- /**
286
- * Cancels any pending execution that was scheduled.
287
- * Does NOT clear out the items.
288
- */
289
- cancel = (): void => {
290
- this.#clearTimeout()
291
- this.#setState({ isPending: false })
292
- }
293
-
294
- /**
295
- * Resets the batcher state to its default values
296
- */
297
- reset = (): void => {
298
- this.#setState(getDefaultBatcherState<TValue>())
299
- this.options.onItemsChange?.(this)
300
- }
301
- }
302
-
303
- /**
304
- * Creates a batcher that processes items in batches.
305
- *
306
- * This synchronous version is lighter weight and often all you need - upgrade to asyncBatch when you need promises, retry support, abort/cancel capabilities, or advanced error handling.
307
- *
308
- * @example
309
- * ```ts
310
- * const batchItems = batch<number>(
311
- * (items) => console.log('Processing:', items),
312
- * {
313
- * maxSize: 3,
314
- * onExecute: (batch, batcher) => console.log('Batch executed:', batch)
315
- * }
316
- * );
317
- *
318
- * batchItems(1);
319
- * batchItems(2);
320
- * batchItems(3); // Triggers batch processing
321
- * ```
322
- */
323
- export function batch<TValue>(
324
- fn: (items: Array<TValue>) => void,
325
- options: BatcherOptions<TValue>,
326
- ) {
327
- const batcher = new Batcher<TValue>(fn, options)
328
- return batcher.addItem
329
- }
package/src/debouncer.ts DELETED
@@ -1,334 +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 DebouncerState<TFn extends AnyFunction> {
7
- /**
8
- * Whether the debouncer can execute on the leading edge of the timeout
9
- */
10
- canLeadingExecute: boolean
11
- /**
12
- * Number of function executions that have been completed
13
- */
14
- executionCount: number
15
- /**
16
- * Whether the debouncer is waiting for the timeout to trigger execution
17
- */
18
- isPending: boolean
19
- /**
20
- * The arguments from the most recent call to maybeExecute
21
- */
22
- lastArgs: Parameters<TFn> | undefined
23
- /**
24
- * Number of times maybeExecute has been called (for reduction calculations)
25
- */
26
- maybeExecuteCount: number
27
- /**
28
- * Current execution status - 'idle' when not active, 'pending' when waiting for timeout
29
- */
30
- status: 'disabled' | 'idle' | 'pending'
31
- }
32
-
33
- function getDefaultDebouncerState<
34
- TFn extends AnyFunction,
35
- >(): DebouncerState<TFn> {
36
- return {
37
- canLeadingExecute: true,
38
- executionCount: 0,
39
- isPending: false,
40
- lastArgs: undefined,
41
- status: 'idle',
42
- maybeExecuteCount: 0,
43
- }
44
- }
45
-
46
- /**
47
- * Options for configuring a debounced function
48
- */
49
- export interface DebouncerOptions<TFn extends AnyFunction> {
50
- /**
51
- * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions.
52
- * Can be a boolean or a function that returns a boolean.
53
- * Defaults to true.
54
- */
55
- enabled?: boolean | ((debouncer: Debouncer<TFn>) => boolean)
56
- /**
57
- * Initial state for the debouncer
58
- */
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
65
- /**
66
- * Whether to execute on the leading edge of the timeout.
67
- * The first call will execute immediately and the rest will wait the delay.
68
- * Defaults to false.
69
- */
70
- leading?: boolean
71
- /**
72
- * Callback function that is called after the function is executed
73
- */
74
- onExecute?: (args: Parameters<TFn>, debouncer: Debouncer<TFn>) => void
75
- /**
76
- * Whether to execute on the trailing edge of the timeout.
77
- * Defaults to true.
78
- */
79
- trailing?: boolean
80
- /**
81
- * Delay in milliseconds before executing the function.
82
- * Can be a number or a function that returns a number.
83
- * Defaults to 0ms
84
- */
85
- wait: number | ((debouncer: Debouncer<TFn>) => number)
86
- }
87
-
88
- /**
89
- * Utility function for sharing common `DebouncerOptions` options between different `Debouncer` instances.
90
- */
91
- export function debouncerOptions<
92
- TFn extends AnyFunction = AnyFunction,
93
- TOptions extends Partial<DebouncerOptions<TFn>> = Partial<
94
- DebouncerOptions<TFn>
95
- >,
96
- >(options: TOptions): TOptions {
97
- return options
98
- }
99
-
100
- const defaultOptions: Omit<
101
- Required<DebouncerOptions<any>>,
102
- 'initialState' | 'onExecute' | 'key'
103
- > = {
104
- enabled: true,
105
- leading: false,
106
- trailing: true,
107
- wait: 0,
108
- }
109
-
110
- /**
111
- * A class that creates a debounced function.
112
- *
113
- * Debouncing ensures that a function is only executed after a certain amount of time has passed
114
- * since its last invocation. This is useful for handling frequent events like window resizing,
115
- * scroll events, or input changes where you want to limit the rate of execution.
116
- * This synchronous version is lighter weight and often all you need - upgrade to AsyncDebouncer when you need promises, retry support, abort/cancel capabilities, or advanced error handling.
117
- *
118
- * The debounced function can be configured to execute either at the start of the delay period
119
- * (leading edge) or at the end (trailing edge, default). Each new call during the wait period
120
- * will reset the timer.
121
- *
122
- * State Management:
123
- * - Uses TanStack Store for reactive state management
124
- * - Use `initialState` to provide initial state values when creating the debouncer
125
- * - Use `onExecute` callback to react to function execution and implement custom logic
126
- * - The state includes canLeadingExecute, execution count, and isPending status
127
- * - State can be accessed via `debouncer.store.state` when using the class directly
128
- * - When using framework adapters (React/Solid), state is accessed from `debouncer.state`
129
- *
130
- * @example
131
- * ```ts
132
- * const debouncer = new Debouncer((value: string) => {
133
- * saveToDatabase(value);
134
- * }, { wait: 500 });
135
- *
136
- * // Will only save after 500ms of no new input
137
- * inputElement.addEventListener('input', () => {
138
- * debouncer.maybeExecute(inputElement.value);
139
- * });
140
- * ```
141
- */
142
- export class Debouncer<TFn extends AnyFunction> {
143
- readonly store: Store<Readonly<DebouncerState<TFn>>> = new Store(
144
- getDefaultDebouncerState<TFn>(),
145
- )
146
- key: string | undefined
147
- options: DebouncerOptions<TFn>
148
- #timeoutId: ReturnType<typeof setTimeout> | undefined
149
-
150
- constructor(
151
- public fn: TFn,
152
- initialOptions: DebouncerOptions<TFn>,
153
- ) {
154
- this.key = initialOptions.key
155
- this.options = {
156
- ...defaultOptions,
157
- ...initialOptions,
158
- }
159
- this.#setState(this.options.initialState ?? {})
160
-
161
- if (this.key) {
162
- pacerEventClient.on('d-Debouncer', (event) => {
163
- if (event.payload.key !== this.key) return
164
- this.#setState(
165
- event.payload.store.state as Partial<DebouncerState<TFn>>,
166
- )
167
- this.setOptions(event.payload.options as Partial<DebouncerOptions<TFn>>)
168
- })
169
- }
170
- }
171
-
172
- /**
173
- * Updates the debouncer options
174
- */
175
- setOptions = (newOptions: Partial<DebouncerOptions<TFn>>): void => {
176
- this.options = { ...this.options, ...newOptions }
177
-
178
- // Cancel pending execution if the debouncer is disabled
179
- if (!this.#getEnabled()) {
180
- this.cancel()
181
- }
182
- }
183
-
184
- #setState = (newState: Partial<DebouncerState<TFn>>): void => {
185
- this.store.setState((state) => {
186
- const combinedState = {
187
- ...state,
188
- ...newState,
189
- }
190
- const { isPending } = combinedState
191
- return {
192
- ...combinedState,
193
- status: !this.#getEnabled()
194
- ? 'disabled'
195
- : isPending
196
- ? 'pending'
197
- : 'idle',
198
- }
199
- })
200
- emitChange('Debouncer', this)
201
- }
202
-
203
- /**
204
- * Returns the current enabled state of the debouncer
205
- */
206
- #getEnabled = (): boolean => {
207
- return !!parseFunctionOrValue(this.options.enabled, this)
208
- }
209
-
210
- /**
211
- * Returns the current wait time in milliseconds
212
- */
213
- #getWait = (): number => {
214
- return parseFunctionOrValue(this.options.wait, this)
215
- }
216
-
217
- /**
218
- * Attempts to execute the debounced function
219
- * If a call is already in progress, it will be queued
220
- */
221
- maybeExecute = (...args: Parameters<TFn>): void => {
222
- if (!this.#getEnabled()) return undefined
223
-
224
- this.#setState({
225
- maybeExecuteCount: this.store.state.maybeExecuteCount + 1,
226
- })
227
-
228
- let _didLeadingExecute = false
229
-
230
- // Handle leading execution
231
- if (this.options.leading && this.store.state.canLeadingExecute) {
232
- this.#setState({ canLeadingExecute: false })
233
- _didLeadingExecute = true
234
- this.#execute(...args)
235
- }
236
-
237
- // Start pending state to indicate that the debouncer is waiting for the trailing edge
238
- if (this.options.trailing) {
239
- this.#setState({ isPending: true, lastArgs: args })
240
- }
241
-
242
- // Clear any existing timeout
243
- if (this.#timeoutId) clearTimeout(this.#timeoutId)
244
-
245
- // Set new timeout that will reset canLeadingExecute and execute trailing only if enabled and did not execute leading
246
- this.#timeoutId = setTimeout(() => {
247
- this.#setState({ canLeadingExecute: true })
248
- if (this.options.trailing && !_didLeadingExecute) {
249
- this.#execute(...args)
250
- }
251
- }, this.#getWait())
252
- }
253
-
254
- #execute = (...args: Parameters<TFn>): void => {
255
- if (!this.#getEnabled()) return undefined
256
- this.fn(...args) // EXECUTE!
257
- this.#setState({
258
- executionCount: this.store.state.executionCount + 1,
259
- isPending: false,
260
- lastArgs: undefined,
261
- })
262
- this.options.onExecute?.(args, this)
263
- }
264
-
265
- /**
266
- * Processes the current pending execution immediately
267
- */
268
- flush = (): void => {
269
- if (this.store.state.isPending && this.store.state.lastArgs) {
270
- this.#clearTimeout() // clear any pending timeout
271
- this.#execute(...this.store.state.lastArgs) // execute immediately
272
- }
273
- }
274
-
275
- #clearTimeout = (): void => {
276
- if (this.#timeoutId) {
277
- clearTimeout(this.#timeoutId)
278
- this.#timeoutId = undefined
279
- }
280
- }
281
-
282
- /**
283
- * Cancels any pending execution
284
- */
285
- cancel = (): void => {
286
- this.#clearTimeout()
287
- this.#setState({
288
- canLeadingExecute: true,
289
- isPending: false,
290
- })
291
- }
292
-
293
- /**
294
- * Resets the debouncer state to its default values
295
- */
296
- reset = (): void => {
297
- this.#setState(getDefaultDebouncerState<TFn>())
298
- }
299
- }
300
-
301
- /**
302
- * Creates a debounced function that delays invoking the provided function until after a specified wait time.
303
- * Multiple calls during the wait period will cancel previous pending invocations and reset the timer.
304
- *
305
- * This synchronous version is lighter weight and often all you need - upgrade to asyncDebounce when you need promises, retry support, abort/cancel capabilities, or advanced error handling.
306
- *
307
- * If leading option is true, the function will execute immediately on the first call, then wait the delay
308
- * before allowing another execution.
309
- *
310
- * State Management:
311
- * - Uses TanStack Store for reactive state management
312
- * - Use `initialState` to provide initial state values when creating the debouncer
313
- * - Use `onExecute` callback to react to function execution and implement custom logic
314
- * - The state includes canLeadingExecute, execution count, and isPending status
315
- * - State can be accessed via the underlying Debouncer instance's `store.state` property
316
- * - When using framework adapters (React/Solid), state is accessed from the hook's state property
317
- *
318
- * @example
319
- * ```ts
320
- * const debounced = debounce(() => {
321
- * saveChanges();
322
- * }, { wait: 1000 });
323
- *
324
- * // Called repeatedly but executes at most once per second
325
- * inputElement.addEventListener('input', debounced);
326
- * ```
327
- */
328
- export function debounce<TFn extends AnyFunction>(
329
- fn: TFn,
330
- initialOptions: DebouncerOptions<TFn>,
331
- ): (...args: Parameters<TFn>) => void {
332
- const debouncer = new Debouncer(fn, initialOptions)
333
- return debouncer.maybeExecute
334
- }