@tanstack/solid-pacer 0.4.0 → 0.6.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 (28) hide show
  1. package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs.map +1 -1
  2. package/dist/cjs/async-debouncer/createAsyncDebouncer.d.cts +21 -2
  3. package/dist/cjs/async-queuer/createAsyncQueuer.cjs +24 -13
  4. package/dist/cjs/async-queuer/createAsyncQueuer.cjs.map +1 -1
  5. package/dist/cjs/async-queuer/createAsyncQueuer.d.cts +40 -32
  6. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs.map +1 -1
  7. package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.d.cts +22 -7
  8. package/dist/cjs/async-throttler/createAsyncThrottler.cjs.map +1 -1
  9. package/dist/cjs/async-throttler/createAsyncThrottler.d.cts +15 -1
  10. package/dist/cjs/rate-limiter/createRateLimiter.cjs.map +1 -1
  11. package/dist/cjs/rate-limiter/createRateLimiter.d.cts +4 -10
  12. package/dist/esm/async-debouncer/createAsyncDebouncer.d.ts +21 -2
  13. package/dist/esm/async-debouncer/createAsyncDebouncer.js.map +1 -1
  14. package/dist/esm/async-queuer/createAsyncQueuer.d.ts +40 -32
  15. package/dist/esm/async-queuer/createAsyncQueuer.js +24 -13
  16. package/dist/esm/async-queuer/createAsyncQueuer.js.map +1 -1
  17. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.d.ts +22 -7
  18. package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js.map +1 -1
  19. package/dist/esm/async-throttler/createAsyncThrottler.d.ts +15 -1
  20. package/dist/esm/async-throttler/createAsyncThrottler.js.map +1 -1
  21. package/dist/esm/rate-limiter/createRateLimiter.d.ts +4 -10
  22. package/dist/esm/rate-limiter/createRateLimiter.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/async-debouncer/createAsyncDebouncer.ts +21 -3
  25. package/src/async-queuer/createAsyncQueuer.ts +76 -55
  26. package/src/async-rate-limiter/createAsyncRateLimiter.ts +22 -7
  27. package/src/async-throttler/createAsyncThrottler.ts +15 -2
  28. package/src/rate-limiter/createRateLimiter.ts +4 -10
@@ -2,34 +2,40 @@ import { AsyncQueuer } from '@tanstack/pacer/async-queuer'
2
2
  import { createSignal } from 'solid-js'
3
3
  import { bindInstanceMethods } from '@tanstack/pacer/utils'
4
4
  import type { Accessor } from 'solid-js'
5
- import type { AsyncQueuerOptions } from '@tanstack/pacer/async-queuer'
5
+ import type {
6
+ AsyncQueuerFn,
7
+ AsyncQueuerOptions,
8
+ } from '@tanstack/pacer/async-queuer'
6
9
 
7
- export interface SolidAsyncQueuer<TValue>
10
+ export interface SolidAsyncQueuer<TFn extends AsyncQueuerFn>
8
11
  extends Omit<
9
- AsyncQueuer<TValue>,
12
+ AsyncQueuer<TFn>,
10
13
  | 'getActiveItems'
11
14
  | 'getAllItems'
12
- | 'getExecutionCount'
15
+ | 'getErrorCount'
13
16
  | 'getIsEmpty'
14
17
  | 'getIsFull'
15
18
  | 'getIsIdle'
16
19
  | 'getIsRunning'
17
20
  | 'getPeek'
18
21
  | 'getPendingItems'
22
+ | 'getRejectionCount'
23
+ | 'getSettledCount'
19
24
  | 'getSize'
25
+ | 'getSuccessCount'
20
26
  > {
21
27
  /**
22
28
  * Signal version of `getActiveItems`
23
29
  */
24
- activeItems: Accessor<Array<() => Promise<TValue>>>
30
+ activeItems: Accessor<Array<TFn>>
25
31
  /**
26
32
  * Signal version of `getAllItems`
27
33
  */
28
- allItems: Accessor<Array<() => Promise<TValue>>>
34
+ allItems: Accessor<Array<TFn>>
29
35
  /**
30
- * Signal version of `getExecutionCount`
36
+ * Signal version of `getErrorCount`
31
37
  */
32
- executionCount: Accessor<number>
38
+ errorCount: Accessor<number>
33
39
  /**
34
40
  * Signal version of `getIsEmpty`
35
41
  */
@@ -49,39 +55,50 @@ export interface SolidAsyncQueuer<TValue>
49
55
  /**
50
56
  * Signal version of `getPeek`
51
57
  */
52
- peek: Accessor<(() => Promise<TValue>) | undefined>
58
+ peek: Accessor<TFn | undefined>
53
59
  /**
54
60
  * Signal version of `getPendingItems`
55
61
  */
56
- pendingItems: Accessor<Array<() => Promise<TValue>>>
62
+ pendingItems: Accessor<Array<TFn>>
57
63
  /**
58
64
  * Signal version of `getRejectionCount`
59
65
  */
60
66
  rejectionCount: Accessor<number>
67
+ /**
68
+ * Signal version of `getSettledCount`
69
+ */
70
+ settledCount: Accessor<number>
61
71
  /**
62
72
  * Signal version of `getSize`
63
73
  */
64
74
  size: Accessor<number>
75
+ /**
76
+ * Signal version of `getSuccessCount`
77
+ */
78
+ successCount: Accessor<number>
65
79
  }
66
80
 
67
81
  /**
68
- * A lower-level React hook that creates an `AsyncQueuer` instance for managing an async queue of items.
82
+ * A lower-level Solid hook that creates an `AsyncQueuer` instance for managing an async queue of items.
69
83
  *
70
- * This hook provides a flexible, state-management agnostic way to handle queued async operations.
71
- * It returns a queuer instance with methods to add items, control queue execution, and monitor queue state.
84
+ * Features:
85
+ * - Priority queue support via getPriority option
86
+ * - Configurable concurrency limit
87
+ * - Task success/error/completion callbacks
88
+ * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior
89
+ * - Pause/resume task processing
90
+ * - Task cancellation
91
+ * - Item expiration to clear stale items from the queue
72
92
  *
73
- * The queue can be configured with:
74
- * - Maximum concurrent operations
75
- * - Maximum queue size
76
- * - Processing function for queue items
77
- * - Various lifecycle callbacks
93
+ * Tasks are processed concurrently up to the configured concurrency limit. When a task completes,
94
+ * the next pending task is processed if below the concurrency limit.
78
95
  *
79
- * The hook returns an object containing methods to:
80
- * - Add/remove items from the queue
81
- * - Start/stop queue processing
82
- * - Get queue status and items
83
- * - Register event handlers
84
- * - Control execution throttling
96
+ * Error Handling:
97
+ * - If an `onError` handler is provided, it will be called with the error and queuer instance
98
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
99
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
100
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
101
+ * - The error state can be checked using the underlying AsyncQueuer instance
85
102
  *
86
103
  * @example
87
104
  * ```tsx
@@ -91,6 +108,12 @@ export interface SolidAsyncQueuer<TValue>
91
108
  * concurrency: 2,
92
109
  * maxSize: 100,
93
110
  * started: false,
111
+ * onSuccess: (result) => {
112
+ * console.log('Item processed:', result);
113
+ * },
114
+ * onError: (error) => {
115
+ * console.error('Processing failed:', error);
116
+ * }
94
117
  * });
95
118
  *
96
119
  * // Add items to queue
@@ -98,24 +121,19 @@ export interface SolidAsyncQueuer<TValue>
98
121
  *
99
122
  * // Start processing
100
123
  * asyncQueuer.start();
101
- *
102
- * // Handle results
103
- * asyncQueuer.onSuccess((result) => {
104
- * console.log('Item processed:', result);
105
- * });
106
- *
107
- * asyncQueuer.onError((error) => {
108
- * console.error('Processing failed:', error);
109
- * });
110
124
  * ```
111
125
  */
112
- export function createAsyncQueuer<TValue>(
113
- initialOptions: AsyncQueuerOptions<TValue> = {},
114
- ): SolidAsyncQueuer<TValue> {
115
- const asyncQueuer = new AsyncQueuer<TValue>(initialOptions)
126
+ export function createAsyncQueuer<TFn extends AsyncQueuerFn>(
127
+ initialOptions: AsyncQueuerOptions<TFn> = {},
128
+ ): SolidAsyncQueuer<TFn> {
129
+ const asyncQueuer = new AsyncQueuer<TFn>(initialOptions)
116
130
 
117
- const [executionCount, setExecutionCount] = createSignal(
118
- asyncQueuer.getExecutionCount(),
131
+ const [successCount, setSuccessCount] = createSignal(
132
+ asyncQueuer.getSuccessCount(),
133
+ )
134
+ const [errorCount, setErrorCount] = createSignal(asyncQueuer.getErrorCount())
135
+ const [settledCount, setSettledCount] = createSignal(
136
+ asyncQueuer.getSettledCount(),
119
137
  )
120
138
  const [rejectionCount, setRejectionCount] = createSignal(
121
139
  asyncQueuer.getRejectionCount(),
@@ -124,31 +142,32 @@ export function createAsyncQueuer<TValue>(
124
142
  const [isFull, setIsFull] = createSignal(asyncQueuer.getIsFull())
125
143
  const [isIdle, setIsIdle] = createSignal(asyncQueuer.getIsIdle())
126
144
  const [isRunning, setIsRunning] = createSignal(asyncQueuer.getIsRunning())
127
- const [allItems, setAllItems] = createSignal<Array<() => Promise<TValue>>>(
145
+ const [allItems, setAllItems] = createSignal<Array<TFn>>(
128
146
  asyncQueuer.getAllItems(),
129
147
  )
130
- const [activeItems, setActiveItems] = createSignal<
131
- Array<() => Promise<TValue>>
132
- >(asyncQueuer.getActiveItems())
133
- const [pendingItems, setPendingItems] = createSignal<
134
- Array<() => Promise<TValue>>
135
- >(asyncQueuer.getPendingItems())
136
- const [peek, setPeek] = createSignal<(() => Promise<TValue>) | undefined>(
137
- asyncQueuer.getPeek(),
148
+ const [activeItems, setActiveItems] = createSignal<Array<TFn>>(
149
+ asyncQueuer.getActiveItems(),
150
+ )
151
+ const [pendingItems, setPendingItems] = createSignal<Array<TFn>>(
152
+ asyncQueuer.getPendingItems(),
138
153
  )
154
+ const [peek, setPeek] = createSignal<TFn | undefined>(asyncQueuer.getPeek())
139
155
  const [size, setSize] = createSignal(asyncQueuer.getSize())
140
156
 
141
157
  asyncQueuer.setOptions({
142
158
  onItemsChange: (queuer) => {
143
- setExecutionCount(queuer.getExecutionCount())
159
+ setActiveItems(queuer.getActiveItems())
160
+ setAllItems(queuer.getAllItems())
161
+ setErrorCount(queuer.getErrorCount())
144
162
  setIsEmpty(queuer.getIsEmpty())
145
163
  setIsFull(queuer.getIsFull())
146
164
  setIsIdle(queuer.getIsIdle())
147
- setAllItems(queuer.getAllItems())
148
- setActiveItems(queuer.getActiveItems())
149
- setPendingItems(queuer.getPendingItems())
150
165
  setPeek(() => queuer.getPeek())
166
+ setPendingItems(queuer.getPendingItems())
167
+ setRejectionCount(queuer.getRejectionCount())
168
+ setSettledCount(queuer.getSettledCount())
151
169
  setSize(queuer.getSize())
170
+ setSuccessCount(queuer.getSuccessCount())
152
171
  initialOptions.onItemsChange?.(queuer)
153
172
  },
154
173
  onIsRunningChange: (queuer) => {
@@ -165,15 +184,17 @@ export function createAsyncQueuer<TValue>(
165
184
  return {
166
185
  ...bindInstanceMethods(asyncQueuer),
167
186
  activeItems,
168
- executionCount,
187
+ allItems,
188
+ errorCount,
169
189
  isEmpty,
170
190
  isFull,
171
191
  isIdle,
172
192
  isRunning,
173
- allItems,
174
193
  peek,
175
194
  pendingItems,
176
195
  rejectionCount,
196
+ settledCount,
177
197
  size,
178
- } as SolidAsyncQueuer<TValue>
198
+ successCount,
199
+ } as SolidAsyncQueuer<TFn>
179
200
  }
@@ -29,13 +29,9 @@ export interface SolidAsyncRateLimiter<TFn extends AnyAsyncFunction>
29
29
  * This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that
30
30
  * you can integrate with any state management solution (createSignal, etc).
31
31
  *
32
- * Rate limiting allows an async function to execute up to a specified limit within a time window,
33
- * then blocks subsequent calls until the window passes. This is useful for respecting API rate limits,
34
- * managing resource constraints, or controlling bursts of async operations.
35
- *
36
- * Unlike the non-async RateLimiter, this async version supports returning values from the rate-limited function,
37
- * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
38
- * instead of setting the result on a state variable from within the rate-limited function.
32
+ * Rate limiting is a simple approach that allows a function to execute up to a limit within a time window,
33
+ * then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where
34
+ * all executions happen immediately, followed by a complete block.
39
35
  *
40
36
  * The rate limiter supports two types of windows:
41
37
  * - 'fixed': A strict window that resets after the window period. All executions within the window count
@@ -43,6 +39,25 @@ export interface SolidAsyncRateLimiter<TFn extends AnyAsyncFunction>
43
39
  * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more
44
40
  * consistent rate of execution over time.
45
41
  *
42
+ * Unlike the non-async RateLimiter, this async version supports returning values from the rate-limited function,
43
+ * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
44
+ * instead of setting the result on a state variable from within the rate-limited function.
45
+ *
46
+ * For smoother execution patterns, consider using:
47
+ * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms)
48
+ * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls)
49
+ *
50
+ * Rate limiting is best used for hard API limits or resource constraints. For UI updates or
51
+ * smoothing out frequent events, throttling or debouncing usually provide better user experience.
52
+ *
53
+ * Error Handling:
54
+ * - If an `onError` handler is provided, it will be called with the error and rate limiter instance
55
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
56
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
57
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
58
+ * - The error state can be checked using the underlying AsyncRateLimiter instance
59
+ * - Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler
60
+ *
46
61
  * @example
47
62
  * ```tsx
48
63
  * // Basic API call rate limiting with return value
@@ -37,6 +37,17 @@ export interface SolidAsyncThrottler<TFn extends AnyAsyncFunction>
37
37
  * regardless of how many times it is called. This is useful for rate-limiting expensive API calls,
38
38
  * database operations, or other async tasks.
39
39
  *
40
+ * Unlike the non-async Throttler, this async version supports returning values from the throttled function,
41
+ * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
42
+ * instead of setting the result on a state variable from within the throttled function.
43
+ *
44
+ * Error Handling:
45
+ * - If an `onError` handler is provided, it will be called with the error and throttler instance
46
+ * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
47
+ * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
48
+ * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
49
+ * - The error state can be checked using the underlying AsyncThrottler instance
50
+ *
40
51
  * @example
41
52
  * ```tsx
42
53
  * // Basic API call throttling
@@ -58,12 +69,14 @@ export interface SolidAsyncThrottler<TFn extends AnyAsyncFunction>
58
69
  * {
59
70
  * wait: 2000,
60
71
  * leading: true, // Execute immediately on first call
61
- * trailing: false // Skip trailing edge updates
72
+ * trailing: false, // Skip trailing edge updates
73
+ * onError: (error) => {
74
+ * console.error('API call failed:', error);
75
+ * }
62
76
  * }
63
77
  * );
64
78
  * ```
65
79
  */
66
-
67
80
  export function createAsyncThrottler<TFn extends AnyAsyncFunction>(
68
81
  fn: TFn,
69
82
  initialOptions: AsyncThrottlerOptions<TFn>,
@@ -46,17 +46,11 @@ export interface SolidRateLimiter<TFn extends AnyFunction>
46
46
  * const rateLimiter = createRateLimiter(apiCall, {
47
47
  * limit: 5,
48
48
  * window: 60000,
49
- * windowType: 'sliding'
50
- * });
51
- *
52
- * // Monitor rate limit status
53
- * const handleClick = () => {
54
- * if (rateLimiter.remainingInWindow() > 0) {
55
- * rateLimiter.maybeExecute(data);
56
- * } else {
57
- * showRateLimitWarning();
49
+ * windowType: 'sliding',
50
+ * onReject: (rateLimiter) => {
51
+ * console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
58
52
  * }
59
- * };
53
+ * });
60
54
  *
61
55
  * // Access rate limiter state via signals
62
56
  * console.log('Executions:', rateLimiter.executionCount());