@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.
Files changed (92) hide show
  1. package/dist/cjs/async-batcher.cjs +8 -0
  2. package/dist/cjs/async-batcher.cjs.map +1 -1
  3. package/dist/cjs/async-batcher.d.cts +8 -2
  4. package/dist/cjs/async-debouncer.cjs +17 -5
  5. package/dist/cjs/async-debouncer.cjs.map +1 -1
  6. package/dist/cjs/async-debouncer.d.cts +11 -1
  7. package/dist/cjs/async-queuer.cjs +14 -2
  8. package/dist/cjs/async-queuer.cjs.map +1 -1
  9. package/dist/cjs/async-queuer.d.cts +11 -1
  10. package/dist/cjs/async-rate-limiter.cjs +14 -2
  11. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  12. package/dist/cjs/async-rate-limiter.d.cts +11 -1
  13. package/dist/cjs/async-throttler.cjs +15 -3
  14. package/dist/cjs/async-throttler.cjs.map +1 -1
  15. package/dist/cjs/async-throttler.d.cts +11 -1
  16. package/dist/cjs/batcher.cjs +8 -0
  17. package/dist/cjs/batcher.cjs.map +1 -1
  18. package/dist/cjs/batcher.d.cts +8 -2
  19. package/dist/cjs/debouncer.cjs +15 -3
  20. package/dist/cjs/debouncer.cjs.map +1 -1
  21. package/dist/cjs/debouncer.d.cts +11 -1
  22. package/dist/cjs/event-client.cjs +18 -0
  23. package/dist/cjs/event-client.cjs.map +1 -0
  24. package/dist/cjs/event-client.d.cts +42 -0
  25. package/dist/cjs/index.cjs +3 -0
  26. package/dist/cjs/index.cjs.map +1 -1
  27. package/dist/cjs/index.d.cts +2 -0
  28. package/dist/cjs/queuer.cjs +13 -1
  29. package/dist/cjs/queuer.cjs.map +1 -1
  30. package/dist/cjs/queuer.d.cts +11 -1
  31. package/dist/cjs/rate-limiter.cjs +15 -3
  32. package/dist/cjs/rate-limiter.cjs.map +1 -1
  33. package/dist/cjs/rate-limiter.d.cts +11 -1
  34. package/dist/cjs/throttler.cjs +15 -3
  35. package/dist/cjs/throttler.cjs.map +1 -1
  36. package/dist/cjs/throttler.d.cts +11 -1
  37. package/dist/cjs/utils.cjs +10 -0
  38. package/dist/cjs/utils.cjs.map +1 -1
  39. package/dist/cjs/utils.d.cts +1 -0
  40. package/dist/esm/async-batcher.d.ts +8 -2
  41. package/dist/esm/async-batcher.js +9 -1
  42. package/dist/esm/async-batcher.js.map +1 -1
  43. package/dist/esm/async-debouncer.d.ts +11 -1
  44. package/dist/esm/async-debouncer.js +18 -6
  45. package/dist/esm/async-debouncer.js.map +1 -1
  46. package/dist/esm/async-queuer.d.ts +11 -1
  47. package/dist/esm/async-queuer.js +15 -3
  48. package/dist/esm/async-queuer.js.map +1 -1
  49. package/dist/esm/async-rate-limiter.d.ts +11 -1
  50. package/dist/esm/async-rate-limiter.js +15 -3
  51. package/dist/esm/async-rate-limiter.js.map +1 -1
  52. package/dist/esm/async-throttler.d.ts +11 -1
  53. package/dist/esm/async-throttler.js +16 -4
  54. package/dist/esm/async-throttler.js.map +1 -1
  55. package/dist/esm/batcher.d.ts +8 -2
  56. package/dist/esm/batcher.js +9 -1
  57. package/dist/esm/batcher.js.map +1 -1
  58. package/dist/esm/debouncer.d.ts +11 -1
  59. package/dist/esm/debouncer.js +16 -4
  60. package/dist/esm/debouncer.js.map +1 -1
  61. package/dist/esm/event-client.d.ts +42 -0
  62. package/dist/esm/event-client.js +18 -0
  63. package/dist/esm/event-client.js.map +1 -0
  64. package/dist/esm/index.d.ts +2 -0
  65. package/dist/esm/index.js +4 -1
  66. package/dist/esm/index.js.map +1 -1
  67. package/dist/esm/queuer.d.ts +11 -1
  68. package/dist/esm/queuer.js +14 -2
  69. package/dist/esm/queuer.js.map +1 -1
  70. package/dist/esm/rate-limiter.d.ts +11 -1
  71. package/dist/esm/rate-limiter.js +16 -4
  72. package/dist/esm/rate-limiter.js.map +1 -1
  73. package/dist/esm/throttler.d.ts +11 -1
  74. package/dist/esm/throttler.js +16 -4
  75. package/dist/esm/throttler.js.map +1 -1
  76. package/dist/esm/utils.d.ts +1 -0
  77. package/dist/esm/utils.js +10 -0
  78. package/dist/esm/utils.js.map +1 -1
  79. package/package.json +12 -1
  80. package/src/async-batcher.ts +23 -3
  81. package/src/async-debouncer.ts +30 -7
  82. package/src/async-queuer.ts +29 -4
  83. package/src/async-rate-limiter.ts +28 -4
  84. package/src/async-throttler.ts +27 -5
  85. package/src/batcher.ts +18 -3
  86. package/src/debouncer.ts +30 -5
  87. package/src/event-client.ts +63 -0
  88. package/src/index.ts +2 -0
  89. package/src/queuer.ts +26 -2
  90. package/src/rate-limiter.ts +29 -5
  91. package/src/throttler.ts +29 -5
  92. package/src/utils.ts +15 -0
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,13 +256,15 @@ 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
 
250
263
  constructor(
251
- private fn: (item: TValue) => void,
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,
@@ -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
  */
@@ -26,13 +31,14 @@ export interface RateLimiterState {
26
31
  }
27
32
 
28
33
  function getDefaultRateLimiterState(): RateLimiterState {
29
- return structuredClone({
34
+ return {
30
35
  executionCount: 0,
31
36
  executionTimes: [],
32
37
  isExceeded: false,
33
38
  rejectionCount: 0,
34
39
  status: 'idle',
35
- })
40
+ maybeExecuteCount: 0,
41
+ }
36
42
  }
37
43
 
38
44
  /**
@@ -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,13 +143,15 @@ 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
 
138
150
  constructor(
139
- private fn: TFn,
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
  */
@@ -32,14 +37,15 @@ export interface ThrottlerState<TFn extends AnyFunction> {
32
37
  function getDefaultThrottlerState<
33
38
  TFn extends AnyFunction,
34
39
  >(): ThrottlerState<TFn> {
35
- return structuredClone({
40
+ return {
36
41
  executionCount: 0,
37
42
  isPending: false,
38
43
  lastArgs: undefined,
39
44
  lastExecutionTime: 0,
40
45
  nextExecutionTime: 0,
41
46
  status: 'idle',
42
- })
47
+ maybeExecuteCount: 0,
48
+ }
43
49
  }
44
50
 
45
51
  /**
@@ -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,18 +138,26 @@ 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
 
133
145
  constructor(
134
- private fn: TFn,
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()
package/src/utils.ts CHANGED
@@ -10,3 +10,18 @@ export function parseFunctionOrValue<T, TArgs extends Array<any>>(
10
10
  ): T {
11
11
  return isFunction(value) ? value(...args) : value
12
12
  }
13
+
14
+ export function createKey(key?: string): string {
15
+ if (key) {
16
+ return key
17
+ }
18
+
19
+ if (
20
+ typeof crypto !== 'undefined' &&
21
+ typeof crypto.randomUUID === 'function'
22
+ ) {
23
+ return crypto.randomUUID()
24
+ }
25
+
26
+ return ''
27
+ }