@tanstack/solid-pacer 0.2.0 → 0.4.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/createAsyncDebouncer.cjs +22 -7
- package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer/createAsyncDebouncer.d.cts +6 -3
- package/dist/cjs/async-queuer/createAsyncQueuer.cjs.map +1 -1
- package/dist/cjs/async-queuer/createAsyncQueuer.d.cts +5 -1
- package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs +20 -9
- package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.d.cts +19 -6
- package/dist/cjs/async-throttler/createAsyncThrottler.cjs +32 -10
- package/dist/cjs/async-throttler/createAsyncThrottler.cjs.map +1 -1
- package/dist/cjs/async-throttler/createAsyncThrottler.d.cts +7 -3
- package/dist/cjs/debouncer/createDebouncedSignal.cjs.map +1 -1
- package/dist/cjs/debouncer/createDebouncedSignal.d.cts +1 -5
- package/dist/cjs/debouncer/createDebouncedValue.cjs +0 -3
- package/dist/cjs/debouncer/createDebouncedValue.cjs.map +1 -1
- package/dist/cjs/debouncer/createDebouncedValue.d.cts +5 -11
- package/dist/cjs/debouncer/createDebouncer.cjs +7 -2
- package/dist/cjs/debouncer/createDebouncer.cjs.map +1 -1
- package/dist/cjs/debouncer/createDebouncer.d.cts +2 -2
- package/dist/cjs/queuer/createQueuer.cjs +2 -2
- package/dist/cjs/queuer/createQueuer.cjs.map +1 -1
- package/dist/cjs/queuer/createQueuer.d.cts +4 -0
- package/dist/cjs/rate-limiter/createRateLimitedSignal.cjs.map +1 -1
- package/dist/cjs/rate-limiter/createRateLimitedSignal.d.cts +12 -8
- package/dist/cjs/rate-limiter/createRateLimitedValue.cjs.map +1 -1
- package/dist/cjs/rate-limiter/createRateLimitedValue.d.cts +18 -22
- package/dist/cjs/rate-limiter/createRateLimiter.cjs +4 -2
- package/dist/cjs/rate-limiter/createRateLimiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter/createRateLimiter.d.cts +10 -3
- package/dist/cjs/throttler/createThrottledSignal.cjs.map +1 -1
- package/dist/cjs/throttler/createThrottledSignal.d.cts +1 -5
- package/dist/cjs/throttler/createThrottledValue.cjs +0 -3
- package/dist/cjs/throttler/createThrottledValue.cjs.map +1 -1
- package/dist/cjs/throttler/createThrottledValue.d.cts +10 -14
- package/dist/cjs/throttler/createThrottler.cjs +7 -2
- package/dist/cjs/throttler/createThrottler.cjs.map +1 -1
- package/dist/cjs/throttler/createThrottler.d.cts +2 -2
- package/dist/esm/async-debouncer/createAsyncDebouncer.d.ts +6 -3
- package/dist/esm/async-debouncer/createAsyncDebouncer.js +22 -7
- package/dist/esm/async-debouncer/createAsyncDebouncer.js.map +1 -1
- package/dist/esm/async-queuer/createAsyncQueuer.d.ts +5 -1
- package/dist/esm/async-queuer/createAsyncQueuer.js.map +1 -1
- package/dist/esm/async-rate-limiter/createAsyncRateLimiter.d.ts +19 -6
- package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js +20 -9
- package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js.map +1 -1
- package/dist/esm/async-throttler/createAsyncThrottler.d.ts +7 -3
- package/dist/esm/async-throttler/createAsyncThrottler.js +32 -10
- package/dist/esm/async-throttler/createAsyncThrottler.js.map +1 -1
- package/dist/esm/debouncer/createDebouncedSignal.d.ts +1 -5
- package/dist/esm/debouncer/createDebouncedSignal.js.map +1 -1
- package/dist/esm/debouncer/createDebouncedValue.d.ts +5 -11
- package/dist/esm/debouncer/createDebouncedValue.js +1 -4
- package/dist/esm/debouncer/createDebouncedValue.js.map +1 -1
- package/dist/esm/debouncer/createDebouncer.d.ts +2 -2
- package/dist/esm/debouncer/createDebouncer.js +8 -3
- package/dist/esm/debouncer/createDebouncer.js.map +1 -1
- package/dist/esm/queuer/createQueuer.d.ts +4 -0
- package/dist/esm/queuer/createQueuer.js +2 -2
- package/dist/esm/queuer/createQueuer.js.map +1 -1
- package/dist/esm/rate-limiter/createRateLimitedSignal.d.ts +12 -8
- package/dist/esm/rate-limiter/createRateLimitedSignal.js.map +1 -1
- package/dist/esm/rate-limiter/createRateLimitedValue.d.ts +18 -22
- package/dist/esm/rate-limiter/createRateLimitedValue.js.map +1 -1
- package/dist/esm/rate-limiter/createRateLimiter.d.ts +10 -3
- package/dist/esm/rate-limiter/createRateLimiter.js +4 -2
- package/dist/esm/rate-limiter/createRateLimiter.js.map +1 -1
- package/dist/esm/throttler/createThrottledSignal.d.ts +1 -5
- package/dist/esm/throttler/createThrottledSignal.js.map +1 -1
- package/dist/esm/throttler/createThrottledValue.d.ts +10 -14
- package/dist/esm/throttler/createThrottledValue.js +1 -4
- package/dist/esm/throttler/createThrottledValue.js.map +1 -1
- package/dist/esm/throttler/createThrottler.d.ts +2 -2
- package/dist/esm/throttler/createThrottler.js +8 -3
- package/dist/esm/throttler/createThrottler.js.map +1 -1
- package/package.json +3 -3
- package/src/async-debouncer/createAsyncDebouncer.ts +40 -24
- package/src/async-queuer/createAsyncQueuer.ts +6 -2
- package/src/async-rate-limiter/createAsyncRateLimiter.ts +48 -29
- package/src/async-throttler/createAsyncThrottler.ts +49 -24
- package/src/debouncer/createDebouncedSignal.ts +2 -6
- package/src/debouncer/createDebouncedValue.ts +7 -16
- package/src/debouncer/createDebouncer.ts +16 -15
- package/src/queuer/createQueuer.ts +7 -3
- package/src/rate-limiter/createRateLimitedSignal.ts +13 -9
- package/src/rate-limiter/createRateLimitedValue.ts +19 -23
- package/src/rate-limiter/createRateLimiter.ts +22 -16
- package/src/throttler/createThrottledSignal.ts +2 -6
- package/src/throttler/createThrottledValue.ts +12 -19
- package/src/throttler/createThrottler.ts +17 -16
|
@@ -5,18 +5,24 @@ import type { Accessor } from 'solid-js'
|
|
|
5
5
|
import type { AnyAsyncFunction } from '@tanstack/pacer/types'
|
|
6
6
|
import type { AsyncThrottlerOptions } from '@tanstack/pacer/async-throttler'
|
|
7
7
|
|
|
8
|
-
export interface SolidAsyncThrottler<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
| '
|
|
8
|
+
export interface SolidAsyncThrottler<TFn extends AnyAsyncFunction>
|
|
9
|
+
extends Omit<
|
|
10
|
+
AsyncThrottler<TFn>,
|
|
11
|
+
| 'getSuccessCount'
|
|
12
|
+
| 'getSettleCount'
|
|
13
|
+
| 'getErrorCount'
|
|
14
14
|
| 'getIsPending'
|
|
15
|
+
| 'getIsExecuting'
|
|
16
|
+
| 'getLastResult'
|
|
15
17
|
| 'getLastExecutionTime'
|
|
16
18
|
| 'getNextExecutionTime'
|
|
17
19
|
> {
|
|
18
|
-
|
|
20
|
+
successCount: Accessor<number>
|
|
21
|
+
settleCount: Accessor<number>
|
|
22
|
+
errorCount: Accessor<number>
|
|
19
23
|
isPending: Accessor<boolean>
|
|
24
|
+
isExecuting: Accessor<boolean>
|
|
25
|
+
lastResult: Accessor<ReturnType<TFn> | undefined>
|
|
20
26
|
lastExecutionTime: Accessor<number>
|
|
21
27
|
nextExecutionTime: Accessor<number>
|
|
22
28
|
}
|
|
@@ -58,19 +64,30 @@ export interface SolidAsyncThrottler<
|
|
|
58
64
|
* ```
|
|
59
65
|
*/
|
|
60
66
|
|
|
61
|
-
export function createAsyncThrottler<
|
|
62
|
-
TFn extends AnyAsyncFunction,
|
|
63
|
-
TArgs extends Parameters<TFn>,
|
|
64
|
-
>(
|
|
67
|
+
export function createAsyncThrottler<TFn extends AnyAsyncFunction>(
|
|
65
68
|
fn: TFn,
|
|
66
|
-
initialOptions: AsyncThrottlerOptions<TFn
|
|
67
|
-
): SolidAsyncThrottler<TFn
|
|
68
|
-
const asyncThrottler =
|
|
69
|
+
initialOptions: AsyncThrottlerOptions<TFn>,
|
|
70
|
+
): SolidAsyncThrottler<TFn> {
|
|
71
|
+
const asyncThrottler = bindInstanceMethods(
|
|
72
|
+
new AsyncThrottler<TFn>(fn, initialOptions),
|
|
73
|
+
)
|
|
69
74
|
|
|
70
|
-
const [
|
|
71
|
-
asyncThrottler.
|
|
75
|
+
const [successCount, setSuccessCount] = createSignal(
|
|
76
|
+
asyncThrottler.getSuccessCount(),
|
|
77
|
+
)
|
|
78
|
+
const [settleCount, setSettleCount] = createSignal(
|
|
79
|
+
asyncThrottler.getSettleCount(),
|
|
80
|
+
)
|
|
81
|
+
const [errorCount, setErrorCount] = createSignal(
|
|
82
|
+
asyncThrottler.getErrorCount(),
|
|
72
83
|
)
|
|
73
84
|
const [isPending, setIsPending] = createSignal(asyncThrottler.getIsPending())
|
|
85
|
+
const [isExecuting, setIsExecuting] = createSignal(
|
|
86
|
+
asyncThrottler.getIsExecuting(),
|
|
87
|
+
)
|
|
88
|
+
const [lastResult, setLastResult] = createSignal(
|
|
89
|
+
asyncThrottler.getLastResult(),
|
|
90
|
+
)
|
|
74
91
|
const [lastExecutionTime, setLastExecutionTime] = createSignal(
|
|
75
92
|
asyncThrottler.getLastExecutionTime(),
|
|
76
93
|
)
|
|
@@ -78,17 +95,21 @@ export function createAsyncThrottler<
|
|
|
78
95
|
asyncThrottler.getNextExecutionTime(),
|
|
79
96
|
)
|
|
80
97
|
|
|
81
|
-
function setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn
|
|
98
|
+
function setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>) {
|
|
82
99
|
asyncThrottler.setOptions({
|
|
83
100
|
...newOptions,
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
onSettled: (throttler) => {
|
|
102
|
+
setSuccessCount(throttler.getSuccessCount())
|
|
103
|
+
setSettleCount(throttler.getSettleCount())
|
|
104
|
+
setErrorCount(throttler.getErrorCount())
|
|
86
105
|
setIsPending(throttler.getIsPending())
|
|
106
|
+
setIsExecuting(throttler.getIsExecuting())
|
|
87
107
|
setLastExecutionTime(throttler.getLastExecutionTime())
|
|
88
108
|
setNextExecutionTime(throttler.getNextExecutionTime())
|
|
109
|
+
setLastResult(throttler.getLastResult())
|
|
89
110
|
|
|
90
|
-
const
|
|
91
|
-
|
|
111
|
+
const onSettled = newOptions.onSettled ?? initialOptions.onSettled
|
|
112
|
+
onSettled?.(throttler)
|
|
92
113
|
},
|
|
93
114
|
})
|
|
94
115
|
}
|
|
@@ -96,11 +117,15 @@ export function createAsyncThrottler<
|
|
|
96
117
|
setOptions(initialOptions)
|
|
97
118
|
|
|
98
119
|
return {
|
|
99
|
-
...
|
|
100
|
-
|
|
120
|
+
...asyncThrottler,
|
|
121
|
+
errorCount,
|
|
122
|
+
isExecuting,
|
|
101
123
|
isPending,
|
|
102
124
|
lastExecutionTime,
|
|
125
|
+
lastResult,
|
|
103
126
|
nextExecutionTime,
|
|
104
127
|
setOptions,
|
|
105
|
-
|
|
128
|
+
settleCount,
|
|
129
|
+
successCount,
|
|
130
|
+
} as SolidAsyncThrottler<TFn>
|
|
106
131
|
}
|
|
@@ -45,12 +45,8 @@ import type { DebouncerOptions } from '@tanstack/pacer/debouncer'
|
|
|
45
45
|
*/
|
|
46
46
|
export function createDebouncedSignal<TValue>(
|
|
47
47
|
value: TValue,
|
|
48
|
-
initialOptions: DebouncerOptions<Setter<TValue
|
|
49
|
-
): [
|
|
50
|
-
Accessor<TValue>,
|
|
51
|
-
Setter<TValue>,
|
|
52
|
-
SolidDebouncer<Setter<TValue>, [Accessor<TValue>]>,
|
|
53
|
-
] {
|
|
48
|
+
initialOptions: DebouncerOptions<Setter<TValue>>,
|
|
49
|
+
): [Accessor<TValue>, Setter<TValue>, SolidDebouncer<Setter<TValue>>] {
|
|
54
50
|
const [debouncedValue, setDebouncedValue] = createSignal<TValue>(value)
|
|
55
51
|
|
|
56
52
|
const debouncer = createDebouncer(setDebouncedValue, initialOptions)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect
|
|
1
|
+
import { createEffect } from 'solid-js'
|
|
2
2
|
import { createDebouncedSignal } from './createDebouncedSignal'
|
|
3
3
|
import type { SolidDebouncer } from './createDebouncer'
|
|
4
4
|
import type { Accessor, Setter } from 'solid-js'
|
|
@@ -18,8 +18,8 @@ import type { DebouncerOptions } from '@tanstack/pacer/debouncer'
|
|
|
18
18
|
* or calculations occur.
|
|
19
19
|
*
|
|
20
20
|
* The hook returns a tuple containing:
|
|
21
|
-
* -
|
|
22
|
-
* - The debouncer instance with control methods
|
|
21
|
+
* - An Accessor that provides the current debounced value
|
|
22
|
+
* - The debouncer instance with control methods
|
|
23
23
|
*
|
|
24
24
|
* @example
|
|
25
25
|
* ```tsx
|
|
@@ -34,20 +34,14 @@ import type { DebouncerOptions } from '@tanstack/pacer/debouncer'
|
|
|
34
34
|
* fetchSearchResults(debouncedQuery());
|
|
35
35
|
* });
|
|
36
36
|
*
|
|
37
|
-
* //
|
|
38
|
-
*
|
|
39
|
-
* console.log('Is pending:', debouncer.isPending());
|
|
40
|
-
*
|
|
41
|
-
* // Handle input changes
|
|
42
|
-
* const handleChange = (e) => {
|
|
43
|
-
* setSearchQuery(e.target.value);
|
|
44
|
-
* };
|
|
37
|
+
* // Control the debouncer
|
|
38
|
+
* debouncer.cancel(); // Cancel any pending updates
|
|
45
39
|
* ```
|
|
46
40
|
*/
|
|
47
41
|
export function createDebouncedValue<TValue>(
|
|
48
42
|
value: Accessor<TValue>,
|
|
49
|
-
initialOptions: DebouncerOptions<Setter<TValue
|
|
50
|
-
): [Accessor<TValue>, SolidDebouncer<Setter<TValue
|
|
43
|
+
initialOptions: DebouncerOptions<Setter<TValue>>,
|
|
44
|
+
): [Accessor<TValue>, SolidDebouncer<Setter<TValue>>] {
|
|
51
45
|
const [debouncedValue, setDebouncedValue, debouncer] = createDebouncedSignal(
|
|
52
46
|
value(),
|
|
53
47
|
initialOptions,
|
|
@@ -55,9 +49,6 @@ export function createDebouncedValue<TValue>(
|
|
|
55
49
|
|
|
56
50
|
createEffect(() => {
|
|
57
51
|
setDebouncedValue(value() as any)
|
|
58
|
-
onCleanup(() => {
|
|
59
|
-
debouncer.cancel()
|
|
60
|
-
})
|
|
61
52
|
})
|
|
62
53
|
|
|
63
54
|
return [debouncedValue, debouncer]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Debouncer } from '@tanstack/pacer/debouncer'
|
|
2
|
-
import { createSignal } from 'solid-js'
|
|
2
|
+
import { createEffect, createSignal, onCleanup } from 'solid-js'
|
|
3
3
|
import { bindInstanceMethods } from '@tanstack/pacer/utils'
|
|
4
4
|
import type { Accessor } from 'solid-js'
|
|
5
5
|
import type { AnyFunction } from '@tanstack/pacer/types'
|
|
@@ -8,10 +8,8 @@ import type { DebouncerOptions } from '@tanstack/pacer/debouncer'
|
|
|
8
8
|
/**
|
|
9
9
|
* An extension of the Debouncer class that adds Solid signals to access the internal state of the debouncer
|
|
10
10
|
*/
|
|
11
|
-
export interface SolidDebouncer<
|
|
12
|
-
TFn
|
|
13
|
-
TArgs extends Parameters<TFn>,
|
|
14
|
-
> extends Omit<Debouncer<TFn, TArgs>, 'getExecutionCount' | 'getIsPending'> {
|
|
11
|
+
export interface SolidDebouncer<TFn extends AnyFunction>
|
|
12
|
+
extends Omit<Debouncer<TFn>, 'getExecutionCount' | 'getIsPending'> {
|
|
15
13
|
executionCount: Accessor<number>
|
|
16
14
|
isPending: Accessor<boolean>
|
|
17
15
|
}
|
|
@@ -52,21 +50,18 @@ export interface SolidDebouncer<
|
|
|
52
50
|
* debouncer.setOptions({ wait: 1000 });
|
|
53
51
|
* ```
|
|
54
52
|
*/
|
|
55
|
-
export function createDebouncer<
|
|
56
|
-
TFn extends AnyFunction,
|
|
57
|
-
TArgs extends Parameters<TFn>,
|
|
58
|
-
>(
|
|
53
|
+
export function createDebouncer<TFn extends AnyFunction>(
|
|
59
54
|
fn: TFn,
|
|
60
|
-
initialOptions: DebouncerOptions<TFn
|
|
61
|
-
): SolidDebouncer<TFn
|
|
62
|
-
const debouncer = new Debouncer<TFn
|
|
55
|
+
initialOptions: DebouncerOptions<TFn>,
|
|
56
|
+
): SolidDebouncer<TFn> {
|
|
57
|
+
const debouncer = bindInstanceMethods(new Debouncer<TFn>(fn, initialOptions))
|
|
63
58
|
|
|
64
59
|
const [executionCount, setExecutionCount] = createSignal(
|
|
65
60
|
debouncer.getExecutionCount(),
|
|
66
61
|
)
|
|
67
62
|
const [isPending, setIsPending] = createSignal(debouncer.getIsPending())
|
|
68
63
|
|
|
69
|
-
function setOptions(newOptions: Partial<DebouncerOptions<TFn
|
|
64
|
+
function setOptions(newOptions: Partial<DebouncerOptions<TFn>>) {
|
|
70
65
|
debouncer.setOptions({
|
|
71
66
|
...newOptions,
|
|
72
67
|
onExecute: (debouncer) => {
|
|
@@ -81,10 +76,16 @@ export function createDebouncer<
|
|
|
81
76
|
|
|
82
77
|
setOptions(initialOptions)
|
|
83
78
|
|
|
79
|
+
createEffect(() => {
|
|
80
|
+
onCleanup(() => {
|
|
81
|
+
debouncer.cancel()
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
84
85
|
return {
|
|
85
|
-
...
|
|
86
|
+
...debouncer,
|
|
86
87
|
executionCount,
|
|
87
88
|
isPending,
|
|
88
89
|
setOptions,
|
|
89
|
-
}
|
|
90
|
+
} as SolidDebouncer<TFn>
|
|
90
91
|
}
|
|
@@ -44,6 +44,10 @@ export interface SolidQueuer<TValue>
|
|
|
44
44
|
* Signal version of `getPeek`
|
|
45
45
|
*/
|
|
46
46
|
peek: Accessor<TValue | undefined>
|
|
47
|
+
/**
|
|
48
|
+
* Signal version of `getRejectionCount`
|
|
49
|
+
*/
|
|
50
|
+
rejectionCount: Accessor<number>
|
|
47
51
|
/**
|
|
48
52
|
* Signal version of `getSize`
|
|
49
53
|
*/
|
|
@@ -98,7 +102,7 @@ export interface SolidQueuer<TValue>
|
|
|
98
102
|
export function createQueuer<TValue>(
|
|
99
103
|
initialOptions: QueuerOptions<TValue> = {},
|
|
100
104
|
): SolidQueuer<TValue> {
|
|
101
|
-
const queuer = new Queuer<TValue>(initialOptions)
|
|
105
|
+
const queuer = bindInstanceMethods(new Queuer<TValue>(initialOptions))
|
|
102
106
|
|
|
103
107
|
const [allItems, setAllItems] = createSignal<Array<TValue>>(
|
|
104
108
|
queuer.getAllItems(),
|
|
@@ -152,7 +156,7 @@ export function createQueuer<TValue>(
|
|
|
152
156
|
setOptions(initialOptions)
|
|
153
157
|
|
|
154
158
|
return {
|
|
155
|
-
...
|
|
159
|
+
...queuer,
|
|
156
160
|
allItems,
|
|
157
161
|
executionCount,
|
|
158
162
|
isEmpty,
|
|
@@ -163,5 +167,5 @@ export function createQueuer<TValue>(
|
|
|
163
167
|
rejectionCount,
|
|
164
168
|
size,
|
|
165
169
|
setOptions,
|
|
166
|
-
}
|
|
170
|
+
} as SolidQueuer<TValue>
|
|
167
171
|
}
|
|
@@ -12,6 +12,12 @@ import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter'
|
|
|
12
12
|
* subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out
|
|
13
13
|
* or intelligently collapse updates. This can lead to bursts of rapid updates followed by periods of no updates.
|
|
14
14
|
*
|
|
15
|
+
* The rate limiter supports two types of windows:
|
|
16
|
+
* - 'fixed': A strict window that resets after the window period. All updates within the window count
|
|
17
|
+
* towards the limit, and the window resets completely after the period.
|
|
18
|
+
* - 'sliding': A rolling window that allows updates as old ones expire. This provides a more
|
|
19
|
+
* consistent rate of updates over time.
|
|
20
|
+
*
|
|
15
21
|
* For smoother update patterns, consider:
|
|
16
22
|
* - createThrottledSignal: When you want consistent spacing between updates (e.g. UI changes)
|
|
17
23
|
* - createDebouncedSignal: When you want to collapse rapid updates into a single update (e.g. search input)
|
|
@@ -28,16 +34,18 @@ import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter'
|
|
|
28
34
|
*
|
|
29
35
|
* @example
|
|
30
36
|
* ```tsx
|
|
31
|
-
* // Basic rate limiting - update state at most 5 times per minute
|
|
37
|
+
* // Basic rate limiting - update state at most 5 times per minute with a sliding window
|
|
32
38
|
* const [value, setValue, rateLimiter] = createRateLimitedSignal(0, {
|
|
33
39
|
* limit: 5,
|
|
34
|
-
* window: 60000
|
|
40
|
+
* window: 60000,
|
|
41
|
+
* windowType: 'sliding'
|
|
35
42
|
* });
|
|
36
43
|
*
|
|
37
|
-
* // With rejection callback
|
|
44
|
+
* // With rejection callback and fixed window
|
|
38
45
|
* const [value, setValue] = createRateLimitedSignal(0, {
|
|
39
46
|
* limit: 3,
|
|
40
47
|
* window: 5000,
|
|
48
|
+
* windowType: 'fixed',
|
|
41
49
|
* onReject: (rateLimiter) => {
|
|
42
50
|
* alert(`Rate limit reached. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
|
|
43
51
|
* }
|
|
@@ -56,12 +64,8 @@ import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter'
|
|
|
56
64
|
*/
|
|
57
65
|
export function createRateLimitedSignal<TValue>(
|
|
58
66
|
value: TValue,
|
|
59
|
-
initialOptions: RateLimiterOptions<Setter<TValue
|
|
60
|
-
): [
|
|
61
|
-
Accessor<TValue>,
|
|
62
|
-
Setter<TValue>,
|
|
63
|
-
SolidRateLimiter<Setter<TValue>, [Accessor<TValue>]>,
|
|
64
|
-
] {
|
|
67
|
+
initialOptions: RateLimiterOptions<Setter<TValue>>,
|
|
68
|
+
): [Accessor<TValue>, Setter<TValue>, SolidRateLimiter<Setter<TValue>>] {
|
|
65
69
|
const [rateLimitedValue, setRateLimitedValue] = createSignal<TValue>(value)
|
|
66
70
|
|
|
67
71
|
const rateLimiter = createRateLimiter(setRateLimitedValue, initialOptions)
|
|
@@ -12,49 +12,45 @@ import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter'
|
|
|
12
12
|
* subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out
|
|
13
13
|
* or intelligently collapse updates. This can lead to bursts of rapid updates followed by periods of no updates.
|
|
14
14
|
*
|
|
15
|
+
* The rate limiter supports two types of windows:
|
|
16
|
+
* - 'fixed': A strict window that resets after the window period. All updates within the window count
|
|
17
|
+
* towards the limit, and the window resets completely after the period.
|
|
18
|
+
* - 'sliding': A rolling window that allows updates as old ones expire. This provides a more
|
|
19
|
+
* consistent rate of updates over time.
|
|
20
|
+
*
|
|
15
21
|
* For smoother update patterns, consider:
|
|
16
22
|
* - createThrottledValue: When you want consistent spacing between updates (e.g. UI changes)
|
|
17
23
|
* - createDebouncedValue: When you want to collapse rapid updates into a single update (e.g. search input)
|
|
18
24
|
*
|
|
19
25
|
* Rate limiting should primarily be used when you need to enforce strict limits, like API rate limits.
|
|
20
26
|
*
|
|
21
|
-
* The hook returns
|
|
27
|
+
* The hook returns a tuple containing:
|
|
28
|
+
* - An accessor function that provides the rate-limited value
|
|
29
|
+
* - The rate limiter instance with control methods
|
|
22
30
|
*
|
|
23
31
|
* For more direct control over rate limiting behavior without Solid state management,
|
|
24
32
|
* consider using the lower-level createRateLimiter hook instead.
|
|
25
33
|
*
|
|
26
34
|
* @example
|
|
27
35
|
* ```tsx
|
|
28
|
-
* // Basic rate limiting - update at most 5 times per minute
|
|
29
|
-
* const [rateLimitedValue] = createRateLimitedValue(rawValue, {
|
|
36
|
+
* // Basic rate limiting - update at most 5 times per minute with a sliding window
|
|
37
|
+
* const [rateLimitedValue, rateLimiter] = createRateLimitedValue(rawValue, {
|
|
30
38
|
* limit: 5,
|
|
31
|
-
* window: 60000
|
|
39
|
+
* window: 60000,
|
|
40
|
+
* windowType: 'sliding'
|
|
32
41
|
* });
|
|
33
42
|
*
|
|
34
|
-
* //
|
|
35
|
-
*
|
|
36
|
-
* limit: 3,
|
|
37
|
-
* window: 5000,
|
|
38
|
-
* onReject: (rateLimiter) => {
|
|
39
|
-
* console.log(`Update rejected. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
|
|
40
|
-
* }
|
|
41
|
-
* });
|
|
43
|
+
* // Use the rate-limited value
|
|
44
|
+
* console.log(rateLimitedValue()); // Access the current rate-limited value
|
|
42
45
|
*
|
|
43
|
-
* //
|
|
44
|
-
*
|
|
45
|
-
* const remaining = rateLimiter.remainingInWindow();
|
|
46
|
-
* if (remaining > 0) {
|
|
47
|
-
* console.log(`${remaining} updates remaining in this window`);
|
|
48
|
-
* } else {
|
|
49
|
-
* console.log('Rate limit reached for this window');
|
|
50
|
-
* }
|
|
51
|
-
* };
|
|
46
|
+
* // Control the rate limiter
|
|
47
|
+
* rateLimiter.reset(); // Reset the rate limit window
|
|
52
48
|
* ```
|
|
53
49
|
*/
|
|
54
50
|
export function createRateLimitedValue<TValue>(
|
|
55
51
|
value: Accessor<TValue>,
|
|
56
|
-
initialOptions: RateLimiterOptions<Setter<TValue
|
|
57
|
-
): [Accessor<TValue>, SolidRateLimiter<Setter<TValue
|
|
52
|
+
initialOptions: RateLimiterOptions<Setter<TValue>>,
|
|
53
|
+
): [Accessor<TValue>, SolidRateLimiter<Setter<TValue>>] {
|
|
58
54
|
const [rateLimitedValue, setRateLimitedValue, rateLimiter] =
|
|
59
55
|
createRateLimitedSignal(value(), initialOptions)
|
|
60
56
|
|
|
@@ -5,11 +5,9 @@ import type { Accessor } from 'solid-js'
|
|
|
5
5
|
import type { AnyFunction } from '@tanstack/pacer/types'
|
|
6
6
|
import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter'
|
|
7
7
|
|
|
8
|
-
export interface SolidRateLimiter<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
> extends Omit<
|
|
12
|
-
RateLimiter<TFn, TArgs>,
|
|
8
|
+
export interface SolidRateLimiter<TFn extends AnyFunction>
|
|
9
|
+
extends Omit<
|
|
10
|
+
RateLimiter<TFn>,
|
|
13
11
|
| 'getExecutionCount'
|
|
14
12
|
| 'getMsUntilNextWindow'
|
|
15
13
|
| 'getRejectionCount'
|
|
@@ -31,6 +29,12 @@ export interface SolidRateLimiter<
|
|
|
31
29
|
* a time window, then blocks all subsequent calls until the window resets. Unlike throttling or debouncing,
|
|
32
30
|
* it does not attempt to space out or collapse executions intelligently.
|
|
33
31
|
*
|
|
32
|
+
* The rate limiter supports two types of windows:
|
|
33
|
+
* - 'fixed': A strict window that resets after the window period. All executions within the window count
|
|
34
|
+
* towards the limit, and the window resets completely after the period.
|
|
35
|
+
* - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
|
|
36
|
+
* consistent rate of execution over time.
|
|
37
|
+
*
|
|
34
38
|
* For smoother execution patterns:
|
|
35
39
|
* - Use throttling when you want consistent spacing between executions (e.g. UI updates)
|
|
36
40
|
* - Use debouncing when you want to collapse rapid-fire events (e.g. search input)
|
|
@@ -38,10 +42,11 @@ export interface SolidRateLimiter<
|
|
|
38
42
|
*
|
|
39
43
|
* @example
|
|
40
44
|
* ```tsx
|
|
41
|
-
* // Basic rate limiting - max 5 calls per minute
|
|
45
|
+
* // Basic rate limiting - max 5 calls per minute with a sliding window
|
|
42
46
|
* const rateLimiter = createRateLimiter(apiCall, {
|
|
43
47
|
* limit: 5,
|
|
44
48
|
* window: 60000,
|
|
49
|
+
* windowType: 'sliding'
|
|
45
50
|
* });
|
|
46
51
|
*
|
|
47
52
|
* // Monitor rate limit status
|
|
@@ -60,14 +65,13 @@ export interface SolidRateLimiter<
|
|
|
60
65
|
* console.log('Next window in:', rateLimiter.msUntilNextWindow());
|
|
61
66
|
* ```
|
|
62
67
|
*/
|
|
63
|
-
export function createRateLimiter<
|
|
64
|
-
TFn extends AnyFunction,
|
|
65
|
-
TArgs extends Parameters<TFn>,
|
|
66
|
-
>(
|
|
68
|
+
export function createRateLimiter<TFn extends AnyFunction>(
|
|
67
69
|
fn: TFn,
|
|
68
|
-
initialOptions: RateLimiterOptions<TFn
|
|
69
|
-
): SolidRateLimiter<TFn
|
|
70
|
-
const rateLimiter =
|
|
70
|
+
initialOptions: RateLimiterOptions<TFn>,
|
|
71
|
+
): SolidRateLimiter<TFn> {
|
|
72
|
+
const rateLimiter = bindInstanceMethods(
|
|
73
|
+
new RateLimiter<TFn>(fn, initialOptions),
|
|
74
|
+
)
|
|
71
75
|
|
|
72
76
|
const [executionCount, setExecutionCount] = createSignal(
|
|
73
77
|
rateLimiter.getExecutionCount(),
|
|
@@ -82,13 +86,14 @@ export function createRateLimiter<
|
|
|
82
86
|
rateLimiter.getMsUntilNextWindow(),
|
|
83
87
|
)
|
|
84
88
|
|
|
85
|
-
function setOptions(newOptions: Partial<RateLimiterOptions<TFn
|
|
89
|
+
function setOptions(newOptions: Partial<RateLimiterOptions<TFn>>) {
|
|
86
90
|
rateLimiter.setOptions({
|
|
87
91
|
...newOptions,
|
|
88
92
|
onExecute: (rateLimiter) => {
|
|
89
93
|
setExecutionCount(rateLimiter.getExecutionCount())
|
|
90
94
|
setRemainingInWindow(rateLimiter.getRemainingInWindow())
|
|
91
95
|
setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow())
|
|
96
|
+
|
|
92
97
|
const onExecute = newOptions.onExecute ?? initialOptions.onExecute
|
|
93
98
|
onExecute?.(rateLimiter)
|
|
94
99
|
},
|
|
@@ -96,6 +101,7 @@ export function createRateLimiter<
|
|
|
96
101
|
setRejectionCount(rateLimiter.getRejectionCount())
|
|
97
102
|
setRemainingInWindow(rateLimiter.getRemainingInWindow())
|
|
98
103
|
setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow())
|
|
104
|
+
|
|
99
105
|
const onReject = newOptions.onReject ?? initialOptions.onReject
|
|
100
106
|
onReject?.(rateLimiter)
|
|
101
107
|
},
|
|
@@ -105,11 +111,11 @@ export function createRateLimiter<
|
|
|
105
111
|
setOptions(initialOptions)
|
|
106
112
|
|
|
107
113
|
return {
|
|
108
|
-
...
|
|
114
|
+
...rateLimiter,
|
|
109
115
|
executionCount,
|
|
110
116
|
rejectionCount,
|
|
111
117
|
remainingInWindow,
|
|
112
118
|
msUntilNextWindow,
|
|
113
119
|
setOptions,
|
|
114
|
-
}
|
|
120
|
+
} as SolidRateLimiter<TFn>
|
|
115
121
|
}
|
|
@@ -40,12 +40,8 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler'
|
|
|
40
40
|
*/
|
|
41
41
|
export function createThrottledSignal<TValue>(
|
|
42
42
|
value: TValue,
|
|
43
|
-
initialOptions: ThrottlerOptions<Setter<TValue
|
|
44
|
-
): [
|
|
45
|
-
Accessor<TValue>,
|
|
46
|
-
Setter<TValue>,
|
|
47
|
-
SolidThrottler<Setter<TValue>, [Accessor<TValue>]>,
|
|
48
|
-
] {
|
|
43
|
+
initialOptions: ThrottlerOptions<Setter<TValue>>,
|
|
44
|
+
): [Accessor<TValue>, Setter<TValue>, SolidThrottler<Setter<TValue>>] {
|
|
49
45
|
const [throttledValue, setThrottledValue] = createSignal<TValue>(value)
|
|
50
46
|
const throttler = createThrottler(setThrottledValue, initialOptions)
|
|
51
47
|
return [throttledValue, throttler.maybeExecute as Setter<TValue>, throttler]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createEffect
|
|
1
|
+
import { createEffect } from 'solid-js'
|
|
2
2
|
import { createThrottledSignal } from './createThrottledSignal'
|
|
3
3
|
import type { SolidThrottler } from './createThrottler'
|
|
4
4
|
import type { Accessor, Setter } from 'solid-js'
|
|
@@ -11,7 +11,10 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler'
|
|
|
11
11
|
* Throttling ensures the value updates occur at a controlled rate regardless of how frequently the input value changes.
|
|
12
12
|
* This is useful for rate-limiting expensive re-renders or API calls that depend on rapidly changing values.
|
|
13
13
|
*
|
|
14
|
-
* The hook returns
|
|
14
|
+
* The hook returns a tuple containing:
|
|
15
|
+
* - An accessor function that provides the throttled value
|
|
16
|
+
* - The throttler instance with control methods
|
|
17
|
+
*
|
|
15
18
|
* The throttled value will update according to the leading/trailing edge behavior specified in the options.
|
|
16
19
|
*
|
|
17
20
|
* For more direct control over throttling behavior without Solid state management,
|
|
@@ -20,26 +23,19 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler'
|
|
|
20
23
|
* @example
|
|
21
24
|
* ```tsx
|
|
22
25
|
* // Basic throttling - update at most once per second
|
|
23
|
-
* const [throttledValue] = createThrottledValue(rawValue, { wait: 1000 });
|
|
26
|
+
* const [throttledValue, throttler] = createThrottledValue(rawValue, { wait: 1000 });
|
|
24
27
|
*
|
|
25
|
-
* //
|
|
26
|
-
*
|
|
27
|
-
* wait: 1000,
|
|
28
|
-
* leading: true, // Update immediately on first change
|
|
29
|
-
* trailing: false // Skip trailing edge updates
|
|
30
|
-
* });
|
|
28
|
+
* // Use the throttled value
|
|
29
|
+
* console.log(throttledValue()); // Access the current throttled value
|
|
31
30
|
*
|
|
32
|
-
* //
|
|
33
|
-
*
|
|
34
|
-
* console.log('Is pending:', throttler.isPending());
|
|
35
|
-
* console.log('Last execution:', throttler.lastExecutionTime());
|
|
36
|
-
* console.log('Next execution:', throttler.nextExecutionTime());
|
|
31
|
+
* // Control the throttler
|
|
32
|
+
* throttler.cancel(); // Cancel any pending updates
|
|
37
33
|
* ```
|
|
38
34
|
*/
|
|
39
35
|
export function createThrottledValue<TValue>(
|
|
40
36
|
value: Accessor<TValue>,
|
|
41
|
-
initialOptions: ThrottlerOptions<Setter<TValue
|
|
42
|
-
): [Accessor<TValue>, SolidThrottler<Setter<TValue
|
|
37
|
+
initialOptions: ThrottlerOptions<Setter<TValue>>,
|
|
38
|
+
): [Accessor<TValue>, SolidThrottler<Setter<TValue>>] {
|
|
43
39
|
const [throttledValue, setThrottledValue, throttler] = createThrottledSignal(
|
|
44
40
|
value(),
|
|
45
41
|
initialOptions,
|
|
@@ -47,9 +43,6 @@ export function createThrottledValue<TValue>(
|
|
|
47
43
|
|
|
48
44
|
createEffect(() => {
|
|
49
45
|
setThrottledValue(value() as any)
|
|
50
|
-
onCleanup(() => {
|
|
51
|
-
throttler.cancel()
|
|
52
|
-
})
|
|
53
46
|
})
|
|
54
47
|
|
|
55
48
|
return [throttledValue, throttler]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Throttler } from '@tanstack/pacer/throttler'
|
|
2
|
-
import { createSignal } from 'solid-js'
|
|
2
|
+
import { createEffect, createSignal, onCleanup } from 'solid-js'
|
|
3
3
|
import { bindInstanceMethods } from '@tanstack/pacer/utils'
|
|
4
4
|
import type { Accessor } from 'solid-js'
|
|
5
5
|
import type { AnyFunction } from '@tanstack/pacer/types'
|
|
@@ -8,11 +8,9 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler'
|
|
|
8
8
|
/**
|
|
9
9
|
* An extension of the Throttler class that adds Solid signals to access the internal state of the throttler
|
|
10
10
|
*/
|
|
11
|
-
export interface SolidThrottler<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
> extends Omit<
|
|
15
|
-
Throttler<TFn, TArgs>,
|
|
11
|
+
export interface SolidThrottler<TFn extends AnyFunction>
|
|
12
|
+
extends Omit<
|
|
13
|
+
Throttler<TFn>,
|
|
16
14
|
| 'getExecutionCount'
|
|
17
15
|
| 'getIsPending'
|
|
18
16
|
| 'getLastExecutionTime'
|
|
@@ -58,14 +56,11 @@ export interface SolidThrottler<
|
|
|
58
56
|
* console.log(throttler.nextExecutionTime()); // timestamp of next allowed execution
|
|
59
57
|
* ```
|
|
60
58
|
*/
|
|
61
|
-
export function createThrottler<
|
|
62
|
-
TFn extends AnyFunction,
|
|
63
|
-
TArgs extends Parameters<TFn>,
|
|
64
|
-
>(
|
|
59
|
+
export function createThrottler<TFn extends AnyFunction>(
|
|
65
60
|
fn: TFn,
|
|
66
|
-
initialOptions: ThrottlerOptions<TFn
|
|
67
|
-
): SolidThrottler<TFn
|
|
68
|
-
const throttler = new Throttler<TFn
|
|
61
|
+
initialOptions: ThrottlerOptions<TFn>,
|
|
62
|
+
): SolidThrottler<TFn> {
|
|
63
|
+
const throttler = bindInstanceMethods(new Throttler<TFn>(fn, initialOptions))
|
|
69
64
|
|
|
70
65
|
const [executionCount, setExecutionCount] = createSignal(
|
|
71
66
|
throttler.getExecutionCount(),
|
|
@@ -78,7 +73,7 @@ export function createThrottler<
|
|
|
78
73
|
throttler.getNextExecutionTime(),
|
|
79
74
|
)
|
|
80
75
|
|
|
81
|
-
function setOptions(newOptions: Partial<ThrottlerOptions<TFn
|
|
76
|
+
function setOptions(newOptions: Partial<ThrottlerOptions<TFn>>) {
|
|
82
77
|
throttler.setOptions({
|
|
83
78
|
...newOptions,
|
|
84
79
|
onExecute: (throttler) => {
|
|
@@ -95,12 +90,18 @@ export function createThrottler<
|
|
|
95
90
|
|
|
96
91
|
setOptions(initialOptions)
|
|
97
92
|
|
|
93
|
+
createEffect(() => {
|
|
94
|
+
onCleanup(() => {
|
|
95
|
+
throttler.cancel()
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
98
99
|
return {
|
|
99
|
-
...
|
|
100
|
+
...throttler,
|
|
100
101
|
executionCount,
|
|
101
102
|
isPending,
|
|
102
103
|
lastExecutionTime,
|
|
103
104
|
nextExecutionTime,
|
|
104
105
|
setOptions,
|
|
105
|
-
}
|
|
106
|
+
} as SolidThrottler<TFn>
|
|
106
107
|
}
|