@tanstack/pacer 0.4.0 → 0.5.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 (67) hide show
  1. package/dist/cjs/async-debouncer.cjs +16 -3
  2. package/dist/cjs/async-debouncer.cjs.map +1 -1
  3. package/dist/cjs/async-debouncer.d.cts +14 -4
  4. package/dist/cjs/async-queuer.cjs +20 -6
  5. package/dist/cjs/async-queuer.cjs.map +1 -1
  6. package/dist/cjs/async-queuer.d.cts +21 -8
  7. package/dist/cjs/async-rate-limiter.cjs +28 -7
  8. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  9. package/dist/cjs/async-rate-limiter.d.cts +21 -6
  10. package/dist/cjs/async-throttler.cjs +19 -5
  11. package/dist/cjs/async-throttler.cjs.map +1 -1
  12. package/dist/cjs/async-throttler.d.cts +14 -4
  13. package/dist/cjs/debouncer.cjs +16 -3
  14. package/dist/cjs/debouncer.cjs.map +1 -1
  15. package/dist/cjs/debouncer.d.cts +14 -4
  16. package/dist/cjs/index.cjs +2 -0
  17. package/dist/cjs/index.cjs.map +1 -1
  18. package/dist/cjs/queuer.cjs +13 -5
  19. package/dist/cjs/queuer.cjs.map +1 -1
  20. package/dist/cjs/queuer.d.cts +9 -3
  21. package/dist/cjs/rate-limiter.cjs +26 -7
  22. package/dist/cjs/rate-limiter.cjs.map +1 -1
  23. package/dist/cjs/rate-limiter.d.cts +20 -6
  24. package/dist/cjs/throttler.cjs +19 -5
  25. package/dist/cjs/throttler.cjs.map +1 -1
  26. package/dist/cjs/throttler.d.cts +15 -4
  27. package/dist/cjs/utils.cjs +18 -7
  28. package/dist/cjs/utils.cjs.map +1 -1
  29. package/dist/cjs/utils.d.cts +3 -0
  30. package/dist/esm/async-debouncer.d.ts +14 -4
  31. package/dist/esm/async-debouncer.js +16 -3
  32. package/dist/esm/async-debouncer.js.map +1 -1
  33. package/dist/esm/async-queuer.d.ts +21 -8
  34. package/dist/esm/async-queuer.js +20 -6
  35. package/dist/esm/async-queuer.js.map +1 -1
  36. package/dist/esm/async-rate-limiter.d.ts +21 -6
  37. package/dist/esm/async-rate-limiter.js +28 -7
  38. package/dist/esm/async-rate-limiter.js.map +1 -1
  39. package/dist/esm/async-throttler.d.ts +14 -4
  40. package/dist/esm/async-throttler.js +19 -5
  41. package/dist/esm/async-throttler.js.map +1 -1
  42. package/dist/esm/debouncer.d.ts +14 -4
  43. package/dist/esm/debouncer.js +16 -3
  44. package/dist/esm/debouncer.js.map +1 -1
  45. package/dist/esm/index.js +3 -1
  46. package/dist/esm/queuer.d.ts +9 -3
  47. package/dist/esm/queuer.js +13 -5
  48. package/dist/esm/queuer.js.map +1 -1
  49. package/dist/esm/rate-limiter.d.ts +20 -6
  50. package/dist/esm/rate-limiter.js +26 -7
  51. package/dist/esm/rate-limiter.js.map +1 -1
  52. package/dist/esm/throttler.d.ts +15 -4
  53. package/dist/esm/throttler.js +19 -5
  54. package/dist/esm/throttler.js.map +1 -1
  55. package/dist/esm/utils.d.ts +3 -0
  56. package/dist/esm/utils.js +19 -8
  57. package/dist/esm/utils.js.map +1 -1
  58. package/package.json +9 -3
  59. package/src/async-debouncer.ts +24 -7
  60. package/src/async-queuer.ts +32 -13
  61. package/src/async-rate-limiter.ts +41 -13
  62. package/src/async-throttler.ts +27 -9
  63. package/src/debouncer.ts +24 -7
  64. package/src/queuer.ts +19 -7
  65. package/src/rate-limiter.ts +37 -13
  66. package/src/throttler.ts +28 -9
  67. package/src/utils.ts +19 -5
@@ -1,3 +1,4 @@
1
+ import { parseFunctionOrValue } from './utils'
1
2
  import type { AnyFunction } from './types'
2
3
 
3
4
  /**
@@ -8,11 +9,12 @@ export interface RateLimiterOptions<TFn extends AnyFunction> {
8
9
  * Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions.
9
10
  * Defaults to true.
10
11
  */
11
- enabled?: boolean
12
+ enabled?: boolean | ((rateLimiter: RateLimiter<TFn>) => boolean)
12
13
  /**
13
- * Maximum number of executions allowed within the time window
14
+ * Maximum number of executions allowed within the time window.
15
+ * Can be a number or a callback function that receives the rate limiter instance and returns a number.
14
16
  */
15
- limit: number
17
+ limit: number | ((rateLimiter: RateLimiter<TFn>) => number)
16
18
  /**
17
19
  * Callback function that is called after the function is executed
18
20
  */
@@ -22,9 +24,10 @@ export interface RateLimiterOptions<TFn extends AnyFunction> {
22
24
  */
23
25
  onReject?: (rateLimiter: RateLimiter<TFn>) => void
24
26
  /**
25
- * Time window in milliseconds within which the limit applies
27
+ * Time window in milliseconds within which the limit applies.
28
+ * Can be a number or a callback function that receives the rate limiter instance and returns a number.
26
29
  */
27
- window: number
30
+ window: number | ((rateLimiter: RateLimiter<TFn>) => number)
28
31
  /**
29
32
  * Type of window to use for rate limiting
30
33
  * - 'fixed': Uses a fixed window that resets after the window period
@@ -105,6 +108,27 @@ export class RateLimiter<TFn extends AnyFunction> {
105
108
  return this._options as Required<RateLimiterOptions<TFn>>
106
109
  }
107
110
 
111
+ /**
112
+ * Returns the current enabled state of the rate limiter
113
+ */
114
+ getEnabled(): boolean {
115
+ return parseFunctionOrValue(this._options.enabled, this)!
116
+ }
117
+
118
+ /**
119
+ * Returns the current limit of executions allowed within the time window
120
+ */
121
+ getLimit(): number {
122
+ return parseFunctionOrValue(this._options.limit, this)
123
+ }
124
+
125
+ /**
126
+ * Returns the current time window in milliseconds
127
+ */
128
+ getWindow(): number {
129
+ return parseFunctionOrValue(this._options.window, this)
130
+ }
131
+
108
132
  /**
109
133
  * Attempts to execute the rate-limited function if within the configured limits.
110
134
  * Will reject execution if the number of calls in the current window exceeds the limit.
@@ -125,7 +149,7 @@ export class RateLimiter<TFn extends AnyFunction> {
125
149
 
126
150
  if (this._options.windowType === 'sliding') {
127
151
  // For sliding window, we can execute if we have capacity in the current window
128
- if (this._executionTimes.length < this._options.limit) {
152
+ if (this._executionTimes.length < this.getLimit()) {
129
153
  this.executeFunction(...args)
130
154
  return true
131
155
  }
@@ -133,9 +157,9 @@ export class RateLimiter<TFn extends AnyFunction> {
133
157
  // For fixed window, we need to check if we're in a new window
134
158
  const now = Date.now()
135
159
  const oldestExecution = Math.min(...this._executionTimes)
136
- const isNewWindow = oldestExecution + this._options.window <= now
160
+ const isNewWindow = oldestExecution + this.getWindow() <= now
137
161
 
138
- if (isNewWindow || this._executionTimes.length < this._options.limit) {
162
+ if (isNewWindow || this._executionTimes.length < this.getLimit()) {
139
163
  this.executeFunction(...args)
140
164
  return true
141
165
  }
@@ -146,7 +170,7 @@ export class RateLimiter<TFn extends AnyFunction> {
146
170
  }
147
171
 
148
172
  private executeFunction(...args: Parameters<TFn>): void {
149
- if (!this._options.enabled) return
173
+ if (!this.getEnabled()) return
150
174
  const now = Date.now()
151
175
  this._executionCount++
152
176
  this._executionTimes.push(now)
@@ -163,7 +187,7 @@ export class RateLimiter<TFn extends AnyFunction> {
163
187
 
164
188
  private cleanupOldExecutions(): void {
165
189
  const now = Date.now()
166
- const windowStart = now - this._options.window
190
+ const windowStart = now - this.getWindow()
167
191
  this._executionTimes = this._executionTimes.filter(
168
192
  (time) => time > windowStart,
169
193
  )
@@ -188,7 +212,7 @@ export class RateLimiter<TFn extends AnyFunction> {
188
212
  */
189
213
  getRemainingInWindow(): number {
190
214
  this.cleanupOldExecutions()
191
- return Math.max(0, this._options.limit - this._executionTimes.length)
215
+ return Math.max(0, this.getLimit() - this._executionTimes.length)
192
216
  }
193
217
 
194
218
  /**
@@ -199,7 +223,7 @@ export class RateLimiter<TFn extends AnyFunction> {
199
223
  return 0
200
224
  }
201
225
  const oldestExecution = Math.min(...this._executionTimes)
202
- return oldestExecution + this._options.window - Date.now()
226
+ return oldestExecution + this.getWindow() - Date.now()
203
227
  }
204
228
 
205
229
  /**
@@ -251,7 +275,7 @@ export class RateLimiter<TFn extends AnyFunction> {
251
275
  */
252
276
  export function rateLimit<TFn extends AnyFunction>(
253
277
  fn: TFn,
254
- initialOptions: Omit<RateLimiterOptions<TFn>, 'enabled'>,
278
+ initialOptions: RateLimiterOptions<TFn>,
255
279
  ) {
256
280
  const rateLimiter = new RateLimiter(fn, initialOptions)
257
281
  return rateLimiter.maybeExecute.bind(rateLimiter)
package/src/throttler.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { parseFunctionOrValue } from './utils'
1
2
  import type { AnyFunction } from './types'
2
3
 
3
4
  /**
@@ -6,9 +7,10 @@ import type { AnyFunction } from './types'
6
7
  export interface ThrottlerOptions<TFn extends AnyFunction> {
7
8
  /**
8
9
  * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions.
10
+ * Can be a boolean or a function that returns a boolean.
9
11
  * Defaults to true.
10
12
  */
11
- enabled?: boolean
13
+ enabled?: boolean | ((throttler: Throttler<TFn>) => boolean)
12
14
  /**
13
15
  * Whether to execute on the leading edge of the timeout.
14
16
  * Defaults to true.
@@ -24,9 +26,11 @@ export interface ThrottlerOptions<TFn extends AnyFunction> {
24
26
  */
25
27
  trailing?: boolean
26
28
  /**
27
- * Time window in milliseconds during which the function can only be executed once
29
+ * Time window in milliseconds during which the function can only be executed once.
30
+ * Can be a number or a function that returns a number.
31
+ * Defaults to 0ms
28
32
  */
29
- wait: number
33
+ wait: number | ((throttler: Throttler<TFn>) => number)
30
34
  }
31
35
 
32
36
  const defaultOptions: Required<ThrottlerOptions<any>> = {
@@ -101,6 +105,20 @@ export class Throttler<TFn extends AnyFunction> {
101
105
  return this._options
102
106
  }
103
107
 
108
+ /**
109
+ * Returns the current enabled state of the throttler
110
+ */
111
+ getEnabled(): boolean {
112
+ return parseFunctionOrValue(this._options.enabled, this)
113
+ }
114
+
115
+ /**
116
+ * Returns the current wait time in milliseconds
117
+ */
118
+ getWait(): number {
119
+ return parseFunctionOrValue(this._options.wait, this)
120
+ }
121
+
104
122
  /**
105
123
  * Attempts to execute the throttled function. The execution behavior depends on the throttler options:
106
124
  *
@@ -126,9 +144,10 @@ export class Throttler<TFn extends AnyFunction> {
126
144
  maybeExecute(...args: Parameters<TFn>): void {
127
145
  const now = Date.now()
128
146
  const timeSinceLastExecution = now - this._lastExecutionTime
147
+ const wait = this.getWait()
129
148
 
130
149
  // Handle leading execution
131
- if (this._options.leading && timeSinceLastExecution >= this._options.wait) {
150
+ if (this._options.leading && timeSinceLastExecution >= wait) {
132
151
  this.executeFunction(...args)
133
152
  } else {
134
153
  // Store the most recent arguments for potential trailing execution
@@ -139,7 +158,7 @@ export class Throttler<TFn extends AnyFunction> {
139
158
  const _timeSinceLastExecution = this._lastExecutionTime
140
159
  ? now - this._lastExecutionTime
141
160
  : 0
142
- const timeoutDuration = this._options.wait - _timeSinceLastExecution
161
+ const timeoutDuration = wait - _timeSinceLastExecution
143
162
  this._timeoutId = setTimeout(() => {
144
163
  if (this._lastArgs !== undefined) {
145
164
  this.executeFunction(...this._lastArgs)
@@ -150,7 +169,7 @@ export class Throttler<TFn extends AnyFunction> {
150
169
  }
151
170
 
152
171
  private executeFunction(...args: Parameters<TFn>): void {
153
- if (!this._options.enabled) return
172
+ if (!this.getEnabled()) return
154
173
  this.fn(...args) // EXECUTE!
155
174
  this._executionCount++
156
175
  this._lastExecutionTime = Date.now()
@@ -187,7 +206,7 @@ export class Throttler<TFn extends AnyFunction> {
187
206
  * Returns the next execution time
188
207
  */
189
208
  getNextExecutionTime(): number {
190
- return this._lastExecutionTime + this._options.wait
209
+ return this._lastExecutionTime + this.getWait()
191
210
  }
192
211
 
193
212
  /**
@@ -201,7 +220,7 @@ export class Throttler<TFn extends AnyFunction> {
201
220
  * Returns `true` if there is a pending execution
202
221
  */
203
222
  getIsPending(): boolean {
204
- return this._options.enabled && !!this._timeoutId
223
+ return this.getEnabled() && !!this._timeoutId
205
224
  }
206
225
  }
207
226
 
@@ -233,7 +252,7 @@ export class Throttler<TFn extends AnyFunction> {
233
252
  */
234
253
  export function throttle<TFn extends AnyFunction>(
235
254
  fn: TFn,
236
- initialOptions: Omit<ThrottlerOptions<TFn>, 'enabled'>,
255
+ initialOptions: ThrottlerOptions<TFn>,
237
256
  ) {
238
257
  const throttler = new Throttler(fn, initialOptions)
239
258
  return throttler.maybeExecute.bind(throttler)
package/src/utils.ts CHANGED
@@ -1,13 +1,27 @@
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
+ }
13
+
1
14
  export function bindInstanceMethods<T extends Record<string, any>>(
2
15
  instance: T,
3
16
  ): T {
4
- return Object.getOwnPropertyNames(Object.getPrototypeOf(instance))
5
- .filter((key) => typeof instance[key as keyof T] === 'function')
6
- .reduce((acc: any, key) => {
17
+ return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).reduce(
18
+ (acc: any, key) => {
7
19
  const method = instance[key as keyof T]
8
- if (typeof method === 'function') {
20
+ if (isFunction(method)) {
9
21
  acc[key] = method.bind(instance)
10
22
  }
11
23
  return acc
12
- }, {} as T)
24
+ },
25
+ instance,
26
+ )
13
27
  }