@tanstack/pacer 0.3.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 +30 -12
  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 +55 -8
  8. package/dist/cjs/async-rate-limiter.cjs.map +1 -1
  9. package/dist/cjs/async-rate-limiter.d.cts +64 -12
  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 +31 -12
  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 +41 -8
  22. package/dist/cjs/rate-limiter.cjs.map +1 -1
  23. package/dist/cjs/rate-limiter.d.cts +42 -8
  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 +30 -12
  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 +64 -12
  37. package/dist/esm/async-rate-limiter.js +55 -8
  38. package/dist/esm/async-rate-limiter.js.map +1 -1
  39. package/dist/esm/async-throttler.d.ts +31 -12
  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 +42 -8
  50. package/dist/esm/rate-limiter.js +41 -8
  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 +40 -15
  60. package/src/async-queuer.ts +32 -13
  61. package/src/async-rate-limiter.ts +107 -19
  62. package/src/async-throttler.ts +44 -17
  63. package/src/debouncer.ts +24 -7
  64. package/src/queuer.ts +19 -7
  65. package/src/rate-limiter.ts +76 -16
  66. package/src/throttler.ts +28 -9
  67. package/src/utils.ts +19 -5
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
  }
@@ -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,17 @@ 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)
31
+ /**
32
+ * Type of window to use for rate limiting
33
+ * - 'fixed': Uses a fixed window that resets after the window period
34
+ * - 'sliding': Uses a sliding window that allows executions as old ones expire
35
+ * Defaults to 'fixed'
36
+ */
37
+ windowType?: 'fixed' | 'sliding'
28
38
  }
29
39
 
30
40
  const defaultOptions: Required<RateLimiterOptions<any>> = {
@@ -33,6 +43,7 @@ const defaultOptions: Required<RateLimiterOptions<any>> = {
33
43
  onExecute: () => {},
34
44
  onReject: () => {},
35
45
  window: 0,
46
+ windowType: 'fixed',
36
47
  }
37
48
 
38
49
  /**
@@ -42,6 +53,12 @@ const defaultOptions: Required<RateLimiterOptions<any>> = {
42
53
  * then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where
43
54
  * all executions happen immediately, followed by a complete block.
44
55
  *
56
+ * The rate limiter supports two types of windows:
57
+ * - 'fixed': A strict window that resets after the window period. All executions within the window count
58
+ * towards the limit, and the window resets completely after the period.
59
+ * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
60
+ * consistent rate of execution over time.
61
+ *
45
62
  * For smoother execution patterns, consider using:
46
63
  * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms)
47
64
  * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls)
@@ -53,7 +70,7 @@ const defaultOptions: Required<RateLimiterOptions<any>> = {
53
70
  * ```ts
54
71
  * const rateLimiter = new RateLimiter(
55
72
  * (id: string) => api.getData(id),
56
- * { limit: 5, window: 1000 } // 5 calls per second
73
+ * { limit: 5, window: 1000, windowType: 'sliding' } // 5 calls per second with sliding window
57
74
  * );
58
75
  *
59
76
  * // Will execute immediately until limit reached, then block
@@ -91,6 +108,27 @@ export class RateLimiter<TFn extends AnyFunction> {
91
108
  return this._options as Required<RateLimiterOptions<TFn>>
92
109
  }
93
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
+
94
132
  /**
95
133
  * Attempts to execute the rate-limited function if within the configured limits.
96
134
  * Will reject execution if the number of calls in the current window exceeds the limit.
@@ -109,18 +147,30 @@ export class RateLimiter<TFn extends AnyFunction> {
109
147
  maybeExecute(...args: Parameters<TFn>): boolean {
110
148
  this.cleanupOldExecutions()
111
149
 
112
- if (this._executionTimes.length < this._options.limit) {
113
- this.executeFunction(...args)
114
- return true
150
+ if (this._options.windowType === 'sliding') {
151
+ // For sliding window, we can execute if we have capacity in the current window
152
+ if (this._executionTimes.length < this.getLimit()) {
153
+ this.executeFunction(...args)
154
+ return true
155
+ }
156
+ } else {
157
+ // For fixed window, we need to check if we're in a new window
158
+ const now = Date.now()
159
+ const oldestExecution = Math.min(...this._executionTimes)
160
+ const isNewWindow = oldestExecution + this.getWindow() <= now
161
+
162
+ if (isNewWindow || this._executionTimes.length < this.getLimit()) {
163
+ this.executeFunction(...args)
164
+ return true
165
+ }
115
166
  }
116
167
 
117
168
  this.rejectFunction()
118
-
119
169
  return false
120
170
  }
121
171
 
122
172
  private executeFunction(...args: Parameters<TFn>): void {
123
- if (!this._options.enabled) return
173
+ if (!this.getEnabled()) return
124
174
  const now = Date.now()
125
175
  this._executionCount++
126
176
  this._executionTimes.push(now)
@@ -137,7 +187,7 @@ export class RateLimiter<TFn extends AnyFunction> {
137
187
 
138
188
  private cleanupOldExecutions(): void {
139
189
  const now = Date.now()
140
- const windowStart = now - this._options.window
190
+ const windowStart = now - this.getWindow()
141
191
  this._executionTimes = this._executionTimes.filter(
142
192
  (time) => time > windowStart,
143
193
  )
@@ -162,15 +212,18 @@ export class RateLimiter<TFn extends AnyFunction> {
162
212
  */
163
213
  getRemainingInWindow(): number {
164
214
  this.cleanupOldExecutions()
165
- return Math.max(0, this._options.limit - this._executionTimes.length)
215
+ return Math.max(0, this.getLimit() - this._executionTimes.length)
166
216
  }
167
217
 
168
218
  /**
169
219
  * Returns the number of milliseconds until the next execution will be possible
170
220
  */
171
221
  getMsUntilNextWindow(): number {
222
+ if (this.getRemainingInWindow() > 0) {
223
+ return 0
224
+ }
172
225
  const oldestExecution = Math.min(...this._executionTimes)
173
- return oldestExecution + this._options.window - Date.now()
226
+ return oldestExecution + this.getWindow() - Date.now()
174
227
  }
175
228
 
176
229
  /**
@@ -191,15 +244,22 @@ export class RateLimiter<TFn extends AnyFunction> {
191
244
  * - A throttler ensures even spacing between executions, which can be better for consistent performance
192
245
  * - A debouncer collapses multiple calls into one, which is better for handling bursts of events
193
246
  *
247
+ * The rate limiter supports two types of windows:
248
+ * - 'fixed': A strict window that resets after the window period. All executions within the window count
249
+ * towards the limit, and the window resets completely after the period.
250
+ * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
251
+ * consistent rate of execution over time.
252
+ *
194
253
  * Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically
195
254
  * need to enforce a hard limit on the number of executions within a time period.
196
255
  *
197
256
  * @example
198
257
  * ```ts
199
- * // Rate limit to 5 calls per minute
258
+ * // Rate limit to 5 calls per minute with a sliding window
200
259
  * const rateLimited = rateLimit(makeApiCall, {
201
260
  * limit: 5,
202
261
  * window: 60000,
262
+ * windowType: 'sliding',
203
263
  * onReject: (rateLimiter) => {
204
264
  * console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
205
265
  * }
@@ -215,7 +275,7 @@ export class RateLimiter<TFn extends AnyFunction> {
215
275
  */
216
276
  export function rateLimit<TFn extends AnyFunction>(
217
277
  fn: TFn,
218
- initialOptions: Omit<RateLimiterOptions<TFn>, 'enabled'>,
278
+ initialOptions: RateLimiterOptions<TFn>,
219
279
  ) {
220
280
  const rateLimiter = new RateLimiter(fn, initialOptions)
221
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
  }