@tanstack/solid-pacer 0.22.0 → 0.23.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 (170) hide show
  1. package/README.md +4 -4
  2. package/dist/async-batcher/createAsyncBatcher.d.ts +5 -9
  3. package/dist/async-batcher/createAsyncBatcher.js +2 -5
  4. package/dist/async-debouncer/createAsyncDebouncer.d.ts +8 -11
  5. package/dist/async-debouncer/createAsyncDebouncer.js +5 -7
  6. package/dist/async-queuer/createAsyncQueuer.d.ts +9 -11
  7. package/dist/async-queuer/createAsyncQueuer.js +4 -5
  8. package/dist/async-rate-limiter/createAsyncRateLimiter.d.ts +10 -12
  9. package/dist/async-rate-limiter/createAsyncRateLimiter.js +7 -8
  10. package/dist/async-throttler/createAsyncThrottler.d.ts +8 -13
  11. package/dist/async-throttler/createAsyncThrottler.js +5 -9
  12. package/dist/batcher/createBatcher.d.ts +11 -14
  13. package/dist/batcher/createBatcher.js +6 -8
  14. package/dist/debouncer/createDebouncedSignal.d.ts +2 -4
  15. package/dist/debouncer/createDebouncedSignal.js +1 -2
  16. package/dist/debouncer/createDebouncedValue.d.ts +2 -4
  17. package/dist/debouncer/createDebouncedValue.js +1 -2
  18. package/dist/debouncer/createDebouncer.d.ts +4 -6
  19. package/dist/debouncer/createDebouncer.js +1 -2
  20. package/dist/provider/PacerProvider.d.ts +6 -8
  21. package/dist/provider/PacerProvider.js +1 -2
  22. package/dist/queuer/createQueuedSignal.d.ts +2 -4
  23. package/dist/queuer/createQueuedSignal.js +1 -2
  24. package/dist/queuer/createQueuer.d.ts +4 -6
  25. package/dist/queuer/createQueuer.js +1 -2
  26. package/dist/rate-limiter/createRateLimitedSignal.d.ts +12 -13
  27. package/dist/rate-limiter/createRateLimitedSignal.js +11 -11
  28. package/dist/rate-limiter/createRateLimitedValue.d.ts +2 -4
  29. package/dist/rate-limiter/createRateLimitedValue.js +1 -2
  30. package/dist/rate-limiter/createRateLimiter.d.ts +8 -9
  31. package/dist/rate-limiter/createRateLimiter.js +5 -5
  32. package/dist/throttler/createThrottledSignal.d.ts +2 -6
  33. package/dist/throttler/createThrottledSignal.js +1 -4
  34. package/dist/throttler/createThrottledValue.d.ts +2 -6
  35. package/dist/throttler/createThrottledValue.js +1 -4
  36. package/dist/throttler/createThrottler.d.ts +4 -8
  37. package/dist/throttler/createThrottler.js +1 -4
  38. package/package.json +23 -67
  39. package/dist/async-batcher/createAsyncBatcher.cjs +0 -166
  40. package/dist/async-batcher/createAsyncBatcher.cjs.map +0 -1
  41. package/dist/async-batcher/createAsyncBatcher.d.cts +0 -177
  42. package/dist/async-batcher/createAsyncBatcher.js.map +0 -1
  43. package/dist/async-batcher/index.cjs +0 -11
  44. package/dist/async-batcher/index.d.cts +0 -3
  45. package/dist/async-debouncer/createAsyncDebouncer.cjs +0 -151
  46. package/dist/async-debouncer/createAsyncDebouncer.cjs.map +0 -1
  47. package/dist/async-debouncer/createAsyncDebouncer.d.cts +0 -163
  48. package/dist/async-debouncer/createAsyncDebouncer.js.map +0 -1
  49. package/dist/async-debouncer/index.cjs +0 -11
  50. package/dist/async-debouncer/index.d.cts +0 -3
  51. package/dist/async-queuer/createAsyncQueuer.cjs +0 -158
  52. package/dist/async-queuer/createAsyncQueuer.cjs.map +0 -1
  53. package/dist/async-queuer/createAsyncQueuer.d.cts +0 -169
  54. package/dist/async-queuer/createAsyncQueuer.js.map +0 -1
  55. package/dist/async-queuer/index.cjs +0 -11
  56. package/dist/async-queuer/index.d.cts +0 -3
  57. package/dist/async-rate-limiter/createAsyncRateLimiter.cjs +0 -193
  58. package/dist/async-rate-limiter/createAsyncRateLimiter.cjs.map +0 -1
  59. package/dist/async-rate-limiter/createAsyncRateLimiter.d.cts +0 -208
  60. package/dist/async-rate-limiter/createAsyncRateLimiter.js.map +0 -1
  61. package/dist/async-rate-limiter/index.cjs +0 -11
  62. package/dist/async-rate-limiter/index.d.cts +0 -3
  63. package/dist/async-throttler/createAsyncThrottler.cjs +0 -151
  64. package/dist/async-throttler/createAsyncThrottler.cjs.map +0 -1
  65. package/dist/async-throttler/createAsyncThrottler.d.cts +0 -163
  66. package/dist/async-throttler/createAsyncThrottler.js.map +0 -1
  67. package/dist/async-throttler/index.cjs +0 -11
  68. package/dist/async-throttler/index.d.cts +0 -3
  69. package/dist/batcher/createBatcher.cjs +0 -132
  70. package/dist/batcher/createBatcher.cjs.map +0 -1
  71. package/dist/batcher/createBatcher.d.cts +0 -146
  72. package/dist/batcher/createBatcher.js.map +0 -1
  73. package/dist/batcher/index.cjs +0 -11
  74. package/dist/batcher/index.d.cts +0 -3
  75. package/dist/debouncer/createDebouncedSignal.cjs +0 -88
  76. package/dist/debouncer/createDebouncedSignal.cjs.map +0 -1
  77. package/dist/debouncer/createDebouncedSignal.d.cts +0 -79
  78. package/dist/debouncer/createDebouncedSignal.js.map +0 -1
  79. package/dist/debouncer/createDebouncedValue.cjs +0 -77
  80. package/dist/debouncer/createDebouncedValue.cjs.map +0 -1
  81. package/dist/debouncer/createDebouncedValue.d.cts +0 -70
  82. package/dist/debouncer/createDebouncedValue.js.map +0 -1
  83. package/dist/debouncer/createDebouncer.cjs +0 -129
  84. package/dist/debouncer/createDebouncer.cjs.map +0 -1
  85. package/dist/debouncer/createDebouncer.d.cts +0 -144
  86. package/dist/debouncer/createDebouncer.js.map +0 -1
  87. package/dist/debouncer/index.cjs +0 -15
  88. package/dist/debouncer/index.d.cts +0 -5
  89. package/dist/index.cjs +0 -47
  90. package/dist/index.d.cts +0 -20
  91. package/dist/provider/PacerProvider.cjs +0 -27
  92. package/dist/provider/PacerProvider.cjs.map +0 -1
  93. package/dist/provider/PacerProvider.d.cts +0 -28
  94. package/dist/provider/PacerProvider.js.map +0 -1
  95. package/dist/provider/index.cjs +0 -6
  96. package/dist/provider/index.d.cts +0 -2
  97. package/dist/queuer/createQueuedSignal.cjs +0 -129
  98. package/dist/queuer/createQueuedSignal.cjs.map +0 -1
  99. package/dist/queuer/createQueuedSignal.d.cts +0 -121
  100. package/dist/queuer/createQueuedSignal.js.map +0 -1
  101. package/dist/queuer/createQueuer.cjs +0 -133
  102. package/dist/queuer/createQueuer.cjs.map +0 -1
  103. package/dist/queuer/createQueuer.d.cts +0 -147
  104. package/dist/queuer/createQueuer.js.map +0 -1
  105. package/dist/queuer/index.cjs +0 -13
  106. package/dist/queuer/index.d.cts +0 -4
  107. package/dist/rate-limiter/createRateLimitedSignal.cjs +0 -102
  108. package/dist/rate-limiter/createRateLimitedSignal.cjs.map +0 -1
  109. package/dist/rate-limiter/createRateLimitedSignal.d.cts +0 -93
  110. package/dist/rate-limiter/createRateLimitedSignal.js.map +0 -1
  111. package/dist/rate-limiter/createRateLimitedValue.cjs +0 -102
  112. package/dist/rate-limiter/createRateLimitedValue.cjs.map +0 -1
  113. package/dist/rate-limiter/createRateLimitedValue.d.cts +0 -95
  114. package/dist/rate-limiter/createRateLimitedValue.js.map +0 -1
  115. package/dist/rate-limiter/createRateLimiter.cjs +0 -157
  116. package/dist/rate-limiter/createRateLimiter.cjs.map +0 -1
  117. package/dist/rate-limiter/createRateLimiter.d.cts +0 -173
  118. package/dist/rate-limiter/createRateLimiter.js.map +0 -1
  119. package/dist/rate-limiter/index.cjs +0 -15
  120. package/dist/rate-limiter/index.d.cts +0 -5
  121. package/dist/throttler/createThrottledSignal.cjs +0 -79
  122. package/dist/throttler/createThrottledSignal.cjs.map +0 -1
  123. package/dist/throttler/createThrottledSignal.d.cts +0 -70
  124. package/dist/throttler/createThrottledSignal.js.map +0 -1
  125. package/dist/throttler/createThrottledValue.cjs +0 -74
  126. package/dist/throttler/createThrottledValue.cjs.map +0 -1
  127. package/dist/throttler/createThrottledValue.d.cts +0 -67
  128. package/dist/throttler/createThrottledValue.js.map +0 -1
  129. package/dist/throttler/createThrottler.cjs +0 -132
  130. package/dist/throttler/createThrottler.cjs.map +0 -1
  131. package/dist/throttler/createThrottler.d.cts +0 -147
  132. package/dist/throttler/createThrottler.js.map +0 -1
  133. package/dist/throttler/index.cjs +0 -15
  134. package/dist/throttler/index.d.cts +0 -5
  135. package/dist/types/index.cjs +0 -8
  136. package/dist/types/index.d.cts +0 -1
  137. package/dist/utils/index.cjs +0 -8
  138. package/dist/utils/index.d.cts +0 -1
  139. package/src/async-batcher/createAsyncBatcher.ts +0 -234
  140. package/src/async-batcher/index.ts +0 -3
  141. package/src/async-debouncer/createAsyncDebouncer.ts +0 -225
  142. package/src/async-debouncer/index.ts +0 -4
  143. package/src/async-queuer/createAsyncQueuer.ts +0 -226
  144. package/src/async-queuer/index.ts +0 -3
  145. package/src/async-rate-limiter/createAsyncRateLimiter.ts +0 -269
  146. package/src/async-rate-limiter/index.ts +0 -4
  147. package/src/async-throttler/createAsyncThrottler.ts +0 -225
  148. package/src/async-throttler/index.ts +0 -4
  149. package/src/batcher/createBatcher.ts +0 -199
  150. package/src/batcher/index.ts +0 -3
  151. package/src/debouncer/createDebouncedSignal.ts +0 -92
  152. package/src/debouncer/createDebouncedValue.ts +0 -85
  153. package/src/debouncer/createDebouncer.ts +0 -201
  154. package/src/debouncer/index.ts +0 -6
  155. package/src/index.ts +0 -46
  156. package/src/provider/PacerProvider.tsx +0 -63
  157. package/src/provider/index.ts +0 -1
  158. package/src/queuer/createQueuedSignal.ts +0 -138
  159. package/src/queuer/createQueuer.ts +0 -199
  160. package/src/queuer/index.ts +0 -4
  161. package/src/rate-limiter/createRateLimitedSignal.ts +0 -117
  162. package/src/rate-limiter/createRateLimitedValue.ts +0 -110
  163. package/src/rate-limiter/createRateLimiter.ts +0 -226
  164. package/src/rate-limiter/index.ts +0 -5
  165. package/src/throttler/createThrottledSignal.ts +0 -81
  166. package/src/throttler/createThrottledValue.ts +0 -82
  167. package/src/throttler/createThrottler.ts +0 -204
  168. package/src/throttler/index.ts +0 -6
  169. package/src/types/index.ts +0 -1
  170. package/src/utils/index.ts +0 -1
@@ -1,226 +0,0 @@
1
- import { RateLimiter } from '@tanstack/pacer/rate-limiter'
2
- import { createEffect, onCleanup } from 'solid-js'
3
- import { shallow, useSelector } from '@tanstack/solid-store'
4
- import { useDefaultPacerOptions } from '../provider/PacerProvider'
5
- import type { Store } from '@tanstack/solid-store'
6
- import type { Accessor, JSX } from 'solid-js'
7
- import type { AnyFunction } from '@tanstack/pacer/types'
8
- import type {
9
- RateLimiterOptions,
10
- RateLimiterState,
11
- } from '@tanstack/pacer/rate-limiter'
12
-
13
- export interface SolidRateLimiterOptions<
14
- TFn extends AnyFunction,
15
- TSelected = {},
16
- > extends RateLimiterOptions<TFn> {
17
- /**
18
- * Optional callback invoked when the owning component unmounts. Receives the rate limiter instance.
19
- * When provided, replaces the default cleanup; use it to call reset(), add logging, etc.
20
- */
21
- onUnmount?: (rateLimiter: SolidRateLimiter<TFn, TSelected>) => void
22
- }
23
-
24
- export interface SolidRateLimiter<
25
- TFn extends AnyFunction,
26
- TSelected = {},
27
- > extends Omit<RateLimiter<TFn>, 'store'> {
28
- /**
29
- * A Solid component that allows you to subscribe to the rate limiter state.
30
- *
31
- * This is useful for tracking specific parts of the rate limiter state
32
- * deep in your component tree without needing to pass a selector to the hook.
33
- *
34
- * @example
35
- * <rateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
36
- * {(state) => (
37
- * <div>Rejections: {state().rejectionCount}</div>
38
- * )}
39
- * </rateLimiter.Subscribe>
40
- */
41
- Subscribe: <TSelected>(props: {
42
- selector: (state: RateLimiterState) => TSelected
43
- children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
44
- }) => JSX.Element
45
- /**
46
- * Reactive state that will be updated when the rate limiter state changes
47
- *
48
- * Use this instead of `rateLimiter.store.state`
49
- */
50
- readonly state: Accessor<Readonly<TSelected>>
51
- /**
52
- * @deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state.
53
- * The state on the store object is not reactive, as it has not been wrapped in a `useSelector` hook internally.
54
- * Although, you can make the state reactive by using the `useSelector` in your own usage.
55
- */
56
- readonly store: Store<Readonly<RateLimiterState>>
57
- }
58
-
59
- /**
60
- * A low-level Solid hook that creates a `RateLimiter` instance to enforce rate limits on function execution.
61
- *
62
- * This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that
63
- * you can integrate with any state management solution (createSignal, etc).
64
- *
65
- * Rate limiting is a simple "hard limit" approach that allows executions until a maximum count is reached within
66
- * a time window, then blocks all subsequent calls until the window resets. Unlike throttling or debouncing,
67
- * it does not attempt to space out or collapse executions intelligently.
68
- *
69
- * The rate limiter supports two types of windows:
70
- * - 'fixed': A strict window that resets after the window period. All executions within the window count
71
- * towards the limit, and the window resets completely after the period.
72
- * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
73
- * consistent rate of execution over time.
74
- *
75
- * For smoother execution patterns:
76
- * - Use throttling when you want consistent spacing between executions (e.g. UI updates)
77
- * - Use debouncing when you want to collapse rapid-fire events (e.g. search input)
78
- * - Use rate limiting only when you need to enforce hard limits (e.g. API rate limits)
79
- *
80
- * ## State Management and Selector
81
- *
82
- * The hook uses TanStack Store for reactive state management. You can subscribe to state changes
83
- * in two ways:
84
- *
85
- * **1. Using `rateLimiter.Subscribe` component (Recommended for component tree subscriptions)**
86
- *
87
- * Use the `Subscribe` component to subscribe to state changes deep in your component tree without
88
- * needing to pass a selector to the hook. This is ideal when you want to subscribe to state
89
- * in child components.
90
- *
91
- * **2. Using the `selector` parameter (For hook-level subscriptions)**
92
- *
93
- * The `selector` parameter allows you to specify which state changes will trigger reactive updates
94
- * at the hook level, optimizing performance by preventing unnecessary updates when irrelevant
95
- * state changes occur.
96
- *
97
- * **By default, there will be no reactive state subscriptions** and you must opt-in to state
98
- * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary
99
- * updates and gives you full control over when your component tracks state changes.
100
- *
101
- * Available state properties:
102
- * - `executionCount`: Number of function executions that have been completed
103
- * - `rejectionCount`: Number of function calls that were rejected due to rate limiting
104
- * - `remainingInWindow`: Number of executions remaining in the current window
105
- * - `nextWindowTime`: Timestamp when the next window begins
106
- * - `currentWindowStart`: Timestamp when the current window started
107
- *
108
- * @example
109
- * ```tsx
110
- * // Default behavior - no reactive state subscriptions
111
- * const rateLimiter = createRateLimiter(apiCall, {
112
- * limit: 5,
113
- * window: 60000,
114
- * windowType: 'sliding',
115
- * });
116
- *
117
- * // Subscribe to state changes deep in component tree using Subscribe component
118
- * <rateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
119
- * {(state) => (
120
- * <div>Rejections: {state().rejectionCount}</div>
121
- * )}
122
- * </rateLimiter.Subscribe>
123
- *
124
- * // Opt-in to track execution count changes at hook level (optimized for tracking successful executions)
125
- * const rateLimiter = createRateLimiter(
126
- * apiCall,
127
- * {
128
- * limit: 5,
129
- * window: 60000,
130
- * windowType: 'sliding',
131
- * },
132
- * (state) => ({ executionCount: state.executionCount })
133
- * );
134
- *
135
- * // Opt-in to track rejection count changes (optimized for tracking rate limit violations)
136
- * const rateLimiter = createRateLimiter(
137
- * apiCall,
138
- * {
139
- * limit: 5,
140
- * window: 60000,
141
- * windowType: 'sliding',
142
- * },
143
- * (state) => ({ rejectionCount: state.rejectionCount })
144
- * );
145
- *
146
- * // Opt-in to track execution times changes (optimized for window calculations)
147
- * const rateLimiter = createRateLimiter(
148
- * apiCall,
149
- * {
150
- * limit: 5,
151
- * window: 60000,
152
- * windowType: 'sliding',
153
- * },
154
- * (state) => ({ executionTimes: state.executionTimes })
155
- * );
156
- *
157
- * // Multiple state properties - track when any of these change
158
- * const rateLimiter = createRateLimiter(
159
- * apiCall,
160
- * {
161
- * limit: 5,
162
- * window: 60000,
163
- * windowType: 'sliding',
164
- * },
165
- * (state) => ({
166
- * executionCount: state.executionCount,
167
- * rejectionCount: state.rejectionCount
168
- * })
169
- * );
170
- *
171
- * // Monitor rate limit status
172
- * const handleClick = () => {
173
- * const remaining = rateLimiter.getRemainingInWindow();
174
- * if (remaining > 0) {
175
- * rateLimiter.maybeExecute(data);
176
- * } else {
177
- * showRateLimitWarning();
178
- * }
179
- * };
180
- *
181
- * // Access the selected state (will be empty object {} unless selector provided)
182
- * const { executionCount, rejectionCount } = rateLimiter.state();
183
- * ```
184
- */
185
- export function createRateLimiter<TFn extends AnyFunction, TSelected = {}>(
186
- fn: TFn,
187
- options: SolidRateLimiterOptions<TFn, TSelected>,
188
- selector: (state: RateLimiterState) => TSelected = () => ({}) as TSelected,
189
- ): SolidRateLimiter<TFn, TSelected> {
190
- const mergedOptions = {
191
- ...useDefaultPacerOptions().rateLimiter,
192
- ...options,
193
- } as SolidRateLimiterOptions<TFn, TSelected>
194
- const rateLimiter = new RateLimiter<TFn>(
195
- fn,
196
- mergedOptions,
197
- ) as unknown as SolidRateLimiter<TFn, TSelected>
198
-
199
- rateLimiter.Subscribe = function Subscribe<TSelected>(props: {
200
- selector: (state: RateLimiterState) => TSelected
201
- children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
202
- }) {
203
- const selected = useSelector(rateLimiter.store, props.selector, {
204
- compare: shallow,
205
- })
206
-
207
- return typeof props.children === 'function'
208
- ? props.children(selected)
209
- : props.children
210
- }
211
-
212
- const state = useSelector(rateLimiter.store, selector, { compare: shallow })
213
-
214
- createEffect(() => {
215
- onCleanup(() => {
216
- if (mergedOptions.onUnmount) {
217
- mergedOptions.onUnmount(rateLimiter)
218
- }
219
- })
220
- })
221
-
222
- return {
223
- ...rateLimiter,
224
- state,
225
- } as SolidRateLimiter<TFn, TSelected> // omit `store` in favor of `state`
226
- }
@@ -1,5 +0,0 @@
1
- export * from '@tanstack/pacer/rate-limiter'
2
-
3
- export * from './createRateLimiter'
4
- export * from './createRateLimitedSignal'
5
- export * from './createRateLimitedValue'
@@ -1,81 +0,0 @@
1
- import { createSignal } from 'solid-js'
2
- import { createThrottler } from './createThrottler'
3
- import type { SolidThrottler, SolidThrottlerOptions } from './createThrottler'
4
- import type { Accessor, Setter } from 'solid-js'
5
- import type { ThrottlerState } from '@tanstack/pacer/throttler'
6
-
7
- /**
8
- * A Solid hook that creates a throttled state value that updates at most once within a specified time window.
9
- * This hook combines Solid's createSignal with throttling functionality to provide controlled state updates.
10
- *
11
- * Throttling ensures state updates occur at a controlled rate regardless of how frequently the setter is called.
12
- * This is useful for rate-limiting expensive updates or operations that depend on rapidly changing state.
13
- *
14
- * The hook returns a tuple containing:
15
- * - The throttled state value accessor
16
- * - A throttled setter function that respects the configured wait time
17
- * - The throttler instance for additional control
18
- *
19
- * For more direct control over throttling without state management,
20
- * consider using the lower-level createThrottler hook instead.
21
- *
22
- * ## State Management and Selector
23
- *
24
- * The hook uses TanStack Store for reactive state management via the underlying throttler instance.
25
- * The `selector` parameter allows you to specify which throttler state changes will trigger reactive updates,
26
- * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur.
27
- *
28
- * **By default, there will be no reactive state subscriptions** and you must opt-in to state
29
- * tracking by providing a selector function. This prevents unnecessary reactive updates and gives you
30
- * full control over when your component subscribes to state changes. Only when you provide a selector will
31
- * the reactive system track the selected state values.
32
- *
33
- * Available throttler state properties:
34
- * - `canLeadingExecute`: Whether the throttler can execute on the leading edge
35
- * - `canTrailingExecute`: Whether the throttler can execute on the trailing edge
36
- * - `executionCount`: Number of function executions that have been completed
37
- * - `isPending`: Whether the throttler is waiting for the timeout to trigger trailing execution
38
- * - `lastArgs`: The arguments from the most recent call to maybeExecute
39
- * - `lastExecutionTime`: Unix timestamp of the last execution
40
- * - `nextExecutionTime`: Unix timestamp of the next allowed execution
41
- * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
42
- *
43
- * @example
44
- * ```tsx
45
- * // Default behavior - no reactive state subscriptions
46
- * const [value, setValue, throttler] = createThrottledSignal(0, { wait: 1000 });
47
- *
48
- * // Opt-in to reactive updates when pending state changes (optimized for loading indicators)
49
- * const [value, setValue, throttler] = createThrottledSignal(
50
- * 0,
51
- * { wait: 1000 },
52
- * (state) => ({ isPending: state.isPending })
53
- * );
54
- *
55
- * // With custom leading/trailing behavior
56
- * const [value, setValue] = createThrottledSignal(0, {
57
- * wait: 1000,
58
- * leading: true, // Update immediately on first change
59
- * trailing: false // Skip trailing edge updates
60
- * });
61
- *
62
- * // Access throttler state via signals
63
- * console.log('Executions:', throttler.state().executionCount);
64
- * console.log('Is pending:', throttler.state().isPending);
65
- * console.log('Last execution:', throttler.state().lastExecutionTime);
66
- * console.log('Next execution:', throttler.state().nextExecutionTime);
67
- * ```
68
- */
69
- export function createThrottledSignal<TValue, TSelected = {}>(
70
- value: TValue,
71
- initialOptions: SolidThrottlerOptions<Setter<TValue>, TSelected>,
72
- selector?: (state: ThrottlerState<Setter<TValue>>) => TSelected,
73
- ): [
74
- Accessor<TValue>,
75
- Setter<TValue>,
76
- SolidThrottler<Setter<TValue>, TSelected>,
77
- ] {
78
- const [throttledValue, setThrottledValue] = createSignal<TValue>(value)
79
- const throttler = createThrottler(setThrottledValue, initialOptions, selector)
80
- return [throttledValue, throttler.maybeExecute as Setter<TValue>, throttler]
81
- }
@@ -1,82 +0,0 @@
1
- import { createEffect } from 'solid-js'
2
- import { createThrottledSignal } from './createThrottledSignal'
3
- import type { SolidThrottler, SolidThrottlerOptions } from './createThrottler'
4
- import type { Accessor, Setter } from 'solid-js'
5
- import type { ThrottlerState } from '@tanstack/pacer/throttler'
6
-
7
- /**
8
- * A high-level Solid hook that creates a throttled version of a value that updates at most once within a specified time window.
9
- * This hook uses Solid's createSignal internally to manage the throttled state.
10
- *
11
- * Throttling ensures the value updates occur at a controlled rate regardless of how frequently the input value changes.
12
- * This is useful for rate-limiting expensive updates or API calls that depend on rapidly changing values.
13
- *
14
- * The hook returns a tuple containing:
15
- * - An accessor function that provides the throttled value
16
- * - The throttler instance with control methods
17
- *
18
- * The throttled value will update according to the leading/trailing edge behavior specified in the options.
19
- *
20
- * For more direct control over throttling behavior without Solid state management,
21
- * consider using the lower-level createThrottler hook instead.
22
- *
23
- * ## State Management and Selector
24
- *
25
- * The hook uses TanStack Store for reactive state management via the underlying throttler instance.
26
- * The `selector` parameter allows you to specify which throttler state changes will trigger reactive updates,
27
- * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur.
28
- *
29
- * **By default, there will be no reactive state subscriptions** and you must opt-in to state
30
- * tracking by providing a selector function. This prevents unnecessary reactive updates and gives you
31
- * full control over when your component subscribes to state changes. Only when you provide a selector will
32
- * the reactive system track the selected state values.
33
- *
34
- * Available throttler state properties:
35
- * - `canLeadingExecute`: Whether the throttler can execute on the leading edge
36
- * - `canTrailingExecute`: Whether the throttler can execute on the trailing edge
37
- * - `executionCount`: Number of function executions that have been completed
38
- * - `isPending`: Whether the throttler is waiting for the timeout to trigger trailing execution
39
- * - `lastArgs`: The arguments from the most recent call to maybeExecute
40
- * - `lastExecutionTime`: Unix timestamp of the last execution
41
- * - `nextExecutionTime`: Unix timestamp of the next allowed execution
42
- * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
43
- *
44
- * @example
45
- * ```tsx
46
- * // Default behavior - no reactive state subscriptions
47
- * const [throttledValue, throttler] = createThrottledValue(rawValue, { wait: 1000 });
48
- *
49
- * // Opt-in to reactive updates when pending state changes (optimized for loading indicators)
50
- * const [throttledValue, throttler] = createThrottledValue(
51
- * rawValue,
52
- * { wait: 1000 },
53
- * (state) => ({ isPending: state.isPending })
54
- * );
55
- *
56
- * // Use the throttled value
57
- * console.log(throttledValue()); // Access the current throttled value
58
- *
59
- * // Access throttler state via signals
60
- * console.log('Is pending:', throttler.state().isPending);
61
- *
62
- * // Control the throttler
63
- * throttler.cancel(); // Cancel any pending updates
64
- * ```
65
- */
66
- export function createThrottledValue<TValue, TSelected = {}>(
67
- value: Accessor<TValue>,
68
- initialOptions: SolidThrottlerOptions<Setter<TValue>, TSelected>,
69
- selector?: (state: ThrottlerState<Setter<TValue>>) => TSelected,
70
- ): [Accessor<TValue>, SolidThrottler<Setter<TValue>, TSelected>] {
71
- const [throttledValue, setThrottledValue, throttler] = createThrottledSignal(
72
- value(),
73
- initialOptions,
74
- selector,
75
- )
76
-
77
- createEffect(() => {
78
- setThrottledValue(value() as any)
79
- })
80
-
81
- return [throttledValue, throttler]
82
- }
@@ -1,204 +0,0 @@
1
- import { Throttler } from '@tanstack/pacer/throttler'
2
- import { createEffect, onCleanup } from 'solid-js'
3
- import { shallow, useSelector } from '@tanstack/solid-store'
4
- import { useDefaultPacerOptions } from '../provider/PacerProvider'
5
- import type { Store } from '@tanstack/solid-store'
6
- import type { Accessor, JSX } from 'solid-js'
7
- import type { AnyFunction } from '@tanstack/pacer/types'
8
- import type {
9
- ThrottlerOptions,
10
- ThrottlerState,
11
- } from '@tanstack/pacer/throttler'
12
-
13
- export interface SolidThrottlerOptions<
14
- TFn extends AnyFunction,
15
- TSelected = {},
16
- > extends ThrottlerOptions<TFn> {
17
- /**
18
- * Optional callback invoked when the owning component unmounts. Receives the throttler instance.
19
- * When provided, replaces the default cleanup (cancel); use it to call flush(), reset(), cancel(), add logging, etc.
20
- */
21
- onUnmount?: (throttler: SolidThrottler<TFn, TSelected>) => void
22
- }
23
-
24
- export interface SolidThrottler<
25
- TFn extends AnyFunction,
26
- TSelected = {},
27
- > extends Omit<Throttler<TFn>, 'store'> {
28
- /**
29
- * A Solid component that allows you to subscribe to the throttler state.
30
- *
31
- * This is useful for tracking specific parts of the throttler state
32
- * deep in your component tree without needing to pass a selector to the hook.
33
- *
34
- * @example
35
- * <throttler.Subscribe selector={(state) => ({ isPending: state.isPending })}>
36
- * {(state) => (
37
- * <div>{state().isPending ? 'Loading...' : 'Ready'}</div>
38
- * )}
39
- * </throttler.Subscribe>
40
- */
41
- Subscribe: <TSelected>(props: {
42
- selector: (state: ThrottlerState<TFn>) => TSelected
43
- children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
44
- }) => JSX.Element
45
- /**
46
- * Reactive state that will be updated when the throttler state changes
47
- *
48
- * Use this instead of `throttler.store.state`
49
- */
50
- readonly state: Accessor<Readonly<TSelected>>
51
- /**
52
- * @deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state.
53
- * The state on the store object is not reactive, as it has not been wrapped in a `useSelector` hook internally.
54
- * Although, you can make the state reactive by using the `useSelector` in your own usage.
55
- */
56
- readonly store: Store<Readonly<ThrottlerState<TFn>>>
57
- }
58
-
59
- /**
60
- * A low-level Solid hook that creates a `Throttler` instance that limits how often the provided function can execute.
61
- *
62
- * This hook is designed to be flexible and state-management agnostic - it simply returns a throttler instance that
63
- * you can integrate with any state management solution (createSignal, Redux, Zustand, Jotai, etc). For a simpler and higher-level hook that
64
- * integrates directly with Solid's createSignal, see createThrottledSignal.
65
- *
66
- * Throttling ensures a function executes at most once within a specified time window,
67
- * regardless of how many times it is called. This is useful for rate-limiting
68
- * expensive operations or UI updates.
69
- *
70
- * ## State Management and Selector
71
- *
72
- * The hook uses TanStack Store for reactive state management. You can subscribe to state changes
73
- * in two ways:
74
- *
75
- * **1. Using `throttler.Subscribe` component (Recommended for component tree subscriptions)**
76
- *
77
- * Use the `Subscribe` component to subscribe to state changes deep in your component tree without
78
- * needing to pass a selector to the hook. This is ideal when you want to subscribe to state
79
- * in child components.
80
- *
81
- * **2. Using the `selector` parameter (For hook-level subscriptions)**
82
- *
83
- * The `selector` parameter allows you to specify which state changes will trigger reactive updates
84
- * at the hook level, optimizing performance by preventing unnecessary updates when irrelevant
85
- * state changes occur.
86
- *
87
- * **By default, there will be no reactive state subscriptions** and you must opt-in to state
88
- * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary
89
- * updates and gives you full control over when your component tracks state changes.
90
- *
91
- * Available state properties:
92
- * - `canLeadingExecute`: Whether the throttler can execute on the leading edge
93
- * - `canTrailingExecute`: Whether the throttler can execute on the trailing edge
94
- * - `executionCount`: Number of function executions that have been completed
95
- * - `isPending`: Whether the throttler is waiting for the timeout to trigger execution
96
- * - `lastArgs`: The arguments from the most recent call to maybeExecute
97
- * - `lastExecutionTime`: Timestamp of the last execution
98
- * - `nextExecutionTime`: Timestamp of the next allowed execution
99
- * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
100
- *
101
- * ## Unmount behavior
102
- *
103
- * By default, the primitive cancels any pending execution when the owning component unmounts.
104
- * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
105
- *
106
- * ```tsx
107
- * const throttler = createThrottler(fn, {
108
- * wait: 1000,
109
- * onUnmount: (t) => t.flush()
110
- * });
111
- * ```
112
- *
113
- * @example
114
- * ```tsx
115
- * // Default behavior - no reactive state subscriptions
116
- * const throttler = createThrottler(setValue, { wait: 1000 });
117
- *
118
- * // Subscribe to state changes deep in component tree using Subscribe component
119
- * <throttler.Subscribe selector={(state) => ({ isPending: state.isPending })}>
120
- * {(state) => (
121
- * <div>{state().isPending ? 'Loading...' : 'Ready'}</div>
122
- * )}
123
- * </throttler.Subscribe>
124
- *
125
- * // Opt-in to track isPending changes at hook level (optimized for loading states)
126
- * const throttler = createThrottler(
127
- * setValue,
128
- * { wait: 1000 },
129
- * (state) => ({ isPending: state.isPending })
130
- * );
131
- *
132
- * // Opt-in to track executionCount changes (optimized for tracking execution)
133
- * const throttler = createThrottler(
134
- * setValue,
135
- * { wait: 1000 },
136
- * (state) => ({ executionCount: state.executionCount })
137
- * );
138
- *
139
- * // Multiple state properties - track when any of these change
140
- * const throttler = createThrottler(
141
- * setValue,
142
- * {
143
- * wait: 2000,
144
- * leading: true, // Execute immediately on first call
145
- * trailing: false // Skip trailing edge updates
146
- * },
147
- * (state) => ({
148
- * isPending: state.isPending,
149
- * executionCount: state.executionCount,
150
- * lastExecutionTime: state.lastExecutionTime,
151
- * nextExecutionTime: state.nextExecutionTime
152
- * })
153
- * );
154
- *
155
- * // Access the selected state (will be empty object {} unless selector provided)
156
- * const { isPending, executionCount } = throttler.state();
157
- * ```
158
- */
159
- export function createThrottler<TFn extends AnyFunction, TSelected = {}>(
160
- fn: TFn,
161
- options: SolidThrottlerOptions<TFn, TSelected>,
162
- selector: (state: ThrottlerState<TFn>) => TSelected = () => ({}) as TSelected,
163
- ): SolidThrottler<TFn, TSelected> {
164
- const mergedOptions = {
165
- ...useDefaultPacerOptions().throttler,
166
- ...options,
167
- } as SolidThrottlerOptions<TFn, TSelected>
168
- const asyncThrottler = new Throttler<TFn>(
169
- fn,
170
- mergedOptions,
171
- ) as unknown as SolidThrottler<TFn, TSelected>
172
-
173
- asyncThrottler.Subscribe = function Subscribe<TSelected>(props: {
174
- selector: (state: ThrottlerState<TFn>) => TSelected
175
- children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
176
- }) {
177
- const selected = useSelector(asyncThrottler.store, props.selector, {
178
- compare: shallow,
179
- })
180
-
181
- return typeof props.children === 'function'
182
- ? props.children(selected)
183
- : props.children
184
- }
185
-
186
- const state = useSelector(asyncThrottler.store, selector, {
187
- compare: shallow,
188
- })
189
-
190
- createEffect(() => {
191
- onCleanup(() => {
192
- if (mergedOptions.onUnmount) {
193
- mergedOptions.onUnmount(asyncThrottler)
194
- } else {
195
- asyncThrottler.cancel()
196
- }
197
- })
198
- })
199
-
200
- return {
201
- ...asyncThrottler,
202
- state,
203
- } as SolidThrottler<TFn, TSelected> // omit `store` in favor of `state`
204
- }
@@ -1,6 +0,0 @@
1
- // re-export everything from the core pacer package, BUT ONLY from the throttler module
2
- export * from '@tanstack/pacer/throttler'
3
-
4
- export * from './createThrottledSignal'
5
- export * from './createThrottledValue'
6
- export * from './createThrottler'
@@ -1 +0,0 @@
1
- export * from '@tanstack/pacer/types'
@@ -1 +0,0 @@
1
- export * from '@tanstack/pacer/utils'