@tanstack/pacer 0.1.0 → 0.2.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 (74) hide show
  1. package/dist/cjs/async-debouncer.cjs +60 -44
  2. package/dist/cjs/async-debouncer.cjs.map +1 -1
  3. package/dist/cjs/async-debouncer.d.cts +37 -24
  4. package/dist/cjs/async-queuer.cjs +149 -125
  5. package/dist/cjs/async-queuer.cjs.map +1 -1
  6. package/dist/cjs/async-queuer.d.cts +65 -48
  7. package/dist/cjs/async-rate-limiter.cjs +63 -46
  8. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  9. package/dist/cjs/async-rate-limiter.d.cts +39 -27
  10. package/dist/cjs/async-throttler.cjs +70 -47
  11. package/dist/cjs/async-throttler.cjs.map +1 -1
  12. package/dist/cjs/async-throttler.d.cts +43 -25
  13. package/dist/cjs/debouncer.cjs +46 -22
  14. package/dist/cjs/debouncer.cjs.map +1 -1
  15. package/dist/cjs/debouncer.d.cts +25 -11
  16. package/dist/cjs/index.cjs +2 -0
  17. package/dist/cjs/index.cjs.map +1 -1
  18. package/dist/cjs/index.d.cts +2 -0
  19. package/dist/cjs/queuer.cjs +114 -104
  20. package/dist/cjs/queuer.cjs.map +1 -1
  21. package/dist/cjs/queuer.d.cts +53 -40
  22. package/dist/cjs/rate-limiter.cjs +54 -42
  23. package/dist/cjs/rate-limiter.cjs.map +1 -1
  24. package/dist/cjs/rate-limiter.d.cts +37 -45
  25. package/dist/cjs/throttler.cjs +61 -41
  26. package/dist/cjs/throttler.cjs.map +1 -1
  27. package/dist/cjs/throttler.d.cts +35 -22
  28. package/dist/cjs/types.d.cts +12 -0
  29. package/dist/cjs/utils.cjs +13 -0
  30. package/dist/cjs/utils.cjs.map +1 -0
  31. package/dist/cjs/utils.d.cts +1 -0
  32. package/dist/esm/async-debouncer.d.ts +37 -24
  33. package/dist/esm/async-debouncer.js +60 -44
  34. package/dist/esm/async-debouncer.js.map +1 -1
  35. package/dist/esm/async-queuer.d.ts +65 -48
  36. package/dist/esm/async-queuer.js +149 -125
  37. package/dist/esm/async-queuer.js.map +1 -1
  38. package/dist/esm/async-rate-limiter.d.ts +39 -27
  39. package/dist/esm/async-rate-limiter.js +63 -46
  40. package/dist/esm/async-rate-limiter.js.map +1 -1
  41. package/dist/esm/async-throttler.d.ts +43 -25
  42. package/dist/esm/async-throttler.js +70 -47
  43. package/dist/esm/async-throttler.js.map +1 -1
  44. package/dist/esm/debouncer.d.ts +25 -11
  45. package/dist/esm/debouncer.js +46 -22
  46. package/dist/esm/debouncer.js.map +1 -1
  47. package/dist/esm/index.d.ts +2 -0
  48. package/dist/esm/index.js +2 -0
  49. package/dist/esm/index.js.map +1 -1
  50. package/dist/esm/queuer.d.ts +53 -40
  51. package/dist/esm/queuer.js +114 -104
  52. package/dist/esm/queuer.js.map +1 -1
  53. package/dist/esm/rate-limiter.d.ts +37 -45
  54. package/dist/esm/rate-limiter.js +54 -42
  55. package/dist/esm/rate-limiter.js.map +1 -1
  56. package/dist/esm/throttler.d.ts +35 -22
  57. package/dist/esm/throttler.js +61 -41
  58. package/dist/esm/throttler.js.map +1 -1
  59. package/dist/esm/types.d.ts +12 -0
  60. package/dist/esm/utils.d.ts +1 -0
  61. package/dist/esm/utils.js +13 -0
  62. package/dist/esm/utils.js.map +1 -0
  63. package/package.json +8 -1
  64. package/src/async-debouncer.ts +90 -62
  65. package/src/async-queuer.ts +178 -145
  66. package/src/async-rate-limiter.ts +93 -67
  67. package/src/async-throttler.ts +98 -63
  68. package/src/debouncer.ts +71 -35
  69. package/src/index.ts +2 -0
  70. package/src/queuer.ts +135 -118
  71. package/src/rate-limiter.ts +79 -81
  72. package/src/throttler.ts +87 -61
  73. package/src/types.ts +17 -0
  74. package/src/utils.ts +13 -0
package/src/throttler.ts CHANGED
@@ -1,7 +1,12 @@
1
+ import type { AnyFunction } from './types'
2
+
1
3
  /**
2
4
  * Options for configuring a throttled function
3
5
  */
4
- export interface ThrottlerOptions {
6
+ export interface ThrottlerOptions<
7
+ TFn extends AnyFunction,
8
+ TArgs extends Parameters<TFn>,
9
+ > {
5
10
  /**
6
11
  * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions.
7
12
  * Defaults to true.
@@ -12,6 +17,10 @@ export interface ThrottlerOptions {
12
17
  * Defaults to true.
13
18
  */
14
19
  leading?: boolean
20
+ /**
21
+ * Callback function that is called after the function is executed
22
+ */
23
+ onExecute?: (throttler: Throttler<TFn, TArgs>) => void
15
24
  /**
16
25
  * Whether to execute on the trailing edge of the timeout.
17
26
  * Defaults to true.
@@ -23,11 +32,12 @@ export interface ThrottlerOptions {
23
32
  wait: number
24
33
  }
25
34
 
26
- const defaultOptions: Required<ThrottlerOptions> = {
35
+ const defaultOptions: Required<ThrottlerOptions<any, any>> = {
27
36
  enabled: true,
28
37
  leading: true,
29
38
  trailing: true,
30
39
  wait: 0,
40
+ onExecute: () => {},
31
41
  }
32
42
 
33
43
  /**
@@ -41,8 +51,7 @@ const defaultOptions: Required<ThrottlerOptions> = {
41
51
  * - Leading: Execute immediately on first call (default: true)
42
52
  * - Trailing: Execute after wait period if called during throttle (default: true)
43
53
  *
44
- * For rate limiting or hard API limits, consider using RateLimiter instead.
45
- * For collapsing rapid-fire events, consider using Debouncer.
54
+ * For collapsing rapid-fire events where you only care about the last call, consider using Debouncer.
46
55
  *
47
56
  * @example
48
57
  * ```ts
@@ -58,21 +67,19 @@ const defaultOptions: Required<ThrottlerOptions> = {
58
67
  * throttler.maybeExecute('123'); // Throttled
59
68
  * ```
60
69
  */
61
- export class Throttler<
62
- TFn extends (...args: Array<any>) => any,
63
- TArgs extends Parameters<TFn>,
64
- > {
65
- private executionCount = 0
66
- private lastArgs: TArgs | undefined
67
- private lastExecutionTime = 0
68
- private options: Required<ThrottlerOptions>
69
- private timeoutId: NodeJS.Timeout | undefined
70
+ export class Throttler<TFn extends AnyFunction, TArgs extends Parameters<TFn>> {
71
+ private _executionCount = 0
72
+ private _lastArgs: TArgs | undefined
73
+ private _lastExecutionTime = 0
74
+ private _options: Required<ThrottlerOptions<TFn, TArgs>>
75
+ private _timeoutId: NodeJS.Timeout | undefined
76
+ private _isPending = false
70
77
 
71
78
  constructor(
72
79
  private fn: TFn,
73
- initialOptions: ThrottlerOptions,
80
+ initialOptions: ThrottlerOptions<TFn, TArgs>,
74
81
  ) {
75
- this.options = {
82
+ this._options = {
76
83
  ...defaultOptions,
77
84
  ...initialOptions,
78
85
  }
@@ -83,34 +90,20 @@ export class Throttler<
83
90
  * Returns the new options state
84
91
  */
85
92
  setOptions(
86
- newOptions: Partial<ThrottlerOptions>,
87
- ): Required<ThrottlerOptions> {
88
- this.options = {
89
- ...this.options,
93
+ newOptions: Partial<ThrottlerOptions<TFn, TArgs>>,
94
+ ): Required<ThrottlerOptions<TFn, TArgs>> {
95
+ this._options = {
96
+ ...this._options,
90
97
  ...newOptions,
91
98
  }
92
- return this.options
93
- }
94
-
95
- /**
96
- * Returns the number of times the function has been executed
97
- */
98
- getExecutionCount(): number {
99
- return this.executionCount
99
+ return this._options
100
100
  }
101
101
 
102
102
  /**
103
- * Returns the last execution time
103
+ * Returns the current throttler options
104
104
  */
105
- getLastExecutionTime(): number {
106
- return this.lastExecutionTime
107
- }
108
-
109
- /**
110
- * Returns the next execution time
111
- */
112
- getNextExecutionTime(): number {
113
- return this.lastExecutionTime + this.options.wait
105
+ getOptions(): Required<ThrottlerOptions<TFn, TArgs>> {
106
+ return this._options
114
107
  }
115
108
 
116
109
  /**
@@ -137,36 +130,40 @@ export class Throttler<
137
130
  */
138
131
  maybeExecute(...args: TArgs): void {
139
132
  const now = Date.now()
140
- const timeSinceLastExecution = now - this.lastExecutionTime
133
+ const timeSinceLastExecution = now - this._lastExecutionTime
141
134
 
142
135
  // Handle leading execution
143
- if (timeSinceLastExecution >= this.options.wait) {
144
- if (this.options.leading) {
136
+ if (timeSinceLastExecution >= this._options.wait) {
137
+ if (this._options.leading) {
145
138
  this.executeFunction(...args)
146
139
  }
147
- this.lastExecutionTime = now
140
+ this._lastExecutionTime = now
141
+ this._isPending = false
148
142
  } else {
149
143
  // Store the most recent arguments for potential trailing execution
150
- this.lastArgs = args
144
+ this._lastArgs = args
151
145
 
152
146
  // Set up trailing execution if not already scheduled
153
- if (!this.timeoutId && this.options.trailing) {
154
- this.timeoutId = setTimeout(() => {
155
- if (this.lastArgs) {
156
- this.executeFunction(...this.lastArgs)
157
- this.lastArgs = undefined
147
+ if (!this._timeoutId && this._options.trailing) {
148
+ this._isPending = true
149
+ this._timeoutId = setTimeout(() => {
150
+ if (this._lastArgs) {
151
+ this.executeFunction(...this._lastArgs)
152
+ this._lastArgs = undefined
158
153
  }
159
- this.lastExecutionTime = Date.now()
160
- this.timeoutId = undefined
161
- }, this.options.wait - timeSinceLastExecution)
154
+ this._lastExecutionTime = Date.now()
155
+ this._timeoutId = undefined
156
+ this._isPending = false
157
+ this._options.onExecute(this)
158
+ }, this._options.wait - timeSinceLastExecution)
162
159
  }
163
160
  }
164
161
  }
165
162
 
166
163
  private executeFunction(...args: TArgs): void {
167
- if (!this.options.enabled) return
168
- this.executionCount++
169
- this.fn(...args)
164
+ if (!this._options.enabled) return
165
+ this.fn(...args) // EXECUTE!
166
+ this._executionCount++
170
167
  }
171
168
 
172
169
  /**
@@ -179,12 +176,41 @@ export class Throttler<
179
176
  * Has no effect if there is no pending execution.
180
177
  */
181
178
  cancel(): void {
182
- if (this.timeoutId) {
183
- clearTimeout(this.timeoutId)
184
- this.timeoutId = undefined
185
- this.lastArgs = undefined
179
+ if (this._timeoutId) {
180
+ clearTimeout(this._timeoutId)
181
+ this._timeoutId = undefined
182
+ this._lastArgs = undefined
183
+ this._isPending = false
186
184
  }
187
185
  }
186
+
187
+ /**
188
+ * Returns the number of times the function has been executed
189
+ */
190
+ getExecutionCount(): number {
191
+ return this._executionCount
192
+ }
193
+
194
+ /**
195
+ * Returns `true` if there is a pending execution
196
+ */
197
+ getIsPending(): boolean {
198
+ return this._options.enabled && this._isPending
199
+ }
200
+
201
+ /**
202
+ * Returns the last execution time
203
+ */
204
+ getLastExecutionTime(): number {
205
+ return this._lastExecutionTime
206
+ }
207
+
208
+ /**
209
+ * Returns the next execution time
210
+ */
211
+ getNextExecutionTime(): number {
212
+ return this._lastExecutionTime + this._options.wait
213
+ }
188
214
  }
189
215
 
190
216
  /**
@@ -213,10 +239,10 @@ export class Throttler<
213
239
  * });
214
240
  * ```
215
241
  */
216
- export function throttle<TFn extends (...args: Array<any>) => any>(
217
- fn: TFn,
218
- initialOptions: Omit<ThrottlerOptions, 'enabled'>,
219
- ) {
242
+ export function throttle<
243
+ TFn extends AnyFunction,
244
+ TArgs extends Parameters<TFn>,
245
+ >(fn: TFn, initialOptions: Omit<ThrottlerOptions<TFn, TArgs>, 'enabled'>) {
220
246
  const throttler = new Throttler(fn, initialOptions)
221
247
  return throttler.maybeExecute.bind(throttler)
222
248
  }
package/src/types.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Represents a function that can be called with any arguments and returns any value.
3
+ * @template TArgs - The type of the arguments the function can be called with.
4
+ * @returns The return value of the function.
5
+ */
6
+ export type AnyFunction<TArgs extends Array<any> = Array<any>> = (
7
+ ...args: TArgs
8
+ ) => any
9
+
10
+ /**
11
+ * Represents an asynchronous function that can be called with any arguments and returns a promise.
12
+ * @template TArgs - The type of the arguments the function can be called with.
13
+ * @returns A promise that resolves to the return value of the function.
14
+ */
15
+ export type AnyAsyncFunction<TArgs extends Array<any> = Array<any>> = (
16
+ ...args: TArgs
17
+ ) => Promise<any>
package/src/utils.ts ADDED
@@ -0,0 +1,13 @@
1
+ export function bindInstanceMethods<T extends Record<string, any>>(
2
+ instance: T,
3
+ ) {
4
+ return Object.getOwnPropertyNames(Object.getPrototypeOf(instance))
5
+ .filter((key) => typeof instance[key as keyof T] === 'function')
6
+ .reduce((acc: any, key) => {
7
+ const method = instance[key as keyof T]
8
+ if (typeof method === 'function') {
9
+ acc[key] = method.bind(instance)
10
+ }
11
+ return acc
12
+ }, {} as T)
13
+ }