diff --git a/.changeset/chubby-laws-smile.md b/.changeset/chubby-laws-smile.md new file mode 100644 index 00000000..5481d5b5 --- /dev/null +++ b/.changeset/chubby-laws-smile.md @@ -0,0 +1,10 @@ +--- +'@tanstack/react-pacer-devtools': minor +'@tanstack/solid-pacer-devtools': minor +'@tanstack/pacer-devtools': minor +'@tanstack/react-pacer': minor +'@tanstack/solid-pacer': minor +'@tanstack/pacer': minor +--- + +feat: added built-in retries and added better abort support to async utils diff --git a/docs/config.json b/docs/config.json index 36594671..972f8cec 100644 --- a/docs/config.json +++ b/docs/config.json @@ -89,6 +89,10 @@ { "label": "Async Batching Guide", "to": "guides/async-batching" + }, + { + "label": "Async Retrying Guide", + "to": "guides/async-retrying" } ] }, diff --git a/docs/framework/react/reference/functions/pacerprovider.md b/docs/framework/react/reference/functions/pacerprovider.md new file mode 100644 index 00000000..29b75d66 --- /dev/null +++ b/docs/framework/react/reference/functions/pacerprovider.md @@ -0,0 +1,24 @@ +--- +id: PacerProvider +title: PacerProvider +--- + + + +# Function: PacerProvider() + +```ts +function PacerProvider(__namedParameters): Element +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:46](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L46) + +## Parameters + +### \_\_namedParameters + +[`PacerProviderProps`](../../interfaces/pacerproviderprops.md) + +## Returns + +`Element` diff --git a/docs/framework/react/reference/functions/useasyncbatcher.md b/docs/framework/react/reference/functions/useasyncbatcher.md index c06c3070..11761a87 100644 --- a/docs/framework/react/reference/functions/useasyncbatcher.md +++ b/docs/framework/react/reference/functions/useasyncbatcher.md @@ -14,7 +14,7 @@ function useAsyncBatcher( selector): ReactAsyncBatcher ``` -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:167](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L167) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:168](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L168) A React hook that creates an `AsyncBatcher` instance for managing asynchronous batches of items. diff --git a/docs/framework/react/reference/functions/useasyncdebouncer.md b/docs/framework/react/reference/functions/useasyncdebouncer.md index 696a696e..54fb45ac 100644 --- a/docs/framework/react/reference/functions/useasyncdebouncer.md +++ b/docs/framework/react/reference/functions/useasyncdebouncer.md @@ -14,7 +14,7 @@ function useAsyncDebouncer( selector): ReactAsyncDebouncer ``` -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:149](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L149) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:150](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L150) A low-level React hook that creates an `AsyncDebouncer` instance to delay execution of an async function. diff --git a/docs/framework/react/reference/functions/useasyncqueuer.md b/docs/framework/react/reference/functions/useasyncqueuer.md index 96329a26..7e86e10b 100644 --- a/docs/framework/react/reference/functions/useasyncqueuer.md +++ b/docs/framework/react/reference/functions/useasyncqueuer.md @@ -14,7 +14,7 @@ function useAsyncQueuer( selector): ReactAsyncQueuer ``` -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:167](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L167) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:168](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L168) A lower-level React hook that creates an `AsyncQueuer` instance for managing an async queue of items. diff --git a/docs/framework/react/reference/functions/useasyncratelimiter.md b/docs/framework/react/reference/functions/useasyncratelimiter.md index 0d6a2fbe..b209c15f 100644 --- a/docs/framework/react/reference/functions/useasyncratelimiter.md +++ b/docs/framework/react/reference/functions/useasyncratelimiter.md @@ -14,7 +14,7 @@ function useAsyncRateLimiter( selector): ReactAsyncRateLimiter ``` -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:178](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L178) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:179](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L179) A low-level React hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. diff --git a/docs/framework/react/reference/functions/useasyncthrottler.md b/docs/framework/react/reference/functions/useasyncthrottler.md index 5802d49c..1feb0fe4 100644 --- a/docs/framework/react/reference/functions/useasyncthrottler.md +++ b/docs/framework/react/reference/functions/useasyncthrottler.md @@ -14,7 +14,7 @@ function useAsyncThrottler( selector): ReactAsyncThrottler ``` -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:160](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L160) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:161](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L161) A low-level React hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. diff --git a/docs/framework/react/reference/functions/usebatcher.md b/docs/framework/react/reference/functions/usebatcher.md index 8ee2d6c9..f16ec58d 100644 --- a/docs/framework/react/reference/functions/usebatcher.md +++ b/docs/framework/react/reference/functions/usebatcher.md @@ -14,7 +14,7 @@ function useBatcher( selector): ReactBatcher ``` -Defined in: [react-pacer/src/batcher/useBatcher.ts:121](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L121) +Defined in: [react-pacer/src/batcher/useBatcher.ts:122](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L122) A React hook that creates and manages a Batcher instance. diff --git a/docs/framework/react/reference/functions/usedebouncer.md b/docs/framework/react/reference/functions/usedebouncer.md index b3f3d0b6..5281a2f6 100644 --- a/docs/framework/react/reference/functions/usedebouncer.md +++ b/docs/framework/react/reference/functions/usedebouncer.md @@ -14,7 +14,7 @@ function useDebouncer( selector): ReactDebouncer ``` -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:102](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L102) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:103](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L103) A React hook that creates and manages a Debouncer instance. diff --git a/docs/framework/react/reference/functions/usedefaultpaceroptions.md b/docs/framework/react/reference/functions/usedefaultpaceroptions.md new file mode 100644 index 00000000..bacbe354 --- /dev/null +++ b/docs/framework/react/reference/functions/usedefaultpaceroptions.md @@ -0,0 +1,18 @@ +--- +id: useDefaultPacerOptions +title: useDefaultPacerOptions +--- + + + +# Function: useDefaultPacerOptions() + +```ts +function useDefaultPacerOptions(): PacerProviderOptions +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:68](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L68) + +## Returns + +[`PacerProviderOptions`](../../interfaces/pacerprovideroptions.md) diff --git a/docs/framework/react/reference/functions/usepacercontext.md b/docs/framework/react/reference/functions/usepacercontext.md new file mode 100644 index 00000000..970e19ce --- /dev/null +++ b/docs/framework/react/reference/functions/usepacercontext.md @@ -0,0 +1,18 @@ +--- +id: usePacerContext +title: usePacerContext +--- + + + +# Function: usePacerContext() + +```ts +function usePacerContext(): null | PacerContextValue +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:64](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L64) + +## Returns + +`null` \| `PacerContextValue` diff --git a/docs/framework/react/reference/functions/usequeuer.md b/docs/framework/react/reference/functions/usequeuer.md index 40d322ce..bdbe8fe2 100644 --- a/docs/framework/react/reference/functions/usequeuer.md +++ b/docs/framework/react/reference/functions/usequeuer.md @@ -14,7 +14,7 @@ function useQueuer( selector): ReactQueuer ``` -Defined in: [react-pacer/src/queuer/useQueuer.ts:132](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L132) +Defined in: [react-pacer/src/queuer/useQueuer.ts:133](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L133) A React hook that creates and manages a Queuer instance. diff --git a/docs/framework/react/reference/functions/useratelimiter.md b/docs/framework/react/reference/functions/useratelimiter.md index e59d9e0d..ac7316b0 100644 --- a/docs/framework/react/reference/functions/useratelimiter.md +++ b/docs/framework/react/reference/functions/useratelimiter.md @@ -14,7 +14,7 @@ function useRateLimiter( selector): ReactRateLimiter ``` -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:141](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L141) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:142](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L142) A low-level React hook that creates a `RateLimiter` instance to enforce rate limits on function execution. diff --git a/docs/framework/react/reference/functions/usethrottler.md b/docs/framework/react/reference/functions/usethrottler.md index 666bebaa..601b3927 100644 --- a/docs/framework/react/reference/functions/usethrottler.md +++ b/docs/framework/react/reference/functions/usethrottler.md @@ -14,7 +14,7 @@ function useThrottler( selector): ReactThrottler ``` -Defined in: [react-pacer/src/throttler/useThrottler.ts:107](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L107) +Defined in: [react-pacer/src/throttler/useThrottler.ts:108](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L108) A low-level React hook that creates a `Throttler` instance that limits how often the provided function can execute. diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md index 1ba0d014..1a4f016f 100644 --- a/docs/framework/react/reference/index.md +++ b/docs/framework/react/reference/index.md @@ -9,6 +9,8 @@ title: "@tanstack/react-pacer" ## Interfaces +- [PacerProviderOptions](../interfaces/pacerprovideroptions.md) +- [PacerProviderProps](../interfaces/pacerproviderprops.md) - [ReactAsyncBatcher](../interfaces/reactasyncbatcher.md) - [ReactAsyncDebouncer](../interfaces/reactasyncdebouncer.md) - [ReactAsyncQueuer](../interfaces/reactasyncqueuer.md) @@ -22,6 +24,7 @@ title: "@tanstack/react-pacer" ## Functions +- [PacerProvider](../functions/pacerprovider.md) - [useAsyncBatchedCallback](../functions/useasyncbatchedcallback.md) - [useAsyncBatcher](../functions/useasyncbatcher.md) - [useAsyncDebouncedCallback](../functions/useasyncdebouncedcallback.md) @@ -38,6 +41,8 @@ title: "@tanstack/react-pacer" - [useDebouncedState](../functions/usedebouncedstate.md) - [useDebouncedValue](../functions/usedebouncedvalue.md) - [useDebouncer](../functions/usedebouncer.md) +- [useDefaultPacerOptions](../functions/usedefaultpaceroptions.md) +- [usePacerContext](../functions/usepacercontext.md) - [useQueuedState](../functions/usequeuedstate.md) - [useQueuedValue](../functions/usequeuedvalue.md) - [useQueuer](../functions/usequeuer.md) diff --git a/docs/framework/react/reference/interfaces/pacerprovideroptions.md b/docs/framework/react/reference/interfaces/pacerprovideroptions.md new file mode 100644 index 00000000..92ac77e5 --- /dev/null +++ b/docs/framework/react/reference/interfaces/pacerprovideroptions.md @@ -0,0 +1,120 @@ +--- +id: PacerProviderOptions +title: PacerProviderOptions +--- + + + +# Interface: PacerProviderOptions + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:19](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L19) + +## Properties + +### asyncBatcher? + +```ts +optional asyncBatcher: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L20) + +*** + +### asyncDebouncer? + +```ts +optional asyncDebouncer: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L21) + +*** + +### asyncQueuer? + +```ts +optional asyncQueuer: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:22](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L22) + +*** + +### asyncRateLimiter? + +```ts +optional asyncRateLimiter: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:23](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L23) + +*** + +### asyncRetryer? + +```ts +optional asyncRetryer: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:24](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L24) + +*** + +### asyncThrottler? + +```ts +optional asyncThrottler: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:25](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L25) + +*** + +### batcher? + +```ts +optional batcher: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L26) + +*** + +### debouncer? + +```ts +optional debouncer: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L27) + +*** + +### queuer? + +```ts +optional queuer: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:28](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L28) + +*** + +### rateLimiter? + +```ts +optional rateLimiter: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:29](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L29) + +*** + +### throttler? + +```ts +optional throttler: Partial>; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:30](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L30) diff --git a/docs/framework/react/reference/interfaces/pacerproviderprops.md b/docs/framework/react/reference/interfaces/pacerproviderprops.md new file mode 100644 index 00000000..305b231b --- /dev/null +++ b/docs/framework/react/reference/interfaces/pacerproviderprops.md @@ -0,0 +1,30 @@ +--- +id: PacerProviderProps +title: PacerProviderProps +--- + + + +# Interface: PacerProviderProps + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:39](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L39) + +## Properties + +### children + +```ts +children: ReactNode; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:40](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L40) + +*** + +### defaultOptions? + +```ts +optional defaultOptions: PacerProviderOptions; +``` + +Defined in: [react-pacer/src/provider/PacerProvider.tsx:41](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/provider/PacerProvider.tsx#L41) diff --git a/docs/framework/react/reference/interfaces/reactasyncbatcher.md b/docs/framework/react/reference/interfaces/reactasyncbatcher.md index 3b603968..4335c3c5 100644 --- a/docs/framework/react/reference/interfaces/reactasyncbatcher.md +++ b/docs/framework/react/reference/interfaces/reactasyncbatcher.md @@ -7,7 +7,7 @@ title: ReactAsyncBatcher # Interface: ReactAsyncBatcher\ -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L10) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L11) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:10](https://github readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:17](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L17) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:18](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L18) Reactive state that will be updated and re-rendered when the batcher state changes @@ -41,7 +41,7 @@ Use this instead of `batcher.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L23) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:24](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L24) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactasyncdebouncer.md b/docs/framework/react/reference/interfaces/reactasyncdebouncer.md index bb9bd246..12644ed1 100644 --- a/docs/framework/react/reference/interfaces/reactasyncdebouncer.md +++ b/docs/framework/react/reference/interfaces/reactasyncdebouncer.md @@ -7,7 +7,7 @@ title: ReactAsyncDebouncer # Interface: ReactAsyncDebouncer\ -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L11) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L12) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:11](https://gi readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L20) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L21) Reactive state that will be updated and re-rendered when the debouncer state changes @@ -41,7 +41,7 @@ Use this instead of `debouncer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L26) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L27) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactasyncqueuer.md b/docs/framework/react/reference/interfaces/reactasyncqueuer.md index a37023a9..f07b1116 100644 --- a/docs/framework/react/reference/interfaces/reactasyncqueuer.md +++ b/docs/framework/react/reference/interfaces/reactasyncqueuer.md @@ -7,7 +7,7 @@ title: ReactAsyncQueuer # Interface: ReactAsyncQueuer\ -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L10) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L11) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:10](https://github.c readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L17) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L18) Reactive state that will be updated and re-rendered when the queuer state changes @@ -41,7 +41,7 @@ Use this instead of `queuer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L23) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:24](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L24) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactasyncratelimiter.md b/docs/framework/react/reference/interfaces/reactasyncratelimiter.md index d226ec96..a69d42d2 100644 --- a/docs/framework/react/reference/interfaces/reactasyncratelimiter.md +++ b/docs/framework/react/reference/interfaces/reactasyncratelimiter.md @@ -7,7 +7,7 @@ title: ReactAsyncRateLimiter # Interface: ReactAsyncRateLimiter\ -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L11) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L12) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:11](https readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L20) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L21) Reactive state that will be updated and re-rendered when the rate limiter state changes @@ -41,7 +41,7 @@ Use this instead of `rateLimiter.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L26) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L27) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactasyncthrottler.md b/docs/framework/react/reference/interfaces/reactasyncthrottler.md index b7d8b408..24902767 100644 --- a/docs/framework/react/reference/interfaces/reactasyncthrottler.md +++ b/docs/framework/react/reference/interfaces/reactasyncthrottler.md @@ -7,7 +7,7 @@ title: ReactAsyncThrottler # Interface: ReactAsyncThrottler\ -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L11) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L12) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:11](https://gi readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L20) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L21) Reactive state that will be updated and re-rendered when the throttler state changes @@ -41,7 +41,7 @@ Use this instead of `throttler.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L26) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L27) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactbatcher.md b/docs/framework/react/reference/interfaces/reactbatcher.md index b1afe1ea..c231c1eb 100644 --- a/docs/framework/react/reference/interfaces/reactbatcher.md +++ b/docs/framework/react/reference/interfaces/reactbatcher.md @@ -7,7 +7,7 @@ title: ReactBatcher # Interface: ReactBatcher\ -Defined in: [react-pacer/src/batcher/useBatcher.ts:7](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L7) +Defined in: [react-pacer/src/batcher/useBatcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L8) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/batcher/useBatcher.ts:7](https://github.com/TanStac readonly state: Readonly; ``` -Defined in: [react-pacer/src/batcher/useBatcher.ts:14](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L14) +Defined in: [react-pacer/src/batcher/useBatcher.ts:15](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L15) Reactive state that will be updated and re-rendered when the batcher state changes @@ -41,7 +41,7 @@ Use this instead of `batcher.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/batcher/useBatcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L20) +Defined in: [react-pacer/src/batcher/useBatcher.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L21) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactdebouncer.md b/docs/framework/react/reference/interfaces/reactdebouncer.md index 85cf17e6..c9c0dbf7 100644 --- a/docs/framework/react/reference/interfaces/reactdebouncer.md +++ b/docs/framework/react/reference/interfaces/reactdebouncer.md @@ -7,7 +7,7 @@ title: ReactDebouncer # Interface: ReactDebouncer\ -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L11) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L12) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/debouncer/useDebouncer.ts:11](https://github.com/Ta readonly state: Readonly; ``` -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L18) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L19) Reactive state that will be updated and re-rendered when the debouncer state changes @@ -41,7 +41,7 @@ Use this instead of `debouncer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:24](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L24) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:25](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L25) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactqueuer.md b/docs/framework/react/reference/interfaces/reactqueuer.md index e3ccdecf..34b3f68f 100644 --- a/docs/framework/react/reference/interfaces/reactqueuer.md +++ b/docs/framework/react/reference/interfaces/reactqueuer.md @@ -7,7 +7,7 @@ title: ReactQueuer # Interface: ReactQueuer\ -Defined in: [react-pacer/src/queuer/useQueuer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L7) +Defined in: [react-pacer/src/queuer/useQueuer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L8) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/queuer/useQueuer.ts:7](https://github.com/TanStack/ readonly state: Readonly; ``` -Defined in: [react-pacer/src/queuer/useQueuer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L14) +Defined in: [react-pacer/src/queuer/useQueuer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L15) Reactive state that will be updated and re-rendered when the queuer state changes @@ -41,7 +41,7 @@ Use this instead of `queuer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/queuer/useQueuer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L20) +Defined in: [react-pacer/src/queuer/useQueuer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L21) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactratelimiter.md b/docs/framework/react/reference/interfaces/reactratelimiter.md index 3096a5dc..885732c3 100644 --- a/docs/framework/react/reference/interfaces/reactratelimiter.md +++ b/docs/framework/react/reference/interfaces/reactratelimiter.md @@ -7,7 +7,7 @@ title: ReactRateLimiter # Interface: ReactRateLimiter\ -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L11) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L12) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:11](https://github.c readonly state: Readonly; ``` -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:18](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L18) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L19) Reactive state that will be updated and re-rendered when the rate limiter state changes @@ -41,7 +41,7 @@ Use this instead of `rateLimiter.store.state` readonly store: Store>; ``` -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:24](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L24) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:25](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L25) #### Deprecated diff --git a/docs/framework/react/reference/interfaces/reactthrottler.md b/docs/framework/react/reference/interfaces/reactthrottler.md index 14ea1336..d90fc71f 100644 --- a/docs/framework/react/reference/interfaces/reactthrottler.md +++ b/docs/framework/react/reference/interfaces/reactthrottler.md @@ -7,7 +7,7 @@ title: ReactThrottler # Interface: ReactThrottler\ -Defined in: [react-pacer/src/throttler/useThrottler.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L11) +Defined in: [react-pacer/src/throttler/useThrottler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L12) ## Extends @@ -27,7 +27,7 @@ Defined in: [react-pacer/src/throttler/useThrottler.ts:11](https://github.com/Ta readonly state: Readonly; ``` -Defined in: [react-pacer/src/throttler/useThrottler.ts:18](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L18) +Defined in: [react-pacer/src/throttler/useThrottler.ts:19](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L19) Reactive state that will be updated and re-rendered when the throttler state changes @@ -41,7 +41,7 @@ Use this instead of `throttler.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/throttler/useThrottler.ts:24](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L24) +Defined in: [react-pacer/src/throttler/useThrottler.ts:25](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L25) #### Deprecated diff --git a/docs/guides/async-batching.md b/docs/guides/async-batching.md index 233b42c5..378f559f 100644 --- a/docs/guides/async-batching.md +++ b/docs/guides/async-batching.md @@ -191,6 +191,73 @@ The `AsyncBatcher` supports these async-specific callbacks: - `onExecute`: Called after each batch execution, providing the batch of items processed and batcher instance (same as synchronous batcher) - `onItemsChange`: Called when items are added or the batch is processed +## Advanced Features: Retry and Abort Support + +The async batcher includes built-in retry and abort capabilities through integration with `AsyncRetryer`. These features help handle transient failures and provide control over in-flight operations. + +### Retry Support + +Configure automatic retries for failed batch executions using the `asyncRetryerOptions`: + +```ts +const batcher = new AsyncBatcher( + async (items) => { + // This might fail due to network issues + const results = await apiCall(items) + return results + }, + { + maxSize: 5, + asyncRetryerOptions: { + maxAttempts: 3, + backoff: 'exponential', + baseWait: 1000, + maxWait: 10000, + jitter: 0.3 + } + } +) +``` + +For complete documentation on retry strategies, backoff algorithms, jitter, and advanced retry patterns, see the [Async Retrying Guide](./async-retrying.md). + +### Abort Support + +Cancel in-flight batch executions using the abort functionality: + +```ts +const batcher = new AsyncBatcher( + async (items) => { + // Access the abort signal for this execution + const signal = batcher.getAbortSignal() + if (signal) { + const response = await fetch('/api/batch', { + method: 'POST', + body: JSON.stringify(items), + signal // Pass signal to fetch for cancellation support + }) + return response.json() + } + }, + { maxSize: 10 } +) + +// Add items +batcher.addItem(1) +batcher.addItem(2) + +// Later, abort any in-flight batch executions +batcher.abort() +``` + +The abort functionality: +- Cancels all ongoing batch executions using AbortController +- Does NOT cancel pending batches that haven't started yet (use `cancel()` for that) +- Does NOT clear items from the batcher +- Can be used alongside retry support + +For more details on abort patterns and integration with fetch/axios, see the [Async Retrying Guide](./async-retrying.md). + ## Error Handling Options The async batcher provides flexible error handling through the `throwOnError` option: diff --git a/docs/guides/async-debouncing.md b/docs/guides/async-debouncing.md index a37e2998..d25aea21 100644 --- a/docs/guides/async-debouncing.md +++ b/docs/guides/async-debouncing.md @@ -103,6 +103,66 @@ Since the debouncer's `maybeExecute` method returns a Promise, you can choose to For example, if you're updating a user's profile and then immediately fetching their updated data, you can await the update operation before starting the fetch. +## Advanced Features: Retry and Abort Support + +The async debouncer includes built-in retry and abort capabilities through integration with `AsyncRetryer`. These features help handle transient failures and provide control over in-flight operations. + +### Retry Support + +Configure automatic retries for failed debounced function executions using the `asyncRetryerOptions`: + +```ts +const debouncedSave = asyncDebounce( + async (data: string) => { + // This might fail due to network issues + await api.save(data) + }, + { + wait: 500, + asyncRetryerOptions: { + maxAttempts: 3, + backoff: 'exponential', + baseWait: 1000, + maxWait: 10000, + jitter: 0.3 + } + } +) +``` + +For complete documentation on retry strategies, backoff algorithms, jitter, and advanced retry patterns, see the [Async Retrying Guide](./async-retrying.md). + +### Abort Support + +Cancel in-flight debounced executions using the abort functionality: + +```ts +const debouncer = new AsyncDebouncer( + async (searchTerm: string) => { + // Access the abort signal for this execution + const signal = debouncer.getAbortSignal() + if (signal) { + const response = await fetch(`/api/search?q=${searchTerm}`, { signal }) + return response.json() + } + }, + { wait: 300 } +) + +// Start a search +debouncer.maybeExecute('query') + +// Later, abort any in-flight execution +debouncer.abort() +``` + +The abort functionality: +- Cancels all ongoing debounced executions using AbortController +- Does NOT cancel pending executions that haven't started yet (use `cancel()` for that) +- Can be used alongside retry support + +For more details on abort patterns and integration with fetch/axios, see the [Async Retrying Guide](./async-retrying.md). + ## Dynamic Options and Enabling/Disabling Just like the synchronous debouncer, the async debouncer supports dynamic options for `wait` and `enabled`, which can be functions that receive the debouncer instance. This allows for sophisticated, runtime-adaptive debouncing behavior. diff --git a/docs/guides/async-queuing.md b/docs/guides/async-queuing.md index f22ef7ba..5396811f 100644 --- a/docs/guides/async-queuing.md +++ b/docs/guides/async-queuing.md @@ -253,6 +253,69 @@ queue.flush(1) // Process 1 more item console.log(queue.store.state.activeItems.length) // 3 (all processing concurrently) ``` +## Advanced Features: Retry and Abort Support + +The async queuer includes built-in retry and abort capabilities through integration with `AsyncRetryer`. These features help handle transient failures and provide control over in-flight task executions. + +### Retry Support + +Configure automatic retries for failed task executions using the `asyncRetryerOptions`. Each queued item's execution will be retried according to these settings: + +```ts +const queuer = new AsyncQueuer( + async (item) => { + // This might fail due to network issues + await api.processItem(item) + }, + { + concurrency: 2, + asyncRetryerOptions: { + maxAttempts: 3, + backoff: 'exponential', + baseWait: 1000, + maxWait: 10000, + jitter: 0.3 + } + } +) +``` + +For complete documentation on retry strategies, backoff algorithms, jitter, and advanced retry patterns, see the [Async Retrying Guide](./async-retrying.md). + +### Abort Support + +Cancel in-flight task executions using the abort functionality: + +```ts +const queuer = new AsyncQueuer( + async (item) => { + // Access the abort signal for this execution + const signal = queuer.getAbortSignal() + if (signal) { + const response = await fetch(`/api/process/${item}`, { signal }) + return response.json() + } + }, + { concurrency: 2 } +) + +// Add items to the queue +queuer.addItem('task1') +queuer.addItem('task2') +queuer.addItem('task3') + +// Later, abort any in-flight task executions +queuer.abort() +``` + +The abort functionality: +- Cancels all ongoing task executions using AbortController +- Does NOT clear items from the queue (pending tasks remain queued) +- Works with concurrent executions - aborts all active tasks +- Can be used alongside retry support + +For more details on abort patterns and integration with fetch/axios, see the [Async Retrying Guide](./async-retrying.md). + ## State Management The `AsyncQueuer` class uses TanStack Store for reactive state management, providing real-time access to queue state, processing statistics, and concurrent task tracking. All state is stored in a TanStack Store and can be accessed via `asyncQueuer.store.state`, although, if you are using a framework adapter like React or Solid, you will not want to read the state from here. Instead, you will read the state from `asyncQueuer.state` along with providing a selector callback as the 3rd argument to the `useAsyncQueuer` hook to opt-in to state tracking as shown below. diff --git a/docs/guides/async-rate-limiting.md b/docs/guides/async-rate-limiting.md index 76fcfd69..0819b909 100644 --- a/docs/guides/async-rate-limiting.md +++ b/docs/guides/async-rate-limiting.md @@ -107,6 +107,69 @@ Since the rate limiter's `maybeExecute` method returns a Promise, you can choose For example, if you're updating a user's profile and then immediately fetching their updated data, you can await the update operation before starting the fetch. +## Advanced Features: Retry and Abort Support + +The async rate limiter includes built-in retry and abort capabilities through integration with `AsyncRetryer`. These features help handle transient failures and provide control over in-flight operations. + +### Retry Support + +Configure automatic retries for failed rate-limited function executions using the `asyncRetryerOptions`: + +```ts +const rateLimitedApi = asyncRateLimit( + async (userId: string) => { + // This might fail due to network issues + const data = await api.fetchUser(userId) + return data + }, + { + limit: 5, + window: 1000, + asyncRetryerOptions: { + maxAttempts: 3, + backoff: 'exponential', + baseWait: 1000, + maxWait: 10000, + jitter: 0.3 + } + } +) +``` + +For complete documentation on retry strategies, backoff algorithms, jitter, and advanced retry patterns, see the [Async Retrying Guide](./async-retrying.md). + +### Abort Support + +Cancel in-flight rate-limited executions using the abort functionality: + +```ts +const rateLimiter = new AsyncRateLimiter( + async (userId: string) => { + // Access the abort signal for this execution + const signal = rateLimiter.getAbortSignal() + if (signal) { + const response = await fetch(`/api/users/${userId}`, { signal }) + return response.json() + } + }, + { limit: 5, window: 1000 } +) + +// Start some operations +rateLimiter.maybeExecute('user1') +rateLimiter.maybeExecute('user2') + +// Later, abort any in-flight executions +rateLimiter.abort() +``` + +The abort functionality: +- Cancels all ongoing rate-limited executions using AbortController +- Does NOT clear execution times or reset the rate limiter +- Can be used alongside retry support + +For more details on abort patterns and integration with fetch/axios, see the [Async Retrying Guide](./async-retrying.md). + ## Dynamic Options and Enabling/Disabling Just like the synchronous rate limiter, the async rate limiter supports dynamic options for `limit`, `window`, and `enabled`, which can be functions that receive the rate limiter instance. This allows for sophisticated, runtime-adaptive rate limiting behavior. diff --git a/docs/guides/async-retrying.md b/docs/guides/async-retrying.md new file mode 100644 index 00000000..ff0bbf4f --- /dev/null +++ b/docs/guides/async-retrying.md @@ -0,0 +1,806 @@ +--- +title: Async Retrying Guide +id: async-retrying +--- + +TanStack Pacer provides its own retrying utility as a standalone `AsyncRetryer` class or a wrapper function `asyncRetry` for convenience. All of the other async utilities from TanStack Pacer use the `AsyncRetryer` class internally to wrap their executions with built-in retrying functionality. The Async Retryer supports features such as different backoff strategies, jitter, max timeouts, aborting, error handling, and more. + +## When to Use Retries + +Async retrying is particularly effective when you need to: +- Handle transient failures in API calls or network requests +- Implement robust error recovery for flaky operations +- Deal with rate-limited APIs that may temporarily reject requests +- Retry database operations that may fail due to temporary connection issues +- Handle operations that depend on external services with variable reliability + +### When Not to Use Retries + +Avoid async retrying when: +- The operation is not idempotent (retrying could cause unwanted side effects) +- Errors are permanent and retrying won't help (e.g., authentication failures, invalid input) +- The operation is time-sensitive and delays are unacceptable +- You need immediate feedback on failures without any retry attempts + +For operations that need to be queued and processed sequentially, use [Queuing](../queuing.md) instead. For operations that should be delayed until inactivity, use [Debouncing](../debouncing.md) instead. + +## Understanding Retry Behavior + +Before implementing retries, understanding the underlying concepts helps you make better decisions about retry strategies and configurations. + +### The Thundering Herd Problem + +The thundering herd problem occurs when many clients simultaneously retry failed requests to a recovering service, overwhelming it and preventing recovery. This typically happens when: + +- A service experiences a brief outage affecting many clients at once +- All clients fail simultaneously and begin retrying +- Without randomization, all clients retry at exactly the same intervals +- The synchronized retry attempts overwhelm the recovering service +- The service continues to fail, triggering more synchronized retries + +**How TanStack Pacer Addresses This:** + +Jitter adds randomness to retry delays, spreading out retry attempts across time rather than having them occur simultaneously. When you configure jitter, each client's retry timing becomes slightly different: + +```ts +// Without jitter: all clients retry at exactly 1s, 2s, 4s, 8s +// This can overwhelm a recovering service + +// With jitter: clients retry at randomized intervals +const retryer = new AsyncRetryer(asyncFn, { + backoff: 'exponential', + baseWait: 1000, + jitter: 0.3 // 30% random variation +}) +// Client A might retry at: 850ms, 1.7s, 3.6s, 7.2s +// Client B might retry at: 1.15s, 2.3s, 4.4s, 8.8s +// Client C might retry at: 950ms, 1.9s, 3.8s, 7.6s +``` + +This distribution prevents synchronized retry waves and gives the service breathing room to recover. + +### Exponential Backoff and Resource Conservation + +Exponential backoff doubles the wait time between retries. This pattern serves multiple purposes: + +**Fast Recovery for Transient Issues:** +The first retry happens quickly (after `baseWait`), catching brief network hiccups or momentary service interruptions. + +**Reduced Load on Failing Services:** +As retries continue, the increasing delays reduce the request rate to a struggling service, giving it time to recover rather than keeping it under constant pressure. + +**Resource Efficiency:** +Long delays between later retries prevent your application from consuming resources (memory, connections, threads) waiting for a service that might be down for an extended period. + +```ts +// With exponential backoff and 1s base wait: +// Attempt 1: immediate +// Attempt 2: 1s later (service might recover quickly) +// Attempt 3: 2s later (giving service more time) +// Attempt 4: 4s later (backing off further) +// Attempt 5: 8s later (minimal load if service is down) +``` + +### Retry Amplification in Distributed Systems + +Retry amplification occurs when retries cascade through multiple layers of a distributed system, multiplying the actual request load. This is a critical concern in microservices architectures. + +**The Amplification Effect:** + +Consider a system where Service A calls Service B, which calls Service C: + +```text +User → Service A (retries 3x) → Service B (retries 3x) → Service C +``` + +If Service C fails, Service B retries 3 times per request. Service A retries 3 times per request to Service B. This means Service C receives up to 9 requests (3 × 3) for a single user request. + +With deeper call chains, this grows exponentially: +- 2 services with 3 retries each: 9 requests +- 3 services with 3 retries each: 27 requests +- 4 services with 3 retries each: 81 requests + +**Mitigation Strategies:** + +1. **Reduce retries at higher layers:** +```ts +// Service A (user-facing): more retries for better UX +const serviceA = new AsyncRetryer(callServiceB, { + maxAttempts: 5 +}) + +// Service B (internal): fewer retries to prevent amplification +const serviceB = new AsyncRetryer(callServiceC, { + maxAttempts: 2 +}) +``` + +2. **Use timeout budgets to limit total retry time:** +```ts +const retryer = new AsyncRetryer(asyncFn, { + maxAttempts: 3, + maxTotalExecutionTime: 5000 // Limit total time regardless of retries +}) +``` + +### Cost Considerations + +Retries have real costs that should factor into your retry strategy: + +**Network Costs:** +- Each retry consumes bandwidth +- Mobile users may have limited or metered data +- Cloud services may charge for bandwidth + +**Time Costs:** +- Users wait longer for results +- Longer waits hurt user experience +- Time spent retrying could be spent on other requests + +**Resource Costs:** +- Memory for pending operations +- CPU for processing retries +- Connection pool exhaustion +- Thread/worker saturation + +**Example: Failing Fast Based on Error Type:** + +You can use dynamic `maxAttempts` to adjust retry behavior based on error conditions: + +```ts +// Your async function that throws errors with status codes +async function fetchData(url: string) { + const response = await fetch(url) + if (!response.ok) { + const error = new Error('Request failed') + ;(error as any).status = response.status + throw error + } + return response.json() +} + +const retryer = new AsyncRetryer(fetchData, { + maxAttempts: (retryer) => { + const error = retryer.store.state.lastError as any + + // Don't retry client errors (400-499) + if (error?.status >= 400 && error?.status < 500) { + return 1 // No retries + } + + // Retry server errors (500-599) + return 3 + } +}) +``` + +## Async Retrying in TanStack Pacer + +TanStack Pacer provides async retrying through the `asyncRetry` function and the more powerful `AsyncRetryer` class. + +### Basic Usage with `asyncRetry` + +The `asyncRetry` function provides a simple way to add retry functionality to any async function: + +```ts +import { asyncRetry } from '@tanstack/pacer' + +// Create a retry-enabled version of your async function +const fetchWithRetry = asyncRetry( + async (url: string) => { + const response = await fetch(url) + if (!response.ok) throw new Error('Request failed') + return response.json() + }, + { + maxAttempts: 3, + backoff: 'exponential', + baseWait: 1000 + } +) + +// Usage +try { + const data = await fetchWithRetry('/api/data') + console.log('Success:', data) +} catch (error) { + console.error('All retries failed:', error) +} +``` + +For more control over retry behavior, use the `AsyncRetryer` class directly. + +### Advanced Usage with `AsyncRetryer` Class + +The `AsyncRetryer` class provides complete control over retry behavior: + +```ts +import { AsyncRetryer } from '@tanstack/pacer' + +const retryer = new AsyncRetryer( + async (url: string) => { + const response = await fetch(url) + if (!response.ok) throw new Error('Request failed') + return response.json() + }, + { + maxAttempts: 5, + backoff: 'exponential', + baseWait: 1000, + jitter: 0.1, // Add 10% random variation + maxExecutionTime: 5000, // Abort individual calls after 5 seconds + maxTotalExecutionTime: 30000, // Abort entire operation after 30 seconds + key: 'api-fetcher', // Identify this retryer in devtools + onRetry: (attempt, error, retryer) => { + console.log(`Retry attempt ${attempt} after error:`, error) + }, + onSuccess: (result, args, retryer) => { + console.log('Request succeeded:', result) + }, + onError: (error, args, retryer) => { + console.error('Request failed:', error) + }, + onLastError: (error, retryer) => { + console.error('All retries exhausted:', error) + } + } +) + +// Execute the function with retry logic +const data = await retryer.execute('/api/data') +``` + +> **Note:** When using React, prefer `useAsyncRetryer` hook over the `asyncRetry` function for better integration with React's lifecycle and automatic cleanup. + +## Backoff Strategies + +The `backoff` option controls how the wait time between retry attempts changes: + +### Exponential Backoff (Default) + +Wait time doubles with each attempt. This is the most common strategy and works well for most scenarios: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + backoff: 'exponential', + baseWait: 1000 +}) +// Attempt 1: immediate +// Attempt 2: wait 1 second (1000ms * 2^0) +// Attempt 3: wait 2 seconds (1000ms * 2^1) +// Attempt 4: wait 4 seconds (1000ms * 2^2) +// Attempt 5: wait 8 seconds (1000ms * 2^3) +``` + +### Linear Backoff + +Wait time increases linearly with each attempt: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + backoff: 'linear', + baseWait: 1000 +}) +// Attempt 1: immediate +// Attempt 2: wait 1 second (1000ms * 1) +// Attempt 3: wait 2 seconds (1000ms * 2) +// Attempt 4: wait 3 seconds (1000ms * 3) +// Attempt 5: wait 4 seconds (1000ms * 4) +``` + +### Fixed Backoff + +Wait time remains constant for all attempts: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + backoff: 'fixed', + baseWait: 1000 +}) +// Attempt 1: immediate +// Attempt 2: wait 1 second +// Attempt 3: wait 1 second +// Attempt 4: wait 1 second +// Attempt 5: wait 1 second +``` + +## Jitter + +Jitter adds randomness to retry delays to prevent thundering herd problems, where many clients retry at the same time and overwhelm a recovering service. The `jitter` option accepts a value between 0 and 1, representing the percentage of random variation to apply: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + backoff: 'exponential', + baseWait: 1000, + jitter: 0.1 // Add ±10% random variation +}) +// Attempt 2: wait 900-1100ms (1000ms ± 10%) +// Attempt 3: wait 1800-2200ms (2000ms ± 10%) +// Attempt 4: wait 3600-4400ms (4000ms ± 10%) +``` + +Jitter is particularly useful when: +- Multiple clients might fail at the same time (e.g., service outage) +- You're dealing with rate-limited APIs +- You want to spread out retry attempts to avoid overwhelming a recovering service + +## Timeout Controls + +TanStack Pacer provides two types of timeout controls to prevent hanging operations: + +### Individual Execution Timeout + +The `maxExecutionTime` option sets the maximum time for a single function call: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + maxExecutionTime: 5000 // Abort individual calls after 5 seconds +}) +``` + +If a single execution exceeds this time, it will be aborted and retried (if attempts remain). + +### Total Execution Timeout + +The `maxTotalExecutionTime` option sets the maximum time for the entire retry operation: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + maxAttempts: 5, + baseWait: 1000, + maxTotalExecutionTime: 30000 // Abort entire operation after 30 seconds +}) +``` + +If the total time across all attempts exceeds this limit, the retry operation will be aborted. + +### Combining Timeouts + +You can combine both timeout types for comprehensive control: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + maxAttempts: 5, + backoff: 'exponential', + baseWait: 1000, + maxExecutionTime: 5000, // Individual call timeout + maxTotalExecutionTime: 30000 // Overall operation timeout +}) +``` + +## Error Handling + +The async retryer provides comprehensive error handling through callbacks and the `throwOnError` option: + +### Error Throwing Behavior + +The `throwOnError` option controls when errors are thrown: + +```ts +// Default: throw only the last error after all retries fail +const retryer1 = new AsyncRetryer(asyncFn, { + throwOnError: 'last' // Default +}) + +// Throw every error immediately (disables retrying) +const retryer2 = new AsyncRetryer(asyncFn, { + throwOnError: true +}) + +// Never throw errors, return undefined instead +const retryer3 = new AsyncRetryer(asyncFn, { + throwOnError: false +}) +``` + +### Error Callbacks + +The async retryer supports multiple callbacks for different stages of execution: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + maxAttempts: 3, + onRetry: (attempt, error, retryer) => { + // Called before each retry attempt + console.log(`Retrying (attempt ${attempt})...`) + console.log('Error:', error.message) + console.log('Current attempt:', retryer.store.state.currentAttempt) + }, + onError: (error, args, retryer) => { + // Called for every error (including during retries) + console.error('Execution failed:', error) + console.log('Failed with arguments:', args) + }, + onLastError: (error, retryer) => { + // Called only for the final error after all retries fail + console.error('All retries exhausted:', error) + console.log('Total execution time:', retryer.store.state.totalExecutionTime) + }, + onSuccess: (result, args, retryer) => { + // Called when execution succeeds + console.log('Execution succeeded:', result) + console.log('Succeeded with arguments:', args) + console.log('Attempts used:', retryer.store.state.currentAttempt) + }, + onSettled: (args, retryer) => { + // Called after execution completes (success or failure) + console.log('Execution settled') + console.log('Total executions:', retryer.store.state.executionCount) + } +}) +``` + +### Callback Execution Order + +The callbacks are executed in the following order: + +```text +1. execute() called +2. Try attempt 1 + └─ If fails: + ├─ onRetry(1, error) called + └─ Wait for backoff +3. Try attempt 2 + └─ If fails: + ├─ onRetry(2, error) called + └─ Wait for backoff +4. Try attempt 3 (last attempt) + └─ If fails: + ├─ onLastError(error) called + ├─ onError(error) called + ├─ onSettled() called + └─ Throw error (if throwOnError is 'last' or true) + └─ If succeeds: + ├─ onSuccess(result) called + ├─ onSettled() called + └─ Return result +``` + +## Dynamic Options and Enabling/Disabling + +The async retryer supports dynamic options that can change based on the retryer's current state: + +### Dynamic Max Attempts + +```ts +const retryer = new AsyncRetryer(asyncFn, { + maxAttempts: (retryer) => { + // Retry more times for critical operations + const errorCount = retryer.store.state.executionCount + return errorCount > 5 ? 2 : 5 + } +}) +``` + +### Dynamic Base Wait + +```ts +const retryer = new AsyncRetryer(asyncFn, { + baseWait: (retryer) => { + // Increase wait time if we've had many errors + const errorCount = retryer.store.state.executionCount + return errorCount > 10 ? 2000 : 1000 + } +}) +``` + +### Enabling/Disabling + +```ts +const retryer = new AsyncRetryer(asyncFn, { + enabled: (retryer) => { + // Disable retrying after too many failures + return retryer.store.state.executionCount < 100 + } +}) +``` + +## Abort and Cancellation + +The async retryer supports manual cancellation of ongoing execution and pending retries: + +### Manual Abort + +```ts +const retryer = new AsyncRetryer(longRunningAsyncFn, { + maxAttempts: 5, + baseWait: 1000 +}) + +// Start execution +const promise = retryer.execute() + +// Cancel execution and pending retries +retryer.abort() + +// The promise will resolve to undefined +const result = await promise +console.log(result) // undefined +``` + +### Automatic Cleanup + +When using framework adapters, cleanup is handled automatically: + +```tsx +// React example +function MyComponent() { + const retryer = useAsyncRetryer(asyncFn, { maxAttempts: 3 }) + + // Automatically calls abort() on unmount + return +} +``` + +### Reset + +The `reset()` method cancels execution and resets all state to initial values: + +```ts +const retryer = new AsyncRetryer(asyncFn, { maxAttempts: 3 }) + +await retryer.execute() +console.log(retryer.store.state.executionCount) // 1 + +// Reset to initial state +retryer.reset() +console.log(retryer.store.state.executionCount) // 0 +console.log(retryer.store.state.lastError) // undefined +console.log(retryer.store.state.lastResult) // undefined +``` + +## State Management + +The `AsyncRetryer` class uses TanStack Store for reactive state management, providing real-time access to execution state, error tracking, and retry statistics. All state is stored in a TanStack Store and can be accessed via `asyncRetryer.store.state`, although, if you are using a framework adapter like React or Solid, you will not want to read the state from here. Instead, you will read the state from `asyncRetryer.state` along with providing a selector callback as the 3rd argument to the `useAsyncRetryer` hook to opt-in to state tracking as shown below. + +### State Selector (Framework Adapters) + +Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. + +**By default, `retryer.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. + +```tsx +// Default behavior - no reactive state subscriptions +const retryer = useAsyncRetryer(asyncFn, { maxAttempts: 3 }) +console.log(retryer.state) // {} + +// Opt-in to re-render when execution state changes +const retryer = useAsyncRetryer( + asyncFn, + { maxAttempts: 3 }, + (state) => ({ + isExecuting: state.isExecuting, + currentAttempt: state.currentAttempt + }) +) +console.log(retryer.state.isExecuting) // Reactive value +console.log(retryer.state.currentAttempt) // Reactive value + +// Opt-in to re-render when results are available +const retryer = useAsyncRetryer( + asyncFn, + { maxAttempts: 3 }, + (state) => ({ + lastResult: state.lastResult, + lastError: state.lastError, + status: state.status + }) +) +``` + +### Initial State + +You can provide initial state values when creating an async retryer. This is commonly used to restore state from persistent storage: + +```ts +// Load initial state from localStorage +const savedState = localStorage.getItem('async-retryer-state') +const initialState = savedState ? JSON.parse(savedState) : {} + +const retryer = new AsyncRetryer(asyncFn, { + maxAttempts: 3, + initialState +}) +``` + +### Subscribing to State Changes + +The store is reactive and supports subscriptions: + +```ts +const retryer = new AsyncRetryer(asyncFn, { maxAttempts: 3 }) + +// Subscribe to state changes +const unsubscribe = retryer.store.subscribe((state) => { + // do something with the state like persist it to localStorage + localStorage.setItem('async-retryer-state', JSON.stringify(state)) +}) + +// Unsubscribe when done +unsubscribe() +``` + +> **Note:** This is unnecessary when using a framework adapter because the underlying `useStore` hook already does this. You can also import and use `useStore` from TanStack Store to turn `retryer.store.state` into reactive state with a custom selector wherever you want if necessary. + +### Available State Properties + +The `AsyncRetryerState` includes: + +- `currentAttempt`: The current retry attempt number (0 when not executing) +- `executionCount`: Total number of completed executions (successful or failed) +- `isExecuting`: Whether the retryer is currently executing the function +- `lastError`: The most recent error encountered during execution +- `lastExecutionTime`: Timestamp of the last execution completion in milliseconds +- `lastResult`: The result from the most recent successful execution +- `status`: Current execution status ('disabled' | 'idle' | 'executing' | 'retrying') +- `totalExecutionTime`: Total time spent executing (including retries) in milliseconds + +### Status Values + +The `status` property indicates the current state of the retryer: + +- `'disabled'`: The retryer is disabled (via `enabled: false`) +- `'idle'`: Ready to execute, not currently running +- `'executing'`: Currently executing the first attempt +- `'retrying'`: Currently executing a retry attempt (attempt > 1) + +## Framework Adapters + +Each framework adapter provides hooks that build on top of the core async retrying functionality to integrate with the framework's state management system. Hooks like `createAsyncRetryer`, `useAsyncRetryer`, or similar are available for each framework. + +### React Example + +```tsx +import { useAsyncRetryer } from '@tanstack/react-pacer' + +function DataFetcher() { + const retryer = useAsyncRetryer( + async (userId: string) => { + const response = await fetch(`/api/users/${userId}`) + if (!response.ok) throw new Error('Failed to fetch user') + return response.json() + }, + { + maxAttempts: 5, + backoff: 'exponential', + baseWait: 1000, + jitter: 0.1, + maxExecutionTime: 5000, + onRetry: (attempt) => console.log(`Retry attempt ${attempt}`) + }, + (state) => ({ + isExecuting: state.isExecuting, + currentAttempt: state.currentAttempt, + lastError: state.lastError, + lastResult: state.lastResult + }) + ) + + const handleFetch = async () => { + try { + const user = await retryer.execute('user123') + console.log('User:', user) + } catch (error) { + console.error('Failed to fetch user:', error) + } + } + + return ( +
+ + {retryer.state.lastError && ( +

Error: {retryer.state.lastError.message}

+ )} + {retryer.state.lastResult && ( +
{JSON.stringify(retryer.state.lastResult, null, 2)}
+ )} +
+ ) +} +``` + +## Best Practices + +### 1. Choose the Right Backoff Strategy + +- Use **exponential** backoff for most scenarios (default) +- Use **linear** backoff when you want slower growth in wait times +- Use **fixed** backoff when you have strict timing requirements + +### 2. Add Jitter for Distributed Systems + +When multiple clients might retry at the same time, add jitter to prevent thundering herd: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + backoff: 'exponential', + jitter: 0.2 // Add 20% random variation +}) +``` + +### 3. Set Appropriate Timeouts + +Always set timeouts to prevent hanging operations: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + maxExecutionTime: 5000, // Individual call timeout + maxTotalExecutionTime: 30000 // Overall operation timeout +}) +``` + +### 4. Use Callbacks for Side Effects + +Instead of wrapping the retryer in try-catch, use callbacks for cleaner code: + +```ts +const retryer = new AsyncRetryer(asyncFn, { + onSuccess: (result) => { + // Update UI, cache result, etc. + }, + onError: (error) => { + // Log error, show notification, etc. + }, + throwOnError: false // Don't throw, handle via callbacks +}) +``` + +### 5. Make Operations Idempotent + +Ensure your operations can be safely retried without side effects: + +```ts +// Good: Idempotent operation +const retryer = new AsyncRetryer( + async (userId: string) => { + // GET request is idempotent + return await fetch(`/api/users/${userId}`) + } +) + +// Be careful: Non-idempotent operation +const retryer = new AsyncRetryer( + async (amount: number) => { + // POST request that creates a charge + // Could charge multiple times if retried! + return await fetch('/api/charge', { + method: 'POST', + body: JSON.stringify({ amount }) + }) + } +) +``` + +### 6. Adjust Retry Count Based on Error Type + +Some errors should retry more or less than others: + +```ts +const retryer = new AsyncRetryer( + async (url: string) => { + const response = await fetch(url) + if (!response.ok) { + const error = new Error('Request failed') + error.status = response.status + throw error + } + return response.json() + }, + { + maxAttempts: (retryer) => { + const lastError = retryer.store.state.lastError + // Don't retry 4xx client errors + if (lastError?.status >= 400 && lastError?.status < 500) { + return 1 + } + // Retry 5xx server errors more times + return 5 + } + } +) +``` + diff --git a/docs/guides/async-throttling.md b/docs/guides/async-throttling.md index 3962e91a..4290bddf 100644 --- a/docs/guides/async-throttling.md +++ b/docs/guides/async-throttling.md @@ -104,6 +104,70 @@ Since the throttler's `maybeExecute` method returns a Promise, you can choose to For example, if you're updating a user's profile and then immediately fetching their updated data, you can await the update operation before starting the fetch. +## Advanced Features: Retry and Abort Support + +The async throttler includes built-in retry and abort capabilities through integration with `AsyncRetryer`. These features help handle transient failures and provide control over in-flight operations. + +### Retry Support + +Configure automatic retries for failed throttled function executions using the `asyncRetryerOptions`: + +```ts +const throttledSave = asyncThrottle( + async (data: string) => { + // This might fail due to network issues + await api.save(data) + }, + { + wait: 1000, + asyncRetryerOptions: { + maxAttempts: 3, + backoff: 'exponential', + baseWait: 1000, + maxWait: 10000, + jitter: 0.3 + } + } +) +``` + +For complete documentation on retry strategies, backoff algorithms, jitter, and advanced retry patterns, see the [Async Retrying Guide](./async-retrying.md). + +### Abort Support + +Cancel in-flight throttled executions using the abort functionality: + +```ts +const throttler = new AsyncThrottler( + async (data: string) => { + // Access the abort signal for this execution + const signal = throttler.getAbortSignal() + if (signal) { + const response = await fetch('/api/save', { + method: 'POST', + body: data, + signal + }) + return response.json() + } + }, + { wait: 1000 } +) + +// Start an operation +throttler.maybeExecute('data') + +// Later, abort any in-flight execution +throttler.abort() +``` + +The abort functionality: +- Cancels all ongoing throttled executions using AbortController +- Does NOT cancel pending executions that haven't started yet (use `cancel()` for that) +- Can be used alongside retry support + +For more details on abort patterns and integration with fetch/axios, see the [Async Retrying Guide](./async-retrying.md). + ## Dynamic Options and Enabling/Disabling Just like the synchronous throttler, the async throttler supports dynamic options for `wait` and `enabled`, which can be functions that receive the throttler instance. This allows for sophisticated, runtime-adaptive throttling behavior. diff --git a/docs/reference/classes/asyncbatcher.md b/docs/reference/classes/asyncbatcher.md index 5a92c897..5fb948ab 100644 --- a/docs/reference/classes/asyncbatcher.md +++ b/docs/reference/classes/asyncbatcher.md @@ -7,17 +7,23 @@ title: AsyncBatcher # Class: AsyncBatcher\ -Defined in: [async-batcher.ts:230](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L230) +Defined in: [async-batcher.ts:252](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L252) A class that collects items and processes them in batches asynchronously. -This is the async version of the Batcher class. Unlike the sync version, this async batcher: -- Handles promises and returns results from batch executions -- Provides error handling with configurable error behavior -- Tracks success, error, and settle counts separately -- Has state tracking for when batches are executing -- Returns the result of the batch function execution +Async vs Sync Versions: +The async version provides advanced features over the sync Batcher: +- Returns promises that can be awaited for batch results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight batch executions +- Cancel support to prevent pending batches from starting +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) +The sync Batcher is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Batching? Batching is a technique for grouping multiple operations together to be processed as a single unit. The AsyncBatcher provides a flexible way to implement async batching with configurable: @@ -82,7 +88,7 @@ batcher.addItem(2); new AsyncBatcher(fn, initialOptions): AsyncBatcher ``` -Defined in: [async-batcher.ts:238](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L238) +Defined in: [async-batcher.ts:264](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L264) #### Parameters @@ -100,13 +106,23 @@ Defined in: [async-batcher.ts:238](https://github.com/TanStack/pacer/blob/main/p ## Properties +### asyncRetryers + +```ts +asyncRetryers: Map Promise>>; +``` + +Defined in: [async-batcher.ts:258](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L258) + +*** + ### fn() ```ts fn: (items) => Promise; ``` -Defined in: [async-batcher.ts:239](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L239) +Defined in: [async-batcher.ts:265](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L265) #### Parameters @@ -126,7 +142,7 @@ Defined in: [async-batcher.ts:239](https://github.com/TanStack/pacer/blob/main/p key: string; ``` -Defined in: [async-batcher.ts:234](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L234) +Defined in: [async-batcher.ts:256](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L256) *** @@ -136,7 +152,7 @@ Defined in: [async-batcher.ts:234](https://github.com/TanStack/pacer/blob/main/p options: AsyncBatcherOptionsWithOptionalCallbacks; ``` -Defined in: [async-batcher.ts:235](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L235) +Defined in: [async-batcher.ts:257](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L257) *** @@ -146,17 +162,51 @@ Defined in: [async-batcher.ts:235](https://github.com/TanStack/pacer/blob/main/p readonly store: Store>>; ``` -Defined in: [async-batcher.ts:231](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L231) +Defined in: [async-batcher.ts:253](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L253) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [async-batcher.ts:286](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L286) + +Emits a change event for the async batcher instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + +### abort() + +```ts +abort(): void +``` + +Defined in: [async-batcher.ts:480](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L480) + +Aborts all ongoing executions with the internal abort controllers. +Does NOT cancel any pending execution that have not started yet. +Does NOT clear out the items. + +#### Returns + +`void` + +*** + ### addItem() ```ts -addItem(item): void +addItem(item): Promise ``` -Defined in: [async-batcher.ts:297](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L297) +Defined in: [async-batcher.ts:332](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L332) Adds an item to the async batcher If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed @@ -169,6 +219,30 @@ If the batch size is reached, timeout occurs, or shouldProcess returns true, the #### Returns +`Promise`\<`any`\> + +The result from the batch function, or undefined if an error occurred and was handled by onError + +#### Throws + +The error from the batch function if no onError handler is configured or throwOnError is true + +*** + +### cancel() + +```ts +cancel(): void +``` + +Defined in: [async-batcher.ts:493](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L493) + +Cancels any pending execution that have not started yet. +Does NOT abort any execution already in progress. +Does NOT clear out the items. + +#### Returns + `void` *** @@ -179,7 +253,7 @@ If the batch size is reached, timeout occurs, or shouldProcess returns true, the clear(): void ``` -Defined in: [async-batcher.ts:398](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L398) +Defined in: [async-batcher.ts:441](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L441) Removes all items from the async batcher @@ -195,7 +269,7 @@ Removes all items from the async batcher flush(): Promise ``` -Defined in: [async-batcher.ts:372](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L372) +Defined in: [async-batcher.ts:415](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L415) Processes the current batch of items immediately @@ -205,13 +279,58 @@ Processes the current batch of items immediately *** +### getAbortSignal() + +```ts +getAbortSignal(executeCount?): null | AbortSignal +``` + +Defined in: [async-batcher.ts:469](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L469) + +Returns the AbortSignal for a specific execution. +If no executeCount is provided, returns the signal for the most recent execution. +Returns null if no execution is found or not currently executing. + +#### Parameters + +##### executeCount? + +`number` + +Optional specific execution to get signal for + +#### Returns + +`null` \| `AbortSignal` + +#### Example + +```typescript +const batcher = new AsyncBatcher( + async (items: string[]) => { + const signal = batcher.getAbortSignal() + if (signal) { + const response = await fetch('/api/batch', { + method: 'POST', + body: JSON.stringify(items), + signal + }) + return response.json() + } + }, + { maxSize: 10, wait: 100 } +) +``` + +*** + ### peekAllItems() ```ts peekAllItems(): TValue[] ``` -Defined in: [async-batcher.ts:380](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L380) +Defined in: [async-batcher.ts:423](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L423) Returns a copy of all items in the async batcher @@ -227,7 +346,7 @@ Returns a copy of all items in the async batcher peekFailedItems(): TValue[] ``` -Defined in: [async-batcher.ts:384](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L384) +Defined in: [async-batcher.ts:427](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L427) #### Returns @@ -241,7 +360,7 @@ Defined in: [async-batcher.ts:384](https://github.com/TanStack/pacer/blob/main/p reset(): void ``` -Defined in: [async-batcher.ts:405](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L405) +Defined in: [async-batcher.ts:503](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L503) Resets the async batcher state to its default values @@ -257,7 +376,7 @@ Resets the async batcher state to its default values setOptions(newOptions): void ``` -Defined in: [async-batcher.ts:260](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L260) +Defined in: [async-batcher.ts:291](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L291) Updates the async batcher options diff --git a/docs/reference/classes/asyncdebouncer.md b/docs/reference/classes/asyncdebouncer.md index 31be6ea3..bb46e0c6 100644 --- a/docs/reference/classes/asyncdebouncer.md +++ b/docs/reference/classes/asyncdebouncer.md @@ -7,10 +7,23 @@ title: AsyncDebouncer # Class: AsyncDebouncer\ -Defined in: [async-debouncer.ts:188](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L188) +Defined in: [async-debouncer.ts:206](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L206) A class that creates an async debounced function. +Async vs Sync Versions: +The async version provides advanced features over the sync Debouncer: +- Returns promises that can be awaited for debounced function results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight executions +- Cancel support to prevent pending executions from starting +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) + +The sync Debouncer is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Debouncing? Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation. Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing or input changes where you only want to execute the handler after the events have stopped occurring. @@ -18,10 +31,6 @@ or input changes where you only want to execute the handler after the events hav Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until the function stops being called for the specified delay period. -Unlike the non-async Debouncer, this async version supports returning values from the debounced function, -making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call -instead of setting the result on a state variable from within the debounced function. - Error Handling: - If an `onError` handler is provided, it will be called with the error and debouncer instance - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown @@ -66,7 +75,7 @@ const results = await asyncDebouncer.maybeExecute(inputElement.value); new AsyncDebouncer(fn, initialOptions): AsyncDebouncer ``` -Defined in: [async-debouncer.ts:200](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L200) +Defined in: [async-debouncer.ts:218](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L218) #### Parameters @@ -84,13 +93,23 @@ Defined in: [async-debouncer.ts:200](https://github.com/TanStack/pacer/blob/main ## Properties +### asyncRetryers + +```ts +asyncRetryers: Map>; +``` + +Defined in: [async-debouncer.ts:212](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L212) + +*** + ### fn ```ts fn: TFn; ``` -Defined in: [async-debouncer.ts:201](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L201) +Defined in: [async-debouncer.ts:219](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L219) *** @@ -100,7 +119,7 @@ Defined in: [async-debouncer.ts:201](https://github.com/TanStack/pacer/blob/main key: string; ``` -Defined in: [async-debouncer.ts:192](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L192) +Defined in: [async-debouncer.ts:210](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L210) *** @@ -110,7 +129,7 @@ Defined in: [async-debouncer.ts:192](https://github.com/TanStack/pacer/blob/main options: AsyncDebouncerOptions; ``` -Defined in: [async-debouncer.ts:193](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L193) +Defined in: [async-debouncer.ts:211](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L211) *** @@ -120,19 +139,53 @@ Defined in: [async-debouncer.ts:193](https://github.com/TanStack/pacer/blob/main readonly store: Store>>; ``` -Defined in: [async-debouncer.ts:189](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L189) +Defined in: [async-debouncer.ts:207](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L207) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [async-debouncer.ts:240](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L240) + +Emits a change event for the async debouncer instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + +### abort() + +```ts +abort(): void +``` + +Defined in: [async-debouncer.ts:456](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L456) + +Aborts all ongoing executions with the internal abort controllers. +Does NOT cancel any pending execution that have not started yet. + +#### Returns + +`void` + +*** + ### cancel() ```ts cancel(): void ``` -Defined in: [async-debouncer.ts:410](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L410) +Defined in: [async-debouncer.ts:468](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L468) -Cancels any pending execution or aborts any execution in progress +Cancels any pending execution that have not started yet. +Does NOT abort any execution already in progress. #### Returns @@ -146,7 +199,7 @@ Cancels any pending execution or aborts any execution in progress flush(): Promise> ``` -Defined in: [async-debouncer.ts:362](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L362) +Defined in: [async-debouncer.ts:391](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L391) Processes the current pending execution immediately @@ -156,13 +209,54 @@ Processes the current pending execution immediately *** +### getAbortSignal() + +```ts +getAbortSignal(maybeExecuteCount?): null | AbortSignal +``` + +Defined in: [async-debouncer.ts:446](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L446) + +Returns the AbortSignal for a specific execution. +If no maybeExecuteCount is provided, returns the signal for the most recent execution. +Returns null if no execution is found or not currently executing. + +#### Parameters + +##### maybeExecuteCount? + +`number` + +Optional specific execution to get signal for + +#### Returns + +`null` \| `AbortSignal` + +#### Example + +```typescript +const debouncer = new AsyncDebouncer( + async (searchTerm: string) => { + const signal = debouncer.getAbortSignal() + if (signal) { + const response = await fetch(`/api/search?q=${searchTerm}`, { signal }) + return response.json() + } + }, + { wait: 300 } +) +``` + +*** + ### maybeExecute() ```ts maybeExecute(...args): Promise> ``` -Defined in: [async-debouncer.ts:282](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L282) +Defined in: [async-debouncer.ts:305](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L305) Attempts to execute the debounced function. If a call is already in progress, it will be queued. @@ -198,7 +292,7 @@ The error from the debounced function if no onError handler is configured reset(): void ``` -Defined in: [async-debouncer.ts:419](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L419) +Defined in: [async-debouncer.ts:476](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L476) Resets the debouncer state to its default values @@ -214,7 +308,7 @@ Resets the debouncer state to its default values setOptions(newOptions): void ``` -Defined in: [async-debouncer.ts:222](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L222) +Defined in: [async-debouncer.ts:245](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L245) Updates the async debouncer options diff --git a/docs/reference/classes/asyncqueuer.md b/docs/reference/classes/asyncqueuer.md index 52edde11..2ce2660e 100644 --- a/docs/reference/classes/asyncqueuer.md +++ b/docs/reference/classes/asyncqueuer.md @@ -7,22 +7,35 @@ title: AsyncQueuer # Class: AsyncQueuer\ -Defined in: [async-queuer.ts:271](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L271) +Defined in: [async-queuer.ts:303](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L303) A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration. -Features: +Async vs Sync Versions: +The async version provides advanced features over the sync Queuer: +- Returns promises that can be awaited for task results +- Built-in retry support via AsyncRetryer integration for each queued task +- Abort support to cancel in-flight task executions +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) +- Concurrent execution support (process multiple items simultaneously) + +The sync Queuer is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Queuing? +Queuing is a technique for managing and processing items sequentially or with controlled concurrency. +Tasks are processed up to the configured concurrency limit. When a task completes, +the next pending task is processed if the concurrency limit allows. + +Key Features: - Priority queue support via the getPriority option - Configurable concurrency limit - Callbacks for task success, error, completion, and queue state changes - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior - Pause and resume processing -- Task cancellation - Item expiration to remove stale items from the queue -Tasks are processed concurrently up to the configured concurrency limit. When a task completes, -the next pending task is processed if the concurrency limit allows. - Error Handling: - If an `onError` handler is provided, it will be called with the error and queuer instance - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown @@ -71,7 +84,7 @@ asyncQueuer.start(); new AsyncQueuer(fn, initialOptions): AsyncQueuer ``` -Defined in: [async-queuer.ts:279](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L279) +Defined in: [async-queuer.ts:315](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L315) #### Parameters @@ -89,13 +102,23 @@ Defined in: [async-queuer.ts:279](https://github.com/TanStack/pacer/blob/main/pa ## Properties +### asyncRetryers + +```ts +asyncRetryers: Map Promise>>; +``` + +Defined in: [async-queuer.ts:309](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L309) + +*** + ### fn() ```ts fn: (item) => Promise; ``` -Defined in: [async-queuer.ts:280](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L280) +Defined in: [async-queuer.ts:316](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L316) #### Parameters @@ -115,7 +138,7 @@ Defined in: [async-queuer.ts:280](https://github.com/TanStack/pacer/blob/main/pa key: string; ``` -Defined in: [async-queuer.ts:275](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L275) +Defined in: [async-queuer.ts:307](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L307) *** @@ -125,7 +148,7 @@ Defined in: [async-queuer.ts:275](https://github.com/TanStack/pacer/blob/main/pa options: AsyncQueuerOptions; ``` -Defined in: [async-queuer.ts:276](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L276) +Defined in: [async-queuer.ts:308](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L308) *** @@ -135,10 +158,43 @@ Defined in: [async-queuer.ts:276](https://github.com/TanStack/pacer/blob/main/pa readonly store: Store>>; ``` -Defined in: [async-queuer.ts:272](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L272) +Defined in: [async-queuer.ts:304](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L304) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [async-queuer.ts:354](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L354) + +Emits a change event for the async queuer instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + +### abort() + +```ts +abort(): void +``` + +Defined in: [async-queuer.ts:824](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L824) + +Aborts all ongoing executions with the internal abort controllers. +Does NOT clear out the items. + +#### Returns + +`void` + +*** + ### addItem() ```ts @@ -148,7 +204,7 @@ addItem( runOnItemsChange): boolean ``` -Defined in: [async-queuer.ts:421](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L421) +Defined in: [async-queuer.ts:462](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L462) Adds an item to the queue. If the queue is full, the item is rejected and onReject is called. Items can be inserted based on priority or at the front/back depending on configuration. @@ -186,9 +242,10 @@ queuer.addItem('task2', 'front'); clear(): void ``` -Defined in: [async-queuer.ts:734](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L734) +Defined in: [async-queuer.ts:789](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L789) -Removes all pending items from the queue. Does not affect active tasks. +Removes all pending items from the queue. +Does NOT affect active tasks. #### Returns @@ -202,7 +259,7 @@ Removes all pending items from the queue. Does not affect active tasks. execute(position?): Promise ``` -Defined in: [async-queuer.ts:556](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L556) +Defined in: [async-queuer.ts:597](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L597) Removes and returns the next item from the queue and executes the task function with it. @@ -232,7 +289,7 @@ queuer.execute('back'); flush(numberOfItems, position?): Promise ``` -Defined in: [async-queuer.ts:591](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L591) +Defined in: [async-queuer.ts:645](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L645) Processes a specified number of items to execute immediately with no wait time If no numberOfItems is provided, all items will be processed @@ -259,7 +316,7 @@ If no numberOfItems is provided, all items will be processed flushAsBatch(batchFunction): Promise ``` -Defined in: [async-queuer.ts:605](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L605) +Defined in: [async-queuer.ts:659](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L659) Processes all items in the queue as a batch using the provided function as an argument The queue is cleared after processing @@ -276,13 +333,54 @@ The queue is cleared after processing *** +### getAbortSignal() + +```ts +getAbortSignal(executeCount?): null | AbortSignal +``` + +Defined in: [async-queuer.ts:814](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L814) + +Returns the AbortSignal for a specific execution. +If no executeCount is provided, returns the signal for the most recent execution. +Returns null if no execution is found or not currently executing. + +#### Parameters + +##### executeCount? + +`number` + +Optional specific execution to get signal for + +#### Returns + +`null` \| `AbortSignal` + +#### Example + +```typescript +const queuer = new AsyncQueuer( + async (item: string) => { + const signal = queuer.getAbortSignal() + if (signal) { + const response = await fetch(`/api/process/${item}`, { signal }) + return response.json() + } + }, + { concurrency: 2 } +) +``` + +*** + ### getNextItem() ```ts getNextItem(position): undefined | TValue ``` -Defined in: [async-queuer.ts:504](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L504) +Defined in: [async-queuer.ts:545](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L545) Removes and returns the next item from the queue without executing the task function. Use for manual queue management. Normally, use execute() to process items. @@ -314,7 +412,7 @@ queuer.getNextItem('back'); peekActiveItems(): TValue[] ``` -Defined in: [async-queuer.ts:697](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L697) +Defined in: [async-queuer.ts:751](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L751) Returns the items currently being processed (active tasks). @@ -330,7 +428,7 @@ Returns the items currently being processed (active tasks). peekAllItems(): TValue[] ``` -Defined in: [async-queuer.ts:690](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L690) +Defined in: [async-queuer.ts:744](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L744) Returns a copy of all items in the queue, including active and pending items. @@ -346,7 +444,7 @@ Returns a copy of all items in the queue, including active and pending items. peekNextItem(position): undefined | TValue ``` -Defined in: [async-queuer.ts:680](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L680) +Defined in: [async-queuer.ts:734](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L734) Returns the next item in the queue without removing it. @@ -375,7 +473,7 @@ queuer.peekNextItem('back'); // back peekPendingItems(): TValue[] ``` -Defined in: [async-queuer.ts:704](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L704) +Defined in: [async-queuer.ts:758](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L758) Returns the items waiting to be processed (pending tasks). @@ -391,7 +489,7 @@ Returns the items waiting to be processed (pending tasks). reset(): void ``` -Defined in: [async-queuer.ts:742](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L742) +Defined in: [async-queuer.ts:835](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L835) Resets the queuer state to its default values @@ -407,7 +505,7 @@ Resets the queuer state to its default values setOptions(newOptions): void ``` -Defined in: [async-queuer.ts:318](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L318) +Defined in: [async-queuer.ts:359](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L359) Updates the queuer options. New options are merged with existing options. @@ -429,7 +527,7 @@ Updates the queuer options. New options are merged with existing options. start(): void ``` -Defined in: [async-queuer.ts:711](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L711) +Defined in: [async-queuer.ts:765](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L765) Starts processing items in the queue. If already running, does nothing. @@ -445,7 +543,7 @@ Starts processing items in the queue. If already running, does nothing. stop(): void ``` -Defined in: [async-queuer.ts:721](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L721) +Defined in: [async-queuer.ts:775](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L775) Stops processing items in the queue. Does not clear the queue. diff --git a/docs/reference/classes/asyncratelimiter.md b/docs/reference/classes/asyncratelimiter.md index 2dc02b87..ec846ad0 100644 --- a/docs/reference/classes/asyncratelimiter.md +++ b/docs/reference/classes/asyncratelimiter.md @@ -7,30 +7,38 @@ title: AsyncRateLimiter # Class: AsyncRateLimiter\ -Defined in: [async-rate-limiter.ts:216](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L216) +Defined in: [async-rate-limiter.ts:233](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L233) A class that creates an async rate-limited function. -Rate limiting is a simple approach that allows a function to execute up to a limit within a time window, +Async vs Sync Versions: +The async version provides advanced features over the sync RateLimiter: +- Returns promises that can be awaited for rate-limited function results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight executions +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts, rejection counts) +- More sophisticated window management with automatic cleanup + +The sync RateLimiter is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Rate Limiting? +Rate limiting allows a function to execute up to a limit within a time window, then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where all executions happen immediately, followed by a complete block. -The rate limiter supports two types of windows: +Window Types: - 'fixed': A strict window that resets after the window period. All executions within the window count towards the limit, and the window resets completely after the period. - 'sliding': A rolling window that allows executions as old ones expire. This provides a more consistent rate of execution over time. -Unlike the non-async RateLimiter, this async version supports returning values from the rate-limited function, -making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call -instead of setting the result on a state variable from within the rate-limited function. - -For smoother execution patterns, consider using: -- Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms) -- Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls) - +When to Use Rate Limiting: Rate limiting is best used for hard API limits or resource constraints. For UI updates or smoothing out frequent events, throttling or debouncing usually provide better user experience. +- Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms) +- Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls) State Management: - Uses TanStack Store for reactive state management @@ -87,7 +95,7 @@ const data = await rateLimiter.maybeExecute('123'); new AsyncRateLimiter(fn, initialOptions): AsyncRateLimiter ``` -Defined in: [async-rate-limiter.ts:224](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L224) +Defined in: [async-rate-limiter.ts:242](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L242) #### Parameters @@ -105,13 +113,23 @@ Defined in: [async-rate-limiter.ts:224](https://github.com/TanStack/pacer/blob/m ## Properties +### asyncRetryers + +```ts +asyncRetryers: Map>; +``` + +Defined in: [async-rate-limiter.ts:239](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L239) + +*** + ### fn ```ts fn: TFn; ``` -Defined in: [async-rate-limiter.ts:225](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L225) +Defined in: [async-rate-limiter.ts:243](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L243) *** @@ -121,7 +139,7 @@ Defined in: [async-rate-limiter.ts:225](https://github.com/TanStack/pacer/blob/m key: string; ``` -Defined in: [async-rate-limiter.ts:220](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L220) +Defined in: [async-rate-limiter.ts:237](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L237) *** @@ -131,7 +149,7 @@ Defined in: [async-rate-limiter.ts:220](https://github.com/TanStack/pacer/blob/m options: AsyncRateLimiterOptions; ``` -Defined in: [async-rate-limiter.ts:221](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L221) +Defined in: [async-rate-limiter.ts:238](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L238) *** @@ -141,17 +159,91 @@ Defined in: [async-rate-limiter.ts:221](https://github.com/TanStack/pacer/blob/m readonly store: Store>>; ``` -Defined in: [async-rate-limiter.ts:217](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L217) +Defined in: [async-rate-limiter.ts:234](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L234) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [async-rate-limiter.ts:267](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L267) + +Emits a change event for the async rate limiter instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + +### abort() + +```ts +abort(): void +``` + +Defined in: [async-rate-limiter.ts:526](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L526) + +Aborts all ongoing executions with the internal abort controllers. +Does NOT clear out the execution times or reset the rate limiter. + +#### Returns + +`void` + +*** + +### getAbortSignal() + +```ts +getAbortSignal(maybeExecuteCount?): null | AbortSignal +``` + +Defined in: [async-rate-limiter.ts:516](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L516) + +Returns the AbortSignal for a specific execution. +If no maybeExecuteCount is provided, returns the signal for the most recent execution. +Returns null if no execution is found or not currently executing. + +#### Parameters + +##### maybeExecuteCount? + +`number` + +Optional specific execution to get signal for + +#### Returns + +`null` \| `AbortSignal` + +#### Example + +```typescript +const rateLimiter = new AsyncRateLimiter( + async (userId: string) => { + const signal = rateLimiter.getAbortSignal() + if (signal) { + const response = await fetch(`/api/users/${userId}`, { signal }) + return response.json() + } + }, + { limit: 5, window: 1000 } +) +``` + +*** + ### getMsUntilNextWindow() ```ts getMsUntilNextWindow(): number ``` -Defined in: [async-rate-limiter.ts:457](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L457) +Defined in: [async-rate-limiter.ts:488](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L488) Returns the number of milliseconds until the next execution will be possible For fixed windows, this is the time until the current window resets @@ -169,7 +261,7 @@ For sliding windows, this is the time until the oldest execution expires getRemainingInWindow(): number ``` -Defined in: [async-rate-limiter.ts:447](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L447) +Defined in: [async-rate-limiter.ts:478](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L478) Returns the number of remaining executions allowed in the current window @@ -185,7 +277,7 @@ Returns the number of remaining executions allowed in the current window maybeExecute(...args): Promise> ``` -Defined in: [async-rate-limiter.ts:322](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L322) +Defined in: [async-rate-limiter.ts:345](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L345) Attempts to execute the rate-limited function if within the configured limits. Will reject execution if the number of calls in the current window exceeds the limit. @@ -233,7 +325,7 @@ const result2 = await rateLimiter.maybeExecute('arg1', 'arg2'); // undefined reset(): void ``` -Defined in: [async-rate-limiter.ts:468](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L468) +Defined in: [async-rate-limiter.ts:537](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L537) Resets the rate limiter state @@ -249,7 +341,7 @@ Resets the rate limiter state setOptions(newOptions): void ``` -Defined in: [async-rate-limiter.ts:249](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L249) +Defined in: [async-rate-limiter.ts:272](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L272) Updates the async rate limiter options diff --git a/docs/reference/classes/asyncretryer.md b/docs/reference/classes/asyncretryer.md new file mode 100644 index 00000000..ffd175da --- /dev/null +++ b/docs/reference/classes/asyncretryer.md @@ -0,0 +1,307 @@ +--- +id: AsyncRetryer +title: AsyncRetryer +--- + + + +# Class: AsyncRetryer\ + +Defined in: [async-retryer.ts:245](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L245) + +Provides robust retry functionality for asynchronous functions, supporting configurable backoff strategies, +attempt limits, timeout controls, and detailed state management. The AsyncRetryer class is designed to help you reliably +execute async operations that may fail intermittently, such as network requests or database operations, +by automatically retrying them according to your chosen policy. + +## Retrying Concepts + +- **Retrying**: Automatically re-executes a failed async function up to a specified number of attempts. + Useful for handling transient errors (e.g., network flakiness, rate limits, temporary server issues). +- **Backoff Strategies**: Controls the delay between retry attempts (default: `'exponential'`): + - `'exponential'`: Wait time doubles with each attempt (1s, 2s, 4s, ...) - **DEFAULT** + - `'linear'`: Wait time increases linearly (1s, 2s, 3s, ...) + - `'fixed'`: Waits a constant amount of time (`baseWait`) between each attempt +- **Jitter**: Adds randomness to retry delays to prevent thundering herd problems (default: `0`). + Set to a value between 0-1 to apply that percentage of random variation to each delay. +- **Timeout Controls**: Set limits on execution time to prevent hanging operations: + - `maxExecutionTime`: Maximum time for a single function call (default: `Infinity`) + - `maxTotalExecutionTime`: Maximum time for the entire retry operation (default: `Infinity`) +- **Abort & Cancellation**: Supports cancellation via an internal `AbortController`. Call `abort()` to stop retries. + +## State Management + +Uses TanStack Store for fine-grained reactivity. State can be accessed via the `store.state` property. + +Available state properties: +- `currentAttempt`: The current retry attempt number (0 when not executing) +- `executionCount`: Total number of completed executions (successful or failed) +- `isExecuting`: Whether the retryer is currently executing the function +- `lastError`: The most recent error encountered during execution +- `lastExecutionTime`: Timestamp of the last execution completion in milliseconds +- `lastResult`: The result from the most recent successful execution +- `status`: Current execution status ('disabled' | 'idle' | 'executing' | 'retrying') +- `totalExecutionTime`: Total time spent executing (including retries) in milliseconds + +## Error Handling + +The `throwOnError` option controls when errors are thrown (default: `'last'`): +- `'last'`: Only throws the final error after all retries are exhausted - **DEFAULT** +- `true`: Throws every error immediately (disables retrying) +- `false`: Never throws errors, returns `undefined` instead + +Callbacks for error handling: +- `onError`: Called for every error (including during retries) +- `onLastError`: Called only for the final error after all retries fail +- `onRetry`: Called before each retry attempt +- `onSuccess`: Called when execution succeeds +- `onSettled`: Called after execution completes (success or failure) + +## Usage + +- Use for async operations that may fail transiently and benefit from retrying. +- Configure `maxAttempts`, `backoff`, `baseWait`, and `jitter` to control retry behavior. +- Set `maxExecutionTime` and `maxTotalExecutionTime` to prevent hanging operations. +- Use `onRetry`, `onSuccess`, `onError`, `onLastError`, and `onSettled` for custom side effects. +- Call `abort()` to cancel ongoing execution and pending retries. +- Call `reset()` to reset state and cancel execution. + +## Example + +```typescript +// Retry a fetch operation up to 5 times with exponential backoff, jitter, and timeouts +const retryer = new AsyncRetryer(fetchData, { + maxAttempts: 5, + backoff: 'exponential', + baseWait: 1000, + jitter: 0.1, // Add 10% random variation to prevent thundering herd + maxExecutionTime: 5000, // Abort individual calls after 5 seconds + maxTotalExecutionTime: 30000, // Abort entire operation after 30 seconds + onRetry: (attempt, error) => console.log(`Retry attempt ${attempt} after error:`, error), + onSuccess: (result) => console.log('Success:', result), + onError: (error) => console.error('Error:', error), + onLastError: (error) => console.error('All retries failed:', error), +}) + +const result = await retryer.execute(userId) +``` + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +The async function type to be retried. + +## Constructors + +### new AsyncRetryer() + +```ts +new AsyncRetryer(fn, initialOptions): AsyncRetryer +``` + +Defined in: [async-retryer.ts:258](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L258) + +Creates a new AsyncRetryer instance + +#### Parameters + +##### fn + +`TFn` + +The async function to retry + +##### initialOptions + +[`AsyncRetryerOptions`](../../interfaces/asyncretryeroptions.md)\<`TFn`\> = `{}` + +Configuration options for the retryer + +#### Returns + +[`AsyncRetryer`](../asyncretryer.md)\<`TFn`\> + +## Properties + +### fn + +```ts +fn: TFn; +``` + +Defined in: [async-retryer.ts:259](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L259) + +The async function to retry + +*** + +### key + +```ts +key: string; +``` + +Defined in: [async-retryer.ts:249](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L249) + +*** + +### options + +```ts +options: AsyncRetryerOptions & Omit>, + | "initialState" + | "onError" + | "onSettled" + | "onSuccess" + | "key" + | "onLastError" +| "onRetry">; +``` + +Defined in: [async-retryer.ts:250](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L250) + +*** + +### store + +```ts +readonly store: Store>>; +``` + +Defined in: [async-retryer.ts:246](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L246) + +## Methods + +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [async-retryer.ts:282](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L282) + +Emits a change event for the async retryer instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + +### abort() + +```ts +abort(): void +``` + +Defined in: [async-retryer.ts:560](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L560) + +Cancels the current execution and any pending retries + +#### Returns + +`void` + +*** + +### execute() + +```ts +execute(...args): Promise> +``` + +Defined in: [async-retryer.ts:372](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L372) + +Executes the function with retry logic + +#### Parameters + +##### args + +...`Parameters`\<`TFn`\> + +Arguments to pass to the function + +#### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +The function result, or undefined if disabled or all retries failed (when throwOnError is false) + +#### Throws + +The last error if throwOnError is true and all retries fail + +*** + +### getAbortSignal() + +```ts +getAbortSignal(): null | AbortSignal +``` + +Defined in: [async-retryer.ts:553](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L553) + +Returns the current AbortSignal for the executing operation. +Use this signal in your async function to make it cancellable. +Returns null when not currently executing. + +#### Returns + +`null` \| `AbortSignal` + +#### Example + +```typescript +const retryer = new AsyncRetryer(async (userId: string) => { + const signal = retryer.getAbortSignal() + if (signal) { + return fetch(`/api/users/${userId}`, { signal }) + } + return fetch(`/api/users/${userId}`) +}) + +// Abort will now actually cancel the fetch +retryer.abort() +``` + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [async-retryer.ts:573](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L573) + +Resets the retryer to its initial state and cancels any ongoing execution + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [async-retryer.ts:288](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L288) + +Updates the retryer options + +#### Parameters + +##### newOptions + +`Partial`\<[`AsyncRetryerOptions`](../../interfaces/asyncretryeroptions.md)\<`TFn`\>\> + +Partial options to merge with existing options + +#### Returns + +`void` diff --git a/docs/reference/classes/asyncthrottler.md b/docs/reference/classes/asyncthrottler.md index 0e501414..ecd70779 100644 --- a/docs/reference/classes/asyncthrottler.md +++ b/docs/reference/classes/asyncthrottler.md @@ -7,18 +7,28 @@ title: AsyncThrottler # Class: AsyncThrottler\ -Defined in: [async-throttler.ts:199](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L199) +Defined in: [async-throttler.ts:218](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L218) A class that creates an async throttled function. +Async vs Sync Versions: +The async version provides advanced features over the sync Throttler: +- Returns promises that can be awaited for throttled function results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight executions +- Cancel support to prevent pending executions from starting +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) +- Waits for ongoing executions to complete before scheduling the next one + +The sync Throttler is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Throttling? Throttling limits how often a function can be executed, allowing only one execution within a specified time window. Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a regular interval regardless of how often it's called. -Unlike the non-async Throttler, this async version supports returning values from the throttled function, -making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call -instead of setting the result on a state variable from within the throttled function. - This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to ensure a maximum execution frequency. @@ -69,7 +79,7 @@ const result = await throttler.maybeExecute(inputElement.value); new AsyncThrottler(fn, initialOptions): AsyncThrottler ``` -Defined in: [async-throttler.ts:211](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L211) +Defined in: [async-throttler.ts:230](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L230) #### Parameters @@ -87,13 +97,23 @@ Defined in: [async-throttler.ts:211](https://github.com/TanStack/pacer/blob/main ## Properties +### asyncRetryers + +```ts +asyncRetryers: Map>; +``` + +Defined in: [async-throttler.ts:224](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L224) + +*** + ### fn ```ts fn: TFn; ``` -Defined in: [async-throttler.ts:212](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L212) +Defined in: [async-throttler.ts:231](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L231) *** @@ -103,7 +123,7 @@ Defined in: [async-throttler.ts:212](https://github.com/TanStack/pacer/blob/main key: string; ``` -Defined in: [async-throttler.ts:203](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L203) +Defined in: [async-throttler.ts:222](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L222) *** @@ -113,7 +133,7 @@ Defined in: [async-throttler.ts:203](https://github.com/TanStack/pacer/blob/main options: AsyncThrottlerOptions; ``` -Defined in: [async-throttler.ts:204](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L204) +Defined in: [async-throttler.ts:223](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L223) *** @@ -123,19 +143,53 @@ Defined in: [async-throttler.ts:204](https://github.com/TanStack/pacer/blob/main readonly store: Store>>; ``` -Defined in: [async-throttler.ts:200](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L200) +Defined in: [async-throttler.ts:219](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L219) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [async-throttler.ts:252](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L252) + +Emits a change event for the async throttler instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + +### abort() + +```ts +abort(): void +``` + +Defined in: [async-throttler.ts:520](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L520) + +Aborts all ongoing executions with the internal abort controllers. +Does NOT cancel any pending execution that have not started yet. + +#### Returns + +`void` + +*** + ### cancel() ```ts cancel(): void ``` -Defined in: [async-throttler.ts:444](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L444) +Defined in: [async-throttler.ts:530](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L530) -Cancels any pending execution or aborts any execution in progress +Cancels any pending execution that have not started yet. +Does NOT abort any execution already in progress. #### Returns @@ -149,7 +203,7 @@ Cancels any pending execution or aborts any execution in progress flush(): Promise> ``` -Defined in: [async-throttler.ts:393](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L393) +Defined in: [async-throttler.ts:449](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L449) Processes the current pending execution immediately @@ -159,13 +213,58 @@ Processes the current pending execution immediately *** +### getAbortSignal() + +```ts +getAbortSignal(maybeExecuteCount?): null | AbortSignal +``` + +Defined in: [async-throttler.ts:510](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L510) + +Returns the AbortSignal for a specific execution. +If no maybeExecuteCount is provided, returns the signal for the most recent execution. +Returns null if no execution is found or not currently executing. + +#### Parameters + +##### maybeExecuteCount? + +`number` + +Optional specific execution to get signal for + +#### Returns + +`null` \| `AbortSignal` + +#### Example + +```typescript +const throttler = new AsyncThrottler( + async (data: string) => { + const signal = throttler.getAbortSignal() + if (signal) { + const response = await fetch('/api/save', { + method: 'POST', + body: data, + signal + }) + return response.json() + } + }, + { wait: 1000 } +) +``` + +*** + ### maybeExecute() ```ts maybeExecute(...args): Promise> ``` -Defined in: [async-throttler.ts:300](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L300) +Defined in: [async-throttler.ts:325](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L325) Attempts to execute the throttled function. The execution behavior depends on the throttler options: @@ -207,7 +306,7 @@ await throttled.maybeExecute('c', 'd'); reset(): void ``` -Defined in: [async-throttler.ts:452](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L452) +Defined in: [async-throttler.ts:544](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L544) Resets the debouncer state to its default values @@ -223,7 +322,7 @@ Resets the debouncer state to its default values setOptions(newOptions): void ``` -Defined in: [async-throttler.ts:232](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L232) +Defined in: [async-throttler.ts:257](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L257) Updates the async throttler options diff --git a/docs/reference/classes/batcher.md b/docs/reference/classes/batcher.md index 2652c2b6..066a35f9 100644 --- a/docs/reference/classes/batcher.md +++ b/docs/reference/classes/batcher.md @@ -7,11 +7,12 @@ title: Batcher # Class: Batcher\ -Defined in: [batcher.ts:144](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L144) +Defined in: [batcher.ts:145](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L145) A class that collects items and processes them in batches. Batching is a technique for grouping multiple operations together to be processed as a single unit. +This synchronous version is lighter weight and often all you need - upgrade to AsyncBatcher when you need promises, retry support, abort/cancel capabilities, or advanced error handling. The Batcher provides a flexible way to implement batching with configurable: - Maximum batch size (number of items per batch) @@ -59,7 +60,7 @@ batcher.addItem(2); new Batcher(fn, initialOptions): Batcher ``` -Defined in: [batcher.ts:152](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L152) +Defined in: [batcher.ts:153](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L153) #### Parameters @@ -83,7 +84,7 @@ Defined in: [batcher.ts:152](https://github.com/TanStack/pacer/blob/main/package fn: (items) => void; ``` -Defined in: [batcher.ts:153](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L153) +Defined in: [batcher.ts:154](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L154) #### Parameters @@ -103,7 +104,7 @@ Defined in: [batcher.ts:153](https://github.com/TanStack/pacer/blob/main/package key: string; ``` -Defined in: [batcher.ts:148](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L148) +Defined in: [batcher.ts:149](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L149) *** @@ -113,7 +114,7 @@ Defined in: [batcher.ts:148](https://github.com/TanStack/pacer/blob/main/package options: BatcherOptionsWithOptionalCallbacks; ``` -Defined in: [batcher.ts:149](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L149) +Defined in: [batcher.ts:150](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L150) *** @@ -123,17 +124,33 @@ Defined in: [batcher.ts:149](https://github.com/TanStack/pacer/blob/main/package readonly store: Store>>; ``` -Defined in: [batcher.ts:145](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L145) +Defined in: [batcher.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L146) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [batcher.ts:174](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L174) + +Emits a change event for the batcher instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + ### addItem() ```ts addItem(item): void ``` -Defined in: [batcher.ts:204](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L204) +Defined in: [batcher.ts:210](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L210) Adds an item to the batcher If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed @@ -150,13 +167,30 @@ If the batch size is reached, timeout occurs, or shouldProcess returns true, the *** +### cancel() + +```ts +cancel(): void +``` + +Defined in: [batcher.ts:288](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L288) + +Cancels any pending execution that was scheduled. +Does NOT clear out the items. + +#### Returns + +`void` + +*** + ### clear() ```ts clear(): void ``` -Defined in: [batcher.ts:274](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L274) +Defined in: [batcher.ts:280](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L280) Removes all items from the batcher @@ -172,7 +206,7 @@ Removes all items from the batcher flush(): void ``` -Defined in: [batcher.ts:252](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L252) +Defined in: [batcher.ts:258](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L258) Processes the current batch of items immediately @@ -188,7 +222,7 @@ Processes the current batch of items immediately peekAllItems(): TValue[] ``` -Defined in: [batcher.ts:260](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L260) +Defined in: [batcher.ts:266](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L266) Returns a copy of all items in the batcher @@ -204,7 +238,7 @@ Returns a copy of all items in the batcher reset(): void ``` -Defined in: [batcher.ts:281](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L281) +Defined in: [batcher.ts:296](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L296) Resets the batcher state to its default values @@ -220,7 +254,7 @@ Resets the batcher state to its default values setOptions(newOptions): void ``` -Defined in: [batcher.ts:173](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L173) +Defined in: [batcher.ts:179](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L179) Updates the batcher options diff --git a/docs/reference/classes/debouncer.md b/docs/reference/classes/debouncer.md index ea0cbe6c..19be306b 100644 --- a/docs/reference/classes/debouncer.md +++ b/docs/reference/classes/debouncer.md @@ -7,13 +7,14 @@ title: Debouncer # Class: Debouncer\ -Defined in: [debouncer.ts:129](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L129) +Defined in: [debouncer.ts:130](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L130) A class that creates a debounced function. Debouncing ensures that a function is only executed after a certain amount of time has passed since its last invocation. This is useful for handling frequent events like window resizing, scroll events, or input changes where you want to limit the rate of execution. +This synchronous version is lighter weight and often all you need - upgrade to AsyncDebouncer when you need promises, retry support, abort/cancel capabilities, or advanced error handling. The debounced function can be configured to execute either at the start of the delay period (leading edge) or at the end (trailing edge, default). Each new call during the wait period @@ -52,7 +53,7 @@ inputElement.addEventListener('input', () => { new Debouncer(fn, initialOptions): Debouncer ``` -Defined in: [debouncer.ts:137](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L137) +Defined in: [debouncer.ts:138](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L138) #### Parameters @@ -76,7 +77,7 @@ Defined in: [debouncer.ts:137](https://github.com/TanStack/pacer/blob/main/packa fn: TFn; ``` -Defined in: [debouncer.ts:138](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L138) +Defined in: [debouncer.ts:139](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L139) *** @@ -86,7 +87,7 @@ Defined in: [debouncer.ts:138](https://github.com/TanStack/pacer/blob/main/packa key: string; ``` -Defined in: [debouncer.ts:133](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L133) +Defined in: [debouncer.ts:134](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L134) *** @@ -96,7 +97,7 @@ Defined in: [debouncer.ts:133](https://github.com/TanStack/pacer/blob/main/packa options: DebouncerOptions; ``` -Defined in: [debouncer.ts:134](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L134) +Defined in: [debouncer.ts:135](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L135) *** @@ -106,17 +107,33 @@ Defined in: [debouncer.ts:134](https://github.com/TanStack/pacer/blob/main/packa readonly store: Store>>; ``` -Defined in: [debouncer.ts:130](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L130) +Defined in: [debouncer.ts:131](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L131) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [debouncer.ts:159](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L159) + +Emits a change event for the debouncer instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + ### cancel() ```ts cancel(): void ``` -Defined in: [debouncer.ts:268](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L268) +Defined in: [debouncer.ts:274](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L274) Cancels any pending execution @@ -132,7 +149,7 @@ Cancels any pending execution flush(): void ``` -Defined in: [debouncer.ts:251](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L251) +Defined in: [debouncer.ts:257](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L257) Processes the current pending execution immediately @@ -148,7 +165,7 @@ Processes the current pending execution immediately maybeExecute(...args): void ``` -Defined in: [debouncer.ts:204](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L204) +Defined in: [debouncer.ts:210](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L210) Attempts to execute the debounced function If a call is already in progress, it will be queued @@ -171,7 +188,7 @@ If a call is already in progress, it will be queued reset(): void ``` -Defined in: [debouncer.ts:279](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L279) +Defined in: [debouncer.ts:285](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L285) Resets the debouncer state to its default values @@ -187,7 +204,7 @@ Resets the debouncer state to its default values setOptions(newOptions): void ``` -Defined in: [debouncer.ts:158](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L158) +Defined in: [debouncer.ts:164](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L164) Updates the debouncer options diff --git a/docs/reference/classes/queuer.md b/docs/reference/classes/queuer.md index f96b618c..d1c6969c 100644 --- a/docs/reference/classes/queuer.md +++ b/docs/reference/classes/queuer.md @@ -7,10 +7,12 @@ title: Queuer # Class: Queuer\ -Defined in: [queuer.ts:255](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L255) +Defined in: [queuer.ts:257](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L257) A flexible queue that processes items with configurable wait times, expiration, and priority. +This synchronous version is lighter weight and often all you need - upgrade to AsyncQueuer when you need promises, retry support, abort capabilities, concurrent execution, or advanced error handling. + Features: - Automatic or manual processing of items - FIFO (First In First Out), LIFO (Last In First Out), or double-ended queue behavior @@ -92,7 +94,7 @@ manualQueue.getNextItem(); // returns 2, queue is empty new Queuer(fn, initialOptions): Queuer ``` -Defined in: [queuer.ts:263](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L263) +Defined in: [queuer.ts:265](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L265) #### Parameters @@ -116,7 +118,7 @@ Defined in: [queuer.ts:263](https://github.com/TanStack/pacer/blob/main/packages fn: (item) => void; ``` -Defined in: [queuer.ts:264](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L264) +Defined in: [queuer.ts:266](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L266) #### Parameters @@ -136,7 +138,7 @@ Defined in: [queuer.ts:264](https://github.com/TanStack/pacer/blob/main/packages key: string; ``` -Defined in: [queuer.ts:259](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L259) +Defined in: [queuer.ts:261](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L261) *** @@ -146,7 +148,7 @@ Defined in: [queuer.ts:259](https://github.com/TanStack/pacer/blob/main/packages options: QueuerOptions; ``` -Defined in: [queuer.ts:260](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L260) +Defined in: [queuer.ts:262](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L262) *** @@ -156,10 +158,26 @@ Defined in: [queuer.ts:260](https://github.com/TanStack/pacer/blob/main/packages readonly store: Store>>; ``` -Defined in: [queuer.ts:256](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L256) +Defined in: [queuer.ts:258](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L258) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [queuer.ts:303](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L303) + +Emits a change event for the queuer instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + ### addItem() ```ts @@ -169,7 +187,7 @@ addItem( runOnItemsChange): boolean ``` -Defined in: [queuer.ts:384](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L384) +Defined in: [queuer.ts:392](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L392) Adds an item to the queue. If the queue is full, the item is rejected and onReject is called. Items can be inserted based on priority or at the front/back depending on configuration. @@ -208,7 +226,7 @@ queuer.addItem('task2', 'front'); clear(): void ``` -Defined in: [queuer.ts:666](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L666) +Defined in: [queuer.ts:674](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L674) Removes all pending items from the queue. Does not affect items being processed. @@ -224,7 +242,7 @@ Removes all pending items from the queue. Does not affect items being processed. execute(position?): undefined | TValue ``` -Defined in: [queuer.ts:520](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L520) +Defined in: [queuer.ts:528](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L528) Removes and returns the next item from the queue and processes it using the provided function. @@ -253,7 +271,7 @@ queuer.execute('back'); flush(numberOfItems, position?): void ``` -Defined in: [queuer.ts:536](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L536) +Defined in: [queuer.ts:544](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L544) Processes a specified number of items to execute immediately with no wait time If no numberOfItems is provided, all items will be processed @@ -280,7 +298,7 @@ If no numberOfItems is provided, all items will be processed flushAsBatch(batchFunction): void ``` -Defined in: [queuer.ts:551](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L551) +Defined in: [queuer.ts:559](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L559) Processes all items in the queue as a batch using the provided function as an argument The queue is cleared after processing @@ -303,7 +321,7 @@ The queue is cleared after processing getNextItem(position): undefined | TValue ``` -Defined in: [queuer.ts:468](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L468) +Defined in: [queuer.ts:476](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L476) Removes and returns the next item from the queue without executing the function. Use for manual queue management. Normally, use execute() to process items. @@ -334,7 +352,7 @@ queuer.getNextItem('back'); peekAllItems(): TValue[] ``` -Defined in: [queuer.ts:634](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L634) +Defined in: [queuer.ts:642](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L642) Returns a copy of all items in the queue. @@ -350,7 +368,7 @@ Returns a copy of all items in the queue. peekNextItem(position): undefined | TValue ``` -Defined in: [queuer.ts:624](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L624) +Defined in: [queuer.ts:632](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L632) Returns the next item in the queue without removing it. @@ -378,7 +396,7 @@ queuer.peekNextItem('back'); // back reset(): void ``` -Defined in: [queuer.ts:674](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L674) +Defined in: [queuer.ts:682](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L682) Resets the queuer state to its default values @@ -394,7 +412,7 @@ Resets the queuer state to its default values setOptions(newOptions): void ``` -Defined in: [queuer.ts:300](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L300) +Defined in: [queuer.ts:308](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L308) Updates the queuer options. New options are merged with existing options. @@ -416,7 +434,7 @@ Updates the queuer options. New options are merged with existing options. start(): void ``` -Defined in: [queuer.ts:641](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L641) +Defined in: [queuer.ts:649](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L649) Starts processing items in the queue. If already isRunning, does nothing. @@ -432,7 +450,7 @@ Starts processing items in the queue. If already isRunning, does nothing. stop(): void ``` -Defined in: [queuer.ts:651](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L651) +Defined in: [queuer.ts:659](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L659) Stops processing items in the queue. Does not clear the queue. diff --git a/docs/reference/classes/ratelimiter.md b/docs/reference/classes/ratelimiter.md index 42ef33ea..40f36c9d 100644 --- a/docs/reference/classes/ratelimiter.md +++ b/docs/reference/classes/ratelimiter.md @@ -7,13 +7,14 @@ title: RateLimiter # Class: RateLimiter\ -Defined in: [rate-limiter.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L143) +Defined in: [rate-limiter.ts:144](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L144) A class that creates a rate-limited function. Rate limiting is a simple approach that allows a function to execute up to a limit within a time window, then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where all executions happen immediately, followed by a complete block. +This synchronous version is lighter weight and often all you need - upgrade to AsyncRateLimiter when you need promises, retry support, abort capabilities, or advanced error handling. The rate limiter supports two types of windows: - 'fixed': A strict window that resets after the window period. All executions within the window count @@ -65,7 +66,7 @@ rateLimiter.maybeExecute('123'); new RateLimiter(fn, initialOptions): RateLimiter ``` -Defined in: [rate-limiter.ts:150](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L150) +Defined in: [rate-limiter.ts:151](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L151) #### Parameters @@ -89,7 +90,7 @@ Defined in: [rate-limiter.ts:150](https://github.com/TanStack/pacer/blob/main/pa fn: TFn; ``` -Defined in: [rate-limiter.ts:151](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L151) +Defined in: [rate-limiter.ts:152](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L152) *** @@ -99,7 +100,7 @@ Defined in: [rate-limiter.ts:151](https://github.com/TanStack/pacer/blob/main/pa key: string; ``` -Defined in: [rate-limiter.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L146) +Defined in: [rate-limiter.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L147) *** @@ -109,7 +110,7 @@ Defined in: [rate-limiter.ts:146](https://github.com/TanStack/pacer/blob/main/pa options: RateLimiterOptions; ``` -Defined in: [rate-limiter.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L147) +Defined in: [rate-limiter.ts:148](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L148) *** @@ -119,17 +120,33 @@ Defined in: [rate-limiter.ts:147](https://github.com/TanStack/pacer/blob/main/pa readonly store: Store>; ``` -Defined in: [rate-limiter.ts:144](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L144) +Defined in: [rate-limiter.ts:145](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L145) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [rate-limiter.ts:175](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L175) + +Emits a change event for the rate limiter instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + ### getMsUntilNextWindow() ```ts getMsUntilNextWindow(): number ``` -Defined in: [rate-limiter.ts:341](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L341) +Defined in: [rate-limiter.ts:347](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L347) Returns the number of milliseconds until the next execution will be possible @@ -145,7 +162,7 @@ Returns the number of milliseconds until the next execution will be possible getRemainingInWindow(): number ``` -Defined in: [rate-limiter.ts:333](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L333) +Defined in: [rate-limiter.ts:339](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L339) Returns the number of remaining executions allowed in the current window @@ -161,7 +178,7 @@ Returns the number of remaining executions allowed in the current window maybeExecute(...args): boolean ``` -Defined in: [rate-limiter.ts:235](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L235) +Defined in: [rate-limiter.ts:241](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L241) Attempts to execute the rate-limited function if within the configured limits. Will reject execution if the number of calls in the current window exceeds the limit. @@ -196,7 +213,7 @@ rateLimiter.maybeExecute('arg1', 'arg2'); // false reset(): void ``` -Defined in: [rate-limiter.ts:352](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L352) +Defined in: [rate-limiter.ts:358](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L358) Resets the rate limiter state @@ -212,7 +229,7 @@ Resets the rate limiter state setOptions(newOptions): void ``` -Defined in: [rate-limiter.ts:174](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L174) +Defined in: [rate-limiter.ts:180](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L180) Updates the rate limiter options diff --git a/docs/reference/classes/throttler.md b/docs/reference/classes/throttler.md index af0001a2..5af8a07c 100644 --- a/docs/reference/classes/throttler.md +++ b/docs/reference/classes/throttler.md @@ -7,13 +7,14 @@ title: Throttler # Class: Throttler\ -Defined in: [throttler.ts:137](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L137) +Defined in: [throttler.ts:138](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L138) A class that creates a throttled function. Throttling ensures a function is called at most once within a specified time window. Unlike debouncing which waits for a pause in calls, throttling guarantees consistent execution timing regardless of call frequency. +This synchronous version is lighter weight and often all you need - upgrade to AsyncThrottler when you need promises, retry support, abort/cancel capabilities, or advanced error handling. Supports both leading and trailing edge execution: - Leading: Execute immediately on first call (default: true) @@ -56,7 +57,7 @@ throttler.maybeExecute('123'); // Throttled new Throttler(fn, initialOptions): Throttler ``` -Defined in: [throttler.ts:145](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L145) +Defined in: [throttler.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L146) #### Parameters @@ -80,7 +81,7 @@ Defined in: [throttler.ts:145](https://github.com/TanStack/pacer/blob/main/packa fn: TFn; ``` -Defined in: [throttler.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L146) +Defined in: [throttler.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L147) *** @@ -90,7 +91,7 @@ Defined in: [throttler.ts:146](https://github.com/TanStack/pacer/blob/main/packa key: undefined | string; ``` -Defined in: [throttler.ts:141](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L141) +Defined in: [throttler.ts:142](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L142) *** @@ -100,7 +101,7 @@ Defined in: [throttler.ts:141](https://github.com/TanStack/pacer/blob/main/packa options: ThrottlerOptions; ``` -Defined in: [throttler.ts:142](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L142) +Defined in: [throttler.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L143) *** @@ -110,17 +111,33 @@ Defined in: [throttler.ts:142](https://github.com/TanStack/pacer/blob/main/packa readonly store: Store>>; ``` -Defined in: [throttler.ts:138](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L138) +Defined in: [throttler.ts:139](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L139) ## Methods +### \_emit() + +```ts +_emit(): void +``` + +Defined in: [throttler.ts:167](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L167) + +Emits a change event for the throttler instance. Mostly useful for devtools. + +#### Returns + +`void` + +*** + ### cancel() ```ts cancel(): void ``` -Defined in: [throttler.ts:305](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L305) +Defined in: [throttler.ts:311](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L311) Cancels any pending trailing execution and clears internal state. @@ -142,7 +159,7 @@ Has no effect if there is no pending execution. flush(): void ``` -Defined in: [throttler.ts:283](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L283) +Defined in: [throttler.ts:289](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L289) Processes the current pending execution immediately @@ -158,7 +175,7 @@ Processes the current pending execution immediately maybeExecute(...args): void ``` -Defined in: [throttler.ts:224](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L224) +Defined in: [throttler.ts:230](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L230) Attempts to execute the throttled function. The execution behavior depends on the throttler options: @@ -200,7 +217,7 @@ throttled.maybeExecute('c', 'd'); reset(): void ``` -Defined in: [throttler.ts:316](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L316) +Defined in: [throttler.ts:322](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L322) Resets the throttler state to its default values @@ -216,7 +233,7 @@ Resets the throttler state to its default values setOptions(newOptions): void ``` -Defined in: [throttler.ts:166](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L166) +Defined in: [throttler.ts:172](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L172) Updates the throttler options diff --git a/docs/reference/functions/asyncbatch.md b/docs/reference/functions/asyncbatch.md index 55a6e9ad..6ef8fda0 100644 --- a/docs/reference/functions/asyncbatch.md +++ b/docs/reference/functions/asyncbatch.md @@ -8,18 +8,34 @@ title: asyncBatch # Function: asyncBatch() ```ts -function asyncBatch(fn, options): (item) => void +function asyncBatch(fn, options): (item) => Promise ``` -Defined in: [async-batcher.ts:460](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L460) +Defined in: [async-batcher.ts:573](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L573) -Creates an async batcher that processes items in batches +Creates an async batcher that processes items in batches. -Unlike the sync batcher, this async version: -- Handles promises and returns results from batch executions -- Provides error handling with configurable error behavior -- Tracks success, error, and settle counts separately -- Has state tracking for when batches are executing +Async vs Sync Versions: +The async version provides advanced features over the sync batch function: +- Returns promises that can be awaited for batch results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight batch executions +- Cancel support to prevent pending batches from starting +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) + +The sync batch function is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Batching? +Batching is a technique for grouping multiple operations together to be processed as a single unit. + +Configuration Options: +- `maxSize`: Maximum number of items per batch (default: Infinity) +- `wait`: Time to wait before processing batch (default: Infinity) +- `getShouldExecute`: Custom logic to trigger batch processing +- `asyncRetryerOptions`: Configure retry behavior for batch executions +- `started`: Whether to start processing immediately (default: true) Error Handling: - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher instance @@ -34,7 +50,6 @@ State Management: - Use `onSuccess` callback to react to successful batch execution and implement custom logic - Use `onError` callback to react to batch execution errors and implement custom error handling - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic -- Use `onExecute` callback to react to batch execution and implement custom logic - Use `onItemsChange` callback to react to items being added or removed from the batcher - The state includes total items processed, success/error counts, and execution status - State can be accessed via the underlying AsyncBatcher instance's `store.state` property @@ -69,7 +84,13 @@ If the batch size is reached, timeout occurs, or shouldProcess returns true, the ### Returns -`void` +`Promise`\<`any`\> + +The result from the batch function, or undefined if an error occurred and was handled by onError + +### Throws + +The error from the batch function if no onError handler is configured or throwOnError is true ## Example diff --git a/docs/reference/functions/asyncdebounce.md b/docs/reference/functions/asyncdebounce.md index 47f3facc..4803e6d2 100644 --- a/docs/reference/functions/asyncdebounce.md +++ b/docs/reference/functions/asyncdebounce.md @@ -11,15 +11,35 @@ title: asyncDebounce function asyncDebounce(fn, initialOptions): (...args) => Promise> ``` -Defined in: [async-debouncer.ts:468](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L468) +Defined in: [async-debouncer.ts:545](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L545) Creates an async debounced function that delays execution until after a specified wait time. The debounced function will only execute once the wait period has elapsed without any new calls. If called again during the wait period, the timer resets and a new wait period begins. -Unlike the non-async Debouncer, this async version supports returning values from the debounced function, -making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call -instead of setting the result on a state variable from within the debounced function. +Async vs Sync Versions: +The async version provides advanced features over the sync debounce function: +- Returns promises that can be awaited for debounced function results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight executions +- Cancel support to prevent pending executions from starting +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) + +The sync debounce function is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Debouncing? +Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation. +Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing +or input changes where you only want to execute the handler after the events have stopped occurring. + +Configuration Options: +- `wait`: Delay in milliseconds to wait after the last call (required) +- `leading`: Execute on the leading edge of the timeout (default: false) +- `trailing`: Execute on the trailing edge of the timeout (default: true) +- `enabled`: Whether the debouncer is enabled (default: true) +- `asyncRetryerOptions`: Configure retry behavior for executions Error Handling: - If an `onError` handler is provided, it will be called with the error and debouncer instance diff --git a/docs/reference/functions/asyncqueue.md b/docs/reference/functions/asyncqueue.md index f3155ab6..7e706105 100644 --- a/docs/reference/functions/asyncqueue.md +++ b/docs/reference/functions/asyncqueue.md @@ -11,11 +11,39 @@ title: asyncQueue function asyncQueue(fn, initialOptions): (item, position, runOnItemsChange) => boolean ``` -Defined in: [async-queuer.ts:781](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L781) +Defined in: [async-queuer.ts:906](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L906) Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks. The queuer is started automatically and ready to process items. +Async vs Sync Versions: +The async version provides advanced features over the sync queue function: +- Returns promises that can be awaited for task results +- Built-in retry support via AsyncRetryer integration for each queued task +- Abort support to cancel in-flight task executions +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) +- Concurrent execution support (process multiple items simultaneously) + +The sync queue function is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Queuing? +Queuing is a technique for managing and processing items sequentially or with controlled concurrency. +Tasks are processed up to the configured concurrency limit. When a task completes, +the next pending task is processed if the concurrency limit allows. + +Configuration Options: +- `concurrency`: Maximum number of concurrent tasks (default: 1) +- `wait`: Time to wait between processing items (default: 0) +- `maxSize`: Maximum number of items allowed in the queue (default: Infinity) +- `getPriority`: Function to determine item priority +- `addItemsTo`: Default position to add items ('back' or 'front', default: 'back') +- `getItemsFrom`: Default position to get items ('front' or 'back', default: 'front') +- `expirationDuration`: Maximum time items can stay in queue +- `started`: Whether to start processing immediately (default: true) +- `asyncRetryerOptions`: Configure retry behavior for task executions + Error Handling: - If an `onError` handler is provided, it will be called with the error and queuer instance - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown @@ -36,15 +64,6 @@ State Management: - State can be accessed via the underlying AsyncQueuer instance's `store.state` property - When using framework adapters (React/Solid), state is accessed from the hook's state property -Example usage: -```ts -const enqueue = asyncQueue(async (item) => { - return item.toUpperCase(); -}, {...options}); - -enqueue('hello'); -``` - ## Type Parameters • **TValue** @@ -90,3 +109,17 @@ Items can be inserted based on priority or at the front/back depending on config queuer.addItem({ value: 'task', priority: 10 }); queuer.addItem('task2', 'front'); ``` + +## Example + +```ts +const enqueue = asyncQueue(async (item) => { + return item.toUpperCase(); +}, { + concurrency: 2, + wait: 100, + onSuccess: (result) => console.log('Processed:', result) +}); + +enqueue('hello'); +``` diff --git a/docs/reference/functions/asyncratelimit.md b/docs/reference/functions/asyncratelimit.md index d74d8276..306a70d1 100644 --- a/docs/reference/functions/asyncratelimit.md +++ b/docs/reference/functions/asyncratelimit.md @@ -11,25 +11,55 @@ title: asyncRateLimit function asyncRateLimit(fn, initialOptions): (...args) => Promise> ``` -Defined in: [async-rate-limiter.ts:539](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L539) +Defined in: [async-rate-limiter.ts:627](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L627) Creates an async rate-limited function that will execute the provided function up to a maximum number of times within a time window. -Unlike the non-async rate limiter, this async version supports returning values from the rate-limited function, -making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call -instead of setting the result on a state variable from within the rate-limited function. +Async vs Sync Versions: +The async version provides advanced features over the sync rate limit function: +- Returns promises that can be awaited for rate-limited function results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight executions +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts, rejection counts) +- More sophisticated window management with automatic cleanup -The rate limiter supports two types of windows: +The sync rate limit function is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Rate Limiting? +Rate limiting allows a function to execute up to a limit within a time window, +then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where +all executions happen immediately, followed by a complete block. + +Window Types: - 'fixed': A strict window that resets after the window period. All executions within the window count towards the limit, and the window resets completely after the period. - 'sliding': A rolling window that allows executions as old ones expire. This provides a more consistent rate of execution over time. -Note that rate limiting is a simpler form of execution control compared to throttling or debouncing: +Configuration Options: +- `limit`: Maximum number of executions allowed within the window (required) +- `window`: Time window in milliseconds (required) +- `windowType`: 'fixed' or 'sliding' (default: 'fixed') +- `enabled`: Whether the rate limiter is enabled (default: true) +- `asyncRetryerOptions`: Configure retry behavior for executions + +When to Use Rate Limiting: +Rate limiting is best used for hard API limits or resource constraints. For UI updates or +smoothing out frequent events, throttling or debouncing usually provide better user experience. - A rate limiter will allow all executions until the limit is reached, then block all subsequent calls until the window resets - A throttler ensures even spacing between executions, which can be better for consistent performance - A debouncer collapses multiple calls into one, which is better for handling bursts of events +Error Handling: +- If an `onError` handler is provided, it will be called with the error and rate limiter instance +- If `throwOnError` is true (default when no onError handler is provided), the error will be thrown +- If `throwOnError` is false (default when onError handler is provided), the error will be swallowed +- Both onError and throwOnError can be used together - the handler will be called before any error is thrown +- The error state can be checked using the underlying AsyncRateLimiter instance +- Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler + State Management: - Uses TanStack Store for reactive state management - Use `initialState` to provide initial state values when creating the rate limiter @@ -42,17 +72,6 @@ State Management: - State can be accessed via the underlying AsyncRateLimiter instance's `store.state` property - When using framework adapters (React/Solid), state is accessed from the hook's state property -Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically -need to enforce a hard limit on the number of executions within a time period. - -Error Handling: -- If an `onError` handler is provided, it will be called with the error and rate limiter instance -- If `throwOnError` is true (default when no onError handler is provided), the error will be thrown -- If `throwOnError` is false (default when onError handler is provided), the error will be swallowed -- Both onError and throwOnError can be used together - the handler will be called before any error is thrown -- The error state can be checked using the underlying AsyncRateLimiter instance -- Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler - ## Type Parameters • **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) diff --git a/docs/reference/functions/asyncretry.md b/docs/reference/functions/asyncretry.md new file mode 100644 index 00000000..ce29fb55 --- /dev/null +++ b/docs/reference/functions/asyncretry.md @@ -0,0 +1,61 @@ +--- +id: asyncRetry +title: asyncRetry +--- + + + +# Function: asyncRetry() + +```ts +function asyncRetry(fn, initialOptions): (...args) => Promise> +``` + +Defined in: [async-retryer.ts:596](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L596) + +Creates a retry-enabled version of an async function + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Parameters + +### fn + +`TFn` + +The async function to add retry functionality to + +### initialOptions + +[`AsyncRetryerOptions`](../../interfaces/asyncretryeroptions.md)\<`TFn`\> = `{}` + +Configuration options for the retry behavior + +## Returns + +`Function` + +A new function that executes the original with retry logic + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +## Example + +```typescript +const retryFetch = asyncRetry(fetch, { + maxAttempts: 3, + backoff: 'exponential' // default +}) + +const response = await retryFetch('/api/data') +``` diff --git a/docs/reference/functions/asyncthrottle.md b/docs/reference/functions/asyncthrottle.md index 07dfd8a9..2264fa26 100644 --- a/docs/reference/functions/asyncthrottle.md +++ b/docs/reference/functions/asyncthrottle.md @@ -11,15 +11,36 @@ title: asyncThrottle function asyncThrottle(fn, initialOptions): (...args) => Promise> ``` -Defined in: [async-throttler.ts:500](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L500) +Defined in: [async-throttler.ts:613](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L613) Creates an async throttled function that limits how often the function can execute. The throttled function will execute at most once per wait period, even if called multiple times. If called while executing, it will wait until execution completes before scheduling the next call. -Unlike the non-async Throttler, this async version supports returning values from the throttled function, -making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call -instead of setting the result on a state variable from within the throttled function. +Async vs Sync Versions: +The async version provides advanced features over the sync throttle function: +- Returns promises that can be awaited for throttled function results +- Built-in retry support via AsyncRetryer integration +- Abort support to cancel in-flight executions +- Cancel support to prevent pending executions from starting +- Comprehensive error handling with onError callbacks and throwOnError control +- Detailed execution tracking (success/error/settle counts) +- Waits for ongoing executions to complete before scheduling the next one + +The sync throttle function is lighter weight and simpler when you don't need async features, +return values, or execution control. + +What is Throttling? +Throttling limits how often a function can be executed, allowing only one execution within a specified time window. +Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a +regular interval regardless of how often it's called. + +Configuration Options: +- `wait`: Time window in milliseconds during which the function can only execute once (required) +- `leading`: Execute immediately when called (default: true) +- `trailing`: Execute on the trailing edge of the wait period (default: true) +- `enabled`: Whether the throttler is enabled (default: true) +- `asyncRetryerOptions`: Configure retry behavior for executions Error Handling: - If an `onError` handler is provided, it will be called with the error and throttler instance diff --git a/docs/reference/functions/batch.md b/docs/reference/functions/batch.md index 4e20536e..d96ff465 100644 --- a/docs/reference/functions/batch.md +++ b/docs/reference/functions/batch.md @@ -11,9 +11,11 @@ title: batch function batch(fn, options): (item) => void ``` -Defined in: [batcher.ts:305](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L305) +Defined in: [batcher.ts:322](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L322) -Creates a batcher that processes items in batches +Creates a batcher that processes items in batches. + +This synchronous version is lighter weight and often all you need - upgrade to asyncBatch when you need promises, retry support, abort/cancel capabilities, or advanced error handling. ## Type Parameters diff --git a/docs/reference/functions/debounce.md b/docs/reference/functions/debounce.md index ebb0464a..d646f85c 100644 --- a/docs/reference/functions/debounce.md +++ b/docs/reference/functions/debounce.md @@ -11,13 +11,12 @@ title: debounce function debounce(fn, initialOptions): (...args) => void ``` -Defined in: [debouncer.ts:312](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L312) +Defined in: [debouncer.ts:317](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L317) Creates a debounced function that delays invoking the provided function until after a specified wait time. Multiple calls during the wait period will cancel previous pending invocations and reset the timer. -This the the simple function wrapper implementation pulled from the Debouncer class. If you need -more control over the debouncing behavior, use the Debouncer class directly. +This synchronous version is lighter weight and often all you need - upgrade to asyncDebounce when you need promises, retry support, abort/cancel capabilities, or advanced error handling. If leading option is true, the function will execute immediately on the first call, then wait the delay before allowing another execution. diff --git a/docs/reference/functions/queue.md b/docs/reference/functions/queue.md index f3f24895..78b2381a 100644 --- a/docs/reference/functions/queue.md +++ b/docs/reference/functions/queue.md @@ -11,14 +11,12 @@ title: queue function queue(fn, initialOptions): (item, position, runOnItemsChange) => boolean ``` -Defined in: [queuer.ts:717](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L717) +Defined in: [queuer.ts:723](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L723) Creates a queue that processes items immediately upon addition. Items are processed sequentially in FIFO order by default. -This is a simplified wrapper around the Queuer class that only exposes the -`addItem` method. The queue is always isRunning and will process items as they are added. -For more control over queue processing, use the Queuer class directly. +This synchronous version is lighter weight and often all you need - upgrade to asyncQueue when you need promises, retry support, abort capabilities, concurrent execution, or advanced error handling. State Management: - Uses TanStack Store for reactive state management diff --git a/docs/reference/functions/ratelimit.md b/docs/reference/functions/ratelimit.md index 5223c7e1..cd61ba57 100644 --- a/docs/reference/functions/ratelimit.md +++ b/docs/reference/functions/ratelimit.md @@ -11,10 +11,12 @@ title: rateLimit function rateLimit(fn, initialOptions): (...args) => boolean ``` -Defined in: [rate-limiter.ts:404](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L404) +Defined in: [rate-limiter.ts:412](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L412) Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window. +This synchronous version is lighter weight and often all you need - upgrade to asyncRateLimit when you need promises, retry support, abort capabilities, or advanced error handling. + Note that rate limiting is a simpler form of execution control compared to throttling or debouncing: - A rate limiter will allow all executions until the limit is reached, then block all subsequent calls until the window resets - A throttler ensures even spacing between executions, which can be better for consistent performance diff --git a/docs/reference/functions/throttle.md b/docs/reference/functions/throttle.md index 888d4300..9b3b45c4 100644 --- a/docs/reference/functions/throttle.md +++ b/docs/reference/functions/throttle.md @@ -11,10 +11,12 @@ title: throttle function throttle(fn, initialOptions): (...args) => void ``` -Defined in: [throttler.ts:355](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L355) +Defined in: [throttler.ts:363](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L363) Creates a throttled function that limits how often the provided function can execute. +This synchronous version is lighter weight and often all you need - upgrade to asyncThrottle when you need promises, retry support, abort/cancel capabilities, or advanced error handling. + Throttling ensures a function executes at most once within a specified time window, regardless of how many times it is called. This is useful for rate-limiting expensive operations or UI updates. diff --git a/docs/reference/index.md b/docs/reference/index.md index 900c1082..572c1a97 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -13,6 +13,7 @@ title: "@tanstack/pacer" - [AsyncDebouncer](../classes/asyncdebouncer.md) - [AsyncQueuer](../classes/asyncqueuer.md) - [AsyncRateLimiter](../classes/asyncratelimiter.md) +- [AsyncRetryer](../classes/asyncretryer.md) - [AsyncThrottler](../classes/asyncthrottler.md) - [Batcher](../classes/batcher.md) - [Debouncer](../classes/debouncer.md) @@ -30,6 +31,8 @@ title: "@tanstack/pacer" - [AsyncQueuerState](../interfaces/asyncqueuerstate.md) - [AsyncRateLimiterOptions](../interfaces/asyncratelimiteroptions.md) - [AsyncRateLimiterState](../interfaces/asyncratelimiterstate.md) +- [AsyncRetryerOptions](../interfaces/asyncretryeroptions.md) +- [AsyncRetryerState](../interfaces/asyncretryerstate.md) - [AsyncThrottlerOptions](../interfaces/asyncthrottleroptions.md) - [AsyncThrottlerState](../interfaces/asyncthrottlerstate.md) - [BatcherOptions](../interfaces/batcheroptions.md) @@ -62,6 +65,7 @@ title: "@tanstack/pacer" - [asyncDebounce](../functions/asyncdebounce.md) - [asyncQueue](../functions/asyncqueue.md) - [asyncRateLimit](../functions/asyncratelimit.md) +- [asyncRetry](../functions/asyncretry.md) - [asyncThrottle](../functions/asyncthrottle.md) - [batch](../functions/batch.md) - [createKey](../functions/createkey.md) diff --git a/docs/reference/interfaces/asyncbatcheroptions.md b/docs/reference/interfaces/asyncbatcheroptions.md index d8e06dae..29d9a36b 100644 --- a/docs/reference/interfaces/asyncbatcheroptions.md +++ b/docs/reference/interfaces/asyncbatcheroptions.md @@ -7,7 +7,7 @@ title: AsyncBatcherOptions # Interface: AsyncBatcherOptions\ -Defined in: [async-batcher.ts:82](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L82) +Defined in: [async-batcher.ts:89](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L89) Options for configuring an AsyncBatcher instance @@ -17,13 +17,25 @@ Options for configuring an AsyncBatcher instance ## Properties +### asyncRetryerOptions? + +```ts +optional asyncRetryerOptions: AsyncRetryerOptions<(items) => Promise>; +``` + +Defined in: [async-batcher.ts:93](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L93) + +Options for configuring the underlying async retryer + +*** + ### getShouldExecute()? ```ts optional getShouldExecute: (items, batcher) => boolean; ``` -Defined in: [async-batcher.ts:87](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L87) +Defined in: [async-batcher.ts:100](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L100) Custom function to determine if a batch should be processed Return true to process the batch immediately @@ -50,7 +62,7 @@ Return true to process the batch immediately optional initialState: Partial>; ``` -Defined in: [async-batcher.ts:94](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L94) +Defined in: [async-batcher.ts:107](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L107) Initial state for the async batcher @@ -62,7 +74,7 @@ Initial state for the async batcher optional key: string; ``` -Defined in: [async-batcher.ts:99](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L99) +Defined in: [async-batcher.ts:112](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L112) Optional key to identify this async batcher instance. If provided, the async batcher will be identified by this key in the devtools and PacerProvider if applicable. @@ -75,7 +87,7 @@ If provided, the async batcher will be identified by this key in the devtools an optional maxSize: number; ``` -Defined in: [async-batcher.ts:104](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L104) +Defined in: [async-batcher.ts:117](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L117) Maximum number of items in a batch @@ -93,7 +105,7 @@ Infinity optional onError: (error, batch, batcher) => void; ``` -Defined in: [async-batcher.ts:110](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L110) +Defined in: [async-batcher.ts:123](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L123) Optional error handler for when the batch function throws. If provided, the handler will be called with the error, the batch of items that failed, and batcher instance. @@ -103,7 +115,7 @@ This can be used alongside throwOnError - the handler will be called before any ##### error -`unknown` +`Error` ##### batch @@ -125,7 +137,7 @@ This can be used alongside throwOnError - the handler will be called before any optional onItemsChange: (batcher) => void; ``` -Defined in: [async-batcher.ts:118](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L118) +Defined in: [async-batcher.ts:131](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L131) Callback fired after items are added to the batcher @@ -147,7 +159,7 @@ Callback fired after items are added to the batcher optional onSettled: (batch, batcher) => void; ``` -Defined in: [async-batcher.ts:122](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L122) +Defined in: [async-batcher.ts:135](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L135) Optional callback to call when a batch is settled (completed or failed) @@ -173,7 +185,7 @@ Optional callback to call when a batch is settled (completed or failed) optional onSuccess: (result, batch, batcher) => void; ``` -Defined in: [async-batcher.ts:126](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L126) +Defined in: [async-batcher.ts:139](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L139) Optional callback to call when a batch succeeds @@ -203,7 +215,7 @@ Optional callback to call when a batch succeeds optional started: boolean; ``` -Defined in: [async-batcher.ts:135](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L135) +Defined in: [async-batcher.ts:148](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L148) Whether the batcher should start processing immediately @@ -221,7 +233,7 @@ true optional throwOnError: boolean; ``` -Defined in: [async-batcher.ts:141](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L141) +Defined in: [async-batcher.ts:154](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L154) Whether to throw errors when they occur. Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -235,7 +247,7 @@ Can be explicitly set to override these defaults. optional wait: number | (asyncBatcher) => number; ``` -Defined in: [async-batcher.ts:148](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L148) +Defined in: [async-batcher.ts:161](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L161) Maximum time in milliseconds to wait before processing a batch. If the wait duration has elapsed, the batch will be processed. diff --git a/docs/reference/interfaces/asyncbatcherstate.md b/docs/reference/interfaces/asyncbatcherstate.md index 38bbde18..e1b88961 100644 --- a/docs/reference/interfaces/asyncbatcherstate.md +++ b/docs/reference/interfaces/asyncbatcherstate.md @@ -7,7 +7,7 @@ title: AsyncBatcherState # Interface: AsyncBatcherState\ -Defined in: [async-batcher.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L6) +Defined in: [async-batcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L8) ## Type Parameters @@ -21,19 +21,31 @@ Defined in: [async-batcher.ts:6](https://github.com/TanStack/pacer/blob/main/pac errorCount: number; ``` -Defined in: [async-batcher.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L10) +Defined in: [async-batcher.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L12) Number of batch executions that have resulted in errors *** +### executeCount + +```ts +executeCount: number; +``` + +Defined in: [async-batcher.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L16) + +Number of batch executions that have been executed + +*** + ### failedItems ```ts failedItems: TValue[]; ``` -Defined in: [async-batcher.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L14) +Defined in: [async-batcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L20) Array of items that failed during batch processing @@ -45,7 +57,7 @@ Array of items that failed during batch processing isEmpty: boolean; ``` -Defined in: [async-batcher.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L18) +Defined in: [async-batcher.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L24) Whether the batcher has no items to process (items array is empty) @@ -57,7 +69,7 @@ Whether the batcher has no items to process (items array is empty) isExecuting: boolean; ``` -Defined in: [async-batcher.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L22) +Defined in: [async-batcher.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L28) Whether a batch is currently being processed asynchronously @@ -69,7 +81,7 @@ Whether a batch is currently being processed asynchronously isPending: boolean; ``` -Defined in: [async-batcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L26) +Defined in: [async-batcher.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L32) Whether the batcher is waiting for the timeout to trigger batch processing @@ -81,7 +93,7 @@ Whether the batcher is waiting for the timeout to trigger batch processing items: TValue[]; ``` -Defined in: [async-batcher.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L30) +Defined in: [async-batcher.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L36) Array of items currently queued for batch processing @@ -93,7 +105,7 @@ Array of items currently queued for batch processing lastResult: any; ``` -Defined in: [async-batcher.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L34) +Defined in: [async-batcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L40) The result from the most recent batch execution @@ -105,7 +117,7 @@ The result from the most recent batch execution settleCount: number; ``` -Defined in: [async-batcher.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L38) +Defined in: [async-batcher.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L44) Number of batch executions that have completed (either successfully or with errors) @@ -117,7 +129,7 @@ Number of batch executions that have completed (either successfully or with erro size: number; ``` -Defined in: [async-batcher.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L42) +Defined in: [async-batcher.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L48) Number of items currently in the batch queue @@ -129,7 +141,7 @@ Number of items currently in the batch queue status: "idle" | "pending" | "executing" | "populated"; ``` -Defined in: [async-batcher.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L46) +Defined in: [async-batcher.ts:52](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L52) Current processing status - 'idle' when not processing, 'pending' when waiting for timeout, 'executing' when processing, 'populated' when items are present, but no wait is configured @@ -141,7 +153,7 @@ Current processing status - 'idle' when not processing, 'pending' when waiting f successCount: number; ``` -Defined in: [async-batcher.ts:50](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L50) +Defined in: [async-batcher.ts:56](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L56) Number of batch executions that have completed successfully @@ -153,7 +165,7 @@ Number of batch executions that have completed successfully totalItemsFailed: number; ``` -Defined in: [async-batcher.ts:54](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L54) +Defined in: [async-batcher.ts:60](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L60) Total number of items that have failed processing across all batches @@ -165,6 +177,6 @@ Total number of items that have failed processing across all batches totalItemsProcessed: number; ``` -Defined in: [async-batcher.ts:58](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L58) +Defined in: [async-batcher.ts:64](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L64) Total number of items that have been processed across all batches diff --git a/docs/reference/interfaces/asyncdebounceroptions.md b/docs/reference/interfaces/asyncdebounceroptions.md index cb03a901..cd725cc9 100644 --- a/docs/reference/interfaces/asyncdebounceroptions.md +++ b/docs/reference/interfaces/asyncdebounceroptions.md @@ -7,7 +7,7 @@ title: AsyncDebouncerOptions # Interface: AsyncDebouncerOptions\ -Defined in: [async-debouncer.ts:69](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L69) +Defined in: [async-debouncer.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L71) Options for configuring an async debounced function @@ -17,13 +17,25 @@ Options for configuring an async debounced function ## Properties +### asyncRetryerOptions? + +```ts +optional asyncRetryerOptions: AsyncRetryerOptions; +``` + +Defined in: [async-debouncer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L75) + +Options for configuring the underlying async retryer + +*** + ### enabled? ```ts optional enabled: boolean | (debouncer) => boolean; ``` -Defined in: [async-debouncer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L75) +Defined in: [async-debouncer.ts:81](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L81) Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions. Can be a boolean or a function that returns a boolean. @@ -37,7 +49,7 @@ Defaults to true. optional initialState: Partial>; ``` -Defined in: [async-debouncer.ts:79](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L79) +Defined in: [async-debouncer.ts:85](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L85) Initial state for the async debouncer @@ -49,7 +61,7 @@ Initial state for the async debouncer optional key: string; ``` -Defined in: [async-debouncer.ts:84](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L84) +Defined in: [async-debouncer.ts:90](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L90) Optional key to identify this async debouncer instance. If provided, the async debouncer will be identified by this key in the devtools and PacerProvider if applicable. @@ -62,7 +74,7 @@ If provided, the async debouncer will be identified by this key in the devtools optional leading: boolean; ``` -Defined in: [async-debouncer.ts:89](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L89) +Defined in: [async-debouncer.ts:95](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L95) Whether to execute on the leading edge of the timeout. Defaults to false. @@ -75,7 +87,7 @@ Defaults to false. optional onError: (error, args, debouncer) => void; ``` -Defined in: [async-debouncer.ts:95](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L95) +Defined in: [async-debouncer.ts:101](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L101) Optional error handler for when the debounced function throws. If provided, the handler will be called with the error and debouncer instance. @@ -85,7 +97,7 @@ This can be used alongside throwOnError - the handler will be called before any ##### error -`unknown` +`Error` ##### args @@ -107,7 +119,7 @@ This can be used alongside throwOnError - the handler will be called before any optional onSettled: (args, debouncer) => void; ``` -Defined in: [async-debouncer.ts:103](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L103) +Defined in: [async-debouncer.ts:109](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L109) Optional callback to call when the debounced function is executed @@ -133,7 +145,7 @@ Optional callback to call when the debounced function is executed optional onSuccess: (result, args, debouncer) => void; ``` -Defined in: [async-debouncer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L107) +Defined in: [async-debouncer.ts:113](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L113) Optional callback to call when the debounced function is executed @@ -163,7 +175,7 @@ Optional callback to call when the debounced function is executed optional throwOnError: boolean; ``` -Defined in: [async-debouncer.ts:117](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L117) +Defined in: [async-debouncer.ts:123](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L123) Whether to throw errors when they occur. Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -177,7 +189,7 @@ Can be explicitly set to override these defaults. optional trailing: boolean; ``` -Defined in: [async-debouncer.ts:122](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L122) +Defined in: [async-debouncer.ts:128](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L128) Whether to execute on the trailing edge of the timeout. Defaults to true. @@ -190,7 +202,7 @@ Defaults to true. wait: number | (debouncer) => number; ``` -Defined in: [async-debouncer.ts:128](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L128) +Defined in: [async-debouncer.ts:134](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L134) Delay in milliseconds to wait after the last call before executing. Can be a number or a function that returns a number. diff --git a/docs/reference/interfaces/asyncdebouncerstate.md b/docs/reference/interfaces/asyncdebouncerstate.md index dddacaac..420c5edc 100644 --- a/docs/reference/interfaces/asyncdebouncerstate.md +++ b/docs/reference/interfaces/asyncdebouncerstate.md @@ -7,7 +7,7 @@ title: AsyncDebouncerState # Interface: AsyncDebouncerState\ -Defined in: [async-debouncer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L6) +Defined in: [async-debouncer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L8) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [async-debouncer.ts:6](https://github.com/TanStack/pacer/blob/main/p canLeadingExecute: boolean; ``` -Defined in: [async-debouncer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L10) +Defined in: [async-debouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L12) Whether the debouncer can execute on the leading edge of the timeout @@ -33,7 +33,7 @@ Whether the debouncer can execute on the leading edge of the timeout errorCount: number; ``` -Defined in: [async-debouncer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L14) +Defined in: [async-debouncer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L16) Number of function executions that have resulted in errors @@ -45,7 +45,7 @@ Number of function executions that have resulted in errors isExecuting: boolean; ``` -Defined in: [async-debouncer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L18) +Defined in: [async-debouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L20) Whether the debounced function is currently executing asynchronously @@ -57,7 +57,7 @@ Whether the debounced function is currently executing asynchronously isPending: boolean; ``` -Defined in: [async-debouncer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L22) +Defined in: [async-debouncer.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L24) Whether the debouncer is waiting for the timeout to trigger execution @@ -69,7 +69,7 @@ Whether the debouncer is waiting for the timeout to trigger execution lastArgs: undefined | Parameters; ``` -Defined in: [async-debouncer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L26) +Defined in: [async-debouncer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L28) The arguments from the most recent call to maybeExecute @@ -81,7 +81,7 @@ The arguments from the most recent call to maybeExecute lastResult: undefined | ReturnType; ``` -Defined in: [async-debouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L30) +Defined in: [async-debouncer.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L32) The result from the most recent successful function execution @@ -93,7 +93,7 @@ The result from the most recent successful function execution maybeExecuteCount: number; ``` -Defined in: [async-debouncer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L34) +Defined in: [async-debouncer.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L36) Number of times maybeExecute has been called (for reduction calculations) @@ -105,7 +105,7 @@ Number of times maybeExecute has been called (for reduction calculations) settleCount: number; ``` -Defined in: [async-debouncer.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L38) +Defined in: [async-debouncer.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L40) Number of function executions that have completed (either successfully or with errors) @@ -117,7 +117,7 @@ Number of function executions that have completed (either successfully or with e status: "disabled" | "idle" | "pending" | "executing" | "settled"; ``` -Defined in: [async-debouncer.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L42) +Defined in: [async-debouncer.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L44) Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed @@ -129,6 +129,6 @@ Current execution status - 'idle' when not active, 'pending' when waiting, 'exec successCount: number; ``` -Defined in: [async-debouncer.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L46) +Defined in: [async-debouncer.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L48) Number of function executions that have completed successfully diff --git a/docs/reference/interfaces/asyncqueueroptions.md b/docs/reference/interfaces/asyncqueueroptions.md index a97171f6..ea9a244e 100644 --- a/docs/reference/interfaces/asyncqueueroptions.md +++ b/docs/reference/interfaces/asyncqueueroptions.md @@ -7,7 +7,7 @@ title: AsyncQueuerOptions # Interface: AsyncQueuerOptions\ -Defined in: [async-queuer.ts:100](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L100) +Defined in: [async-queuer.ts:112](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L112) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [async-queuer.ts:100](https://github.com/TanStack/pacer/blob/main/pa optional addItemsTo: QueuePosition; ``` -Defined in: [async-queuer.ts:105](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L105) +Defined in: [async-queuer.ts:121](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L121) Default position to add items to the queuer @@ -33,13 +33,25 @@ Default position to add items to the queuer *** +### asyncRetryerOptions? + +```ts +optional asyncRetryerOptions: AsyncRetryerOptions<(item) => Promise>; +``` + +Defined in: [async-queuer.ts:116](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L116) + +Options for configuring the underlying async retryer + +*** + ### concurrency? ```ts optional concurrency: number | (queuer) => number; ``` -Defined in: [async-queuer.ts:111](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L111) +Defined in: [async-queuer.ts:127](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L127) Maximum number of concurrent tasks to process. Can be a number or a function that returns a number. @@ -58,7 +70,7 @@ Can be a number or a function that returns a number. optional expirationDuration: number; ``` -Defined in: [async-queuer.ts:116](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L116) +Defined in: [async-queuer.ts:132](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L132) Maximum time in milliseconds that an item can stay in the queue If not provided, items will never expire @@ -71,7 +83,7 @@ If not provided, items will never expire optional getIsExpired: (item, addedAt) => boolean; ``` -Defined in: [async-queuer.ts:121](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L121) +Defined in: [async-queuer.ts:137](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L137) Function to determine if an item has expired If provided, this overrides the expirationDuration behavior @@ -98,7 +110,7 @@ If provided, this overrides the expirationDuration behavior optional getItemsFrom: QueuePosition; ``` -Defined in: [async-queuer.ts:126](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L126) +Defined in: [async-queuer.ts:142](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L142) Default position to get items from during processing @@ -116,7 +128,7 @@ Default position to get items from during processing optional getPriority: (item) => number; ``` -Defined in: [async-queuer.ts:132](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L132) +Defined in: [async-queuer.ts:148](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L148) Function to determine priority of items in the queuer Higher priority items will be processed first @@ -140,7 +152,7 @@ If not provided, will use static priority values attached to tasks optional initialItems: TValue[]; ``` -Defined in: [async-queuer.ts:136](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L136) +Defined in: [async-queuer.ts:152](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L152) Initial items to populate the queuer with @@ -152,7 +164,7 @@ Initial items to populate the queuer with optional initialState: Partial>; ``` -Defined in: [async-queuer.ts:140](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L140) +Defined in: [async-queuer.ts:156](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L156) Initial state for the async queuer @@ -164,7 +176,7 @@ Initial state for the async queuer optional key: string; ``` -Defined in: [async-queuer.ts:145](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L145) +Defined in: [async-queuer.ts:161](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L161) Optional key to identify this async queuer instance. If provided, the async queuer will be identified by this key in the devtools and PacerProvider if applicable. @@ -177,7 +189,7 @@ If provided, the async queuer will be identified by this key in the devtools and optional maxSize: number; ``` -Defined in: [async-queuer.ts:149](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L149) +Defined in: [async-queuer.ts:165](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L165) Maximum number of items allowed in the queuer @@ -189,7 +201,7 @@ Maximum number of items allowed in the queuer optional onError: (error, item, queuer) => void; ``` -Defined in: [async-queuer.ts:155](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L155) +Defined in: [async-queuer.ts:171](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L171) Optional error handler for when a task throws. If provided, the handler will be called with the error and queuer instance. @@ -199,7 +211,7 @@ This can be used alongside throwOnError - the handler will be called before any ##### error -`unknown` +`Error` ##### item @@ -221,7 +233,7 @@ This can be used alongside throwOnError - the handler will be called before any optional onExpire: (item, queuer) => void; ``` -Defined in: [async-queuer.ts:159](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L159) +Defined in: [async-queuer.ts:175](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L175) Callback fired whenever an item expires in the queuer @@ -247,7 +259,7 @@ Callback fired whenever an item expires in the queuer optional onItemsChange: (queuer) => void; ``` -Defined in: [async-queuer.ts:163](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L163) +Defined in: [async-queuer.ts:179](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L179) Callback fired whenever an item is added or removed from the queuer @@ -269,7 +281,7 @@ Callback fired whenever an item is added or removed from the queuer optional onReject: (item, queuer) => void; ``` -Defined in: [async-queuer.ts:167](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L167) +Defined in: [async-queuer.ts:183](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L183) Callback fired whenever an item is rejected from being added to the queuer @@ -295,7 +307,7 @@ Callback fired whenever an item is rejected from being added to the queuer optional onSettled: (item, queuer) => void; ``` -Defined in: [async-queuer.ts:171](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L171) +Defined in: [async-queuer.ts:187](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L187) Optional callback to call when a task is settled @@ -321,7 +333,7 @@ Optional callback to call when a task is settled optional onSuccess: (result, item, queuer) => void; ``` -Defined in: [async-queuer.ts:175](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L175) +Defined in: [async-queuer.ts:191](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L191) Optional callback to call when a task succeeds @@ -351,7 +363,7 @@ Optional callback to call when a task succeeds optional started: boolean; ``` -Defined in: [async-queuer.ts:179](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L179) +Defined in: [async-queuer.ts:195](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L195) Whether the queuer should start processing tasks immediately or not. @@ -363,7 +375,7 @@ Whether the queuer should start processing tasks immediately or not. optional throwOnError: boolean; ``` -Defined in: [async-queuer.ts:185](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L185) +Defined in: [async-queuer.ts:201](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L201) Whether to throw errors when they occur. Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -377,7 +389,7 @@ Can be explicitly set to override these defaults. optional wait: number | (queuer) => number; ``` -Defined in: [async-queuer.ts:191](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L191) +Defined in: [async-queuer.ts:207](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L207) Time in milliseconds to wait between processing items. Can be a number or a function that returns a number. diff --git a/docs/reference/interfaces/asyncqueuerstate.md b/docs/reference/interfaces/asyncqueuerstate.md index 01d49279..c327be73 100644 --- a/docs/reference/interfaces/asyncqueuerstate.md +++ b/docs/reference/interfaces/asyncqueuerstate.md @@ -7,7 +7,7 @@ title: AsyncQueuerState # Interface: AsyncQueuerState\ -Defined in: [async-queuer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L7) +Defined in: [async-queuer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L9) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [async-queuer.ts:7](https://github.com/TanStack/pacer/blob/main/pack activeItems: TValue[]; ``` -Defined in: [async-queuer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L11) +Defined in: [async-queuer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L13) Items currently being processed by the queuer @@ -33,7 +33,7 @@ Items currently being processed by the queuer addItemCount: number; ``` -Defined in: [async-queuer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L15) +Defined in: [async-queuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L17) Number of times addItem has been called (for reduction calculations) @@ -45,19 +45,31 @@ Number of times addItem has been called (for reduction calculations) errorCount: number; ``` -Defined in: [async-queuer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L19) +Defined in: [async-queuer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L21) Number of task executions that have resulted in errors *** +### executeCount + +```ts +executeCount: number; +``` + +Defined in: [async-queuer.ts:25](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L25) + +Number of times execute has been called + +*** + ### expirationCount ```ts expirationCount: number; ``` -Defined in: [async-queuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L23) +Defined in: [async-queuer.ts:29](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L29) Number of items that have been removed from the queue due to expiration @@ -69,19 +81,31 @@ Number of items that have been removed from the queue due to expiration isEmpty: boolean; ``` -Defined in: [async-queuer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L27) +Defined in: [async-queuer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L33) Whether the queuer has no items to process (items array is empty) *** +### isExecuting + +```ts +isExecuting: boolean; +``` + +Defined in: [async-queuer.ts:37](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L37) + +Whether the queuer is currently executing + +*** + ### isFull ```ts isFull: boolean; ``` -Defined in: [async-queuer.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L31) +Defined in: [async-queuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L41) Whether the queuer has reached its maximum capacity @@ -93,7 +117,7 @@ Whether the queuer has reached its maximum capacity isIdle: boolean; ``` -Defined in: [async-queuer.ts:35](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L35) +Defined in: [async-queuer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L45) Whether the queuer is not currently processing any items @@ -105,7 +129,7 @@ Whether the queuer is not currently processing any items isRunning: boolean; ``` -Defined in: [async-queuer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L39) +Defined in: [async-queuer.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L49) Whether the queuer is active and will process items automatically @@ -117,7 +141,7 @@ Whether the queuer is active and will process items automatically items: TValue[]; ``` -Defined in: [async-queuer.ts:43](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L43) +Defined in: [async-queuer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L53) Array of items currently waiting to be processed @@ -129,7 +153,7 @@ Array of items currently waiting to be processed itemTimestamps: number[]; ``` -Defined in: [async-queuer.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L47) +Defined in: [async-queuer.ts:57](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L57) Timestamps when items were added to the queue for expiration tracking @@ -141,7 +165,7 @@ Timestamps when items were added to the queue for expiration tracking lastResult: any; ``` -Defined in: [async-queuer.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L51) +Defined in: [async-queuer.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L61) The result from the most recent task execution @@ -153,7 +177,7 @@ The result from the most recent task execution pendingTick: boolean; ``` -Defined in: [async-queuer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L55) +Defined in: [async-queuer.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L65) Whether the queuer has a pending timeout for processing the next item @@ -165,7 +189,7 @@ Whether the queuer has a pending timeout for processing the next item rejectionCount: number; ``` -Defined in: [async-queuer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L59) +Defined in: [async-queuer.ts:69](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L69) Number of items that have been rejected from being added to the queue @@ -177,7 +201,7 @@ Number of items that have been rejected from being added to the queue settledCount: number; ``` -Defined in: [async-queuer.ts:63](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L63) +Defined in: [async-queuer.ts:73](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L73) Number of task executions that have completed (either successfully or with errors) @@ -189,7 +213,7 @@ Number of task executions that have completed (either successfully or with error size: number; ``` -Defined in: [async-queuer.ts:67](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L67) +Defined in: [async-queuer.ts:77](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L77) Number of items currently in the queue @@ -201,7 +225,7 @@ Number of items currently in the queue status: "idle" | "running" | "stopped"; ``` -Defined in: [async-queuer.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L71) +Defined in: [async-queuer.ts:81](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L81) Current processing status - 'idle' when not processing, 'running' when active, 'stopped' when paused @@ -213,6 +237,6 @@ Current processing status - 'idle' when not processing, 'running' when active, ' successCount: number; ``` -Defined in: [async-queuer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L75) +Defined in: [async-queuer.ts:85](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L85) Number of task executions that have completed successfully diff --git a/docs/reference/interfaces/asyncratelimiteroptions.md b/docs/reference/interfaces/asyncratelimiteroptions.md index 89a7bacf..9e418bbc 100644 --- a/docs/reference/interfaces/asyncratelimiteroptions.md +++ b/docs/reference/interfaces/asyncratelimiteroptions.md @@ -7,7 +7,7 @@ title: AsyncRateLimiterOptions # Interface: AsyncRateLimiterOptions\ -Defined in: [async-rate-limiter.ts:69](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L69) +Defined in: [async-rate-limiter.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L71) Options for configuring an async rate-limited function @@ -17,13 +17,25 @@ Options for configuring an async rate-limited function ## Properties +### asyncRetryerOptions? + +```ts +optional asyncRetryerOptions: AsyncRetryerOptions; +``` + +Defined in: [async-rate-limiter.ts:75](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L75) + +Options for configuring the underlying async retryer + +*** + ### enabled? ```ts optional enabled: boolean | (rateLimiter) => boolean; ``` -Defined in: [async-rate-limiter.ts:75](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L75) +Defined in: [async-rate-limiter.ts:81](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L81) Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions. Can be a boolean or a function that returns a boolean. @@ -37,7 +49,7 @@ Defaults to true. optional initialState: Partial>; ``` -Defined in: [async-rate-limiter.ts:79](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L79) +Defined in: [async-rate-limiter.ts:85](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L85) Initial state for the rate limiter @@ -49,7 +61,7 @@ Initial state for the rate limiter optional key: string; ``` -Defined in: [async-rate-limiter.ts:84](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L84) +Defined in: [async-rate-limiter.ts:90](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L90) Optional key to identify this async rate limiter instance. If provided, the async rate limiter will be identified by this key in the devtools and PacerProvider if applicable. @@ -62,7 +74,7 @@ If provided, the async rate limiter will be identified by this key in the devtoo limit: number | (rateLimiter) => number; ``` -Defined in: [async-rate-limiter.ts:89](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L89) +Defined in: [async-rate-limiter.ts:95](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L95) Maximum number of executions allowed within the time window. Can be a number or a function that returns a number. @@ -75,7 +87,7 @@ Can be a number or a function that returns a number. optional onError: (error, args, rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:95](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L95) +Defined in: [async-rate-limiter.ts:101](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L101) Optional error handler for when the rate-limited function throws. If provided, the handler will be called with the error and rate limiter instance. @@ -85,7 +97,7 @@ This can be used alongside throwOnError - the handler will be called before any ##### error -`unknown` +`Error` ##### args @@ -107,7 +119,7 @@ This can be used alongside throwOnError - the handler will be called before any optional onReject: (args, rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:103](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L103) +Defined in: [async-rate-limiter.ts:109](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L109) Optional callback function that is called when an execution is rejected due to rate limiting @@ -133,7 +145,7 @@ Optional callback function that is called when an execution is rejected due to r optional onSettled: (args, rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:107](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L107) +Defined in: [async-rate-limiter.ts:113](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L113) Optional function to call when the rate-limited function is executed @@ -159,7 +171,7 @@ Optional function to call when the rate-limited function is executed optional onSuccess: (result, args, rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:114](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L114) +Defined in: [async-rate-limiter.ts:120](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L120) Optional function to call when the rate-limited function is executed @@ -189,7 +201,7 @@ Optional function to call when the rate-limited function is executed optional throwOnError: boolean; ``` -Defined in: [async-rate-limiter.ts:124](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L124) +Defined in: [async-rate-limiter.ts:130](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L130) Whether to throw errors when they occur. Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -203,7 +215,7 @@ Can be explicitly set to override these defaults. window: number | (rateLimiter) => number; ``` -Defined in: [async-rate-limiter.ts:129](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L129) +Defined in: [async-rate-limiter.ts:135](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L135) Time window in milliseconds within which the limit applies. Can be a number or a function that returns a number. @@ -216,7 +228,7 @@ Can be a number or a function that returns a number. optional windowType: "fixed" | "sliding"; ``` -Defined in: [async-rate-limiter.ts:136](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L136) +Defined in: [async-rate-limiter.ts:142](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L142) Type of window to use for rate limiting - 'fixed': Uses a fixed window that resets after the window period diff --git a/docs/reference/interfaces/asyncratelimiterstate.md b/docs/reference/interfaces/asyncratelimiterstate.md index 9efddb6b..3d153430 100644 --- a/docs/reference/interfaces/asyncratelimiterstate.md +++ b/docs/reference/interfaces/asyncratelimiterstate.md @@ -7,7 +7,7 @@ title: AsyncRateLimiterState # Interface: AsyncRateLimiterState\ -Defined in: [async-rate-limiter.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L6) +Defined in: [async-rate-limiter.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L8) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [async-rate-limiter.ts:6](https://github.com/TanStack/pacer/blob/mai errorCount: number; ``` -Defined in: [async-rate-limiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L10) +Defined in: [async-rate-limiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L12) Number of function executions that have resulted in errors @@ -33,7 +33,7 @@ Number of function executions that have resulted in errors executionTimes: number[]; ``` -Defined in: [async-rate-limiter.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L14) +Defined in: [async-rate-limiter.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L16) Array of timestamps when executions occurred for rate limiting calculations @@ -45,7 +45,7 @@ Array of timestamps when executions occurred for rate limiting calculations isExceeded: boolean; ``` -Defined in: [async-rate-limiter.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L18) +Defined in: [async-rate-limiter.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L20) Whether the rate limiter has exceeded the limit @@ -57,7 +57,7 @@ Whether the rate limiter has exceeded the limit isExecuting: boolean; ``` -Defined in: [async-rate-limiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L22) +Defined in: [async-rate-limiter.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L24) Whether the rate-limited function is currently executing asynchronously @@ -69,7 +69,7 @@ Whether the rate-limited function is currently executing asynchronously lastResult: undefined | ReturnType; ``` -Defined in: [async-rate-limiter.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L26) +Defined in: [async-rate-limiter.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L28) The result from the most recent successful function execution @@ -81,7 +81,7 @@ The result from the most recent successful function execution maybeExecuteCount: number; ``` -Defined in: [async-rate-limiter.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L46) +Defined in: [async-rate-limiter.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L48) Number of times maybeExecute has been called (for reduction calculations) @@ -93,7 +93,7 @@ Number of times maybeExecute has been called (for reduction calculations) rejectionCount: number; ``` -Defined in: [async-rate-limiter.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L30) +Defined in: [async-rate-limiter.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L32) Number of function executions that have been rejected due to rate limiting @@ -105,7 +105,7 @@ Number of function executions that have been rejected due to rate limiting settleCount: number; ``` -Defined in: [async-rate-limiter.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L34) +Defined in: [async-rate-limiter.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L36) Number of function executions that have completed (either successfully or with errors) @@ -117,7 +117,7 @@ Number of function executions that have completed (either successfully or with e status: "disabled" | "idle" | "executing" | "exceeded"; ``` -Defined in: [async-rate-limiter.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L38) +Defined in: [async-rate-limiter.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L40) Current execution status - 'disabled' when not active, 'executing' when executing, 'idle' when not executing, 'exceeded' when rate limit is exceeded @@ -129,6 +129,6 @@ Current execution status - 'disabled' when not active, 'executing' when executin successCount: number; ``` -Defined in: [async-rate-limiter.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L42) +Defined in: [async-rate-limiter.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L44) Number of function executions that have completed successfully diff --git a/docs/reference/interfaces/asyncretryeroptions.md b/docs/reference/interfaces/asyncretryeroptions.md new file mode 100644 index 00000000..d02145e0 --- /dev/null +++ b/docs/reference/interfaces/asyncretryeroptions.md @@ -0,0 +1,331 @@ +--- +id: AsyncRetryerOptions +title: AsyncRetryerOptions +--- + + + +# Interface: AsyncRetryerOptions\ + +Defined in: [async-retryer.ts:60](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L60) + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### backoff? + +```ts +optional backoff: "linear" | "exponential" | "fixed"; +``` + +Defined in: [async-retryer.ts:68](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L68) + +The backoff strategy for retry delays: +- 'exponential': Wait time doubles with each attempt (1s, 2s, 4s, ...) +- 'linear': Wait time increases linearly (1s, 2s, 3s, ...) +- 'fixed': Same wait time for all attempts + +#### Default + +```ts +'exponential' +``` + +*** + +### baseWait? + +```ts +optional baseWait: number | (retryer) => number; +``` + +Defined in: [async-retryer.ts:73](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L73) + +Base wait time in milliseconds between retries, or a function that returns the wait time + +#### Default + +```ts +1000 +``` + +*** + +### enabled? + +```ts +optional enabled: boolean | (retryer) => boolean; +``` + +Defined in: [async-retryer.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L78) + +Whether the retryer is enabled, or a function that determines if it's enabled + +#### Default + +```ts +true +``` + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [async-retryer.ts:82](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L82) + +Initial state to merge with the default state + +*** + +### jitter? + +```ts +optional jitter: number; +``` + +Defined in: [async-retryer.ts:92](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L92) + +Jitter percentage to add to retry delays (0-1). Adds randomness to prevent thundering herd. + +#### Default + +```ts +0 +``` + +*** + +### key? + +```ts +optional key: string; +``` + +Defined in: [async-retryer.ts:87](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L87) + +Optional key to identify this async retryer instance. +If provided, the async retryer will be identified by this key in the devtools and PacerProvider if applicable. + +*** + +### maxAttempts? + +```ts +optional maxAttempts: number | (retryer) => number; +``` + +Defined in: [async-retryer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L107) + +Maximum number of retry attempts, or a function that returns the max attempts + +#### Default + +```ts +3 +``` + +*** + +### maxExecutionTime? + +```ts +optional maxExecutionTime: number; +``` + +Defined in: [async-retryer.ts:97](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L97) + +Maximum execution time in milliseconds for a single function call before aborting + +#### Default + +```ts +Infinity +``` + +*** + +### maxTotalExecutionTime? + +```ts +optional maxTotalExecutionTime: number; +``` + +Defined in: [async-retryer.ts:102](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L102) + +Maximum total execution time in milliseconds for the entire retry operation before aborting + +#### Default + +```ts +Infinity +``` + +*** + +### onError()? + +```ts +optional onError: (error, args, retryer) => void; +``` + +Defined in: [async-retryer.ts:111](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L111) + +Callback invoked when any error occurs during execution (including retries) + +#### Parameters + +##### error + +`Error` + +##### args + +`Parameters`\<`TFn`\> + +##### retryer + +[`AsyncRetryer`](../../classes/asyncretryer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onLastError()? + +```ts +optional onLastError: (error, retryer) => void; +``` + +Defined in: [async-retryer.ts:119](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L119) + +Callback invoked when the final error occurs after all retries are exhausted + +#### Parameters + +##### error + +`Error` + +##### retryer + +[`AsyncRetryer`](../../classes/asyncretryer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onRetry()? + +```ts +optional onRetry: (attempt, error, retryer) => void; +``` + +Defined in: [async-retryer.ts:123](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L123) + +Callback invoked before each retry attempt + +#### Parameters + +##### attempt + +`number` + +##### error + +`Error` + +##### retryer + +[`AsyncRetryer`](../../classes/asyncretryer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSettled()? + +```ts +optional onSettled: (args, retryer) => void; +``` + +Defined in: [async-retryer.ts:127](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L127) + +Callback invoked after execution completes (success or failure) + +#### Parameters + +##### args + +`Parameters`\<`TFn`\> + +##### retryer + +[`AsyncRetryer`](../../classes/asyncretryer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSuccess()? + +```ts +optional onSuccess: (result, args, retryer) => void; +``` + +Defined in: [async-retryer.ts:131](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L131) + +Callback invoked when execution succeeds + +#### Parameters + +##### result + +`ReturnType`\<`TFn`\> + +##### args + +`Parameters`\<`TFn`\> + +##### retryer + +[`AsyncRetryer`](../../classes/asyncretryer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### throwOnError? + +```ts +optional throwOnError: boolean | "last"; +``` + +Defined in: [async-retryer.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L143) + +Controls when errors are thrown: +- 'last': Only throw the final error after all retries are exhausted +- true: Throw every error immediately (disables retrying) +- false: Never throw errors, return undefined instead + +#### Default + +```ts +'last' +``` diff --git a/docs/reference/interfaces/asyncretryerstate.md b/docs/reference/interfaces/asyncretryerstate.md new file mode 100644 index 00000000..3bc08be1 --- /dev/null +++ b/docs/reference/interfaces/asyncretryerstate.md @@ -0,0 +1,110 @@ +--- +id: AsyncRetryerState +title: AsyncRetryerState +--- + + + +# Interface: AsyncRetryerState\ + +Defined in: [async-retryer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L6) + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### currentAttempt + +```ts +currentAttempt: number; +``` + +Defined in: [async-retryer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L10) + +The current retry attempt number (0 when not executing) + +*** + +### executionCount + +```ts +executionCount: number; +``` + +Defined in: [async-retryer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L14) + +Total number of completed executions (successful or failed) + +*** + +### isExecuting + +```ts +isExecuting: boolean; +``` + +Defined in: [async-retryer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L18) + +Whether the retryer is currently executing the function + +*** + +### lastError + +```ts +lastError: undefined | Error; +``` + +Defined in: [async-retryer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L22) + +The most recent error encountered during execution + +*** + +### lastExecutionTime + +```ts +lastExecutionTime: number; +``` + +Defined in: [async-retryer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L26) + +Timestamp of the last execution completion in milliseconds + +*** + +### lastResult + +```ts +lastResult: undefined | ReturnType; +``` + +Defined in: [async-retryer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L30) + +The result from the most recent successful execution + +*** + +### status + +```ts +status: "disabled" | "idle" | "executing" | "retrying"; +``` + +Defined in: [async-retryer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L34) + +Current execution status - 'disabled' when not enabled, 'idle' when ready, 'executing' when running + +*** + +### totalExecutionTime + +```ts +totalExecutionTime: number; +``` + +Defined in: [async-retryer.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-retryer.ts#L38) + +Total time spent executing (including retries) in milliseconds diff --git a/docs/reference/interfaces/asyncthrottleroptions.md b/docs/reference/interfaces/asyncthrottleroptions.md index dd555a61..2a9d28f8 100644 --- a/docs/reference/interfaces/asyncthrottleroptions.md +++ b/docs/reference/interfaces/asyncthrottleroptions.md @@ -7,7 +7,7 @@ title: AsyncThrottlerOptions # Interface: AsyncThrottlerOptions\ -Defined in: [async-throttler.ts:74](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L74) +Defined in: [async-throttler.ts:76](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L76) Options for configuring an async throttled function @@ -17,13 +17,25 @@ Options for configuring an async throttled function ## Properties +### asyncRetryerOptions? + +```ts +optional asyncRetryerOptions: AsyncRetryerOptions; +``` + +Defined in: [async-throttler.ts:80](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L80) + +Options for configuring the underlying async retryer + +*** + ### enabled? ```ts optional enabled: boolean | (throttler) => boolean; ``` -Defined in: [async-throttler.ts:80](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L80) +Defined in: [async-throttler.ts:86](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L86) Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions. Can be a boolean or a function that returns a boolean. @@ -37,7 +49,7 @@ Defaults to true. optional initialState: Partial>; ``` -Defined in: [async-throttler.ts:84](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L84) +Defined in: [async-throttler.ts:90](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L90) Initial state for the async throttler @@ -49,7 +61,7 @@ Initial state for the async throttler optional key: string; ``` -Defined in: [async-throttler.ts:89](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L89) +Defined in: [async-throttler.ts:95](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L95) Optional key to identify this async throttler instance. If provided, the async throttler will be identified by this key in the devtools and PacerProvider if applicable. @@ -62,7 +74,7 @@ If provided, the async throttler will be identified by this key in the devtools optional leading: boolean; ``` -Defined in: [async-throttler.ts:94](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L94) +Defined in: [async-throttler.ts:100](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L100) Whether to execute the function immediately when called Defaults to true @@ -75,7 +87,7 @@ Defaults to true optional onError: (error, args, asyncThrottler) => void; ``` -Defined in: [async-throttler.ts:100](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L100) +Defined in: [async-throttler.ts:106](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L106) Optional error handler for when the throttled function throws. If provided, the handler will be called with the error and throttler instance. @@ -85,7 +97,7 @@ This can be used alongside throwOnError - the handler will be called before any ##### error -`unknown` +`Error` ##### args @@ -107,7 +119,7 @@ This can be used alongside throwOnError - the handler will be called before any optional onSettled: (args, asyncThrottler) => void; ``` -Defined in: [async-throttler.ts:108](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L108) +Defined in: [async-throttler.ts:114](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L114) Optional function to call when the throttled function is executed @@ -133,7 +145,7 @@ Optional function to call when the throttled function is executed optional onSuccess: (result, args, asyncThrottler) => void; ``` -Defined in: [async-throttler.ts:115](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L115) +Defined in: [async-throttler.ts:121](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L121) Optional function to call when the throttled function is executed @@ -163,7 +175,7 @@ Optional function to call when the throttled function is executed optional throwOnError: boolean; ``` -Defined in: [async-throttler.ts:125](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L125) +Defined in: [async-throttler.ts:131](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L131) Whether to throw errors when they occur. Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -177,7 +189,7 @@ Can be explicitly set to override these defaults. optional trailing: boolean; ``` -Defined in: [async-throttler.ts:130](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L130) +Defined in: [async-throttler.ts:136](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L136) Whether to execute the function on the trailing edge of the wait period Defaults to true @@ -190,7 +202,7 @@ Defaults to true wait: number | (throttler) => number; ``` -Defined in: [async-throttler.ts:136](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L136) +Defined in: [async-throttler.ts:142](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L142) Time window in milliseconds during which the function can only be executed once. Can be a number or a function that returns a number. diff --git a/docs/reference/interfaces/asyncthrottlerstate.md b/docs/reference/interfaces/asyncthrottlerstate.md index 0b839bcc..2d191757 100644 --- a/docs/reference/interfaces/asyncthrottlerstate.md +++ b/docs/reference/interfaces/asyncthrottlerstate.md @@ -7,7 +7,7 @@ title: AsyncThrottlerState # Interface: AsyncThrottlerState\ -Defined in: [async-throttler.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L6) +Defined in: [async-throttler.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L8) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [async-throttler.ts:6](https://github.com/TanStack/pacer/blob/main/p errorCount: number; ``` -Defined in: [async-throttler.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L10) +Defined in: [async-throttler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L12) Number of function executions that have resulted in errors @@ -33,7 +33,7 @@ Number of function executions that have resulted in errors isExecuting: boolean; ``` -Defined in: [async-throttler.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L14) +Defined in: [async-throttler.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L16) Whether the throttled function is currently executing asynchronously @@ -45,7 +45,7 @@ Whether the throttled function is currently executing asynchronously isPending: boolean; ``` -Defined in: [async-throttler.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L18) +Defined in: [async-throttler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L20) Whether the throttler is waiting for the timeout to trigger execution @@ -57,7 +57,7 @@ Whether the throttler is waiting for the timeout to trigger execution lastArgs: undefined | Parameters; ``` -Defined in: [async-throttler.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L22) +Defined in: [async-throttler.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L24) The arguments from the most recent call to maybeExecute @@ -69,7 +69,7 @@ The arguments from the most recent call to maybeExecute lastExecutionTime: number; ``` -Defined in: [async-throttler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L26) +Defined in: [async-throttler.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L28) Timestamp of the last function execution in milliseconds @@ -81,7 +81,7 @@ Timestamp of the last function execution in milliseconds lastResult: undefined | ReturnType; ``` -Defined in: [async-throttler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L30) +Defined in: [async-throttler.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L32) The result from the most recent successful function execution @@ -93,7 +93,7 @@ The result from the most recent successful function execution maybeExecuteCount: number; ``` -Defined in: [async-throttler.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L34) +Defined in: [async-throttler.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L36) Number of times maybeExecute has been called (for reduction calculations) @@ -105,7 +105,7 @@ Number of times maybeExecute has been called (for reduction calculations) nextExecutionTime: undefined | number; ``` -Defined in: [async-throttler.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L38) +Defined in: [async-throttler.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L40) Timestamp when the next execution can occur in milliseconds @@ -117,7 +117,7 @@ Timestamp when the next execution can occur in milliseconds settleCount: number; ``` -Defined in: [async-throttler.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L42) +Defined in: [async-throttler.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L44) Number of function executions that have completed (either successfully or with errors) @@ -129,7 +129,7 @@ Number of function executions that have completed (either successfully or with e status: "disabled" | "idle" | "pending" | "executing" | "settled"; ``` -Defined in: [async-throttler.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L46) +Defined in: [async-throttler.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L48) Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed @@ -141,6 +141,6 @@ Current execution status - 'idle' when not active, 'pending' when waiting, 'exec successCount: number; ``` -Defined in: [async-throttler.ts:50](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L50) +Defined in: [async-throttler.ts:52](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L52) Number of function executions that have completed successfully diff --git a/docs/reference/interfaces/pacereventmap.md b/docs/reference/interfaces/pacereventmap.md index c3b2c862..76095348 100644 --- a/docs/reference/interfaces/pacereventmap.md +++ b/docs/reference/interfaces/pacereventmap.md @@ -7,7 +7,7 @@ title: PacerEventMap # Interface: PacerEventMap -Defined in: [event-client.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L13) +Defined in: [event-client.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L14) ## Properties @@ -17,7 +17,7 @@ Defined in: [event-client.ts:13](https://github.com/TanStack/pacer/blob/main/pac pacer:AsyncBatcher: AsyncBatcher; ``` -Defined in: [event-client.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L24) +Defined in: [event-client.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L26) *** @@ -27,7 +27,7 @@ Defined in: [event-client.ts:24](https://github.com/TanStack/pacer/blob/main/pac pacer:AsyncDebouncer: AsyncDebouncer; ``` -Defined in: [event-client.ts:25](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L25) +Defined in: [event-client.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L27) *** @@ -37,7 +37,7 @@ Defined in: [event-client.ts:25](https://github.com/TanStack/pacer/blob/main/pac pacer:AsyncQueuer: AsyncQueuer; ``` -Defined in: [event-client.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L26) +Defined in: [event-client.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L28) *** @@ -47,7 +47,17 @@ Defined in: [event-client.ts:26](https://github.com/TanStack/pacer/blob/main/pac pacer:AsyncRateLimiter: AsyncRateLimiter; ``` -Defined in: [event-client.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L27) +Defined in: [event-client.ts:29](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L29) + +*** + +### pacer:AsyncRetryer + +```ts +pacer:AsyncRetryer: AsyncRetryer; +``` + +Defined in: [event-client.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L30) *** @@ -57,7 +67,7 @@ Defined in: [event-client.ts:27](https://github.com/TanStack/pacer/blob/main/pac pacer:AsyncThrottler: AsyncThrottler; ``` -Defined in: [event-client.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L28) +Defined in: [event-client.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L31) *** @@ -67,7 +77,7 @@ Defined in: [event-client.ts:28](https://github.com/TanStack/pacer/blob/main/pac pacer:Batcher: Batcher; ``` -Defined in: [event-client.ts:29](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L29) +Defined in: [event-client.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L32) *** @@ -77,7 +87,7 @@ Defined in: [event-client.ts:29](https://github.com/TanStack/pacer/blob/main/pac pacer:d-AsyncBatcher: AsyncBatcher; ``` -Defined in: [event-client.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L14) +Defined in: [event-client.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L15) *** @@ -87,7 +97,7 @@ Defined in: [event-client.ts:14](https://github.com/TanStack/pacer/blob/main/pac pacer:d-AsyncDebouncer: AsyncDebouncer; ``` -Defined in: [event-client.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L15) +Defined in: [event-client.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L16) *** @@ -97,7 +107,7 @@ Defined in: [event-client.ts:15](https://github.com/TanStack/pacer/blob/main/pac pacer:d-AsyncQueuer: AsyncQueuer; ``` -Defined in: [event-client.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L16) +Defined in: [event-client.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L17) *** @@ -107,7 +117,17 @@ Defined in: [event-client.ts:16](https://github.com/TanStack/pacer/blob/main/pac pacer:d-AsyncRateLimiter: AsyncRateLimiter; ``` -Defined in: [event-client.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L17) +Defined in: [event-client.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L18) + +*** + +### pacer:d-AsyncRetryer + +```ts +pacer:d-AsyncRetryer: AsyncRetryer; +``` + +Defined in: [event-client.ts:19](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L19) *** @@ -117,7 +137,7 @@ Defined in: [event-client.ts:17](https://github.com/TanStack/pacer/blob/main/pac pacer:d-AsyncThrottler: AsyncThrottler; ``` -Defined in: [event-client.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L18) +Defined in: [event-client.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L20) *** @@ -127,7 +147,7 @@ Defined in: [event-client.ts:18](https://github.com/TanStack/pacer/blob/main/pac pacer:d-Batcher: Batcher; ``` -Defined in: [event-client.ts:19](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L19) +Defined in: [event-client.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L21) *** @@ -137,7 +157,7 @@ Defined in: [event-client.ts:19](https://github.com/TanStack/pacer/blob/main/pac pacer:d-Debouncer: Debouncer; ``` -Defined in: [event-client.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L20) +Defined in: [event-client.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L22) *** @@ -147,7 +167,7 @@ Defined in: [event-client.ts:20](https://github.com/TanStack/pacer/blob/main/pac pacer:d-Queuer: Queuer; ``` -Defined in: [event-client.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L21) +Defined in: [event-client.ts:23](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L23) *** @@ -157,7 +177,7 @@ Defined in: [event-client.ts:21](https://github.com/TanStack/pacer/blob/main/pac pacer:d-RateLimiter: RateLimiter; ``` -Defined in: [event-client.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L22) +Defined in: [event-client.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L24) *** @@ -167,7 +187,7 @@ Defined in: [event-client.ts:22](https://github.com/TanStack/pacer/blob/main/pac pacer:d-Throttler: Throttler; ``` -Defined in: [event-client.ts:23](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L23) +Defined in: [event-client.ts:25](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L25) *** @@ -177,7 +197,7 @@ Defined in: [event-client.ts:23](https://github.com/TanStack/pacer/blob/main/pac pacer:Debouncer: Debouncer; ``` -Defined in: [event-client.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L30) +Defined in: [event-client.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L33) *** @@ -187,7 +207,7 @@ Defined in: [event-client.ts:30](https://github.com/TanStack/pacer/blob/main/pac pacer:Queuer: Queuer; ``` -Defined in: [event-client.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L31) +Defined in: [event-client.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L34) *** @@ -197,7 +217,7 @@ Defined in: [event-client.ts:31](https://github.com/TanStack/pacer/blob/main/pac pacer:RateLimiter: RateLimiter; ``` -Defined in: [event-client.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L32) +Defined in: [event-client.ts:35](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L35) *** @@ -207,4 +227,4 @@ Defined in: [event-client.ts:32](https://github.com/TanStack/pacer/blob/main/pac pacer:Throttler: Throttler; ``` -Defined in: [event-client.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L33) +Defined in: [event-client.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L36) diff --git a/docs/reference/type-aliases/pacereventname.md b/docs/reference/type-aliases/pacereventname.md index 37966115..27060281 100644 --- a/docs/reference/type-aliases/pacereventname.md +++ b/docs/reference/type-aliases/pacereventname.md @@ -11,4 +11,4 @@ title: PacerEventName type PacerEventName = keyof PacerEventMap extends `pacer:${infer T}` ? T : never; ``` -Defined in: [event-client.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L36) +Defined in: [event-client.ts:39](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L39) diff --git a/docs/reference/variables/pacereventclient.md b/docs/reference/variables/pacereventclient.md index ea0d496c..eb89579c 100644 --- a/docs/reference/variables/pacereventclient.md +++ b/docs/reference/variables/pacereventclient.md @@ -11,4 +11,4 @@ title: pacerEventClient const pacerEventClient: PacerEventClient; ``` -Defined in: [event-client.ts:63](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L63) +Defined in: [event-client.ts:66](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/event-client.ts#L66) diff --git a/examples/react/asyncBatch/package.json b/examples/react/asyncBatch/package.json index c2df3c42..ac7dbcbf 100644 --- a/examples/react/asyncBatch/package.json +++ b/examples/react/asyncBatch/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/asyncBatch/src/index.tsx b/examples/react/asyncBatch/src/index.tsx index be35e2b1..10e19a77 100644 --- a/examples/react/asyncBatch/src/index.tsx +++ b/examples/react/asyncBatch/src/index.tsx @@ -53,7 +53,7 @@ function App() { } catch (error: any) { setErrors((prev) => [ ...prev, - `Error: ${error.message} (${new Date().toLocaleTimeString()})`, + `Error: ${error} (${new Date().toLocaleTimeString()})`, ]) setErrorCount((prev) => prev + 1) console.error('Batch failed:', error) diff --git a/examples/react/asyncDebounce/package.json b/examples/react/asyncDebounce/package.json index 10d86eb3..e9b44279 100644 --- a/examples/react/asyncDebounce/package.json +++ b/examples/react/asyncDebounce/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/asyncRateLimit/package.json b/examples/react/asyncRateLimit/package.json index f5046768..55c3df58 100644 --- a/examples/react/asyncRateLimit/package.json +++ b/examples/react/asyncRateLimit/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/asyncRetry/.eslintrc.cjs b/examples/react/asyncRetry/.eslintrc.cjs new file mode 100644 index 00000000..9ff0b9fc --- /dev/null +++ b/examples/react/asyncRetry/.eslintrc.cjs @@ -0,0 +1,13 @@ +// @ts-check + +/** @type {import('eslint').Linter.Config} */ +const config = { + settings: { + extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'], + rules: { + 'react/no-children-prop': 'off', + }, + }, +} + +module.exports = config diff --git a/examples/react/asyncRetry/.gitignore b/examples/react/asyncRetry/.gitignore new file mode 100644 index 00000000..872082d1 --- /dev/null +++ b/examples/react/asyncRetry/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +pnpm-lock.yaml +yarn.lock +package-lock.json + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/examples/react/asyncRetry/README.md b/examples/react/asyncRetry/README.md new file mode 100644 index 00000000..c953e6ee --- /dev/null +++ b/examples/react/asyncRetry/README.md @@ -0,0 +1,24 @@ +# AsyncBatch Example + +This example demonstrates how to use the `asyncBatch` function from `@tanstack/react-pacer` wrapped in a `useCallback` hook for optimal performance. + +The example combines features from both the `batch` and `useAsyncBatcher` examples: + +- Uses `asyncBatch` function (functional API) like in the sync batcher examples +- Wrapped in `useCallback` for performance optimization +- Demonstrates async processing with error handling +- Shows manual state management for batch tracking + +## Key Features + +- **Async Processing**: Processes items asynchronously with simulated delays +- **Batch Control**: Batches up to 5 items or processes after 3 seconds +- **Urgent Processing**: Items marked as "urgent" trigger immediate processing +- **Error Handling**: Graceful error handling with configurable failure simulation +- **Performance**: Uses `useCallback` to prevent unnecessary re-renders +- **Manual State Management**: Tracks processing state without reactive hooks + +## To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/asyncRetry/index.html b/examples/react/asyncRetry/index.html new file mode 100644 index 00000000..ef327a8e --- /dev/null +++ b/examples/react/asyncRetry/index.html @@ -0,0 +1,16 @@ + + + + + + + + + TanStack Pacer AsyncBatch Example + + + +
+ + + diff --git a/examples/react/asyncRetry/package.json b/examples/react/asyncRetry/package.json new file mode 100644 index 00000000..b065f377 --- /dev/null +++ b/examples/react/asyncRetry/package.json @@ -0,0 +1,34 @@ +{ + "name": "@tanstack/pacer-example-react-asyncretry", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3005", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-pacer": "^0.16.4", + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", + "@vitejs/plugin-react": "^5.0.4", + "vite": "^7.1.7" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/examples/react/asyncRetry/public/emblem-light.svg b/examples/react/asyncRetry/public/emblem-light.svg new file mode 100644 index 00000000..16d57489 --- /dev/null +++ b/examples/react/asyncRetry/public/emblem-light.svg @@ -0,0 +1,13 @@ + + + + emblem-light + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/react/asyncRetry/src/index.tsx b/examples/react/asyncRetry/src/index.tsx new file mode 100644 index 00000000..1d6a06a2 --- /dev/null +++ b/examples/react/asyncRetry/src/index.tsx @@ -0,0 +1,424 @@ +import { useState, useRef } from 'react' +import ReactDOM from 'react-dom/client' +import { asyncRetry } from '@tanstack/react-pacer/async-retryer' + +interface UserData { + id: number + name: string + email: string +} + +// Simulate API call with fake data that can fail or timeout +const fakeApi = async ( + userId: string, + options: { shouldFail?: boolean; shouldTimeout?: boolean } = {}, +): Promise => { + const delay = options.shouldTimeout ? 3000 : 800 // Simulate network delay + await new Promise((resolve) => setTimeout(resolve, delay)) + + if (options.shouldFail || Math.random() < 0.6) { + throw new Error(`Network error fetching user ${userId}`) + } + + return { + id: parseInt(userId), + name: `User ${userId}`, + email: `user${userId}@example.com`, + } +} + +function App() { + const [userId, setUserId] = useState('123') + const [userData, setUserData] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + const [currentAttempt, setCurrentAttempt] = useState(0) + const [scenario, setScenario] = useState< + 'default' | 'timeout' | 'jitter' | 'linear' + >('default') + const [logs, setLogs] = useState([]) + const abortControllerRef = useRef(null) + + const addLog = (message: string) => { + setLogs((prev) => [ + ...prev.slice(-9), + `${new Date().toLocaleTimeString()}: ${message}`, + ]) + } + + // Get options based on selected scenario + const getOptions = () => { + const baseOptions = { + onRetry: (attempt: number, error: Error) => { + addLog(`Retry attempt ${attempt} after error: ${error.message}`) + setCurrentAttempt(attempt + 1) + }, + onError: (error: Error) => { + addLog(`Request failed: ${error.message}`) + }, + onLastError: (error: Error) => { + addLog(`All retries exhausted: ${error.message}`) + setError(error.message) + setUserData(null) + }, + onSuccess: async (result: Promise) => { + const data = await result + addLog(`Request succeeded for user ${data.id}`) + setUserData(data) + setError(null) + }, + onSettled: () => { + addLog('Request settled') + setCurrentAttempt(0) + }, + } + + switch (scenario) { + case 'timeout': + return { + ...baseOptions, + maxAttempts: 3, + backoff: 'exponential' as const, + baseWait: 500, + maxExecutionTime: 2000, // Individual call timeout + maxTotalExecutionTime: 8000, // Total timeout for all retries + jitter: 0, + } + case 'jitter': + return { + ...baseOptions, + maxAttempts: 5, + backoff: 'exponential' as const, + baseWait: 500, + jitter: 0.3, // 30% random variation + } + case 'linear': + return { + ...baseOptions, + maxAttempts: 4, + backoff: 'linear' as const, + baseWait: 1000, + jitter: 0, + } + default: + return { + ...baseOptions, + maxAttempts: 5, + backoff: 'exponential' as const, + baseWait: 1000, + jitter: 0, + } + } + } + + // Create the retry-wrapped function + const fetchUserWithRetry = asyncRetry(async (id: string) => { + addLog(`Attempting to fetch user ${id}`) + return await fakeApi(id, { + shouldTimeout: scenario === 'timeout', + }) + }, getOptions()) + + // Handle fetch with abort support + async function onFetchUser() { + setLogs([]) + setIsLoading(true) + setError(null) + setCurrentAttempt(1) + addLog('Starting fetch operation') + + // Create abort controller + abortControllerRef.current = new AbortController() + + try { + const result = await fetchUserWithRetry(userId) + addLog(`Final result: ${result ? `User ${result.id}` : 'undefined'}`) + } catch (error) { + addLog( + `Caught error: ${error instanceof Error ? error.message : 'Unknown error'}`, + ) + setError(error instanceof Error ? error.message : 'Unknown error') + } finally { + setIsLoading(false) + abortControllerRef.current = null + } + } + + function onAbort() { + if (abortControllerRef.current) { + abortControllerRef.current.abort() + addLog('Operation aborted by user') + setIsLoading(false) + setCurrentAttempt(0) + } + } + + function onReset() { + setUserData(null) + setError(null) + setLogs([]) + setCurrentAttempt(0) + addLog('State reset') + } + + return ( +
+

TanStack Pacer asyncRetry Example

+

+ Demonstrates the asyncRetry utility function with configurable backoff + strategies, timeouts, jitter, and error handling. +

+ +
+
+

Configuration

+
+ + +
+ +
+ + setUserId(e.target.value)} + placeholder="Enter user ID..." + style={{ padding: '5px', width: '100%' }} + disabled={isLoading} + /> +
+ +
+ + + +
+ +
+

Current Options:

+
+              {(() => {
+                const opts = getOptions()
+                return JSON.stringify(
+                  {
+                    maxAttempts: opts.maxAttempts,
+                    backoff: opts.backoff,
+                    baseWait: opts.baseWait,
+                    jitter: opts.jitter,
+                    maxExecutionTime:
+                      'maxExecutionTime' in opts
+                        ? opts.maxExecutionTime
+                        : Infinity,
+                    maxTotalExecutionTime:
+                      'maxTotalExecutionTime' in opts
+                        ? opts.maxTotalExecutionTime
+                        : Infinity,
+                  },
+                  null,
+                  2,
+                )
+              })()}
+            
+
+
+ +
+

State

+
+
+ Status:{' '} + 1 + ? '#ff8c00' + : '#ffd700' + : '#90ee90', + }} + > + {isLoading + ? currentAttempt > 1 + ? 'retrying' + : 'executing' + : 'idle'} + +
+ {currentAttempt > 0 && ( +

+ Current Attempt: {currentAttempt} /{' '} + {(() => { + const opts = getOptions() + return opts.maxAttempts + })()} +

+ )} + {error && ( +
+ Error: +
+ {error} +
+ )} +
+ + {userData && ( +
+

User Data:

+

+ ID: {userData.id} +

+

+ Name: {userData.name} +

+

+ Email: {userData.email} +

+
+ )} +
+
+ +
+

Activity Log

+
+ {logs.length === 0 ? ( +
No activity yet
+ ) : ( + logs.map((log, i) => ( +
+ {log} +
+ )) + )} +
+
+ +
+ Note: This example uses the asyncRetry{' '} + utility function, which is a lightweight wrapper that creates a + retry-enabled version of your async function. For React integration with + hooks and reactive state, check out the useAsyncRetryer{' '} + example. +
+ +
+ Key Differences: +
    +
  • + asyncRetry - Functional utility that wraps an async + function with retry logic +
  • +
  • + useAsyncRetryer - React hook that provides reactive + state and lifecycle management +
  • +
  • + AsyncRetryer - Low-level class for advanced use cases + with fine-grained control +
  • +
+
+
+ ) +} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render() diff --git a/examples/react/asyncRetry/tsconfig.json b/examples/react/asyncRetry/tsconfig.json new file mode 100644 index 00000000..6e9088d6 --- /dev/null +++ b/examples/react/asyncRetry/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/react/asyncRetry/vite.config.ts b/examples/react/asyncRetry/vite.config.ts new file mode 100644 index 00000000..4e194366 --- /dev/null +++ b/examples/react/asyncRetry/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + react({ + // babel: { + // plugins: [['babel-plugin-react-compiler', { target: '19' }]], + // }, + }), + ], +}) diff --git a/examples/react/asyncThrottle/package.json b/examples/react/asyncThrottle/package.json index f9258293..c418fe07 100644 --- a/examples/react/asyncThrottle/package.json +++ b/examples/react/asyncThrottle/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/batch/package.json b/examples/react/batch/package.json index 413d092d..6a5bbb6c 100644 --- a/examples/react/batch/package.json +++ b/examples/react/batch/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/debounce/package.json b/examples/react/debounce/package.json index e2ff68f3..ce3b6ba0 100644 --- a/examples/react/debounce/package.json +++ b/examples/react/debounce/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/queue/package.json b/examples/react/queue/package.json index af4f0939..f2310921 100644 --- a/examples/react/queue/package.json +++ b/examples/react/queue/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@tanstack/react-devtools": "0.7.0", + "@tanstack/react-devtools": "0.7.1", "@tanstack/react-pacer-devtools": "0.3.1", - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/rateLimit/package.json b/examples/react/rateLimit/package.json index a8534be3..d65194e0 100644 --- a/examples/react/rateLimit/package.json +++ b/examples/react/rateLimit/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/react-query-debounced-prefetch/package.json b/examples/react/react-query-debounced-prefetch/package.json index 61bdb26a..c0362188 100644 --- a/examples/react/react-query-debounced-prefetch/package.json +++ b/examples/react/react-query-debounced-prefetch/package.json @@ -16,8 +16,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/react-query-queued-prefetch/package.json b/examples/react/react-query-queued-prefetch/package.json index be9e132f..d7040d24 100644 --- a/examples/react/react-query-queued-prefetch/package.json +++ b/examples/react/react-query-queued-prefetch/package.json @@ -16,8 +16,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/react-query-throttled-prefetch/package.json b/examples/react/react-query-throttled-prefetch/package.json index d181a72b..45f462a5 100644 --- a/examples/react/react-query-throttled-prefetch/package.json +++ b/examples/react/react-query-throttled-prefetch/package.json @@ -16,8 +16,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/throttle/package.json b/examples/react/throttle/package.json index ed5c7ad2..9d020fc6 100644 --- a/examples/react/throttle/package.json +++ b/examples/react/throttle/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncBatchedCallback/package.json b/examples/react/useAsyncBatchedCallback/package.json index ea84495e..861a326e 100644 --- a/examples/react/useAsyncBatchedCallback/package.json +++ b/examples/react/useAsyncBatchedCallback/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncBatcher/package.json b/examples/react/useAsyncBatcher/package.json index c59844d0..219ded29 100644 --- a/examples/react/useAsyncBatcher/package.json +++ b/examples/react/useAsyncBatcher/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncBatcher/src/index.tsx b/examples/react/useAsyncBatcher/src/index.tsx index c7ea201b..253059a6 100644 --- a/examples/react/useAsyncBatcher/src/index.tsx +++ b/examples/react/useAsyncBatcher/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncBatcher } from '@tanstack/react-pacer/async-batcher' +import { PacerProvider } from '@tanstack/react-pacer/provider' const fakeProcessingTime = 1000 @@ -15,7 +16,6 @@ function App() { Array<{ items: Array; result: string; timestamp: number }> >([]) const [errors, setErrors] = useState>([]) - const [shouldFail, setShouldFail] = useState(false) // The async function that will process a batch of items async function processBatch(items: Array): Promise { @@ -25,9 +25,8 @@ function App() { await new Promise((resolve) => setTimeout(resolve, fakeProcessingTime)) // Simulate occasional failures for demo purposes - if (shouldFail && Math.random() < 0.3) { - throw new Error(`Processing failed for batch with ${items.length} items`) - } + + // throw new Error(`Processing failed for batch with ${items.length} items`) // Return a result from the batch processing const result = `Processed ${items.length} items: ${items.map((item) => item.value).join(', ')}` @@ -60,7 +59,7 @@ function App() { console.error('Batch failed:', error) setErrors((prev) => [ ...prev, - `Error: ${error.message} (${new Date().toLocaleTimeString()})`, + `Error: ${error} (${new Date().toLocaleTimeString()})`, ]) }, onSettled: (batch, batcher) => { @@ -165,17 +164,6 @@ function App() { Clear Current Batch - -
- -
@@ -215,4 +203,15 @@ function App() { } const root = ReactDOM.createRoot(document.getElementById('root')!) -root.render() +root.render( + // optionally, provide default options to an optional PacerProvider + + + , +) diff --git a/examples/react/useAsyncDebouncedCallback/package.json b/examples/react/useAsyncDebouncedCallback/package.json index 12f220ec..86820e92 100644 --- a/examples/react/useAsyncDebouncedCallback/package.json +++ b/examples/react/useAsyncDebouncedCallback/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncDebouncer/package.json b/examples/react/useAsyncDebouncer/package.json index f3315969..53d35083 100644 --- a/examples/react/useAsyncDebouncer/package.json +++ b/examples/react/useAsyncDebouncer/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncDebouncer/src/index.tsx b/examples/react/useAsyncDebouncer/src/index.tsx index 956aded5..620111e4 100644 --- a/examples/react/useAsyncDebouncer/src/index.tsx +++ b/examples/react/useAsyncDebouncer/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncDebouncer } from '@tanstack/react-pacer/async-debouncer' +import { PacerProvider } from '@tanstack/react-pacer/provider' interface SearchResult { id: number @@ -23,7 +24,6 @@ function App() { // The function that will become debounced const handleSearch = async (term: string) => { - throw new Error('Test error') if (!term) { setResults([]) return @@ -106,13 +106,29 @@ function App() { const root = ReactDOM.createRoot(document.getElementById('root')!) +function renderApp(mounted: boolean) { + root.render( + mounted ? ( + // defaultOptions can be provided to the PacerProvider to set default options for all instances + + + + ) : null, + ) +} + let mounted = true -root.render() +renderApp(mounted) -// demo unmounting and cancellation document.addEventListener('keydown', (e) => { if (e.shiftKey && e.key === 'Enter') { mounted = !mounted - root.render(mounted ? : null) + renderApp(mounted) } }) diff --git a/examples/react/useAsyncQueuedState/package.json b/examples/react/useAsyncQueuedState/package.json index e96187da..3f9984d9 100644 --- a/examples/react/useAsyncQueuedState/package.json +++ b/examples/react/useAsyncQueuedState/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncQueuedState/src/index.tsx b/examples/react/useAsyncQueuedState/src/index.tsx index 0153e792..0c2e5ead 100644 --- a/examples/react/useAsyncQueuedState/src/index.tsx +++ b/examples/react/useAsyncQueuedState/src/index.tsx @@ -31,7 +31,7 @@ function App() { asyncQueuer.store.state.rejectionCount, ) }, - onError: (error: unknown, item: Item, asyncQueuer) => { + onError: (error, item: Item, asyncQueuer) => { console.error( `Error processing item: ${item}`, error, diff --git a/examples/react/useAsyncQueuer/package.json b/examples/react/useAsyncQueuer/package.json index 0a6c8374..dac66649 100644 --- a/examples/react/useAsyncQueuer/package.json +++ b/examples/react/useAsyncQueuer/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncQueuer/src/index.tsx b/examples/react/useAsyncQueuer/src/index.tsx index 28899e78..4c611363 100644 --- a/examples/react/useAsyncQueuer/src/index.tsx +++ b/examples/react/useAsyncQueuer/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncQueuer } from '@tanstack/react-pacer/async-queuer' +import { PacerProvider } from '@tanstack/react-pacer/provider' const fakeWaitTime = 500 @@ -30,7 +31,7 @@ function App() { asyncQueuer.store.state.rejectionCount, ) }, - onError: (error: unknown, item: Item, asyncQueuer) => { + onError: (error, item: Item, asyncQueuer) => { console.error( `Error processing item: ${item}`, error, @@ -142,4 +143,15 @@ function App() { } const root = ReactDOM.createRoot(document.getElementById('root')!) -root.render() +root.render( + // optionally, provide default options to an optional PacerProvider + + + , +) diff --git a/examples/react/useAsyncRateLimiter/package.json b/examples/react/useAsyncRateLimiter/package.json index 90c8fb84..0682a88f 100644 --- a/examples/react/useAsyncRateLimiter/package.json +++ b/examples/react/useAsyncRateLimiter/package.json @@ -15,8 +15,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index e88ef1e3..2e157f67 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncRateLimiter } from '@tanstack/react-pacer/async-rate-limiter' +import { PacerProvider } from '@tanstack/react-pacer/provider' interface SearchResult { id: number @@ -165,12 +166,36 @@ function App() { const root = ReactDOM.createRoot(document.getElementById('root')!) let mounted = true -root.render() +root.render( + // optionally, provide default options to an optional PacerProvider + + + , +) // demo unmounting and cancellation document.addEventListener('keydown', (e) => { if (e.key === 'Enter') { mounted = !mounted - root.render(mounted ? : null) + root.render( + mounted ? ( + // optionally, provide default options to an optional PacerProvider + + + + ) : null, + ) } }) diff --git a/examples/react/useAsyncRateLimiterWithPersister/package.json b/examples/react/useAsyncRateLimiterWithPersister/package.json index a6b0e50e..099da11a 100644 --- a/examples/react/useAsyncRateLimiterWithPersister/package.json +++ b/examples/react/useAsyncRateLimiterWithPersister/package.json @@ -15,8 +15,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncThrottledCallback/README.md b/examples/react/useAsyncThrottledCallback/README.md new file mode 100644 index 00000000..1cf88926 --- /dev/null +++ b/examples/react/useAsyncThrottledCallback/README.md @@ -0,0 +1,6 @@ +# Example + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/useAsyncThrottledCallback/index.html b/examples/react/useAsyncThrottledCallback/index.html new file mode 100644 index 00000000..719eb9a3 --- /dev/null +++ b/examples/react/useAsyncThrottledCallback/index.html @@ -0,0 +1,13 @@ + + + + + + + TanStack Pacer - useAsyncThrottledCallback Example + + +
+ + + diff --git a/examples/react/useAsyncThrottledCallback/package.json b/examples/react/useAsyncThrottledCallback/package.json new file mode 100644 index 00000000..2cf2c090 --- /dev/null +++ b/examples/react/useAsyncThrottledCallback/package.json @@ -0,0 +1,34 @@ +{ + "name": "@tanstack/pacer-example-react-use-async-throttled-callback", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3005", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-pacer": "^0.16.4", + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", + "@vitejs/plugin-react": "^5.0.4", + "vite": "^7.1.7" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/examples/react/useAsyncThrottledCallback/public/emblem-light.svg b/examples/react/useAsyncThrottledCallback/public/emblem-light.svg new file mode 100644 index 00000000..a58e69ad --- /dev/null +++ b/examples/react/useAsyncThrottledCallback/public/emblem-light.svg @@ -0,0 +1,13 @@ + + + + emblem-light + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/react/useAsyncThrottledCallback/src/index.tsx b/examples/react/useAsyncThrottledCallback/src/index.tsx new file mode 100644 index 00000000..e153ad71 --- /dev/null +++ b/examples/react/useAsyncThrottledCallback/src/index.tsx @@ -0,0 +1,262 @@ +import { useState } from 'react' +import ReactDOM from 'react-dom/client' +import { useAsyncThrottledCallback } from '@tanstack/react-pacer/async-throttler' + +interface SearchResult { + id: number + title: string +} + +// Simulate API call with fake data +const fakeApi = async (term: string): Promise> => { + await new Promise((resolve) => setTimeout(resolve, 500)) // Simulate network delay + if (term === 'error') { + throw new Error('Simulated API error') + } + return [ + { id: 1, title: `${term} result ${Math.floor(Math.random() * 100)}` }, + { id: 2, title: `${term} result ${Math.floor(Math.random() * 100)}` }, + { id: 3, title: `${term} result ${Math.floor(Math.random() * 100)}` }, + ] +} + +function App1() { + const [searchTerm, setSearchTerm] = useState('') + const [results, setResults] = useState>([]) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + // Create async throttled function - Stable reference provided by useAsyncThrottledCallback + const throttledSearch = useAsyncThrottledCallback( + async (term: string) => { + if (!term.trim()) { + setResults([]) + return [] + } + + setIsLoading(true) + setError(null) + + try { + const data = await fakeApi(term) + setResults(data) + return data + } catch (err) { + const errorMessage = + err instanceof Error ? err.message : 'Unknown error' + setError(errorMessage) + setResults([]) + throw err + } finally { + setIsLoading(false) + } + }, + { + wait: 1000, + // leading: true, // optional, defaults to true + // trailing: true, // optional, defaults to true + }, + ) + + async function handleSearchChange(e: React.ChangeEvent) { + const newValue = e.target.value + setSearchTerm(newValue) + + try { + await throttledSearch(newValue) + } catch (err) { + // Error is already handled in the throttled function + console.log('Search failed:', err) + } + } + + return ( +
+

TanStack Pacer useAsyncThrottledCallback Example 1

+
+ +
+ + {isLoading &&

Searching...

} + {error &&

Error: {error}

} + +
+

Current search term: {searchTerm}

+ {results.length > 0 && ( +
+

Results:

+
    + {results.map((result) => ( +
  • {result.title}
  • + ))} +
+
+ )} +
+
+ ) +} + +function App2() { + const [count, setCount] = useState(0) + const [apiCallCount, setApiCallCount] = useState(0) + + // Simulate API call that returns a value + const incrementApi = async (value: number): Promise => { + await new Promise((resolve) => setTimeout(resolve, 300)) + const newCount = value + 1 + setApiCallCount((prev) => prev + 1) + return newCount + } + + // Create async throttled increment function + const throttledIncrement = useAsyncThrottledCallback( + async (currentValue: number) => { + const result = await incrementApi(currentValue) + setCount(result) + return result + }, + { + wait: 1000, + leading: true, // Execute immediately on first call + trailing: true, // Execute after throttle period ends + }, + ) + + function handleIncrement() { + // Update local state immediately for instant feedback + setCount((prev) => { + const newCount = prev + 1 + throttledIncrement(newCount) + return newCount + }) + } + + return ( +
+

TanStack Pacer useAsyncThrottledCallback Example 2

+ + + + + + + + + + + +
Current Count:{count}
API Calls Made:{apiCallCount}
+
+ +
+

+ Click rapidly - API calls are throttled to 1 second, but UI updates + immediately. First click executes immediately, then at most once per + second. +

+
+ ) +} + +function App3() { + const [scrollPosition, setScrollPosition] = useState(0) + const [saveCount, setSaveCount] = useState(0) + const [lastSaved, setLastSaved] = useState(null) + const [isSaving, setIsSaving] = useState(false) + + // Simulate saving scroll position to server + const saveScrollPosition = async ( + position: number, + ): Promise<{ success: boolean; position: number }> => { + await new Promise((resolve) => setTimeout(resolve, 300)) + return { success: true, position } + } + + // Create throttled save function + const throttledSave = useAsyncThrottledCallback( + async (position: number) => { + setIsSaving(true) + + try { + const result = await saveScrollPosition(position) + setSaveCount((prev) => prev + 1) + setLastSaved(new Date()) + return result + } finally { + setIsSaving(false) + } + }, + { + wait: 1000, + leading: true, + trailing: true, + }, + ) + + function handleScroll(e: React.UIEvent) { + const position = e.currentTarget.scrollTop + setScrollPosition(position) + throttledSave(position) + } + + return ( +
+

TanStack Pacer useAsyncThrottledCallback Example 3

+
+
+

Scroll this area to trigger throttled saves!

+

Current scroll position: {Math.round(scrollPosition)}px

+ {isSaving &&

Saving position...

} +
+

Saves triggered: {saveCount}

+ {lastSaved && ( +

Last saved at: {lastSaved.toLocaleTimeString()}

+ )} +
+
+

Keep scrolling...

+

More content...

+

Even more content...

+

Almost there...

+

You made it to the end!

+
+
+
+ +

+ Scroll position is saved at most once per second, but updates instantly + on screen +

+
+ ) +} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render( +
+ +
+ +
+ +
, +) diff --git a/examples/react/useAsyncThrottledCallback/tsconfig.json b/examples/react/useAsyncThrottledCallback/tsconfig.json new file mode 100644 index 00000000..6e9088d6 --- /dev/null +++ b/examples/react/useAsyncThrottledCallback/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/react/useAsyncThrottledCallback/vite.config.ts b/examples/react/useAsyncThrottledCallback/vite.config.ts new file mode 100644 index 00000000..5a33944a --- /dev/null +++ b/examples/react/useAsyncThrottledCallback/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/examples/react/useAsyncThrottler/package.json b/examples/react/useAsyncThrottler/package.json index 6ee9940f..5719042a 100644 --- a/examples/react/useAsyncThrottler/package.json +++ b/examples/react/useAsyncThrottler/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useAsyncThrottler/src/index.tsx b/examples/react/useAsyncThrottler/src/index.tsx index 2ef4878a..c9dedf1e 100644 --- a/examples/react/useAsyncThrottler/src/index.tsx +++ b/examples/react/useAsyncThrottler/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncThrottler } from '@tanstack/react-pacer/async-throttler' +import { PacerProvider } from '@tanstack/react-pacer/provider' interface SearchResult { id: number @@ -29,7 +30,7 @@ function App() { return } - throw new Error('Test error') // you don't have to catch errors here (though you still can). The onError optional handler will catch it + // throw new Error('Test error') // you don't have to catch errors here (though you still can). The onError optional handler will catch it const data = await fakeApi(term) setResults(data) @@ -54,11 +55,7 @@ function App() { // throwOnError: true, }, // Optional Selector function to pick the state you want to track and use - (state) => ({ - successCount: state.successCount, - isPending: state.isPending, - isExecuting: state.isExecuting, - }), + (state) => state, ) // get and name our throttled function @@ -114,13 +111,15 @@ function App() { const root = ReactDOM.createRoot(document.getElementById('root')!) -let mounted = true -root.render() - -// demo unmounting and cancellation -document.addEventListener('keydown', (e) => { - if (e.shiftKey && e.key === 'Enter') { - mounted = !mounted - root.render(mounted ? : null) - } -}) +// optionally, provide default options to an optional PacerProvider +root.render( + + + , +) diff --git a/examples/react/useBatchedCallback/package.json b/examples/react/useBatchedCallback/package.json index 9461039b..a94437b0 100644 --- a/examples/react/useBatchedCallback/package.json +++ b/examples/react/useBatchedCallback/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useBatcher/package.json b/examples/react/useBatcher/package.json index 17016793..6bb11067 100644 --- a/examples/react/useBatcher/package.json +++ b/examples/react/useBatcher/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useBatcher/src/index.tsx b/examples/react/useBatcher/src/index.tsx index 03e9bd8b..2fed18f9 100644 --- a/examples/react/useBatcher/src/index.tsx +++ b/examples/react/useBatcher/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useBatcher } from '@tanstack/react-pacer/batcher' +import { PacerProvider } from '@tanstack/react-pacer/provider' function App1() { // Use your state management library of choice @@ -83,8 +84,17 @@ function App1() { const root = ReactDOM.createRoot(document.getElementById('root')!) root.render( -
- -
-
, + // optionally, provide default options to an optional PacerProvider + +
+ +
+
+
, ) diff --git a/examples/react/useDebouncedCallback/package.json b/examples/react/useDebouncedCallback/package.json index c74350ba..ae3369b8 100644 --- a/examples/react/useDebouncedCallback/package.json +++ b/examples/react/useDebouncedCallback/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useDebouncedState/package.json b/examples/react/useDebouncedState/package.json index ca6349e8..4f3ef77b 100644 --- a/examples/react/useDebouncedState/package.json +++ b/examples/react/useDebouncedState/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useDebouncedValue/package.json b/examples/react/useDebouncedValue/package.json index ea922329..73df68c8 100644 --- a/examples/react/useDebouncedValue/package.json +++ b/examples/react/useDebouncedValue/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useDebouncer/package.json b/examples/react/useDebouncer/package.json index 6e4d2974..02c8e5e1 100644 --- a/examples/react/useDebouncer/package.json +++ b/examples/react/useDebouncer/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useDebouncer/src/index.tsx b/examples/react/useDebouncer/src/index.tsx index 575945e7..b5173af8 100644 --- a/examples/react/useDebouncer/src/index.tsx +++ b/examples/react/useDebouncer/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useDebouncer } from '@tanstack/react-pacer/debouncer' +import { PacerProvider } from '@tanstack/react-pacer/provider' function App1() { // Use your state management library of choice @@ -254,11 +255,21 @@ function App3() { const root = ReactDOM.createRoot(document.getElementById('root')!) root.render( -
- -
- -
- -
, + // optionally, provide default options to an optional PacerProvider + +
+ +
+ +
+ +
+ , +
, ) diff --git a/examples/react/useQueuedState/package.json b/examples/react/useQueuedState/package.json index 232cd32f..57a50791 100644 --- a/examples/react/useQueuedState/package.json +++ b/examples/react/useQueuedState/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useQueuedValue/package.json b/examples/react/useQueuedValue/package.json index 95bf5425..5877b47e 100644 --- a/examples/react/useQueuedValue/package.json +++ b/examples/react/useQueuedValue/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useQueuer/package.json b/examples/react/useQueuer/package.json index d142e381..c68ddf4c 100644 --- a/examples/react/useQueuer/package.json +++ b/examples/react/useQueuer/package.json @@ -15,10 +15,10 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@tanstack/react-devtools": "0.7.0", + "@tanstack/react-devtools": "0.7.1", "@tanstack/react-pacer-devtools": "0.3.1", - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useQueuer/src/index.tsx b/examples/react/useQueuer/src/index.tsx index acd29667..243e5ff3 100644 --- a/examples/react/useQueuer/src/index.tsx +++ b/examples/react/useQueuer/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useQueuer } from '@tanstack/react-pacer/queuer' +import { PacerProvider } from '@tanstack/react-pacer/provider' import { PacerDevtoolsPanel } from '@tanstack/react-pacer-devtools' import { TanStackDevtools } from '@tanstack/react-devtools' @@ -232,15 +233,24 @@ function App2() { const root = ReactDOM.createRoot(document.getElementById('root')!) root.render( -
- -
- + // optionally, provide default options to an optional PacerProvider + +
+ +
+ +
}]} /> -
, + , ) diff --git a/examples/react/useQueuerWithPersister/package.json b/examples/react/useQueuerWithPersister/package.json index a061d5f4..529083a5 100644 --- a/examples/react/useQueuerWithPersister/package.json +++ b/examples/react/useQueuerWithPersister/package.json @@ -15,8 +15,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useRateLimitedCallback/package.json b/examples/react/useRateLimitedCallback/package.json index c96f000d..0adab469 100644 --- a/examples/react/useRateLimitedCallback/package.json +++ b/examples/react/useRateLimitedCallback/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useRateLimitedState/package.json b/examples/react/useRateLimitedState/package.json index c009dd23..b87cbd31 100644 --- a/examples/react/useRateLimitedState/package.json +++ b/examples/react/useRateLimitedState/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useRateLimitedValue/package.json b/examples/react/useRateLimitedValue/package.json index 970459db..23fec07a 100644 --- a/examples/react/useRateLimitedValue/package.json +++ b/examples/react/useRateLimitedValue/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useRateLimiter/package.json b/examples/react/useRateLimiter/package.json index 9478a211..62e94ac4 100644 --- a/examples/react/useRateLimiter/package.json +++ b/examples/react/useRateLimiter/package.json @@ -15,8 +15,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index 5ca429a6..03abd849 100644 --- a/examples/react/useRateLimiter/src/index.tsx +++ b/examples/react/useRateLimiter/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useRateLimiter } from '@tanstack/react-pacer/rate-limiter' +import { PacerProvider } from '@tanstack/react-pacer/provider' function App1() { const [windowType, setWindowType] = useState<'fixed' | 'sliding'>('fixed') @@ -310,11 +311,20 @@ function App3() { const root = ReactDOM.createRoot(document.getElementById('root')!) root.render( -
- -
- -
- -
, + // optionally, provide default options to an optional PacerProvider + +
+ +
+ +
+ +
+
, ) diff --git a/examples/react/useRateLimiterWithPersister/package.json b/examples/react/useRateLimiterWithPersister/package.json index 7e7c075c..eb32e0a5 100644 --- a/examples/react/useRateLimiterWithPersister/package.json +++ b/examples/react/useRateLimiterWithPersister/package.json @@ -15,8 +15,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useThrottledCallback/package.json b/examples/react/useThrottledCallback/package.json index ec3b6d07..42ded86f 100644 --- a/examples/react/useThrottledCallback/package.json +++ b/examples/react/useThrottledCallback/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useThrottledState/package.json b/examples/react/useThrottledState/package.json index ce684751..f12e3fe0 100644 --- a/examples/react/useThrottledState/package.json +++ b/examples/react/useThrottledState/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useThrottledValue/package.json b/examples/react/useThrottledValue/package.json index 65e9992f..d832cc80 100644 --- a/examples/react/useThrottledValue/package.json +++ b/examples/react/useThrottledValue/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useThrottler/package.json b/examples/react/useThrottler/package.json index 51b44518..5666131e 100644 --- a/examples/react/useThrottler/package.json +++ b/examples/react/useThrottler/package.json @@ -14,8 +14,8 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/react/useThrottler/src/index.tsx b/examples/react/useThrottler/src/index.tsx index 991b77f1..f381e048 100644 --- a/examples/react/useThrottler/src/index.tsx +++ b/examples/react/useThrottler/src/index.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useThrottler } from '@tanstack/react-pacer/throttler' +import { PacerProvider } from '@tanstack/react-pacer/provider' function App1() { // Use your state management library of choice @@ -226,11 +227,20 @@ function App3() { const root = ReactDOM.createRoot(document.getElementById('root')!) root.render( -
- -
- -
- -
, + // optionally, provide default options to an optional PacerProvider + +
+ +
+ +
+ +
+
, ) diff --git a/examples/react/util-comparison/package.json b/examples/react/util-comparison/package.json index 1fef05d6..d56c587b 100644 --- a/examples/react/util-comparison/package.json +++ b/examples/react/util-comparison/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.1" }, "devDependencies": { - "@tanstack/react-devtools": "0.7.0", + "@tanstack/react-devtools": "0.7.1", "@tanstack/react-pacer-devtools": "0.3.1", - "@types/react": "^19.1.15", - "@types/react-dom": "^19.1.9", + "@types/react": "^19.1.17", + "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", "vite": "^7.1.7" }, diff --git a/examples/solid/asyncBatch/package.json b/examples/solid/asyncBatch/package.json index 61dbef5e..76a1feb6 100644 --- a/examples/solid/asyncBatch/package.json +++ b/examples/solid/asyncBatch/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncBatch/src/index.tsx b/examples/solid/asyncBatch/src/index.tsx index 9d6c3435..9e4ee525 100644 --- a/examples/solid/asyncBatch/src/index.tsx +++ b/examples/solid/asyncBatch/src/index.tsx @@ -52,7 +52,7 @@ function App() { } catch (error: any) { setErrors((prev) => [ ...prev, - `Error: ${error.message} (${new Date().toLocaleTimeString()})`, + `Error: ${error} (${new Date().toLocaleTimeString()})`, ]) setErrorCount((prev) => prev + 1) console.error('Batch failed:', error) diff --git a/examples/solid/asyncDebounce/package.json b/examples/solid/asyncDebounce/package.json index e5fd33b0..3bda1992 100644 --- a/examples/solid/asyncDebounce/package.json +++ b/examples/solid/asyncDebounce/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncRateLimit/package.json b/examples/solid/asyncRateLimit/package.json index d15b55c5..b51fada5 100644 --- a/examples/solid/asyncRateLimit/package.json +++ b/examples/solid/asyncRateLimit/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncThrottle/package.json b/examples/solid/asyncThrottle/package.json index 7d238c2f..91635f30 100644 --- a/examples/solid/asyncThrottle/package.json +++ b/examples/solid/asyncThrottle/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/batch/package.json b/examples/solid/batch/package.json index ba7c7d70..5ba90e80 100644 --- a/examples/solid/batch/package.json +++ b/examples/solid/batch/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncBatcher/package.json b/examples/solid/createAsyncBatcher/package.json index 8c5faec9..a4aa4756 100644 --- a/examples/solid/createAsyncBatcher/package.json +++ b/examples/solid/createAsyncBatcher/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncBatcher/src/index.tsx b/examples/solid/createAsyncBatcher/src/index.tsx index 1f40d553..01c4b412 100644 --- a/examples/solid/createAsyncBatcher/src/index.tsx +++ b/examples/solid/createAsyncBatcher/src/index.tsx @@ -61,7 +61,7 @@ function App() { console.error('Batch failed:', error) setErrors((prev) => [ ...prev, - `Error: ${error.message} (${new Date().toLocaleTimeString()})`, + `Error: ${error} (${new Date().toLocaleTimeString()})`, ]) }, onSettled: (batch, batcher) => { diff --git a/examples/solid/createAsyncDebouncer/package.json b/examples/solid/createAsyncDebouncer/package.json index a48e6618..e4da60d2 100644 --- a/examples/solid/createAsyncDebouncer/package.json +++ b/examples/solid/createAsyncDebouncer/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncQueuer/package.json b/examples/solid/createAsyncQueuer/package.json index d8e706c0..212328f4 100644 --- a/examples/solid/createAsyncQueuer/package.json +++ b/examples/solid/createAsyncQueuer/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncRateLimiter/package.json b/examples/solid/createAsyncRateLimiter/package.json index f56fadff..bf1fa596 100644 --- a/examples/solid/createAsyncRateLimiter/package.json +++ b/examples/solid/createAsyncRateLimiter/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncThrottler/package.json b/examples/solid/createAsyncThrottler/package.json index df3fe98f..93188419 100644 --- a/examples/solid/createAsyncThrottler/package.json +++ b/examples/solid/createAsyncThrottler/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createBatcher/package.json b/examples/solid/createBatcher/package.json index ba33be3f..28448153 100644 --- a/examples/solid/createBatcher/package.json +++ b/examples/solid/createBatcher/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createDebouncedSignal/package.json b/examples/solid/createDebouncedSignal/package.json index 39686f9a..626cab55 100644 --- a/examples/solid/createDebouncedSignal/package.json +++ b/examples/solid/createDebouncedSignal/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createDebouncedValue/package.json b/examples/solid/createDebouncedValue/package.json index dc776301..e871da96 100644 --- a/examples/solid/createDebouncedValue/package.json +++ b/examples/solid/createDebouncedValue/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createDebouncer/package.json b/examples/solid/createDebouncer/package.json index 252778c0..a41155f9 100644 --- a/examples/solid/createDebouncer/package.json +++ b/examples/solid/createDebouncer/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createQueuer/package.json b/examples/solid/createQueuer/package.json index fcfbc9ba..0dcfb86b 100644 --- a/examples/solid/createQueuer/package.json +++ b/examples/solid/createQueuer/package.json @@ -9,14 +9,14 @@ "test:types": "tsc" }, "dependencies": { - "@tanstack/solid-devtools": "0.7.0", + "@tanstack/solid-devtools": "0.7.1", "@tanstack/solid-pacer": "^0.14.4", "@tanstack/solid-pacer-devtools": "0.3.1", "solid-js": "^1.9.9" }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createRateLimitedSignal/package.json b/examples/solid/createRateLimitedSignal/package.json index 6ce08629..81be94c2 100644 --- a/examples/solid/createRateLimitedSignal/package.json +++ b/examples/solid/createRateLimitedSignal/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createRateLimitedValue/package.json b/examples/solid/createRateLimitedValue/package.json index 9427eeba..cf29a7ef 100644 --- a/examples/solid/createRateLimitedValue/package.json +++ b/examples/solid/createRateLimitedValue/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createRateLimiter/package.json b/examples/solid/createRateLimiter/package.json index a81f5f87..306ca83a 100644 --- a/examples/solid/createRateLimiter/package.json +++ b/examples/solid/createRateLimiter/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createThrottledSignal/package.json b/examples/solid/createThrottledSignal/package.json index e41af8ec..1047facd 100644 --- a/examples/solid/createThrottledSignal/package.json +++ b/examples/solid/createThrottledSignal/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createThrottledValue/package.json b/examples/solid/createThrottledValue/package.json index 1319ddac..a707991c 100644 --- a/examples/solid/createThrottledValue/package.json +++ b/examples/solid/createThrottledValue/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/createThrottler/package.json b/examples/solid/createThrottler/package.json index 1315d041..523364e7 100644 --- a/examples/solid/createThrottler/package.json +++ b/examples/solid/createThrottler/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/debounce/package.json b/examples/solid/debounce/package.json index 13c7ac06..c34f23ef 100644 --- a/examples/solid/debounce/package.json +++ b/examples/solid/debounce/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/queue/package.json b/examples/solid/queue/package.json index 88807dcb..5be7b6a6 100644 --- a/examples/solid/queue/package.json +++ b/examples/solid/queue/package.json @@ -9,14 +9,14 @@ "test:types": "tsc" }, "dependencies": { - "@tanstack/solid-devtools": "0.7.0", + "@tanstack/solid-devtools": "0.7.1", "@tanstack/solid-pacer": "^0.14.4", "@tanstack/solid-pacer-devtools": "0.3.1", "solid-js": "^1.9.9" }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/rateLimit/package.json b/examples/solid/rateLimit/package.json index 495a75bf..26767754 100644 --- a/examples/solid/rateLimit/package.json +++ b/examples/solid/rateLimit/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/examples/solid/throttle/package.json b/examples/solid/throttle/package.json index 08394f10..0fb47204 100644 --- a/examples/solid/throttle/package.json +++ b/examples/solid/throttle/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "vite": "^7.1.7", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "browserslist": { "production": [ diff --git a/nx.json b/nx.json index be6938dc..adfddd41 100644 --- a/nx.json +++ b/nx.json @@ -1,5 +1,8 @@ { "$schema": "./node_modules/nx/schemas/nx-schema.json", + "tui": { + "enabled": false + }, "defaultBase": "main", "useInferencePlugins": false, "parallel": 5, diff --git a/package.json b/package.json index 435a568a..ba55a416 100644 --- a/package.json +++ b/package.json @@ -58,22 +58,22 @@ "@size-limit/preset-small-lib": "^11.2.0", "@svitejs/changesets-changelog-github-compact": "^1.2.0", "@tanstack/config": "0.20.3", - "@testing-library/jest-dom": "^6.8.0", - "@types/node": "^24.3.1", + "@testing-library/jest-dom": "^6.9.1", + "@types/node": "^24.6.1", "eslint": "^9.36.0", "eslint-plugin-unused-imports": "^4.2.0", "fast-glob": "^3.3.3", "jsdom": "^27.0.0", "knip": "^5.64.1", "markdown-link-extractor": "^4.0.2", - "nx": "^21.5.3", + "nx": "^21.6.2", "premove": "^4.0.0", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "publint": "^0.3.13", "sherif": "^1.6.1", "size-limit": "^11.2.0", - "typescript": "5.9.2", + "typescript": "5.9.3", "vite": "^7.1.7", "vitest": "^3.2.4" }, diff --git a/packages/pacer-devtools/package.json b/packages/pacer-devtools/package.json index 90e5b6db..95b5f8a2 100644 --- a/packages/pacer-devtools/package.json +++ b/packages/pacer-devtools/package.json @@ -71,6 +71,6 @@ "solid-js": "^1.9.9" }, "devDependencies": { - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" } } diff --git a/packages/pacer/package.json b/packages/pacer/package.json index 9d026557..32ed59b4 100644 --- a/packages/pacer/package.json +++ b/packages/pacer/package.json @@ -77,6 +77,16 @@ "default": "./dist/cjs/async-rate-limiter.cjs" } }, + "./async-retryer": { + "import": { + "types": "./dist/esm/async-retryer.d.ts", + "default": "./dist/esm/async-retryer.js" + }, + "require": { + "types": "./dist/cjs/async-retryer.d.cts", + "default": "./dist/cjs/async-retryer.cjs" + } + }, "./async-throttler": { "import": { "types": "./dist/esm/async-throttler.d.ts", diff --git a/packages/pacer/src/async-batcher.ts b/packages/pacer/src/async-batcher.ts index bfc9059f..8892dffa 100644 --- a/packages/pacer/src/async-batcher.ts +++ b/packages/pacer/src/async-batcher.ts @@ -1,6 +1,8 @@ import { Store } from '@tanstack/store' +import { AsyncRetryer } from './async-retryer' import { createKey, parseFunctionOrValue } from './utils' import { emitChange, pacerEventClient } from './event-client' +import type { AsyncRetryerOptions } from './async-retryer' import type { OptionalKeys } from './types' export interface AsyncBatcherState { @@ -8,6 +10,10 @@ export interface AsyncBatcherState { * Number of batch executions that have resulted in errors */ errorCount: number + /** + * Number of batch executions that have been executed + */ + executeCount: number /** * Array of items that failed during batch processing */ @@ -61,6 +67,7 @@ export interface AsyncBatcherState { function getDefaultAsyncBatcherState(): AsyncBatcherState { return { errorCount: 0, + executeCount: 0, failedItems: [], isEmpty: true, isExecuting: false, @@ -80,6 +87,12 @@ function getDefaultAsyncBatcherState(): AsyncBatcherState { * Options for configuring an AsyncBatcher instance */ export interface AsyncBatcherOptions { + /** + * Options for configuring the underlying async retryer + */ + asyncRetryerOptions?: AsyncRetryerOptions< + (items: Array) => Promise + > /** * Custom function to determine if a batch should be processed * Return true to process the batch immediately @@ -108,7 +121,7 @@ export interface AsyncBatcherOptions { * This can be used alongside throwOnError - the handler will be called before any error is thrown. */ onError?: ( - error: unknown, + error: Error, batch: Array, batcher: AsyncBatcher, ) => void @@ -159,6 +172,9 @@ type AsyncBatcherOptionsWithOptionalCallbacks = OptionalKeys< > const defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks = { + asyncRetryerOptions: { + maxAttempts: 1, + }, getShouldExecute: () => false, maxSize: Infinity, started: true, @@ -169,13 +185,19 @@ const defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks = { /** * A class that collects items and processes them in batches asynchronously. * - * This is the async version of the Batcher class. Unlike the sync version, this async batcher: - * - Handles promises and returns results from batch executions - * - Provides error handling with configurable error behavior - * - Tracks success, error, and settle counts separately - * - Has state tracking for when batches are executing - * - Returns the result of the batch function execution + * Async vs Sync Versions: + * The async version provides advanced features over the sync Batcher: + * - Returns promises that can be awaited for batch results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight batch executions + * - Cancel support to prevent pending batches from starting + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) + * + * The sync Batcher is lighter weight and simpler when you don't need async features, + * return values, or execution control. * + * What is Batching? * Batching is a technique for grouping multiple operations together to be processed as a single unit. * * The AsyncBatcher provides a flexible way to implement async batching with configurable: @@ -233,6 +255,10 @@ export class AsyncBatcher { ) key: string options: AsyncBatcherOptionsWithOptionalCallbacks + asyncRetryers = new Map< + number, + AsyncRetryer<(items: Array) => Promise> + >() #timeoutId: NodeJS.Timeout | null = null constructor( @@ -254,6 +280,11 @@ export class AsyncBatcher { }) } + /** + * Emits a change event for the async batcher instance. Mostly useful for devtools. + */ + _emit = () => emitChange('AsyncBatcher', this) + /** * Updates the async batcher options */ @@ -293,8 +324,12 @@ export class AsyncBatcher { /** * Adds an item to the async batcher * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed + * + * @returns The result from the batch function, or undefined if an error occurred and was handled by onError + * + * @throws The error from the batch function if no onError handler is configured or throwOnError is true */ - addItem = (item: TValue): void => { + addItem = async (item: TValue): Promise => { this.#setState({ items: [...this.store.state.items, item], isPending: this.options.wait !== Infinity, @@ -306,10 +341,11 @@ export class AsyncBatcher { this.options.getShouldExecute(this.store.state.items, this) if (shouldProcess) { - this.#execute() + return await this.#execute() } else if (this.options.wait !== Infinity) { this.#clearTimeout() // clear any pending timeout to replace it with a new one this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait()) + await new Promise((resolve) => setTimeout(resolve, this.#getWait())) } } @@ -330,14 +366,20 @@ export class AsyncBatcher { return undefined } + const currentExecuteCount = this.store.state.executeCount + 1 const batch = this.peekAllItems() // copy of the items to be processed (to prevent race conditions) this.clear() // Clear items before processing to prevent race conditions this.options.onItemsChange?.(this) - this.#setState({ isExecuting: true }) + this.#setState({ isExecuting: true, executeCount: currentExecuteCount }) try { - const result = await this.fn(batch) // EXECUTE + const currentAsyncRetryer = new AsyncRetryer( + this.fn, + this.options.asyncRetryerOptions, + ) + this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer) + const result = await currentAsyncRetryer.execute(batch) // EXECUTE this.#setState({ totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length, @@ -352,12 +394,13 @@ export class AsyncBatcher { failedItems: [...this.store.state.failedItems, ...batch], totalItemsFailed: this.store.state.totalItemsFailed + batch.length, }) - this.options.onError?.(error, batch, this) + this.options.onError?.(error as Error, batch, this) if (this.options.throwOnError) { throw error } return undefined } finally { + this.asyncRetryers.delete(currentExecuteCount) // dispose retryer this.#setState({ isExecuting: false, settleCount: this.store.state.settleCount + 1, @@ -399,6 +442,61 @@ export class AsyncBatcher { this.#setState({ items: [], failedItems: [], isPending: false }) } + /** + * Returns the AbortSignal for a specific execution. + * If no executeCount is provided, returns the signal for the most recent execution. + * Returns null if no execution is found or not currently executing. + * + * @param executeCount - Optional specific execution to get signal for + * @example + * ```typescript + * const batcher = new AsyncBatcher( + * async (items: string[]) => { + * const signal = batcher.getAbortSignal() + * if (signal) { + * const response = await fetch('/api/batch', { + * method: 'POST', + * body: JSON.stringify(items), + * signal + * }) + * return response.json() + * } + * }, + * { maxSize: 10, wait: 100 } + * ) + * ``` + */ + getAbortSignal(executeCount?: number): AbortSignal | null { + const count = executeCount ?? this.store.state.executeCount + const retryer = this.asyncRetryers.get(count) + return retryer?.getAbortSignal() ?? null + } + + /** + * Aborts all ongoing executions with the internal abort controllers. + * Does NOT cancel any pending execution that have not started yet. + * Does NOT clear out the items. + */ + abort = (): void => { + this.asyncRetryers.forEach((retryer) => retryer.abort()) + this.asyncRetryers.clear() + this.#setState({ + isExecuting: false, + }) + } + + /** + * Cancels any pending execution that have not started yet. + * Does NOT abort any execution already in progress. + * Does NOT clear out the items. + */ + cancel = (): void => { + this.#clearTimeout() + this.#setState({ + isPending: false, + }) + } + /** * Resets the async batcher state to its default values */ @@ -409,13 +507,29 @@ export class AsyncBatcher { } /** - * Creates an async batcher that processes items in batches + * Creates an async batcher that processes items in batches. + * + * Async vs Sync Versions: + * The async version provides advanced features over the sync batch function: + * - Returns promises that can be awaited for batch results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight batch executions + * - Cancel support to prevent pending batches from starting + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) * - * Unlike the sync batcher, this async version: - * - Handles promises and returns results from batch executions - * - Provides error handling with configurable error behavior - * - Tracks success, error, and settle counts separately - * - Has state tracking for when batches are executing + * The sync batch function is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Batching? + * Batching is a technique for grouping multiple operations together to be processed as a single unit. + * + * Configuration Options: + * - `maxSize`: Maximum number of items per batch (default: Infinity) + * - `wait`: Time to wait before processing batch (default: Infinity) + * - `getShouldExecute`: Custom logic to trigger batch processing + * - `asyncRetryerOptions`: Configure retry behavior for batch executions + * - `started`: Whether to start processing immediately (default: true) * * Error Handling: * - If an `onError` handler is provided, it will be called with the error, the batch of items that failed, and batcher instance @@ -430,7 +544,6 @@ export class AsyncBatcher { * - Use `onSuccess` callback to react to successful batch execution and implement custom logic * - Use `onError` callback to react to batch execution errors and implement custom error handling * - Use `onSettled` callback to react to batch execution completion (success or error) and implement custom logic - * - Use `onExecute` callback to react to batch execution and implement custom logic * - Use `onItemsChange` callback to react to items being added or removed from the batcher * - The state includes total items processed, success/error counts, and execution status * - State can be accessed via the underlying AsyncBatcher instance's `store.state` property diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 4185ed3d..14130744 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -1,6 +1,8 @@ import { Store } from '@tanstack/store' +import { AsyncRetryer } from './async-retryer' import { createKey, parseFunctionOrValue } from './utils' import { emitChange, pacerEventClient } from './event-client' +import type { AsyncRetryerOptions } from './async-retryer' import type { AnyAsyncFunction, OptionalKeys } from './types' export interface AsyncDebouncerState { @@ -67,6 +69,10 @@ function getDefaultAsyncDebouncerState< * Options for configuring an async debounced function */ export interface AsyncDebouncerOptions { + /** + * Options for configuring the underlying async retryer + */ + asyncRetryerOptions?: AsyncRetryerOptions /** * Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions. * Can be a boolean or a function that returns a boolean. @@ -93,7 +99,7 @@ export interface AsyncDebouncerOptions { * This can be used alongside throwOnError - the handler will be called before any error is thrown. */ onError?: ( - error: unknown, + error: Error, args: Parameters, debouncer: AsyncDebouncer, ) => void @@ -134,6 +140,9 @@ type AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys< > const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { + asyncRetryerOptions: { + maxAttempts: 1, + }, enabled: true, leading: false, trailing: true, @@ -143,6 +152,19 @@ const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { /** * A class that creates an async debounced function. * + * Async vs Sync Versions: + * The async version provides advanced features over the sync Debouncer: + * - Returns promises that can be awaited for debounced function results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight executions + * - Cancel support to prevent pending executions from starting + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) + * + * The sync Debouncer is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Debouncing? * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation. * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing * or input changes where you only want to execute the handler after the events have stopped occurring. @@ -150,10 +172,6 @@ const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { * Unlike throttling which allows execution at regular intervals, debouncing prevents any execution until * the function stops being called for the specified delay period. * - * Unlike the non-async Debouncer, this async version supports returning values from the debounced function, - * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call - * instead of setting the result on a state variable from within the debounced function. - * * Error Handling: * - If an `onError` handler is provided, it will be called with the error and debouncer instance * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown @@ -191,7 +209,7 @@ export class AsyncDebouncer { >(getDefaultAsyncDebouncerState()) key: string options: AsyncDebouncerOptions - #abortController: AbortController | null = null + asyncRetryers = new Map>() #timeoutId: NodeJS.Timeout | null = null #resolvePreviousPromise: | ((value?: ReturnType | undefined) => void) @@ -216,6 +234,11 @@ export class AsyncDebouncer { }) } + /** + * Emits a change event for the async debouncer instance. Mostly useful for devtools. + */ + _emit = () => emitChange('AsyncDebouncer', this) + /** * Updates the async debouncer options */ @@ -326,31 +349,37 @@ export class AsyncDebouncer { ...args: Parameters ): Promise | undefined> => { if (!this.#getEnabled()) return undefined - this.#abortController = new AbortController() + const currentMaybeExecuteCount = this.store.state.maybeExecuteCount + 1 + try { this.#setState({ isExecuting: true }) - const result = await this.fn(...args) // EXECUTE! + const currentAsyncRetryer = new AsyncRetryer(this.fn, { + ...this.options.asyncRetryerOptions, + key: `${this.key}-retryer-${currentMaybeExecuteCount}`, + }) + this.asyncRetryers.set(currentMaybeExecuteCount, currentAsyncRetryer) + const result = await currentAsyncRetryer.execute(...args) // EXECUTE! this.#setState({ lastResult: result, successCount: this.store.state.successCount + 1, }) - this.options.onSuccess?.(result, args, this) + this.options.onSuccess?.(result as ReturnType, args, this) } catch (error) { this.#setState({ errorCount: this.store.state.errorCount + 1, }) - this.options.onError?.(error, args, this) + this.options.onError?.(error as Error, args, this) if (this.options.throwOnError) { throw error } } finally { + this.asyncRetryers.delete(currentMaybeExecuteCount) // dispose retryer this.#setState({ isExecuting: false, isPending: false, lastArgs: undefined, settleCount: this.store.state.settleCount + 1, }) - this.#abortController = null this.options.onSettled?.(args, this) } return this.store.state.lastResult @@ -361,14 +390,9 @@ export class AsyncDebouncer { */ flush = async (): Promise | undefined> => { if (this.store.state.isPending && this.store.state.lastArgs) { - this.#abortExecution() // abort any current execution - this.#clearTimeout() // clear any existing timeout - const result = await this.#execute(...this.store.state.lastArgs) - - // Resolve any pending promise from maybeExecute - this.#resolvePreviousPromiseInternal() - - return result + const { lastArgs } = this.store.state + this.#cancelPendingExecution() + return await this.#execute(...lastArgs) } return undefined } @@ -387,29 +411,62 @@ export class AsyncDebouncer { } } + /** + * Internal cancel without resetting the leading execute state + */ #cancelPendingExecution = (): void => { this.#clearTimeout() this.#resolvePreviousPromiseInternal() this.#setState({ isPending: false, - isExecuting: false, lastArgs: undefined, }) } - #abortExecution = (): void => { - if (this.#abortController) { - this.#abortController.abort() - this.#abortController = null - } + /** + * Returns the AbortSignal for a specific execution. + * If no maybeExecuteCount is provided, returns the signal for the most recent execution. + * Returns null if no execution is found or not currently executing. + * + * @param maybeExecuteCount - Optional specific execution to get signal for + * @example + * ```typescript + * const debouncer = new AsyncDebouncer( + * async (searchTerm: string) => { + * const signal = debouncer.getAbortSignal() + * if (signal) { + * const response = await fetch(`/api/search?q=${searchTerm}`, { signal }) + * return response.json() + * } + * }, + * { wait: 300 } + * ) + * ``` + */ + getAbortSignal(maybeExecuteCount?: number): AbortSignal | null { + const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount + const retryer = this.asyncRetryers.get(count) + return retryer?.getAbortSignal() ?? null + } + + /** + * Aborts all ongoing executions with the internal abort controllers. + * Does NOT cancel any pending execution that have not started yet. + */ + abort = (): void => { + this.asyncRetryers.forEach((retryer) => retryer.abort()) + this.asyncRetryers.clear() + this.#setState({ + isExecuting: false, + }) } /** - * Cancels any pending execution or aborts any execution in progress + * Cancels any pending execution that have not started yet. + * Does NOT abort any execution already in progress. */ cancel = (): void => { this.#cancelPendingExecution() - this.#abortExecution() this.#setState({ canLeadingExecute: true }) } @@ -426,9 +483,29 @@ export class AsyncDebouncer { * The debounced function will only execute once the wait period has elapsed without any new calls. * If called again during the wait period, the timer resets and a new wait period begins. * - * Unlike the non-async Debouncer, this async version supports returning values from the debounced function, - * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call - * instead of setting the result on a state variable from within the debounced function. + * Async vs Sync Versions: + * The async version provides advanced features over the sync debounce function: + * - Returns promises that can be awaited for debounced function results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight executions + * - Cancel support to prevent pending executions from starting + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) + * + * The sync debounce function is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Debouncing? + * Debouncing ensures that a function is only executed after a specified delay has passed since its last invocation. + * Each new invocation resets the delay timer. This is useful for handling frequent events like window resizing + * or input changes where you only want to execute the handler after the events have stopped occurring. + * + * Configuration Options: + * - `wait`: Delay in milliseconds to wait after the last call (required) + * - `leading`: Execute on the leading edge of the timeout (default: false) + * - `trailing`: Execute on the trailing edge of the timeout (default: true) + * - `enabled`: Whether the debouncer is enabled (default: true) + * - `asyncRetryerOptions`: Configure retry behavior for executions * * Error Handling: * - If an `onError` handler is provided, it will be called with the error and debouncer instance diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index dc985232..c40135f4 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -1,6 +1,8 @@ import { Store } from '@tanstack/store' +import { AsyncRetryer } from './async-retryer' import { createKey, parseFunctionOrValue } from './utils' import { emitChange, pacerEventClient } from './event-client' +import type { AsyncRetryerOptions } from './async-retryer' import type { OptionalKeys } from './types' import type { QueuePosition } from './queuer' @@ -17,6 +19,10 @@ export interface AsyncQueuerState { * Number of task executions that have resulted in errors */ errorCount: number + /** + * Number of times execute has been called + */ + executeCount: number /** * Number of items that have been removed from the queue due to expiration */ @@ -25,6 +31,10 @@ export interface AsyncQueuerState { * Whether the queuer has no items to process (items array is empty) */ isEmpty: boolean + /** + * Whether the queuer is currently executing + */ + isExecuting: boolean /** * Whether the queuer has reached its maximum capacity */ @@ -80,8 +90,10 @@ function getDefaultAsyncQueuerState(): AsyncQueuerState { activeItems: [], addItemCount: 0, errorCount: 0, + executeCount: 0, expirationCount: 0, isEmpty: true, + isExecuting: false, isFull: false, isIdle: true, isRunning: true, @@ -98,6 +110,10 @@ function getDefaultAsyncQueuerState(): AsyncQueuerState { } export interface AsyncQueuerOptions { + /** + * Options for configuring the underlying async retryer + */ + asyncRetryerOptions?: AsyncRetryerOptions<(item: TValue) => Promise> /** * Default position to add items to the queuer * @default 'back' @@ -152,7 +168,7 @@ export interface AsyncQueuerOptions { * If provided, the handler will be called with the error and queuer instance. * This can be used alongside throwOnError - the handler will be called before any error is thrown. */ - onError?: (error: unknown, item: TValue, queuer: AsyncQueuer) => void + onError?: (error: Error, item: TValue, queuer: AsyncQueuer) => void /** * Callback fired whenever an item expires in the queuer */ @@ -206,6 +222,9 @@ type AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys< const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { addItemsTo: 'back', + asyncRetryerOptions: { + maxAttempts: 1, + }, concurrency: 1, expirationDuration: Infinity, getIsExpired: () => false, @@ -220,18 +239,31 @@ const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { /** * A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration. * - * Features: + * Async vs Sync Versions: + * The async version provides advanced features over the sync Queuer: + * - Returns promises that can be awaited for task results + * - Built-in retry support via AsyncRetryer integration for each queued task + * - Abort support to cancel in-flight task executions + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) + * - Concurrent execution support (process multiple items simultaneously) + * + * The sync Queuer is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Queuing? + * Queuing is a technique for managing and processing items sequentially or with controlled concurrency. + * Tasks are processed up to the configured concurrency limit. When a task completes, + * the next pending task is processed if the concurrency limit allows. + * + * Key Features: * - Priority queue support via the getPriority option * - Configurable concurrency limit * - Callbacks for task success, error, completion, and queue state changes * - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior * - Pause and resume processing - * - Task cancellation * - Item expiration to remove stale items from the queue * - * Tasks are processed concurrently up to the configured concurrency limit. When a task completes, - * the next pending task is processed if the concurrency limit allows. - * * Error Handling: * - If an `onError` handler is provided, it will be called with the error and queuer instance * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown @@ -274,6 +306,10 @@ export class AsyncQueuer { >(getDefaultAsyncQueuerState()) key: string options: AsyncQueuerOptions + asyncRetryers = new Map< + number, + AsyncRetryer<(item: TValue) => Promise> + >() #timeoutIds: Set = new Set() constructor( @@ -312,6 +348,11 @@ export class AsyncQueuer { }) } + /** + * Emits a change event for the async queuer instance. Mostly useful for devtools. + */ + _emit = () => emitChange('AsyncQueuer', this) + /** * Updates the queuer options. New options are merged with existing options. */ @@ -555,9 +596,20 @@ export class AsyncQueuer { */ execute = async (position?: QueuePosition): Promise => { const item = this.getNextItem(position) + if (item !== undefined) { + const currentExecuteCount = this.store.state.executeCount + 1 + this.#setState({ + executeCount: currentExecuteCount, + isExecuting: true, + }) try { - const lastResult = await this.fn(item) // EXECUTE! + const currentAsyncRetryer = new AsyncRetryer(this.fn, { + ...this.options.asyncRetryerOptions, + key: `${this.key}-retryer-${currentExecuteCount}`, + }) + this.asyncRetryers.set(currentExecuteCount, currentAsyncRetryer) + const lastResult = await currentAsyncRetryer.execute(item) // EXECUTE! this.#setState({ successCount: this.store.state.successCount + 1, lastResult, @@ -567,15 +619,17 @@ export class AsyncQueuer { this.#setState({ errorCount: this.store.state.errorCount + 1, }) - this.options.onError?.(error, item, this) + this.options.onError?.(error as Error, item, this) if (this.options.throwOnError) { throw error } } finally { + this.asyncRetryers.delete(currentExecuteCount) // dispose retryer this.#setState({ activeItems: this.store.state.activeItems.filter( (activeItem) => activeItem !== item, ), + isExecuting: false, settledCount: this.store.state.settledCount + 1, }) this.options.onSettled?.(item, this) @@ -729,13 +783,52 @@ export class AsyncQueuer { } /** - * Removes all pending items from the queue. Does not affect active tasks. + * Removes all pending items from the queue. + * Does NOT affect active tasks. */ clear = (): void => { this.#setState({ items: [], itemTimestamps: [] }) this.options.onItemsChange?.(this) } + /** + * Returns the AbortSignal for a specific execution. + * If no executeCount is provided, returns the signal for the most recent execution. + * Returns null if no execution is found or not currently executing. + * + * @param executeCount - Optional specific execution to get signal for + * @example + * ```typescript + * const queuer = new AsyncQueuer( + * async (item: string) => { + * const signal = queuer.getAbortSignal() + * if (signal) { + * const response = await fetch(`/api/process/${item}`, { signal }) + * return response.json() + * } + * }, + * { concurrency: 2 } + * ) + * ``` + */ + getAbortSignal(executeCount?: number): AbortSignal | null { + const count = executeCount ?? this.store.state.executeCount + const retryer = this.asyncRetryers.get(count) + return retryer?.getAbortSignal() ?? null + } + + /** + * Aborts all ongoing executions with the internal abort controllers. + * Does NOT clear out the items. + */ + abort = (): void => { + this.asyncRetryers.forEach((retryer) => retryer.abort()) + this.asyncRetryers.clear() + this.#setState({ + isExecuting: false, + }) + } + /** * Resets the queuer state to its default values */ @@ -749,6 +842,34 @@ export class AsyncQueuer { * Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks. * The queuer is started automatically and ready to process items. * + * Async vs Sync Versions: + * The async version provides advanced features over the sync queue function: + * - Returns promises that can be awaited for task results + * - Built-in retry support via AsyncRetryer integration for each queued task + * - Abort support to cancel in-flight task executions + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) + * - Concurrent execution support (process multiple items simultaneously) + * + * The sync queue function is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Queuing? + * Queuing is a technique for managing and processing items sequentially or with controlled concurrency. + * Tasks are processed up to the configured concurrency limit. When a task completes, + * the next pending task is processed if the concurrency limit allows. + * + * Configuration Options: + * - `concurrency`: Maximum number of concurrent tasks (default: 1) + * - `wait`: Time to wait between processing items (default: 0) + * - `maxSize`: Maximum number of items allowed in the queue (default: Infinity) + * - `getPriority`: Function to determine item priority + * - `addItemsTo`: Default position to add items ('back' or 'front', default: 'back') + * - `getItemsFrom`: Default position to get items ('front' or 'back', default: 'front') + * - `expirationDuration`: Maximum time items can stay in queue + * - `started`: Whether to start processing immediately (default: true) + * - `asyncRetryerOptions`: Configure retry behavior for task executions + * * Error Handling: * - If an `onError` handler is provided, it will be called with the error and queuer instance * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown @@ -769,11 +890,15 @@ export class AsyncQueuer { * - State can be accessed via the underlying AsyncQueuer instance's `store.state` property * - When using framework adapters (React/Solid), state is accessed from the hook's state property * - * Example usage: + * @example * ```ts * const enqueue = asyncQueue(async (item) => { * return item.toUpperCase(); - * }, {...options}); + * }, { + * concurrency: 2, + * wait: 100, + * onSuccess: (result) => console.log('Processed:', result) + * }); * * enqueue('hello'); * ``` diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 9d1f664b..e92a20a8 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -1,6 +1,8 @@ import { Store } from '@tanstack/store' +import { AsyncRetryer } from './async-retryer' import { createKey, parseFunctionOrValue } from './utils' import { emitChange, pacerEventClient } from './event-client' +import type { AsyncRetryerOptions } from './async-retryer' import type { AnyAsyncFunction } from './types' export interface AsyncRateLimiterState { @@ -67,6 +69,10 @@ function getDefaultAsyncRateLimiterState< * Options for configuring an async rate-limited function */ export interface AsyncRateLimiterOptions { + /** + * Options for configuring the underlying async retryer + */ + asyncRetryerOptions?: AsyncRetryerOptions /** * Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions. * Can be a boolean or a function that returns a boolean. @@ -93,7 +99,7 @@ export interface AsyncRateLimiterOptions { * This can be used alongside throwOnError - the handler will be called before any error is thrown. */ onError?: ( - error: unknown, + error: Error, args: Parameters, rateLimiter: AsyncRateLimiter, ) => void @@ -140,6 +146,9 @@ const defaultOptions: Omit< Required>, 'initialState' | 'onError' | 'onReject' | 'onSettled' | 'onSuccess' | 'key' > = { + asyncRetryerOptions: { + maxAttempts: 1, + }, enabled: true, limit: 1, window: 0, @@ -150,26 +159,34 @@ const defaultOptions: Omit< /** * A class that creates an async rate-limited function. * - * Rate limiting is a simple approach that allows a function to execute up to a limit within a time window, + * Async vs Sync Versions: + * The async version provides advanced features over the sync RateLimiter: + * - Returns promises that can be awaited for rate-limited function results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight executions + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts, rejection counts) + * - More sophisticated window management with automatic cleanup + * + * The sync RateLimiter is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Rate Limiting? + * Rate limiting allows a function to execute up to a limit within a time window, * then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where * all executions happen immediately, followed by a complete block. * - * The rate limiter supports two types of windows: + * Window Types: * - 'fixed': A strict window that resets after the window period. All executions within the window count * towards the limit, and the window resets completely after the period. * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more * consistent rate of execution over time. * - * Unlike the non-async RateLimiter, this async version supports returning values from the rate-limited function, - * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call - * instead of setting the result on a state variable from within the rate-limited function. - * - * For smoother execution patterns, consider using: - * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms) - * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls) - * + * When to Use Rate Limiting: * Rate limiting is best used for hard API limits or resource constraints. For UI updates or * smoothing out frequent events, throttling or debouncing usually provide better user experience. + * - Throttling: Ensures consistent spacing between executions (e.g. max once per 200ms) + * - Debouncing: Waits for a pause in calls before executing (e.g. after 500ms of no calls) * * State Management: * - Uses TanStack Store for reactive state management @@ -219,6 +236,7 @@ export class AsyncRateLimiter { >(getDefaultAsyncRateLimiterState()) key: string options: AsyncRateLimiterOptions + asyncRetryers = new Map>() #timeoutIds: Set = new Set() constructor( @@ -243,6 +261,11 @@ export class AsyncRateLimiter { }) } + /** + * Emits a change event for the async rate limiter instance. Mostly useful for devtools. + */ + _emit = () => emitChange('AsyncRateLimiter', this) + /** * Updates the async rate limiter options */ @@ -347,6 +370,7 @@ export class AsyncRateLimiter { ): Promise | undefined> => { if (!this.#getEnabled()) return + const currentMaybeExecute = this.store.state.maybeExecuteCount const now = Date.now() const executionTimes = [...this.store.state.executionTimes, now] this.#setState({ @@ -355,22 +379,29 @@ export class AsyncRateLimiter { }) try { - const result = await this.fn(...args) // EXECUTE! + // Create a new AsyncRetryer for this execution to avoid cancelling concurrent executions + const currentAsyncRetryer = new AsyncRetryer(this.fn, { + ...this.options.asyncRetryerOptions, + key: `${this.key}-retryer-${currentMaybeExecute}`, + }) + this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer) + const result = await currentAsyncRetryer.execute(...args) // EXECUTE! this.#setCleanupTimeout(now) this.#setState({ successCount: this.store.state.successCount + 1, lastResult: result, }) - this.options.onSuccess?.(result, args, this) + this.options.onSuccess?.(result as ReturnType, args, this) } catch (error) { this.#setState({ errorCount: this.store.state.errorCount + 1, }) - this.options.onError?.(error, args, this) + this.options.onError?.(error as Error, args, this) if (this.options.throwOnError) { throw error } } finally { + this.asyncRetryers.delete(currentMaybeExecute) // dispose retryer this.#setState({ isExecuting: false, settleCount: this.store.state.settleCount + 1, @@ -462,6 +493,44 @@ export class AsyncRateLimiter { return oldestExecution + this.#getWindow() - Date.now() } + /** + * Returns the AbortSignal for a specific execution. + * If no maybeExecuteCount is provided, returns the signal for the most recent execution. + * Returns null if no execution is found or not currently executing. + * + * @param maybeExecuteCount - Optional specific execution to get signal for + * @example + * ```typescript + * const rateLimiter = new AsyncRateLimiter( + * async (userId: string) => { + * const signal = rateLimiter.getAbortSignal() + * if (signal) { + * const response = await fetch(`/api/users/${userId}`, { signal }) + * return response.json() + * } + * }, + * { limit: 5, window: 1000 } + * ) + * ``` + */ + getAbortSignal(maybeExecuteCount?: number): AbortSignal | null { + const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount + const retryer = this.asyncRetryers.get(count) + return retryer?.getAbortSignal() ?? null + } + + /** + * Aborts all ongoing executions with the internal abort controllers. + * Does NOT clear out the execution times or reset the rate limiter. + */ + abort = (): void => { + this.asyncRetryers.forEach((retryer) => retryer.abort()) + this.asyncRetryers.clear() + this.#setState({ + isExecuting: false, + }) + } + /** * Resets the rate limiter state */ @@ -474,21 +543,51 @@ export class AsyncRateLimiter { /** * Creates an async rate-limited function that will execute the provided function up to a maximum number of times within a time window. * - * Unlike the non-async rate limiter, this async version supports returning values from the rate-limited function, - * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call - * instead of setting the result on a state variable from within the rate-limited function. + * Async vs Sync Versions: + * The async version provides advanced features over the sync rate limit function: + * - Returns promises that can be awaited for rate-limited function results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight executions + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts, rejection counts) + * - More sophisticated window management with automatic cleanup + * + * The sync rate limit function is lighter weight and simpler when you don't need async features, + * return values, or execution control. * - * The rate limiter supports two types of windows: + * What is Rate Limiting? + * Rate limiting allows a function to execute up to a limit within a time window, + * then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where + * all executions happen immediately, followed by a complete block. + * + * Window Types: * - 'fixed': A strict window that resets after the window period. All executions within the window count * towards the limit, and the window resets completely after the period. * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more * consistent rate of execution over time. * - * Note that rate limiting is a simpler form of execution control compared to throttling or debouncing: + * Configuration Options: + * - `limit`: Maximum number of executions allowed within the window (required) + * - `window`: Time window in milliseconds (required) + * - `windowType`: 'fixed' or 'sliding' (default: 'fixed') + * - `enabled`: Whether the rate limiter is enabled (default: true) + * - `asyncRetryerOptions`: Configure retry behavior for executions + * + * When to Use Rate Limiting: + * Rate limiting is best used for hard API limits or resource constraints. For UI updates or + * smoothing out frequent events, throttling or debouncing usually provide better user experience. * - A rate limiter will allow all executions until the limit is reached, then block all subsequent calls until the window resets * - A throttler ensures even spacing between executions, which can be better for consistent performance * - A debouncer collapses multiple calls into one, which is better for handling bursts of events * + * Error Handling: + * - If an `onError` handler is provided, it will be called with the error and rate limiter instance + * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown + * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed + * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown + * - The error state can be checked using the underlying AsyncRateLimiter instance + * - Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler + * * State Management: * - Uses TanStack Store for reactive state management * - Use `initialState` to provide initial state values when creating the rate limiter @@ -501,17 +600,6 @@ export class AsyncRateLimiter { * - State can be accessed via the underlying AsyncRateLimiter instance's `store.state` property * - When using framework adapters (React/Solid), state is accessed from the hook's state property * - * Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically - * need to enforce a hard limit on the number of executions within a time period. - * - * Error Handling: - * - If an `onError` handler is provided, it will be called with the error and rate limiter instance - * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown - * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed - * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown - * - The error state can be checked using the underlying AsyncRateLimiter instance - * - Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler - * * @example * ```ts * // Rate limit to 5 calls per minute with a sliding window diff --git a/packages/pacer/src/async-retryer.ts b/packages/pacer/src/async-retryer.ts new file mode 100644 index 00000000..e48e4e4b --- /dev/null +++ b/packages/pacer/src/async-retryer.ts @@ -0,0 +1,602 @@ +import { Store } from '@tanstack/store' +import { createKey, parseFunctionOrValue } from './utils' +import { emitChange, pacerEventClient } from './event-client' +import type { AnyAsyncFunction } from './types' + +export interface AsyncRetryerState { + /** + * The current retry attempt number (0 when not executing) + */ + currentAttempt: number + /** + * Total number of completed executions (successful or failed) + */ + executionCount: number + /** + * Whether the retryer is currently executing the function + */ + isExecuting: boolean + /** + * The most recent error encountered during execution + */ + lastError: Error | undefined + /** + * Timestamp of the last execution completion in milliseconds + */ + lastExecutionTime: number + /** + * The result from the most recent successful execution + */ + lastResult: ReturnType | undefined + /** + * Current execution status - 'disabled' when not enabled, 'idle' when ready, 'executing' when running + */ + status: 'disabled' | 'idle' | 'executing' | 'retrying' + /** + * Total time spent executing (including retries) in milliseconds + */ + totalExecutionTime: number +} + +/** + * Creates the default initial state for an AsyncRetryer instance + * @returns The default state with all values reset to initial values + */ +function getDefaultAsyncRetryerState< + TFn extends AnyAsyncFunction, +>(): AsyncRetryerState { + return structuredClone({ + currentAttempt: 0, + executionCount: 0, + isExecuting: false, + lastError: undefined, + lastExecutionTime: 0, + lastResult: undefined, + status: 'idle', + totalExecutionTime: 0, + }) +} + +export interface AsyncRetryerOptions { + /** + * The backoff strategy for retry delays: + * - 'exponential': Wait time doubles with each attempt (1s, 2s, 4s, ...) + * - 'linear': Wait time increases linearly (1s, 2s, 3s, ...) + * - 'fixed': Same wait time for all attempts + * @default 'exponential' + */ + backoff?: 'linear' | 'exponential' | 'fixed' + /** + * Base wait time in milliseconds between retries, or a function that returns the wait time + * @default 1000 + */ + baseWait?: number | ((retryer: AsyncRetryer) => number) + /** + * Whether the retryer is enabled, or a function that determines if it's enabled + * @default true + */ + enabled?: boolean | ((retryer: AsyncRetryer) => boolean) + /** + * Initial state to merge with the default state + */ + initialState?: Partial> + /** + * Optional key to identify this async retryer instance. + * If provided, the async retryer will be identified by this key in the devtools and PacerProvider if applicable. + */ + key?: string + /** + * Jitter percentage to add to retry delays (0-1). Adds randomness to prevent thundering herd. + * @default 0 + */ + jitter?: number + /** + * Maximum execution time in milliseconds for a single function call before aborting + * @default Infinity + */ + maxExecutionTime?: number + /** + * Maximum total execution time in milliseconds for the entire retry operation before aborting + * @default Infinity + */ + maxTotalExecutionTime?: number + /** + * Maximum number of retry attempts, or a function that returns the max attempts + * @default 3 + */ + maxAttempts?: number | ((retryer: AsyncRetryer) => number) + /** + * Callback invoked when any error occurs during execution (including retries) + */ + onError?: ( + error: Error, + args: Parameters, + retryer: AsyncRetryer, + ) => void + /** + * Callback invoked when the final error occurs after all retries are exhausted + */ + onLastError?: (error: Error, retryer: AsyncRetryer) => void + /** + * Callback invoked before each retry attempt + */ + onRetry?: (attempt: number, error: Error, retryer: AsyncRetryer) => void + /** + * Callback invoked after execution completes (success or failure) + */ + onSettled?: (args: Parameters, retryer: AsyncRetryer) => void + /** + * Callback invoked when execution succeeds + */ + onSuccess?: ( + result: ReturnType, + args: Parameters, + retryer: AsyncRetryer, + ) => void + /** + * Controls when errors are thrown: + * - 'last': Only throw the final error after all retries are exhausted + * - true: Throw every error immediately (disables retrying) + * - false: Never throw errors, return undefined instead + * @default 'last' + */ + throwOnError?: boolean | 'last' +} + +const defaultOptions: Omit< + Required>, + | 'initialState' + | 'key' + | 'onError' + | 'onLastError' + | 'onRetry' + | 'onSettled' + | 'onSuccess' +> = { + backoff: 'exponential', + baseWait: 1000, + enabled: true, + jitter: 0, + maxAttempts: 3, + maxExecutionTime: Infinity, + maxTotalExecutionTime: Infinity, + throwOnError: 'last', +} + +/** + * Provides robust retry functionality for asynchronous functions, supporting configurable backoff strategies, + * attempt limits, timeout controls, and detailed state management. The AsyncRetryer class is designed to help you reliably + * execute async operations that may fail intermittently, such as network requests or database operations, + * by automatically retrying them according to your chosen policy. + * + * ## Retrying Concepts + * + * - **Retrying**: Automatically re-executes a failed async function up to a specified number of attempts. + * Useful for handling transient errors (e.g., network flakiness, rate limits, temporary server issues). + * - **Backoff Strategies**: Controls the delay between retry attempts (default: `'exponential'`): + * - `'exponential'`: Wait time doubles with each attempt (1s, 2s, 4s, ...) - **DEFAULT** + * - `'linear'`: Wait time increases linearly (1s, 2s, 3s, ...) + * - `'fixed'`: Waits a constant amount of time (`baseWait`) between each attempt + * - **Jitter**: Adds randomness to retry delays to prevent thundering herd problems (default: `0`). + * Set to a value between 0-1 to apply that percentage of random variation to each delay. + * - **Timeout Controls**: Set limits on execution time to prevent hanging operations: + * - `maxExecutionTime`: Maximum time for a single function call (default: `Infinity`) + * - `maxTotalExecutionTime`: Maximum time for the entire retry operation (default: `Infinity`) + * - **Abort & Cancellation**: Supports cancellation via an internal `AbortController`. Call `abort()` to stop retries. + * + * ## State Management + * + * Uses TanStack Store for fine-grained reactivity. State can be accessed via the `store.state` property. + * + * Available state properties: + * - `currentAttempt`: The current retry attempt number (0 when not executing) + * - `executionCount`: Total number of completed executions (successful or failed) + * - `isExecuting`: Whether the retryer is currently executing the function + * - `lastError`: The most recent error encountered during execution + * - `lastExecutionTime`: Timestamp of the last execution completion in milliseconds + * - `lastResult`: The result from the most recent successful execution + * - `status`: Current execution status ('disabled' | 'idle' | 'executing' | 'retrying') + * - `totalExecutionTime`: Total time spent executing (including retries) in milliseconds + * + * ## Error Handling + * + * The `throwOnError` option controls when errors are thrown (default: `'last'`): + * - `'last'`: Only throws the final error after all retries are exhausted - **DEFAULT** + * - `true`: Throws every error immediately (disables retrying) + * - `false`: Never throws errors, returns `undefined` instead + * + * Callbacks for error handling: + * - `onError`: Called for every error (including during retries) + * - `onLastError`: Called only for the final error after all retries fail + * - `onRetry`: Called before each retry attempt + * - `onSuccess`: Called when execution succeeds + * - `onSettled`: Called after execution completes (success or failure) + * + * ## Usage + * + * - Use for async operations that may fail transiently and benefit from retrying. + * - Configure `maxAttempts`, `backoff`, `baseWait`, and `jitter` to control retry behavior. + * - Set `maxExecutionTime` and `maxTotalExecutionTime` to prevent hanging operations. + * - Use `onRetry`, `onSuccess`, `onError`, `onLastError`, and `onSettled` for custom side effects. + * - Call `abort()` to cancel ongoing execution and pending retries. + * - Call `reset()` to reset state and cancel execution. + * + * @example + * ```typescript + * // Retry a fetch operation up to 5 times with exponential backoff, jitter, and timeouts + * const retryer = new AsyncRetryer(fetchData, { + * maxAttempts: 5, + * backoff: 'exponential', + * baseWait: 1000, + * jitter: 0.1, // Add 10% random variation to prevent thundering herd + * maxExecutionTime: 5000, // Abort individual calls after 5 seconds + * maxTotalExecutionTime: 30000, // Abort entire operation after 30 seconds + * onRetry: (attempt, error) => console.log(`Retry attempt ${attempt} after error:`, error), + * onSuccess: (result) => console.log('Success:', result), + * onError: (error) => console.error('Error:', error), + * onLastError: (error) => console.error('All retries failed:', error), + * }) + * + * const result = await retryer.execute(userId) + * ``` + * + * @template TFn The async function type to be retried. + */ +export class AsyncRetryer { + readonly store: Store>> = new Store( + getDefaultAsyncRetryerState(), + ) + key: string + options: AsyncRetryerOptions & typeof defaultOptions + #abortController: AbortController | null = null + + /** + * Creates a new AsyncRetryer instance + * @param fn The async function to retry + * @param initialOptions Configuration options for the retryer + */ + constructor( + public fn: TFn, + initialOptions: AsyncRetryerOptions = {}, + ) { + this.key = createKey(initialOptions.key) + this.options = { + ...defaultOptions, + ...initialOptions, + throwOnError: + initialOptions.throwOnError ?? + (initialOptions.onError ? false : defaultOptions.throwOnError), + } + this.#setState(this.options.initialState ?? {}) + + pacerEventClient.on('d-AsyncRetryer', (event) => { + if (event.payload.key !== this.key) return + this.#setState(event.payload.store.state as AsyncRetryerState) + this.setOptions(event.payload.options) + }) + } + + /** + * Emits a change event for the async retryer instance. Mostly useful for devtools. + */ + _emit = () => emitChange('AsyncRetryer', this) + + /** + * Updates the retryer options + * @param newOptions Partial options to merge with existing options + */ + setOptions = (newOptions: Partial>): void => { + this.options = { ...this.options, ...newOptions } + } + + #setState = (newState: Partial>): void => { + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + const { isExecuting, currentAttempt } = combinedState + return { + ...combinedState, + status: !this.#getEnabled() + ? 'disabled' + : isExecuting && currentAttempt === 1 + ? 'executing' + : isExecuting && currentAttempt > 1 + ? 'retrying' + : 'idle', + } + }) + emitChange('AsyncRetryer', this) + } + + #getEnabled = (): boolean => { + return !!parseFunctionOrValue(this.options.enabled, this) + } + + #getMaxAttempts = (): number => { + return parseFunctionOrValue(this.options.maxAttempts, this) + } + + #getBaseWait = (): number => { + return parseFunctionOrValue(this.options.baseWait, this) + } + + #calculateJitter = (waitTime: number): number => { + const jitterAmount = this.options.jitter + if (jitterAmount <= 0) return 0 + + try { + const crypto = + typeof globalThis !== 'undefined' ? globalThis.crypto : undefined + if (crypto?.getRandomValues) { + const array = new Uint32Array(1) + crypto.getRandomValues(array) + // Convert to 0-1 range and apply jitter percentage + const randomFactor = (array[0]! / 0xffffffff) * 2 - 1 // -1 to 1 + return Math.floor(waitTime * jitterAmount * randomFactor) + } + } catch { + // No crypto available + } + return 0 + } + + #calculateWait = (attempt: number): number => { + const baseWait = this.#getBaseWait() + let waitTime: number + + switch (this.options.backoff) { + case 'linear': + waitTime = baseWait * attempt + break + case 'exponential': + waitTime = baseWait * Math.pow(2, attempt - 1) + break + case 'fixed': + default: + waitTime = baseWait + break + } + + const jitter = this.#calculateJitter(waitTime) + return Math.max(0, waitTime + jitter) + } + + /** + * Executes the function with retry logic + * @param args Arguments to pass to the function + * @returns The function result, or undefined if disabled or all retries failed (when throwOnError is false) + * @throws The last error if throwOnError is true and all retries fail + */ + execute = async ( + ...args: Parameters + ): Promise | undefined> => { + if (!this.#getEnabled()) { + return undefined + } + + // Cancel any existing execution + this.abort() + + const startTime = Date.now() + let lastError: Error | undefined + let result: ReturnType | undefined + + this.#abortController = new AbortController() + const signal = this.#abortController.signal + + this.#setState({ + isExecuting: true, + currentAttempt: 0, + lastError: undefined, + }) + + // Set up total execution timeout + let totalTimeoutId: NodeJS.Timeout | undefined + if (this.options.maxTotalExecutionTime !== Infinity) { + totalTimeoutId = setTimeout(() => { + this.abort() + }, this.options.maxTotalExecutionTime) + } + + let isLastAttempt = false + for (let attempt = 1; attempt <= this.#getMaxAttempts(); attempt++) { + isLastAttempt = attempt === this.#getMaxAttempts() + this.#setState({ currentAttempt: attempt }) + + try { + if (signal.aborted) { + return undefined + } + + // Check if total execution time has been exceeded + const currentTotalTime = Date.now() - startTime + if ( + this.options.maxTotalExecutionTime !== Infinity && + currentTotalTime >= this.options.maxTotalExecutionTime + ) { + this.abort() + return undefined + } + + // Execute with individual timeout if specified + if (this.options.maxExecutionTime === Infinity) { + result = (await this.fn(...args)) as ReturnType + } else { + result = (await Promise.race([ + this.fn(...args), + new Promise((_, reject) => { + const timeout = setTimeout(() => { + reject( + new Error( + `Execution timeout: ${this.options.maxExecutionTime}ms exceeded`, + ), + ) + }, this.options.maxExecutionTime) + + signal.addEventListener( + 'abort', + () => { + clearTimeout(timeout) + reject(new Error('Aborted')) + }, + { once: true }, + ) + }), + ])) as ReturnType + } + + // Check if cancelled during execution + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (signal.aborted) { + return undefined + } + + const totalTime = Date.now() - startTime + this.#setState({ + executionCount: this.store.state.executionCount + 1, + isExecuting: false, + lastExecutionTime: Date.now(), + totalExecutionTime: totalTime, + currentAttempt: 0, + lastResult: result, + }) + + this.options.onSuccess?.(result, args, this) + + return result + } catch (error) { + // Treat abort as a non-error cancellation outcome + if ( + error && + typeof error === 'object' && + 'name' in error && + (error as Error).name === 'AbortError' + ) { + return undefined + } + lastError = error instanceof Error ? error : new Error(String(error)) + this.#setState({ lastError }) + + if (attempt < this.#getMaxAttempts()) { + this.options.onRetry?.(attempt, lastError, this) + + const wait = this.#calculateWait(attempt) + if (wait > 0) { + // Eagerly reflect retrying status during the wait window + this.#setState({ isExecuting: true, currentAttempt: attempt + 1 }) + await new Promise((resolve) => { + const timeout = setTimeout(() => { + signal.removeEventListener('abort', onAbort) + resolve() + }, wait) + const onAbort = () => { + clearTimeout(timeout) + signal.removeEventListener('abort', onAbort) + resolve() + } + signal.addEventListener('abort', onAbort) + }) + if (signal.aborted) { + // When cancelled during retry wait, surface the last error exactly once + this.options.onError?.(lastError, args, this) + return undefined + } + } + } + } finally { + this.options.onSettled?.(args, this) + } + } + + // Clean up total timeout + if (totalTimeoutId) { + clearTimeout(totalTimeoutId) + } + + // Exhausted retries - finalize state + this.#setState({ isExecuting: false }) + this.options.onLastError?.(lastError as Error, this) + this.options.onError?.(lastError as Error, args, this) + this.options.onSettled?.(args, this) + + if ( + (this.options.throwOnError === 'last' && isLastAttempt) || + this.options.throwOnError === true + ) { + throw lastError + } + + return undefined + } + + /** + * Returns the current AbortSignal for the executing operation. + * Use this signal in your async function to make it cancellable. + * Returns null when not currently executing. + * + * @example + * ```typescript + * const retryer = new AsyncRetryer(async (userId: string) => { + * const signal = retryer.getAbortSignal() + * if (signal) { + * return fetch(`/api/users/${userId}`, { signal }) + * } + * return fetch(`/api/users/${userId}`) + * }) + * + * // Abort will now actually cancel the fetch + * retryer.abort() + * ``` + */ + getAbortSignal(): AbortSignal | null { + return this.#abortController?.signal ?? null + } + + /** + * Cancels the current execution and any pending retries + */ + abort = (): void => { + if (this.#abortController) { + this.#abortController.abort() + this.#abortController = null + this.#setState({ + isExecuting: false, + }) + } + } + + /** + * Resets the retryer to its initial state and cancels any ongoing execution + */ + reset = (): void => { + this.abort() + this.#setState(getDefaultAsyncRetryerState()) + } +} + +/** + * Creates a retry-enabled version of an async function + * + * @param fn The async function to add retry functionality to + * @param initialOptions Configuration options for the retry behavior + * @returns A new function that executes the original with retry logic + * + * @example + * ```typescript + * const retryFetch = asyncRetry(fetch, { + * maxAttempts: 3, + * backoff: 'exponential' // default + * }) + * + * const response = await retryFetch('/api/data') + * ``` + */ +export function asyncRetry( + fn: TFn, + initialOptions: AsyncRetryerOptions = {}, +): (...args: Parameters) => Promise | undefined> { + const retryer = new AsyncRetryer(fn, initialOptions) + return retryer.execute +} diff --git a/packages/pacer/src/async-throttler.ts b/packages/pacer/src/async-throttler.ts index adbe3cd5..08dd6d4e 100644 --- a/packages/pacer/src/async-throttler.ts +++ b/packages/pacer/src/async-throttler.ts @@ -1,6 +1,8 @@ import { Store } from '@tanstack/store' +import { AsyncRetryer } from './async-retryer' import { createKey, parseFunctionOrValue } from './utils' import { emitChange, pacerEventClient } from './event-client' +import type { AsyncRetryerOptions } from './async-retryer' import type { AnyAsyncFunction, OptionalKeys } from './types' export interface AsyncThrottlerState { @@ -72,6 +74,10 @@ function getDefaultAsyncThrottlerState< * Options for configuring an async throttled function */ export interface AsyncThrottlerOptions { + /** + * Options for configuring the underlying async retryer + */ + asyncRetryerOptions?: AsyncRetryerOptions /** * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions. * Can be a boolean or a function that returns a boolean. @@ -98,7 +104,7 @@ export interface AsyncThrottlerOptions { * This can be used alongside throwOnError - the handler will be called before any error is thrown. */ onError?: ( - error: unknown, + error: Error, args: Parameters, asyncThrottler: AsyncThrottler, ) => void @@ -142,6 +148,9 @@ type AsyncThrottlerOptionsWithOptionalCallbacks = OptionalKeys< > const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = { + asyncRetryerOptions: { + maxAttempts: 1, + }, enabled: true, leading: true, trailing: true, @@ -151,14 +160,24 @@ const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = { /** * A class that creates an async throttled function. * + * Async vs Sync Versions: + * The async version provides advanced features over the sync Throttler: + * - Returns promises that can be awaited for throttled function results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight executions + * - Cancel support to prevent pending executions from starting + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) + * - Waits for ongoing executions to complete before scheduling the next one + * + * The sync Throttler is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Throttling? * Throttling limits how often a function can be executed, allowing only one execution within a specified time window. * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a * regular interval regardless of how often it's called. * - * Unlike the non-async Throttler, this async version supports returning values from the throttled function, - * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call - * instead of setting the result on a state variable from within the throttled function. - * * This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to * ensure a maximum execution frequency. * @@ -202,7 +221,7 @@ export class AsyncThrottler { >(getDefaultAsyncThrottlerState()) key: string options: AsyncThrottlerOptions - #abortController: AbortController | null = null + asyncRetryers = new Map>() #timeoutId: NodeJS.Timeout | null = null #resolvePreviousPromise: | ((value?: ReturnType | undefined) => void) @@ -219,6 +238,7 @@ export class AsyncThrottler { throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } this.#setState(this.options.initialState ?? {}) + pacerEventClient.on('d-AsyncThrottler', (event) => { if (event.payload.key !== this.key) return this.#setState(event.payload.store.state as AsyncThrottlerState) @@ -226,6 +246,11 @@ export class AsyncThrottler { }) } + /** + * Emits a change event for the async throttler instance. Mostly useful for devtools. + */ + _emit = () => emitChange('AsyncThrottler', this) + /** * Updates the async throttler options */ @@ -301,88 +326,119 @@ export class AsyncThrottler { ...args: Parameters ): Promise | undefined> => { if (!this.#getEnabled()) return undefined - const now = Date.now() - const timeSinceLastExecution = now - this.store.state.lastExecutionTime - const wait = this.#getWait() - // Store the most recent arguments for potential trailing execution + + this.#resolvePreviousPromiseInternal() + this.#setState({ - lastArgs: args, maybeExecuteCount: this.store.state.maybeExecuteCount + 1, + lastArgs: args, // store the arguments for potential trailing execution }) - this.#resolvePreviousPromiseInternal() + const wait = this.#getWait() + const thisMaybeExecuteNumber = this.store.state.maybeExecuteCount + + // Wait for the wait period for the previous execution to complete if it's still running + for ( + let maxNumIterations = wait / 10; + this.store.state.isExecuting && maxNumIterations > 0; + maxNumIterations-- + ) { + await new Promise((resolve) => setTimeout(resolve, 10)) + if (this.store.state.maybeExecuteCount !== thisMaybeExecuteNumber) { + // cancel the current maybeExecute loop because a new maybeExecute call was made + return this.store.state.lastResult + } + } + + const now = Date.now() + const timeSinceLastExecution = now - this.store.state.lastExecutionTime + + if ( + this.options.leading && + !this.store.state.isPending && + timeSinceLastExecution >= wait + ) { + await this.#execute(...args) // Leading EXECUTE! + } else if (this.options.trailing) { + // replace old pending execution with a new one + this.cancel() + this.#setState({ + isPending: true, + }) - // Handle leading execution - if (this.options.leading && timeSinceLastExecution >= wait) { - await this.#execute(...args) - return this.store.state.lastResult - } else { + // Set up new trailing execution return new Promise((resolve, reject) => { this.#resolvePreviousPromise = resolve - // Clear any existing timeout to ensure we use the latest arguments - this.#clearTimeout() - - // Set up trailing execution if enabled - if (this.options.trailing) { - const _timeSinceLastExecution = this.store.state.lastExecutionTime - ? now - this.store.state.lastExecutionTime - : 0 - const timeoutDuration = wait - _timeSinceLastExecution - this.#setState({ isPending: true }) - this.#timeoutId = setTimeout(async () => { - if (this.store.state.lastArgs !== undefined) { - try { - await this.#execute(...this.store.state.lastArgs) // EXECUTE! - } catch (error) { - reject(error) - } + + const newTimeSinceLastExecution = this.store.state.lastExecutionTime + ? now - this.store.state.lastExecutionTime + : 0 + const timeoutDuration = Math.max(0, wait - newTimeSinceLastExecution) + + this.#timeoutId = setTimeout(async () => { + this.#clearTimeout() + if (this.store.state.lastArgs !== undefined) { + try { + await this.#execute(...this.store.state.lastArgs) // Trailing EXECUTE! + } catch (error) { + reject(error) } - this.#resolvePreviousPromise = null - resolve(this.store.state.lastResult) - }, timeoutDuration) - } + } + this.#resolvePreviousPromise = null + resolve(this.store.state.lastResult) + }, timeoutDuration) }) } + return this.store.state.lastResult } #execute = async ( ...args: Parameters ): Promise | undefined> => { - if (!this.#getEnabled() || this.store.state.isExecuting) return undefined - this.#abortController = new AbortController() + if (!this.#getEnabled()) return undefined + + const currentMaybeExecute = this.store.state.maybeExecuteCount + try { this.#setState({ isExecuting: true }) - const result = await this.fn(...args) // EXECUTE! + const currentAsyncRetryer = new AsyncRetryer(this.fn, { + ...this.options.asyncRetryerOptions, + key: `${this.key}-retryer-${currentMaybeExecute}`, + }) + this.asyncRetryers.set(currentMaybeExecute, currentAsyncRetryer) + const result = await currentAsyncRetryer.execute(...args) // EXECUTE! this.#setState({ lastResult: result, successCount: this.store.state.successCount + 1, }) - this.options.onSuccess?.(result, args, this) + this.options.onSuccess?.(result as ReturnType, args, this) } catch (error) { this.#setState({ errorCount: this.store.state.errorCount + 1, }) - this.options.onError?.(error, args, this) + this.options.onError?.(error as Error, args, this) if (this.options.throwOnError) { throw error } } finally { + this.asyncRetryers.delete(currentMaybeExecute) // dispose retryer const lastExecutionTime = Date.now() - const nextExecutionTime = lastExecutionTime + this.#getWait() + const wait = this.#getWait() + const nextExecutionTime = lastExecutionTime + wait this.#setState({ isExecuting: false, - isPending: false, + isPending: !!this.#timeoutId, settleCount: this.store.state.settleCount + 1, lastExecutionTime, nextExecutionTime, }) - this.#abortController = null this.options.onSettled?.(args, this) setTimeout(() => { if (!this.store.state.isPending) { + // clear nextExecutionTime if there is no pending execution this.#setState({ nextExecutionTime: undefined }) } - }, this.#getWait()) + }, wait) } return this.store.state.lastResult } @@ -392,12 +448,21 @@ export class AsyncThrottler { */ flush = async (): Promise | undefined> => { if (this.store.state.isPending && this.store.state.lastArgs) { - this.#abortExecution() // abort any current execution - this.#clearTimeout() // clear any existing timeout + // Store the pending promise resolver before clearing timeout + const resolvePromise = this.#resolvePreviousPromise + + // Clear timeout and state without resolving the promise + this.#clearTimeout() + this.#setState({ + isPending: false, + }) + const result = await this.#execute(...this.store.state.lastArgs) - // Resolve any pending promise from maybeExecute - this.#resolvePreviousPromiseInternal() + // Resolve the pending promise with the result + if (resolvePromise) { + resolvePromise(result) + } return result } @@ -418,7 +483,51 @@ export class AsyncThrottler { } } - #cancelPendingExecution = (): void => { + /** + * Returns the AbortSignal for a specific execution. + * If no maybeExecuteCount is provided, returns the signal for the most recent execution. + * Returns null if no execution is found or not currently executing. + * + * @param maybeExecuteCount - Optional specific execution to get signal for + * @example + * ```typescript + * const throttler = new AsyncThrottler( + * async (data: string) => { + * const signal = throttler.getAbortSignal() + * if (signal) { + * const response = await fetch('/api/save', { + * method: 'POST', + * body: data, + * signal + * }) + * return response.json() + * } + * }, + * { wait: 1000 } + * ) + * ``` + */ + getAbortSignal(maybeExecuteCount?: number): AbortSignal | null { + const count = maybeExecuteCount ?? this.store.state.maybeExecuteCount + const retryer = this.asyncRetryers.get(count) + return retryer?.getAbortSignal() ?? null + } + + /** + * Aborts all ongoing executions with the internal abort controllers. + * Does NOT cancel any pending execution that have not started yet. + */ + abort = (): void => { + this.asyncRetryers.forEach((retryer) => retryer.abort()) + this.asyncRetryers.clear() + this.#setState({ isExecuting: false }) + } + + /** + * Cancels any pending execution that have not started yet. + * Does NOT abort any execution already in progress. + */ + cancel = (): void => { this.#clearTimeout() if (this.#resolvePreviousPromise) { this.#resolvePreviousPromiseInternal() @@ -426,26 +535,9 @@ export class AsyncThrottler { } this.#setState({ isPending: false, - isExecuting: false, - lastArgs: undefined, }) } - #abortExecution = (): void => { - if (this.#abortController) { - this.#abortController.abort() - this.#abortController = null - } - } - - /** - * Cancels any pending execution or aborts any execution in progress - */ - cancel = (): void => { - this.#cancelPendingExecution() - this.#abortExecution() - } - /** * Resets the debouncer state to its default values */ @@ -459,9 +551,30 @@ export class AsyncThrottler { * The throttled function will execute at most once per wait period, even if called multiple times. * If called while executing, it will wait until execution completes before scheduling the next call. * - * Unlike the non-async Throttler, this async version supports returning values from the throttled function, - * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call - * instead of setting the result on a state variable from within the throttled function. + * Async vs Sync Versions: + * The async version provides advanced features over the sync throttle function: + * - Returns promises that can be awaited for throttled function results + * - Built-in retry support via AsyncRetryer integration + * - Abort support to cancel in-flight executions + * - Cancel support to prevent pending executions from starting + * - Comprehensive error handling with onError callbacks and throwOnError control + * - Detailed execution tracking (success/error/settle counts) + * - Waits for ongoing executions to complete before scheduling the next one + * + * The sync throttle function is lighter weight and simpler when you don't need async features, + * return values, or execution control. + * + * What is Throttling? + * Throttling limits how often a function can be executed, allowing only one execution within a specified time window. + * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a + * regular interval regardless of how often it's called. + * + * Configuration Options: + * - `wait`: Time window in milliseconds during which the function can only execute once (required) + * - `leading`: Execute immediately when called (default: true) + * - `trailing`: Execute on the trailing edge of the wait period (default: true) + * - `enabled`: Whether the throttler is enabled (default: true) + * - `asyncRetryerOptions`: Configure retry behavior for executions * * Error Handling: * - If an `onError` handler is provided, it will be called with the error and throttler instance diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index 9308cf30..681e6daf 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -107,6 +107,7 @@ const defaultOptions: BatcherOptionsWithOptionalCallbacks = { * A class that collects items and processes them in batches. * * Batching is a technique for grouping multiple operations together to be processed as a single unit. + * This synchronous version is lighter weight and often all you need - upgrade to AsyncBatcher when you need promises, retry support, abort/cancel capabilities, or advanced error handling. * * The Batcher provides a flexible way to implement batching with configurable: * - Maximum batch size (number of items per batch) @@ -167,6 +168,11 @@ export class Batcher { }) } + /** + * Emits a change event for the batcher instance. Mostly useful for devtools. + */ + _emit = () => emitChange('Batcher', this) + /** * Updates the batcher options */ @@ -275,6 +281,15 @@ export class Batcher { this.#setState({ items: [], isPending: false }) } + /** + * Cancels any pending execution that was scheduled. + * Does NOT clear out the items. + */ + cancel = (): void => { + this.#clearTimeout() + this.#setState({ isPending: false }) + } + /** * Resets the batcher state to its default values */ @@ -285,7 +300,9 @@ export class Batcher { } /** - * Creates a batcher that processes items in batches + * Creates a batcher that processes items in batches. + * + * This synchronous version is lighter weight and often all you need - upgrade to asyncBatch when you need promises, retry support, abort/cancel capabilities, or advanced error handling. * * @example * ```ts diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index e91fa626..b7008146 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -101,6 +101,7 @@ const defaultOptions: Omit< * Debouncing ensures that a function is only executed after a certain amount of time has passed * since its last invocation. This is useful for handling frequent events like window resizing, * scroll events, or input changes where you want to limit the rate of execution. + * This synchronous version is lighter weight and often all you need - upgrade to AsyncDebouncer when you need promises, retry support, abort/cancel capabilities, or advanced error handling. * * The debounced function can be configured to execute either at the start of the delay period * (leading edge) or at the end (trailing edge, default). Each new call during the wait period @@ -152,6 +153,11 @@ export class Debouncer { }) } + /** + * Emits a change event for the debouncer instance. Mostly useful for devtools. + */ + _emit = () => emitChange('Debouncer', this) + /** * Updates the debouncer options */ @@ -285,8 +291,7 @@ export class Debouncer { * Creates a debounced function that delays invoking the provided function until after a specified wait time. * Multiple calls during the wait period will cancel previous pending invocations and reset the timer. * - * This the the simple function wrapper implementation pulled from the Debouncer class. If you need - * more control over the debouncing behavior, use the Debouncer class directly. + * This synchronous version is lighter weight and often all you need - upgrade to asyncDebounce when you need promises, retry support, abort/cancel capabilities, or advanced error handling. * * If leading option is true, the function will execute immediately on the first call, then wait the delay * before allowing another execution. diff --git a/packages/pacer/src/event-client.ts b/packages/pacer/src/event-client.ts index 341cf1f3..255ebcd9 100644 --- a/packages/pacer/src/event-client.ts +++ b/packages/pacer/src/event-client.ts @@ -3,6 +3,7 @@ import type { AsyncBatcher } from './async-batcher' import type { AsyncDebouncer } from './async-debouncer' import type { AsyncQueuer } from './async-queuer' import type { AsyncRateLimiter } from './async-rate-limiter' +import type { AsyncRetryer } from './async-retryer' import type { AsyncThrottler } from './async-throttler' import type { Debouncer } from './debouncer' import type { Batcher } from './batcher' @@ -15,6 +16,7 @@ export interface PacerEventMap { 'pacer:d-AsyncDebouncer': AsyncDebouncer 'pacer:d-AsyncQueuer': AsyncQueuer 'pacer:d-AsyncRateLimiter': AsyncRateLimiter + 'pacer:d-AsyncRetryer': AsyncRetryer 'pacer:d-AsyncThrottler': AsyncThrottler 'pacer:d-Batcher': Batcher 'pacer:d-Debouncer': Debouncer @@ -25,6 +27,7 @@ export interface PacerEventMap { 'pacer:AsyncDebouncer': AsyncDebouncer 'pacer:AsyncQueuer': AsyncQueuer 'pacer:AsyncRateLimiter': AsyncRateLimiter + 'pacer:AsyncRetryer': AsyncRetryer 'pacer:AsyncThrottler': AsyncThrottler 'pacer:Batcher': Batcher 'pacer:Debouncer': Debouncer diff --git a/packages/pacer/src/index.ts b/packages/pacer/src/index.ts index 2e510c02..e1348ef1 100644 --- a/packages/pacer/src/index.ts +++ b/packages/pacer/src/index.ts @@ -2,6 +2,7 @@ export * from './async-batcher' export * from './async-debouncer' export * from './async-queuer' export * from './async-rate-limiter' +export * from './async-retryer' export * from './async-throttler' export * from './batcher' export * from './debouncer' @@ -10,5 +11,6 @@ export * from './rate-limiter' export * from './throttler' export * from './types' export * from './utils' + export { pacerEventClient } from './event-client' export type { PacerEventMap, PacerEventName } from './event-client' diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index 7a5e57f9..facf1c85 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -183,6 +183,8 @@ export type QueuePosition = 'front' | 'back' /** * A flexible queue that processes items with configurable wait times, expiration, and priority. * + * This synchronous version is lighter weight and often all you need - upgrade to AsyncQueuer when you need promises, retry support, abort capabilities, concurrent execution, or advanced error handling. + * * Features: * - Automatic or manual processing of items * - FIFO (First In First Out), LIFO (Last In First Out), or double-ended queue behavior @@ -287,6 +289,7 @@ export class Queuer { this.addItem(item, this.options.addItemsTo ?? 'back', isLast) } } + pacerEventClient.on('d-Queuer', (event) => { if (event.payload.key !== this.key) return this.#setState(event.payload.store.state) @@ -294,6 +297,11 @@ export class Queuer { }) } + /** + * Emits a change event for the queuer instance. Mostly useful for devtools. + */ + _emit = () => emitChange('Queuer', this) + /** * Updates the queuer options. New options are merged with existing options. */ @@ -681,9 +689,7 @@ export class Queuer { * Creates a queue that processes items immediately upon addition. * Items are processed sequentially in FIFO order by default. * - * This is a simplified wrapper around the Queuer class that only exposes the - * `addItem` method. The queue is always isRunning and will process items as they are added. - * For more control over queue processing, use the Queuer class directly. + * This synchronous version is lighter weight and often all you need - upgrade to asyncQueue when you need promises, retry support, abort capabilities, concurrent execution, or advanced error handling. * * State Management: * - Uses TanStack Store for reactive state management diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index 66b3cd94..8f494f6d 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -102,6 +102,7 @@ const defaultOptions: Omit< * Rate limiting is a simple approach that allows a function to execute up to a limit within a time window, * then blocks all subsequent calls until the window passes. This can lead to "bursty" behavior where * all executions happen immediately, followed by a complete block. + * This synchronous version is lighter weight and often all you need - upgrade to AsyncRateLimiter when you need promises, retry support, abort capabilities, or advanced error handling. * * The rate limiter supports two types of windows: * - 'fixed': A strict window that resets after the window period. All executions within the window count @@ -168,6 +169,11 @@ export class RateLimiter { }) } + /** + * Emits a change event for the rate limiter instance. Mostly useful for devtools. + */ + _emit = () => emitChange('RateLimiter', this) + /** * Updates the rate limiter options */ @@ -358,6 +364,8 @@ export class RateLimiter { /** * Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window. * + * This synchronous version is lighter weight and often all you need - upgrade to asyncRateLimit when you need promises, retry support, abort capabilities, or advanced error handling. + * * Note that rate limiting is a simpler form of execution control compared to throttling or debouncing: * - A rate limiter will allow all executions until the limit is reached, then block all subsequent calls until the window resets * - A throttler ensures even spacing between executions, which can be better for consistent performance diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index 7978f51e..24bf89fd 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -105,6 +105,7 @@ const defaultOptions: Omit< * Throttling ensures a function is called at most once within a specified time window. * Unlike debouncing which waits for a pause in calls, throttling guarantees consistent * execution timing regardless of call frequency. + * This synchronous version is lighter weight and often all you need - upgrade to AsyncThrottler when you need promises, retry support, abort/cancel capabilities, or advanced error handling. * * Supports both leading and trailing edge execution: * - Leading: Execute immediately on first call (default: true) @@ -160,6 +161,11 @@ export class Throttler { }) } + /** + * Emits a change event for the throttler instance. Mostly useful for devtools. + */ + _emit = () => emitChange('Throttler', this) + /** * Updates the throttler options */ @@ -321,6 +327,8 @@ export class Throttler { /** * Creates a throttled function that limits how often the provided function can execute. * + * This synchronous version is lighter weight and often all you need - upgrade to asyncThrottle when you need promises, retry support, abort/cancel capabilities, or advanced error handling. + * * Throttling ensures a function executes at most once within a specified time window, * regardless of how many times it is called. This is useful for rate-limiting * expensive operations or UI updates. diff --git a/packages/pacer/tests/async-batcher.test.ts b/packages/pacer/tests/async-batcher.test.ts index 4be62363..6ac6898c 100644 --- a/packages/pacer/tests/async-batcher.test.ts +++ b/packages/pacer/tests/async-batcher.test.ts @@ -71,20 +71,20 @@ describe('AsyncBatcher', () => { }) it('should track execution state during async processing', async () => { - let resolvePromise: (value: any) => void - const promise = new Promise((resolve) => { - resolvePromise = resolve - }) - const mockFn = vi.fn().mockReturnValue(promise) - const batcher = new AsyncBatcher(mockFn, { maxSize: 1 }) + const mockFn = vi.fn().mockResolvedValue('result') + const batcher = new AsyncBatcher(mockFn, { maxSize: 2 }) + // Add item without triggering execution (since maxSize is 2) batcher.addItem(1) + expect(batcher.store.state.isExecuting).toBe(false) + expect(batcher.store.state.status).toBe('populated') + + // Use flush to trigger execution and wait for completion + const executionPromise = batcher.flush() expect(batcher.store.state.isExecuting).toBe(true) expect(batcher.store.state.status).toBe('executing') - resolvePromise!('result') - await promise - + await executionPromise expect(batcher.store.state.isExecuting).toBe(false) expect(batcher.store.state.status).toBe('idle') }) diff --git a/packages/pacer/tests/async-retryer.test.ts b/packages/pacer/tests/async-retryer.test.ts new file mode 100644 index 00000000..20962ca7 --- /dev/null +++ b/packages/pacer/tests/async-retryer.test.ts @@ -0,0 +1,931 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { AsyncRetryer, asyncRetry } from '../src/async-retryer' + +describe('AsyncRetryer', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + describe('Constructor and Defaults', () => { + it('should create with default options', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn) + + expect(retryer.options.backoff).toBe('exponential') + expect(retryer.options.baseWait).toBe(1000) + expect(retryer.options.enabled).toBe(true) + expect(retryer.options.maxAttempts).toBe(3) + expect(retryer.options.maxExecutionTime).toBe(Infinity) + expect(retryer.options.maxTotalExecutionTime).toBe(Infinity) + expect(retryer.options.throwOnError).toBe('last') + }) + + it('should merge custom options with defaults', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 5, + backoff: 'linear', + baseWait: 500, + }) + + expect(retryer.options.maxAttempts).toBe(5) + expect(retryer.options.backoff).toBe('linear') + expect(retryer.options.baseWait).toBe(500) + expect(retryer.options.enabled).toBe(true) // Still default + }) + + it('should initialize with default state', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn) + + expect(retryer.store.state).toEqual({ + currentAttempt: 0, + executionCount: 0, + isExecuting: false, + lastError: undefined, + lastExecutionTime: 0, + lastResult: undefined, + status: 'idle', + totalExecutionTime: 0, + }) + }) + + it('should merge initial state', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + initialState: { executionCount: 5 }, + }) + + expect(retryer.store.state.executionCount).toBe(5) + expect(retryer.store.state.currentAttempt).toBe(0) // Other defaults preserved + }) + }) + + describe('Successful Execution', () => { + it('should execute function successfully on first attempt', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn) + + const result = await retryer.execute('arg1', 'arg2') + + expect(result).toBe('success') + expect(mockFn).toHaveBeenCalledTimes(1) + expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2') + expect(retryer.store.state.executionCount).toBe(1) + expect(retryer.store.state.lastResult).toBe('success') + expect(retryer.store.state.status).toBe('idle') + expect(retryer.store.state.currentAttempt).toBe(0) + }) + + it('should call onSuccess callback', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const onSuccess = vi.fn() + const retryer = new AsyncRetryer(mockFn, { onSuccess }) + + await retryer.execute('arg1') + + expect(onSuccess).toHaveBeenCalledTimes(1) + expect(onSuccess).toHaveBeenCalledWith('success', ['arg1'], retryer) + }) + + it('should update execution time and timestamp', async () => { + const mockFn = vi.fn().mockImplementation(async () => { + vi.advanceTimersByTime(100) + return 'success' + }) + const retryer = new AsyncRetryer(mockFn) + + const beforeTime = Date.now() + await retryer.execute() + const afterTime = Date.now() + + expect(retryer.store.state.totalExecutionTime).toBeGreaterThan(0) + expect(retryer.store.state.lastExecutionTime).toBeGreaterThanOrEqual( + beforeTime, + ) + expect(retryer.store.state.lastExecutionTime).toBeLessThanOrEqual( + afterTime, + ) + }) + }) + + describe('Retry Logic', () => { + it('should retry on failure and succeed on second attempt', async () => { + const mockFn = vi + .fn() + .mockRejectedValueOnce(new Error('First failure')) + .mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + baseWait: 100, + throwOnError: 'last', + }) + + const executePromise = retryer.execute('arg1') + + // Let the retry timer run + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + + const result = await executePromise + + expect(result).toBe('success') + expect(mockFn).toHaveBeenCalledTimes(2) + expect(retryer.store.state.executionCount).toBe(1) + expect(retryer.store.state.lastResult).toBe('success') + expect(retryer.store.state.status).toBe('idle') + }) + + it('should call onRetry callback for each retry', async () => { + const mockFn = vi + .fn() + .mockRejectedValueOnce(new Error('First failure')) + .mockRejectedValueOnce(new Error('Second failure')) + .mockResolvedValue('success') + const onRetry = vi.fn() + const retryer = new AsyncRetryer(mockFn, { onRetry, baseWait: 100 }) + + const executePromise = retryer.execute() + + // Let first retry happen + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + + // Let second retry happen + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(200) // Exponential backoff: 100 * 2^1 + + await executePromise + + expect(onRetry).toHaveBeenCalledTimes(2) + expect(onRetry).toHaveBeenNthCalledWith(1, 1, expect.any(Error), retryer) + expect(onRetry).toHaveBeenNthCalledWith(2, 2, expect.any(Error), retryer) + }) + + it('should fail after exhausting all retries', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Persistent failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 0, // No delay to avoid timer issues + throwOnError: 'last', + }) + + await expect(retryer.execute()).rejects.toThrow('Persistent failure') + + expect(mockFn).toHaveBeenCalledTimes(2) + expect(retryer.store.state.lastError?.message).toBe('Persistent failure') + expect(retryer.store.state.status).toBe('idle') + }) + }) + + describe('Backoff Strategies', () => { + it('should use exponential backoff by default', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 3, + baseWait: 100, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + // First retry: wait 100ms (100 * 2^0) + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + + // Second retry: wait 200ms (100 * 2^1) + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(200) + + // Third attempt will not retry, just finish + await executePromise + + expect(mockFn).toHaveBeenCalledTimes(3) + expect(retryer.store.state.currentAttempt).toBe(3) + }) + + it('should use linear backoff', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 3, + baseWait: 100, + backoff: 'linear', + throwOnError: false, + }) + + const executePromise = retryer.execute() + + // First retry should wait 100ms (100 * 1) + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + + // Second retry should wait 200ms (100 * 2) + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(200) + + await executePromise + expect(mockFn).toHaveBeenCalledTimes(3) + }) + + it('should use fixed backoff', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 3, + baseWait: 150, + backoff: 'fixed', + throwOnError: false, + }) + + const executePromise = retryer.execute() + + // Both retries should wait 150ms + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(150) + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(150) + + await executePromise + expect(mockFn).toHaveBeenCalledTimes(3) + }) + }) + + describe('Error Handling', () => { + it('should throw on last error by default', async () => { + const error = new Error('Test error') + const mockFn = vi.fn().mockRejectedValue(error) + const retryer = new AsyncRetryer(mockFn, { maxAttempts: 1 }) + + await expect(retryer.execute()).rejects.toThrow('Test error') + }) + + it('should not throw when throwOnError is false', async () => { + const error = new Error('Test error') + const mockFn = vi.fn().mockRejectedValue(error) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 1, + throwOnError: false, + }) + + const result = await retryer.execute() + expect(result).toBeUndefined() + }) + + it('should throw after retries when throwOnError is true', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Test error')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 3, + baseWait: 0, // No delay to avoid timer issues + throwOnError: true, + }) + + await expect(retryer.execute()).rejects.toThrow('Test error') + + expect(mockFn).toHaveBeenCalledTimes(3) // Should still retry but throw at end + }) + + it('should call onError for every error', async () => { + const error = new Error('Test error') + const mockFn = vi.fn().mockRejectedValue(error) + const onError = vi.fn() + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 100, + onError, + throwOnError: false, + }) + + const executePromise = retryer.execute('arg1') + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + await executePromise + + expect(onError).toHaveBeenCalledTimes(1) // Only called once at the end after all retries fail + expect(onError).toHaveBeenCalledWith(error, ['arg1'], retryer) + }) + + it('should call onLastError only for final error', async () => { + const error = new Error('Test error') + const mockFn = vi.fn().mockRejectedValue(error) + const onLastError = vi.fn() + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 100, + onLastError, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + await executePromise + + expect(onLastError).toHaveBeenCalledTimes(1) + expect(onLastError).toHaveBeenCalledWith(error, retryer) + }) + }) + + describe('State Management', () => { + it('should track execution state correctly', async () => { + const mockFn = vi.fn().mockImplementation(async () => { + vi.advanceTimersByTime(50) + return 'success' + }) + const retryer = new AsyncRetryer(mockFn) + + expect(retryer.store.state.status).toBe('idle') + expect(retryer.store.state.isExecuting).toBe(false) + + const executePromise = retryer.execute() + + // Should be executing now + expect(retryer.store.state.status).toBe('executing') + expect(retryer.store.state.isExecuting).toBe(true) + expect(retryer.store.state.currentAttempt).toBe(1) + + await executePromise + + expect(retryer.store.state.status).toBe('idle') + expect(retryer.store.state.isExecuting).toBe(false) + expect(retryer.store.state.currentAttempt).toBe(0) + }) + + it('should show retrying status during retries', async () => { + const mockFn = vi + .fn() + .mockRejectedValueOnce(new Error('Failure')) + .mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + baseWait: 100, + throwOnError: false, + }) + + // Start execution but do not await yet + const executePromise = retryer.execute() + + // After first rejection, status should be 'retrying' + // Allow microtasks to schedule the retry wait without advancing timers + await Promise.resolve() + expect(retryer.store.state.status).toBe('retrying') + + // Fast-forward the retry wait time + vi.advanceTimersByTime(100) + await executePromise + + // After completion, status should be 'idle' + expect(retryer.store.state.status).toBe('idle') + }) + + it('should show disabled status when not enabled', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { enabled: false }) + + expect(retryer.store.state.status).toBe('disabled') + + const result = await retryer.execute() + expect(result).toBeUndefined() + expect(mockFn).not.toHaveBeenCalled() + }) + }) + + describe('Callbacks', () => { + it('should call onSettled after every execution attempt', async () => { + const mockFn = vi + .fn() + .mockRejectedValueOnce(new Error('Failure')) + .mockResolvedValue('success') + const onSettled = vi.fn() + const retryer = new AsyncRetryer(mockFn, { onSettled, baseWait: 100 }) + + const executePromise = retryer.execute('arg1') + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + await executePromise + + // Called after each attempt (failed + successful) + expect(onSettled).toHaveBeenCalledTimes(2) + expect(onSettled).toHaveBeenCalledWith(['arg1'], retryer) + }) + }) + + describe('Dynamic Options', () => { + it('should support function-based maxAttempts', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const maxAttemptsFn = vi.fn().mockReturnValue(2) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: maxAttemptsFn, + baseWait: 100, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + await executePromise + + expect(maxAttemptsFn).toHaveBeenCalledWith(retryer) + expect(mockFn).toHaveBeenCalledTimes(2) + }) + + it('should support function-based baseWait', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const baseWaitFn = vi.fn().mockReturnValue(200) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: baseWaitFn, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(200) // Should use function return value + await executePromise + + expect(baseWaitFn).toHaveBeenCalledWith(retryer) + }) + + it('should support function-based enabled', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const enabledFn = vi.fn().mockReturnValue(false) + const retryer = new AsyncRetryer(mockFn, { enabled: enabledFn }) + + const result = await retryer.execute() + + expect(enabledFn).toHaveBeenCalledWith(retryer) + expect(result).toBeUndefined() + expect(mockFn).not.toHaveBeenCalled() + }) + }) + + describe('Cancellation', () => { + it('should allow new executions after cancel and resolve undefined without error', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const onError = vi.fn() + const retryer = new AsyncRetryer(mockFn, { onError, throwOnError: false }) + + // Start and immediately cancel + const executePromise1 = retryer.execute() + retryer.abort() + + const result1 = await executePromise1 + expect(result1).toBeUndefined() + expect(onError).not.toHaveBeenCalled() + + // Should be able to execute again after cancel + const result2 = await retryer.execute() + + expect(result2).toBe('success') + expect(retryer.store.state.isExecuting).toBe(false) + }) + + it('should cancel retry delays without error', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const onError = vi.fn() + const retryer = new AsyncRetryer(mockFn, { + baseWait: 1000, + throwOnError: false, + onError, + }) + + const executePromise = retryer.execute() + + // Wait for first attempt to fail and retry wait to be scheduled + await Promise.resolve() + + // Cancel during retry delay + retryer.abort() + + const result = await executePromise + expect(result).toBeUndefined() + expect(mockFn).toHaveBeenCalledTimes(1) // Only first attempt + expect(onError).toHaveBeenCalledTimes(1) // only final onError after loop completion before cancel + }) + }) + + describe('Reset', () => { + it('should reset to initial state', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn) + + await retryer.execute() + expect(retryer.store.state.executionCount).toBe(1) + expect(retryer.store.state.lastResult).toBe('success') + + retryer.reset() + + expect(retryer.store.state).toEqual({ + currentAttempt: 0, + executionCount: 0, + isExecuting: false, + lastError: undefined, + lastExecutionTime: 0, + lastResult: undefined, + status: 'idle', + totalExecutionTime: 0, + }) + }) + + it('should cancel ongoing execution when resetting', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + baseWait: 1000, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + // Wait for first attempt to fail and retry wait to be scheduled + await Promise.resolve() + + retryer.reset() + + const result = await executePromise + expect(result).toBeUndefined() + expect(mockFn).toHaveBeenCalledTimes(1) // Only first attempt before reset + }) + }) + + describe('setOptions', () => { + it('should update options', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { maxAttempts: 3 }) + + expect(retryer.options.maxAttempts).toBe(3) + + retryer.setOptions({ maxAttempts: 5, backoff: 'linear' }) + + expect(retryer.options.maxAttempts).toBe(5) + expect(retryer.options.backoff).toBe('linear') + expect(retryer.options.baseWait).toBe(1000) // Unchanged + }) + }) + + describe('Timeout Controls', () => { + it('should have timeout options in default options', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn) + + expect(retryer.options.maxExecutionTime).toBe(Infinity) + expect(retryer.options.maxTotalExecutionTime).toBe(Infinity) + }) + + it('should accept custom timeout options', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + maxExecutionTime: 5000, + maxTotalExecutionTime: 10000, + }) + + expect(retryer.options.maxExecutionTime).toBe(5000) + expect(retryer.options.maxTotalExecutionTime).toBe(10000) + }) + + it('should not timeout when execution completes quickly', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + maxExecutionTime: 1000, + maxTotalExecutionTime: 2000, + }) + + const result = await retryer.execute() + + expect(result).toBe('success') + expect(mockFn).toHaveBeenCalledTimes(1) + }) + + it('should clean up timeouts when execution succeeds', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + maxExecutionTime: 1000, + maxTotalExecutionTime: 2000, + }) + + const result = await retryer.execute() + + expect(result).toBe('success') + // Advance time to ensure no timeout fires + vi.advanceTimersByTime(5000) + expect(retryer.store.state.status).toBe('idle') + }) + }) + + describe('Jitter', () => { + it('should apply jitter to retry delays', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 1000, + jitter: 0.1, // 10% jitter + throwOnError: false, + }) + + const executePromise = retryer.execute() + + // First retry should have jitter applied + await vi.runOnlyPendingTimersAsync() + + // The exact time will vary due to jitter, but should be within range + // Base wait is 1000ms, jitter is 10%, so range is 900-1100ms + // We'll advance by a bit more than the base to ensure it triggers + vi.advanceTimersByTime(1100) + + await executePromise + + expect(mockFn).toHaveBeenCalledTimes(2) + }) + + it('should handle jitter when crypto is not available', async () => { + // Mock crypto to be undefined by temporarily replacing it + const originalCrypto = globalThis.crypto + Object.defineProperty(globalThis, 'crypto', { + value: undefined, + writable: true, + configurable: true, + }) + + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 100, + jitter: 0.1, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) // Should use base wait without jitter + await executePromise + + expect(mockFn).toHaveBeenCalledTimes(2) + + // Restore crypto + Object.defineProperty(globalThis, 'crypto', { + value: originalCrypto, + writable: true, + configurable: true, + }) + }) + + it('should not apply jitter when jitter is 0', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 100, + jitter: 0, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) // Exact base wait time + await executePromise + + expect(mockFn).toHaveBeenCalledTimes(2) + }) + }) + + describe('Initial State', () => { + it('should merge all initial state properties', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + initialState: { + executionCount: 5, + lastResult: 'previous result', + lastError: new Error('Previous error'), + totalExecutionTime: 1000, + }, + }) + + expect(retryer.store.state.executionCount).toBe(5) + expect(retryer.store.state.lastResult).toBe('previous result') + expect(retryer.store.state.lastError?.message).toBe('Previous error') + expect(retryer.store.state.totalExecutionTime).toBe(1000) + expect(retryer.store.state.currentAttempt).toBe(0) // Default preserved + expect(retryer.store.state.isExecuting).toBe(false) // Default preserved + }) + + it('should update status based on initial state', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + initialState: { + isExecuting: true, + currentAttempt: 2, + }, + }) + + expect(retryer.store.state.status).toBe('retrying') + }) + + it('should show disabled status when enabled is false in initial state', () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryer = new AsyncRetryer(mockFn, { + enabled: false, + initialState: { + isExecuting: true, + }, + }) + + expect(retryer.store.state.status).toBe('disabled') + }) + }) + + describe('Edge Cases and Error Scenarios', () => { + it('should handle function that throws non-Error objects', async () => { + const mockFn = vi.fn().mockRejectedValue('String error') + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 1, + throwOnError: false, + }) + + const result = await retryer.execute() + + expect(result).toBeUndefined() + expect(retryer.store.state.lastError?.message).toBe('String error') + }) + + it('should handle function that throws null', async () => { + const mockFn = vi.fn().mockRejectedValue(null) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 1, + throwOnError: false, + }) + + const result = await retryer.execute() + + expect(result).toBeUndefined() + expect(retryer.store.state.lastError?.message).toBe('null') + }) + + it('should handle function that throws undefined', async () => { + const mockFn = vi.fn().mockRejectedValue(undefined) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 1, + throwOnError: false, + }) + + const result = await retryer.execute() + + expect(result).toBeUndefined() + expect(retryer.store.state.lastError?.message).toBe('undefined') + }) + + it('should handle very large jitter values', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 100, + jitter: 1.5, // 150% jitter + throwOnError: false, + }) + + const executePromise = retryer.execute() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(250) // Should be enough for max jitter + await executePromise + + expect(mockFn).toHaveBeenCalledTimes(2) + }) + + it('should handle negative jitter values', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 100, + jitter: -0.5, // Negative jitter + throwOnError: false, + }) + + const executePromise = retryer.execute() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) // Should use base wait + await executePromise + + expect(mockFn).toHaveBeenCalledTimes(2) + }) + + it('should handle zero maxAttempts', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 0, + throwOnError: false, + }) + + const result = await retryer.execute() + + expect(result).toBeUndefined() + expect(mockFn).toHaveBeenCalledTimes(0) + }) + + it('should handle zero baseWait', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryer = new AsyncRetryer(mockFn, { + maxAttempts: 2, + baseWait: 0, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + // No delay between retries + await vi.runOnlyPendingTimersAsync() + await executePromise + + expect(mockFn).toHaveBeenCalledTimes(2) + }) + }) + + describe('Complex Integration Scenarios', () => { + it('should handle cancellation during execution', async () => { + const mockFn = vi.fn().mockImplementation(async () => { + vi.advanceTimersByTime(2000) + return 'success' + }) + const retryer = new AsyncRetryer(mockFn, { + maxExecutionTime: 1000, + throwOnError: false, + }) + + const executePromise = retryer.execute() + + // Cancel before timeout + vi.advanceTimersByTime(500) + retryer.abort() + + const result = await executePromise + + expect(result).toBeUndefined() + expect(mockFn).toHaveBeenCalledTimes(1) + }) + }) +}) + +describe('asyncRetry utility function', () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + it('should create a retry-enabled function', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryFn = asyncRetry(mockFn, { maxAttempts: 2 }) + + const result = await retryFn('arg1', 'arg2') + + expect(result).toBe('success') + expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2') + }) + + it('should retry on failure', async () => { + const mockFn = vi + .fn() + .mockRejectedValueOnce(new Error('Failure')) + .mockResolvedValue('success') + const retryFn = asyncRetry(mockFn, { baseWait: 100, throwOnError: 'last' }) + + const executePromise = retryFn() + + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(100) + const result = await executePromise + + expect(result).toBe('success') + expect(mockFn).toHaveBeenCalledTimes(2) + }) + + it('should use default options', async () => { + const mockFn = vi.fn().mockRejectedValue(new Error('Failure')) + const retryFn = asyncRetry(mockFn, { throwOnError: false }) + + const executePromise = retryFn() + + // Should retry 3 times by default + vi.advanceTimersByTime(1000) // First retry: 1000ms + await vi.runOnlyPendingTimersAsync() + vi.advanceTimersByTime(2000) // Second retry: 2000ms + await vi.runOnlyPendingTimersAsync() + + const result = await executePromise + expect(result).toBeUndefined() + expect(mockFn).toHaveBeenCalledTimes(3) // Default maxAttempts + }) + + it('should support timeout options', async () => { + const mockFn = vi.fn().mockResolvedValue('success') + const retryFn = asyncRetry(mockFn, { + maxExecutionTime: 1000, + maxTotalExecutionTime: 2000, + }) + + const result = await retryFn() + + expect(result).toBe('success') + expect(mockFn).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/pacer/tests/async-throttler.test.ts b/packages/pacer/tests/async-throttler.test.ts index 010a04cf..115715bb 100644 --- a/packages/pacer/tests/async-throttler.test.ts +++ b/packages/pacer/tests/async-throttler.test.ts @@ -175,12 +175,37 @@ describe('AsyncThrottler', () => { expect(throttler.store.state.nextExecutionTime).toBe(now + 200) }) + it('should handle basic throttling behavior correctly', async () => { + const mockFn = vi.fn().mockResolvedValue('result') + const throttler = new AsyncThrottler(mockFn, { wait: 100 }) + + // First call should execute immediately + const result1 = await throttler.maybeExecute('first') + expect(mockFn).toHaveBeenCalledTimes(1) + expect(result1).toBe('result') + + // Second call within wait period should set up trailing execution + const promise2 = throttler.maybeExecute('second') + expect(mockFn).toHaveBeenCalledTimes(1) // Still only one call + + // Advance timers to trigger trailing execution + vi.advanceTimersByTime(100) + const result2 = await promise2 + expect(result2).toBe('result') // Returns result from trailing execution + + // After wait period, should execute again + vi.advanceTimersByTime(100) + const result3 = await throttler.maybeExecute('third') + expect(mockFn).toHaveBeenCalledTimes(3) // First call + trailing execution + third call + expect(result3).toBe('result') + }) + it('should cancel pending execution', async () => { const mockFn = vi.fn().mockResolvedValue(undefined) const throttler = new AsyncThrottler(mockFn, { wait: 100 }) // First call executes immediately - throttler.maybeExecute('first') + await throttler.maybeExecute('first') expect(mockFn).toHaveBeenCalledTimes(1) expect(mockFn).toHaveBeenLastCalledWith('first') @@ -189,6 +214,9 @@ describe('AsyncThrottler', () => { // Cancel it immediately throttler.cancel() + // Advance timers to allow any waiting loops to exit + await vi.advanceTimersByTimeAsync(10) + // Wait for the promise to settle await expect(promise).resolves.toBeUndefined() expect(mockFn).toHaveBeenCalledTimes(1) @@ -199,12 +227,15 @@ describe('AsyncThrottler', () => { const mockFn = vi.fn().mockResolvedValue(undefined) const throttler = new AsyncThrottler(mockFn, { wait: 100 }) - throttler.maybeExecute('first') + await throttler.maybeExecute('first') const promise = throttler.maybeExecute('second') throttler.cancel() + + // Advance timers to allow any waiting loops to exit + await vi.advanceTimersByTimeAsync(10) await promise - vi.advanceTimersByTime(100) + await vi.advanceTimersByTimeAsync(100) await throttler.maybeExecute('third') expect(mockFn).toHaveBeenCalledTimes(2) @@ -217,13 +248,16 @@ describe('AsyncThrottler', () => { const throttler = new AsyncThrottler(mockFn, { wait: 100 }) // First call should execute - throttler.maybeExecute('first') + await throttler.maybeExecute('first') expect(mockFn).toHaveBeenCalledTimes(1) expect(mockFn).toHaveBeenLastCalledWith('first') // Second call should be cancelled const promise = throttler.maybeExecute('second') throttler.cancel() + + // Advance timers to allow any waiting loops to exit + await vi.advanceTimersByTimeAsync(10) await promise // Multiple cancels should not throw @@ -245,14 +279,24 @@ describe('AsyncThrottler', () => { // Start first long-running call const promise1 = throttler.maybeExecute('first') const promise2 = throttler.maybeExecute('second') + + // Advance timers to allow waiting loop to start + await vi.advanceTimersByTimeAsync(50) + throttler.cancel() + // Advance timers to allow cancel to take effect + await vi.advanceTimersByTimeAsync(50) + resolveFirst!({}) await promise1 - await promise2 - expect(mockFn).toHaveBeenCalledTimes(1) - expect(mockFn).toHaveBeenLastCalledWith('first') + // The second promise should resolve with the result from the first execution + const result2 = await promise2 + expect(result2).toEqual({}) + + expect(mockFn).toHaveBeenCalledTimes(2) // First call + second call that triggered trailing execution + expect(mockFn).toHaveBeenLastCalledWith('second') }) it('should cancel pending calls when cancel is called', async () => { @@ -368,7 +412,10 @@ describe('AsyncThrottler', () => { expect(mockFn).toHaveBeenCalledTimes(1) expect(mockFn).toHaveBeenCalledWith('first') expect(flushResult).toBe('result') - expect(await promise).toBe('result') + + // The promise gets resolved by flush with the result + const promiseResult = await promise + expect(promiseResult).toBe('result') }) it('should update state correctly after flush', async () => { diff --git a/packages/react-pacer-devtools/package.json b/packages/react-pacer-devtools/package.json index 46f61ee0..fbe49934 100644 --- a/packages/react-pacer-devtools/package.json +++ b/packages/react-pacer-devtools/package.json @@ -69,7 +69,7 @@ "@tanstack/pacer-devtools": "workspace:*" }, "devDependencies": { - "@eslint-react/eslint-plugin": "^1.53.1", + "@eslint-react/eslint-plugin": "^2.0.4", "@vitejs/plugin-react": "^5.0.4", "eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-hooks": "^5.2.0" diff --git a/packages/react-pacer/package.json b/packages/react-pacer/package.json index 4c43a1d8..76ad58fc 100644 --- a/packages/react-pacer/package.json +++ b/packages/react-pacer/package.json @@ -77,6 +77,16 @@ "default": "./dist/cjs/async-rate-limiter/index.cjs" } }, + "./async-retryer": { + "import": { + "types": "./dist/esm/async-retryer/index.d.ts", + "default": "./dist/esm/async-retryer/index.js" + }, + "require": { + "types": "./dist/cjs/async-retryer/index.d.cts", + "default": "./dist/cjs/async-retryer/index.cjs" + } + }, "./async-throttler": { "import": { "types": "./dist/esm/async-throttler/index.d.ts", @@ -127,6 +137,16 @@ "default": "./dist/cjs/rate-limiter/index.cjs" } }, + "./provider": { + "import": { + "types": "./dist/esm/provider/index.d.ts", + "default": "./dist/esm/provider/index.js" + }, + "require": { + "types": "./dist/cjs/provider/index.d.cts", + "default": "./dist/cjs/provider/index.cjs" + } + }, "./throttler": { "import": { "types": "./dist/esm/throttler/index.d.ts", @@ -175,8 +195,8 @@ "@tanstack/react-store": "^0.7.7" }, "devDependencies": { - "@eslint-react/eslint-plugin": "^1.53.1", - "@types/react": "^19.1.15", + "@eslint-react/eslint-plugin": "^2.0.4", + "@types/react": "^19.1.17", "@vitejs/plugin-react": "^5.0.4", "eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts b/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts index ec9aa342..1d0f32c5 100644 --- a/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts +++ b/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react' import { AsyncBatcher } from '@tanstack/pacer/async-batcher' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { AsyncBatcherOptions, @@ -170,12 +171,19 @@ export function useAsyncBatcher( selector: (state: AsyncBatcherState) => TSelected = () => ({}) as TSelected, ): ReactAsyncBatcher { - const [asyncBatcher] = useState(() => new AsyncBatcher(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().asyncBatcher, + ...options, + } as AsyncBatcherOptions - const state = useStore(asyncBatcher.store, selector) + const [asyncBatcher] = useState( + () => new AsyncBatcher(fn, mergedOptions), + ) asyncBatcher.fn = fn - asyncBatcher.setOptions(options) + asyncBatcher.setOptions(mergedOptions) + + const state = useStore(asyncBatcher.store, selector) return useMemo( () => diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index 86637757..36b8ae99 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react' import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { @@ -152,12 +153,19 @@ export function useAsyncDebouncer( selector: (state: AsyncDebouncerState) => TSelected = () => ({}) as TSelected, ): ReactAsyncDebouncer { - const [asyncDebouncer] = useState(() => new AsyncDebouncer(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().asyncDebouncer, + ...options, + } as AsyncDebouncerOptions - const state = useStore(asyncDebouncer.store, selector) + const [asyncDebouncer] = useState( + () => new AsyncDebouncer(fn, mergedOptions), + ) asyncDebouncer.fn = fn - asyncDebouncer.setOptions(options) + asyncDebouncer.setOptions(mergedOptions) + + const state = useStore(asyncDebouncer.store, selector) useEffect(() => { return () => { diff --git a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts index 2e925fd7..0d88f364 100644 --- a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts +++ b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react' import { AsyncQueuer } from '@tanstack/pacer/async-queuer' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { AsyncQueuerOptions, @@ -170,12 +171,19 @@ export function useAsyncQueuer( selector: (state: AsyncQueuerState) => TSelected = () => ({}) as TSelected, ): ReactAsyncQueuer { - const [asyncQueuer] = useState(() => new AsyncQueuer(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().asyncQueuer, + ...options, + } as AsyncQueuerOptions - const state = useStore(asyncQueuer.store, selector) + const [asyncQueuer] = useState( + () => new AsyncQueuer(fn, mergedOptions), + ) asyncQueuer.fn = fn - asyncQueuer.setOptions(options) + asyncQueuer.setOptions(mergedOptions) + + const state = useStore(asyncQueuer.store, selector) return useMemo( () => diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts index 179a8277..81bf9f6d 100644 --- a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react' import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { @@ -184,14 +185,19 @@ export function useAsyncRateLimiter< selector: (state: AsyncRateLimiterState) => TSelected = () => ({}) as TSelected, ): ReactAsyncRateLimiter { + const mergedOptions = { + ...useDefaultPacerOptions().asyncRateLimiter, + ...options, + } as AsyncRateLimiterOptions + const [asyncRateLimiter] = useState( - () => new AsyncRateLimiter(fn, options), + () => new AsyncRateLimiter(fn, mergedOptions), ) - const state = useStore(asyncRateLimiter.store, selector) - asyncRateLimiter.fn = fn - asyncRateLimiter.setOptions(options) + asyncRateLimiter.setOptions(mergedOptions) + + const state = useStore(asyncRateLimiter.store, selector) return useMemo( () => diff --git a/packages/react-pacer/src/async-retryer/index.ts b/packages/react-pacer/src/async-retryer/index.ts new file mode 100644 index 00000000..371a59aa --- /dev/null +++ b/packages/react-pacer/src/async-retryer/index.ts @@ -0,0 +1,2 @@ +// re-export everything from the core pacer package, BUT ONLY from the async-retryer module +export * from '@tanstack/pacer/async-retryer' diff --git a/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts b/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts index 08f083b8..1f583c5c 100644 --- a/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts +++ b/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react' import { AsyncThrottler } from '@tanstack/pacer/async-throttler' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { @@ -163,12 +164,19 @@ export function useAsyncThrottler( selector: (state: AsyncThrottlerState) => TSelected = () => ({}) as TSelected, ): ReactAsyncThrottler { - const [asyncThrottler] = useState(() => new AsyncThrottler(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().asyncThrottler, + ...options, + } as AsyncThrottlerOptions - const state = useStore(asyncThrottler.store, selector) + const [asyncThrottler] = useState( + () => new AsyncThrottler(fn, mergedOptions), + ) asyncThrottler.fn = fn - asyncThrottler.setOptions(options) + asyncThrottler.setOptions(mergedOptions) + + const state = useStore(asyncThrottler.store, selector) useEffect(() => { return () => asyncThrottler.cancel() diff --git a/packages/react-pacer/src/batcher/useBatcher.ts b/packages/react-pacer/src/batcher/useBatcher.ts index 2709d6d0..b40d86e1 100644 --- a/packages/react-pacer/src/batcher/useBatcher.ts +++ b/packages/react-pacer/src/batcher/useBatcher.ts @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react' import { Batcher } from '@tanstack/pacer/batcher' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher' @@ -124,12 +125,17 @@ export function useBatcher( selector: (state: BatcherState) => TSelected = () => ({}) as TSelected, ): ReactBatcher { - const [batcher] = useState(() => new Batcher(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().batcher, + ...options, + } as BatcherOptions - const state = useStore(batcher.store, selector) + const [batcher] = useState(() => new Batcher(fn, mergedOptions)) batcher.fn = fn - batcher.setOptions(options) + batcher.setOptions(mergedOptions) + + const state = useStore(batcher.store, selector) return useMemo( () => diff --git a/packages/react-pacer/src/debouncer/useDebouncer.ts b/packages/react-pacer/src/debouncer/useDebouncer.ts index 24674b64..cb9030dc 100644 --- a/packages/react-pacer/src/debouncer/useDebouncer.ts +++ b/packages/react-pacer/src/debouncer/useDebouncer.ts @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react' import { Debouncer } from '@tanstack/pacer/debouncer' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { DebouncerOptions, @@ -104,12 +105,15 @@ export function useDebouncer( options: DebouncerOptions, selector: (state: DebouncerState) => TSelected = () => ({}) as TSelected, ): ReactDebouncer { - const [debouncer] = useState(() => new Debouncer(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().debouncer, + ...options, + } as DebouncerOptions - const state = useStore(debouncer.store, selector) + const [debouncer] = useState(() => new Debouncer(fn, mergedOptions)) debouncer.fn = fn - debouncer.setOptions(options) + debouncer.setOptions(mergedOptions) useEffect(() => { return () => { @@ -117,6 +121,8 @@ export function useDebouncer( } }, [debouncer]) + const state = useStore(debouncer.store, selector) + return useMemo( () => ({ diff --git a/packages/react-pacer/src/index.ts b/packages/react-pacer/src/index.ts index 49344428..7ab02da1 100644 --- a/packages/react-pacer/src/index.ts +++ b/packages/react-pacer/src/index.ts @@ -1,6 +1,9 @@ // re-export everything from the core pacer package export * from '@tanstack/pacer' +// provider +export * from './provider/PacerProvider' + /** * Export every hook individually - DON'T export from barrel files */ diff --git a/packages/react-pacer/src/provider/PacerProvider.tsx b/packages/react-pacer/src/provider/PacerProvider.tsx new file mode 100644 index 00000000..06df8207 --- /dev/null +++ b/packages/react-pacer/src/provider/PacerProvider.tsx @@ -0,0 +1,71 @@ +import React, { createContext, useContext, useMemo } from 'react' +import type { ReactNode } from 'react' +import type { + AnyAsyncFunction, + AnyFunction, + AsyncBatcherOptions, + AsyncDebouncerOptions, + AsyncQueuerOptions, + AsyncRateLimiterOptions, + AsyncRetryerOptions, + AsyncThrottlerOptions, + BatcherOptions, + DebouncerOptions, + QueuerOptions, + RateLimiterOptions, + ThrottlerOptions, +} from '@tanstack/pacer' + +export interface PacerProviderOptions { + asyncBatcher?: Partial> + asyncDebouncer?: Partial> + asyncQueuer?: Partial> + asyncRateLimiter?: Partial> + asyncRetryer?: Partial> + asyncThrottler?: Partial> + batcher?: Partial> + debouncer?: Partial> + queuer?: Partial> + rateLimiter?: Partial> + throttler?: Partial> +} + +interface PacerContextValue { + defaultOptions: PacerProviderOptions +} + +const PacerContext = createContext(null) + +export interface PacerProviderProps { + children: ReactNode + defaultOptions?: PacerProviderOptions +} + +const DEFAULT_OPTIONS: PacerProviderOptions = {} + +export function PacerProvider({ + children, + defaultOptions = DEFAULT_OPTIONS, +}: PacerProviderProps) { + const contextValue: PacerContextValue = useMemo( + () => ({ + defaultOptions, + }), + [defaultOptions], + ) + + return ( + + {children} + + ) +} + +export function usePacerContext() { + return useContext(PacerContext) +} + +export function useDefaultPacerOptions() { + const context = useContext(PacerContext) + return context?.defaultOptions ?? {} +} diff --git a/packages/react-pacer/src/provider/index.ts b/packages/react-pacer/src/provider/index.ts new file mode 100644 index 00000000..5f4fe385 --- /dev/null +++ b/packages/react-pacer/src/provider/index.ts @@ -0,0 +1 @@ +export * from './PacerProvider' diff --git a/packages/react-pacer/src/queuer/useQueuer.ts b/packages/react-pacer/src/queuer/useQueuer.ts index dec8992b..6bedc088 100644 --- a/packages/react-pacer/src/queuer/useQueuer.ts +++ b/packages/react-pacer/src/queuer/useQueuer.ts @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react' import { Queuer } from '@tanstack/pacer/queuer' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' @@ -134,12 +135,17 @@ export function useQueuer( options: QueuerOptions = {}, selector: (state: QueuerState) => TSelected = () => ({}) as TSelected, ): ReactQueuer { - const [queuer] = useState(() => new Queuer(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().queuer, + ...options, + } as QueuerOptions - const state = useStore(queuer.store, selector) + const [queuer] = useState(() => new Queuer(fn, mergedOptions)) queuer.fn = fn - queuer.setOptions(options) + queuer.setOptions(mergedOptions) + + const state = useStore(queuer.store, selector) return useMemo( () => diff --git a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts index 8bf4442f..dfc2a4c5 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react' import { RateLimiter } from '@tanstack/pacer/rate-limiter' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { RateLimiterOptions, @@ -143,12 +144,17 @@ export function useRateLimiter( options: RateLimiterOptions, selector: (state: RateLimiterState) => TSelected = () => ({}) as TSelected, ): ReactRateLimiter { - const [rateLimiter] = useState(() => new RateLimiter(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().rateLimiter, + ...options, + } as RateLimiterOptions - const state = useStore(rateLimiter.store, selector) + const [rateLimiter] = useState(() => new RateLimiter(fn, mergedOptions)) rateLimiter.fn = fn - rateLimiter.setOptions(options) + rateLimiter.setOptions(mergedOptions) + + const state = useStore(rateLimiter.store, selector) return useMemo( () => diff --git a/packages/react-pacer/src/throttler/useThrottler.ts b/packages/react-pacer/src/throttler/useThrottler.ts index 3bed9bef..327b5ba1 100644 --- a/packages/react-pacer/src/throttler/useThrottler.ts +++ b/packages/react-pacer/src/throttler/useThrottler.ts @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'react' import { Throttler } from '@tanstack/pacer/throttler' import { useStore } from '@tanstack/react-store' +import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { AnyFunction } from '@tanstack/pacer/types' import type { @@ -109,12 +110,17 @@ export function useThrottler( options: ThrottlerOptions, selector: (state: ThrottlerState) => TSelected = () => ({}) as TSelected, ): ReactThrottler { - const [throttler] = useState(() => new Throttler(fn, options)) + const mergedOptions = { + ...useDefaultPacerOptions().throttler, + ...options, + } as ThrottlerOptions - const state = useStore(throttler.store, selector) + const [throttler] = useState(() => new Throttler(fn, mergedOptions)) throttler.fn = fn - throttler.setOptions(options) + throttler.setOptions(mergedOptions) + + const state = useStore(throttler.store, selector) useEffect(() => { return () => { diff --git a/packages/react-pacer/vite.config.ts b/packages/react-pacer/vite.config.ts index 2657f80b..4d8e77bf 100644 --- a/packages/react-pacer/vite.config.ts +++ b/packages/react-pacer/vite.config.ts @@ -23,10 +23,12 @@ export default mergeConfig( './src/async-debouncer/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', + './src/async-retryer/index.ts', './src/async-throttler/index.ts', './src/batcher/index.ts', './src/debouncer/index.ts', './src/index.ts', + './src/provider/index.ts', './src/queuer/index.ts', './src/rate-limiter/index.ts', './src/throttler/index.ts', diff --git a/packages/solid-pacer-devtools/package.json b/packages/solid-pacer-devtools/package.json index f83ec566..a81a5bb7 100644 --- a/packages/solid-pacer-devtools/package.json +++ b/packages/solid-pacer-devtools/package.json @@ -66,6 +66,6 @@ "@tanstack/pacer-devtools": "workspace:*" }, "devDependencies": { - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" } } diff --git a/packages/solid-pacer/package.json b/packages/solid-pacer/package.json index cbf7d99a..96527581 100644 --- a/packages/solid-pacer/package.json +++ b/packages/solid-pacer/package.json @@ -176,7 +176,7 @@ }, "devDependencies": { "solid-js": "^1.9.9", - "vite-plugin-solid": "^2.11.8" + "vite-plugin-solid": "^2.11.9" }, "peerDependencies": { "solid-js": ">=1.9.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32e94549..098072fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,19 +22,19 @@ importers: version: 1.2.0 '@tanstack/config': specifier: 0.20.3 - version: 0.20.3(@types/node@24.6.1)(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(rollup@4.52.3)(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + version: 0.20.3(@types/node@24.6.1)(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) '@testing-library/jest-dom': - specifier: ^6.8.0 - version: 6.9.0 + specifier: ^6.9.1 + version: 6.9.1 '@types/node': - specifier: ^24.3.1 + specifier: ^24.6.1 version: 24.6.1 eslint: specifier: ^9.36.0 version: 9.36.0(jiti@2.6.0) eslint-plugin-unused-imports: specifier: ^4.2.0 - version: 4.2.0(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)) + version: 4.2.0(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0)) fast-glob: specifier: ^3.3.3 version: 3.3.3 @@ -43,12 +43,12 @@ importers: version: 27.0.0(postcss@8.5.6) knip: specifier: ^5.64.1 - version: 5.64.1(@types/node@24.6.1)(typescript@5.9.2) + version: 5.64.1(@types/node@24.6.1)(typescript@5.9.3) markdown-link-extractor: specifier: ^4.0.2 version: 4.0.2 nx: - specifier: ^21.5.3 + specifier: ^21.6.2 version: 21.6.2 premove: specifier: ^4.0.0 @@ -69,8 +69,8 @@ importers: specifier: ^11.2.0 version: 11.2.0 typescript: - specifier: 5.9.2 - version: 5.9.2 + specifier: 5.9.3 + version: 5.9.3 vite: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) @@ -91,11 +91,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -116,11 +116,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -141,11 +141,36 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) + '@vitejs/plugin-react': + specifier: ^5.0.4 + version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + vite: + specifier: ^7.1.7 + version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) + + examples/react/asyncRetry: + dependencies: + '@tanstack/react-pacer': + specifier: ^0.16.4 + version: link:../../../packages/react-pacer + react: + specifier: ^19.1.1 + version: 19.1.1 + react-dom: + specifier: ^19.1.1 + version: 19.1.1(react@19.1.1) + devDependencies: + '@types/react': + specifier: ^19.1.17 + version: 19.2.0 + '@types/react-dom': + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -166,11 +191,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -191,11 +216,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -216,11 +241,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -241,17 +266,17 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@tanstack/react-devtools': - specifier: 0.7.0 - version: 0.7.0(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9) + specifier: 0.7.1 + version: 0.7.1(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9) '@tanstack/react-pacer-devtools': specifier: 0.3.1 version: link:../../../packages/react-pacer-devtools '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -272,11 +297,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -303,11 +328,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -334,11 +359,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -365,11 +390,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -390,11 +415,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -415,11 +440,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -440,11 +465,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -465,11 +490,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -490,11 +515,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -515,11 +540,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -540,11 +565,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -568,11 +593,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -596,11 +621,36 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) + '@vitejs/plugin-react': + specifier: ^5.0.4 + version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + vite: + specifier: ^7.1.7 + version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) + + examples/react/useAsyncThrottledCallback: + dependencies: + '@tanstack/react-pacer': + specifier: ^0.16.4 + version: link:../../../packages/react-pacer + react: + specifier: ^19.1.1 + version: 19.1.1 + react-dom: + specifier: ^19.1.1 + version: 19.1.1(react@19.1.1) + devDependencies: + '@types/react': + specifier: ^19.1.17 + version: 19.2.0 + '@types/react-dom': + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -621,11 +671,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -646,11 +696,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -671,11 +721,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -696,11 +746,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -721,11 +771,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -746,11 +796,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -771,11 +821,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -796,11 +846,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -821,11 +871,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -849,17 +899,17 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@tanstack/react-devtools': - specifier: 0.7.0 - version: 0.7.0(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9) + specifier: 0.7.1 + version: 0.7.1(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9) '@tanstack/react-pacer-devtools': specifier: 0.3.1 version: link:../../../packages/react-pacer-devtools '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -883,11 +933,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -908,11 +958,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -933,11 +983,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -958,11 +1008,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -986,11 +1036,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1014,11 +1064,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1039,11 +1089,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1064,11 +1114,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1089,11 +1139,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1114,11 +1164,11 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1139,17 +1189,17 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@tanstack/react-devtools': - specifier: 0.7.0 - version: 0.7.0(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9) + specifier: 0.7.1 + version: 0.7.1(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9) '@tanstack/react-pacer-devtools': specifier: 0.3.1 version: link:../../../packages/react-pacer-devtools '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.16) + specifier: ^19.1.11 + version: 19.2.0(@types/react@19.2.0) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1170,8 +1220,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/asyncDebounce: dependencies: @@ -1186,8 +1236,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/asyncRateLimit: dependencies: @@ -1202,8 +1252,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/asyncThrottle: dependencies: @@ -1218,8 +1268,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/batch: dependencies: @@ -1234,8 +1284,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createAsyncBatcher: dependencies: @@ -1250,8 +1300,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createAsyncDebouncer: dependencies: @@ -1266,8 +1316,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createAsyncQueuer: dependencies: @@ -1282,8 +1332,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createAsyncRateLimiter: dependencies: @@ -1298,8 +1348,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createAsyncThrottler: dependencies: @@ -1314,8 +1364,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createBatcher: dependencies: @@ -1330,8 +1380,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createDebouncedSignal: dependencies: @@ -1346,8 +1396,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createDebouncedValue: dependencies: @@ -1362,8 +1412,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createDebouncer: dependencies: @@ -1378,14 +1428,14 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createQueuer: dependencies: '@tanstack/solid-devtools': - specifier: 0.7.0 - version: 0.7.0(csstype@3.1.3)(solid-js@1.9.9) + specifier: 0.7.1 + version: 0.7.1(csstype@3.1.3)(solid-js@1.9.9) '@tanstack/solid-pacer': specifier: ^0.14.4 version: link:../../../packages/solid-pacer @@ -1400,8 +1450,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createRateLimitedSignal: dependencies: @@ -1416,8 +1466,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createRateLimitedValue: dependencies: @@ -1432,8 +1482,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createRateLimiter: dependencies: @@ -1448,8 +1498,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createThrottledSignal: dependencies: @@ -1464,8 +1514,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createThrottledValue: dependencies: @@ -1480,8 +1530,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/createThrottler: dependencies: @@ -1496,8 +1546,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/debounce: dependencies: @@ -1512,14 +1562,14 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/queue: dependencies: '@tanstack/solid-devtools': - specifier: 0.7.0 - version: 0.7.0(csstype@3.1.3)(solid-js@1.9.9) + specifier: 0.7.1 + version: 0.7.1(csstype@3.1.3)(solid-js@1.9.9) '@tanstack/solid-pacer': specifier: ^0.14.4 version: link:../../../packages/solid-pacer @@ -1534,8 +1584,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/rateLimit: dependencies: @@ -1550,8 +1600,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) examples/solid/throttle: dependencies: @@ -1566,8 +1616,8 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) packages/pacer: dependencies: @@ -1585,7 +1635,7 @@ importers: version: 0.3.5(csstype@3.1.3)(solid-js@1.9.9) '@tanstack/devtools-utils': specifier: ^0.0.3 - version: 0.0.3(@types/react@19.1.16)(csstype@3.1.3)(react@19.1.1)(solid-js@1.9.9) + version: 0.0.3(@types/react@19.2.0)(csstype@3.1.3)(react@19.1.1)(solid-js@1.9.9) '@tanstack/pacer': specifier: '>=0.15.0' version: link:../pacer @@ -1606,8 +1656,8 @@ importers: version: 1.9.9 devDependencies: vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) packages/react-pacer: dependencies: @@ -1616,17 +1666,17 @@ importers: version: link:../pacer '@tanstack/react-store': specifier: ^0.7.7 - version: 0.7.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 0.7.7(react-dom@19.1.0(react@19.1.1))(react@19.1.1) react-dom: specifier: '>=16.8' - version: 19.1.1(react@19.1.1) + version: 19.1.0(react@19.1.1) devDependencies: '@eslint-react/eslint-plugin': - specifier: ^1.53.1 - version: 1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2) + specifier: ^2.0.4 + version: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@types/react': - specifier: ^19.1.15 - version: 19.1.16 + specifier: ^19.1.17 + version: 19.2.0 '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1644,7 +1694,7 @@ importers: dependencies: '@tanstack/devtools-utils': specifier: ^0.0.3 - version: 0.0.3(@types/react@19.1.16)(csstype@3.1.3)(react@19.1.1)(solid-js@1.9.9) + version: 0.0.3(@types/react@19.1.16)(react@19.1.1)(solid-js@1.9.9) '@tanstack/pacer-devtools': specifier: workspace:* version: link:../pacer-devtools @@ -1662,8 +1712,8 @@ importers: version: 19.1.1(react@19.1.1) devDependencies: '@eslint-react/eslint-plugin': - specifier: ^1.53.1 - version: 1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2) + specifier: ^2.0.4 + version: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^5.0.4 version: 5.0.4(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) @@ -1687,30 +1737,34 @@ importers: specifier: ^1.9.9 version: 1.9.9 vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) packages/solid-pacer-devtools: dependencies: '@tanstack/devtools-utils': specifier: ^0.0.3 - version: 0.0.3(@types/react@19.1.16)(csstype@3.1.3)(react@19.1.1)(solid-js@1.9.9) + version: 0.0.3(@types/react@19.2.0)(react@19.1.1)(solid-js@1.9.7) '@tanstack/pacer-devtools': specifier: workspace:* version: link:../pacer-devtools solid-js: specifier: '>=1.9.7' - version: 1.9.9 + version: 1.9.7 devDependencies: vite-plugin-solid: - specifier: ^2.11.8 - version: 2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + specifier: ^2.11.9 + version: 2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.7)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@asamuzakjp/css-color@4.0.5': resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} @@ -1720,6 +1774,10 @@ packages: '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -1728,10 +1786,26 @@ packages: resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + '@babel/core@7.28.4': resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} + '@babel/generator@7.26.9': + resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.28.3': resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} @@ -1740,6 +1814,10 @@ packages: resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.26.5': + resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.27.2': resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} @@ -1762,10 +1840,20 @@ packages: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.3': resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} @@ -1790,22 +1878,48 @@ packages: resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.4': resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/parser@7.28.4': resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} @@ -1840,14 +1954,46 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} + '@babel/template@7.26.9': + resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.26.9': + resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.28.4': resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.9': + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.0': + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} @@ -2127,39 +2273,36 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-react/ast@1.53.1': - resolution: {integrity: sha512-qvUC99ewtriJp9quVEOvZ6+RHcsMLfVQ0OhZ4/LupZUDhjW7GiX1dxJsFaxHdJ9rLNLhQyLSPmbAToeqUrSruQ==} - engines: {node: '>=18.18.0'} + '@eslint-react/ast@2.0.4': + resolution: {integrity: sha512-n05swQRVYbeAi0kLXrnrxHmfacptOOlZzpCKSt8L2fobVx+xgMc0w+trRnyrlwcjGnf3IC1ZYT8h/AuqE4OJdw==} + engines: {node: '>=20.19.0'} - '@eslint-react/core@1.53.1': - resolution: {integrity: sha512-8prroos5/Uvvh8Tjl1HHCpq4HWD3hV9tYkm7uXgKA6kqj0jHlgRcQzuO6ZPP7feBcK3uOeug7xrq03BuG8QKCA==} - engines: {node: '>=18.18.0'} + '@eslint-react/core@2.0.4': + resolution: {integrity: sha512-TJDvUdID0fUhXrOlgK992WhEV2qOvmYdjE+bPLVLPFU2eVk0li0L+G9w+KWKDzUdIS2OVjIxmDWdg98eqJOyfQ==} + engines: {node: '>=20.19.0'} - '@eslint-react/eff@1.53.1': - resolution: {integrity: sha512-uq20lPRAmsWRjIZm+mAV/2kZsU2nDqn5IJslxGWe3Vfdw23hoyhEw3S1KKlxbftwbTvsZjKvVP0iw3bZo/NUpg==} - engines: {node: '>=18.18.0'} + '@eslint-react/eff@2.0.4': + resolution: {integrity: sha512-5+cEbTkpjP+sEszzOEF4lW176uSaez7S0t1jqXBQ0RortHVrm9OGPY9zx2ehNozYa2hqvztQa6mgmRMZGeC8xg==} + engines: {node: '>=20.19.0'} - '@eslint-react/eslint-plugin@1.53.1': - resolution: {integrity: sha512-JZ2ciXNCC9CtBBAqYtwWH+Jy/7ZzLw+whei8atP4Fxsbh+Scs30MfEwBzuiEbNw6uF9eZFfPidchpr5RaEhqxg==} - engines: {node: '>=18.18.0'} + '@eslint-react/eslint-plugin@2.0.4': + resolution: {integrity: sha512-jrYOfOW5YYJMQfekn3SctF0QD2WhVrjqrJ1yw+FBEMgL3eie7Jxkm4OerDVGP5+M6kjAaEew89ifNsMJKKNRbQ==} + engines: {node: '>=20.19.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + eslint: ^9.36.0 + typescript: ^5.9.2 - '@eslint-react/kit@1.53.1': - resolution: {integrity: sha512-zOi2le9V4rMrJvQV4OeedGvMGvDT46OyFPOwXKs7m0tQu5vXVJ8qwIPaVQT1n/WIuvOg49OfmAVaHpGxK++xLQ==} - engines: {node: '>=18.18.0'} + '@eslint-react/kit@2.0.4': + resolution: {integrity: sha512-v/kG7X+vfMIfquKZOtXMgfZPp6+tVds97mBpmvQvpivd5XYLM3SrR/cODa0EpvOy6mF0XO4RCkJqjAptaJKKew==} + engines: {node: '>=20.19.0'} - '@eslint-react/shared@1.53.1': - resolution: {integrity: sha512-gomJQmFqQgQVI3Ra4vTMG/s6a4bx3JqeNiTBjxBJt4C9iGaBj458GkP4LJHX7TM6xUzX+fMSKOPX7eV3C/+UCw==} - engines: {node: '>=18.18.0'} + '@eslint-react/shared@2.0.4': + resolution: {integrity: sha512-nO1pO8GBNiFyoSFfplHG/cni0INs70OoKIgZtnKkm73L99h2sxX1S+ZH+f5p2vzltDYVaiJ41xzwMuuf+57ubA==} + engines: {node: '>=20.19.0'} - '@eslint-react/var@1.53.1': - resolution: {integrity: sha512-yzwopvPntcHU7mmDvWzRo1fb8QhjD8eDRRohD11rTV1u7nWO4QbJi0pOyugQakvte1/W11Y0Vr8Of0Ojk/A6zg==} - engines: {node: '>=18.18.0'} + '@eslint-react/var@2.0.4': + resolution: {integrity: sha512-lgw3Hv8vH3dh9e7FacX+IoBdfckvg1j77Nlei4T7kYzj4ziD0wziwQ6foLfKwfdvbgHbw2pBJmkwgs0iSC6w6w==} + engines: {node: '>=20.19.0'} '@eslint/config-array@0.21.0': resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} @@ -2244,6 +2387,10 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + '@jridgewell/remapping@2.3.5': resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} @@ -2251,6 +2398,10 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -2693,8 +2844,8 @@ packages: solid-js: optional: true - '@tanstack/devtools@0.6.14': - resolution: {integrity: sha512-dOtHoeLjjcHeNscu+ZEf89EFboQsy0ggb6pf8Sha59qBUeQbjUsaAvwP8Ogwg89oJxFQbTP7DKYNBNw5CxlNEA==} + '@tanstack/devtools@0.6.15': + resolution: {integrity: sha512-rfa1Kb0wrvsn4eYsCnYXuTzK2BEmHXCEmk3kuGdbZrHo2UTqjxFRK4E1NEGxd1yUgkzbn1VToO3GzDdt9mQTwA==} engines: {node: '>=18'} peerDependencies: solid-js: '>=1.9.7' @@ -2717,8 +2868,8 @@ packages: '@tanstack/query-devtools@5.90.1': resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} - '@tanstack/react-devtools@0.7.0': - resolution: {integrity: sha512-HJH+oNBYQotgsKWAQqvkY8KnfQbbZptHbrkPGVaIwj393vVFGT1BuXMYy+rlmOYxczyerb90ltRFgsQyUtJTuw==} + '@tanstack/react-devtools@0.7.1': + resolution: {integrity: sha512-PluCZ9ytBVKJrgaNYC9wcNfwwm6ysGZampUiXCs9A4HmXA7moAfcxbLKnQ+EFsuF4bg5Qqn/9ofhKxpvP1Fcmg==} engines: {node: '>=18'} peerDependencies: '@types/react': '>=16.8' @@ -2750,8 +2901,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/solid-devtools@0.7.0': - resolution: {integrity: sha512-BOwIadgyg0ISx7awjbCQEDwelMExZ2vuLQ6NO2Z7H2DSPPBultIBEWPK//cZhvdVrDaTmsEK+AvMDWI4SsSQ8w==} + '@tanstack/solid-devtools@0.7.1': + resolution: {integrity: sha512-YoWS6EqXkiWGfpPTlcF53XPjB4s6BtpAsBiI96prYWtAUghJTQ0v4zbAMK1m1uCemVXZMhNTUDPpcpzuuSIcTw==} engines: {node: '>=18'} peerDependencies: solid-js: '>=1.9.7' @@ -2772,8 +2923,8 @@ packages: resolution: {integrity: sha512-p1HuuSD3OUoVMYHfjkzUJqVBUurp1BDrGwT3302GRpSjpBHiJvZex3+NzRIXdG9NdjeJ0mT7DYpNMoii7GVROQ==} engines: {node: '>=18'} - '@testing-library/jest-dom@6.9.0': - resolution: {integrity: sha512-QHdxYMJ0YPGKYofMc6zYvo7LOViVhdc6nPg/OtM2cf9MQrwEcTxFCs7d/GJ5eSyPkHzOiBkc/KfLdFJBHzldtQ==} + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} '@tybys/wasm-util@0.10.1': @@ -2826,9 +2977,17 @@ packages: peerDependencies: '@types/react': ^19.0.0 + '@types/react-dom@19.2.0': + resolution: {integrity: sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react@19.1.16': resolution: {integrity: sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==} + '@types/react@19.2.0': + resolution: {integrity: sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -3550,35 +3709,26 @@ packages: peerDependencies: eslint: '>=7' - eslint-plugin-react-debug@1.53.1: - resolution: {integrity: sha512-WNOiQ6jhodJE88VjBU/IVDM+2Zr9gKHlBFDUSA3fQ0dMB5RiBVj5wMtxbxRuipK/GqNJbteqHcZoYEod7nfddg==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-debug@2.0.4: + resolution: {integrity: sha512-0H4ELREV7fr1eA4be7QQZHiR/rzxhV3iquR/HQSQy5Ab3x7Bv7fTdA3dhWOlF9m/XTtfeNKz5iD66u9XFTGQzA==} + engines: {node: '>=20.19.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + eslint: ^9.36.0 + typescript: ^5.9.2 - eslint-plugin-react-dom@1.53.1: - resolution: {integrity: sha512-UYrWJ2cS4HpJ1A5XBuf1HfMpPoLdfGil+27g/ldXfGemb4IXqlxHt4ANLyC8l2CWcE3SXGJW7mTslL34MG0qTQ==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-dom@2.0.4: + resolution: {integrity: sha512-YQ7BAaFPEvh+sjVJp7STeCk5Uz6cKTVOXFAiOKYKX1FJ34xHhma1csfkDwIgEyr37mglS6wn/VRkS5HGx2/cHw==} + engines: {node: '>=20.19.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + eslint: ^9.36.0 + typescript: ^5.9.2 - eslint-plugin-react-hooks-extra@1.53.1: - resolution: {integrity: sha512-fshTnMWNn9NjFLIuy7HzkRgGK29vKv4ZBO9UMr+kltVAfKLMeXXP6021qVKk66i/XhQjbktiS+vQsu1Rd3ZKvg==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-hooks-extra@2.0.4: + resolution: {integrity: sha512-K4XEvsRD1NWj4phthkOMgBpRnexyP6f1+GLBv0i+Bo2sxS7qP/y/UgI5m8ZI6xCuh0H+MqWrUq1a8eku3hdb7g==} + engines: {node: '>=20.0.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + eslint: ^9.36.0 + typescript: ^5.9.2 eslint-plugin-react-hooks@5.2.0: resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} @@ -3586,38 +3736,26 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react-naming-convention@1.53.1: - resolution: {integrity: sha512-rvZ/B/CSVF8d34HQ4qIt90LRuxotVx+KUf3i1OMXAyhsagEFMRe4gAlPJiRufZ+h9lnuu279bEdd+NINsXOteA==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-naming-convention@2.0.4: + resolution: {integrity: sha512-/7cAWZyW/M5HH4WolPb9CwCf4HxribmQQ/tBrg28hexVx8EPDZO+PbONyESC6DvF2Y1P2N8lYo657h+y0tJhTg==} + engines: {node: '>=20.19.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + eslint: ^9.36.0 + typescript: ^5.9.2 - eslint-plugin-react-web-api@1.53.1: - resolution: {integrity: sha512-INVZ3Cbl9/b+sizyb43ChzEPXXYuDsBGU9BIg7OVTNPyDPloCXdI+dQFAcSlDocZhPrLxhPV3eT6+gXbygzYXg==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-web-api@2.0.4: + resolution: {integrity: sha512-U4ql2C+AQVnZeQ+nxBb3A8NA58W4tx4Fdc+cEro5/QMJKuiXltNUnhAJ5N4ttS6K0D/Iz1nHtkyULuWXFq+p1w==} + engines: {node: '>=20.19.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - typescript: - optional: true + eslint: ^9.36.0 + typescript: ^5.9.2 - eslint-plugin-react-x@1.53.1: - resolution: {integrity: sha512-MwMNnVwiPem0U6SlejDF/ddA4h/lmP6imL1RDZ2m3pUBrcdcOwOx0gyiRVTA3ENnhRlWfHljHf5y7m8qDSxMEg==} - engines: {node: '>=18.18.0'} + eslint-plugin-react-x@2.0.4: + resolution: {integrity: sha512-9YqZGzu5lvevtzQjkaQZut2dMvOl9bjNgn+LUcwBdhqWkvoD7dFC6I0p6jgIZdA4ql8CGoYBlcYqve55wddOWw==} + engines: {node: '>=20.19.0'} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - ts-api-utils: ^2.1.0 - typescript: ^4.9.5 || ^5.3.3 - peerDependenciesMeta: - ts-api-utils: - optional: true - typescript: - optional: true + eslint: ^9.36.0 + typescript: ^5.9.2 eslint-plugin-unused-imports@4.2.0: resolution: {integrity: sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w==} @@ -3713,6 +3851,14 @@ packages: fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -3820,6 +3966,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -4019,6 +4169,10 @@ packages: resolution: {integrity: sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + jiti@2.6.0: resolution: {integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==} hasBin: true @@ -4373,6 +4527,9 @@ packages: parse5-parser-stream@7.1.2: resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -4408,6 +4565,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -4474,6 +4635,11 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + react-dom@19.1.1: resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} peerDependencies: @@ -4667,6 +4833,9 @@ packages: resolution: {integrity: sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==} engines: {node: '>= 18'} + solid-js@1.9.7: + resolution: {integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==} + solid-js@1.9.9: resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==} @@ -4784,6 +4953,14 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -4895,8 +5072,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true @@ -4964,8 +5141,8 @@ packages: peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 - vite-plugin-solid@2.11.8: - resolution: {integrity: sha512-hFrCxBfv3B1BmFqnJF4JOCYpjrmi/zwyeKjcomQ0khh8HFyQ8SbuBWQ7zGojfrz6HUOBFrJBNySDi/JgAHytWg==} + vite-plugin-solid@2.11.9: + resolution: {integrity: sha512-bTA6p+bspXZsuulSd2y6aTzegF8xGaJYcq1Uyh/mv+W4DQtzCgL9nN6n2fsTaxp/dMk+ZHHKgGndlNeooqHLKw==} peerDependencies: '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* solid-js: ^1.7.2 @@ -5186,6 +5363,11 @@ snapshots: '@adobe/css-tools@4.4.4': {} + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@asamuzakjp/css-color@4.0.5': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -5204,6 +5386,12 @@ snapshots: '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -5212,6 +5400,26 @@ snapshots: '@babel/compat-data@7.28.4': {} + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@7.28.4': dependencies: '@babel/code-frame': 7.27.1 @@ -5232,6 +5440,30 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/generator@7.26.9': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/generator@7.28.3': dependencies: '@babel/parser': 7.28.4 @@ -5244,6 +5476,14 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.28.4 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.26.2 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-compilation-targets@7.27.2': dependencies: '@babel/compat-data': 7.28.4 @@ -5252,13 +5492,13 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4)': + '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.26.10 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.10) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/traverse': 7.28.4 semver: 6.3.1 @@ -5278,10 +5518,26 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.9 + '@babel/types': 7.26.9 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.9 transitivePeerDependencies: - supports-color @@ -5300,9 +5556,9 @@ snapshots: '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': + '@babel/helper-replace-supers@7.27.1(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.26.10 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/traverse': 7.28.4 @@ -5316,25 +5572,44 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-validator-identifier@7.25.9': {} + '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-option@7.25.9': {} + '@babel/helper-validator-option@7.27.1': {} + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 '@babel/types': 7.28.4 + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.0 + '@babel/parser@7.28.4': dependencies: '@babel/types': 7.28.4 - '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.28.4)': + '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -5356,11 +5631,59 @@ snapshots: '@babel/runtime@7.28.4': {} + '@babel/template@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.4 + + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.28.0 + '@babel/types': 7.27.0 + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + + '@babel/traverse@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.9 + '@babel/parser': 7.28.0 + '@babel/template': 7.26.9 + '@babel/types': 7.26.9 + debug: 4.4.3 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.4.3 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.28.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color '@babel/traverse@7.28.4': dependencies: @@ -5374,6 +5697,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/types@7.26.9': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@babel/types@7.28.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -5663,30 +6001,28 @@ snapshots: '@eslint-community/regexpp@4.12.1': {} - '@eslint-react/ast@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@eslint-react/ast@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 1.53.1 + '@eslint-react/eff': 2.0.4 '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) string-ts: 2.2.1 - ts-pattern: 5.8.0 transitivePeerDependencies: - eslint - supports-color - typescript - '@eslint-react/core@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@eslint-react/core@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/var': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) birecord: 0.1.1 ts-pattern: 5.8.0 transitivePeerDependencies: @@ -5694,46 +6030,43 @@ snapshots: - supports-color - typescript - '@eslint-react/eff@1.53.1': {} + '@eslint-react/eff@2.0.4': {} - '@eslint-react/eslint-plugin@1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2)': + '@eslint-react/eslint-plugin@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) - eslint-plugin-react-debug: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint-plugin-react-dom: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint-plugin-react-hooks-extra: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint-plugin-react-naming-convention: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint-plugin-react-web-api: 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - eslint-plugin-react-x: 1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2) - optionalDependencies: - typescript: 5.9.2 + eslint-plugin-react-debug: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + eslint-plugin-react-dom: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + eslint-plugin-react-hooks-extra: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + eslint-plugin-react-naming-convention: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + eslint-plugin-react-web-api: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + eslint-plugin-react-x: 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - - ts-api-utils - '@eslint-react/kit@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@eslint-react/kit@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 1.53.1 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - ts-pattern: 5.8.0 - zod: 4.1.11 + '@eslint-react/eff': 2.0.4 + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) transitivePeerDependencies: - eslint - supports-color - typescript - '@eslint-react/shared@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@eslint-react/shared@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) ts-pattern: 5.8.0 zod: 4.1.11 transitivePeerDependencies: @@ -5741,14 +6074,13 @@ snapshots: - supports-color - typescript - '@eslint-react/var@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@eslint-react/var@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 '@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - string-ts: 2.2.1 + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) ts-pattern: 5.8.0 transitivePeerDependencies: - eslint @@ -5837,6 +6169,12 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/remapping@2.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -5844,6 +6182,8 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/set-array@1.2.1': {} + '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.31': @@ -6215,12 +6555,12 @@ snapshots: transitivePeerDependencies: - encoding - '@tanstack/config@0.20.3(@types/node@24.6.1)(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(rollup@4.52.3)(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1))': + '@tanstack/config@0.20.3(@types/node@24.6.1)(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1))': dependencies: - '@tanstack/eslint-config': 0.3.2(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@tanstack/eslint-config': 0.3.2(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@tanstack/publish-config': 0.2.1 - '@tanstack/typedoc-config': 0.2.1(typescript@5.9.2) - '@tanstack/vite-config': 0.3.0(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + '@tanstack/typedoc-config': 0.2.1(typescript@5.9.3) + '@tanstack/vite-config': 0.3.0(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) transitivePeerDependencies: - '@types/node' - '@typescript-eslint/utils' @@ -6248,9 +6588,25 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools-utils@0.0.3(@types/react@19.1.16)(csstype@3.1.3)(react@19.1.1)(solid-js@1.9.9)': + '@tanstack/devtools-ui@0.3.5(solid-js@1.9.7)': dependencies: - '@tanstack/devtools-ui': 0.3.5(csstype@3.1.3)(solid-js@1.9.9) + clsx: 2.1.1 + goober: 2.1.16(csstype@3.1.3) + solid-js: 1.9.7 + transitivePeerDependencies: + - csstype + + '@tanstack/devtools-ui@0.3.5(solid-js@1.9.9)': + dependencies: + clsx: 2.1.1 + goober: 2.1.16(csstype@3.1.3) + solid-js: 1.9.9 + transitivePeerDependencies: + - csstype + + '@tanstack/devtools-utils@0.0.3(@types/react@19.1.16)(react@19.1.1)(solid-js@1.9.9)': + dependencies: + '@tanstack/devtools-ui': 0.3.5(solid-js@1.9.9) optionalDependencies: '@types/react': 19.1.16 react: 19.1.1 @@ -6258,7 +6614,27 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools@0.6.14(csstype@3.1.3)(solid-js@1.9.9)': + '@tanstack/devtools-utils@0.0.3(@types/react@19.2.0)(csstype@3.1.3)(react@19.1.1)(solid-js@1.9.9)': + dependencies: + '@tanstack/devtools-ui': 0.3.5(csstype@3.1.3)(solid-js@1.9.9) + optionalDependencies: + '@types/react': 19.2.0 + react: 19.1.1 + solid-js: 1.9.9 + transitivePeerDependencies: + - csstype + + '@tanstack/devtools-utils@0.0.3(@types/react@19.2.0)(react@19.1.1)(solid-js@1.9.7)': + dependencies: + '@tanstack/devtools-ui': 0.3.5(solid-js@1.9.7) + optionalDependencies: + '@types/react': 19.2.0 + react: 19.1.1 + solid-js: 1.9.7 + transitivePeerDependencies: + - csstype + + '@tanstack/devtools@0.6.15(csstype@3.1.3)(solid-js@1.9.9)': dependencies: '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.9) '@tanstack/devtools-event-bus': 0.3.2 @@ -6271,14 +6647,14 @@ snapshots: - csstype - utf-8-validate - '@tanstack/eslint-config@0.3.2(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@tanstack/eslint-config@0.3.2(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: '@eslint/js': 9.36.0 '@stylistic/eslint-plugin': 5.4.0(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-n: 17.23.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0)) + eslint-plugin-n: 17.23.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) globals: 16.4.0 - typescript-eslint: 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + typescript-eslint: 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) vue-eslint-parser: 10.2.0(eslint@9.36.0(jiti@2.6.0)) transitivePeerDependencies: - '@typescript-eslint/utils' @@ -6302,11 +6678,11 @@ snapshots: '@tanstack/query-devtools@5.90.1': {} - '@tanstack/react-devtools@0.7.0(@types/react-dom@19.1.9(@types/react@19.1.16))(@types/react@19.1.16)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9)': + '@tanstack/react-devtools@0.7.1(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9)': dependencies: - '@tanstack/devtools': 0.6.14(csstype@3.1.3)(solid-js@1.9.9) - '@types/react': 19.1.16 - '@types/react-dom': 19.1.9(@types/react@19.1.16) + '@tanstack/devtools': 0.6.15(csstype@3.1.3)(solid-js@1.9.9) + '@types/react': 19.2.0 + '@types/react-dom': 19.2.0(@types/react@19.2.0) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) transitivePeerDependencies: @@ -6332,16 +6708,16 @@ snapshots: '@tanstack/query-core': 5.90.2 react: 19.1.1 - '@tanstack/react-store@0.7.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@tanstack/react-store@0.7.7(react-dom@19.1.0(react@19.1.1))(react@19.1.1)': dependencies: '@tanstack/store': 0.7.7 react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react-dom: 19.1.0(react@19.1.1) use-sync-external-store: 1.5.0(react@19.1.1) - '@tanstack/solid-devtools@0.7.0(csstype@3.1.3)(solid-js@1.9.9)': + '@tanstack/solid-devtools@0.7.1(csstype@3.1.3)(solid-js@1.9.9)': dependencies: - '@tanstack/devtools': 0.6.14(csstype@3.1.3)(solid-js@1.9.9) + '@tanstack/devtools': 0.6.15(csstype@3.1.3)(solid-js@1.9.9) solid-js: 1.9.9 transitivePeerDependencies: - bufferutil @@ -6355,20 +6731,20 @@ snapshots: '@tanstack/store@0.7.7': {} - '@tanstack/typedoc-config@0.2.1(typescript@5.9.2)': + '@tanstack/typedoc-config@0.2.1(typescript@5.9.3)': dependencies: - typedoc: 0.27.9(typescript@5.9.2) - typedoc-plugin-frontmatter: 1.2.1(typedoc-plugin-markdown@4.4.2(typedoc@0.27.9(typescript@5.9.2))) - typedoc-plugin-markdown: 4.4.2(typedoc@0.27.9(typescript@5.9.2)) + typedoc: 0.27.9(typescript@5.9.3) + typedoc-plugin-frontmatter: 1.2.1(typedoc-plugin-markdown@4.4.2(typedoc@0.27.9(typescript@5.9.3))) + typedoc-plugin-markdown: 4.4.2(typedoc@0.27.9(typescript@5.9.3)) transitivePeerDependencies: - typescript - '@tanstack/vite-config@0.3.0(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1))': + '@tanstack/vite-config@0.3.0(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1))': dependencies: rollup-plugin-preserve-directives: 0.4.0(rollup@4.52.3) - vite-plugin-dts: 4.2.3(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + vite-plugin-dts: 4.2.3(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) vite-plugin-externalize-deps: 0.9.0(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) - vite-tsconfig-paths: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + vite-tsconfig-paths: 5.1.4(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) transitivePeerDependencies: - '@types/node' - rollup @@ -6376,7 +6752,7 @@ snapshots: - typescript - vite - '@testing-library/jest-dom@6.9.0': + '@testing-library/jest-dom@6.9.1': dependencies: '@adobe/css-tools': 4.4.4 aria-query: 5.3.2 @@ -6406,12 +6782,12 @@ snapshots: '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.27.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.0 + '@babel/types': 7.27.0 '@types/babel__traverse@7.28.0': dependencies: @@ -6445,47 +6821,55 @@ snapshots: dependencies: '@types/react': 19.1.16 + '@types/react-dom@19.2.0(@types/react@19.2.0)': + dependencies: + '@types/react': 19.2.0 + '@types/react@19.1.16': dependencies: csstype: 3.1.3 + '@types/react@19.2.0': + dependencies: + csstype: 3.1.3 + '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.45.0 eslint: 9.36.0(jiti@2.6.0) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.45.0 debug: 4.4.3 eslint: 9.36.0(jiti@2.6.0) - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.45.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.45.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3) '@typescript-eslint/types': 8.45.0 debug: 4.4.3 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6494,28 +6878,28 @@ snapshots: '@typescript-eslint/types': 8.45.0 '@typescript-eslint/visitor-keys': 8.45.0 - '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.3)': dependencies: - typescript: 5.9.2 + typescript: 5.9.3 - '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) debug: 4.4.3 eslint: 9.36.0(jiti@2.6.0) - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@8.45.0': {} - '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.45.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.2) + '@typescript-eslint/project-service': 8.45.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3) '@typescript-eslint/types': 8.45.0 '@typescript-eslint/visitor-keys': 8.45.0 debug: 4.4.3 @@ -6523,19 +6907,19 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2)': + '@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) '@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -6687,7 +7071,7 @@ snapshots: de-indent: 1.0.2 he: 1.2.0 - '@vue/language-core@2.1.6(typescript@5.9.2)': + '@vue/language-core@2.1.6(typescript@5.9.3)': dependencies: '@volar/language-core': 2.4.23 '@vue/compiler-dom': 3.5.22 @@ -6698,7 +7082,7 @@ snapshots: muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 '@vue/shared@3.5.22': {} @@ -6801,6 +7185,13 @@ snapshots: parse5: 7.3.0 validate-html-nesting: 1.2.3 + babel-preset-solid@1.9.9(@babel/core@7.28.4)(solid-js@1.9.7): + dependencies: + '@babel/core': 7.28.4 + babel-plugin-jsx-dom-expressions: 0.40.1(@babel/core@7.28.4) + optionalDependencies: + solid-js: 1.9.7 + babel-preset-solid@1.9.9(@babel/core@7.28.4)(solid-js@1.9.9): dependencies: '@babel/core': 7.28.4 @@ -7181,7 +7572,7 @@ snapshots: eslint: 9.36.0(jiti@2.6.0) eslint-compat-utils: 0.5.1(eslint@9.36.0(jiti@2.6.0)) - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0)): dependencies: '@typescript-eslint/types': 8.45.0 comment-parser: 1.4.1 @@ -7194,11 +7585,11 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.11.1 optionalDependencies: - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) transitivePeerDependencies: - supports-color - eslint-plugin-n@17.23.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-plugin-n@17.23.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.0)) enhanced-resolve: 5.18.3 @@ -7209,15 +7600,15 @@ snapshots: globrex: 0.1.2 ignore: 5.3.2 semver: 7.7.2 - ts-declaration-location: 1.0.7(typescript@5.9.2) + ts-declaration-location: 1.0.7(typescript@5.9.3) transitivePeerDependencies: - typescript eslint-plugin-react-compiler@19.1.0-rc.2(eslint@9.36.0(jiti@2.6.0)): dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.28.4) + '@babel/core': 7.26.10 + '@babel/parser': 7.27.0 + '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.26.10) eslint: 9.36.0(jiti@2.6.0) hermes-parser: 0.25.1 zod: 3.25.76 @@ -7225,63 +7616,60 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-debug@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-plugin-react-debug@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/core': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/var': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) string-ts: 2.2.1 ts-pattern: 5.8.0 - optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-dom@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-plugin-react-dom@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/core': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/var': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) compare-versions: 6.1.1 eslint: 9.36.0(jiti@2.6.0) string-ts: 2.2.1 ts-pattern: 5.8.0 - optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks-extra@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-plugin-react-hooks-extra@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/core': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/var': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) string-ts: 2.2.1 ts-pattern: 5.8.0 - optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7289,73 +7677,70 @@ snapshots: dependencies: eslint: 9.36.0(jiti@2.6.0) - eslint-plugin-react-naming-convention@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-plugin-react-naming-convention@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/core': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/var': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) string-ts: 2.2.1 ts-pattern: 5.8.0 - optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-web-api@1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + eslint-plugin-react-web-api@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/core': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/var': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) string-ts: 2.2.1 ts-pattern: 5.8.0 - optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-react-x@1.53.1(eslint@9.36.0(jiti@2.6.0))(ts-api-utils@2.1.0(typescript@5.9.2))(typescript@5.9.2): + eslint-plugin-react-x@2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@eslint-react/ast': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/core': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/eff': 1.53.1 - '@eslint-react/kit': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/shared': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@eslint-react/var': 1.53.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@eslint-react/ast': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/core': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/eff': 2.0.4 + '@eslint-react/kit': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/shared': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@eslint-react/var': 2.0.4(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) compare-versions: 6.1.1 eslint: 9.36.0(jiti@2.6.0) - is-immutable-type: 5.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + is-immutable-type: 5.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) string-ts: 2.2.1 + ts-api-utils: 2.1.0(typescript@5.9.3) ts-pattern: 5.8.0 - optionalDependencies: - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0)): + eslint-plugin-unused-imports@4.2.0(@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0)): dependencies: eslint: 9.36.0(jiti@2.6.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint-scope@8.4.0: dependencies: @@ -7466,6 +7851,10 @@ snapshots: dependencies: walk-up-path: 4.0.0 + fdir@6.4.3(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -7572,6 +7961,8 @@ snapshots: dependencies: is-glob: 4.0.3 + globals@11.12.0: {} + globals@14.0.0: {} globals@15.15.0: {} @@ -7693,13 +8084,13 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-immutable-type@5.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + is-immutable-type@5.0.1(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) - ts-api-utils: 2.1.0(typescript@5.9.2) - ts-declaration-location: 1.0.7(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + ts-declaration-location: 1.0.7(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7742,6 +8133,8 @@ snapshots: chalk: 4.1.2 pretty-format: 30.2.0 + jiti@2.4.2: {} + jiti@2.6.0: {} jju@1.4.0: {} @@ -7817,7 +8210,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - knip@5.64.1(@types/node@24.6.1)(typescript@5.9.2): + knip@5.64.1(@types/node@24.6.1)(typescript@5.9.3): dependencies: '@nodelib/fs.walk': 1.2.8 '@types/node': 24.6.1 @@ -7831,7 +8224,7 @@ snapshots: picomatch: 4.0.3 smol-toml: 1.4.2 strip-json-comments: 5.0.2 - typescript: 5.9.2 + typescript: 5.9.3 zod: 4.1.11 kolorist@1.8.0: {} @@ -8149,11 +8542,15 @@ snapshots: parse5-htmlparser2-tree-adapter@7.1.0: dependencies: domhandler: 5.0.3 - parse5: 7.3.0 + parse5: 7.2.1 parse5-parser-stream@7.1.2: dependencies: - parse5: 7.3.0 + parse5: 7.2.1 + + parse5@7.2.1: + dependencies: + entities: 4.5.0 parse5@7.3.0: dependencies: @@ -8177,6 +8574,8 @@ snapshots: picomatch@2.3.1: {} + picomatch@4.0.2: {} + picomatch@4.0.3: {} pify@4.0.1: {} @@ -8229,6 +8628,11 @@ snapshots: queue-microtask@1.2.3: {} + react-dom@19.1.0(react@19.1.1): + dependencies: + react: 19.1.1 + scheduler: 0.26.0 + react-dom@19.1.1(react@19.1.1): dependencies: react: 19.1.1 @@ -8404,22 +8808,37 @@ snapshots: dependencies: bytes-iec: 3.1.1 chokidar: 4.0.3 - jiti: 2.6.0 + jiti: 2.4.2 lilconfig: 3.1.3 nanospinner: 1.2.2 picocolors: 1.1.1 - tinyglobby: 0.2.15 + tinyglobby: 0.2.12 slash@3.0.0: {} smol-toml@1.4.2: {} + solid-js@1.9.7: + dependencies: + csstype: 3.1.3 + seroval: 1.3.2 + seroval-plugins: 1.3.3(seroval@1.3.2) + solid-js@1.9.9: dependencies: csstype: 3.1.3 seroval: 1.3.2 seroval-plugins: 1.3.3(seroval@1.3.2) + solid-refresh@0.6.3(solid-js@1.9.7): + dependencies: + '@babel/generator': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/types': 7.28.4 + solid-js: 1.9.7 + transitivePeerDependencies: + - supports-color + solid-refresh@0.6.3(solid-js@1.9.9): dependencies: '@babel/generator': 7.28.3 @@ -8529,6 +8948,16 @@ snapshots: tinyexec@0.3.2: {} + tinyglobby@0.2.12: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + + tinyglobby@0.2.14: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -8564,20 +8993,20 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@2.1.0(typescript@5.9.2): + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 - ts-declaration-location@1.0.7(typescript@5.9.2): + ts-declaration-location@1.0.7(typescript@5.9.3): dependencies: picomatch: 4.0.3 - typescript: 5.9.2 + typescript: 5.9.3 ts-pattern@5.8.0: {} - tsconfck@3.1.6(typescript@5.9.2): + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 tsconfig-paths@4.2.0: dependencies: @@ -8591,38 +9020,38 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typedoc-plugin-frontmatter@1.2.1(typedoc-plugin-markdown@4.4.2(typedoc@0.27.9(typescript@5.9.2))): + typedoc-plugin-frontmatter@1.2.1(typedoc-plugin-markdown@4.4.2(typedoc@0.27.9(typescript@5.9.3))): dependencies: - typedoc-plugin-markdown: 4.4.2(typedoc@0.27.9(typescript@5.9.2)) + typedoc-plugin-markdown: 4.4.2(typedoc@0.27.9(typescript@5.9.3)) yaml: 2.8.1 - typedoc-plugin-markdown@4.4.2(typedoc@0.27.9(typescript@5.9.2)): + typedoc-plugin-markdown@4.4.2(typedoc@0.27.9(typescript@5.9.3)): dependencies: - typedoc: 0.27.9(typescript@5.9.2) + typedoc: 0.27.9(typescript@5.9.3) - typedoc@0.27.9(typescript@5.9.2): + typedoc@0.27.9(typescript@5.9.3): dependencies: '@gerrit0/mini-shiki': 1.27.2 lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 - typescript: 5.9.2 + typescript: 5.9.3 yaml: 2.8.1 - typescript-eslint@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2): + typescript-eslint@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.0))(typescript@5.9.3) eslint: 9.36.0(jiti@2.6.0) - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color typescript@5.4.2: {} - typescript@5.9.2: {} + typescript@5.9.3: {} uc.micro@2.1.0: {} @@ -8699,18 +9128,18 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.2.3(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)): + vite-plugin-dts@4.2.3(@types/node@24.6.1)(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)): dependencies: '@microsoft/api-extractor': 7.47.7(@types/node@24.6.1) '@rollup/pluginutils': 5.3.0(rollup@4.52.3) '@volar/typescript': 2.4.23 - '@vue/language-core': 2.1.6(typescript@5.9.2) + '@vue/language-core': 2.1.6(typescript@5.9.3) compare-versions: 6.1.1 debug: 4.4.3 kolorist: 1.8.0 local-pkg: 0.5.1 magic-string: 0.30.19 - typescript: 5.9.2 + typescript: 5.9.3 optionalDependencies: vite: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) transitivePeerDependencies: @@ -8722,7 +9151,22 @@ snapshots: dependencies: vite: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) - vite-plugin-solid@2.11.8(@testing-library/jest-dom@6.9.0)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)): + vite-plugin-solid@2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.7)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)): + dependencies: + '@babel/core': 7.28.4 + '@types/babel__core': 7.20.5 + babel-preset-solid: 1.9.9(@babel/core@7.28.4)(solid-js@1.9.7) + merge-anything: 5.1.7 + solid-js: 1.9.7 + solid-refresh: 0.6.3(solid-js@1.9.7) + vite: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) + vitefu: 1.1.1(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) + optionalDependencies: + '@testing-library/jest-dom': 6.9.1 + transitivePeerDependencies: + - supports-color + + vite-plugin-solid@2.11.9(@testing-library/jest-dom@6.9.1)(solid-js@1.9.9)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)): dependencies: '@babel/core': 7.28.4 '@types/babel__core': 7.20.5 @@ -8733,15 +9177,15 @@ snapshots: vite: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) vitefu: 1.1.1(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)) optionalDependencies: - '@testing-library/jest-dom': 6.9.0 + '@testing-library/jest-dom': 6.9.1 transitivePeerDependencies: - supports-color - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.9.2) + tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: vite: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1) transitivePeerDependencies: @@ -8781,11 +9225,11 @@ snapshots: expect-type: 1.2.2 magic-string: 0.30.19 pathe: 2.0.3 - picomatch: 4.0.3 + picomatch: 4.0.2 std-env: 3.9.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.15 + tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 vite: 7.1.7(@types/node@24.6.1)(jiti@2.6.0)(yaml@2.8.1)