@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 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["export function bindInstanceMethods<T extends Record<string, any>>(\n instance: T,\n): T {\n return Object.getOwnPropertyNames(Object.getPrototypeOf(instance))\n .filter((key) => typeof instance[key as keyof T] === 'function')\n .reduce((acc: any, key) => {\n const method = instance[key as keyof T]\n if (typeof method === 'function') {\n acc[key] = method.bind(instance)\n }\n return acc\n }, {} as T)\n}\n"],"names":[],"mappings":"AAAO,SAAS,oBACd,UACG;AACH,SAAO,OAAO,oBAAoB,OAAO,eAAe,QAAQ,CAAC,EAC9D,OAAO,CAAC,QAAQ,OAAO,SAAS,GAAc,MAAM,UAAU,EAC9D,OAAO,CAAC,KAAU,QAAQ;AACnB,UAAA,SAAS,SAAS,GAAc;AAClC,QAAA,OAAO,WAAW,YAAY;AAChC,UAAI,GAAG,IAAI,OAAO,KAAK,QAAQ;AAAA,IAAA;AAE1B,WAAA;AAAA,EACT,GAAG,EAAO;AACd;"}
1
+ {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["import type { AnyFunction } from './types'\n\nexport function isFunction<T extends AnyFunction>(value: any): value is T {\n return typeof value === 'function'\n}\n\nexport function parseFunctionOrValue<T, TArgs extends Array<any>>(\n value: T | ((...args: TArgs) => T),\n ...args: TArgs\n): T {\n return isFunction(value) ? value(...args) : value\n}\n\nexport function bindInstanceMethods<T extends Record<string, any>>(\n instance: T,\n): T {\n return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).reduce(\n (acc: any, key) => {\n const method = instance[key as keyof T]\n if (isFunction(method)) {\n acc[key] = method.bind(instance)\n }\n return acc\n },\n instance,\n )\n}\n"],"names":[],"mappings":"AAEO,SAAS,WAAkC,OAAwB;AACxE,SAAO,OAAO,UAAU;AAC1B;AAEgB,SAAA,qBACd,UACG,MACA;AACH,SAAO,WAAW,KAAK,IAAI,MAAM,GAAG,IAAI,IAAI;AAC9C;AAEO,SAAS,oBACd,UACG;AACH,SAAO,OAAO,oBAAoB,OAAO,eAAe,QAAQ,CAAC,EAAE;AAAA,IACjE,CAAC,KAAU,QAAQ;AACX,YAAA,SAAS,SAAS,GAAc;AAClC,UAAA,WAAW,MAAM,GAAG;AACtB,YAAI,GAAG,IAAI,OAAO,KAAK,QAAQ;AAAA,MAAA;AAE1B,aAAA;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/pacer",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Utilities for debouncing, throttling, rate-limiting, queuing, and more.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -131,8 +131,14 @@
131
131
  "types": "./dist/esm/types.d.ts"
132
132
  },
133
133
  "./utils": {
134
- "types": "./dist/esm/utils.d.ts",
135
- "default": "./dist/esm/utils.js"
134
+ "import": {
135
+ "types": "./dist/esm/utils.d.ts",
136
+ "default": "./dist/esm/utils.js"
137
+ },
138
+ "require": {
139
+ "types": "./dist/cjs/utils.d.cts",
140
+ "default": "./dist/cjs/utils.cjs"
141
+ }
136
142
  },
137
143
  "./package.json": "./package.json"
138
144
  },
@@ -1,3 +1,4 @@
1
+ import { parseFunctionOrValue } from './utils'
1
2
  import type { AnyAsyncFunction } from './types'
2
3
 
3
4
  /**
@@ -6,9 +7,10 @@ import type { AnyAsyncFunction } from './types'
6
7
  export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
7
8
  /**
8
9
  * Whether the debouncer 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 | ((debouncer: AsyncDebouncer<TFn>) => boolean)
12
14
  /**
13
15
  * Whether to execute on the leading edge of the timeout.
14
16
  * Defaults to false.
@@ -32,10 +34,11 @@ export interface AsyncDebouncerOptions<TFn extends AnyAsyncFunction> {
32
34
  */
33
35
  trailing?: boolean
34
36
  /**
35
- * Delay in milliseconds to wait after the last call before executing
37
+ * Delay in milliseconds to wait after the last call before executing.
38
+ * Can be a number or a function that returns a number.
36
39
  * Defaults to 0ms
37
40
  */
38
- wait: number
41
+ wait: number | ((debouncer: AsyncDebouncer<TFn>) => number)
39
42
  }
40
43
 
41
44
  const defaultOptions: Required<AsyncDebouncerOptions<any>> = {
@@ -117,6 +120,20 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
117
120
  return this._options
118
121
  }
119
122
 
123
+ /**
124
+ * Returns the current debouncer enabled state
125
+ */
126
+ getEnabled(): boolean {
127
+ return parseFunctionOrValue(this._options.enabled, this)
128
+ }
129
+
130
+ /**
131
+ * Returns the current debouncer wait state
132
+ */
133
+ getWait(): number {
134
+ return parseFunctionOrValue(this._options.wait, this)
135
+ }
136
+
120
137
  /**
121
138
  * Attempts to execute the debounced function
122
139
  * If a call is already in progress, it will be queued
@@ -149,14 +166,14 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
149
166
  // Reset state and resolve
150
167
  this._canLeadingExecute = true
151
168
  resolve(this._lastResult)
152
- }, this._options.wait)
169
+ }, this.getWait())
153
170
  })
154
171
  }
155
172
 
156
173
  private async executeFunction(
157
174
  ...args: Parameters<TFn>
158
175
  ): Promise<ReturnType<TFn> | undefined> {
159
- if (!this._options.enabled) return undefined
176
+ if (!this.getEnabled()) return undefined
160
177
  this._abortController = new AbortController()
161
178
  try {
162
179
  this._isExecuting = true
@@ -233,7 +250,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
233
250
  * Returns `true` if there is a pending execution queued up for trailing execution
234
251
  */
235
252
  getIsPending(): boolean {
236
- return this._options.enabled && this._isPending
253
+ return this.getEnabled() && this._isPending
237
254
  }
238
255
 
239
256
  /**
@@ -267,7 +284,7 @@ export class AsyncDebouncer<TFn extends AnyAsyncFunction> {
267
284
  */
268
285
  export function asyncDebounce<TFn extends AnyAsyncFunction>(
269
286
  fn: TFn,
270
- initialOptions: Omit<AsyncDebouncerOptions<TFn>, 'enabled'>,
287
+ initialOptions: AsyncDebouncerOptions<TFn>,
271
288
  ) {
272
289
  const asyncDebouncer = new AsyncDebouncer(fn, initialOptions)
273
290
  return asyncDebouncer.maybeExecute.bind(asyncDebouncer)
@@ -1,3 +1,5 @@
1
+ import { parseFunctionOrValue } from './utils'
2
+ import type { AnyAsyncFunction } from './types'
1
3
  import type { QueuePosition } from './queuer'
2
4
 
3
5
  export interface AsyncQueuerOptions<TValue> {
@@ -7,9 +9,11 @@ export interface AsyncQueuerOptions<TValue> {
7
9
  */
8
10
  addItemsTo?: QueuePosition
9
11
  /**
10
- * Maximum number of concurrent tasks to process
12
+ * Maximum number of concurrent tasks to process.
13
+ * Can be a number or a function that returns a number.
14
+ * @default 1
11
15
  */
12
- concurrency?: number
16
+ concurrency?: number | ((queuer: AsyncQueuer<TValue>) => number)
13
17
  /**
14
18
  * Maximum time in milliseconds that an item can stay in the queue
15
19
  * If not provided, items will never expire
@@ -67,9 +71,11 @@ export interface AsyncQueuerOptions<TValue> {
67
71
  */
68
72
  started?: boolean
69
73
  /**
70
- * Time in milliseconds to wait between processing items
74
+ * Time in milliseconds to wait between processing items.
75
+ * Can be a number or a function that returns a number.
76
+ * @default 0
71
77
  */
72
- wait?: number
78
+ wait?: number | ((queuer: AsyncQueuer<TValue>) => number)
73
79
  }
74
80
 
75
81
  const defaultOptions: Required<AsyncQueuerOptions<any>> = {
@@ -160,6 +166,20 @@ export class AsyncQueuer<TValue> {
160
166
  return this._options
161
167
  }
162
168
 
169
+ /**
170
+ * Returns the current wait time between processing items
171
+ */
172
+ getWait(): number {
173
+ return parseFunctionOrValue(this._options.wait, this)
174
+ }
175
+
176
+ /**
177
+ * Returns the current concurrency limit
178
+ */
179
+ getConcurrency(): number {
180
+ return parseFunctionOrValue(this._options.concurrency, this)
181
+ }
182
+
163
183
  /**
164
184
  * Processes items in the queuer
165
185
  */
@@ -173,7 +193,7 @@ export class AsyncQueuer<TValue> {
173
193
  this.checkExpiredItems()
174
194
 
175
195
  while (
176
- this._activeItems.size < this._options.concurrency &&
196
+ this._activeItems.size < this.getConcurrency() &&
177
197
  !this.getIsEmpty()
178
198
  ) {
179
199
  const nextFn = this.getNextItem()
@@ -204,8 +224,9 @@ export class AsyncQueuer<TValue> {
204
224
  }
205
225
  this._onSettledCallbacks.forEach((cb) => cb(success ? res : error!))
206
226
 
207
- if (this._options.wait > 0) {
208
- setTimeout(() => this.tick(), this._options.wait)
227
+ const wait = this.getWait()
228
+ if (wait > 0) {
229
+ setTimeout(() => this.tick(), wait)
209
230
  return
210
231
  }
211
232
 
@@ -322,9 +343,9 @@ export class AsyncQueuer<TValue> {
322
343
  * Adds a task to the queuer
323
344
  */
324
345
  addItem(
325
- fn: (() => Promise<TValue>) & { priority?: number },
346
+ fn: AnyAsyncFunction & { priority?: number },
326
347
  position: QueuePosition = this._options.addItemsTo,
327
- runOnUpdate: boolean = true,
348
+ runOnItemsChange: boolean = true,
328
349
  ): Promise<TValue> {
329
350
  if (this.getIsFull()) {
330
351
  this._rejectionCount++
@@ -381,7 +402,7 @@ export class AsyncQueuer<TValue> {
381
402
  }
382
403
  }
383
404
 
384
- if (runOnUpdate) {
405
+ if (runOnItemsChange) {
385
406
  this._options.onItemsChange(this)
386
407
  }
387
408
 
@@ -557,9 +578,7 @@ export class AsyncQueuer<TValue> {
557
578
  * @param options - Configuration options for the AsyncQueuer
558
579
  * @returns A bound addItem function that can be used to add tasks to the queuer
559
580
  */
560
- export function asyncQueue<TValue>(
561
- options: Omit<AsyncQueuerOptions<TValue>, 'started'> = {},
562
- ) {
581
+ export function asyncQueue<TValue>(options: AsyncQueuerOptions<TValue>) {
563
582
  const queuer = new AsyncQueuer<TValue>(options)
564
583
  return queuer.addItem.bind(queuer)
565
584
  }
@@ -1,3 +1,4 @@
1
+ import { parseFunctionOrValue } from './utils'
1
2
  import type { AnyAsyncFunction } from './types'
2
3
 
3
4
  /**
@@ -6,13 +7,15 @@ import type { AnyAsyncFunction } from './types'
6
7
  export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
7
8
  /**
8
9
  * Whether the rate limiter 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 | ((rateLimiter: AsyncRateLimiter<TFn>) => boolean)
12
14
  /**
13
- * Maximum number of executions allowed within the time window
15
+ * Maximum number of executions allowed within the time window.
16
+ * Can be a number or a function that returns a number.
14
17
  */
15
- limit: number
18
+ limit: number | ((rateLimiter: AsyncRateLimiter<TFn>) => number)
16
19
  /**
17
20
  * Optional error handler for when the rate-limited function throws
18
21
  */
@@ -33,9 +36,10 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
33
36
  rateLimiter: AsyncRateLimiter<TFn>,
34
37
  ) => void
35
38
  /**
36
- * Time window in milliseconds within which the limit applies
39
+ * Time window in milliseconds within which the limit applies.
40
+ * Can be a number or a function that returns a number.
37
41
  */
38
- window: number
42
+ window: number | ((rateLimiter: AsyncRateLimiter<TFn>) => number)
39
43
  /**
40
44
  * Type of window to use for rate limiting
41
45
  * - 'fixed': Uses a fixed window that resets after the window period
@@ -127,6 +131,27 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
127
131
  return this._options as Required<AsyncRateLimiterOptions<TFn>>
128
132
  }
129
133
 
134
+ /**
135
+ * Returns the current enabled state of the rate limiter
136
+ */
137
+ getEnabled(): boolean {
138
+ return !!parseFunctionOrValue(this._options.enabled, this)
139
+ }
140
+
141
+ /**
142
+ * Returns the current limit of executions allowed within the time window
143
+ */
144
+ getLimit(): number {
145
+ return parseFunctionOrValue(this._options.limit, this)
146
+ }
147
+
148
+ /**
149
+ * Returns the current time window in milliseconds
150
+ */
151
+ getWindow(): number {
152
+ return parseFunctionOrValue(this._options.window, this)
153
+ }
154
+
130
155
  /**
131
156
  * Attempts to execute the rate-limited function if within the configured limits.
132
157
  * Will reject execution if the number of calls in the current window exceeds the limit.
@@ -148,9 +173,12 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
148
173
  ): Promise<ReturnType<TFn> | undefined> {
149
174
  this.cleanupOldExecutions()
150
175
 
176
+ const limit = this.getLimit()
177
+ const window = this.getWindow()
178
+
151
179
  if (this._options.windowType === 'sliding') {
152
180
  // For sliding window, we can execute if we have capacity in the current window
153
- if (this._executionTimes.length < this._options.limit) {
181
+ if (this._executionTimes.length < limit) {
154
182
  await this.executeFunction(...args)
155
183
  return this._lastResult
156
184
  }
@@ -158,9 +186,9 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
158
186
  // For fixed window, we need to check if we're in a new window
159
187
  const now = Date.now()
160
188
  const oldestExecution = Math.min(...this._executionTimes)
161
- const isNewWindow = oldestExecution + this._options.window <= now
189
+ const isNewWindow = oldestExecution + window <= now
162
190
 
163
- if (isNewWindow || this._executionTimes.length < this._options.limit) {
191
+ if (isNewWindow || this._executionTimes.length < limit) {
164
192
  await this.executeFunction(...args)
165
193
  return this._lastResult
166
194
  }
@@ -173,7 +201,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
173
201
  private async executeFunction(
174
202
  ...args: Parameters<TFn>
175
203
  ): Promise<ReturnType<TFn> | undefined> {
176
- if (!this._options.enabled) return
204
+ if (!this.getEnabled()) return
177
205
  this._isExecuting = true
178
206
  const now = Date.now()
179
207
  this._executionTimes.push(now)
@@ -203,7 +231,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
203
231
 
204
232
  private cleanupOldExecutions(): void {
205
233
  const now = Date.now()
206
- const windowStart = now - this._options.window
234
+ const windowStart = now - this.getWindow()
207
235
  this._executionTimes = this._executionTimes.filter(
208
236
  (time) => time > windowStart,
209
237
  )
@@ -214,7 +242,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
214
242
  */
215
243
  getRemainingInWindow(): number {
216
244
  this.cleanupOldExecutions()
217
- return Math.max(0, this._options.limit - this._executionTimes.length)
245
+ return Math.max(0, this.getLimit() - this._executionTimes.length)
218
246
  }
219
247
 
220
248
  /**
@@ -227,7 +255,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
227
255
  return 0
228
256
  }
229
257
  const oldestExecution = Math.min(...this._executionTimes)
230
- return oldestExecution + this._options.window - Date.now()
258
+ return oldestExecution + this.getWindow() - Date.now()
231
259
  }
232
260
 
233
261
  /**
@@ -321,7 +349,7 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
321
349
  */
322
350
  export function asyncRateLimit<TFn extends AnyAsyncFunction>(
323
351
  fn: TFn,
324
- initialOptions: Omit<AsyncRateLimiterOptions<TFn>, 'enabled'>,
352
+ initialOptions: AsyncRateLimiterOptions<TFn>,
325
353
  ) {
326
354
  const rateLimiter = new AsyncRateLimiter(fn, initialOptions)
327
355
  return rateLimiter.maybeExecute.bind(rateLimiter)
@@ -1,3 +1,4 @@
1
+ import { parseFunctionOrValue } from './utils'
1
2
  import type { AnyAsyncFunction } from './types'
2
3
 
3
4
  /**
@@ -6,9 +7,10 @@ import type { AnyAsyncFunction } from './types'
6
7
  export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
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: AsyncThrottler<TFn>) => boolean)
12
14
  /**
13
15
  * Whether to execute the function immediately when called
14
16
  * Defaults to true
@@ -35,10 +37,11 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
35
37
  */
36
38
  trailing?: boolean
37
39
  /**
38
- * Time window in milliseconds during which the function can only be executed once
40
+ * Time window in milliseconds during which the function can only be executed once.
41
+ * Can be a number or a function that returns a number.
39
42
  * Defaults to 0ms
40
43
  */
41
- wait: number
44
+ wait: number | ((throttler: AsyncThrottler<TFn>) => number)
42
45
  }
43
46
 
44
47
  const defaultOptions: Required<AsyncThrottlerOptions<any>> = {
@@ -120,6 +123,20 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
120
123
  return this._options
121
124
  }
122
125
 
126
+ /**
127
+ * Returns the current enabled state of the throttler
128
+ */
129
+ getEnabled(): boolean {
130
+ return parseFunctionOrValue(this._options.enabled, this)
131
+ }
132
+
133
+ /**
134
+ * Returns the current wait time in milliseconds
135
+ */
136
+ getWait(): number {
137
+ return parseFunctionOrValue(this._options.wait, this)
138
+ }
139
+
123
140
  /**
124
141
  * Attempts to execute the throttled function
125
142
  * If a call is already in progress, it may be blocked or queued depending on the `wait` option
@@ -129,9 +146,10 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
129
146
  ): Promise<ReturnType<TFn> | undefined> {
130
147
  const now = Date.now()
131
148
  const timeSinceLastExecution = now - this._lastExecutionTime
149
+ const wait = this.getWait()
132
150
 
133
151
  // Handle leading execution
134
- if (this._options.leading && timeSinceLastExecution >= this._options.wait) {
152
+ if (this._options.leading && timeSinceLastExecution >= wait) {
135
153
  await this.executeFunction(...args)
136
154
  return this._lastResult
137
155
  } else {
@@ -149,7 +167,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
149
167
  const _timeSinceLastExecution = this._lastExecutionTime
150
168
  ? now - this._lastExecutionTime
151
169
  : 0
152
- const timeoutDuration = this._options.wait - _timeSinceLastExecution
170
+ const timeoutDuration = wait - _timeSinceLastExecution
153
171
  this._timeoutId = setTimeout(async () => {
154
172
  if (this._lastArgs !== undefined) {
155
173
  await this.executeFunction(...this._lastArgs)
@@ -164,7 +182,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
164
182
  private async executeFunction(
165
183
  ...args: Parameters<TFn>
166
184
  ): Promise<ReturnType<TFn> | undefined> {
167
- if (!this._options.enabled || this._isExecuting) return undefined
185
+ if (!this.getEnabled() || this._isExecuting) return undefined
168
186
  this._abortController = new AbortController()
169
187
  try {
170
188
  this._isExecuting = true
@@ -179,7 +197,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
179
197
  this._settleCount++
180
198
  this._abortController = null
181
199
  this._lastExecutionTime = Date.now()
182
- this._nextExecutionTime = this._lastExecutionTime + this._options.wait
200
+ this._nextExecutionTime = this._lastExecutionTime + this.getWait()
183
201
  this._options.onSettled(this)
184
202
  }
185
203
  return this._lastResult
@@ -246,7 +264,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
246
264
  * Returns the current pending state
247
265
  */
248
266
  getIsPending(): boolean {
249
- return this._options.enabled && !!this._timeoutId
267
+ return this.getEnabled() && !!this._timeoutId
250
268
  }
251
269
 
252
270
  /**
@@ -280,7 +298,7 @@ export class AsyncThrottler<TFn extends AnyAsyncFunction> {
280
298
  */
281
299
  export function asyncThrottle<TFn extends AnyAsyncFunction>(
282
300
  fn: TFn,
283
- initialOptions: Omit<AsyncThrottlerOptions<TFn>, 'enabled'>,
301
+ initialOptions: AsyncThrottlerOptions<TFn>,
284
302
  ) {
285
303
  const asyncThrottler = new AsyncThrottler(fn, initialOptions)
286
304
  return asyncThrottler.maybeExecute.bind(asyncThrottler)
package/src/debouncer.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 DebouncerOptions<TFn extends AnyFunction> {
7
8
  /**
8
9
  * Whether the debouncer 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 | ((debouncer: Debouncer<TFn>) => boolean)
12
14
  /**
13
15
  * Whether to execute on the leading edge of the timeout.
14
16
  * The first call will execute immediately and the rest will wait the delay.
@@ -25,10 +27,11 @@ export interface DebouncerOptions<TFn extends AnyFunction> {
25
27
  */
26
28
  trailing?: boolean
27
29
  /**
28
- * Delay in milliseconds before executing the function
30
+ * Delay in milliseconds before executing the function.
31
+ * Can be a number or a function that returns a number.
29
32
  * Defaults to 0ms
30
33
  */
31
- wait: number
34
+ wait: number | ((debouncer: Debouncer<TFn>) => number)
32
35
  }
33
36
 
34
37
  const defaultOptions: Required<DebouncerOptions<any>> = {
@@ -99,6 +102,20 @@ export class Debouncer<TFn extends AnyFunction> {
99
102
  return this._options
100
103
  }
101
104
 
105
+ /**
106
+ * Returns the current enabled state of the debouncer
107
+ */
108
+ getEnabled(): boolean {
109
+ return parseFunctionOrValue(this._options.enabled, this)
110
+ }
111
+
112
+ /**
113
+ * Returns the current wait time in milliseconds
114
+ */
115
+ getWait(): number {
116
+ return parseFunctionOrValue(this._options.wait, this)
117
+ }
118
+
102
119
  /**
103
120
  * Attempts to execute the debounced function
104
121
  * If a call is already in progress, it will be queued
@@ -127,11 +144,11 @@ export class Debouncer<TFn extends AnyFunction> {
127
144
  if (this._options.trailing && !_didLeadingExecute) {
128
145
  this.executeFunction(...args)
129
146
  }
130
- }, this._options.wait)
147
+ }, this.getWait())
131
148
  }
132
149
 
133
150
  private executeFunction(...args: Parameters<TFn>): void {
134
- if (!this._options.enabled) return undefined
151
+ if (!this.getEnabled()) return undefined
135
152
  this.fn(...args) // EXECUTE!
136
153
  this._isPending = false
137
154
  this._executionCount++
@@ -160,7 +177,7 @@ export class Debouncer<TFn extends AnyFunction> {
160
177
  * Returns `true` if debouncing
161
178
  */
162
179
  getIsPending(): boolean {
163
- return this._options.enabled && this._isPending
180
+ return this.getEnabled() && this._isPending
164
181
  }
165
182
  }
166
183
 
@@ -186,7 +203,7 @@ export class Debouncer<TFn extends AnyFunction> {
186
203
  */
187
204
  export function debounce<TFn extends AnyFunction>(
188
205
  fn: TFn,
189
- initialOptions: Omit<DebouncerOptions<TFn>, 'enabled'>,
206
+ initialOptions: DebouncerOptions<TFn>,
190
207
  ): (...args: Parameters<TFn>) => void {
191
208
  const debouncer = new Debouncer(fn, initialOptions)
192
209
  return debouncer.maybeExecute.bind(debouncer)
package/src/queuer.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { parseFunctionOrValue } from './utils'
2
+
1
3
  /**
2
4
  * Options for configuring a Queuer instance
3
5
  */
@@ -60,9 +62,11 @@ export interface QueuerOptions<TValue> {
60
62
  */
61
63
  started?: boolean
62
64
  /**
63
- * Time in milliseconds to wait between processing items
65
+ * Time in milliseconds to wait between processing items.
66
+ * Can be a number or a function that returns a number.
67
+ * @default 0
64
68
  */
65
- wait?: number
69
+ wait?: number | ((queuer: Queuer<TValue>) => number)
66
70
  }
67
71
 
68
72
  const defaultOptions: Required<QueuerOptions<any>> = {
@@ -78,7 +82,7 @@ const defaultOptions: Required<QueuerOptions<any>> = {
78
82
  onItemsChange: () => {},
79
83
  onReject: () => {},
80
84
  onExpire: () => {},
81
- started: false,
85
+ started: true,
82
86
  wait: 0,
83
87
  }
84
88
 
@@ -178,6 +182,13 @@ export class Queuer<TValue> {
178
182
  return this._options
179
183
  }
180
184
 
185
+ /**
186
+ * Returns the current wait time in milliseconds
187
+ */
188
+ getWait(): number {
189
+ return parseFunctionOrValue(this._options.wait, this)
190
+ }
191
+
181
192
  /**
182
193
  * Processes items in the queuer
183
194
  */
@@ -197,9 +208,10 @@ export class Queuer<TValue> {
197
208
  }
198
209
  this._onItemsChanges.forEach((cb) => cb(nextItem))
199
210
 
200
- if (this._options.wait > 0) {
211
+ const wait = this.getWait()
212
+ if (wait > 0) {
201
213
  // Use setTimeout to wait before processing next item
202
- setTimeout(() => this.tick(), this._options.wait)
214
+ setTimeout(() => this.tick(), wait)
203
215
  return
204
216
  }
205
217
 
@@ -493,7 +505,7 @@ export class Queuer<TValue> {
493
505
  * processPriority(3) // Processed before 1
494
506
  * ```
495
507
  */
496
- export function queue<TValue>(options: QueuerOptions<TValue> = {}) {
497
- const queuer = new Queuer<TValue>({ ...options, started: true })
508
+ export function queue<TValue>(options: QueuerOptions<TValue>) {
509
+ const queuer = new Queuer<TValue>(options)
498
510
  return queuer.addItem.bind(queuer)
499
511
  }