@tanstack/solid-pacer 0.17.0 → 0.18.1

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 (105) hide show
  1. package/dist/async-batcher/createAsyncBatcher.cjs +26 -12
  2. package/dist/async-batcher/createAsyncBatcher.cjs.map +1 -1
  3. package/dist/async-batcher/createAsyncBatcher.d.cts +36 -9
  4. package/dist/async-batcher/createAsyncBatcher.d.ts +36 -9
  5. package/dist/async-batcher/createAsyncBatcher.js +22 -8
  6. package/dist/async-batcher/createAsyncBatcher.js.map +1 -1
  7. package/dist/async-batcher/index.cjs +3 -3
  8. package/dist/async-debouncer/createAsyncDebouncer.cjs +26 -12
  9. package/dist/async-debouncer/createAsyncDebouncer.cjs.map +1 -1
  10. package/dist/async-debouncer/createAsyncDebouncer.d.cts +36 -9
  11. package/dist/async-debouncer/createAsyncDebouncer.d.ts +36 -9
  12. package/dist/async-debouncer/createAsyncDebouncer.js +22 -8
  13. package/dist/async-debouncer/createAsyncDebouncer.js.map +1 -1
  14. package/dist/async-debouncer/index.cjs +3 -3
  15. package/dist/async-queuer/createAsyncQueuer.cjs +26 -12
  16. package/dist/async-queuer/createAsyncQueuer.cjs.map +1 -1
  17. package/dist/async-queuer/createAsyncQueuer.d.cts +36 -9
  18. package/dist/async-queuer/createAsyncQueuer.d.ts +36 -9
  19. package/dist/async-queuer/createAsyncQueuer.js +22 -8
  20. package/dist/async-queuer/createAsyncQueuer.js.map +1 -1
  21. package/dist/async-queuer/index.cjs +3 -3
  22. package/dist/async-rate-limiter/createAsyncRateLimiter.cjs +89 -30
  23. package/dist/async-rate-limiter/createAsyncRateLimiter.cjs.map +1 -1
  24. package/dist/async-rate-limiter/createAsyncRateLimiter.d.cts +99 -27
  25. package/dist/async-rate-limiter/createAsyncRateLimiter.d.ts +99 -27
  26. package/dist/async-rate-limiter/createAsyncRateLimiter.js +85 -26
  27. package/dist/async-rate-limiter/createAsyncRateLimiter.js.map +1 -1
  28. package/dist/async-rate-limiter/index.cjs +3 -3
  29. package/dist/async-throttler/createAsyncThrottler.cjs +26 -12
  30. package/dist/async-throttler/createAsyncThrottler.cjs.map +1 -1
  31. package/dist/async-throttler/createAsyncThrottler.d.cts +36 -9
  32. package/dist/async-throttler/createAsyncThrottler.d.ts +36 -9
  33. package/dist/async-throttler/createAsyncThrottler.js +22 -8
  34. package/dist/async-throttler/createAsyncThrottler.js.map +1 -1
  35. package/dist/async-throttler/index.cjs +3 -3
  36. package/dist/batcher/createBatcher.cjs +26 -12
  37. package/dist/batcher/createBatcher.cjs.map +1 -1
  38. package/dist/batcher/createBatcher.d.cts +36 -9
  39. package/dist/batcher/createBatcher.d.ts +36 -9
  40. package/dist/batcher/createBatcher.js +22 -8
  41. package/dist/batcher/createBatcher.js.map +1 -1
  42. package/dist/batcher/index.cjs +3 -3
  43. package/dist/debouncer/createDebouncer.cjs +27 -13
  44. package/dist/debouncer/createDebouncer.cjs.map +1 -1
  45. package/dist/debouncer/createDebouncer.d.cts +37 -10
  46. package/dist/debouncer/createDebouncer.d.ts +37 -10
  47. package/dist/debouncer/createDebouncer.js +23 -9
  48. package/dist/debouncer/createDebouncer.js.map +1 -1
  49. package/dist/debouncer/index.cjs +3 -3
  50. package/dist/index.cjs +3 -3
  51. package/dist/queuer/createQueuedSignal.cjs +8 -8
  52. package/dist/queuer/createQueuedSignal.cjs.map +1 -1
  53. package/dist/queuer/createQueuedSignal.d.cts +8 -8
  54. package/dist/queuer/createQueuedSignal.d.ts +8 -8
  55. package/dist/queuer/createQueuedSignal.js +8 -8
  56. package/dist/queuer/createQueuedSignal.js.map +1 -1
  57. package/dist/queuer/createQueuer.cjs +26 -12
  58. package/dist/queuer/createQueuer.cjs.map +1 -1
  59. package/dist/queuer/createQueuer.d.cts +36 -9
  60. package/dist/queuer/createQueuer.d.ts +36 -9
  61. package/dist/queuer/createQueuer.js +22 -8
  62. package/dist/queuer/createQueuer.js.map +1 -1
  63. package/dist/queuer/index.cjs +3 -3
  64. package/dist/rate-limiter/createRateLimiter.cjs +78 -23
  65. package/dist/rate-limiter/createRateLimiter.cjs.map +1 -1
  66. package/dist/rate-limiter/createRateLimiter.d.cts +88 -20
  67. package/dist/rate-limiter/createRateLimiter.d.ts +88 -20
  68. package/dist/rate-limiter/createRateLimiter.js +74 -19
  69. package/dist/rate-limiter/createRateLimiter.js.map +1 -1
  70. package/dist/rate-limiter/index.cjs +3 -3
  71. package/dist/throttler/createThrottledSignal.cjs +1 -1
  72. package/dist/throttler/createThrottledSignal.cjs.map +1 -1
  73. package/dist/throttler/createThrottledSignal.d.cts +1 -1
  74. package/dist/throttler/createThrottledSignal.d.ts +1 -1
  75. package/dist/throttler/createThrottledSignal.js +1 -1
  76. package/dist/throttler/createThrottledSignal.js.map +1 -1
  77. package/dist/throttler/createThrottledValue.cjs +1 -1
  78. package/dist/throttler/createThrottledValue.cjs.map +1 -1
  79. package/dist/throttler/createThrottledValue.d.cts +1 -1
  80. package/dist/throttler/createThrottledValue.d.ts +1 -1
  81. package/dist/throttler/createThrottledValue.js +1 -1
  82. package/dist/throttler/createThrottledValue.js.map +1 -1
  83. package/dist/throttler/createThrottler.cjs +34 -13
  84. package/dist/throttler/createThrottler.cjs.map +1 -1
  85. package/dist/throttler/createThrottler.d.cts +44 -10
  86. package/dist/throttler/createThrottler.d.ts +44 -10
  87. package/dist/throttler/createThrottler.js +30 -9
  88. package/dist/throttler/createThrottler.js.map +1 -1
  89. package/dist/throttler/index.cjs +3 -3
  90. package/dist/types/index.cjs +3 -3
  91. package/dist/utils/index.cjs +3 -3
  92. package/package.json +2 -2
  93. package/src/async-batcher/createAsyncBatcher.ts +51 -10
  94. package/src/async-debouncer/createAsyncDebouncer.ts +51 -10
  95. package/src/async-queuer/createAsyncQueuer.ts +51 -10
  96. package/src/async-rate-limiter/createAsyncRateLimiter.ts +114 -28
  97. package/src/async-throttler/createAsyncThrottler.ts +51 -10
  98. package/src/batcher/createBatcher.ts +51 -10
  99. package/src/debouncer/createDebouncer.ts +52 -11
  100. package/src/queuer/createQueuedSignal.ts +8 -8
  101. package/src/queuer/createQueuer.ts +51 -10
  102. package/src/rate-limiter/createRateLimiter.ts +103 -21
  103. package/src/throttler/createThrottledSignal.ts +1 -1
  104. package/src/throttler/createThrottledValue.ts +1 -1
  105. package/src/throttler/createThrottler.ts +59 -11
@@ -2,7 +2,7 @@ import { AsyncQueuer } from '@tanstack/pacer/async-queuer'
2
2
  import { useStore } from '@tanstack/solid-store'
3
3
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
4
4
  import type { Store } from '@tanstack/solid-store'
5
- import type { Accessor } from 'solid-js'
5
+ import type { Accessor, JSX } from 'solid-js'
6
6
  import type {
7
7
  AsyncQueuerOptions,
8
8
  AsyncQueuerState,
@@ -12,6 +12,23 @@ export interface SolidAsyncQueuer<TValue, TSelected = {}> extends Omit<
12
12
  AsyncQueuer<TValue>,
13
13
  'store'
14
14
  > {
15
+ /**
16
+ * A Solid component that allows you to subscribe to the queuer state.
17
+ *
18
+ * This is useful for tracking specific parts of the queuer state
19
+ * deep in your component tree without needing to pass a selector to the hook.
20
+ *
21
+ * @example
22
+ * <queuer.Subscribe selector={(state) => ({ pendingItems: state.pendingItems, activeItems: state.activeItems })}>
23
+ * {(state) => (
24
+ * <div>Pending: {state().pendingItems.length}, Active: {state().activeItems.length}</div>
25
+ * )}
26
+ * </queuer.Subscribe>
27
+ */
28
+ Subscribe: <TSelected>(props: {
29
+ selector: (state: AsyncQueuerState<TValue>) => TSelected
30
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
31
+ }) => JSX.Element
15
32
  /**
16
33
  * Reactive state that will be updated when the queuer state changes
17
34
  *
@@ -51,14 +68,24 @@ export interface SolidAsyncQueuer<TValue, TSelected = {}> extends Omit<
51
68
  *
52
69
  * ## State Management and Selector
53
70
  *
54
- * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
55
- * to specify which state changes will trigger a re-render, optimizing performance by preventing
56
- * unnecessary re-renders when irrelevant state changes occur.
71
+ * The hook uses TanStack Store for reactive state management. You can subscribe to state changes
72
+ * in two ways:
73
+ *
74
+ * **1. Using `queuer.Subscribe` component (Recommended for component tree subscriptions)**
75
+ *
76
+ * Use the `Subscribe` component to subscribe to state changes deep in your component tree without
77
+ * needing to pass a selector to the hook. This is ideal when you want to subscribe to state
78
+ * in child components.
79
+ *
80
+ * **2. Using the `selector` parameter (For hook-level subscriptions)**
81
+ *
82
+ * The `selector` parameter allows you to specify which state changes will trigger reactive updates
83
+ * at the hook level, optimizing performance by preventing unnecessary updates when irrelevant
84
+ * state changes occur.
57
85
  *
58
86
  * **By default, there will be no reactive state subscriptions** and you must opt-in to state
59
- * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
60
- * full control over when your component updates. Only when you provide a selector will the
61
- * component re-render when the selected state values change.
87
+ * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary
88
+ * updates and gives you full control over when your component tracks state changes.
62
89
  *
63
90
  * Available state properties:
64
91
  * - `activeItems`: Array of items currently being processed
@@ -88,7 +115,7 @@ export interface SolidAsyncQueuer<TValue, TSelected = {}> extends Omit<
88
115
  * }
89
116
  * });
90
117
  *
91
- * // Opt-in to re-render when queue state changes (optimized for UI updates)
118
+ * // Opt-in to track queue state changes (optimized for UI updates)
92
119
  * const asyncQueuer = createAsyncQueuer(
93
120
  * async (item) => await fetchData(item),
94
121
  * { concurrency: 2, started: true },
@@ -99,7 +126,7 @@ export interface SolidAsyncQueuer<TValue, TSelected = {}> extends Omit<
99
126
  * })
100
127
  * );
101
128
  *
102
- * // Opt-in to re-render when processing metrics change (optimized for tracking progress)
129
+ * // Opt-in to track processing metrics changes (optimized for tracking progress)
103
130
  * const asyncQueuer = createAsyncQueuer(
104
131
  * async (item) => await fetchData(item),
105
132
  * { concurrency: 2, started: true },
@@ -131,7 +158,21 @@ export function createAsyncQueuer<TValue, TSelected = {}>(
131
158
  ...options,
132
159
  } as AsyncQueuerOptions<TValue>
133
160
 
134
- const asyncQueuer = new AsyncQueuer<TValue>(fn, mergedOptions)
161
+ const asyncQueuer = new AsyncQueuer<TValue>(
162
+ fn,
163
+ mergedOptions,
164
+ ) as unknown as SolidAsyncQueuer<TValue, TSelected>
165
+
166
+ asyncQueuer.Subscribe = function Subscribe<TSelected>(props: {
167
+ selector: (state: AsyncQueuerState<TValue>) => TSelected
168
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
169
+ }) {
170
+ const selected = useStore(asyncQueuer.store, props.selector)
171
+
172
+ return typeof props.children === 'function'
173
+ ? props.children(selected)
174
+ : props.children
175
+ }
135
176
 
136
177
  const state = useStore(asyncQueuer.store, selector)
137
178
 
@@ -2,7 +2,7 @@ import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter'
2
2
  import { useStore } from '@tanstack/solid-store'
3
3
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
4
4
  import type { Store } from '@tanstack/solid-store'
5
- import type { Accessor } from 'solid-js'
5
+ import type { Accessor, JSX } from 'solid-js'
6
6
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
7
7
  import type {
8
8
  AsyncRateLimiterOptions,
@@ -13,6 +13,23 @@ export interface SolidAsyncRateLimiter<
13
13
  TFn extends AnyAsyncFunction,
14
14
  TSelected = {},
15
15
  > extends Omit<AsyncRateLimiter<TFn>, 'store'> {
16
+ /**
17
+ * A Solid component that allows you to subscribe to the rate limiter state.
18
+ *
19
+ * This is useful for tracking specific parts of the rate limiter state
20
+ * deep in your component tree without needing to pass a selector to the hook.
21
+ *
22
+ * @example
23
+ * <rateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}>
24
+ * {(state) => (
25
+ * <div>Rejected: {state().rejectionCount}, {state().isExecuting ? 'Executing' : 'Idle'}</div>
26
+ * )}
27
+ * </rateLimiter.Subscribe>
28
+ */
29
+ Subscribe: <TSelected>(props: {
30
+ selector: (state: AsyncRateLimiterState<TFn>) => TSelected
31
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
32
+ }) => JSX.Element
16
33
  /**
17
34
  * Reactive state that will be updated when the rate limiter state changes
18
35
  *
@@ -64,14 +81,24 @@ export interface SolidAsyncRateLimiter<
64
81
  *
65
82
  * ## State Management and Selector
66
83
  *
67
- * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
68
- * to specify which state changes will trigger a re-render, optimizing performance by preventing
69
- * unnecessary re-renders when irrelevant state changes occur.
84
+ * The hook uses TanStack Store for reactive state management. You can subscribe to state changes
85
+ * in two ways:
86
+ *
87
+ * **1. Using `rateLimiter.Subscribe` component (Recommended for component tree subscriptions)**
88
+ *
89
+ * Use the `Subscribe` component to subscribe to state changes deep in your component tree without
90
+ * needing to pass a selector to the hook. This is ideal when you want to subscribe to state
91
+ * in child components.
92
+ *
93
+ * **2. Using the `selector` parameter (For hook-level subscriptions)**
94
+ *
95
+ * The `selector` parameter allows you to specify which state changes will trigger reactive updates
96
+ * at the hook level, optimizing performance by preventing unnecessary updates when irrelevant
97
+ * state changes occur.
70
98
  *
71
99
  * **By default, there will be no reactive state subscriptions** and you must opt-in to state
72
- * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
73
- * full control over when your component updates. Only when you provide a selector will the
74
- * component re-render when the selected state values change.
100
+ * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary
101
+ * updates and gives you full control over when your component tracks state changes.
75
102
  *
76
103
  * Available state properties:
77
104
  * - `currentWindowStart`: Timestamp when the current window started
@@ -87,7 +114,7 @@ export interface SolidAsyncRateLimiter<
87
114
  * @example
88
115
  * ```tsx
89
116
  * // Default behavior - no reactive state subscriptions
90
- * const { maybeExecute } = createAsyncRateLimiter(
117
+ * const asyncRateLimiter = createAsyncRateLimiter(
91
118
  * async (id: string) => {
92
119
  * const data = await api.fetchData(id);
93
120
  * return data; // Return value is preserved
@@ -95,36 +122,81 @@ export interface SolidAsyncRateLimiter<
95
122
  * { limit: 5, window: 1000 } // 5 calls per second
96
123
  * );
97
124
  *
98
- * // Opt-in to re-render when rate limit and execution state changes (optimized for UI feedback)
99
- * const rateLimiter = createAsyncRateLimiter(
100
- * async (query) => {
101
- * const result = await searchAPI(query);
102
- * return result;
125
+ * // Subscribe to state changes deep in component tree using Subscribe component
126
+ * <asyncRateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}>
127
+ * {(state) => (
128
+ * <div>Rejected: {state().rejectionCount}, {state().isExecuting ? 'Executing' : 'Idle'}</div>
129
+ * )}
130
+ * </asyncRateLimiter.Subscribe>
131
+ *
132
+ * // Opt-in to track execution state changes at hook level (optimized for loading indicators)
133
+ * const asyncRateLimiter = createAsyncRateLimiter(
134
+ * async (id: string) => {
135
+ * const data = await api.fetchData(id);
136
+ * return data;
103
137
  * },
104
- * { limit: 10, window: 60000 },
138
+ * { limit: 5, window: 1000 },
139
+ * (state) => ({ isExecuting: state.isExecuting })
140
+ * );
141
+ *
142
+ * // Opt-in to track results when available (optimized for data display)
143
+ * const asyncRateLimiter = createAsyncRateLimiter(
144
+ * async (id: string) => {
145
+ * const data = await api.fetchData(id);
146
+ * return data;
147
+ * },
148
+ * { limit: 5, window: 1000 },
105
149
  * (state) => ({
106
- * remainingInWindow: state.remainingInWindow,
107
- * isExecuting: state.isExecuting,
108
- * rejectionCount: state.rejectionCount
150
+ * lastResult: state.lastResult,
151
+ * successCount: state.successCount
109
152
  * })
110
153
  * );
111
154
  *
112
- * // Opt-in to re-render when error state changes (optimized for error handling)
113
- * const rateLimiter = createAsyncRateLimiter(
114
- * async (query) => {
115
- * const result = await searchAPI(query);
116
- * return result;
155
+ * // Opt-in to track error/rejection state changes (optimized for error handling)
156
+ * const asyncRateLimiter = createAsyncRateLimiter(
157
+ * async (id: string) => {
158
+ * const data = await api.fetchData(id);
159
+ * return data;
117
160
  * },
118
161
  * {
119
- * limit: 10,
120
- * window: 60000, // 10 calls per minute
121
- * onReject: (info) => console.log(`Rate limit exceeded: ${info.nextValidTime - Date.now()}ms until next window`)
162
+ * limit: 5,
163
+ * window: 1000,
164
+ * onError: (error) => console.error('API call failed:', error),
165
+ * onReject: (rateLimiter) => console.log('Rate limit exceeded')
122
166
  * },
123
- * (state) => ({ hasError: state.hasError, lastError: state.lastError })
167
+ * (state) => ({
168
+ * errorCount: state.errorCount,
169
+ * rejectionCount: state.rejectionCount
170
+ * })
171
+ * );
172
+ *
173
+ * // Opt-in to track execution metrics changes (optimized for stats display)
174
+ * const asyncRateLimiter = createAsyncRateLimiter(
175
+ * async (id: string) => {
176
+ * const data = await api.fetchData(id);
177
+ * return data;
178
+ * },
179
+ * { limit: 5, window: 1000 },
180
+ * (state) => ({
181
+ * successCount: state.successCount,
182
+ * errorCount: state.errorCount,
183
+ * settleCount: state.settleCount,
184
+ * rejectionCount: state.rejectionCount
185
+ * })
186
+ * );
187
+ *
188
+ * // Opt-in to track execution times changes (optimized for window calculations)
189
+ * const asyncRateLimiter = createAsyncRateLimiter(
190
+ * async (id: string) => {
191
+ * const data = await api.fetchData(id);
192
+ * return data;
193
+ * },
194
+ * { limit: 5, window: 1000 },
195
+ * (state) => ({ executionTimes: state.executionTimes })
124
196
  * );
125
197
  *
126
198
  * // Access the selected state (will be empty object {} unless selector provided)
127
- * const { remainingInWindow, isExecuting } = rateLimiter.state();
199
+ * const { isExecuting, lastResult, rejectionCount } = asyncRateLimiter.state();
128
200
  * ```
129
201
  */
130
202
  export function createAsyncRateLimiter<
@@ -141,7 +213,21 @@ export function createAsyncRateLimiter<
141
213
  ...options,
142
214
  } as AsyncRateLimiterOptions<TFn>
143
215
 
144
- const asyncRateLimiter = new AsyncRateLimiter<TFn>(fn, mergedOptions)
216
+ const asyncRateLimiter = new AsyncRateLimiter<TFn>(
217
+ fn,
218
+ mergedOptions,
219
+ ) as unknown as SolidAsyncRateLimiter<TFn, TSelected>
220
+
221
+ asyncRateLimiter.Subscribe = function Subscribe<TSelected>(props: {
222
+ selector: (state: AsyncRateLimiterState<TFn>) => TSelected
223
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
224
+ }) {
225
+ const selected = useStore(asyncRateLimiter.store, props.selector)
226
+
227
+ return typeof props.children === 'function'
228
+ ? props.children(selected)
229
+ : props.children
230
+ }
145
231
 
146
232
  const state = useStore(asyncRateLimiter.store, selector)
147
233
 
@@ -2,7 +2,7 @@ import { AsyncThrottler } from '@tanstack/pacer/async-throttler'
2
2
  import { useStore } from '@tanstack/solid-store'
3
3
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
4
4
  import type { Store } from '@tanstack/solid-store'
5
- import type { Accessor } from 'solid-js'
5
+ import type { Accessor, JSX } from 'solid-js'
6
6
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
7
7
  import type {
8
8
  AsyncThrottlerOptions,
@@ -13,6 +13,23 @@ export interface SolidAsyncThrottler<
13
13
  TFn extends AnyAsyncFunction,
14
14
  TSelected = {},
15
15
  > extends Omit<AsyncThrottler<TFn>, 'store'> {
16
+ /**
17
+ * A Solid component that allows you to subscribe to the throttler state.
18
+ *
19
+ * This is useful for tracking specific parts of the throttler state
20
+ * deep in your component tree without needing to pass a selector to the hook.
21
+ *
22
+ * @example
23
+ * <throttler.Subscribe selector={(state) => ({ isPending: state.isPending, isExecuting: state.isExecuting })}>
24
+ * {(state) => (
25
+ * <div>{state().isPending ? 'Pending...' : state().isExecuting ? 'Executing...' : 'Ready'}</div>
26
+ * )}
27
+ * </throttler.Subscribe>
28
+ */
29
+ Subscribe: <TSelected>(props: {
30
+ selector: (state: AsyncThrottlerState<TFn>) => TSelected
31
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
32
+ }) => JSX.Element
16
33
  /**
17
34
  * Reactive state that will be updated when the throttler state changes
18
35
  *
@@ -50,14 +67,24 @@ export interface SolidAsyncThrottler<
50
67
  *
51
68
  * ## State Management and Selector
52
69
  *
53
- * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
54
- * to specify which state changes will trigger a re-render, optimizing performance by preventing
55
- * unnecessary re-renders when irrelevant state changes occur.
70
+ * The hook uses TanStack Store for reactive state management. You can subscribe to state changes
71
+ * in two ways:
72
+ *
73
+ * **1. Using `throttler.Subscribe` component (Recommended for component tree subscriptions)**
74
+ *
75
+ * Use the `Subscribe` component to subscribe to state changes deep in your component tree without
76
+ * needing to pass a selector to the hook. This is ideal when you want to subscribe to state
77
+ * in child components.
78
+ *
79
+ * **2. Using the `selector` parameter (For hook-level subscriptions)**
80
+ *
81
+ * The `selector` parameter allows you to specify which state changes will trigger reactive updates
82
+ * at the hook level, optimizing performance by preventing unnecessary updates when irrelevant
83
+ * state changes occur.
56
84
  *
57
85
  * **By default, there will be no reactive state subscriptions** and you must opt-in to state
58
- * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
59
- * full control over when your component updates. Only when you provide a selector will the
60
- * component re-render when the selected state values change.
86
+ * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary
87
+ * updates and gives you full control over when your component tracks state changes.
61
88
  *
62
89
  * Available state properties:
63
90
  * - `canLeadingExecute`: Whether the throttler can execute on the leading edge
@@ -84,7 +111,7 @@ export interface SolidAsyncThrottler<
84
111
  * { wait: 1000 }
85
112
  * );
86
113
  *
87
- * // Opt-in to re-render when isPending or isExecuting changes (optimized for loading states)
114
+ * // Opt-in to track isPending or isExecuting changes (optimized for loading states)
88
115
  * const throttler = createAsyncThrottler(
89
116
  * async (query) => {
90
117
  * const result = await searchAPI(query);
@@ -94,7 +121,7 @@ export interface SolidAsyncThrottler<
94
121
  * (state) => ({ isPending: state.isPending, isExecuting: state.isExecuting })
95
122
  * );
96
123
  *
97
- * // Opt-in to re-render when error state changes (optimized for error handling)
124
+ * // Opt-in to track error state changes (optimized for error handling)
98
125
  * const throttler = createAsyncThrottler(
99
126
  * async (query) => {
100
127
  * const result = await searchAPI(query);
@@ -129,7 +156,21 @@ export function createAsyncThrottler<
129
156
  ...options,
130
157
  } as AsyncThrottlerOptions<TFn>
131
158
 
132
- const asyncThrottler = new AsyncThrottler(fn, mergedOptions)
159
+ const asyncThrottler = new AsyncThrottler(
160
+ fn,
161
+ mergedOptions,
162
+ ) as unknown as SolidAsyncThrottler<TFn, TSelected>
163
+
164
+ asyncThrottler.Subscribe = function Subscribe<TSelected>(props: {
165
+ selector: (state: AsyncThrottlerState<TFn>) => TSelected
166
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
167
+ }) {
168
+ const selected = useStore(asyncThrottler.store, props.selector)
169
+
170
+ return typeof props.children === 'function'
171
+ ? props.children(selected)
172
+ : props.children
173
+ }
133
174
 
134
175
  const state = useStore(asyncThrottler.store, selector)
135
176
 
@@ -2,13 +2,30 @@ import { Batcher } from '@tanstack/pacer/batcher'
2
2
  import { useStore } from '@tanstack/solid-store'
3
3
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
4
4
  import type { Store } from '@tanstack/solid-store'
5
- import type { Accessor } from 'solid-js'
5
+ import type { Accessor, JSX } from 'solid-js'
6
6
  import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher'
7
7
 
8
8
  export interface SolidBatcher<TValue, TSelected = {}> extends Omit<
9
9
  Batcher<TValue>,
10
10
  'store'
11
11
  > {
12
+ /**
13
+ * A Solid component that allows you to subscribe to the batcher state.
14
+ *
15
+ * This is useful for tracking specific parts of the batcher state
16
+ * deep in your component tree without needing to pass a selector to the hook.
17
+ *
18
+ * @example
19
+ * <batcher.Subscribe selector={(state) => ({ size: state.size, isRunning: state.isRunning })}>
20
+ * {(state) => (
21
+ * <div>Batch: {state().size} items, {state().isRunning ? 'Processing' : 'Idle'}</div>
22
+ * )}
23
+ * </batcher.Subscribe>
24
+ */
25
+ Subscribe: <TSelected>(props: {
26
+ selector: (state: BatcherState<TValue>) => TSelected
27
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
28
+ }) => JSX.Element
12
29
  /**
13
30
  * Reactive state that will be updated when the batcher state changes
14
31
  *
@@ -40,14 +57,24 @@ export interface SolidBatcher<TValue, TSelected = {}> extends Omit<
40
57
  *
41
58
  * ## State Management and Selector
42
59
  *
43
- * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
44
- * to specify which state changes will trigger a re-render, optimizing performance by preventing
45
- * unnecessary re-renders when irrelevant state changes occur.
60
+ * The hook uses TanStack Store for reactive state management. You can subscribe to state changes
61
+ * in two ways:
62
+ *
63
+ * **1. Using `batcher.Subscribe` component (Recommended for component tree subscriptions)**
64
+ *
65
+ * Use the `Subscribe` component to subscribe to state changes deep in your component tree without
66
+ * needing to pass a selector to the hook. This is ideal when you want to subscribe to state
67
+ * in child components.
68
+ *
69
+ * **2. Using the `selector` parameter (For hook-level subscriptions)**
70
+ *
71
+ * The `selector` parameter allows you to specify which state changes will trigger reactive updates
72
+ * at the hook level, optimizing performance by preventing unnecessary updates when irrelevant
73
+ * state changes occur.
46
74
  *
47
75
  * **By default, there will be no reactive state subscriptions** and you must opt-in to state
48
- * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
49
- * full control over when your component updates. Only when you provide a selector will the
50
- * component re-render when the selected state values change.
76
+ * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary
77
+ * updates and gives you full control over when your component tracks state changes.
51
78
  *
52
79
  * Available state properties:
53
80
  * - `executionCount`: Number of batch executions that have been completed
@@ -71,14 +98,14 @@ export interface SolidBatcher<TValue, TSelected = {}> extends Omit<
71
98
  * }
72
99
  * );
73
100
  *
74
- * // Opt-in to re-render when items or isRunning changes (optimized for UI updates)
101
+ * // Opt-in to track items or isRunning changes (optimized for UI updates)
75
102
  * const batcher = createBatcher(
76
103
  * (items) => console.log('Processing batch:', items),
77
104
  * { maxSize: 5, wait: 2000 },
78
105
  * (state) => ({ items: state.items, isRunning: state.isRunning })
79
106
  * );
80
107
  *
81
- * // Opt-in to re-render when execution metrics change (optimized for tracking progress)
108
+ * // Opt-in to track execution metrics changes (optimized for tracking progress)
82
109
  * const batcher = createBatcher(
83
110
  * (items) => console.log('Processing batch:', items),
84
111
  * { maxSize: 5, wait: 2000 },
@@ -111,7 +138,21 @@ export function createBatcher<TValue, TSelected = {}>(
111
138
  ...options,
112
139
  } as BatcherOptions<TValue>
113
140
 
114
- const batcher = new Batcher(fn, mergedOptions)
141
+ const batcher = new Batcher(fn, mergedOptions) as unknown as SolidBatcher<
142
+ TValue,
143
+ TSelected
144
+ >
145
+
146
+ batcher.Subscribe = function Subscribe<TSelected>(props: {
147
+ selector: (state: BatcherState<TValue>) => TSelected
148
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
149
+ }) {
150
+ const selected = useStore(batcher.store, props.selector)
151
+
152
+ return typeof props.children === 'function'
153
+ ? props.children(selected)
154
+ : props.children
155
+ }
115
156
 
116
157
  const state = useStore(batcher.store, selector)
117
158
  return {
@@ -3,7 +3,7 @@ import { createEffect, onCleanup } from 'solid-js'
3
3
  import { useStore } from '@tanstack/solid-store'
4
4
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
5
5
  import type { Store } from '@tanstack/solid-store'
6
- import type { Accessor } from 'solid-js'
6
+ import type { Accessor, JSX } from 'solid-js'
7
7
  import type { AnyFunction } from '@tanstack/pacer/types'
8
8
  import type {
9
9
  DebouncerOptions,
@@ -14,6 +14,23 @@ export interface SolidDebouncer<
14
14
  TFn extends AnyFunction,
15
15
  TSelected = {},
16
16
  > extends Omit<Debouncer<TFn>, 'store'> {
17
+ /**
18
+ * A Solid component that allows you to subscribe to the debouncer state.
19
+ *
20
+ * This is useful for tracking specific parts of the debouncer state
21
+ * deep in your component tree without needing to pass a selector to the hook.
22
+ *
23
+ * @example
24
+ * <debouncer.Subscribe selector={(state) => ({ isPending: state.isPending })}>
25
+ * {(state) => (
26
+ * <div>{state().isPending ? 'Waiting...' : 'Ready'}</div>
27
+ * )}
28
+ * </debouncer.Subscribe>
29
+ */
30
+ Subscribe: <TSelected>(props: {
31
+ selector: (state: DebouncerState<TFn>) => TSelected
32
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
33
+ }) => JSX.Element
17
34
  /**
18
35
  * Reactive state that will be updated when the debouncer state changes
19
36
  *
@@ -45,14 +62,24 @@ export interface SolidDebouncer<
45
62
  *
46
63
  * ## State Management and Selector
47
64
  *
48
- * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
49
- * to specify which state changes will trigger a re-render, optimizing performance by preventing
50
- * unnecessary re-renders when irrelevant state changes occur.
65
+ * The hook uses TanStack Store for reactive state management. You can subscribe to state changes
66
+ * in two ways:
67
+ *
68
+ * **1. Using `debouncer.Subscribe` component (Recommended for component tree subscriptions)**
69
+ *
70
+ * Use the `Subscribe` component to subscribe to state changes deep in your component tree without
71
+ * needing to pass a selector to the hook. This is ideal when you want to subscribe to state
72
+ * in child components.
73
+ *
74
+ * **2. Using the `selector` parameter (For hook-level subscriptions)**
75
+ *
76
+ * The `selector` parameter allows you to specify which state changes will trigger reactive updates
77
+ * at the hook level, optimizing performance by preventing unnecessary updates when irrelevant
78
+ * state changes occur.
51
79
  *
52
80
  * **By default, there will be no reactive state subscriptions** and you must opt-in to state
53
- * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
54
- * full control over when your component updates. Only when you provide a selector will the
55
- * component re-render when the selected state values change.
81
+ * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary
82
+ * updates and gives you full control over when your component tracks state changes.
56
83
  *
57
84
  * Available state properties:
58
85
  * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge
@@ -69,21 +96,21 @@ export interface SolidDebouncer<
69
96
  * { wait: 500 }
70
97
  * );
71
98
  *
72
- * // Opt-in to re-render when isPending changes (optimized for loading states)
99
+ * // Opt-in to track isPending changes (optimized for loading states)
73
100
  * const debouncer = createDebouncer(
74
101
  * (query: string) => fetchSearchResults(query),
75
102
  * { wait: 500 },
76
103
  * (state) => ({ isPending: state.isPending })
77
104
  * );
78
105
  *
79
- * // Opt-in to re-render when executionCount changes (optimized for tracking execution)
106
+ * // Opt-in to track executionCount changes (optimized for tracking execution)
80
107
  * const debouncer = createDebouncer(
81
108
  * (query: string) => fetchSearchResults(query),
82
109
  * { wait: 500 },
83
110
  * (state) => ({ executionCount: state.executionCount })
84
111
  * );
85
112
  *
86
- * // Multiple state properties - re-render when any of these change
113
+ * // Multiple state properties - track when any of these change
87
114
  * const debouncer = createDebouncer(
88
115
  * (query: string) => fetchSearchResults(query),
89
116
  * { wait: 500 },
@@ -113,7 +140,21 @@ export function createDebouncer<TFn extends AnyFunction, TSelected = {}>(
113
140
  ...options,
114
141
  } as DebouncerOptions<TFn>
115
142
 
116
- const asyncDebouncer = new Debouncer<TFn>(fn, mergedOptions)
143
+ const asyncDebouncer = new Debouncer<TFn>(
144
+ fn,
145
+ mergedOptions,
146
+ ) as unknown as SolidDebouncer<TFn, TSelected>
147
+
148
+ asyncDebouncer.Subscribe = function Subscribe<TSelected>(props: {
149
+ selector: (state: DebouncerState<TFn>) => TSelected
150
+ children: ((state: Accessor<TSelected>) => JSX.Element) | JSX.Element
151
+ }) {
152
+ const selected = useStore(asyncDebouncer.store, props.selector)
153
+
154
+ return typeof props.children === 'function'
155
+ ? props.children(selected)
156
+ : props.children
157
+ }
117
158
 
118
159
  const state = useStore(asyncDebouncer.store, selector)
119
160
 
@@ -20,13 +20,13 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
20
20
  * ## State Management and Selector
21
21
  *
22
22
  * The primitive uses Solid's reactive state management via the underlying queuer instance.
23
- * The `selector` parameter allows you to specify which queuer state changes will trigger a re-render,
24
- * optimizing performance by preventing unnecessary re-renders when irrelevant state changes occur.
23
+ * The `selector` parameter allows you to specify which queuer state changes will trigger reactive updates,
24
+ * optimizing performance by preventing unnecessary updates when irrelevant state changes occur.
25
25
  *
26
26
  * **By default, there will be no reactive state subscriptions** and you must opt-in to state
27
- * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
28
- * full control over when your component updates. Only when you provide a selector will the
29
- * component re-render when the selected state values change.
27
+ * tracking by providing a selector function. This prevents unnecessary updates and gives you
28
+ * full control over when your component tracks state changes. Only when you provide a selector will the
29
+ * component track changes to the selected state values.
30
30
  *
31
31
  * Available queuer state properties:
32
32
  * - `executionCount`: Number of items that have been processed by the queuer
@@ -55,7 +55,7 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
55
55
  * }
56
56
  * );
57
57
  *
58
- * // Opt-in to re-render when queue contents change (optimized for displaying queue items)
58
+ * // Opt-in to track queue contents changes (optimized for displaying queue items)
59
59
  * const [items, addItem, queue] = createQueuedSignal(
60
60
  * (item) => console.log('Processing:', item),
61
61
  * { started: true, wait: 1000 },
@@ -66,7 +66,7 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
66
66
  * })
67
67
  * );
68
68
  *
69
- * // Opt-in to re-render when processing state changes (optimized for loading indicators)
69
+ * // Opt-in to track processing state changes (optimized for loading indicators)
70
70
  * const [items, addItem, queue] = createQueuedSignal(
71
71
  * (item) => console.log('Processing:', item),
72
72
  * { started: true, wait: 1000 },
@@ -78,7 +78,7 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
78
78
  * })
79
79
  * );
80
80
  *
81
- * // Opt-in to re-render when execution metrics change (optimized for stats display)
81
+ * // Opt-in to track execution metrics changes (optimized for stats display)
82
82
  * const [items, addItem, queue] = createQueuedSignal(
83
83
  * (item) => console.log('Processing:', item),
84
84
  * { started: true, wait: 1000 },