@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.
Files changed (89) hide show
  1. package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs +22 -7
  2. package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs.map +1 -1
  3. package/dist/cjs/async-debouncer/createAsyncDebouncer.d.cts +6 -3
  4. package/dist/cjs/async-queuer/createAsyncQueuer.cjs.map +1 -1
  5. package/dist/cjs/async-queuer/createAsyncQueuer.d.cts +5 -1
  6. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs +20 -9
  7. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs.map +1 -1
  8. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.d.cts +19 -6
  9. package/dist/cjs/async-throttler/createAsyncThrottler.cjs +32 -10
  10. package/dist/cjs/async-throttler/createAsyncThrottler.cjs.map +1 -1
  11. package/dist/cjs/async-throttler/createAsyncThrottler.d.cts +7 -3
  12. package/dist/cjs/debouncer/createDebouncedSignal.cjs.map +1 -1
  13. package/dist/cjs/debouncer/createDebouncedSignal.d.cts +1 -5
  14. package/dist/cjs/debouncer/createDebouncedValue.cjs +0 -3
  15. package/dist/cjs/debouncer/createDebouncedValue.cjs.map +1 -1
  16. package/dist/cjs/debouncer/createDebouncedValue.d.cts +5 -11
  17. package/dist/cjs/debouncer/createDebouncer.cjs +7 -2
  18. package/dist/cjs/debouncer/createDebouncer.cjs.map +1 -1
  19. package/dist/cjs/debouncer/createDebouncer.d.cts +2 -2
  20. package/dist/cjs/queuer/createQueuer.cjs +2 -2
  21. package/dist/cjs/queuer/createQueuer.cjs.map +1 -1
  22. package/dist/cjs/queuer/createQueuer.d.cts +4 -0
  23. package/dist/cjs/rate-limiter/createRateLimitedSignal.cjs.map +1 -1
  24. package/dist/cjs/rate-limiter/createRateLimitedSignal.d.cts +12 -8
  25. package/dist/cjs/rate-limiter/createRateLimitedValue.cjs.map +1 -1
  26. package/dist/cjs/rate-limiter/createRateLimitedValue.d.cts +18 -22
  27. package/dist/cjs/rate-limiter/createRateLimiter.cjs +4 -2
  28. package/dist/cjs/rate-limiter/createRateLimiter.cjs.map +1 -1
  29. package/dist/cjs/rate-limiter/createRateLimiter.d.cts +10 -3
  30. package/dist/cjs/throttler/createThrottledSignal.cjs.map +1 -1
  31. package/dist/cjs/throttler/createThrottledSignal.d.cts +1 -5
  32. package/dist/cjs/throttler/createThrottledValue.cjs +0 -3
  33. package/dist/cjs/throttler/createThrottledValue.cjs.map +1 -1
  34. package/dist/cjs/throttler/createThrottledValue.d.cts +10 -14
  35. package/dist/cjs/throttler/createThrottler.cjs +7 -2
  36. package/dist/cjs/throttler/createThrottler.cjs.map +1 -1
  37. package/dist/cjs/throttler/createThrottler.d.cts +2 -2
  38. package/dist/esm/async-debouncer/createAsyncDebouncer.d.ts +6 -3
  39. package/dist/esm/async-debouncer/createAsyncDebouncer.js +22 -7
  40. package/dist/esm/async-debouncer/createAsyncDebouncer.js.map +1 -1
  41. package/dist/esm/async-queuer/createAsyncQueuer.d.ts +5 -1
  42. package/dist/esm/async-queuer/createAsyncQueuer.js.map +1 -1
  43. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.d.ts +19 -6
  44. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js +20 -9
  45. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js.map +1 -1
  46. package/dist/esm/async-throttler/createAsyncThrottler.d.ts +7 -3
  47. package/dist/esm/async-throttler/createAsyncThrottler.js +32 -10
  48. package/dist/esm/async-throttler/createAsyncThrottler.js.map +1 -1
  49. package/dist/esm/debouncer/createDebouncedSignal.d.ts +1 -5
  50. package/dist/esm/debouncer/createDebouncedSignal.js.map +1 -1
  51. package/dist/esm/debouncer/createDebouncedValue.d.ts +5 -11
  52. package/dist/esm/debouncer/createDebouncedValue.js +1 -4
  53. package/dist/esm/debouncer/createDebouncedValue.js.map +1 -1
  54. package/dist/esm/debouncer/createDebouncer.d.ts +2 -2
  55. package/dist/esm/debouncer/createDebouncer.js +8 -3
  56. package/dist/esm/debouncer/createDebouncer.js.map +1 -1
  57. package/dist/esm/queuer/createQueuer.d.ts +4 -0
  58. package/dist/esm/queuer/createQueuer.js +2 -2
  59. package/dist/esm/queuer/createQueuer.js.map +1 -1
  60. package/dist/esm/rate-limiter/createRateLimitedSignal.d.ts +12 -8
  61. package/dist/esm/rate-limiter/createRateLimitedSignal.js.map +1 -1
  62. package/dist/esm/rate-limiter/createRateLimitedValue.d.ts +18 -22
  63. package/dist/esm/rate-limiter/createRateLimitedValue.js.map +1 -1
  64. package/dist/esm/rate-limiter/createRateLimiter.d.ts +10 -3
  65. package/dist/esm/rate-limiter/createRateLimiter.js +4 -2
  66. package/dist/esm/rate-limiter/createRateLimiter.js.map +1 -1
  67. package/dist/esm/throttler/createThrottledSignal.d.ts +1 -5
  68. package/dist/esm/throttler/createThrottledSignal.js.map +1 -1
  69. package/dist/esm/throttler/createThrottledValue.d.ts +10 -14
  70. package/dist/esm/throttler/createThrottledValue.js +1 -4
  71. package/dist/esm/throttler/createThrottledValue.js.map +1 -1
  72. package/dist/esm/throttler/createThrottler.d.ts +2 -2
  73. package/dist/esm/throttler/createThrottler.js +8 -3
  74. package/dist/esm/throttler/createThrottler.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/async-debouncer/createAsyncDebouncer.ts +40 -24
  77. package/src/async-queuer/createAsyncQueuer.ts +6 -2
  78. package/src/async-rate-limiter/createAsyncRateLimiter.ts +48 -29
  79. package/src/async-throttler/createAsyncThrottler.ts +49 -24
  80. package/src/debouncer/createDebouncedSignal.ts +2 -6
  81. package/src/debouncer/createDebouncedValue.ts +7 -16
  82. package/src/debouncer/createDebouncer.ts +16 -15
  83. package/src/queuer/createQueuer.ts +7 -3
  84. package/src/rate-limiter/createRateLimitedSignal.ts +13 -9
  85. package/src/rate-limiter/createRateLimitedValue.ts +19 -23
  86. package/src/rate-limiter/createRateLimiter.ts +22 -16
  87. package/src/throttler/createThrottledSignal.ts +2 -6
  88. package/src/throttler/createThrottledValue.ts +12 -19
  89. 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
- TFn extends AnyAsyncFunction,
10
- TArgs extends Parameters<TFn>,
11
- > extends Omit<
12
- AsyncThrottler<TFn, TArgs>,
13
- | 'getExecutionCount'
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
- executionCount: Accessor<number>
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, TArgs>,
67
- ): SolidAsyncThrottler<TFn, TArgs> {
68
- const asyncThrottler = new AsyncThrottler<TFn, TArgs>(fn, initialOptions)
69
+ initialOptions: AsyncThrottlerOptions<TFn>,
70
+ ): SolidAsyncThrottler<TFn> {
71
+ const asyncThrottler = bindInstanceMethods(
72
+ new AsyncThrottler<TFn>(fn, initialOptions),
73
+ )
69
74
 
70
- const [executionCount, setExecutionCount] = createSignal(
71
- asyncThrottler.getExecutionCount(),
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, TArgs>>) {
98
+ function setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>) {
82
99
  asyncThrottler.setOptions({
83
100
  ...newOptions,
84
- onExecute: (throttler) => {
85
- setExecutionCount(throttler.getExecutionCount())
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 onExecute = newOptions.onExecute ?? initialOptions.onExecute
91
- onExecute?.(throttler)
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
- ...bindInstanceMethods(asyncThrottler),
100
- executionCount,
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>, [Accessor<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, onCleanup } from 'solid-js'
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
- * - The current debounced value (as an Accessor)
22
- * - The debouncer instance with control methods and state signals
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
- * // Access debouncer state via signals
38
- * console.log('Executions:', debouncer.executionCount());
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>, [Accessor<TValue>]>,
50
- ): [Accessor<TValue>, SolidDebouncer<Setter<TValue>, [Accessor<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 extends AnyFunction,
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, TArgs>,
61
- ): SolidDebouncer<TFn, TArgs> {
62
- const debouncer = new Debouncer<TFn, TArgs>(fn, initialOptions)
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, TArgs>>) {
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
- ...bindInstanceMethods(debouncer),
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
- ...bindInstanceMethods(queuer),
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>, [Accessor<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 both the rate-limited value and the underlying rateLimiter instance for additional control.
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
- * // With rejection callback
35
- * const [rateLimitedValue, rateLimiter] = createRateLimitedValue(rawValue, {
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
- * // Optionally access rateLimiter state via signals
44
- * const handleSubmit = () => {
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>, [Accessor<TValue>]>,
57
- ): [Accessor<TValue>, SolidRateLimiter<Setter<TValue>, [Accessor<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
- TFn extends AnyFunction,
10
- TArgs extends Parameters<TFn>,
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, TArgs>,
69
- ): SolidRateLimiter<TFn, TArgs> {
70
- const rateLimiter = new RateLimiter<TFn, TArgs>(fn, initialOptions)
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, TArgs>>) {
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
- ...bindInstanceMethods(rateLimiter),
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>, [Accessor<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, onCleanup } from 'solid-js'
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 both the throttled value and the underlying throttler instance for additional control.
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
- * // With custom leading/trailing behavior
26
- * const [throttledValue, throttler] = createThrottledValue(rawValue, {
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
- * // Access throttler state via signals
33
- * console.log('Executions:', throttler.executionCount());
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>, [Accessor<TValue>]>,
42
- ): [Accessor<TValue>, SolidThrottler<Setter<TValue>, [Accessor<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
- TFn extends AnyFunction,
13
- TArgs extends Parameters<TFn>,
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, TArgs>,
67
- ): SolidThrottler<TFn, TArgs> {
68
- const throttler = new Throttler<TFn, TArgs>(fn, initialOptions)
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, TArgs>>) {
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
- ...bindInstanceMethods(throttler),
100
+ ...throttler,
100
101
  executionCount,
101
102
  isPending,
102
103
  lastExecutionTime,
103
104
  nextExecutionTime,
104
105
  setOptions,
105
- }
106
+ } as SolidThrottler<TFn>
106
107
  }