@tanstack/solid-pacer 0.5.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.
- package/dist/cjs/async-debouncer/createAsyncDebouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer/createAsyncDebouncer.d.cts +21 -2
- package/dist/cjs/async-queuer/createAsyncQueuer.cjs +24 -13
- package/dist/cjs/async-queuer/createAsyncQueuer.cjs.map +1 -1
- package/dist/cjs/async-queuer/createAsyncQueuer.d.cts +40 -32
- package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter/createAsyncRateLimiter.d.cts +22 -7
- package/dist/cjs/async-throttler/createAsyncThrottler.cjs.map +1 -1
- package/dist/cjs/async-throttler/createAsyncThrottler.d.cts +15 -1
- package/dist/cjs/rate-limiter/createRateLimiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter/createRateLimiter.d.cts +4 -10
- package/dist/esm/async-debouncer/createAsyncDebouncer.d.ts +21 -2
- package/dist/esm/async-debouncer/createAsyncDebouncer.js.map +1 -1
- package/dist/esm/async-queuer/createAsyncQueuer.d.ts +40 -32
- package/dist/esm/async-queuer/createAsyncQueuer.js +24 -13
- package/dist/esm/async-queuer/createAsyncQueuer.js.map +1 -1
- package/dist/esm/async-rate-limiter/createAsyncRateLimiter.d.ts +22 -7
- package/dist/esm/async-rate-limiter/createAsyncRateLimiter.js.map +1 -1
- package/dist/esm/async-throttler/createAsyncThrottler.d.ts +15 -1
- package/dist/esm/async-throttler/createAsyncThrottler.js.map +1 -1
- package/dist/esm/rate-limiter/createRateLimiter.d.ts +4 -10
- package/dist/esm/rate-limiter/createRateLimiter.js.map +1 -1
- package/package.json +2 -2
- package/src/async-debouncer/createAsyncDebouncer.ts +21 -3
- package/src/async-queuer/createAsyncQueuer.ts +76 -55
- package/src/async-rate-limiter/createAsyncRateLimiter.ts +22 -7
- package/src/async-throttler/createAsyncThrottler.ts +15 -2
- 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 {
|
|
5
|
+
import type {
|
|
6
|
+
AsyncQueuerFn,
|
|
7
|
+
AsyncQueuerOptions,
|
|
8
|
+
} from '@tanstack/pacer/async-queuer'
|
|
6
9
|
|
|
7
|
-
export interface SolidAsyncQueuer<
|
|
10
|
+
export interface SolidAsyncQueuer<TFn extends AsyncQueuerFn>
|
|
8
11
|
extends Omit<
|
|
9
|
-
AsyncQueuer<
|
|
12
|
+
AsyncQueuer<TFn>,
|
|
10
13
|
| 'getActiveItems'
|
|
11
14
|
| 'getAllItems'
|
|
12
|
-
| '
|
|
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<
|
|
30
|
+
activeItems: Accessor<Array<TFn>>
|
|
25
31
|
/**
|
|
26
32
|
* Signal version of `getAllItems`
|
|
27
33
|
*/
|
|
28
|
-
allItems: Accessor<Array<
|
|
34
|
+
allItems: Accessor<Array<TFn>>
|
|
29
35
|
/**
|
|
30
|
-
* Signal version of `
|
|
36
|
+
* Signal version of `getErrorCount`
|
|
31
37
|
*/
|
|
32
|
-
|
|
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<
|
|
58
|
+
peek: Accessor<TFn | undefined>
|
|
53
59
|
/**
|
|
54
60
|
* Signal version of `getPendingItems`
|
|
55
61
|
*/
|
|
56
|
-
pendingItems: Accessor<Array<
|
|
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
|
|
82
|
+
* A lower-level Solid hook that creates an `AsyncQueuer` instance for managing an async queue of items.
|
|
69
83
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
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
|
-
*
|
|
74
|
-
*
|
|
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
|
-
*
|
|
80
|
-
* -
|
|
81
|
-
* -
|
|
82
|
-
* -
|
|
83
|
-
* -
|
|
84
|
-
* -
|
|
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<
|
|
113
|
-
initialOptions: AsyncQueuerOptions<
|
|
114
|
-
): SolidAsyncQueuer<
|
|
115
|
-
const asyncQueuer = new AsyncQueuer<
|
|
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 [
|
|
118
|
-
asyncQueuer.
|
|
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<(
|
|
145
|
+
const [allItems, setAllItems] = createSignal<Array<TFn>>(
|
|
128
146
|
asyncQueuer.getAllItems(),
|
|
129
147
|
)
|
|
130
|
-
const [activeItems, setActiveItems] = createSignal<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const [pendingItems, setPendingItems] = createSignal<
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
33
|
-
* then blocks subsequent calls until the window passes. This
|
|
34
|
-
*
|
|
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
|
|
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());
|