@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.
- package/dist/cjs/async-debouncer.cjs +16 -3
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +14 -4
- package/dist/cjs/async-queuer.cjs +20 -6
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +21 -8
- package/dist/cjs/async-rate-limiter.cjs +28 -7
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +21 -6
- package/dist/cjs/async-throttler.cjs +19 -5
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +14 -4
- package/dist/cjs/debouncer.cjs +16 -3
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +14 -4
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/queuer.cjs +13 -5
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +9 -3
- package/dist/cjs/rate-limiter.cjs +26 -7
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +20 -6
- package/dist/cjs/throttler.cjs +19 -5
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +15 -4
- package/dist/cjs/utils.cjs +18 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +3 -0
- package/dist/esm/async-debouncer.d.ts +14 -4
- package/dist/esm/async-debouncer.js +16 -3
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +21 -8
- package/dist/esm/async-queuer.js +20 -6
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +21 -6
- package/dist/esm/async-rate-limiter.js +28 -7
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +14 -4
- package/dist/esm/async-throttler.js +19 -5
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/debouncer.d.ts +14 -4
- package/dist/esm/debouncer.js +16 -3
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/queuer.d.ts +9 -3
- package/dist/esm/queuer.js +13 -5
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +20 -6
- package/dist/esm/rate-limiter.js +26 -7
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +15 -4
- package/dist/esm/throttler.js +19 -5
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +19 -8
- package/dist/esm/utils.js.map +1 -1
- package/package.json +9 -3
- package/src/async-debouncer.ts +24 -7
- package/src/async-queuer.ts +32 -13
- package/src/async-rate-limiter.ts +41 -13
- package/src/async-throttler.ts +27 -9
- package/src/debouncer.ts +24 -7
- package/src/queuer.ts +19 -7
- package/src/rate-limiter.ts +37 -13
- package/src/throttler.ts +28 -9
- package/src/utils.ts +19 -5
package/src/rate-limiter.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
160
|
+
const isNewWindow = oldestExecution + this.getWindow() <= now
|
|
137
161
|
|
|
138
|
-
if (isNewWindow || this._executionTimes.length < this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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 >=
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
20
|
+
if (isFunction(method)) {
|
|
9
21
|
acc[key] = method.bind(instance)
|
|
10
22
|
}
|
|
11
23
|
return acc
|
|
12
|
-
},
|
|
24
|
+
},
|
|
25
|
+
instance,
|
|
26
|
+
)
|
|
13
27
|
}
|