@tanstack/pacer 0.8.0 → 0.9.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-batcher.cjs +163 -0
- package/dist/cjs/async-batcher.cjs.map +1 -0
- package/dist/cjs/async-batcher.d.cts +273 -0
- package/dist/cjs/async-debouncer.cjs +149 -162
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +76 -57
- package/dist/cjs/async-queuer.cjs +282 -343
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +121 -100
- package/dist/cjs/async-rate-limiter.cjs +128 -185
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +72 -61
- package/dist/cjs/async-throttler.cjs +168 -178
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +97 -69
- package/dist/cjs/batcher.cjs +110 -119
- package/dist/cjs/batcher.cjs.map +1 -1
- package/dist/cjs/batcher.d.cts +76 -51
- package/dist/cjs/debouncer.cjs +97 -85
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +54 -26
- package/dist/cjs/index.cjs +3 -6
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/queuer.cjs +246 -294
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +102 -81
- package/dist/cjs/rate-limiter.cjs +97 -130
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +50 -37
- package/dist/cjs/throttler.cjs +107 -123
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +59 -35
- package/dist/cjs/utils.cjs +0 -13
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +0 -1
- package/dist/esm/async-batcher.d.ts +273 -0
- package/dist/esm/async-batcher.js +163 -0
- package/dist/esm/async-batcher.js.map +1 -0
- package/dist/esm/async-debouncer.d.ts +76 -57
- package/dist/esm/async-debouncer.js +149 -162
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +121 -100
- package/dist/esm/async-queuer.js +282 -343
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +72 -61
- package/dist/esm/async-rate-limiter.js +128 -185
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +97 -69
- package/dist/esm/async-throttler.js +168 -178
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/batcher.d.ts +76 -51
- package/dist/esm/batcher.js +110 -119
- package/dist/esm/batcher.js.map +1 -1
- package/dist/esm/debouncer.d.ts +54 -26
- package/dist/esm/debouncer.js +97 -85
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +4 -7
- package/dist/esm/queuer.d.ts +102 -81
- package/dist/esm/queuer.js +246 -294
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +50 -37
- package/dist/esm/rate-limiter.js +97 -130
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +59 -35
- package/dist/esm/throttler.js +107 -123
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/utils.d.ts +0 -1
- package/dist/esm/utils.js +0 -13
- package/dist/esm/utils.js.map +1 -1
- package/package.json +14 -11
- package/src/async-batcher.ts +475 -0
- package/src/async-debouncer.ts +201 -121
- package/src/async-queuer.ts +337 -216
- package/src/async-rate-limiter.ts +176 -136
- package/src/async-throttler.ts +233 -139
- package/src/batcher.ts +158 -92
- package/src/debouncer.ts +135 -52
- package/src/index.ts +1 -1
- package/src/queuer.ts +348 -226
- package/src/rate-limiter.ts +125 -80
- package/src/throttler.ts +152 -78
- package/src/utils.ts +0 -15
- package/dist/cjs/compare.cjs +0 -72
- package/dist/cjs/compare.cjs.map +0 -1
- package/dist/cjs/compare.d.cts +0 -12
- package/dist/esm/compare.d.ts +0 -12
- package/dist/esm/compare.js +0 -72
- package/dist/esm/compare.js.map +0 -1
- package/src/compare.ts +0 -105
|
@@ -1,5 +1,51 @@
|
|
|
1
|
+
import { Store } from '@tanstack/store'
|
|
1
2
|
import { parseFunctionOrValue } from './utils'
|
|
2
|
-
import type { AnyAsyncFunction
|
|
3
|
+
import type { AnyAsyncFunction } from './types'
|
|
4
|
+
|
|
5
|
+
export interface AsyncRateLimiterState<TFn extends AnyAsyncFunction> {
|
|
6
|
+
/**
|
|
7
|
+
* Number of function executions that have resulted in errors
|
|
8
|
+
*/
|
|
9
|
+
errorCount: number
|
|
10
|
+
/**
|
|
11
|
+
* Array of timestamps when executions occurred for rate limiting calculations
|
|
12
|
+
*/
|
|
13
|
+
executionTimes: Array<number>
|
|
14
|
+
/**
|
|
15
|
+
* Whether the rate-limited function is currently executing asynchronously
|
|
16
|
+
*/
|
|
17
|
+
isExecuting: boolean
|
|
18
|
+
/**
|
|
19
|
+
* The result from the most recent successful function execution
|
|
20
|
+
*/
|
|
21
|
+
lastResult: ReturnType<TFn> | undefined
|
|
22
|
+
/**
|
|
23
|
+
* Number of function executions that have been rejected due to rate limiting
|
|
24
|
+
*/
|
|
25
|
+
rejectionCount: number
|
|
26
|
+
/**
|
|
27
|
+
* Number of function executions that have completed (either successfully or with errors)
|
|
28
|
+
*/
|
|
29
|
+
settleCount: number
|
|
30
|
+
/**
|
|
31
|
+
* Number of function executions that have completed successfully
|
|
32
|
+
*/
|
|
33
|
+
successCount: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getDefaultAsyncRateLimiterState<
|
|
37
|
+
TFn extends AnyAsyncFunction,
|
|
38
|
+
>(): AsyncRateLimiterState<TFn> {
|
|
39
|
+
return {
|
|
40
|
+
errorCount: 0,
|
|
41
|
+
executionTimes: [],
|
|
42
|
+
isExecuting: false,
|
|
43
|
+
lastResult: undefined,
|
|
44
|
+
rejectionCount: 0,
|
|
45
|
+
settleCount: 0,
|
|
46
|
+
successCount: 0,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
3
49
|
|
|
4
50
|
/**
|
|
5
51
|
* Options for configuring an async rate-limited function
|
|
@@ -11,6 +57,10 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
|
|
|
11
57
|
* Defaults to true.
|
|
12
58
|
*/
|
|
13
59
|
enabled?: boolean | ((rateLimiter: AsyncRateLimiter<TFn>) => boolean)
|
|
60
|
+
/**
|
|
61
|
+
* Initial state for the rate limiter
|
|
62
|
+
*/
|
|
63
|
+
initialState?: Partial<AsyncRateLimiterState<TFn>>
|
|
14
64
|
/**
|
|
15
65
|
* Maximum number of executions allowed within the time window.
|
|
16
66
|
* Can be a number or a function that returns a number.
|
|
@@ -57,17 +107,15 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
|
|
|
57
107
|
windowType?: 'fixed' | 'sliding'
|
|
58
108
|
}
|
|
59
109
|
|
|
60
|
-
type AsyncRateLimiterOptionsWithOptionalCallbacks = OptionalKeys<
|
|
61
|
-
AsyncRateLimiterOptions<any>,
|
|
62
|
-
'onError' | 'onReject' | 'onSettled' | 'onSuccess'
|
|
63
|
-
>
|
|
64
|
-
|
|
65
110
|
const defaultOptions: Omit<
|
|
66
|
-
|
|
67
|
-
'
|
|
111
|
+
Required<AsyncRateLimiterOptions<any>>,
|
|
112
|
+
'initialState' | 'onError' | 'onReject' | 'onSettled' | 'onSuccess'
|
|
68
113
|
> = {
|
|
69
114
|
enabled: true,
|
|
115
|
+
limit: 1,
|
|
116
|
+
window: 0,
|
|
70
117
|
windowType: 'fixed',
|
|
118
|
+
throwOnError: true,
|
|
71
119
|
}
|
|
72
120
|
|
|
73
121
|
/**
|
|
@@ -94,6 +142,18 @@ const defaultOptions: Omit<
|
|
|
94
142
|
* Rate limiting is best used for hard API limits or resource constraints. For UI updates or
|
|
95
143
|
* smoothing out frequent events, throttling or debouncing usually provide better user experience.
|
|
96
144
|
*
|
|
145
|
+
* State Management:
|
|
146
|
+
* - Uses TanStack Store for reactive state management
|
|
147
|
+
* - Use `initialState` to provide initial state values when creating the rate limiter
|
|
148
|
+
* - `initialState` can be a partial state object
|
|
149
|
+
* - Use `onSuccess` callback to react to successful function execution and implement custom logic
|
|
150
|
+
* - Use `onError` callback to react to function execution errors and implement custom error handling
|
|
151
|
+
* - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
|
|
152
|
+
* - Use `onReject` callback to react to executions being rejected when rate limit is exceeded
|
|
153
|
+
* - The state includes execution times, success/error counts, and current execution status
|
|
154
|
+
* - State can be accessed via `asyncRateLimiter.store.state` when using the class directly
|
|
155
|
+
* - When using framework adapters (React/Solid), state is accessed from `asyncRateLimiter.state`
|
|
156
|
+
*
|
|
97
157
|
* Error Handling:
|
|
98
158
|
* - If an `onError` handler is provided, it will be called with the error and rate limiter instance
|
|
99
159
|
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
@@ -125,75 +185,71 @@ const defaultOptions: Omit<
|
|
|
125
185
|
* ```
|
|
126
186
|
*/
|
|
127
187
|
export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
private _rejectionCount = 0
|
|
133
|
-
private _settleCount = 0
|
|
134
|
-
private _successCount = 0
|
|
135
|
-
private _isExecuting = false
|
|
188
|
+
readonly store: Store<Readonly<AsyncRateLimiterState<TFn>>> = new Store<
|
|
189
|
+
AsyncRateLimiterState<TFn>
|
|
190
|
+
>(getDefaultAsyncRateLimiterState<TFn>())
|
|
191
|
+
options: AsyncRateLimiterOptions<TFn>
|
|
136
192
|
|
|
137
193
|
constructor(
|
|
138
194
|
private fn: TFn,
|
|
139
195
|
initialOptions: AsyncRateLimiterOptions<TFn>,
|
|
140
196
|
) {
|
|
141
|
-
this.
|
|
197
|
+
this.options = {
|
|
142
198
|
...defaultOptions,
|
|
143
199
|
...initialOptions,
|
|
144
200
|
throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,
|
|
145
201
|
}
|
|
202
|
+
this.#setState(this.options.initialState ?? {})
|
|
146
203
|
}
|
|
147
204
|
|
|
148
205
|
/**
|
|
149
|
-
* Updates the rate limiter options
|
|
206
|
+
* Updates the async rate limiter options
|
|
150
207
|
*/
|
|
151
|
-
setOptions(newOptions: Partial<AsyncRateLimiterOptions<TFn>>): void {
|
|
152
|
-
this.
|
|
208
|
+
setOptions = (newOptions: Partial<AsyncRateLimiterOptions<TFn>>): void => {
|
|
209
|
+
this.options = { ...this.options, ...newOptions }
|
|
153
210
|
}
|
|
154
211
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
#setState = (newState: Partial<AsyncRateLimiterState<TFn>>): void => {
|
|
213
|
+
this.store.setState((state) => {
|
|
214
|
+
const combinedState = {
|
|
215
|
+
...state,
|
|
216
|
+
...newState,
|
|
217
|
+
}
|
|
218
|
+
return combinedState
|
|
219
|
+
})
|
|
160
220
|
}
|
|
161
221
|
|
|
162
222
|
/**
|
|
163
|
-
* Returns the current enabled state of the rate limiter
|
|
223
|
+
* Returns the current enabled state of the async rate limiter
|
|
164
224
|
*/
|
|
165
|
-
getEnabled(): boolean {
|
|
166
|
-
return !!parseFunctionOrValue(this.
|
|
225
|
+
#getEnabled = (): boolean => {
|
|
226
|
+
return !!parseFunctionOrValue(this.options.enabled, this)
|
|
167
227
|
}
|
|
168
228
|
|
|
169
229
|
/**
|
|
170
230
|
* Returns the current limit of executions allowed within the time window
|
|
171
231
|
*/
|
|
172
|
-
getLimit(): number {
|
|
173
|
-
return parseFunctionOrValue(this.
|
|
232
|
+
#getLimit = (): number => {
|
|
233
|
+
return parseFunctionOrValue(this.options.limit, this)
|
|
174
234
|
}
|
|
175
235
|
|
|
176
236
|
/**
|
|
177
237
|
* Returns the current time window in milliseconds
|
|
178
238
|
*/
|
|
179
|
-
getWindow(): number {
|
|
180
|
-
return parseFunctionOrValue(this.
|
|
239
|
+
#getWindow = (): number => {
|
|
240
|
+
return parseFunctionOrValue(this.options.window, this)
|
|
181
241
|
}
|
|
182
242
|
|
|
183
243
|
/**
|
|
184
244
|
* Attempts to execute the rate-limited function if within the configured limits.
|
|
185
245
|
* Will reject execution if the number of calls in the current window exceeds the limit.
|
|
186
|
-
* If execution is allowed, waits for any previous execution to complete before proceeding.
|
|
187
246
|
*
|
|
188
247
|
* Error Handling:
|
|
189
248
|
* - If the rate-limited function throws and no `onError` handler is configured,
|
|
190
249
|
* the error will be thrown from this method.
|
|
191
250
|
* - If an `onError` handler is configured, errors will be caught and passed to the handler,
|
|
192
251
|
* and this method will return undefined.
|
|
193
|
-
* - If the rate limit is exceeded, the execution will be rejected and the `onReject` handler
|
|
194
|
-
* will be called if configured.
|
|
195
252
|
* - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
|
|
196
|
-
* - Rate limit rejections can be tracked using `getRejectionCount()`.
|
|
197
253
|
*
|
|
198
254
|
* @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
|
|
199
255
|
* @throws The error from the rate-limited function if no onError handler is configured
|
|
@@ -202,93 +258,104 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
202
258
|
* ```ts
|
|
203
259
|
* const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 });
|
|
204
260
|
*
|
|
205
|
-
* // First 5 calls will
|
|
206
|
-
* await rateLimiter.maybeExecute('arg1', 'arg2');
|
|
261
|
+
* // First 5 calls will return a promise that resolves with the result
|
|
262
|
+
* const result = await rateLimiter.maybeExecute('arg1', 'arg2');
|
|
207
263
|
*
|
|
208
|
-
* // Additional calls within the window will
|
|
209
|
-
* await rateLimiter.maybeExecute('arg1', 'arg2'); //
|
|
264
|
+
* // Additional calls within the window will return undefined
|
|
265
|
+
* const result2 = await rateLimiter.maybeExecute('arg1', 'arg2'); // undefined
|
|
210
266
|
* ```
|
|
211
267
|
*/
|
|
212
|
-
async
|
|
268
|
+
maybeExecute = async (
|
|
213
269
|
...args: Parameters<TFn>
|
|
214
|
-
): Promise<ReturnType<TFn> | undefined> {
|
|
215
|
-
this
|
|
270
|
+
): Promise<ReturnType<TFn> | undefined> => {
|
|
271
|
+
this.#cleanupOldExecutions()
|
|
216
272
|
|
|
217
|
-
const
|
|
218
|
-
const window = this.getWindow()
|
|
273
|
+
const relevantExecutionTimes = this.#getRelevantExecutionTimes()
|
|
219
274
|
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
await this.execute(...args)
|
|
224
|
-
return this._lastResult
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
// For fixed window, we need to check if we're in a new window
|
|
228
|
-
const now = Date.now()
|
|
229
|
-
const oldestExecution = Math.min(...this._executionTimes)
|
|
230
|
-
const isNewWindow = oldestExecution + window <= now
|
|
231
|
-
|
|
232
|
-
if (isNewWindow || this._executionTimes.length < limit) {
|
|
233
|
-
await this.execute(...args)
|
|
234
|
-
return this._lastResult
|
|
235
|
-
}
|
|
275
|
+
if (relevantExecutionTimes.length < this.#getLimit()) {
|
|
276
|
+
await this.#execute(...args)
|
|
277
|
+
return this.store.state.lastResult
|
|
236
278
|
}
|
|
237
279
|
|
|
238
|
-
this
|
|
280
|
+
this.#setState({
|
|
281
|
+
rejectionCount: this.store.state.rejectionCount + 1,
|
|
282
|
+
})
|
|
283
|
+
this.options.onReject?.(this)
|
|
239
284
|
return undefined
|
|
240
285
|
}
|
|
241
286
|
|
|
242
|
-
|
|
287
|
+
#execute = async (
|
|
243
288
|
...args: Parameters<TFn>
|
|
244
|
-
): Promise<ReturnType<TFn> | undefined> {
|
|
245
|
-
if (!this
|
|
246
|
-
|
|
289
|
+
): Promise<ReturnType<TFn> | undefined> => {
|
|
290
|
+
if (!this.#getEnabled()) return
|
|
291
|
+
|
|
247
292
|
const now = Date.now()
|
|
248
|
-
this.
|
|
293
|
+
const executionTimes = [...this.store.state.executionTimes, now]
|
|
294
|
+
this.#setState({
|
|
295
|
+
isExecuting: true,
|
|
296
|
+
executionTimes,
|
|
297
|
+
})
|
|
249
298
|
|
|
250
299
|
try {
|
|
251
|
-
|
|
252
|
-
this
|
|
253
|
-
|
|
300
|
+
const result = await this.fn(...args)
|
|
301
|
+
this.#setState({
|
|
302
|
+
successCount: this.store.state.successCount + 1,
|
|
303
|
+
lastResult: result,
|
|
304
|
+
})
|
|
305
|
+
this.options.onSuccess?.(result, this)
|
|
254
306
|
} catch (error) {
|
|
255
|
-
this
|
|
256
|
-
|
|
257
|
-
|
|
307
|
+
this.#setState({
|
|
308
|
+
errorCount: this.store.state.errorCount + 1,
|
|
309
|
+
})
|
|
310
|
+
this.options.onError?.(error, this)
|
|
311
|
+
if (this.options.throwOnError) {
|
|
258
312
|
throw error
|
|
259
|
-
} else {
|
|
260
|
-
console.error(error)
|
|
261
313
|
}
|
|
262
314
|
} finally {
|
|
263
|
-
this
|
|
264
|
-
|
|
265
|
-
|
|
315
|
+
this.#setState({
|
|
316
|
+
isExecuting: false,
|
|
317
|
+
settleCount: this.store.state.settleCount + 1,
|
|
318
|
+
})
|
|
319
|
+
this.options.onSettled?.(this)
|
|
266
320
|
}
|
|
267
321
|
|
|
268
|
-
return this.
|
|
322
|
+
return this.store.state.lastResult
|
|
269
323
|
}
|
|
270
324
|
|
|
271
|
-
|
|
272
|
-
this.
|
|
273
|
-
|
|
274
|
-
this.
|
|
325
|
+
#getRelevantExecutionTimes = (): Array<number> => {
|
|
326
|
+
if (this.options.windowType === 'sliding') {
|
|
327
|
+
// For sliding window, return all executions within the current window
|
|
328
|
+
return this.store.state.executionTimes.filter(
|
|
329
|
+
(time) => time > Date.now() - this.#getWindow(),
|
|
330
|
+
)
|
|
331
|
+
} else {
|
|
332
|
+
// For fixed window, return all executions in the current window
|
|
333
|
+
// The window starts from the oldest execution time
|
|
334
|
+
const oldestExecution = Math.min(...this.store.state.executionTimes)
|
|
335
|
+
const windowStart = oldestExecution
|
|
336
|
+
return this.store.state.executionTimes.filter(
|
|
337
|
+
(time) =>
|
|
338
|
+
time >= windowStart && time <= windowStart + this.#getWindow(),
|
|
339
|
+
)
|
|
275
340
|
}
|
|
276
341
|
}
|
|
277
342
|
|
|
278
|
-
|
|
343
|
+
#cleanupOldExecutions = (): void => {
|
|
279
344
|
const now = Date.now()
|
|
280
|
-
const windowStart = now - this
|
|
281
|
-
this
|
|
282
|
-
(
|
|
283
|
-
|
|
345
|
+
const windowStart = now - this.#getWindow()
|
|
346
|
+
this.#setState({
|
|
347
|
+
executionTimes: this.store.state.executionTimes.filter(
|
|
348
|
+
(time) => time > windowStart,
|
|
349
|
+
),
|
|
350
|
+
})
|
|
284
351
|
}
|
|
285
352
|
|
|
286
353
|
/**
|
|
287
354
|
* Returns the number of remaining executions allowed in the current window
|
|
288
355
|
*/
|
|
289
|
-
getRemainingInWindow(): number {
|
|
290
|
-
this
|
|
291
|
-
return Math.max(0, this
|
|
356
|
+
getRemainingInWindow = (): number => {
|
|
357
|
+
const relevantExecutionTimes = this.#getRelevantExecutionTimes()
|
|
358
|
+
return Math.max(0, this.#getLimit() - relevantExecutionTimes.length)
|
|
292
359
|
}
|
|
293
360
|
|
|
294
361
|
/**
|
|
@@ -296,58 +363,19 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
296
363
|
* For fixed windows, this is the time until the current window resets
|
|
297
364
|
* For sliding windows, this is the time until the oldest execution expires
|
|
298
365
|
*/
|
|
299
|
-
getMsUntilNextWindow(): number {
|
|
366
|
+
getMsUntilNextWindow = (): number => {
|
|
300
367
|
if (this.getRemainingInWindow() > 0) {
|
|
301
368
|
return 0
|
|
302
369
|
}
|
|
303
|
-
const oldestExecution =
|
|
304
|
-
return oldestExecution + this
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Returns the number of times the function has been executed
|
|
309
|
-
*/
|
|
310
|
-
getSuccessCount(): number {
|
|
311
|
-
return this._successCount
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Returns the number of times the function has been settled
|
|
316
|
-
*/
|
|
317
|
-
getSettleCount(): number {
|
|
318
|
-
return this._settleCount
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Returns the number of times the function has errored
|
|
323
|
-
*/
|
|
324
|
-
getErrorCount(): number {
|
|
325
|
-
return this._errorCount
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Returns the number of times the function has been rejected
|
|
330
|
-
*/
|
|
331
|
-
getRejectionCount(): number {
|
|
332
|
-
return this._rejectionCount
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Returns whether the function is currently executing
|
|
337
|
-
*/
|
|
338
|
-
getIsExecuting(): boolean {
|
|
339
|
-
return this._isExecuting
|
|
370
|
+
const oldestExecution = this.store.state.executionTimes[0] ?? Infinity
|
|
371
|
+
return oldestExecution + this.#getWindow() - Date.now()
|
|
340
372
|
}
|
|
341
373
|
|
|
342
374
|
/**
|
|
343
375
|
* Resets the rate limiter state
|
|
344
376
|
*/
|
|
345
|
-
reset(): void {
|
|
346
|
-
this
|
|
347
|
-
this._successCount = 0
|
|
348
|
-
this._errorCount = 0
|
|
349
|
-
this._rejectionCount = 0
|
|
350
|
-
this._settleCount = 0
|
|
377
|
+
reset = (): void => {
|
|
378
|
+
this.#setState(getDefaultAsyncRateLimiterState())
|
|
351
379
|
}
|
|
352
380
|
}
|
|
353
381
|
|
|
@@ -369,6 +397,18 @@ export class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
369
397
|
* - A throttler ensures even spacing between executions, which can be better for consistent performance
|
|
370
398
|
* - A debouncer collapses multiple calls into one, which is better for handling bursts of events
|
|
371
399
|
*
|
|
400
|
+
* State Management:
|
|
401
|
+
* - Uses TanStack Store for reactive state management
|
|
402
|
+
* - Use `initialState` to provide initial state values when creating the rate limiter
|
|
403
|
+
* - `initialState` can be a partial state object
|
|
404
|
+
* - Use `onSuccess` callback to react to successful function execution and implement custom logic
|
|
405
|
+
* - Use `onError` callback to react to function execution errors and implement custom error handling
|
|
406
|
+
* - Use `onSettled` callback to react to function execution completion (success or error) and implement custom logic
|
|
407
|
+
* - Use `onReject` callback to react to executions being rejected when rate limit is exceeded
|
|
408
|
+
* - The state includes execution times, success/error counts, and current execution status
|
|
409
|
+
* - State can be accessed via the underlying AsyncRateLimiter instance's `store.state` property
|
|
410
|
+
* - When using framework adapters (React/Solid), state is accessed from the hook's state property
|
|
411
|
+
*
|
|
372
412
|
* Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically
|
|
373
413
|
* need to enforce a hard limit on the number of executions within a time period.
|
|
374
414
|
*
|
|
@@ -409,5 +449,5 @@ export function asyncRateLimit<TFn extends AnyAsyncFunction>(
|
|
|
409
449
|
initialOptions: AsyncRateLimiterOptions<TFn>,
|
|
410
450
|
) {
|
|
411
451
|
const rateLimiter = new AsyncRateLimiter(fn, initialOptions)
|
|
412
|
-
return rateLimiter.maybeExecute
|
|
452
|
+
return rateLimiter.maybeExecute
|
|
413
453
|
}
|