@tanstack/solid-pacer 0.2.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 (184) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs +32 -0
  3. package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs.map +1 -0
  4. package/dist/cjs/async-debouncer/createAsyncDebouncer.d.cts +42 -0
  5. package/dist/cjs/async-debouncer/index.cjs +12 -0
  6. package/dist/cjs/async-debouncer/index.cjs.map +1 -0
  7. package/dist/cjs/async-debouncer/index.d.cts +2 -0
  8. package/dist/cjs/async-queuer/createAsyncQueuer.cjs +69 -0
  9. package/dist/cjs/async-queuer/createAsyncQueuer.cjs.map +1 -0
  10. package/dist/cjs/async-queuer/createAsyncQueuer.d.cts +90 -0
  11. package/dist/cjs/async-queuer/index.cjs +12 -0
  12. package/dist/cjs/async-queuer/index.cjs.map +1 -0
  13. package/dist/cjs/async-queuer/index.d.cts +2 -0
  14. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs +50 -0
  15. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs.map +1 -0
  16. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.d.cts +46 -0
  17. package/dist/cjs/async-rate-limiter/index.cjs +12 -0
  18. package/dist/cjs/async-rate-limiter/index.cjs.map +1 -0
  19. package/dist/cjs/async-rate-limiter/index.d.cts +2 -0
  20. package/dist/cjs/async-throttler/createAsyncThrottler.cjs +42 -0
  21. package/dist/cjs/async-throttler/createAsyncThrottler.cjs.map +1 -0
  22. package/dist/cjs/async-throttler/createAsyncThrottler.d.cts +46 -0
  23. package/dist/cjs/async-throttler/index.cjs +12 -0
  24. package/dist/cjs/async-throttler/index.cjs.map +1 -0
  25. package/dist/cjs/async-throttler/index.d.cts +2 -0
  26. package/dist/cjs/compare/index.cjs +10 -0
  27. package/dist/cjs/compare/index.cjs.map +1 -0
  28. package/dist/cjs/compare/index.d.cts +1 -0
  29. package/dist/cjs/debouncer/createDebouncedSignal.cjs +11 -0
  30. package/dist/cjs/debouncer/createDebouncedSignal.cjs.map +1 -0
  31. package/dist/cjs/debouncer/createDebouncedSignal.d.cts +47 -0
  32. package/dist/cjs/debouncer/createDebouncedValue.cjs +19 -0
  33. package/dist/cjs/debouncer/createDebouncedValue.cjs.map +1 -0
  34. package/dist/cjs/debouncer/createDebouncedValue.d.cts +44 -0
  35. package/dist/cjs/debouncer/createDebouncer.cjs +32 -0
  36. package/dist/cjs/debouncer/createDebouncer.cjs.map +1 -0
  37. package/dist/cjs/debouncer/createDebouncer.d.cts +47 -0
  38. package/dist/cjs/debouncer/index.cjs +16 -0
  39. package/dist/cjs/debouncer/index.cjs.map +1 -0
  40. package/dist/cjs/debouncer/index.d.cts +4 -0
  41. package/dist/cjs/index.cjs +38 -0
  42. package/dist/cjs/index.cjs.map +1 -0
  43. package/dist/cjs/index.d.cts +18 -0
  44. package/dist/cjs/queuer/createQueuer.cjs +66 -0
  45. package/dist/cjs/queuer/createQueuer.cjs.map +1 -0
  46. package/dist/cjs/queuer/createQueuer.d.cts +82 -0
  47. package/dist/cjs/queuer/index.cjs +12 -0
  48. package/dist/cjs/queuer/index.cjs.map +1 -0
  49. package/dist/cjs/queuer/index.d.cts +2 -0
  50. package/dist/cjs/rate-limiter/createRateLimitedSignal.cjs +15 -0
  51. package/dist/cjs/rate-limiter/createRateLimitedSignal.cjs.map +1 -0
  52. package/dist/cjs/rate-limiter/createRateLimitedSignal.d.cts +58 -0
  53. package/dist/cjs/rate-limiter/createRateLimitedValue.cjs +13 -0
  54. package/dist/cjs/rate-limiter/createRateLimitedValue.cjs.map +1 -0
  55. package/dist/cjs/rate-limiter/createRateLimitedValue.d.cts +51 -0
  56. package/dist/cjs/rate-limiter/createRateLimiter.cjs +50 -0
  57. package/dist/cjs/rate-limiter/createRateLimiter.cjs.map +1 -0
  58. package/dist/cjs/rate-limiter/createRateLimiter.d.cts +49 -0
  59. package/dist/cjs/rate-limiter/index.cjs +16 -0
  60. package/dist/cjs/rate-limiter/index.cjs.map +1 -0
  61. package/dist/cjs/rate-limiter/index.d.cts +4 -0
  62. package/dist/cjs/throttler/createThrottledSignal.cjs +11 -0
  63. package/dist/cjs/throttler/createThrottledSignal.cjs.map +1 -0
  64. package/dist/cjs/throttler/createThrottledSignal.d.cts +42 -0
  65. package/dist/cjs/throttler/createThrottledValue.cjs +19 -0
  66. package/dist/cjs/throttler/createThrottledValue.cjs.map +1 -0
  67. package/dist/cjs/throttler/createThrottledValue.d.cts +36 -0
  68. package/dist/cjs/throttler/createThrottler.cjs +42 -0
  69. package/dist/cjs/throttler/createThrottler.cjs.map +1 -0
  70. package/dist/cjs/throttler/createThrottler.d.cts +47 -0
  71. package/dist/cjs/throttler/index.cjs +16 -0
  72. package/dist/cjs/throttler/index.cjs.map +1 -0
  73. package/dist/cjs/throttler/index.d.cts +4 -0
  74. package/dist/cjs/types/index.cjs +10 -0
  75. package/dist/cjs/types/index.cjs.map +1 -0
  76. package/dist/cjs/types/index.d.cts +1 -0
  77. package/dist/cjs/utils/index.cjs +10 -0
  78. package/dist/cjs/utils/index.cjs.map +1 -0
  79. package/dist/cjs/utils/index.d.cts +1 -0
  80. package/dist/esm/async-debouncer/createAsyncDebouncer.d.ts +42 -0
  81. package/dist/esm/async-debouncer/createAsyncDebouncer.js +32 -0
  82. package/dist/esm/async-debouncer/createAsyncDebouncer.js.map +1 -0
  83. package/dist/esm/async-debouncer/index.d.ts +2 -0
  84. package/dist/esm/async-debouncer/index.js +6 -0
  85. package/dist/esm/async-debouncer/index.js.map +1 -0
  86. package/dist/esm/async-queuer/createAsyncQueuer.d.ts +90 -0
  87. package/dist/esm/async-queuer/createAsyncQueuer.js +69 -0
  88. package/dist/esm/async-queuer/createAsyncQueuer.js.map +1 -0
  89. package/dist/esm/async-queuer/index.d.ts +2 -0
  90. package/dist/esm/async-queuer/index.js +6 -0
  91. package/dist/esm/async-queuer/index.js.map +1 -0
  92. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.d.ts +46 -0
  93. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js +50 -0
  94. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js.map +1 -0
  95. package/dist/esm/async-rate-limiter/index.d.ts +2 -0
  96. package/dist/esm/async-rate-limiter/index.js +6 -0
  97. package/dist/esm/async-rate-limiter/index.js.map +1 -0
  98. package/dist/esm/async-throttler/createAsyncThrottler.d.ts +46 -0
  99. package/dist/esm/async-throttler/createAsyncThrottler.js +42 -0
  100. package/dist/esm/async-throttler/createAsyncThrottler.js.map +1 -0
  101. package/dist/esm/async-throttler/index.d.ts +2 -0
  102. package/dist/esm/async-throttler/index.js +6 -0
  103. package/dist/esm/async-throttler/index.js.map +1 -0
  104. package/dist/esm/compare/index.d.ts +1 -0
  105. package/dist/esm/compare/index.js +2 -0
  106. package/dist/esm/compare/index.js.map +1 -0
  107. package/dist/esm/debouncer/createDebouncedSignal.d.ts +47 -0
  108. package/dist/esm/debouncer/createDebouncedSignal.js +11 -0
  109. package/dist/esm/debouncer/createDebouncedSignal.js.map +1 -0
  110. package/dist/esm/debouncer/createDebouncedValue.d.ts +44 -0
  111. package/dist/esm/debouncer/createDebouncedValue.js +19 -0
  112. package/dist/esm/debouncer/createDebouncedValue.js.map +1 -0
  113. package/dist/esm/debouncer/createDebouncer.d.ts +47 -0
  114. package/dist/esm/debouncer/createDebouncer.js +32 -0
  115. package/dist/esm/debouncer/createDebouncer.js.map +1 -0
  116. package/dist/esm/debouncer/index.d.ts +4 -0
  117. package/dist/esm/debouncer/index.js +10 -0
  118. package/dist/esm/debouncer/index.js.map +1 -0
  119. package/dist/esm/index.d.ts +18 -0
  120. package/dist/esm/index.js +32 -0
  121. package/dist/esm/index.js.map +1 -0
  122. package/dist/esm/queuer/createQueuer.d.ts +82 -0
  123. package/dist/esm/queuer/createQueuer.js +66 -0
  124. package/dist/esm/queuer/createQueuer.js.map +1 -0
  125. package/dist/esm/queuer/index.d.ts +2 -0
  126. package/dist/esm/queuer/index.js +6 -0
  127. package/dist/esm/queuer/index.js.map +1 -0
  128. package/dist/esm/rate-limiter/createRateLimitedSignal.d.ts +58 -0
  129. package/dist/esm/rate-limiter/createRateLimitedSignal.js +15 -0
  130. package/dist/esm/rate-limiter/createRateLimitedSignal.js.map +1 -0
  131. package/dist/esm/rate-limiter/createRateLimitedValue.d.ts +51 -0
  132. package/dist/esm/rate-limiter/createRateLimitedValue.js +13 -0
  133. package/dist/esm/rate-limiter/createRateLimitedValue.js.map +1 -0
  134. package/dist/esm/rate-limiter/createRateLimiter.d.ts +49 -0
  135. package/dist/esm/rate-limiter/createRateLimiter.js +50 -0
  136. package/dist/esm/rate-limiter/createRateLimiter.js.map +1 -0
  137. package/dist/esm/rate-limiter/index.d.ts +4 -0
  138. package/dist/esm/rate-limiter/index.js +10 -0
  139. package/dist/esm/rate-limiter/index.js.map +1 -0
  140. package/dist/esm/throttler/createThrottledSignal.d.ts +42 -0
  141. package/dist/esm/throttler/createThrottledSignal.js +11 -0
  142. package/dist/esm/throttler/createThrottledSignal.js.map +1 -0
  143. package/dist/esm/throttler/createThrottledValue.d.ts +36 -0
  144. package/dist/esm/throttler/createThrottledValue.js +19 -0
  145. package/dist/esm/throttler/createThrottledValue.js.map +1 -0
  146. package/dist/esm/throttler/createThrottler.d.ts +47 -0
  147. package/dist/esm/throttler/createThrottler.js +42 -0
  148. package/dist/esm/throttler/createThrottler.js.map +1 -0
  149. package/dist/esm/throttler/index.d.ts +4 -0
  150. package/dist/esm/throttler/index.js +10 -0
  151. package/dist/esm/throttler/index.js.map +1 -0
  152. package/dist/esm/types/index.d.ts +1 -0
  153. package/dist/esm/types/index.js +2 -0
  154. package/dist/esm/types/index.js.map +1 -0
  155. package/dist/esm/utils/index.d.ts +1 -0
  156. package/dist/esm/utils/index.js +2 -0
  157. package/dist/esm/utils/index.js.map +1 -0
  158. package/package.json +163 -0
  159. package/src/async-debouncer/createAsyncDebouncer.ts +89 -0
  160. package/src/async-debouncer/index.ts +4 -0
  161. package/src/async-queuer/createAsyncQueuer.ts +175 -0
  162. package/src/async-queuer/index.ts +3 -0
  163. package/src/async-rate-limiter/createAsyncRateLimiter.ts +116 -0
  164. package/src/async-rate-limiter/index.ts +4 -0
  165. package/src/async-throttler/createAsyncThrottler.ts +106 -0
  166. package/src/async-throttler/index.ts +4 -0
  167. package/src/compare/index.ts +1 -0
  168. package/src/debouncer/createDebouncedSignal.ts +59 -0
  169. package/src/debouncer/createDebouncedValue.ts +64 -0
  170. package/src/debouncer/createDebouncer.ts +90 -0
  171. package/src/debouncer/index.ts +6 -0
  172. package/src/index.ts +36 -0
  173. package/src/queuer/createQueuer.ts +167 -0
  174. package/src/queuer/index.ts +3 -0
  175. package/src/rate-limiter/createRateLimitedSignal.ts +74 -0
  176. package/src/rate-limiter/createRateLimitedValue.ts +66 -0
  177. package/src/rate-limiter/createRateLimiter.ts +115 -0
  178. package/src/rate-limiter/index.ts +5 -0
  179. package/src/throttler/createThrottledSignal.ts +52 -0
  180. package/src/throttler/createThrottledValue.ts +56 -0
  181. package/src/throttler/createThrottler.ts +106 -0
  182. package/src/throttler/index.ts +6 -0
  183. package/src/types/index.ts +1 -0
  184. package/src/utils/index.ts +1 -0
@@ -0,0 +1,3 @@
1
+ export * from '@tanstack/pacer/async-queuer'
2
+
3
+ export * from './createAsyncQueuer'
@@ -0,0 +1,116 @@
1
+ import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter'
2
+ import { createSignal } from 'solid-js'
3
+ import { bindInstanceMethods } from '@tanstack/pacer/utils'
4
+ import type { Accessor } from 'solid-js'
5
+ import type { AnyAsyncFunction } from '@tanstack/pacer/types'
6
+ import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter'
7
+
8
+ export interface SolidAsyncRateLimiter<
9
+ TFn extends AnyAsyncFunction,
10
+ TArgs extends Parameters<TFn>,
11
+ > extends Omit<
12
+ AsyncRateLimiter<TFn, TArgs>,
13
+ | 'getExecutionCount'
14
+ | 'getRejectionCount'
15
+ | 'getRemainingInWindow'
16
+ | 'getMsUntilNextWindow'
17
+ > {
18
+ executionCount: Accessor<number>
19
+ rejectionCount: Accessor<number>
20
+ remainingInWindow: Accessor<number>
21
+ msUntilNextWindow: Accessor<number>
22
+ }
23
+
24
+ /**
25
+ * A low-level Solid hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window.
26
+ *
27
+ * This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that
28
+ * you can integrate with any state management solution (createSignal, etc).
29
+ *
30
+ * Rate limiting allows an async function to execute up to a specified limit within a time window,
31
+ * then blocks subsequent calls until the window passes. This is useful for respecting API rate limits,
32
+ * managing resource constraints, or controlling bursts of async operations.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * // Basic API call rate limiting
37
+ * const { maybeExecute } = createAsyncRateLimiter(
38
+ * async (id: string) => {
39
+ * const data = await api.fetchData(id);
40
+ * return data;
41
+ * },
42
+ * { limit: 5, window: 1000 } // 5 calls per second
43
+ * );
44
+ *
45
+ * // With state management
46
+ * const [data, setData] = createSignal(null);
47
+ * const { maybeExecute } = createAsyncRateLimiter(
48
+ * async (query) => {
49
+ * const result = await searchAPI(query);
50
+ * setData(result);
51
+ * },
52
+ * {
53
+ * limit: 10,
54
+ * window: 60000, // 10 calls per minute
55
+ * onReject: (info) => console.log(`Rate limit exceeded: ${info.nextValidTime - Date.now()}ms until next window`)
56
+ * }
57
+ * );
58
+ * ```
59
+ */
60
+ export function createAsyncRateLimiter<
61
+ TFn extends AnyAsyncFunction,
62
+ TArgs extends Parameters<TFn>,
63
+ >(
64
+ fn: TFn,
65
+ initialOptions: AsyncRateLimiterOptions<TFn, TArgs>,
66
+ ): SolidAsyncRateLimiter<TFn, TArgs> {
67
+ const asyncRateLimiter = new AsyncRateLimiter<TFn, TArgs>(fn, initialOptions)
68
+
69
+ const [executionCount, setExecutionCount] = createSignal(
70
+ asyncRateLimiter.getExecutionCount(),
71
+ )
72
+ const [rejectionCount, setRejectionCount] = createSignal(
73
+ asyncRateLimiter.getRejectionCount(),
74
+ )
75
+ const [remainingInWindow, setRemainingInWindow] = createSignal(
76
+ asyncRateLimiter.getRemainingInWindow(),
77
+ )
78
+ const [msUntilNextWindow, setMsUntilNextWindow] = createSignal(
79
+ asyncRateLimiter.getMsUntilNextWindow(),
80
+ )
81
+
82
+ function setOptions(
83
+ newOptions: Partial<AsyncRateLimiterOptions<TFn, TArgs>>,
84
+ ) {
85
+ asyncRateLimiter.setOptions({
86
+ ...newOptions,
87
+ onExecute: (rateLimiter) => {
88
+ setExecutionCount(rateLimiter.getExecutionCount())
89
+ setRemainingInWindow(rateLimiter.getRemainingInWindow())
90
+ setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow())
91
+
92
+ const onExecute = newOptions.onExecute ?? initialOptions.onExecute
93
+ onExecute?.(rateLimiter)
94
+ },
95
+ onReject: (rateLimiter) => {
96
+ setRejectionCount(rateLimiter.getRejectionCount())
97
+ setRemainingInWindow(rateLimiter.getRemainingInWindow())
98
+ setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow())
99
+
100
+ const onReject = newOptions.onReject ?? initialOptions.onReject
101
+ onReject?.(rateLimiter)
102
+ },
103
+ })
104
+ }
105
+
106
+ setOptions(initialOptions)
107
+
108
+ return {
109
+ ...bindInstanceMethods(asyncRateLimiter),
110
+ executionCount,
111
+ rejectionCount,
112
+ remainingInWindow,
113
+ msUntilNextWindow,
114
+ setOptions,
115
+ }
116
+ }
@@ -0,0 +1,4 @@
1
+ // re-export everything from the core pacer package, BUT ONLY from the async-rate-limiter module
2
+ export * from '@tanstack/pacer/async-rate-limiter'
3
+
4
+ export * from './createAsyncRateLimiter'
@@ -0,0 +1,106 @@
1
+ import { AsyncThrottler } from '@tanstack/pacer/async-throttler'
2
+ import { createSignal } from 'solid-js'
3
+ import { bindInstanceMethods } from '@tanstack/pacer/utils'
4
+ import type { Accessor } from 'solid-js'
5
+ import type { AnyAsyncFunction } from '@tanstack/pacer/types'
6
+ import type { AsyncThrottlerOptions } from '@tanstack/pacer/async-throttler'
7
+
8
+ export interface SolidAsyncThrottler<
9
+ TFn extends AnyAsyncFunction,
10
+ TArgs extends Parameters<TFn>,
11
+ > extends Omit<
12
+ AsyncThrottler<TFn, TArgs>,
13
+ | 'getExecutionCount'
14
+ | 'getIsPending'
15
+ | 'getLastExecutionTime'
16
+ | 'getNextExecutionTime'
17
+ > {
18
+ executionCount: Accessor<number>
19
+ isPending: Accessor<boolean>
20
+ lastExecutionTime: Accessor<number>
21
+ nextExecutionTime: Accessor<number>
22
+ }
23
+
24
+ /**
25
+ * A low-level Solid hook that creates an `AsyncThrottler` instance to limit how often an async function can execute.
26
+ *
27
+ * This hook is designed to be flexible and state-management agnostic - it simply returns a throttler instance that
28
+ * you can integrate with any state management solution (createSignal, etc).
29
+ *
30
+ * Async throttling ensures an async function executes at most once within a specified time window,
31
+ * regardless of how many times it is called. This is useful for rate-limiting expensive API calls,
32
+ * database operations, or other async tasks.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * // Basic API call throttling
37
+ * const { maybeExecute } = createAsyncThrottler(
38
+ * async (id: string) => {
39
+ * const data = await api.fetchData(id);
40
+ * return data;
41
+ * },
42
+ * { wait: 1000 }
43
+ * );
44
+ *
45
+ * // With state management
46
+ * const [data, setData] = createSignal(null);
47
+ * const { maybeExecute } = createAsyncThrottler(
48
+ * async (query) => {
49
+ * const result = await searchAPI(query);
50
+ * setData(result);
51
+ * },
52
+ * {
53
+ * wait: 2000,
54
+ * leading: true, // Execute immediately on first call
55
+ * trailing: false // Skip trailing edge updates
56
+ * }
57
+ * );
58
+ * ```
59
+ */
60
+
61
+ export function createAsyncThrottler<
62
+ TFn extends AnyAsyncFunction,
63
+ TArgs extends Parameters<TFn>,
64
+ >(
65
+ fn: TFn,
66
+ initialOptions: AsyncThrottlerOptions<TFn, TArgs>,
67
+ ): SolidAsyncThrottler<TFn, TArgs> {
68
+ const asyncThrottler = new AsyncThrottler<TFn, TArgs>(fn, initialOptions)
69
+
70
+ const [executionCount, setExecutionCount] = createSignal(
71
+ asyncThrottler.getExecutionCount(),
72
+ )
73
+ const [isPending, setIsPending] = createSignal(asyncThrottler.getIsPending())
74
+ const [lastExecutionTime, setLastExecutionTime] = createSignal(
75
+ asyncThrottler.getLastExecutionTime(),
76
+ )
77
+ const [nextExecutionTime, setNextExecutionTime] = createSignal(
78
+ asyncThrottler.getNextExecutionTime(),
79
+ )
80
+
81
+ function setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn, TArgs>>) {
82
+ asyncThrottler.setOptions({
83
+ ...newOptions,
84
+ onExecute: (throttler) => {
85
+ setExecutionCount(throttler.getExecutionCount())
86
+ setIsPending(throttler.getIsPending())
87
+ setLastExecutionTime(throttler.getLastExecutionTime())
88
+ setNextExecutionTime(throttler.getNextExecutionTime())
89
+
90
+ const onExecute = newOptions.onExecute ?? initialOptions.onExecute
91
+ onExecute?.(throttler)
92
+ },
93
+ })
94
+ }
95
+
96
+ setOptions(initialOptions)
97
+
98
+ return {
99
+ ...bindInstanceMethods(asyncThrottler),
100
+ executionCount,
101
+ isPending,
102
+ lastExecutionTime,
103
+ nextExecutionTime,
104
+ setOptions,
105
+ }
106
+ }
@@ -0,0 +1,4 @@
1
+ // re-export everything from the core pacer package, BUT ONLY from the async-throttler module
2
+ export * from '@tanstack/pacer/async-throttler'
3
+
4
+ export * from './createAsyncThrottler'
@@ -0,0 +1 @@
1
+ export * from '@tanstack/pacer/compare'
@@ -0,0 +1,59 @@
1
+ import { createSignal } from 'solid-js'
2
+ import { createDebouncer } from './createDebouncer'
3
+ import type { SolidDebouncer } from './createDebouncer'
4
+ import type { Accessor, Setter } from 'solid-js'
5
+ import type { DebouncerOptions } from '@tanstack/pacer/debouncer'
6
+
7
+ /**
8
+ * A Solid hook that creates a debounced state value, combining Solid's createSignal with debouncing functionality.
9
+ * This hook provides both the current debounced value and methods to update it.
10
+ *
11
+ * The state value is only updated after the specified wait time has elapsed since the last update attempt.
12
+ * If another update is attempted before the wait time expires, the timer resets and starts waiting again.
13
+ * This is useful for handling frequent state updates that should be throttled, like search input values
14
+ * or window resize dimensions.
15
+ *
16
+ * The hook returns a tuple containing:
17
+ * - The current debounced value accessor
18
+ * - A function to update the debounced value
19
+ * - The debouncer instance with additional control methods and state signals
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // Debounced search input
24
+ * const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal('', {
25
+ * wait: 500 // Wait 500ms after last keystroke
26
+ * });
27
+ *
28
+ * // Update value - will be debounced
29
+ * const handleChange = (e) => {
30
+ * setSearchTerm(e.target.value);
31
+ * };
32
+ *
33
+ * // Access debouncer state via signals
34
+ * console.log('Executions:', debouncer.executionCount());
35
+ * console.log('Is pending:', debouncer.isPending());
36
+ *
37
+ * // In onExecute callback, use get* methods
38
+ * const [searchTerm, setSearchTerm, debouncer] = createDebouncedSignal('', {
39
+ * wait: 500,
40
+ * onExecute: (debouncer) => {
41
+ * console.log('Total executions:', debouncer.getExecutionCount());
42
+ * }
43
+ * });
44
+ * ```
45
+ */
46
+ export function createDebouncedSignal<TValue>(
47
+ value: TValue,
48
+ initialOptions: DebouncerOptions<Setter<TValue>, [Accessor<TValue>]>,
49
+ ): [
50
+ Accessor<TValue>,
51
+ Setter<TValue>,
52
+ SolidDebouncer<Setter<TValue>, [Accessor<TValue>]>,
53
+ ] {
54
+ const [debouncedValue, setDebouncedValue] = createSignal<TValue>(value)
55
+
56
+ const debouncer = createDebouncer(setDebouncedValue, initialOptions)
57
+
58
+ return [debouncedValue, debouncer.maybeExecute as Setter<TValue>, debouncer]
59
+ }
@@ -0,0 +1,64 @@
1
+ import { createEffect, onCleanup } from 'solid-js'
2
+ import { createDebouncedSignal } from './createDebouncedSignal'
3
+ import type { SolidDebouncer } from './createDebouncer'
4
+ import type { Accessor, Setter } from 'solid-js'
5
+ import type { DebouncerOptions } from '@tanstack/pacer/debouncer'
6
+
7
+ /**
8
+ * A Solid hook that creates a debounced value that updates only after a specified delay.
9
+ * Unlike createDebouncedSignal, this hook automatically tracks changes to the input value
10
+ * and updates the debounced value accordingly.
11
+ *
12
+ * The debounced value will only update after the specified wait time has elapsed since
13
+ * the last change to the input value. If the input value changes again before the wait
14
+ * time expires, the timer resets and starts waiting again.
15
+ *
16
+ * This is useful for deriving debounced values from props or state that change frequently,
17
+ * like search queries or form inputs, where you want to limit how often downstream effects
18
+ * or calculations occur.
19
+ *
20
+ * The hook returns a tuple containing:
21
+ * - The current debounced value (as an Accessor)
22
+ * - The debouncer instance with control methods and state signals
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // Debounce a search query
27
+ * const [searchQuery, setSearchQuery] = createSignal('');
28
+ * const [debouncedQuery, debouncer] = createDebouncedValue(searchQuery, {
29
+ * wait: 500 // Wait 500ms after last change
30
+ * });
31
+ *
32
+ * // debouncedQuery will update 500ms after searchQuery stops changing
33
+ * createEffect(() => {
34
+ * fetchSearchResults(debouncedQuery());
35
+ * });
36
+ *
37
+ * // Access debouncer state via signals
38
+ * console.log('Executions:', debouncer.executionCount());
39
+ * console.log('Is pending:', debouncer.isPending());
40
+ *
41
+ * // Handle input changes
42
+ * const handleChange = (e) => {
43
+ * setSearchQuery(e.target.value);
44
+ * };
45
+ * ```
46
+ */
47
+ export function createDebouncedValue<TValue>(
48
+ value: Accessor<TValue>,
49
+ initialOptions: DebouncerOptions<Setter<TValue>, [Accessor<TValue>]>,
50
+ ): [Accessor<TValue>, SolidDebouncer<Setter<TValue>, [Accessor<TValue>]>] {
51
+ const [debouncedValue, setDebouncedValue, debouncer] = createDebouncedSignal(
52
+ value(),
53
+ initialOptions,
54
+ )
55
+
56
+ createEffect(() => {
57
+ setDebouncedValue(value() as any)
58
+ onCleanup(() => {
59
+ debouncer.cancel()
60
+ })
61
+ })
62
+
63
+ return [debouncedValue, debouncer]
64
+ }
@@ -0,0 +1,90 @@
1
+ import { Debouncer } from '@tanstack/pacer/debouncer'
2
+ import { createSignal } from 'solid-js'
3
+ import { bindInstanceMethods } from '@tanstack/pacer/utils'
4
+ import type { Accessor } from 'solid-js'
5
+ import type { AnyFunction } from '@tanstack/pacer/types'
6
+ import type { DebouncerOptions } from '@tanstack/pacer/debouncer'
7
+
8
+ /**
9
+ * An extension of the Debouncer class that adds Solid signals to access the internal state of the debouncer
10
+ */
11
+ export interface SolidDebouncer<
12
+ TFn extends AnyFunction,
13
+ TArgs extends Parameters<TFn>,
14
+ > extends Omit<Debouncer<TFn, TArgs>, 'getExecutionCount' | 'getIsPending'> {
15
+ executionCount: Accessor<number>
16
+ isPending: Accessor<boolean>
17
+ }
18
+
19
+ /**
20
+ * A Solid hook that creates and manages a Debouncer instance.
21
+ *
22
+ * This is a lower-level hook that provides direct access to the Debouncer's functionality without
23
+ * any built-in state management. This allows you to integrate it with any state management solution
24
+ * you prefer (createSignal, Redux, Zustand, etc.).
25
+ *
26
+ * This hook provides debouncing functionality to limit how often a function can be called,
27
+ * waiting for a specified delay before executing the latest call. This is useful for handling
28
+ * frequent events like window resizing, scroll events, or real-time search inputs.
29
+ *
30
+ * The debouncer will only execute the function after the specified wait time has elapsed
31
+ * since the last call. If the function is called again before the wait time expires, the
32
+ * timer resets and starts waiting again.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * // Debounce a search function to limit API calls
37
+ * const debouncer = createDebouncer(
38
+ * (query: string) => fetchSearchResults(query),
39
+ * { wait: 500 } // Wait 500ms after last keystroke
40
+ * );
41
+ *
42
+ * // In an event handler
43
+ * const handleChange = (e) => {
44
+ * debouncer.maybeExecute(e.target.value);
45
+ * };
46
+ *
47
+ * // Access debouncer state via signals
48
+ * console.log('Executions:', debouncer.executionCount());
49
+ * console.log('Is pending:', debouncer.isPending());
50
+ *
51
+ * // Update options
52
+ * debouncer.setOptions({ wait: 1000 });
53
+ * ```
54
+ */
55
+ export function createDebouncer<
56
+ TFn extends AnyFunction,
57
+ TArgs extends Parameters<TFn>,
58
+ >(
59
+ fn: TFn,
60
+ initialOptions: DebouncerOptions<TFn, TArgs>,
61
+ ): SolidDebouncer<TFn, TArgs> {
62
+ const debouncer = new Debouncer<TFn, TArgs>(fn, initialOptions)
63
+
64
+ const [executionCount, setExecutionCount] = createSignal(
65
+ debouncer.getExecutionCount(),
66
+ )
67
+ const [isPending, setIsPending] = createSignal(debouncer.getIsPending())
68
+
69
+ function setOptions(newOptions: Partial<DebouncerOptions<TFn, TArgs>>) {
70
+ debouncer.setOptions({
71
+ ...newOptions,
72
+ onExecute: (debouncer) => {
73
+ setExecutionCount(debouncer.getExecutionCount())
74
+ setIsPending(debouncer.getIsPending())
75
+
76
+ const onExecute = newOptions.onExecute ?? initialOptions.onExecute
77
+ onExecute?.(debouncer)
78
+ },
79
+ })
80
+ }
81
+
82
+ setOptions(initialOptions)
83
+
84
+ return {
85
+ ...bindInstanceMethods(debouncer),
86
+ executionCount,
87
+ isPending,
88
+ setOptions,
89
+ }
90
+ }
@@ -0,0 +1,6 @@
1
+ // re-export everything from the core pacer package, BUT ONLY from the debouncer module
2
+ export * from '@tanstack/pacer/debouncer'
3
+
4
+ export * from './createDebouncedSignal'
5
+ export * from './createDebouncedValue'
6
+ export * from './createDebouncer'
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ // re-export everything from the core pacer package
2
+ export * from '@tanstack/pacer'
3
+
4
+ /**
5
+ * Export every hook individually - DON'T export from barrel files
6
+ */
7
+
8
+ // async-debouncer
9
+ export * from './async-debouncer/createAsyncDebouncer'
10
+
11
+ // async-queuer
12
+ export * from './async-queuer/createAsyncQueuer'
13
+
14
+ // async-rate-limiter
15
+ export * from './async-rate-limiter/createAsyncRateLimiter'
16
+
17
+ // async-throttler
18
+ export * from './async-throttler/createAsyncThrottler'
19
+
20
+ // debouncer
21
+ export * from './debouncer/createDebouncedSignal'
22
+ export * from './debouncer/createDebouncedValue'
23
+ export * from './debouncer/createDebouncer'
24
+
25
+ // queuer
26
+ export * from './queuer/createQueuer'
27
+
28
+ // rate-limiter
29
+ export * from './rate-limiter/createRateLimiter'
30
+ export * from './rate-limiter/createRateLimitedSignal'
31
+ export * from './rate-limiter/createRateLimitedValue'
32
+
33
+ // throttler
34
+ export * from './throttler/createThrottledSignal'
35
+ export * from './throttler/createThrottledValue'
36
+ export * from './throttler/createThrottler'
@@ -0,0 +1,167 @@
1
+ import { Queuer } from '@tanstack/pacer/queuer'
2
+ import { createSignal } from 'solid-js'
3
+ import { bindInstanceMethods } from '@tanstack/pacer/utils'
4
+ import type { Accessor } from 'solid-js'
5
+ import type { QueuerOptions } from '@tanstack/pacer/queuer'
6
+
7
+ export interface SolidQueuer<TValue>
8
+ extends Omit<
9
+ Queuer<TValue>,
10
+ | 'getAllItems'
11
+ | 'getExecutionCount'
12
+ | 'getIsEmpty'
13
+ | 'getIsFull'
14
+ | 'getIsIdle'
15
+ | 'getIsRunning'
16
+ | 'getPeek'
17
+ | 'getSize'
18
+ > {
19
+ /**
20
+ * Signal version of `getAllItems`
21
+ */
22
+ allItems: Accessor<Array<TValue>>
23
+ /**
24
+ * Signal version of `getExecutionCount`
25
+ */
26
+ executionCount: Accessor<number>
27
+ /**
28
+ * Signal version of `getIsEmpty`
29
+ */
30
+ isEmpty: Accessor<boolean>
31
+ /**
32
+ * Signal version of `getIsFull`
33
+ */
34
+ isFull: Accessor<boolean>
35
+ /**
36
+ * Signal version of `getIsIdle`
37
+ */
38
+ isIdle: Accessor<boolean>
39
+ /**
40
+ * Signal version of `getIsRunning`
41
+ */
42
+ isRunning: Accessor<boolean>
43
+ /**
44
+ * Signal version of `getPeek`
45
+ */
46
+ peek: Accessor<TValue | undefined>
47
+ /**
48
+ * Signal version of `getSize`
49
+ */
50
+ size: Accessor<number>
51
+ }
52
+
53
+ /**
54
+ * A Solid hook that creates and manages a Queuer instance.
55
+ *
56
+ * This is a lower-level hook that provides direct access to the Queuer's functionality without
57
+ * any built-in state management. This allows you to integrate it with any state management solution
58
+ * you prefer (createSignal, Redux, Zustand, etc.) by utilizing the onItemsChange callback.
59
+ *
60
+ * For a hook with built-in state management, see createQueuerSignal.
61
+ *
62
+ * The Queuer extends the base Queue to add processing capabilities. Items are processed
63
+ * synchronously in order, with optional delays between processing each item. The queuer includes
64
+ * an internal tick mechanism that can be started and stopped, making it useful as a scheduler.
65
+ * When started, it will process one item per tick, with an optional wait time between ticks.
66
+ *
67
+ * By default uses FIFO (First In First Out) behavior, but can be configured for LIFO
68
+ * (Last In First Out) by specifying 'front' position when adding items.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * // Example with custom state management and scheduling
73
+ * const [items, setItems] = createSignal([]);
74
+ *
75
+ * const queue = createQueuer({
76
+ * started: true, // Start processing immediately
77
+ * wait: 1000, // Process one item every second
78
+ * onItemsChange: (queue) => setItems(queue.getAllItems()),
79
+ * getPriority: (item) => item.priority // Process higher priority items first
80
+ * });
81
+ *
82
+ * // Add items to process - they'll be handled automatically
83
+ * queue.addItem('task1');
84
+ * queue.addItem('task2');
85
+ *
86
+ * // Control the scheduler
87
+ * queue.stop(); // Pause processing
88
+ * queue.start(); // Resume processing
89
+ *
90
+ * // Access queue state via signals
91
+ * console.log('Items:', queue.allItems());
92
+ * console.log('Size:', queue.size());
93
+ * console.log('Is empty:', queue.isEmpty());
94
+ * console.log('Is running:', queue.isRunning());
95
+ * console.log('Next item:', queue.peek());
96
+ * ```
97
+ */
98
+ export function createQueuer<TValue>(
99
+ initialOptions: QueuerOptions<TValue> = {},
100
+ ): SolidQueuer<TValue> {
101
+ const queuer = new Queuer<TValue>(initialOptions)
102
+
103
+ const [allItems, setAllItems] = createSignal<Array<TValue>>(
104
+ queuer.getAllItems(),
105
+ )
106
+ const [executionCount, setExecutionCount] = createSignal(
107
+ queuer.getExecutionCount(),
108
+ )
109
+ const [rejectionCount, setRejectionCount] = createSignal(
110
+ queuer.getRejectionCount(),
111
+ )
112
+ const [isEmpty, setIsEmpty] = createSignal(queuer.getIsEmpty())
113
+ const [isFull, setIsFull] = createSignal(queuer.getIsFull())
114
+ const [isIdle, setIsIdle] = createSignal(queuer.getIsIdle())
115
+ const [isRunning, setIsRunning] = createSignal(queuer.getIsRunning())
116
+ const [peek, setPeek] = createSignal<TValue | undefined>(queuer.getPeek())
117
+ const [size, setSize] = createSignal(queuer.getSize())
118
+
119
+ function setOptions(newOptions: Partial<QueuerOptions<TValue>>) {
120
+ queuer.setOptions({
121
+ ...newOptions,
122
+ onItemsChange: (queuer) => {
123
+ setAllItems(queuer.getAllItems())
124
+ setExecutionCount(queuer.getExecutionCount())
125
+ setIsEmpty(queuer.getIsEmpty())
126
+ setIsFull(queuer.getIsFull())
127
+ setIsIdle(queuer.getIsIdle())
128
+ setPeek(() => queuer.getPeek())
129
+ setSize(queuer.getSize())
130
+
131
+ const onItemsChange =
132
+ newOptions.onItemsChange ?? initialOptions.onItemsChange
133
+ onItemsChange?.(queuer)
134
+ },
135
+ onIsRunningChange: (queuer) => {
136
+ setIsRunning(queuer.getIsRunning())
137
+ setIsIdle(queuer.getIsIdle())
138
+
139
+ const onIsRunningChange =
140
+ newOptions.onIsRunningChange ?? initialOptions.onIsRunningChange
141
+ onIsRunningChange?.(queuer)
142
+ },
143
+ onReject: (item, queuer) => {
144
+ setRejectionCount(queuer.getRejectionCount())
145
+
146
+ const onReject = newOptions.onReject ?? initialOptions.onReject
147
+ onReject?.(item, queuer)
148
+ },
149
+ })
150
+ }
151
+
152
+ setOptions(initialOptions)
153
+
154
+ return {
155
+ ...bindInstanceMethods(queuer),
156
+ allItems,
157
+ executionCount,
158
+ isEmpty,
159
+ isFull,
160
+ isIdle,
161
+ isRunning,
162
+ peek,
163
+ rejectionCount,
164
+ size,
165
+ setOptions,
166
+ }
167
+ }
@@ -0,0 +1,3 @@
1
+ export * from '@tanstack/pacer/queuer'
2
+
3
+ export * from './createQueuer'