@tanstack/solid-pacer 0.9.1 → 0.11.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 (101) hide show
  1. package/dist/cjs/async-batcher/createAsyncBatcher.cjs +1 -1
  2. package/dist/cjs/async-batcher/createAsyncBatcher.cjs.map +1 -1
  3. package/dist/cjs/async-batcher/createAsyncBatcher.d.cts +54 -6
  4. package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs +1 -1
  5. package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs.map +1 -1
  6. package/dist/cjs/async-debouncer/createAsyncDebouncer.d.cts +50 -8
  7. package/dist/cjs/async-queuer/createAsyncQueuer.cjs +1 -1
  8. package/dist/cjs/async-queuer/createAsyncQueuer.cjs.map +1 -1
  9. package/dist/cjs/async-queuer/createAsyncQueuer.d.cts +55 -6
  10. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs +1 -1
  11. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs.map +1 -1
  12. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.d.cts +59 -9
  13. package/dist/cjs/async-throttler/createAsyncThrottler.cjs +1 -1
  14. package/dist/cjs/async-throttler/createAsyncThrottler.cjs.map +1 -1
  15. package/dist/cjs/async-throttler/createAsyncThrottler.d.cts +54 -9
  16. package/dist/cjs/batcher/createBatcher.cjs +1 -1
  17. package/dist/cjs/batcher/createBatcher.cjs.map +1 -1
  18. package/dist/cjs/batcher/createBatcher.d.cts +46 -9
  19. package/dist/cjs/debouncer/createDebouncedSignal.cjs.map +1 -1
  20. package/dist/cjs/debouncer/createDebouncedSignal.d.cts +36 -4
  21. package/dist/cjs/debouncer/createDebouncedValue.cjs.map +1 -1
  22. package/dist/cjs/debouncer/createDebouncedValue.d.cts +30 -2
  23. package/dist/cjs/debouncer/createDebouncer.cjs +1 -1
  24. package/dist/cjs/debouncer/createDebouncer.cjs.map +1 -1
  25. package/dist/cjs/debouncer/createDebouncer.d.cts +56 -10
  26. package/dist/cjs/queuer/createQueuer.cjs +1 -1
  27. package/dist/cjs/queuer/createQueuer.cjs.map +1 -1
  28. package/dist/cjs/queuer/createQueuer.d.cts +46 -12
  29. package/dist/cjs/rate-limiter/createRateLimitedSignal.cjs.map +1 -1
  30. package/dist/cjs/rate-limiter/createRateLimitedSignal.d.cts +30 -3
  31. package/dist/cjs/rate-limiter/createRateLimitedValue.cjs.map +1 -1
  32. package/dist/cjs/rate-limiter/createRateLimitedValue.d.cts +32 -2
  33. package/dist/cjs/rate-limiter/createRateLimiter.cjs +1 -1
  34. package/dist/cjs/rate-limiter/createRateLimiter.cjs.map +1 -1
  35. package/dist/cjs/rate-limiter/createRateLimiter.d.cts +50 -8
  36. package/dist/cjs/throttler/createThrottledSignal.cjs.map +1 -1
  37. package/dist/cjs/throttler/createThrottledSignal.d.cts +34 -6
  38. package/dist/cjs/throttler/createThrottledValue.cjs.map +1 -1
  39. package/dist/cjs/throttler/createThrottledValue.d.cts +33 -2
  40. package/dist/cjs/throttler/createThrottler.cjs +1 -1
  41. package/dist/cjs/throttler/createThrottler.cjs.map +1 -1
  42. package/dist/cjs/throttler/createThrottler.d.cts +56 -12
  43. package/dist/esm/async-batcher/createAsyncBatcher.d.ts +54 -6
  44. package/dist/esm/async-batcher/createAsyncBatcher.js +1 -1
  45. package/dist/esm/async-batcher/createAsyncBatcher.js.map +1 -1
  46. package/dist/esm/async-debouncer/createAsyncDebouncer.d.ts +50 -8
  47. package/dist/esm/async-debouncer/createAsyncDebouncer.js +1 -1
  48. package/dist/esm/async-debouncer/createAsyncDebouncer.js.map +1 -1
  49. package/dist/esm/async-queuer/createAsyncQueuer.d.ts +55 -6
  50. package/dist/esm/async-queuer/createAsyncQueuer.js +1 -1
  51. package/dist/esm/async-queuer/createAsyncQueuer.js.map +1 -1
  52. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.d.ts +59 -9
  53. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js +1 -1
  54. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js.map +1 -1
  55. package/dist/esm/async-throttler/createAsyncThrottler.d.ts +54 -9
  56. package/dist/esm/async-throttler/createAsyncThrottler.js +1 -1
  57. package/dist/esm/async-throttler/createAsyncThrottler.js.map +1 -1
  58. package/dist/esm/batcher/createBatcher.d.ts +46 -9
  59. package/dist/esm/batcher/createBatcher.js +1 -1
  60. package/dist/esm/batcher/createBatcher.js.map +1 -1
  61. package/dist/esm/debouncer/createDebouncedSignal.d.ts +36 -4
  62. package/dist/esm/debouncer/createDebouncedSignal.js.map +1 -1
  63. package/dist/esm/debouncer/createDebouncedValue.d.ts +30 -2
  64. package/dist/esm/debouncer/createDebouncedValue.js.map +1 -1
  65. package/dist/esm/debouncer/createDebouncer.d.ts +56 -10
  66. package/dist/esm/debouncer/createDebouncer.js +1 -1
  67. package/dist/esm/debouncer/createDebouncer.js.map +1 -1
  68. package/dist/esm/queuer/createQueuer.d.ts +46 -12
  69. package/dist/esm/queuer/createQueuer.js +1 -1
  70. package/dist/esm/queuer/createQueuer.js.map +1 -1
  71. package/dist/esm/rate-limiter/createRateLimitedSignal.d.ts +30 -3
  72. package/dist/esm/rate-limiter/createRateLimitedSignal.js.map +1 -1
  73. package/dist/esm/rate-limiter/createRateLimitedValue.d.ts +32 -2
  74. package/dist/esm/rate-limiter/createRateLimitedValue.js.map +1 -1
  75. package/dist/esm/rate-limiter/createRateLimiter.d.ts +50 -8
  76. package/dist/esm/rate-limiter/createRateLimiter.js +1 -1
  77. package/dist/esm/rate-limiter/createRateLimiter.js.map +1 -1
  78. package/dist/esm/throttler/createThrottledSignal.d.ts +34 -6
  79. package/dist/esm/throttler/createThrottledSignal.js.map +1 -1
  80. package/dist/esm/throttler/createThrottledValue.d.ts +33 -2
  81. package/dist/esm/throttler/createThrottledValue.js.map +1 -1
  82. package/dist/esm/throttler/createThrottler.d.ts +56 -12
  83. package/dist/esm/throttler/createThrottler.js +1 -1
  84. package/dist/esm/throttler/createThrottler.js.map +1 -1
  85. package/package.json +2 -2
  86. package/src/async-batcher/createAsyncBatcher.ts +58 -14
  87. package/src/async-debouncer/createAsyncDebouncer.ts +53 -10
  88. package/src/async-queuer/createAsyncQueuer.ts +58 -8
  89. package/src/async-rate-limiter/createAsyncRateLimiter.ts +61 -10
  90. package/src/async-throttler/createAsyncThrottler.ts +56 -10
  91. package/src/batcher/createBatcher.ts +48 -10
  92. package/src/debouncer/createDebouncedSignal.ts +36 -7
  93. package/src/debouncer/createDebouncedValue.ts +30 -5
  94. package/src/debouncer/createDebouncer.ts +58 -17
  95. package/src/queuer/createQueuer.ts +48 -14
  96. package/src/rate-limiter/createRateLimitedSignal.ts +30 -3
  97. package/src/rate-limiter/createRateLimitedValue.ts +32 -2
  98. package/src/rate-limiter/createRateLimiter.ts +53 -16
  99. package/src/throttler/createThrottledSignal.ts +34 -9
  100. package/src/throttler/createThrottledValue.ts +33 -5
  101. package/src/throttler/createThrottler.ts +58 -19
@@ -1,5 +1,6 @@
1
1
  import { AsyncThrottler } from '@tanstack/pacer/async-throttler'
2
2
  import { useStore } from '@tanstack/solid-store'
3
+ import type { Store } from '@tanstack/solid-store'
3
4
  import type { Accessor } from 'solid-js'
4
5
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
5
6
  import type {
@@ -9,14 +10,20 @@ import type {
9
10
 
10
11
  export interface SolidAsyncThrottler<
11
12
  TFn extends AnyAsyncFunction,
12
- TSelected = AsyncThrottlerState<TFn>,
13
+ TSelected = {},
13
14
  > extends Omit<AsyncThrottler<TFn>, 'store'> {
14
15
  /**
15
- * Reactive state that will be updated and re-rendered when the throttler state changes
16
+ * Reactive state that will be updated when the throttler state changes
16
17
  *
17
18
  * Use this instead of `throttler.store.state`
18
19
  */
19
20
  readonly state: Accessor<Readonly<TSelected>>
21
+ /**
22
+ * @deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state.
23
+ * The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally.
24
+ * Although, you can make the state reactive by using the `useStore` in your own usage.
25
+ */
26
+ readonly store: Store<Readonly<AsyncThrottlerState<TFn>>>
20
27
  }
21
28
 
22
29
  /**
@@ -40,9 +47,34 @@ export interface SolidAsyncThrottler<
40
47
  * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
41
48
  * - The error state can be checked using the underlying AsyncThrottler instance
42
49
  *
50
+ * ## State Management and Selector
51
+ *
52
+ * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
53
+ * to specify which state changes will trigger a re-render, optimizing performance by preventing
54
+ * unnecessary re-renders when irrelevant state changes occur.
55
+ *
56
+ * **By default, there will be no reactive state subscriptions** and you must opt-in to state
57
+ * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
58
+ * full control over when your component updates. Only when you provide a selector will the
59
+ * component re-render when the selected state values change.
60
+ *
61
+ * Available state properties:
62
+ * - `canLeadingExecute`: Whether the throttler can execute on the leading edge
63
+ * - `canTrailingExecute`: Whether the throttler can execute on the trailing edge
64
+ * - `executionCount`: Number of function executions that have been completed
65
+ * - `hasError`: Whether the last execution resulted in an error
66
+ * - `isPending`: Whether the throttler is waiting for the timeout to trigger execution
67
+ * - `isExecuting`: Whether an async function execution is currently in progress
68
+ * - `lastArgs`: The arguments from the most recent call to maybeExecute
69
+ * - `lastError`: The error from the most recent failed execution (if any)
70
+ * - `lastExecutionTime`: Timestamp of the last execution
71
+ * - `lastResult`: The result from the most recent successful execution
72
+ * - `nextExecutionTime`: Timestamp of the next allowed execution
73
+ * - `status`: Current execution status ('disabled' | 'idle' | 'pending' | 'executing')
74
+ *
43
75
  * @example
44
76
  * ```tsx
45
- * // Basic API call throttling
77
+ * // Default behavior - no reactive state subscriptions
46
78
  * const { maybeExecute } = createAsyncThrottler(
47
79
  * async (id: string) => {
48
80
  * const data = await api.fetchData(id);
@@ -51,12 +83,21 @@ export interface SolidAsyncThrottler<
51
83
  * { wait: 1000 }
52
84
  * );
53
85
  *
54
- * // With state management
55
- * const [data, setData] = createSignal(null);
56
- * const { maybeExecute } = createAsyncThrottler(
86
+ * // Opt-in to re-render when isPending or isExecuting changes (optimized for loading states)
87
+ * const throttler = createAsyncThrottler(
57
88
  * async (query) => {
58
89
  * const result = await searchAPI(query);
59
- * setData(result);
90
+ * return result;
91
+ * },
92
+ * { wait: 2000 },
93
+ * (state) => ({ isPending: state.isPending, isExecuting: state.isExecuting })
94
+ * );
95
+ *
96
+ * // Opt-in to re-render when error state changes (optimized for error handling)
97
+ * const throttler = createAsyncThrottler(
98
+ * async (query) => {
99
+ * const result = await searchAPI(query);
100
+ * return result;
60
101
  * },
61
102
  * {
62
103
  * wait: 2000,
@@ -65,17 +106,22 @@ export interface SolidAsyncThrottler<
65
106
  * onError: (error) => {
66
107
  * console.error('API call failed:', error);
67
108
  * }
68
- * }
109
+ * },
110
+ * (state) => ({ hasError: state.hasError, lastError: state.lastError })
69
111
  * );
112
+ *
113
+ * // Access the selected state (will be empty object {} unless selector provided)
114
+ * const { isPending, isExecuting } = throttler.state();
70
115
  * ```
71
116
  */
72
117
  export function createAsyncThrottler<
73
118
  TFn extends AnyAsyncFunction,
74
- TSelected = AsyncThrottlerState<TFn>,
119
+ TSelected = {},
75
120
  >(
76
121
  fn: TFn,
77
122
  initialOptions: AsyncThrottlerOptions<TFn>,
78
- selector?: (state: AsyncThrottlerState<TFn>) => TSelected,
123
+ selector: (state: AsyncThrottlerState<TFn>) => TSelected = () =>
124
+ ({}) as TSelected,
79
125
  ): SolidAsyncThrottler<TFn, TSelected> {
80
126
  const asyncThrottler = new AsyncThrottler(fn, initialOptions)
81
127
 
@@ -1,9 +1,10 @@
1
1
  import { Batcher } from '@tanstack/pacer/batcher'
2
2
  import { useStore } from '@tanstack/solid-store'
3
+ import type { Store } from '@tanstack/solid-store'
3
4
  import type { Accessor } from 'solid-js'
4
5
  import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher'
5
6
 
6
- export interface SolidBatcher<TValue, TSelected = BatcherState<TValue>>
7
+ export interface SolidBatcher<TValue, TSelected = {}>
7
8
  extends Omit<Batcher<TValue>, 'store'> {
8
9
  /**
9
10
  * Reactive state that will be updated when the batcher state changes
@@ -11,6 +12,12 @@ export interface SolidBatcher<TValue, TSelected = BatcherState<TValue>>
11
12
  * Use this instead of `batcher.store.state`
12
13
  */
13
14
  readonly state: Accessor<Readonly<TSelected>>
15
+ /**
16
+ * @deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state.
17
+ * The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally.
18
+ * Although, you can make the state reactive by using the `useStore` in your own usage.
19
+ */
20
+ readonly store: Store<Readonly<BatcherState<TValue>>>
14
21
  }
15
22
 
16
23
  /**
@@ -28,8 +35,26 @@ export interface SolidBatcher<TValue, TSelected = BatcherState<TValue>>
28
35
  * - Time-based batching (process after X milliseconds)
29
36
  * - Custom batch processing logic via getShouldExecute
30
37
  *
38
+ * ## State Management and Selector
39
+ *
40
+ * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
41
+ * to specify which state changes will trigger a re-render, optimizing performance by preventing
42
+ * unnecessary re-renders when irrelevant state changes occur.
43
+ *
44
+ * **By default, there will be no reactive state subscriptions** and you must opt-in to state
45
+ * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
46
+ * full control over when your component updates. Only when you provide a selector will the
47
+ * component re-render when the selected state values change.
48
+ *
49
+ * Available state properties:
50
+ * - `executionCount`: Number of batch executions that have been completed
51
+ * - `isRunning`: Whether the batcher is currently running (not stopped)
52
+ * - `items`: Array of items currently queued for batching
53
+ * - `totalItemsProcessed`: Total number of individual items that have been processed across all batches
54
+ *
31
55
  * Example usage:
32
56
  * ```tsx
57
+ * // Default behavior - no reactive state subscriptions
33
58
  * const batcher = createBatcher(
34
59
  * (items) => {
35
60
  * // Process batch of items
@@ -43,6 +68,23 @@ export interface SolidBatcher<TValue, TSelected = BatcherState<TValue>>
43
68
  * }
44
69
  * );
45
70
  *
71
+ * // Opt-in to re-render when items or isRunning changes (optimized for UI updates)
72
+ * const batcher = createBatcher(
73
+ * (items) => console.log('Processing batch:', items),
74
+ * { maxSize: 5, wait: 2000 },
75
+ * (state) => ({ items: state.items, isRunning: state.isRunning })
76
+ * );
77
+ *
78
+ * // Opt-in to re-render when execution metrics change (optimized for tracking progress)
79
+ * const batcher = createBatcher(
80
+ * (items) => console.log('Processing batch:', items),
81
+ * { maxSize: 5, wait: 2000 },
82
+ * (state) => ({
83
+ * executionCount: state.executionCount,
84
+ * totalItemsProcessed: state.totalItemsProcessed
85
+ * })
86
+ * );
87
+ *
46
88
  * // Add items to batch
47
89
  * batcher.addItem('task1');
48
90
  * batcher.addItem('task2');
@@ -51,19 +93,15 @@ export interface SolidBatcher<TValue, TSelected = BatcherState<TValue>>
51
93
  * batcher.stop(); // Pause processing
52
94
  * batcher.start(); // Resume processing
53
95
  *
54
- * // Access batcher state via signals
55
- * console.log('Items:', batcher.allItems());
56
- * console.log('Size:', batcher.size());
57
- * console.log('Is empty:', batcher.isEmpty());
58
- * console.log('Is running:', batcher.isRunning());
59
- * console.log('Batch count:', batcher.executionCount());
60
- * console.log('Item count:', batcher.totalItemsProcessed());
96
+ * // Access the selected state (will be empty object {} unless selector provided)
97
+ * const { items, isRunning } = batcher.state();
61
98
  * ```
62
99
  */
63
- export function createBatcher<TValue, TSelected = BatcherState<TValue>>(
100
+ export function createBatcher<TValue, TSelected = {}>(
64
101
  fn: (items: Array<TValue>) => void,
65
102
  initialOptions: BatcherOptions<TValue> = {},
66
- selector?: (state: BatcherState<TValue>) => TSelected,
103
+ selector: (state: BatcherState<TValue>) => TSelected = () =>
104
+ ({}) as TSelected,
67
105
  ): SolidBatcher<TValue, TSelected> {
68
106
  const batcher = new Batcher(fn, initialOptions)
69
107
 
@@ -21,21 +21,53 @@ import type {
21
21
  * - A function to update the debounced value
22
22
  * - The debouncer instance with additional control methods and state signals
23
23
  *
24
+ * ## State Management and Selector
25
+ *
26
+ * The hook uses TanStack Store for reactive state management via the underlying debouncer instance.
27
+ * The `selector` parameter allows you to specify which debouncer state changes will trigger reactive updates,
28
+ * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur.
29
+ *
30
+ * **By default, there will be no reactive state subscriptions** and you must opt-in to state
31
+ * tracking by providing a selector function. This prevents unnecessary reactive updates and gives you
32
+ * full control over when your component subscribes to state changes. Only when you provide a selector will
33
+ * the reactive system track the selected state values.
34
+ *
35
+ * Available debouncer state properties:
36
+ * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge
37
+ * - `executionCount`: Number of function executions that have been completed
38
+ * - `isPending`: Whether the debouncer is waiting for the timeout to trigger execution
39
+ * - `lastArgs`: The arguments from the most recent call to maybeExecute
40
+ * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
41
+ *
24
42
  * @example
25
43
  * ```tsx
26
- * // Debounced search input
44
+ * // Default behavior - no reactive state subscriptions
27
45
  * const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal('', {
28
46
  * wait: 500 // Wait 500ms after last keystroke
29
47
  * });
30
48
  *
49
+ * // Opt-in to reactive updates when pending state changes (optimized for loading indicators)
50
+ * const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal(
51
+ * '',
52
+ * { wait: 500 },
53
+ * (state) => ({ isPending: state.isPending })
54
+ * );
55
+ *
56
+ * // Opt-in to reactive updates when execution count changes (optimized for tracking executions)
57
+ * const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal(
58
+ * '',
59
+ * { wait: 500 },
60
+ * (state) => ({ executionCount: state.executionCount })
61
+ * );
62
+ *
31
63
  * // Update value - will be debounced
32
64
  * const handleChange = (e) => {
33
65
  * setSearchTerm(e.target.value);
34
66
  * };
35
67
  *
36
68
  * // Access debouncer state via signals
37
- * console.log('Executions:', debouncer.executionCount());
38
- * console.log('Is pending:', debouncer.isPending());
69
+ * console.log('Executions:', debouncer.state().executionCount);
70
+ * console.log('Is pending:', debouncer.state().isPending);
39
71
  *
40
72
  * // In onExecute callback, use get* methods
41
73
  * const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal('', {
@@ -46,10 +78,7 @@ import type {
46
78
  * });
47
79
  * ```
48
80
  */
49
- export function createDebouncedSignal<
50
- TValue,
51
- TSelected = DebouncerState<Setter<TValue>>,
52
- >(
81
+ export function createDebouncedSignal<TValue, TSelected = {}>(
53
82
  value: TValue,
54
83
  initialOptions: DebouncerOptions<Setter<TValue>>,
55
84
  selector?: (state: DebouncerState<Setter<TValue>>) => TSelected,
@@ -24,27 +24,52 @@ import type {
24
24
  * - An Accessor that provides the current debounced value
25
25
  * - The debouncer instance with control methods
26
26
  *
27
+ * ## State Management and Selector
28
+ *
29
+ * The hook uses TanStack Store for reactive state management via the underlying debouncer instance.
30
+ * The `selector` parameter allows you to specify which debouncer state changes will trigger reactive updates,
31
+ * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur.
32
+ *
33
+ * **By default, there will be no reactive state subscriptions** and you must opt-in to state
34
+ * tracking by providing a selector function. This prevents unnecessary reactive updates and gives you
35
+ * full control over when your component subscribes to state changes. Only when you provide a selector will
36
+ * the reactive system track the selected state values.
37
+ *
38
+ * Available debouncer state properties:
39
+ * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge
40
+ * - `executionCount`: Number of function executions that have been completed
41
+ * - `isPending`: Whether the debouncer is waiting for the timeout to trigger execution
42
+ * - `lastArgs`: The arguments from the most recent call to maybeExecute
43
+ * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
44
+ *
27
45
  * @example
28
46
  * ```tsx
29
- * // Debounce a search query
47
+ * // Default behavior - no reactive state subscriptions
30
48
  * const [searchQuery, setSearchQuery] = createSignal('');
31
49
  * const [debouncedQuery, debouncer] = createDebouncedValue(searchQuery, {
32
50
  * wait: 500 // Wait 500ms after last change
33
51
  * });
34
52
  *
53
+ * // Opt-in to reactive updates when pending state changes (optimized for loading indicators)
54
+ * const [debouncedQuery, debouncer] = createDebouncedValue(
55
+ * searchQuery,
56
+ * { wait: 500 },
57
+ * (state) => ({ isPending: state.isPending })
58
+ * );
59
+ *
35
60
  * // debouncedQuery will update 500ms after searchQuery stops changing
36
61
  * createEffect(() => {
37
62
  * fetchSearchResults(debouncedQuery());
38
63
  * });
39
64
  *
65
+ * // Access debouncer state via signals
66
+ * console.log('Is pending:', debouncer.state().isPending);
67
+ *
40
68
  * // Control the debouncer
41
69
  * debouncer.cancel(); // Cancel any pending updates
42
70
  * ```
43
71
  */
44
- export function createDebouncedValue<
45
- TValue,
46
- TSelected = DebouncerState<Setter<TValue>>,
47
- >(
72
+ export function createDebouncedValue<TValue, TSelected = {}>(
48
73
  value: Accessor<TValue>,
49
74
  initialOptions: DebouncerOptions<Setter<TValue>>,
50
75
  selector?: (state: DebouncerState<Setter<TValue>>) => TSelected,
@@ -1,6 +1,7 @@
1
1
  import { Debouncer } from '@tanstack/pacer/debouncer'
2
2
  import { createEffect, onCleanup } from 'solid-js'
3
3
  import { useStore } from '@tanstack/solid-store'
4
+ import type { Store } from '@tanstack/solid-store'
4
5
  import type { Accessor } from 'solid-js'
5
6
  import type { AnyFunction } from '@tanstack/pacer/types'
6
7
  import type {
@@ -8,16 +9,20 @@ import type {
8
9
  DebouncerState,
9
10
  } from '@tanstack/pacer/debouncer'
10
11
 
11
- export interface SolidDebouncer<
12
- TFn extends AnyFunction,
13
- TSelected = DebouncerState<TFn>,
14
- > extends Omit<Debouncer<TFn>, 'store'> {
12
+ export interface SolidDebouncer<TFn extends AnyFunction, TSelected = {}>
13
+ extends Omit<Debouncer<TFn>, 'store'> {
15
14
  /**
16
15
  * Reactive state that will be updated when the debouncer state changes
17
16
  *
18
17
  * Use this instead of `debouncer.store.state`
19
18
  */
20
19
  readonly state: Accessor<Readonly<TSelected>>
20
+ /**
21
+ * @deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state.
22
+ * The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally.
23
+ * Although, you can make the state reactive by using the `useStore` in your own usage.
24
+ */
25
+ readonly store: Store<Readonly<DebouncerState<TFn>>>
21
26
  }
22
27
 
23
28
  /**
@@ -35,12 +40,55 @@ export interface SolidDebouncer<
35
40
  * since the last call. If the function is called again before the wait time expires, the
36
41
  * timer resets and starts waiting again.
37
42
  *
43
+ * ## State Management and Selector
44
+ *
45
+ * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
46
+ * to specify which state changes will trigger a re-render, optimizing performance by preventing
47
+ * unnecessary re-renders when irrelevant state changes occur.
48
+ *
49
+ * **By default, there will be no reactive state subscriptions** and you must opt-in to state
50
+ * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
51
+ * full control over when your component updates. Only when you provide a selector will the
52
+ * component re-render when the selected state values change.
53
+ *
54
+ * Available state properties:
55
+ * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge
56
+ * - `executionCount`: Number of function executions that have been completed
57
+ * - `isPending`: Whether the debouncer is waiting for the timeout to trigger execution
58
+ * - `lastArgs`: The arguments from the most recent call to maybeExecute
59
+ * - `status`: Current execution status ('disabled' | 'idle' | 'pending')
60
+ *
38
61
  * @example
39
62
  * ```tsx
40
- * // Debounce a search function to limit API calls
63
+ * // Default behavior - no reactive state subscriptions
64
+ * const debouncer = createDebouncer(
65
+ * (query: string) => fetchSearchResults(query),
66
+ * { wait: 500 }
67
+ * );
68
+ *
69
+ * // Opt-in to re-render when isPending changes (optimized for loading states)
41
70
  * const debouncer = createDebouncer(
42
71
  * (query: string) => fetchSearchResults(query),
43
- * { wait: 500 } // Wait 500ms after last keystroke
72
+ * { wait: 500 },
73
+ * (state) => ({ isPending: state.isPending })
74
+ * );
75
+ *
76
+ * // Opt-in to re-render when executionCount changes (optimized for tracking execution)
77
+ * const debouncer = createDebouncer(
78
+ * (query: string) => fetchSearchResults(query),
79
+ * { wait: 500 },
80
+ * (state) => ({ executionCount: state.executionCount })
81
+ * );
82
+ *
83
+ * // Multiple state properties - re-render when any of these change
84
+ * const debouncer = createDebouncer(
85
+ * (query: string) => fetchSearchResults(query),
86
+ * { wait: 500 },
87
+ * (state) => ({
88
+ * isPending: state.isPending,
89
+ * executionCount: state.executionCount,
90
+ * status: state.status
91
+ * })
44
92
  * );
45
93
  *
46
94
  * // In an event handler
@@ -48,21 +96,14 @@ export interface SolidDebouncer<
48
96
  * debouncer.maybeExecute(e.target.value);
49
97
  * };
50
98
  *
51
- * // Access debouncer state via signals
52
- * console.log('Executions:', debouncer.executionCount());
53
- * console.log('Is pending:', debouncer.isPending());
54
- *
55
- * // Update options
56
- * debouncer.setOptions({ wait: 1000 });
99
+ * // Access the selected state (will be empty object {} unless selector provided)
100
+ * const { isPending } = debouncer.state();
57
101
  * ```
58
102
  */
59
- export function createDebouncer<
60
- TFn extends AnyFunction,
61
- TSelected = DebouncerState<TFn>,
62
- >(
103
+ export function createDebouncer<TFn extends AnyFunction, TSelected = {}>(
63
104
  fn: TFn,
64
105
  initialOptions: DebouncerOptions<TFn>,
65
- selector?: (state: DebouncerState<TFn>) => TSelected,
106
+ selector: (state: DebouncerState<TFn>) => TSelected = () => ({}) as TSelected,
66
107
  ): SolidDebouncer<TFn, TSelected> {
67
108
  const asyncDebouncer = new Debouncer<TFn>(fn, initialOptions)
68
109
 
@@ -1,9 +1,10 @@
1
1
  import { Queuer } from '@tanstack/pacer/queuer'
2
2
  import { useStore } from '@tanstack/solid-store'
3
+ import type { Store } from '@tanstack/solid-store'
3
4
  import type { Accessor } from 'solid-js'
4
5
  import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer'
5
6
 
6
- export interface SolidQueuer<TValue, TSelected = QueuerState<TValue>>
7
+ export interface SolidQueuer<TValue, TSelected = {}>
7
8
  extends Omit<Queuer<TValue>, 'store'> {
8
9
  /**
9
10
  * Reactive state that will be updated when the queuer state changes
@@ -11,6 +12,12 @@ export interface SolidQueuer<TValue, TSelected = QueuerState<TValue>>
11
12
  * Use this instead of `queuer.store.state`
12
13
  */
13
14
  readonly state: Accessor<Readonly<TSelected>>
15
+ /**
16
+ * @deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state.
17
+ * The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally.
18
+ * Although, you can make the state reactive by using the `useStore` in your own usage.
19
+ */
20
+ readonly store: Store<Readonly<QueuerState<TValue>>>
14
21
  }
15
22
 
16
23
  /**
@@ -30,11 +37,26 @@ export interface SolidQueuer<TValue, TSelected = QueuerState<TValue>>
30
37
  *
31
38
  * By default, the queue uses FIFO behavior, but you can configure LIFO or double-ended queueing by specifying the position when adding or removing items.
32
39
  *
40
+ * ## State Management and Selector
41
+ *
42
+ * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
43
+ * to specify which state changes will trigger a re-render, optimizing performance by preventing
44
+ * unnecessary re-renders when irrelevant state changes occur.
45
+ *
46
+ * **By default, there will be no reactive state subscriptions** and you must opt-in to state
47
+ * tracking by providing a selector function. This prevents unnecessary re-renders and gives you
48
+ * full control over when your component updates. Only when you provide a selector will the
49
+ * component re-render when the selected state values change.
50
+ *
51
+ * Available state properties:
52
+ * - `executionCount`: Number of items that have been processed
53
+ * - `isRunning`: Whether the queuer is currently running (not stopped)
54
+ * - `items`: Array of items currently queued for processing
55
+ * - `rejectionCount`: Number of items that were rejected (expired or failed validation)
56
+ *
33
57
  * Example usage:
34
58
  * ```tsx
35
- * // Example with Solid signals and scheduling
36
- * const [items, setItems] = createSignal([]);
37
- *
59
+ * // Default behavior - no reactive state subscriptions
38
60
  * const queue = createQueuer(
39
61
  * (item) => {
40
62
  * // process item synchronously
@@ -43,11 +65,27 @@ export interface SolidQueuer<TValue, TSelected = QueuerState<TValue>>
43
65
  * {
44
66
  * started: true, // Start processing immediately
45
67
  * wait: 1000, // Process one item every second
46
- * onItemsChange: (queue) => setItems(queue.peekAllItems()),
47
68
  * getPriority: (item) => item.priority // Process higher priority items first
48
69
  * }
49
70
  * );
50
71
  *
72
+ * // Opt-in to re-render when items or isRunning changes (optimized for UI updates)
73
+ * const queue = createQueuer(
74
+ * (item) => console.log('Processing', item),
75
+ * { started: true, wait: 1000 },
76
+ * (state) => ({ items: state.items, isRunning: state.isRunning })
77
+ * );
78
+ *
79
+ * // Opt-in to re-render when execution metrics change (optimized for tracking progress)
80
+ * const queue = createQueuer(
81
+ * (item) => console.log('Processing', item),
82
+ * { started: true, wait: 1000 },
83
+ * (state) => ({
84
+ * executionCount: state.executionCount,
85
+ * rejectionCount: state.rejectionCount
86
+ * })
87
+ * );
88
+ *
51
89
  * // Add items to process - they'll be handled automatically
52
90
  * queue.addItem('task1');
53
91
  * queue.addItem('task2');
@@ -56,18 +94,14 @@ export interface SolidQueuer<TValue, TSelected = QueuerState<TValue>>
56
94
  * queue.stop(); // Pause processing
57
95
  * queue.start(); // Resume processing
58
96
  *
59
- * // Access queue state via signals
60
- * console.log('Items:', queue.allItems());
61
- * console.log('Size:', queue.size());
62
- * console.log('Is empty:', queue.isEmpty());
63
- * console.log('Is running:', queue.isRunning());
64
- * console.log('Next item:', queue.nextItem());
97
+ * // Access the selected state (will be empty object {} unless selector provided)
98
+ * const { items, isRunning } = queue.state();
65
99
  * ```
66
100
  */
67
- export function createQueuer<TValue, TSelected = QueuerState<TValue>>(
101
+ export function createQueuer<TValue, TSelected = {}>(
68
102
  fn: (item: TValue) => void,
69
103
  initialOptions: QueuerOptions<TValue> = {},
70
- selector?: (state: QueuerState<TValue>) => TSelected,
104
+ selector: (state: QueuerState<TValue>) => TSelected = () => ({}) as TSelected,
71
105
  ): SolidQueuer<TValue, TSelected> {
72
106
  const queuer = new Queuer(fn, initialOptions)
73
107
 
@@ -76,5 +110,5 @@ export function createQueuer<TValue, TSelected = QueuerState<TValue>>(
76
110
  return {
77
111
  ...queuer,
78
112
  state,
79
- } as unknown as SolidQueuer<TValue, TSelected> // omit `store` in favor of `state`
113
+ } as SolidQueuer<TValue, TSelected> // omit `store` in favor of `state`
80
114
  }
@@ -35,15 +35,42 @@ import type {
35
35
  * For more direct control over rate limiting without state management,
36
36
  * consider using the lower-level createRateLimiter hook instead.
37
37
  *
38
+ * ## State Management and Selector
39
+ *
40
+ * The hook uses TanStack Store for reactive state management via the underlying rate limiter instance.
41
+ * The `selector` parameter allows you to specify which rate limiter state changes will trigger reactive updates,
42
+ * optimizing performance by preventing unnecessary subscriptions when irrelevant state changes occur.
43
+ *
44
+ * **By default, there will be no reactive state subscriptions** and you must opt-in to state
45
+ * tracking by providing a selector function. This prevents unnecessary reactive updates and gives you
46
+ * full control over when your component subscribes to state changes. Only when you provide a selector will
47
+ * the reactive system track the selected state values.
48
+ *
49
+ * Available rate limiter state properties:
50
+ * - `callsInWindow`: Number of calls made in the current window
51
+ * - `remainingInWindow`: Number of calls remaining in the current window
52
+ * - `windowStart`: Unix timestamp when the current window started
53
+ * - `nextWindowStart`: Unix timestamp when the next window will start
54
+ * - `msUntilNextWindow`: Milliseconds until the next window starts
55
+ * - `isAtLimit`: Whether the call limit for the current window has been reached
56
+ * - `status`: Current status ('disabled' | 'idle' | 'at-limit')
57
+ *
38
58
  * @example
39
59
  * ```tsx
40
- * // Basic rate limiting - update state at most 5 times per minute with a sliding window
60
+ * // Default behavior - no reactive state subscriptions
41
61
  * const [value, setValue, rateLimiter] = createRateLimitedSignal(0, {
42
62
  * limit: 5,
43
63
  * window: 60000,
44
64
  * windowType: 'sliding'
45
65
  * });
46
66
  *
67
+ * // Opt-in to reactive updates when limit state changes (optimized for UI feedback)
68
+ * const [value, setValue, rateLimiter] = createRateLimitedSignal(
69
+ * 0,
70
+ * { limit: 5, window: 60000 },
71
+ * (state) => ({ isAtLimit: state.isAtLimit, remainingInWindow: state.remainingInWindow })
72
+ * );
73
+ *
47
74
  * // With rejection callback and fixed window
48
75
  * const [value, setValue] = createRateLimitedSignal(0, {
49
76
  * limit: 3,
@@ -56,7 +83,7 @@ import type {
56
83
  *
57
84
  * // Access rateLimiter state via signals
58
85
  * const handleSubmit = () => {
59
- * const remaining = rateLimiter.remainingInWindow();
86
+ * const remaining = rateLimiter.state().remainingInWindow;
60
87
  * if (remaining > 0) {
61
88
  * setValue(newValue);
62
89
  * } else {
@@ -65,7 +92,7 @@ import type {
65
92
  * };
66
93
  * ```
67
94
  */
68
- export function createRateLimitedSignal<TValue, TSelected = RateLimiterState>(
95
+ export function createRateLimitedSignal<TValue, TSelected = {}>(
69
96
  value: TValue,
70
97
  initialOptions: RateLimiterOptions<Setter<TValue>>,
71
98
  selector?: (state: RateLimiterState) => TSelected,