From 0a86348d094aeabe8a27121a6531a2008876d97b Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 10 Jun 2025 01:33:51 -0500 Subject: [PATCH 01/51] feat: add persister util and persistent rate limited examples --- docs/config.json | 46 ++++ .../functions/uselocalstoragestate.md | 53 +++++ .../functions/usesessionstoragestate.md | 53 +++++ .../functions/usestoragepersister.md | 28 +++ docs/framework/react/reference/index.md | 3 + docs/reference/classes/asyncpersister.md | 90 +++++++ docs/reference/classes/asyncratelimiter.md | 32 +-- docs/reference/classes/persister.md | 114 +++++++++ docs/reference/classes/ratelimiter.md | 26 +-- docs/reference/classes/storagepersister.md | 176 ++++++++++++++ docs/reference/functions/asyncratelimit.md | 2 +- docs/reference/functions/ratelimit.md | 2 +- docs/reference/index.md | 7 + .../interfaces/asyncratelimiteroptions.md | 34 ++- .../interfaces/asyncratelimiterstate.md | 86 +++++++ docs/reference/interfaces/persistedstorage.md | 44 ++++ .../interfaces/ratelimiteroptions.md | 26 ++- docs/reference/interfaces/ratelimiterstate.md | 42 ++++ .../interfaces/storagepersisteroptions.md | 221 ++++++++++++++++++ .../react/useAsyncRateLimiter/src/index.tsx | 9 + examples/react/useRateLimiter/src/index.tsx | 7 + package.json | 2 +- packages/pacer/package.json | 20 ++ packages/pacer/src/async-persister.ts | 9 + packages/pacer/src/async-rate-limiter.ts | 164 +++++++++---- packages/pacer/src/index.ts | 2 + packages/pacer/src/persister.ts | 195 ++++++++++++++++ packages/pacer/src/rate-limiter.ts | 113 ++++++--- packages/pacer/tests/persister.test.ts | 132 +++++++++++ packages/react-pacer/package.json | 20 ++ .../react-pacer/src/async-persister/index.ts | 1 + packages/react-pacer/src/index.ts | 3 + packages/react-pacer/src/persister/index.ts | 4 + .../src/persister/useStoragePersister.ts | 16 ++ .../src/persister/useStorageState.ts | 83 +++++++ packages/react-pacer/vite.config.ts | 2 + packages/solid-pacer/package.json | 20 ++ .../solid-pacer/src/async-persister/index.ts | 1 + packages/solid-pacer/src/persister/index.ts | 1 + packages/solid-pacer/vite.config.ts | 2 + 40 files changed, 1776 insertions(+), 115 deletions(-) create mode 100644 docs/framework/react/reference/functions/uselocalstoragestate.md create mode 100644 docs/framework/react/reference/functions/usesessionstoragestate.md create mode 100644 docs/framework/react/reference/functions/usestoragepersister.md create mode 100644 docs/reference/classes/asyncpersister.md create mode 100644 docs/reference/classes/persister.md create mode 100644 docs/reference/classes/storagepersister.md create mode 100644 docs/reference/interfaces/asyncratelimiterstate.md create mode 100644 docs/reference/interfaces/persistedstorage.md create mode 100644 docs/reference/interfaces/ratelimiterstate.md create mode 100644 docs/reference/interfaces/storagepersisteroptions.md create mode 100644 packages/pacer/src/async-persister.ts create mode 100644 packages/pacer/src/persister.ts create mode 100644 packages/pacer/tests/persister.test.ts create mode 100644 packages/react-pacer/src/async-persister/index.ts create mode 100644 packages/react-pacer/src/persister/index.ts create mode 100644 packages/react-pacer/src/persister/useStoragePersister.ts create mode 100644 packages/react-pacer/src/persister/useStorageState.ts create mode 100644 packages/solid-pacer/src/async-persister/index.ts create mode 100644 packages/solid-pacer/src/persister/index.ts diff --git a/docs/config.json b/docs/config.json index ead093a21..f9da70b73 100644 --- a/docs/config.json +++ b/docs/config.json @@ -81,6 +81,10 @@ { "label": "Batching Guide", "to": "guides/batching" + }, + { + "label": "Persisting State Guide", + "to": "guides/persisting-state" } ] }, @@ -456,6 +460,48 @@ } ] }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Persister API Reference", + "children": [ + { + "label": "Persister", + "to": "reference/classes/persister" + }, + { + "label": "PersistedStorage", + "to": "reference/interfaces/persistedstorage" + }, + { + "label": "StoragePersisterOptions", + "to": "reference/interfaces/storagepersisteroptions" + }, + { + "label": "StoragePersister", + "to": "reference/classes/storagepersister" + } + ], + "frameworks": [ + { + "label": "react", + "children": [ + { + "label": "useStoragePersister", + "to": "framework/react/reference/functions/usestoragepersister" + }, + { + "label": "useLocalStorageState", + "to": "framework/react/reference/functions/uselocalstoragestate" + }, + { + "label": "useSessionStorageState", + "to": "framework/react/reference/functions/usesessionstoragestate" + } + ] + } + ] + }, { "label": "Debouncer Examples", "children": [], diff --git a/docs/framework/react/reference/functions/uselocalstoragestate.md b/docs/framework/react/reference/functions/uselocalstoragestate.md new file mode 100644 index 000000000..4a21a680d --- /dev/null +++ b/docs/framework/react/reference/functions/uselocalstoragestate.md @@ -0,0 +1,53 @@ +--- +id: useLocalStorageState +title: useLocalStorageState +--- + + + +# Function: useLocalStorageState() + +```ts +function useLocalStorageState( + key, + initialValue, + options?): readonly [TValue, Dispatch>] +``` + +Defined in: react-pacer/src/persister/useStorageState.ts:47 + +A hook that persists state to localStorage and syncs it across tabs + +## Type Parameters + +• **TValue** + +## Parameters + +### key + +`string` + +### initialValue + +`TValue` + +### options? + +#### buster? + +`string` + +#### maxAge? + +`number` + +## Returns + +readonly \[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>\] + +## Example + +```tsx +const [value, setValue] = useLocalStorageState('my-key', 'initial value') +``` diff --git a/docs/framework/react/reference/functions/usesessionstoragestate.md b/docs/framework/react/reference/functions/usesessionstoragestate.md new file mode 100644 index 000000000..9a118a6ee --- /dev/null +++ b/docs/framework/react/reference/functions/usesessionstoragestate.md @@ -0,0 +1,53 @@ +--- +id: useSessionStorageState +title: useSessionStorageState +--- + + + +# Function: useSessionStorageState() + +```ts +function useSessionStorageState( + key, + initialValue, + options?): readonly [TValue, Dispatch>] +``` + +Defined in: react-pacer/src/persister/useStorageState.ts:70 + +A hook that persists state to sessionStorage and syncs it across tabs + +## Type Parameters + +• **TValue** + +## Parameters + +### key + +`string` + +### initialValue + +`TValue` + +### options? + +#### buster? + +`string` + +#### maxAge? + +`number` + +## Returns + +readonly \[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>\] + +## Example + +```tsx +const [value, setValue] = useSessionStorageState('my-key', 'initial value') +``` diff --git a/docs/framework/react/reference/functions/usestoragepersister.md b/docs/framework/react/reference/functions/usestoragepersister.md new file mode 100644 index 000000000..8478ddb5e --- /dev/null +++ b/docs/framework/react/reference/functions/usestoragepersister.md @@ -0,0 +1,28 @@ +--- +id: useStoragePersister +title: useStoragePersister +--- + + + +# Function: useStoragePersister() + +```ts +function useStoragePersister(options): StoragePersister +``` + +Defined in: react-pacer/src/persister/useStoragePersister.ts:6 + +## Type Parameters + +• **TState** + +## Parameters + +### options + +`StoragePersisterOptions`\<`TState`\> + +## Returns + +`StoragePersister`\<`TState`\> diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md index b1c1e5192..bde6e4642 100644 --- a/docs/framework/react/reference/index.md +++ b/docs/framework/react/reference/index.md @@ -19,6 +19,7 @@ title: "@tanstack/react-pacer" - [useDebouncedState](../functions/usedebouncedstate.md) - [useDebouncedValue](../functions/usedebouncedvalue.md) - [useDebouncer](../functions/usedebouncer.md) +- [useLocalStorageState](../functions/uselocalstoragestate.md) - [useQueuedState](../functions/usequeuedstate.md) - [useQueuedValue](../functions/usequeuedvalue.md) - [useQueuer](../functions/usequeuer.md) @@ -26,6 +27,8 @@ title: "@tanstack/react-pacer" - [useRateLimitedState](../functions/useratelimitedstate.md) - [useRateLimitedValue](../functions/useratelimitedvalue.md) - [useRateLimiter](../functions/useratelimiter.md) +- [useSessionStorageState](../functions/usesessionstoragestate.md) +- [useStoragePersister](../functions/usestoragepersister.md) - [useThrottledCallback](../functions/usethrottledcallback.md) - [useThrottledState](../functions/usethrottledstate.md) - [useThrottledValue](../functions/usethrottledvalue.md) diff --git a/docs/reference/classes/asyncpersister.md b/docs/reference/classes/asyncpersister.md new file mode 100644 index 000000000..9211b4174 --- /dev/null +++ b/docs/reference/classes/asyncpersister.md @@ -0,0 +1,90 @@ +--- +id: AsyncPersister +title: AsyncPersister +--- + + + +# Class: `abstract` AsyncPersister\ + +Defined in: async-persister.ts:4 + +Interface for an async persister that can save/load state for a given type + +## Type Parameters + +• **TState** + +## Constructors + +### new AsyncPersister() + +```ts +new AsyncPersister(key): AsyncPersister +``` + +Defined in: async-persister.ts:5 + +#### Parameters + +##### key + +`string` + +#### Returns + +[`AsyncPersister`](../asyncpersister.md)\<`TState`\> + +## Properties + +### key + +```ts +readonly key: string; +``` + +Defined in: async-persister.ts:5 + +## Methods + +### loadState() + +```ts +abstract loadState(key): Promise +``` + +Defined in: async-persister.ts:7 + +#### Parameters + +##### key + +`string` + +#### Returns + +`Promise`\<`undefined` \| `TState`\> + +*** + +### saveState() + +```ts +abstract saveState(key, state): Promise +``` + +Defined in: async-persister.ts:8 + +#### Parameters + +##### key + +`string` + +##### state + +`TState` + +#### Returns + +`Promise`\<`void`\> diff --git a/docs/reference/classes/asyncratelimiter.md b/docs/reference/classes/asyncratelimiter.md index 33dd724ac..f7f130b68 100644 --- a/docs/reference/classes/asyncratelimiter.md +++ b/docs/reference/classes/asyncratelimiter.md @@ -7,7 +7,7 @@ title: AsyncRateLimiter # Class: AsyncRateLimiter\ -Defined in: [async-rate-limiter.ts:127](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L127) +Defined in: [async-rate-limiter.ts:148](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L148) A class that creates an async rate-limited function. @@ -75,7 +75,7 @@ const data = await rateLimiter.maybeExecute('123'); new AsyncRateLimiter(fn, initialOptions): AsyncRateLimiter ``` -Defined in: [async-rate-limiter.ts:137](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L137) +Defined in: [async-rate-limiter.ts:163](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L163) #### Parameters @@ -99,7 +99,7 @@ Defined in: [async-rate-limiter.ts:137](https://github.com/TanStack/pacer/blob/m getEnabled(): boolean ``` -Defined in: [async-rate-limiter.ts:165](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L165) +Defined in: [async-rate-limiter.ts:234](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L234) Returns the current enabled state of the rate limiter @@ -115,7 +115,7 @@ Returns the current enabled state of the rate limiter getErrorCount(): number ``` -Defined in: [async-rate-limiter.ts:324](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L324) +Defined in: [async-rate-limiter.ts:404](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L404) Returns the number of times the function has errored @@ -131,7 +131,7 @@ Returns the number of times the function has errored getIsExecuting(): boolean ``` -Defined in: [async-rate-limiter.ts:338](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L338) +Defined in: [async-rate-limiter.ts:418](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L418) Returns whether the function is currently executing @@ -147,7 +147,7 @@ Returns whether the function is currently executing getLimit(): number ``` -Defined in: [async-rate-limiter.ts:172](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L172) +Defined in: [async-rate-limiter.ts:241](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L241) Returns the current limit of executions allowed within the time window @@ -163,7 +163,7 @@ Returns the current limit of executions allowed within the time window getMsUntilNextWindow(): number ``` -Defined in: [async-rate-limiter.ts:299](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L299) +Defined in: [async-rate-limiter.ts:379](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L379) Returns the number of milliseconds until the next execution will be possible For fixed windows, this is the time until the current window resets @@ -181,7 +181,7 @@ For sliding windows, this is the time until the oldest execution expires getOptions(): AsyncRateLimiterOptions ``` -Defined in: [async-rate-limiter.ts:158](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L158) +Defined in: [async-rate-limiter.ts:227](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L227) Returns the current rate limiter options @@ -197,7 +197,7 @@ Returns the current rate limiter options getRejectionCount(): number ``` -Defined in: [async-rate-limiter.ts:331](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L331) +Defined in: [async-rate-limiter.ts:411](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L411) Returns the number of times the function has been rejected @@ -213,7 +213,7 @@ Returns the number of times the function has been rejected getRemainingInWindow(): number ``` -Defined in: [async-rate-limiter.ts:289](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L289) +Defined in: [async-rate-limiter.ts:369](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L369) Returns the number of remaining executions allowed in the current window @@ -229,7 +229,7 @@ Returns the number of remaining executions allowed in the current window getSettleCount(): number ``` -Defined in: [async-rate-limiter.ts:317](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L317) +Defined in: [async-rate-limiter.ts:397](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L397) Returns the number of times the function has been settled @@ -245,7 +245,7 @@ Returns the number of times the function has been settled getSuccessCount(): number ``` -Defined in: [async-rate-limiter.ts:310](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L310) +Defined in: [async-rate-limiter.ts:390](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L390) Returns the number of times the function has been executed @@ -261,7 +261,7 @@ Returns the number of times the function has been executed getWindow(): number ``` -Defined in: [async-rate-limiter.ts:179](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L179) +Defined in: [async-rate-limiter.ts:248](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L248) Returns the current time window in milliseconds @@ -277,7 +277,7 @@ Returns the current time window in milliseconds maybeExecute(...args): Promise> ``` -Defined in: [async-rate-limiter.ts:212](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L212) +Defined in: [async-rate-limiter.ts:281](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L281) 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. @@ -329,7 +329,7 @@ await rateLimiter.maybeExecute('arg1', 'arg2'); // Rejected reset(): void ``` -Defined in: [async-rate-limiter.ts:345](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L345) +Defined in: [async-rate-limiter.ts:425](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L425) Resets the rate limiter state @@ -345,7 +345,7 @@ Resets the rate limiter state setOptions(newOptions): void ``` -Defined in: [async-rate-limiter.ts:151](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L151) +Defined in: [async-rate-limiter.ts:220](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L220) Updates the rate limiter options diff --git a/docs/reference/classes/persister.md b/docs/reference/classes/persister.md new file mode 100644 index 000000000..2accf6ff4 --- /dev/null +++ b/docs/reference/classes/persister.md @@ -0,0 +1,114 @@ +--- +id: Persister +title: Persister +--- + + + +# Class: `abstract` Persister\ + +Defined in: persister.ts:23 + +Abstract class that defines the contract for a state persister implementation. +A persister is responsible for loading and saving state to a storage medium. + +## Example + +```ts +class MyPersister extends Persister { + constructor(key: string) { + super(key) + } + + loadState(key: string): MyState | undefined { + // Load state from storage + return state + } + + saveState(key: string, state: MyState): void { + // Save state to storage + } +} +``` + +## Extended by + +- [`StoragePersister`](../storagepersister.md) + +## Type Parameters + +• **TState** + +## Constructors + +### new Persister() + +```ts +new Persister(key): Persister +``` + +Defined in: persister.ts:24 + +#### Parameters + +##### key + +`string` + +#### Returns + +[`Persister`](../persister.md)\<`TState`\> + +## Properties + +### key + +```ts +readonly key: string; +``` + +Defined in: persister.ts:24 + +## Methods + +### loadState() + +```ts +abstract loadState(key): undefined | TState +``` + +Defined in: persister.ts:26 + +#### Parameters + +##### key + +`string` + +#### Returns + +`undefined` \| `TState` + +*** + +### saveState() + +```ts +abstract saveState(key, state): void +``` + +Defined in: persister.ts:27 + +#### Parameters + +##### key + +`string` + +##### state + +`TState` + +#### Returns + +`void` diff --git a/docs/reference/classes/ratelimiter.md b/docs/reference/classes/ratelimiter.md index 751ebe509..b6334011e 100644 --- a/docs/reference/classes/ratelimiter.md +++ b/docs/reference/classes/ratelimiter.md @@ -7,7 +7,7 @@ title: RateLimiter # Class: RateLimiter\ -Defined in: [rate-limiter.ts:80](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L80) +Defined in: [rate-limiter.ts:95](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L95) A class that creates a rate-limited function. @@ -52,7 +52,7 @@ rateLimiter.maybeExecute('123'); new RateLimiter(fn, initialOptions): RateLimiter ``` -Defined in: [rate-limiter.ts:86](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L86) +Defined in: [rate-limiter.ts:104](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L104) #### Parameters @@ -76,7 +76,7 @@ Defined in: [rate-limiter.ts:86](https://github.com/TanStack/pacer/blob/main/pac getEnabled(): boolean ``` -Defined in: [rate-limiter.ts:113](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L113) +Defined in: [rate-limiter.ts:168](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L168) Returns the current enabled state of the rate limiter @@ -92,7 +92,7 @@ Returns the current enabled state of the rate limiter getExecutionCount(): number ``` -Defined in: [rate-limiter.ts:198](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L198) +Defined in: [rate-limiter.ts:257](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L257) Returns the number of times the function has been executed @@ -108,7 +108,7 @@ Returns the number of times the function has been executed getLimit(): number ``` -Defined in: [rate-limiter.ts:120](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L120) +Defined in: [rate-limiter.ts:175](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L175) Returns the current limit of executions allowed within the time window @@ -124,7 +124,7 @@ Returns the current limit of executions allowed within the time window getMsUntilNextWindow(): number ``` -Defined in: [rate-limiter.ts:220](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L220) +Defined in: [rate-limiter.ts:279](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L279) Returns the number of milliseconds until the next execution will be possible @@ -140,7 +140,7 @@ Returns the number of milliseconds until the next execution will be possible getOptions(): Required> ``` -Defined in: [rate-limiter.ts:106](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L106) +Defined in: [rate-limiter.ts:161](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L161) Returns the current rate limiter options @@ -156,7 +156,7 @@ Returns the current rate limiter options getRejectionCount(): number ``` -Defined in: [rate-limiter.ts:205](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L205) +Defined in: [rate-limiter.ts:264](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L264) Returns the number of times the function has been rejected @@ -172,7 +172,7 @@ Returns the number of times the function has been rejected getRemainingInWindow(): number ``` -Defined in: [rate-limiter.ts:212](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L212) +Defined in: [rate-limiter.ts:271](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L271) Returns the number of remaining executions allowed in the current window @@ -188,7 +188,7 @@ Returns the number of remaining executions allowed in the current window getWindow(): number ``` -Defined in: [rate-limiter.ts:127](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L127) +Defined in: [rate-limiter.ts:182](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L182) Returns the current time window in milliseconds @@ -204,7 +204,7 @@ Returns the current time window in milliseconds maybeExecute(...args): boolean ``` -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:201](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L201) 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. @@ -239,7 +239,7 @@ rateLimiter.maybeExecute('arg1', 'arg2'); // false reset(): void ``` -Defined in: [rate-limiter.ts:231](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L231) +Defined in: [rate-limiter.ts:290](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L290) Resets the rate limiter state @@ -255,7 +255,7 @@ Resets the rate limiter state setOptions(newOptions): void ``` -Defined in: [rate-limiter.ts:99](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L99) +Defined in: [rate-limiter.ts:154](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L154) Updates the rate limiter options diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md new file mode 100644 index 000000000..e2a5d382e --- /dev/null +++ b/docs/reference/classes/storagepersister.md @@ -0,0 +1,176 @@ +--- +id: StoragePersister +title: StoragePersister +--- + + + +# Class: StoragePersister\ + +Defined in: persister.ts:126 + +A persister that saves state to browser local/session storage. + +The persister can use either localStorage (persists across browser sessions) or +sessionStorage (cleared when browser tab/window closes). State is automatically +serialized to JSON when saving and deserialized when loading. + +Optionally, a `buster` string can be provided to force cache busting by storing it in the value. +Optionally, a `maxAge` (in ms) can be provided to expire the stored state after a certain duration. +Optionally, callbacks can be provided to run after state is saved or loaded. + +## Example + +```ts +const persister = new StoragePersister({ + key: 'my-rate-limiter', + storage: window.localStorage, + buster: 'v2', + maxAge: 1000 * 60 * 60, // 1 hour + onSaveState: (key, state) => console.log('State saved:', key, state), + onLoadState: (key, state) => console.log('State loaded:', key, state), + onLoadStateError: (key, error) => console.error('Error loading state:', key, error), + onSaveStateError: (key, error) => console.error('Error saving state:', key, error) +}) +const rateLimiter = new RateLimiter(fn, { + persister, + limit: 5, + window: 1000 +}) +``` + +## Extends + +- [`Persister`](../persister.md)\<`TState`\> + +## Type Parameters + +• **TState** + +## Constructors + +### new StoragePersister() + +```ts +new StoragePersister(options): StoragePersister +``` + +Defined in: persister.ts:128 + +#### Parameters + +##### options + +[`StoragePersisterOptions`](../../interfaces/storagepersisteroptions.md)\<`TState`\> + +#### Returns + +[`StoragePersister`](../storagepersister.md)\<`TState`\> + +#### Overrides + +[`Persister`](../persister.md).[`constructor`](../Persister.md#constructors) + +## Properties + +### key + +```ts +readonly key: string; +``` + +Defined in: persister.ts:24 + +#### Inherited from + +[`Persister`](../persister.md).[`key`](../Persister.md#key-1) + +## Methods + +### getOptions() + +```ts +getOptions(): StoragePersisterOptions +``` + +Defined in: persister.ts:146 + +Returns the current persister options + +#### Returns + +[`StoragePersisterOptions`](../../interfaces/storagepersisteroptions.md)\<`TState`\> + +*** + +### loadState() + +```ts +loadState(key): undefined | TState +``` + +Defined in: persister.ts:167 + +#### Parameters + +##### key + +`string` + +#### Returns + +`undefined` \| `TState` + +#### Overrides + +[`Persister`](../persister.md).[`loadState`](../Persister.md#loadstate) + +*** + +### saveState() + +```ts +saveState(key, state): void +``` + +Defined in: persister.ts:150 + +#### Parameters + +##### key + +`string` + +##### state + +`TState` + +#### Returns + +`void` + +#### Overrides + +[`Persister`](../persister.md).[`saveState`](../Persister.md#savestate) + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: persister.ts:139 + +Updates the persister options + +#### Parameters + +##### newOptions + +`Partial`\<[`StoragePersisterOptions`](../../interfaces/storagepersisteroptions.md)\<`TState`\>\> + +#### Returns + +`void` diff --git a/docs/reference/functions/asyncratelimit.md b/docs/reference/functions/asyncratelimit.md index 57d2b95f8..09e45adcf 100644 --- a/docs/reference/functions/asyncratelimit.md +++ b/docs/reference/functions/asyncratelimit.md @@ -11,7 +11,7 @@ title: asyncRateLimit function asyncRateLimit(fn, initialOptions): (...args) => Promise> ``` -Defined in: [async-rate-limiter.ts:407](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L407) +Defined in: [async-rate-limiter.ts:491](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L491) Creates an async rate-limited function that will execute the provided function up to a maximum number of times within a time window. diff --git a/docs/reference/functions/ratelimit.md b/docs/reference/functions/ratelimit.md index f83d03a2f..b15248c2b 100644 --- a/docs/reference/functions/ratelimit.md +++ b/docs/reference/functions/ratelimit.md @@ -11,7 +11,7 @@ title: rateLimit function rateLimit(fn, initialOptions): (...args) => boolean ``` -Defined in: [rate-limiter.ts:275](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L275) +Defined in: [rate-limiter.ts:336](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L336) Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window. diff --git a/docs/reference/index.md b/docs/reference/index.md index 4e2a51641..a0655006c 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -10,13 +10,16 @@ title: "@tanstack/pacer" ## Classes - [AsyncDebouncer](../classes/asyncdebouncer.md) +- [AsyncPersister](../classes/asyncpersister.md) - [AsyncQueuer](../classes/asyncqueuer.md) - [AsyncRateLimiter](../classes/asyncratelimiter.md) - [AsyncThrottler](../classes/asyncthrottler.md) - [Batcher](../classes/batcher.md) - [Debouncer](../classes/debouncer.md) +- [Persister](../classes/persister.md) - [Queuer](../classes/queuer.md) - [RateLimiter](../classes/ratelimiter.md) +- [StoragePersister](../classes/storagepersister.md) - [Throttler](../classes/throttler.md) ## Interfaces @@ -24,11 +27,15 @@ title: "@tanstack/pacer" - [AsyncDebouncerOptions](../interfaces/asyncdebounceroptions.md) - [AsyncQueuerOptions](../interfaces/asyncqueueroptions.md) - [AsyncRateLimiterOptions](../interfaces/asyncratelimiteroptions.md) +- [AsyncRateLimiterState](../interfaces/asyncratelimiterstate.md) - [AsyncThrottlerOptions](../interfaces/asyncthrottleroptions.md) - [BatcherOptions](../interfaces/batcheroptions.md) - [DebouncerOptions](../interfaces/debounceroptions.md) +- [PersistedStorage](../interfaces/persistedstorage.md) - [QueuerOptions](../interfaces/queueroptions.md) - [RateLimiterOptions](../interfaces/ratelimiteroptions.md) +- [RateLimiterState](../interfaces/ratelimiterstate.md) +- [StoragePersisterOptions](../interfaces/storagepersisteroptions.md) - [ThrottlerOptions](../interfaces/throttleroptions.md) ## Type Aliases diff --git a/docs/reference/interfaces/asyncratelimiteroptions.md b/docs/reference/interfaces/asyncratelimiteroptions.md index 9a8e4b169..d37cfc439 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:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L7) +Defined in: [async-rate-limiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L22) Options for configuring an async rate-limited function @@ -23,7 +23,7 @@ Options for configuring an async rate-limited function optional enabled: boolean | (rateLimiter) => boolean; ``` -Defined in: [async-rate-limiter.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L13) +Defined in: [async-rate-limiter.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L28) 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 +37,7 @@ Defaults to true. limit: number | (rateLimiter) => number; ``` -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:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L33) Maximum number of executions allowed within the time window. Can be a number or a function that returns a number. @@ -50,7 +50,7 @@ Can be a number or a function that returns a number. optional onError: (error, rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L24) +Defined in: [async-rate-limiter.ts:39](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L39) Optional error handler for when the rate-limited function throws. If provided, the handler will be called with the error and rate limiter instance. @@ -78,7 +78,7 @@ This can be used alongside throwOnError - the handler will be called before any optional onReject: (rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L28) +Defined in: [async-rate-limiter.ts:43](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L43) Optional callback function that is called when an execution is rejected due to rate limiting @@ -100,7 +100,7 @@ Optional callback function that is called when an execution is rejected due to r optional onSettled: (rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L32) +Defined in: [async-rate-limiter.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L47) Optional function to call when the rate-limited function is executed @@ -122,7 +122,7 @@ Optional function to call when the rate-limited function is executed optional onSuccess: (result, rateLimiter) => void; ``` -Defined in: [async-rate-limiter.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L36) +Defined in: [async-rate-limiter.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L51) Optional function to call when the rate-limited function is executed @@ -142,13 +142,27 @@ Optional function to call when the rate-limited function is executed *** +### persister? + +```ts +optional persister: + | AsyncPersister> +| Persister>; +``` + +Defined in: [async-rate-limiter.ts:58](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L58) + +Optional persister for saving/loading rate limiter state + +*** + ### throwOnError? ```ts optional throwOnError: boolean; ``` -Defined in: [async-rate-limiter.ts:45](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L45) +Defined in: [async-rate-limiter.ts:66](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L66) Whether to throw errors when they occur. Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -162,7 +176,7 @@ Can be explicitly set to override these defaults. window: number | (rateLimiter) => number; ``` -Defined in: [async-rate-limiter.ts:50](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L50) +Defined in: [async-rate-limiter.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L71) Time window in milliseconds within which the limit applies. Can be a number or a function that returns a number. @@ -175,7 +189,7 @@ Can be a number or a function that returns a number. optional windowType: "fixed" | "sliding"; ``` -Defined in: [async-rate-limiter.ts:57](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L57) +Defined in: [async-rate-limiter.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L78) 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 new file mode 100644 index 000000000..81b2b2b6e --- /dev/null +++ b/docs/reference/interfaces/asyncratelimiterstate.md @@ -0,0 +1,86 @@ +--- +id: AsyncRateLimiterState +title: AsyncRateLimiterState +--- + + + +# Interface: AsyncRateLimiterState\ + +Defined in: [async-rate-limiter.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L9) + +State shape for persisting AsyncRateLimiter + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### errorCount + +```ts +errorCount: number; +``` + +Defined in: [async-rate-limiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L10) + +*** + +### executionTimes + +```ts +executionTimes: number[]; +``` + +Defined in: [async-rate-limiter.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L11) + +*** + +### isExecuting + +```ts +isExecuting: boolean; +``` + +Defined in: [async-rate-limiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L12) + +*** + +### lastResult + +```ts +lastResult: undefined | ReturnType; +``` + +Defined in: [async-rate-limiter.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L13) + +*** + +### rejectionCount + +```ts +rejectionCount: number; +``` + +Defined in: [async-rate-limiter.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L14) + +*** + +### settleCount + +```ts +settleCount: number; +``` + +Defined in: [async-rate-limiter.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L15) + +*** + +### successCount + +```ts +successCount: number; +``` + +Defined in: [async-rate-limiter.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L16) diff --git a/docs/reference/interfaces/persistedstorage.md b/docs/reference/interfaces/persistedstorage.md new file mode 100644 index 000000000..a1950014e --- /dev/null +++ b/docs/reference/interfaces/persistedstorage.md @@ -0,0 +1,44 @@ +--- +id: PersistedStorage +title: PersistedStorage +--- + + + +# Interface: PersistedStorage\ + +Defined in: persister.ts:30 + +## Type Parameters + +• **TState** + +## Properties + +### buster? + +```ts +optional buster: string; +``` + +Defined in: persister.ts:31 + +*** + +### state + +```ts +state: undefined | TState; +``` + +Defined in: persister.ts:32 + +*** + +### timestamp + +```ts +timestamp: number; +``` + +Defined in: persister.ts:33 diff --git a/docs/reference/interfaces/ratelimiteroptions.md b/docs/reference/interfaces/ratelimiteroptions.md index 59c1f4f4e..1ba3140a3 100644 --- a/docs/reference/interfaces/ratelimiteroptions.md +++ b/docs/reference/interfaces/ratelimiteroptions.md @@ -7,7 +7,7 @@ title: RateLimiterOptions # Interface: RateLimiterOptions\ -Defined in: [rate-limiter.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L7) +Defined in: [rate-limiter.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L17) Options for configuring a rate-limited function @@ -23,7 +23,7 @@ Options for configuring a rate-limited function optional enabled: boolean | (rateLimiter) => boolean; ``` -Defined in: [rate-limiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L12) +Defined in: [rate-limiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L22) Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions. Defaults to true. @@ -36,7 +36,7 @@ Defaults to true. limit: number | (rateLimiter) => number; ``` -Defined in: [rate-limiter.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L17) +Defined in: [rate-limiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L27) Maximum number of executions allowed within the time window. Can be a number or a callback function that receives the rate limiter instance and returns a number. @@ -49,7 +49,7 @@ Can be a number or a callback function that receives the rate limiter instance a optional onExecute: (rateLimiter) => void; ``` -Defined in: [rate-limiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L21) +Defined in: [rate-limiter.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L31) Callback function that is called after the function is executed @@ -71,7 +71,7 @@ Callback function that is called after the function is executed optional onReject: (rateLimiter) => void; ``` -Defined in: [rate-limiter.ts:25](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L25) +Defined in: [rate-limiter.ts:35](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L35) Optional callback function that is called when an execution is rejected due to rate limiting @@ -87,13 +87,25 @@ Optional callback function that is called when an execution is rejected due to r *** +### persister? + +```ts +optional persister: Persister; +``` + +Defined in: [rate-limiter.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L51) + +Optional persister for saving/loading rate limiter state + +*** + ### window ```ts window: number | (rateLimiter) => number; ``` -Defined in: [rate-limiter.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L30) +Defined in: [rate-limiter.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L40) Time window in milliseconds within which the limit applies. Can be a number or a callback function that receives the rate limiter instance and returns a number. @@ -106,7 +118,7 @@ Can be a number or a callback function that receives the rate limiter instance a optional windowType: "fixed" | "sliding"; ``` -Defined in: [rate-limiter.ts:37](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L37) +Defined in: [rate-limiter.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L47) 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/ratelimiterstate.md b/docs/reference/interfaces/ratelimiterstate.md new file mode 100644 index 000000000..b7de2981a --- /dev/null +++ b/docs/reference/interfaces/ratelimiterstate.md @@ -0,0 +1,42 @@ +--- +id: RateLimiterState +title: RateLimiterState +--- + + + +# Interface: RateLimiterState + +Defined in: [rate-limiter.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L8) + +State shape for persisting RateLimiter + +## Properties + +### executionCount + +```ts +executionCount: number; +``` + +Defined in: [rate-limiter.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L9) + +*** + +### executionTimes + +```ts +executionTimes: number[]; +``` + +Defined in: [rate-limiter.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L11) + +*** + +### rejectionCount + +```ts +rejectionCount: number; +``` + +Defined in: [rate-limiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L10) diff --git a/docs/reference/interfaces/storagepersisteroptions.md b/docs/reference/interfaces/storagepersisteroptions.md new file mode 100644 index 000000000..acc909bf7 --- /dev/null +++ b/docs/reference/interfaces/storagepersisteroptions.md @@ -0,0 +1,221 @@ +--- +id: StoragePersisterOptions +title: StoragePersisterOptions +--- + + + +# Interface: StoragePersisterOptions\ + +Defined in: persister.ts:42 + +Configuration options for creating a browser-based state persister. + +The persister can use either localStorage (persists across browser sessions) or +sessionStorage (cleared when browser tab/window closes) to store serialized state. + +## Type Parameters + +• **TState** + +## Properties + +### buster? + +```ts +optional buster: string; +``` + +Defined in: persister.ts:47 + +A version string used to invalidate cached state. When changed, any existing +stored state will be considered invalid and cleared. + +*** + +### deserializer()? + +```ts +optional deserializer: (state) => PersistedStorage; +``` + +Defined in: persister.ts:52 + +Optional function to customize how state is deserialized after loading from storage. +By default, JSON.parse is used. + +#### Parameters + +##### state + +`string` + +#### Returns + +[`PersistedStorage`](../persistedstorage.md)\<`TState`\> + +*** + +### key + +```ts +key: string; +``` + +Defined in: persister.ts:56 + +Unique identifier used as the storage key for persisting state. + +*** + +### maxAge? + +```ts +optional maxAge: number; +``` + +Defined in: persister.ts:61 + +Maximum age in milliseconds before stored state is considered expired. +When exceeded, the state will be cleared and treated as if it doesn't exist. + +*** + +### onLoadState()? + +```ts +optional onLoadState: (key, state) => void; +``` + +Defined in: persister.ts:65 + +Optional callback that runs after state is successfully loaded. + +#### Parameters + +##### key + +`string` + +##### state + +`undefined` | `TState` + +#### Returns + +`void` + +*** + +### onLoadStateError()? + +```ts +optional onLoadStateError: (key, error) => void; +``` + +Defined in: persister.ts:69 + +Optional callback that runs after state is unable to be loaded. + +#### Parameters + +##### key + +`string` + +##### error + +`Error` + +#### Returns + +`void` + +*** + +### onSaveState()? + +```ts +optional onSaveState: (key, state) => void; +``` + +Defined in: persister.ts:73 + +Optional callback that runs after state is successfully saved. + +#### Parameters + +##### key + +`string` + +##### state + +`TState` + +#### Returns + +`void` + +*** + +### onSaveStateError()? + +```ts +optional onSaveStateError: (key, error) => void; +``` + +Defined in: persister.ts:78 + +Optional callback that runs after state is unable to be saved. +For example, if the storage is full (localStorage >= 5MB) + +#### Parameters + +##### key + +`string` + +##### error + +`Error` + +#### Returns + +`void` + +*** + +### serializer()? + +```ts +optional serializer: (state) => string; +``` + +Defined in: persister.ts:83 + +Optional function to customize how state is serialized before saving to storage. +By default, JSON.stringify is used. + +#### Parameters + +##### state + +[`PersistedStorage`](../persistedstorage.md)\<`TState`\> + +#### Returns + +`string` + +*** + +### storage + +```ts +storage: Storage; +``` + +Defined in: persister.ts:88 + +The browser storage implementation to use for persisting state. +Typically window.localStorage or window.sessionStorage. diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index 391ad2b02..486672f00 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -1,6 +1,8 @@ import { useEffect, useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncRateLimiter } from '@tanstack/react-pacer/async-rate-limiter' +import { useStoragePersister } from '@tanstack/react-pacer/persister' +import type { AsyncRateLimiterState } from '@tanstack/react-pacer/async-rate-limiter' interface SearchResult { id: number @@ -54,6 +56,13 @@ function App() { setError(error as Error) setResults([]) }, + // optionally, you can persist the rate limiter state to localStorage + persister: useStoragePersister>({ + key: 'my-async-rate-limiter', + storage: localStorage, + maxAge: 1000 * 60, // 1 minute + buster: 'v1', + }), }) // get and name our rate limited function diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index 285715f12..2805ec4b3 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 { useStoragePersister } from '@tanstack/react-pacer/persister' function App1() { // Use your state management library of choice @@ -18,6 +19,12 @@ function App1() { 'Rejected by rate limiter', rateLimiter.getMsUntilNextWindow(), ), + persister: useStoragePersister({ + key: 'my-rate-limiter', + storage: localStorage, + maxAge: 1000 * 60, // 1 minute + buster: 'v1', + }), }) function increment() { diff --git a/package.json b/package.json index 170670ab4..8e2c599e5 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "size-limit": [ { "path": "packages/pacer/dist/esm/index.js", - "limit": "4 KB" + "limit": "8 KB" } ], "devDependencies": { diff --git a/packages/pacer/package.json b/packages/pacer/package.json index 5c8b2e3de..991f00c19 100644 --- a/packages/pacer/package.json +++ b/packages/pacer/package.json @@ -47,6 +47,16 @@ "default": "./dist/cjs/async-debouncer.cjs" } }, + "./async-persister": { + "import": { + "types": "./dist/esm/async-persister.d.ts", + "default": "./dist/esm/async-persister.js" + }, + "require": { + "types": "./dist/cjs/async-persister.d.cts", + "default": "./dist/cjs/async-persister.cjs" + } + }, "./async-queuer": { "import": { "types": "./dist/esm/async-queuer.d.ts", @@ -107,6 +117,16 @@ "default": "./dist/cjs/debouncer.cjs" } }, + "./persister": { + "import": { + "types": "./dist/esm/persister.d.ts", + "default": "./dist/esm/persister.js" + }, + "require": { + "types": "./dist/cjs/persister.d.cts", + "default": "./dist/cjs/persister.cjs" + } + }, "./queuer": { "import": { "types": "./dist/esm/queuer.d.ts", diff --git a/packages/pacer/src/async-persister.ts b/packages/pacer/src/async-persister.ts new file mode 100644 index 000000000..009d11b92 --- /dev/null +++ b/packages/pacer/src/async-persister.ts @@ -0,0 +1,9 @@ +/** + * Interface for an async persister that can save/load state for a given type + */ +export abstract class AsyncPersister { + constructor(public readonly key: string) {} + + abstract loadState(key: string): Promise + abstract saveState(key: string, state: TState): Promise +} diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 6d0d46a69..7dd04c0a5 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -1,5 +1,20 @@ import { parseFunctionOrValue } from './utils' +import type { Persister } from './persister' import type { AnyAsyncFunction, OptionalKeys } from './types' +import type { AsyncPersister } from './async-persister' + +/** + * State shape for persisting AsyncRateLimiter + */ +export interface AsyncRateLimiterState { + errorCount: number + executionTimes: Array + isExecuting: boolean + lastResult: ReturnType | undefined + rejectionCount: number + settleCount: number + successCount: number +} /** * Options for configuring an async rate-limited function @@ -37,6 +52,12 @@ export interface AsyncRateLimiterOptions { result: ReturnType, rateLimiter: AsyncRateLimiter, ) => void + /** + * Optional persister for saving/loading rate limiter state + */ + persister?: + | AsyncPersister> + | Persister> /** * Whether to throw errors when they occur. * Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -125,14 +146,19 @@ const defaultOptions: Omit< * ``` */ export class AsyncRateLimiter { + private _state: AsyncRateLimiterState = { + rejectionCount: 0, + executionTimes: [], + errorCount: 0, + settleCount: 0, + successCount: 0, + isExecuting: false, + lastResult: undefined as ReturnType | undefined, + } private _options: AsyncRateLimiterOptionsWithOptionalCallbacks - private _errorCount = 0 - private _executionTimes: Array = [] - private _lastResult: ReturnType | undefined - private _rejectionCount = 0 - private _settleCount = 0 - private _successCount = 0 - private _isExecuting = false + private _persister?: + | AsyncPersister> + | Persister> constructor( private fn: TFn, @@ -143,6 +169,49 @@ export class AsyncRateLimiter { ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } + this._persister = this._options.persister + if (this._persister) { + // Load state + const loadedState = this._persister.loadState(this._persister.key) + if (loadedState instanceof Promise) { + loadedState.then((state) => { + if (state) { + this.setState(state, false) + } + }) + } else if (loadedState) { + this.setState(loadedState, false) + } + } + } + + /** + * Returns the current state for persistence + */ + private getState(): AsyncRateLimiterState { + return { ...this._state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + private setState( + state: Partial>, + save: boolean = true, + ): void { + this._state = { ...this._state, ...state } + if (save) { + this.saveState() + } + } + + /** + * Saves state using the persister if available + */ + private saveState(): void { + if (this._persister) { + this._persister.saveState(this._persister.key, this.getState()) + } } /** @@ -219,19 +288,19 @@ export class AsyncRateLimiter { if (this._options.windowType === 'sliding') { // For sliding window, we can execute if we have capacity in the current window - if (this._executionTimes.length < limit) { + if (this._state.executionTimes.length < limit) { await this.execute(...args) - return this._lastResult + return this._state.lastResult } } else { // For fixed window, we need to check if we're in a new window const now = Date.now() - const oldestExecution = Math.min(...this._executionTimes) + const oldestExecution = Math.min(...this._state.executionTimes) const isNewWindow = oldestExecution + window <= now - if (isNewWindow || this._executionTimes.length < limit) { + if (isNewWindow || this._state.executionTimes.length < limit) { await this.execute(...args) - return this._lastResult + return this._state.lastResult } } @@ -243,16 +312,23 @@ export class AsyncRateLimiter { ...args: Parameters ): Promise | undefined> { if (!this.getEnabled()) return - this._isExecuting = true const now = Date.now() - this._executionTimes.push(now) + this._state.executionTimes.push(now) // mutate state directly for performance + this.setState({ + isExecuting: true, + }) try { - this._lastResult = await this.fn(...args) - this._successCount++ - this._options.onSuccess?.(this._lastResult!, this) + const result = await this.fn(...args) + this.setState({ + successCount: this._state.successCount + 1, + lastResult: result, + }) + this._options.onSuccess?.(result, this) } catch (error) { - this._errorCount++ + this.setState({ + errorCount: this._state.errorCount + 1, + }) this._options.onError?.(error, this) if (this._options.throwOnError) { throw error @@ -260,27 +336,31 @@ export class AsyncRateLimiter { console.error(error) } } finally { - this._isExecuting = false - this._settleCount++ + this.setState({ + isExecuting: false, + settleCount: this._state.settleCount + 1, + }) this._options.onSettled?.(this) } - return this._lastResult + return this._state.lastResult } private rejectFunction(): void { - this._rejectionCount++ - if (this._options.onReject) { - this._options.onReject(this) - } + this.setState({ + rejectionCount: this._state.rejectionCount + 1, + }) + this._options.onReject?.(this) } private cleanupOldExecutions(): void { const now = Date.now() const windowStart = now - this.getWindow() - this._executionTimes = this._executionTimes.filter( - (time) => time > windowStart, - ) + this.setState({ + executionTimes: this._state.executionTimes.filter( + (time) => time > windowStart, + ), + }) } /** @@ -288,7 +368,7 @@ export class AsyncRateLimiter { */ getRemainingInWindow(): number { this.cleanupOldExecutions() - return Math.max(0, this.getLimit() - this._executionTimes.length) + return Math.max(0, this.getLimit() - this._state.executionTimes.length) } /** @@ -300,7 +380,7 @@ export class AsyncRateLimiter { if (this.getRemainingInWindow() > 0) { return 0 } - const oldestExecution = Math.min(...this._executionTimes) + const oldestExecution = this._state.executionTimes[0] ?? Infinity return oldestExecution + this.getWindow() - Date.now() } @@ -308,46 +388,50 @@ export class AsyncRateLimiter { * Returns the number of times the function has been executed */ getSuccessCount(): number { - return this._successCount + return this._state.successCount } /** * Returns the number of times the function has been settled */ getSettleCount(): number { - return this._settleCount + return this._state.settleCount } /** * Returns the number of times the function has errored */ getErrorCount(): number { - return this._errorCount + return this._state.errorCount } /** * Returns the number of times the function has been rejected */ getRejectionCount(): number { - return this._rejectionCount + return this._state.rejectionCount } /** * Returns whether the function is currently executing */ getIsExecuting(): boolean { - return this._isExecuting + return this._state.isExecuting } /** * Resets the rate limiter state */ reset(): void { - this._executionTimes = [] - this._successCount = 0 - this._errorCount = 0 - this._rejectionCount = 0 - this._settleCount = 0 + this.setState({ + executionTimes: [], + rejectionCount: 0, + errorCount: 0, + settleCount: 0, + successCount: 0, + isExecuting: false, + lastResult: undefined, + }) } } diff --git a/packages/pacer/src/index.ts b/packages/pacer/src/index.ts index 480f0d443..9f8d62cd3 100644 --- a/packages/pacer/src/index.ts +++ b/packages/pacer/src/index.ts @@ -1,10 +1,12 @@ export * from './async-debouncer' +export * from './async-persister' export * from './async-queuer' export * from './async-rate-limiter' export * from './async-throttler' export * from './batcher' export * from './compare' export * from './debouncer' +export * from './persister' export * from './queuer' export * from './rate-limiter' export * from './throttler' diff --git a/packages/pacer/src/persister.ts b/packages/pacer/src/persister.ts new file mode 100644 index 000000000..f90fc76b0 --- /dev/null +++ b/packages/pacer/src/persister.ts @@ -0,0 +1,195 @@ +/** + * Abstract class that defines the contract for a state persister implementation. + * A persister is responsible for loading and saving state to a storage medium. + * + * @example + * ```ts + * class MyPersister extends Persister { + * constructor(key: string) { + * super(key) + * } + * + * loadState(key: string): MyState | undefined { + * // Load state from storage + * return state + * } + * + * saveState(key: string, state: MyState): void { + * // Save state to storage + * } + * } + * ``` + */ +export abstract class Persister { + constructor(public readonly key: string) {} + + abstract loadState(key: string): TState | undefined + abstract saveState(key: string, state: TState): void +} + +export interface PersistedStorage { + buster?: string + state: TState | undefined + timestamp: number +} + +/** + * Configuration options for creating a browser-based state persister. + * + * The persister can use either localStorage (persists across browser sessions) or + * sessionStorage (cleared when browser tab/window closes) to store serialized state. + */ +export interface StoragePersisterOptions { + /** + * A version string used to invalidate cached state. When changed, any existing + * stored state will be considered invalid and cleared. + */ + buster?: string + /** + * Optional function to customize how state is deserialized after loading from storage. + * By default, JSON.parse is used. + */ + deserializer?: (state: string) => PersistedStorage + /** + * Unique identifier used as the storage key for persisting state. + */ + key: string + /** + * Maximum age in milliseconds before stored state is considered expired. + * When exceeded, the state will be cleared and treated as if it doesn't exist. + */ + maxAge?: number + /** + * Optional callback that runs after state is successfully loaded. + */ + onLoadState?: (key: string, state: TState | undefined) => void + /** + * Optional callback that runs after state is unable to be loaded. + */ + onLoadStateError?: (key: string, error: Error) => void + /** + * Optional callback that runs after state is successfully saved. + */ + onSaveState?: (key: string, state: TState) => void + /** + * Optional callback that runs after state is unable to be saved. + * For example, if the storage is full (localStorage >= 5MB) + */ + onSaveStateError?: (key: string, error: Error) => void + /** + * Optional function to customize how state is serialized before saving to storage. + * By default, JSON.stringify is used. + */ + serializer?: (state: PersistedStorage) => string + /** + * The browser storage implementation to use for persisting state. + * Typically window.localStorage or window.sessionStorage. + */ + storage: Storage +} + +const defaultOptions: Partial> = { + deserializer: JSON.parse, + serializer: JSON.stringify, +} + +/** + * A persister that saves state to browser local/session storage. + * + * The persister can use either localStorage (persists across browser sessions) or + * sessionStorage (cleared when browser tab/window closes). State is automatically + * serialized to JSON when saving and deserialized when loading. + * + * Optionally, a `buster` string can be provided to force cache busting by storing it in the value. + * Optionally, a `maxAge` (in ms) can be provided to expire the stored state after a certain duration. + * Optionally, callbacks can be provided to run after state is saved or loaded. + * + * @example + * ```ts + * const persister = new StoragePersister({ + * key: 'my-rate-limiter', + * storage: window.localStorage, + * buster: 'v2', + * maxAge: 1000 * 60 * 60, // 1 hour + * onSaveState: (key, state) => console.log('State saved:', key, state), + * onLoadState: (key, state) => console.log('State loaded:', key, state), + * onLoadStateError: (key, error) => console.error('Error loading state:', key, error), + * onSaveStateError: (key, error) => console.error('Error saving state:', key, error) + * }) + * const rateLimiter = new RateLimiter(fn, { + * persister, + * limit: 5, + * window: 1000 + * }) + * ``` + */ +export class StoragePersister extends Persister { + private _options: StoragePersisterOptions + constructor(options: StoragePersisterOptions) { + super(options.key) + this._options = { + ...defaultOptions, + ...options, + } + } + + /** + * Updates the persister options + */ + setOptions(newOptions: Partial>): void { + this._options = { ...this._options, ...newOptions } + } + + /** + * Returns the current persister options + */ + getOptions(): StoragePersisterOptions { + return this._options + } + + saveState(key: string, state: TState): void { + try { + this._options.storage.setItem( + key, + this._options.serializer!({ + buster: this._options.buster, + state, + timestamp: Date.now(), + }), + ) + this._options.onSaveState?.(key, state) + } catch (error) { + console.error(error) + this._options.onSaveStateError?.(key, error as Error) + } + } + + loadState(key: string): TState | undefined { + const stored = this._options.storage.getItem(key) + if (!stored) { + return undefined + } + + try { + const parsed = this._options.deserializer!(stored) + const isValid = + !this._options.buster || parsed.buster === this._options.buster + const isNotExpired = + !this._options.maxAge || + !parsed.timestamp || + Date.now() - parsed.timestamp <= this._options.maxAge + + if (!isValid || !isNotExpired) { + return undefined + } + + const state = parsed.state as TState + this._options.onLoadState?.(key, state) + return state + } catch (error) { + console.error(error) + this._options.onLoadStateError?.(key, error as Error) + return undefined + } + } +} diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index e7d17c3e1..f6ec03c96 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -1,6 +1,16 @@ import { parseFunctionOrValue } from './utils' +import type { Persister } from './persister' import type { AnyFunction } from './types' +/** + * State shape for persisting RateLimiter + */ +export interface RateLimiterState { + executionCount: number + rejectionCount: number + executionTimes: Array +} + /** * Options for configuring a rate-limited function */ @@ -35,13 +45,18 @@ export interface RateLimiterOptions { * Defaults to 'fixed' */ windowType?: 'fixed' | 'sliding' + /** + * Optional persister for saving/loading rate limiter state + */ + persister?: Persister } -const defaultOptions: Required> = { +const defaultOptions: Omit< + Required>, + 'persister' | 'onExecute' | 'onReject' +> = { enabled: true, limit: 1, - onExecute: () => {}, - onReject: () => {}, window: 0, windowType: 'fixed', } @@ -78,10 +93,13 @@ const defaultOptions: Required> = { * ``` */ export class RateLimiter { - private _executionCount = 0 - private _rejectionCount = 0 - private _executionTimes: Array = [] + private _state: RateLimiterState = { + executionCount: 0, + rejectionCount: 0, + executionTimes: [], + } private _options: RateLimiterOptions + private _persister?: Persister constructor( private fn: TFn, @@ -91,6 +109,43 @@ export class RateLimiter { ...defaultOptions, ...initialOptions, } + this._persister = this._options.persister + if (this._persister) { + // Load state + const loadedState = this._persister.loadState(this._persister.key) + if (loadedState) { + this.setState(loadedState, false) + } + } + } + + /** + * Returns the current state for persistence + */ + private getState(): RateLimiterState { + return { ...this._state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + private setState( + state: Partial, + save: boolean = true, + ): void { + this._state = { ...this._state, ...state } + if (save) { + this.saveState() + } + } + + /** + * Saves state using the persister if available + */ + private saveState(): void { + if (this._persister) { + this._persister.saveState(this._persister.key, this.getState()) + } } /** @@ -148,17 +203,17 @@ export class RateLimiter { if (this._options.windowType === 'sliding') { // For sliding window, we can execute if we have capacity in the current window - if (this._executionTimes.length < this.getLimit()) { + if (this._state.executionTimes.length < this.getLimit()) { this.execute(...args) return true } } else { // For fixed window, we need to check if we're in a new window const now = Date.now() - const oldestExecution = Math.min(...this._executionTimes) + const oldestExecution = Math.min(...this._state.executionTimes) const isNewWindow = oldestExecution + this.getWindow() <= now - if (isNewWindow || this._executionTimes.length < this.getLimit()) { + if (isNewWindow || this._state.executionTimes.length < this.getLimit()) { this.execute(...args) return true } @@ -171,39 +226,43 @@ export class RateLimiter { private execute(...args: Parameters): void { if (!this.getEnabled()) return const now = Date.now() - this._executionCount++ - this._executionTimes.push(now) - this.fn(...args) // execute the function + this.fn(...args) // EXECUTE! + this._state.executionTimes.push(now) // mutate state directly for performance + this.setState({ + executionCount: this._state.executionCount + 1, + }) this._options.onExecute?.(this) } private rejectFunction(): void { - this._rejectionCount++ - if (this._options.onReject) { - this._options.onReject(this) - } + this.setState({ + rejectionCount: this._state.rejectionCount + 1, + }) + this._options.onReject?.(this) } private cleanupOldExecutions(): void { const now = Date.now() const windowStart = now - this.getWindow() - this._executionTimes = this._executionTimes.filter( - (time) => time > windowStart, - ) + this.setState({ + executionTimes: this._state.executionTimes.filter( + (time) => time > windowStart, + ), + }) } /** * Returns the number of times the function has been executed */ getExecutionCount(): number { - return this._executionCount + return this._state.executionCount } /** * Returns the number of times the function has been rejected */ getRejectionCount(): number { - return this._rejectionCount + return this._state.rejectionCount } /** @@ -211,7 +270,7 @@ export class RateLimiter { */ getRemainingInWindow(): number { this.cleanupOldExecutions() - return Math.max(0, this.getLimit() - this._executionTimes.length) + return Math.max(0, this.getLimit() - this._state.executionTimes.length) } /** @@ -221,7 +280,7 @@ export class RateLimiter { if (this.getRemainingInWindow() > 0) { return 0 } - const oldestExecution = Math.min(...this._executionTimes) + const oldestExecution = this._state.executionTimes[0] ?? Infinity return oldestExecution + this.getWindow() - Date.now() } @@ -229,9 +288,11 @@ export class RateLimiter { * Resets the rate limiter state */ reset(): void { - this._executionTimes = [] - this._executionCount = 0 - this._rejectionCount = 0 + this.setState({ + executionTimes: [], + executionCount: 0, + rejectionCount: 0, + }) } } diff --git a/packages/pacer/tests/persister.test.ts b/packages/pacer/tests/persister.test.ts new file mode 100644 index 000000000..84e872c0b --- /dev/null +++ b/packages/pacer/tests/persister.test.ts @@ -0,0 +1,132 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { StoragePersister } from '../src/persister' + +describe('createStoragePersister', () => { + let storage: Storage + let persister: StoragePersister + + beforeEach(() => { + storage = { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), + length: 0, + key: vi.fn(), + } + persister = new StoragePersister({ + key: 'test-key', + storage, + }) + }) + + it('should load state from storage', () => { + const state = { count: 1 } + const timestamp = Date.now() + ;(storage.getItem as any).mockReturnValue( + JSON.stringify({ state, timestamp }), + ) + const result = persister.loadState('test-key') + expect(result).toEqual(state) + }) + + it('should return undefined when no state exists', () => { + ;(storage.getItem as any).mockReturnValue(null) + const result = persister.loadState('test-key') + expect(result).toBeUndefined() + }) + + it('should handle storage errors when saving', () => { + const error = new Error('Storage error') + ;(storage.setItem as any).mockImplementation(() => { + throw error + }) + const onSaveStateError = vi.fn() + persister = new StoragePersister({ + key: 'test-key', + storage, + onSaveStateError, + }) + persister.saveState('test-key', { count: 1 }) + expect(onSaveStateError).toHaveBeenCalledWith('test-key', error) + }) + + it('should call onSaveState callback when state is saved', () => { + const onSaveState = vi.fn() + persister = new StoragePersister({ + key: 'test-key', + storage, + onSaveState, + }) + const state = { count: 1 } + persister.saveState('test-key', state) + expect(onSaveState).toHaveBeenCalledWith('test-key', state) + }) + + it('should call onLoadState callback when state is loaded', () => { + const onLoadState = vi.fn() + persister = new StoragePersister({ + key: 'test-key', + storage, + onLoadState, + }) + const state = { count: 1 } + ;(storage.getItem as any).mockReturnValue( + JSON.stringify({ state, timestamp: Date.now() }), + ) + persister.loadState('test-key') + expect(onLoadState).toHaveBeenCalledWith('test-key', state) + }) + + it('should respect buster string when loading state', () => { + persister = new StoragePersister({ + key: 'test-key', + storage, + buster: 'v2', + }) + ;(storage.getItem as any).mockReturnValue( + JSON.stringify({ + state: { count: 1 }, + timestamp: Date.now(), + buster: 'v1', + }), + ) + const result = persister.loadState('test-key') + expect(result).toBeUndefined() + }) + + it('should respect maxAge when loading state', () => { + persister = new StoragePersister({ + key: 'test-key', + storage, + maxAge: 1000, + }) + ;(storage.getItem as any).mockReturnValue( + JSON.stringify({ + state: { count: 1 }, + timestamp: Date.now() - 2000, + }), + ) + const result = persister.loadState('test-key') + expect(result).toBeUndefined() + }) + + it('should use custom serializer and deserializer', () => { + const serializer = vi.fn((data) => JSON.stringify(data)) + const deserializer = vi.fn((data) => JSON.parse(data)) + persister = new StoragePersister({ + key: 'test-key', + storage, + serializer, + deserializer, + }) + const state = { count: 1 } + persister.saveState('test-key', state) + expect(serializer).toHaveBeenCalled() + ;(storage.getItem as any).mockReturnValue( + JSON.stringify({ state, timestamp: Date.now() }), + ) + persister.loadState('test-key') + expect(deserializer).toHaveBeenCalled() + }) +}) diff --git a/packages/react-pacer/package.json b/packages/react-pacer/package.json index b55562472..9c8d164ca 100644 --- a/packages/react-pacer/package.json +++ b/packages/react-pacer/package.json @@ -47,6 +47,16 @@ "default": "./dist/cjs/async-debouncer/index.cjs" } }, + "./async-persister": { + "import": { + "types": "./dist/esm/async-persister/index.d.ts", + "default": "./dist/esm/async-persister/index.js" + }, + "require": { + "types": "./dist/cjs/async-persister/index.d.cts", + "default": "./dist/cjs/async-persister/index.cjs" + } + }, "./async-queuer": { "import": { "types": "./dist/esm/async-queuer/index.d.ts", @@ -97,6 +107,16 @@ "default": "./dist/cjs/debouncer/index.cjs" } }, + "./persister": { + "import": { + "types": "./dist/esm/persister/index.d.ts", + "default": "./dist/esm/persister/index.js" + }, + "require": { + "types": "./dist/cjs/persister/index.d.cts", + "default": "./dist/cjs/persister/index.cjs" + } + }, "./queuer": { "import": { "types": "./dist/esm/queuer/index.d.ts", diff --git a/packages/react-pacer/src/async-persister/index.ts b/packages/react-pacer/src/async-persister/index.ts new file mode 100644 index 000000000..bfbfc5627 --- /dev/null +++ b/packages/react-pacer/src/async-persister/index.ts @@ -0,0 +1 @@ +export * from '@tanstack/pacer/async-persister' diff --git a/packages/react-pacer/src/index.ts b/packages/react-pacer/src/index.ts index f1c4a7d1b..346804cd0 100644 --- a/packages/react-pacer/src/index.ts +++ b/packages/react-pacer/src/index.ts @@ -30,6 +30,9 @@ export * from './debouncer/useDebouncedState' export * from './debouncer/useDebouncedValue' export * from './debouncer/useDebouncer' +export * from './persister/useStoragePersister' +export * from './persister/useStorageState' + // queuer export * from './queuer/useQueuer' export * from './queuer/useQueuedState' diff --git a/packages/react-pacer/src/persister/index.ts b/packages/react-pacer/src/persister/index.ts new file mode 100644 index 000000000..d51c1265b --- /dev/null +++ b/packages/react-pacer/src/persister/index.ts @@ -0,0 +1,4 @@ +export * from '@tanstack/pacer/persister' + +export * from './useStoragePersister' +export * from './useStorageState' diff --git a/packages/react-pacer/src/persister/useStoragePersister.ts b/packages/react-pacer/src/persister/useStoragePersister.ts new file mode 100644 index 000000000..41423a5d3 --- /dev/null +++ b/packages/react-pacer/src/persister/useStoragePersister.ts @@ -0,0 +1,16 @@ +import { useState } from 'react' +import { StoragePersister } from '@tanstack/pacer/persister' +import { bindInstanceMethods } from '@tanstack/pacer/utils' +import type { StoragePersisterOptions } from '@tanstack/pacer/persister' + +export function useStoragePersister( + options: StoragePersisterOptions, +) { + const [persister] = useState(() => + bindInstanceMethods(new StoragePersister(options)), + ) + + persister.setOptions(options) + + return persister +} diff --git a/packages/react-pacer/src/persister/useStorageState.ts b/packages/react-pacer/src/persister/useStorageState.ts new file mode 100644 index 000000000..a5acaf35c --- /dev/null +++ b/packages/react-pacer/src/persister/useStorageState.ts @@ -0,0 +1,83 @@ +import { useEffect, useState } from 'react' +import { useStoragePersister } from './useStoragePersister' +import type { StoragePersisterOptions } from '@tanstack/pacer/persister' + +function useStorageState( + initialValue: TValue, + options: StoragePersisterOptions, +) { + const { key } = options + const persister = useStoragePersister(options) + + const [state, setState] = useState(() => { + return persister.loadState(key) ?? initialValue + }) + + useEffect(() => { + persister.saveState(key, state) + + const handleStorageChange = (e: StorageEvent) => { + if (e.key === key && e.newValue) { + try { + const parsed = JSON.parse(e.newValue) + if (parsed.state) { + setState(parsed.state) + } + } catch { + // Ignore invalid JSON + } + } + } + + window.addEventListener('storage', handleStorageChange) + return () => window.removeEventListener('storage', handleStorageChange) + }, [key, state, persister]) + + return [state, setState] as const +} + +/** + * A hook that persists state to localStorage and syncs it across tabs + * + * @example + * ```tsx + * const [value, setValue] = useLocalStorageState('my-key', 'initial value') + * ``` + */ +export function useLocalStorageState( + key: string, + initialValue: TValue, + options?: { + buster?: string + maxAge?: number + }, +) { + return useStorageState(initialValue, { + key, + storage: localStorage, + ...options, + }) +} + +/** + * A hook that persists state to sessionStorage and syncs it across tabs + * + * @example + * ```tsx + * const [value, setValue] = useSessionStorageState('my-key', 'initial value') + * ``` + */ +export function useSessionStorageState( + key: string, + initialValue: TValue, + options?: { + buster?: string + maxAge?: number + }, +) { + return useStorageState(initialValue, { + key, + storage: sessionStorage, + ...options, + }) +} diff --git a/packages/react-pacer/vite.config.ts b/packages/react-pacer/vite.config.ts index ed729bf51..bbfa011e7 100644 --- a/packages/react-pacer/vite.config.ts +++ b/packages/react-pacer/vite.config.ts @@ -20,6 +20,7 @@ export default mergeConfig( tanstackViteConfig({ entry: [ './src/async-debouncer/index.ts', + './src/async-persister/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', './src/async-throttler/index.ts', @@ -27,6 +28,7 @@ export default mergeConfig( './src/compare/index.ts', './src/debouncer/index.ts', './src/index.ts', + './src/persister/index.ts', './src/queuer/index.ts', './src/rate-limiter/index.ts', './src/throttler/index.ts', diff --git a/packages/solid-pacer/package.json b/packages/solid-pacer/package.json index 82320aab1..c89d90387 100644 --- a/packages/solid-pacer/package.json +++ b/packages/solid-pacer/package.json @@ -47,6 +47,16 @@ "default": "./dist/cjs/async-debouncer/index.cjs" } }, + "./async-persister": { + "import": { + "types": "./dist/esm/async-persister/index.d.ts", + "default": "./dist/esm/async-persister/index.js" + }, + "require": { + "types": "./dist/cjs/async-persister/index.d.cts", + "default": "./dist/cjs/async-persister/index.cjs" + } + }, "./async-queuer": { "import": { "types": "./dist/esm/async-queuer/index.d.ts", @@ -97,6 +107,16 @@ "default": "./dist/cjs/debouncer/index.cjs" } }, + "./persister": { + "import": { + "types": "./dist/esm/persister/index.d.ts", + "default": "./dist/esm/persister/index.js" + }, + "require": { + "types": "./dist/cjs/persister/index.d.cts", + "default": "./dist/cjs/persister/index.cjs" + } + }, "./queuer": { "import": { "types": "./dist/esm/queuer/index.d.ts", diff --git a/packages/solid-pacer/src/async-persister/index.ts b/packages/solid-pacer/src/async-persister/index.ts new file mode 100644 index 000000000..bfbfc5627 --- /dev/null +++ b/packages/solid-pacer/src/async-persister/index.ts @@ -0,0 +1 @@ +export * from '@tanstack/pacer/async-persister' diff --git a/packages/solid-pacer/src/persister/index.ts b/packages/solid-pacer/src/persister/index.ts new file mode 100644 index 000000000..75f962e34 --- /dev/null +++ b/packages/solid-pacer/src/persister/index.ts @@ -0,0 +1 @@ +export * from '@tanstack/pacer/persister' diff --git a/packages/solid-pacer/vite.config.ts b/packages/solid-pacer/vite.config.ts index 621b9e0a7..19f6f07c2 100644 --- a/packages/solid-pacer/vite.config.ts +++ b/packages/solid-pacer/vite.config.ts @@ -20,6 +20,7 @@ export default mergeConfig( tanstackViteConfig({ entry: [ './src/async-debouncer/index.ts', + './src/async-persister/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', './src/async-throttler/index.ts', @@ -27,6 +28,7 @@ export default mergeConfig( './src/compare/index.ts', './src/debouncer/index.ts', './src/index.ts', + './src/persister/index.ts', './src/queuer/index.ts', './src/rate-limiter/index.ts', './src/throttler/index.ts', From 7ea9d517f2608e286e0e59210263dd14b9340b89 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 06:35:11 +0000 Subject: [PATCH 02/51] ci: apply automated fixes --- .../functions/uselocalstoragestate.md | 2 +- .../functions/usesessionstoragestate.md | 2 +- .../functions/usestoragepersister.md | 2 +- docs/reference/classes/asyncpersister.md | 10 ++++----- docs/reference/classes/persister.md | 10 ++++----- docs/reference/classes/storagepersister.md | 14 ++++++------ docs/reference/interfaces/persistedstorage.md | 8 +++---- .../interfaces/storagepersisteroptions.md | 22 +++++++++---------- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/framework/react/reference/functions/uselocalstoragestate.md b/docs/framework/react/reference/functions/uselocalstoragestate.md index 4a21a680d..dfd08f6c5 100644 --- a/docs/framework/react/reference/functions/uselocalstoragestate.md +++ b/docs/framework/react/reference/functions/uselocalstoragestate.md @@ -14,7 +14,7 @@ function useLocalStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: react-pacer/src/persister/useStorageState.ts:47 +Defined in: [react-pacer/src/persister/useStorageState.ts:47](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L47) A hook that persists state to localStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usesessionstoragestate.md b/docs/framework/react/reference/functions/usesessionstoragestate.md index 9a118a6ee..e65ede42e 100644 --- a/docs/framework/react/reference/functions/usesessionstoragestate.md +++ b/docs/framework/react/reference/functions/usesessionstoragestate.md @@ -14,7 +14,7 @@ function useSessionStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: react-pacer/src/persister/useStorageState.ts:70 +Defined in: [react-pacer/src/persister/useStorageState.ts:70](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L70) A hook that persists state to sessionStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usestoragepersister.md b/docs/framework/react/reference/functions/usestoragepersister.md index 8478ddb5e..1ac19868b 100644 --- a/docs/framework/react/reference/functions/usestoragepersister.md +++ b/docs/framework/react/reference/functions/usestoragepersister.md @@ -11,7 +11,7 @@ title: useStoragePersister function useStoragePersister(options): StoragePersister ``` -Defined in: react-pacer/src/persister/useStoragePersister.ts:6 +Defined in: [react-pacer/src/persister/useStoragePersister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStoragePersister.ts#L6) ## Type Parameters diff --git a/docs/reference/classes/asyncpersister.md b/docs/reference/classes/asyncpersister.md index 9211b4174..a9e73bc1c 100644 --- a/docs/reference/classes/asyncpersister.md +++ b/docs/reference/classes/asyncpersister.md @@ -7,7 +7,7 @@ title: AsyncPersister # Class: `abstract` AsyncPersister\ -Defined in: async-persister.ts:4 +Defined in: [async-persister.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L4) Interface for an async persister that can save/load state for a given type @@ -23,7 +23,7 @@ Interface for an async persister that can save/load state for a given type new AsyncPersister(key): AsyncPersister ``` -Defined in: async-persister.ts:5 +Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L5) #### Parameters @@ -43,7 +43,7 @@ Defined in: async-persister.ts:5 readonly key: string; ``` -Defined in: async-persister.ts:5 +Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L5) ## Methods @@ -53,7 +53,7 @@ Defined in: async-persister.ts:5 abstract loadState(key): Promise ``` -Defined in: async-persister.ts:7 +Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L7) #### Parameters @@ -73,7 +73,7 @@ Defined in: async-persister.ts:7 abstract saveState(key, state): Promise ``` -Defined in: async-persister.ts:8 +Defined in: [async-persister.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L8) #### Parameters diff --git a/docs/reference/classes/persister.md b/docs/reference/classes/persister.md index 2accf6ff4..03f1e85cf 100644 --- a/docs/reference/classes/persister.md +++ b/docs/reference/classes/persister.md @@ -7,7 +7,7 @@ title: Persister # Class: `abstract` Persister\ -Defined in: persister.ts:23 +Defined in: [persister.ts:23](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L23) Abstract class that defines the contract for a state persister implementation. A persister is responsible for loading and saving state to a storage medium. @@ -47,7 +47,7 @@ class MyPersister extends Persister { new Persister(key): Persister ``` -Defined in: persister.ts:24 +Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L24) #### Parameters @@ -67,7 +67,7 @@ Defined in: persister.ts:24 readonly key: string; ``` -Defined in: persister.ts:24 +Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L24) ## Methods @@ -77,7 +77,7 @@ Defined in: persister.ts:24 abstract loadState(key): undefined | TState ``` -Defined in: persister.ts:26 +Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L26) #### Parameters @@ -97,7 +97,7 @@ Defined in: persister.ts:26 abstract saveState(key, state): void ``` -Defined in: persister.ts:27 +Defined in: [persister.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L27) #### Parameters diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md index e2a5d382e..e0fa149bc 100644 --- a/docs/reference/classes/storagepersister.md +++ b/docs/reference/classes/storagepersister.md @@ -7,7 +7,7 @@ title: StoragePersister # Class: StoragePersister\ -Defined in: persister.ts:126 +Defined in: [persister.ts:126](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L126) A persister that saves state to browser local/session storage. @@ -55,7 +55,7 @@ const rateLimiter = new RateLimiter(fn, { new StoragePersister(options): StoragePersister ``` -Defined in: persister.ts:128 +Defined in: [persister.ts:128](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L128) #### Parameters @@ -79,7 +79,7 @@ Defined in: persister.ts:128 readonly key: string; ``` -Defined in: persister.ts:24 +Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L24) #### Inherited from @@ -93,7 +93,7 @@ Defined in: persister.ts:24 getOptions(): StoragePersisterOptions ``` -Defined in: persister.ts:146 +Defined in: [persister.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L146) Returns the current persister options @@ -109,7 +109,7 @@ Returns the current persister options loadState(key): undefined | TState ``` -Defined in: persister.ts:167 +Defined in: [persister.ts:167](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L167) #### Parameters @@ -133,7 +133,7 @@ Defined in: persister.ts:167 saveState(key, state): void ``` -Defined in: persister.ts:150 +Defined in: [persister.ts:150](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L150) #### Parameters @@ -161,7 +161,7 @@ Defined in: persister.ts:150 setOptions(newOptions): void ``` -Defined in: persister.ts:139 +Defined in: [persister.ts:139](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L139) Updates the persister options diff --git a/docs/reference/interfaces/persistedstorage.md b/docs/reference/interfaces/persistedstorage.md index a1950014e..a09e20e17 100644 --- a/docs/reference/interfaces/persistedstorage.md +++ b/docs/reference/interfaces/persistedstorage.md @@ -7,7 +7,7 @@ title: PersistedStorage # Interface: PersistedStorage\ -Defined in: persister.ts:30 +Defined in: [persister.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L30) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: persister.ts:30 optional buster: string; ``` -Defined in: persister.ts:31 +Defined in: [persister.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L31) *** @@ -31,7 +31,7 @@ Defined in: persister.ts:31 state: undefined | TState; ``` -Defined in: persister.ts:32 +Defined in: [persister.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L32) *** @@ -41,4 +41,4 @@ Defined in: persister.ts:32 timestamp: number; ``` -Defined in: persister.ts:33 +Defined in: [persister.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L33) diff --git a/docs/reference/interfaces/storagepersisteroptions.md b/docs/reference/interfaces/storagepersisteroptions.md index acc909bf7..f5093dfff 100644 --- a/docs/reference/interfaces/storagepersisteroptions.md +++ b/docs/reference/interfaces/storagepersisteroptions.md @@ -7,7 +7,7 @@ title: StoragePersisterOptions # Interface: StoragePersisterOptions\ -Defined in: persister.ts:42 +Defined in: [persister.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L42) Configuration options for creating a browser-based state persister. @@ -26,7 +26,7 @@ sessionStorage (cleared when browser tab/window closes) to store serialized stat optional buster: string; ``` -Defined in: persister.ts:47 +Defined in: [persister.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L47) A version string used to invalidate cached state. When changed, any existing stored state will be considered invalid and cleared. @@ -39,7 +39,7 @@ stored state will be considered invalid and cleared. optional deserializer: (state) => PersistedStorage; ``` -Defined in: persister.ts:52 +Defined in: [persister.ts:52](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L52) Optional function to customize how state is deserialized after loading from storage. By default, JSON.parse is used. @@ -62,7 +62,7 @@ By default, JSON.parse is used. key: string; ``` -Defined in: persister.ts:56 +Defined in: [persister.ts:56](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L56) Unique identifier used as the storage key for persisting state. @@ -74,7 +74,7 @@ Unique identifier used as the storage key for persisting state. optional maxAge: number; ``` -Defined in: persister.ts:61 +Defined in: [persister.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L61) Maximum age in milliseconds before stored state is considered expired. When exceeded, the state will be cleared and treated as if it doesn't exist. @@ -87,7 +87,7 @@ When exceeded, the state will be cleared and treated as if it doesn't exist. optional onLoadState: (key, state) => void; ``` -Defined in: persister.ts:65 +Defined in: [persister.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L65) Optional callback that runs after state is successfully loaded. @@ -113,7 +113,7 @@ Optional callback that runs after state is successfully loaded. optional onLoadStateError: (key, error) => void; ``` -Defined in: persister.ts:69 +Defined in: [persister.ts:69](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L69) Optional callback that runs after state is unable to be loaded. @@ -139,7 +139,7 @@ Optional callback that runs after state is unable to be loaded. optional onSaveState: (key, state) => void; ``` -Defined in: persister.ts:73 +Defined in: [persister.ts:73](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L73) Optional callback that runs after state is successfully saved. @@ -165,7 +165,7 @@ Optional callback that runs after state is successfully saved. optional onSaveStateError: (key, error) => void; ``` -Defined in: persister.ts:78 +Defined in: [persister.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L78) Optional callback that runs after state is unable to be saved. For example, if the storage is full (localStorage >= 5MB) @@ -192,7 +192,7 @@ For example, if the storage is full (localStorage >= 5MB) optional serializer: (state) => string; ``` -Defined in: persister.ts:83 +Defined in: [persister.ts:83](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L83) Optional function to customize how state is serialized before saving to storage. By default, JSON.stringify is used. @@ -215,7 +215,7 @@ By default, JSON.stringify is used. storage: Storage; ``` -Defined in: persister.ts:88 +Defined in: [persister.ts:88](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L88) The browser storage implementation to use for persisting state. Typically window.localStorage or window.sessionStorage. From e43360b8e419e9d250608e28f86d9fa1b5909e1e Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 10 Jun 2025 22:21:24 -0500 Subject: [PATCH 03/51] simplify storage key management --- .../functions/uselocalstoragestate.md | 2 +- .../functions/usesessionstoragestate.md | 2 +- docs/reference/classes/asyncpersister.md | 14 ++------ docs/reference/classes/asyncratelimiter.md | 28 +++++++-------- docs/reference/classes/persister.md | 20 +++-------- docs/reference/classes/ratelimiter.md | 22 ++++++------ docs/reference/classes/storagepersister.md | 14 ++------ docs/reference/functions/asyncratelimit.md | 2 +- docs/reference/functions/ratelimit.md | 2 +- .../interfaces/storagepersisteroptions.md | 24 +++---------- packages/pacer/src/async-persister.ts | 4 +-- packages/pacer/src/async-rate-limiter.ts | 13 ++----- packages/pacer/src/persister.ts | 34 +++++++++---------- packages/pacer/src/rate-limiter.ts | 13 ++----- packages/pacer/tests/persister.test.ts | 24 ++++++------- .../src/persister/useStorageState.ts | 15 ++++---- 16 files changed, 84 insertions(+), 149 deletions(-) diff --git a/docs/framework/react/reference/functions/uselocalstoragestate.md b/docs/framework/react/reference/functions/uselocalstoragestate.md index dfd08f6c5..9944c4d87 100644 --- a/docs/framework/react/reference/functions/uselocalstoragestate.md +++ b/docs/framework/react/reference/functions/uselocalstoragestate.md @@ -14,7 +14,7 @@ function useLocalStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: [react-pacer/src/persister/useStorageState.ts:47](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L47) +Defined in: [react-pacer/src/persister/useStorageState.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L46) A hook that persists state to localStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usesessionstoragestate.md b/docs/framework/react/reference/functions/usesessionstoragestate.md index e65ede42e..d5f2f90be 100644 --- a/docs/framework/react/reference/functions/usesessionstoragestate.md +++ b/docs/framework/react/reference/functions/usesessionstoragestate.md @@ -14,7 +14,7 @@ function useSessionStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: [react-pacer/src/persister/useStorageState.ts:70](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L70) +Defined in: [react-pacer/src/persister/useStorageState.ts:69](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L69) A hook that persists state to sessionStorage and syncs it across tabs diff --git a/docs/reference/classes/asyncpersister.md b/docs/reference/classes/asyncpersister.md index a9e73bc1c..8e1a1f6e0 100644 --- a/docs/reference/classes/asyncpersister.md +++ b/docs/reference/classes/asyncpersister.md @@ -50,17 +50,11 @@ Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/p ### loadState() ```ts -abstract loadState(key): Promise +abstract loadState(): Promise ``` Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L7) -#### Parameters - -##### key - -`string` - #### Returns `Promise`\<`undefined` \| `TState`\> @@ -70,17 +64,13 @@ Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/p ### saveState() ```ts -abstract saveState(key, state): Promise +abstract saveState(state): Promise ``` Defined in: [async-persister.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L8) #### Parameters -##### key - -`string` - ##### state `TState` diff --git a/docs/reference/classes/asyncratelimiter.md b/docs/reference/classes/asyncratelimiter.md index f7f130b68..4ab512922 100644 --- a/docs/reference/classes/asyncratelimiter.md +++ b/docs/reference/classes/asyncratelimiter.md @@ -99,7 +99,7 @@ Defined in: [async-rate-limiter.ts:163](https://github.com/TanStack/pacer/blob/m getEnabled(): boolean ``` -Defined in: [async-rate-limiter.ts:234](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L234) +Defined in: [async-rate-limiter.ts:225](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L225) Returns the current enabled state of the rate limiter @@ -115,7 +115,7 @@ Returns the current enabled state of the rate limiter getErrorCount(): number ``` -Defined in: [async-rate-limiter.ts:404](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L404) +Defined in: [async-rate-limiter.ts:395](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L395) Returns the number of times the function has errored @@ -131,7 +131,7 @@ Returns the number of times the function has errored getIsExecuting(): boolean ``` -Defined in: [async-rate-limiter.ts:418](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L418) +Defined in: [async-rate-limiter.ts:409](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L409) Returns whether the function is currently executing @@ -147,7 +147,7 @@ Returns whether the function is currently executing getLimit(): number ``` -Defined in: [async-rate-limiter.ts:241](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L241) +Defined in: [async-rate-limiter.ts:232](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L232) Returns the current limit of executions allowed within the time window @@ -163,7 +163,7 @@ Returns the current limit of executions allowed within the time window getMsUntilNextWindow(): number ``` -Defined in: [async-rate-limiter.ts:379](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L379) +Defined in: [async-rate-limiter.ts:370](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L370) Returns the number of milliseconds until the next execution will be possible For fixed windows, this is the time until the current window resets @@ -181,7 +181,7 @@ For sliding windows, this is the time until the oldest execution expires getOptions(): AsyncRateLimiterOptions ``` -Defined in: [async-rate-limiter.ts:227](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L227) +Defined in: [async-rate-limiter.ts:218](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L218) Returns the current rate limiter options @@ -197,7 +197,7 @@ Returns the current rate limiter options getRejectionCount(): number ``` -Defined in: [async-rate-limiter.ts:411](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L411) +Defined in: [async-rate-limiter.ts:402](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L402) Returns the number of times the function has been rejected @@ -213,7 +213,7 @@ Returns the number of times the function has been rejected getRemainingInWindow(): number ``` -Defined in: [async-rate-limiter.ts:369](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L369) +Defined in: [async-rate-limiter.ts:360](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L360) Returns the number of remaining executions allowed in the current window @@ -229,7 +229,7 @@ Returns the number of remaining executions allowed in the current window getSettleCount(): number ``` -Defined in: [async-rate-limiter.ts:397](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L397) +Defined in: [async-rate-limiter.ts:388](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L388) Returns the number of times the function has been settled @@ -245,7 +245,7 @@ Returns the number of times the function has been settled getSuccessCount(): number ``` -Defined in: [async-rate-limiter.ts:390](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L390) +Defined in: [async-rate-limiter.ts:381](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L381) Returns the number of times the function has been executed @@ -261,7 +261,7 @@ Returns the number of times the function has been executed getWindow(): number ``` -Defined in: [async-rate-limiter.ts:248](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L248) +Defined in: [async-rate-limiter.ts:239](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L239) Returns the current time window in milliseconds @@ -277,7 +277,7 @@ Returns the current time window in milliseconds maybeExecute(...args): Promise> ``` -Defined in: [async-rate-limiter.ts:281](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L281) +Defined in: [async-rate-limiter.ts:272](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L272) 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. @@ -329,7 +329,7 @@ await rateLimiter.maybeExecute('arg1', 'arg2'); // Rejected reset(): void ``` -Defined in: [async-rate-limiter.ts:425](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L425) +Defined in: [async-rate-limiter.ts:416](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L416) Resets the rate limiter state @@ -345,7 +345,7 @@ Resets the rate limiter state setOptions(newOptions): void ``` -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:211](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L211) Updates the rate limiter options diff --git a/docs/reference/classes/persister.md b/docs/reference/classes/persister.md index 03f1e85cf..c6756d581 100644 --- a/docs/reference/classes/persister.md +++ b/docs/reference/classes/persister.md @@ -16,16 +16,16 @@ A persister is responsible for loading and saving state to a storage medium. ```ts class MyPersister extends Persister { - constructor(key: string) { + constructor() { super(key) } - loadState(key: string): MyState | undefined { + loadState(): MyState | undefined { // Load state from storage return state } - saveState(key: string, state: MyState): void { + saveState(, state: MyState): void { // Save state to storage } } @@ -74,17 +74,11 @@ Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packag ### loadState() ```ts -abstract loadState(key): undefined | TState +abstract loadState(): undefined | TState ``` Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L26) -#### Parameters - -##### key - -`string` - #### Returns `undefined` \| `TState` @@ -94,17 +88,13 @@ Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packag ### saveState() ```ts -abstract saveState(key, state): void +abstract saveState(state): void ``` Defined in: [persister.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L27) #### Parameters -##### key - -`string` - ##### state `TState` diff --git a/docs/reference/classes/ratelimiter.md b/docs/reference/classes/ratelimiter.md index b6334011e..bc60b4418 100644 --- a/docs/reference/classes/ratelimiter.md +++ b/docs/reference/classes/ratelimiter.md @@ -76,7 +76,7 @@ Defined in: [rate-limiter.ts:104](https://github.com/TanStack/pacer/blob/main/pa getEnabled(): boolean ``` -Defined in: [rate-limiter.ts:168](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L168) +Defined in: [rate-limiter.ts:159](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L159) Returns the current enabled state of the rate limiter @@ -92,7 +92,7 @@ Returns the current enabled state of the rate limiter getExecutionCount(): number ``` -Defined in: [rate-limiter.ts:257](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L257) +Defined in: [rate-limiter.ts:248](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L248) Returns the number of times the function has been executed @@ -108,7 +108,7 @@ Returns the number of times the function has been executed getLimit(): number ``` -Defined in: [rate-limiter.ts:175](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L175) +Defined in: [rate-limiter.ts:166](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L166) Returns the current limit of executions allowed within the time window @@ -124,7 +124,7 @@ Returns the current limit of executions allowed within the time window getMsUntilNextWindow(): number ``` -Defined in: [rate-limiter.ts:279](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L279) +Defined in: [rate-limiter.ts:270](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L270) Returns the number of milliseconds until the next execution will be possible @@ -140,7 +140,7 @@ Returns the number of milliseconds until the next execution will be possible getOptions(): Required> ``` -Defined in: [rate-limiter.ts:161](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L161) +Defined in: [rate-limiter.ts:152](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L152) Returns the current rate limiter options @@ -156,7 +156,7 @@ Returns the current rate limiter options getRejectionCount(): number ``` -Defined in: [rate-limiter.ts:264](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L264) +Defined in: [rate-limiter.ts:255](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L255) Returns the number of times the function has been rejected @@ -172,7 +172,7 @@ Returns the number of times the function has been rejected getRemainingInWindow(): number ``` -Defined in: [rate-limiter.ts:271](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L271) +Defined in: [rate-limiter.ts:262](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L262) Returns the number of remaining executions allowed in the current window @@ -188,7 +188,7 @@ Returns the number of remaining executions allowed in the current window getWindow(): number ``` -Defined in: [rate-limiter.ts:182](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L182) +Defined in: [rate-limiter.ts:173](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L173) Returns the current time window in milliseconds @@ -204,7 +204,7 @@ Returns the current time window in milliseconds maybeExecute(...args): boolean ``` -Defined in: [rate-limiter.ts:201](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L201) +Defined in: [rate-limiter.ts:192](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L192) 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. @@ -239,7 +239,7 @@ rateLimiter.maybeExecute('arg1', 'arg2'); // false reset(): void ``` -Defined in: [rate-limiter.ts:290](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L290) +Defined in: [rate-limiter.ts:281](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L281) Resets the rate limiter state @@ -255,7 +255,7 @@ Resets the rate limiter state setOptions(newOptions): void ``` -Defined in: [rate-limiter.ts:154](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L154) +Defined in: [rate-limiter.ts:145](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L145) Updates the rate limiter options diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md index e0fa149bc..88d1322ff 100644 --- a/docs/reference/classes/storagepersister.md +++ b/docs/reference/classes/storagepersister.md @@ -106,17 +106,11 @@ Returns the current persister options ### loadState() ```ts -loadState(key): undefined | TState +loadState(): undefined | TState ``` Defined in: [persister.ts:167](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L167) -#### Parameters - -##### key - -`string` - #### Returns `undefined` \| `TState` @@ -130,17 +124,13 @@ Defined in: [persister.ts:167](https://github.com/TanStack/pacer/blob/main/packa ### saveState() ```ts -saveState(key, state): void +saveState(state): void ``` Defined in: [persister.ts:150](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L150) #### Parameters -##### key - -`string` - ##### state `TState` diff --git a/docs/reference/functions/asyncratelimit.md b/docs/reference/functions/asyncratelimit.md index 09e45adcf..fa1129639 100644 --- a/docs/reference/functions/asyncratelimit.md +++ b/docs/reference/functions/asyncratelimit.md @@ -11,7 +11,7 @@ title: asyncRateLimit function asyncRateLimit(fn, initialOptions): (...args) => Promise> ``` -Defined in: [async-rate-limiter.ts:491](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L491) +Defined in: [async-rate-limiter.ts:482](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L482) Creates an async rate-limited function that will execute the provided function up to a maximum number of times within a time window. diff --git a/docs/reference/functions/ratelimit.md b/docs/reference/functions/ratelimit.md index b15248c2b..6c998336a 100644 --- a/docs/reference/functions/ratelimit.md +++ b/docs/reference/functions/ratelimit.md @@ -11,7 +11,7 @@ title: rateLimit function rateLimit(fn, initialOptions): (...args) => boolean ``` -Defined in: [rate-limiter.ts:336](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L336) +Defined in: [rate-limiter.ts:327](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L327) Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window. diff --git a/docs/reference/interfaces/storagepersisteroptions.md b/docs/reference/interfaces/storagepersisteroptions.md index f5093dfff..867aa574b 100644 --- a/docs/reference/interfaces/storagepersisteroptions.md +++ b/docs/reference/interfaces/storagepersisteroptions.md @@ -84,7 +84,7 @@ When exceeded, the state will be cleared and treated as if it doesn't exist. ### onLoadState()? ```ts -optional onLoadState: (key, state) => void; +optional onLoadState: (state) => void; ``` Defined in: [persister.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L65) @@ -93,10 +93,6 @@ Optional callback that runs after state is successfully loaded. #### Parameters -##### key - -`string` - ##### state `undefined` | `TState` @@ -110,7 +106,7 @@ Optional callback that runs after state is successfully loaded. ### onLoadStateError()? ```ts -optional onLoadStateError: (key, error) => void; +optional onLoadStateError: (error) => void; ``` Defined in: [persister.ts:69](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L69) @@ -119,10 +115,6 @@ Optional callback that runs after state is unable to be loaded. #### Parameters -##### key - -`string` - ##### error `Error` @@ -136,7 +128,7 @@ Optional callback that runs after state is unable to be loaded. ### onSaveState()? ```ts -optional onSaveState: (key, state) => void; +optional onSaveState: (state) => void; ``` Defined in: [persister.ts:73](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L73) @@ -145,10 +137,6 @@ Optional callback that runs after state is successfully saved. #### Parameters -##### key - -`string` - ##### state `TState` @@ -162,7 +150,7 @@ Optional callback that runs after state is successfully saved. ### onSaveStateError()? ```ts -optional onSaveStateError: (key, error) => void; +optional onSaveStateError: (error) => void; ``` Defined in: [persister.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L78) @@ -172,10 +160,6 @@ For example, if the storage is full (localStorage >= 5MB) #### Parameters -##### key - -`string` - ##### error `Error` diff --git a/packages/pacer/src/async-persister.ts b/packages/pacer/src/async-persister.ts index 009d11b92..9dad5ee81 100644 --- a/packages/pacer/src/async-persister.ts +++ b/packages/pacer/src/async-persister.ts @@ -4,6 +4,6 @@ export abstract class AsyncPersister { constructor(public readonly key: string) {} - abstract loadState(key: string): Promise - abstract saveState(key: string, state: TState): Promise + abstract loadState(): Promise + abstract saveState(state: TState): Promise } diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 7dd04c0a5..7996f2272 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -172,7 +172,7 @@ export class AsyncRateLimiter { this._persister = this._options.persister if (this._persister) { // Load state - const loadedState = this._persister.loadState(this._persister.key) + const loadedState = this._persister.loadState() if (loadedState instanceof Promise) { loadedState.then((state) => { if (state) { @@ -201,16 +201,7 @@ export class AsyncRateLimiter { ): void { this._state = { ...this._state, ...state } if (save) { - this.saveState() - } - } - - /** - * Saves state using the persister if available - */ - private saveState(): void { - if (this._persister) { - this._persister.saveState(this._persister.key, this.getState()) + this._persister?.saveState(this.getState()) } } diff --git a/packages/pacer/src/persister.ts b/packages/pacer/src/persister.ts index f90fc76b0..8d1fa670b 100644 --- a/packages/pacer/src/persister.ts +++ b/packages/pacer/src/persister.ts @@ -5,16 +5,16 @@ * @example * ```ts * class MyPersister extends Persister { - * constructor(key: string) { + * constructor() { * super(key) * } * - * loadState(key: string): MyState | undefined { + * loadState(): MyState | undefined { * // Load state from storage * return state * } * - * saveState(key: string, state: MyState): void { + * saveState(, state: MyState): void { * // Save state to storage * } * } @@ -23,8 +23,8 @@ export abstract class Persister { constructor(public readonly key: string) {} - abstract loadState(key: string): TState | undefined - abstract saveState(key: string, state: TState): void + abstract loadState(): TState | undefined + abstract saveState(state: TState): void } export interface PersistedStorage { @@ -62,20 +62,20 @@ export interface StoragePersisterOptions { /** * Optional callback that runs after state is successfully loaded. */ - onLoadState?: (key: string, state: TState | undefined) => void + onLoadState?: (state: TState | undefined) => void /** * Optional callback that runs after state is unable to be loaded. */ - onLoadStateError?: (key: string, error: Error) => void + onLoadStateError?: (error: Error) => void /** * Optional callback that runs after state is successfully saved. */ - onSaveState?: (key: string, state: TState) => void + onSaveState?: (state: TState) => void /** * Optional callback that runs after state is unable to be saved. * For example, if the storage is full (localStorage >= 5MB) */ - onSaveStateError?: (key: string, error: Error) => void + onSaveStateError?: (error: Error) => void /** * Optional function to customize how state is serialized before saving to storage. * By default, JSON.stringify is used. @@ -147,25 +147,25 @@ export class StoragePersister extends Persister { return this._options } - saveState(key: string, state: TState): void { + saveState(state: TState): void { try { this._options.storage.setItem( - key, + this.key, this._options.serializer!({ buster: this._options.buster, state, timestamp: Date.now(), }), ) - this._options.onSaveState?.(key, state) + this._options.onSaveState?.(state) } catch (error) { console.error(error) - this._options.onSaveStateError?.(key, error as Error) + this._options.onSaveStateError?.(error as Error) } } - loadState(key: string): TState | undefined { - const stored = this._options.storage.getItem(key) + loadState(): TState | undefined { + const stored = this._options.storage.getItem(this.key) if (!stored) { return undefined } @@ -184,11 +184,11 @@ export class StoragePersister extends Persister { } const state = parsed.state as TState - this._options.onLoadState?.(key, state) + this._options.onLoadState?.(state) return state } catch (error) { console.error(error) - this._options.onLoadStateError?.(key, error as Error) + this._options.onLoadStateError?.(error as Error) return undefined } } diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index f6ec03c96..918c29c5d 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -112,7 +112,7 @@ export class RateLimiter { this._persister = this._options.persister if (this._persister) { // Load state - const loadedState = this._persister.loadState(this._persister.key) + const loadedState = this._persister.loadState() if (loadedState) { this.setState(loadedState, false) } @@ -135,16 +135,7 @@ export class RateLimiter { ): void { this._state = { ...this._state, ...state } if (save) { - this.saveState() - } - } - - /** - * Saves state using the persister if available - */ - private saveState(): void { - if (this._persister) { - this._persister.saveState(this._persister.key, this.getState()) + this._persister?.saveState(this.getState()) } } diff --git a/packages/pacer/tests/persister.test.ts b/packages/pacer/tests/persister.test.ts index 84e872c0b..cea69f71d 100644 --- a/packages/pacer/tests/persister.test.ts +++ b/packages/pacer/tests/persister.test.ts @@ -26,13 +26,13 @@ describe('createStoragePersister', () => { ;(storage.getItem as any).mockReturnValue( JSON.stringify({ state, timestamp }), ) - const result = persister.loadState('test-key') + const result = persister.loadState() expect(result).toEqual(state) }) it('should return undefined when no state exists', () => { ;(storage.getItem as any).mockReturnValue(null) - const result = persister.loadState('test-key') + const result = persister.loadState() expect(result).toBeUndefined() }) @@ -47,8 +47,8 @@ describe('createStoragePersister', () => { storage, onSaveStateError, }) - persister.saveState('test-key', { count: 1 }) - expect(onSaveStateError).toHaveBeenCalledWith('test-key', error) + persister.saveState({ count: 1 }) + expect(onSaveStateError).toHaveBeenCalledWith(error) }) it('should call onSaveState callback when state is saved', () => { @@ -59,8 +59,8 @@ describe('createStoragePersister', () => { onSaveState, }) const state = { count: 1 } - persister.saveState('test-key', state) - expect(onSaveState).toHaveBeenCalledWith('test-key', state) + persister.saveState(state) + expect(onSaveState).toHaveBeenCalledWith(state) }) it('should call onLoadState callback when state is loaded', () => { @@ -74,8 +74,8 @@ describe('createStoragePersister', () => { ;(storage.getItem as any).mockReturnValue( JSON.stringify({ state, timestamp: Date.now() }), ) - persister.loadState('test-key') - expect(onLoadState).toHaveBeenCalledWith('test-key', state) + persister.loadState() + expect(onLoadState).toHaveBeenCalledWith(state) }) it('should respect buster string when loading state', () => { @@ -91,7 +91,7 @@ describe('createStoragePersister', () => { buster: 'v1', }), ) - const result = persister.loadState('test-key') + const result = persister.loadState() expect(result).toBeUndefined() }) @@ -107,7 +107,7 @@ describe('createStoragePersister', () => { timestamp: Date.now() - 2000, }), ) - const result = persister.loadState('test-key') + const result = persister.loadState() expect(result).toBeUndefined() }) @@ -121,12 +121,12 @@ describe('createStoragePersister', () => { deserializer, }) const state = { count: 1 } - persister.saveState('test-key', state) + persister.saveState(state) expect(serializer).toHaveBeenCalled() ;(storage.getItem as any).mockReturnValue( JSON.stringify({ state, timestamp: Date.now() }), ) - persister.loadState('test-key') + persister.loadState() expect(deserializer).toHaveBeenCalled() }) }) diff --git a/packages/react-pacer/src/persister/useStorageState.ts b/packages/react-pacer/src/persister/useStorageState.ts index a5acaf35c..50d584e0b 100644 --- a/packages/react-pacer/src/persister/useStorageState.ts +++ b/packages/react-pacer/src/persister/useStorageState.ts @@ -6,32 +6,31 @@ function useStorageState( initialValue: TValue, options: StoragePersisterOptions, ) { - const { key } = options const persister = useStoragePersister(options) const [state, setState] = useState(() => { - return persister.loadState(key) ?? initialValue + return persister.loadState() ?? initialValue }) useEffect(() => { - persister.saveState(key, state) + persister.saveState(state) const handleStorageChange = (e: StorageEvent) => { - if (e.key === key && e.newValue) { + if (e.key === options.key && e.newValue) { try { - const parsed = JSON.parse(e.newValue) + const parsed = (options.deserializer ?? JSON.parse)(e.newValue) if (parsed.state) { setState(parsed.state) } - } catch { - // Ignore invalid JSON + } catch (e) { + console.error('Failed to parse storage event', e) } } } window.addEventListener('storage', handleStorageChange) return () => window.removeEventListener('storage', handleStorageChange) - }, [key, state, persister]) + }, [state, persister, options.deserializer, options.key]) return [state, setState] as const } From d5abc25c0ad6dbbef692dd15c5cfe9ff6ca8a7a7 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 10 Jun 2025 23:02:33 -0500 Subject: [PATCH 04/51] start writing persisting guide --- docs/config.json | 2 +- docs/guides/persisting.md | 202 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 docs/guides/persisting.md diff --git a/docs/config.json b/docs/config.json index f9da70b73..c83a878c2 100644 --- a/docs/config.json +++ b/docs/config.json @@ -84,7 +84,7 @@ }, { "label": "Persisting State Guide", - "to": "guides/persisting-state" + "to": "guides/persisting" } ] }, diff --git a/docs/guides/persisting.md b/docs/guides/persisting.md new file mode 100644 index 000000000..ba42cf40c --- /dev/null +++ b/docs/guides/persisting.md @@ -0,0 +1,202 @@ +--- +title: Persisting State Guide +id: persisting +--- + +Persisting (saving and loading state) would not seem to be in the original scope of TanStack Pacer, but each of the utilities in TanStack Pacer needed to be written with persistence in mind in order to be useful for longer running tasks or back-end API integrations. So we built the persistence features in a way that was standalone and could be used with either the TanStack Pacer utilities, or as any other simple implementations like a `useLocalStorageState` hook. + +## Understanding Persisters + +At its core, a persister is responsible for saving and loading state. TanStack Pacer provides two types of persisters to handle different persistence needs: + +The `Persister` class is designed for synchronous operations, making it perfect for client-side storage like `localStorage` or `sessionStorage`. It provides immediate access to state, which is ideal for most browser-based applications where you need to maintain state across page reloads or browser sessions. + +For more complex scenarios, particularly when working with external storage systems like databases or Redis, the `AsyncPersister` class offers a promise-based approach to state management. This is especially useful in server-side applications or when you need to share state across multiple instances of your application. + +## Storage Persister (Client-Side) + +The `StoragePersister` is TanStack Pacer's built-in solution for browser-based state persistence. It wraps the browser's storage APIs (`localStorage` or `sessionStorage`) with additional features like version control, state expiration, and error handling. + +Here's a practical example of using a `StoragePersister` with a rate limiter in a React application: + +```tsx +import { useState } from 'react' +import { useRateLimiter } from '@tanstack/react-pacer/rate-limiter' +import { useStoragePersister } from '@tanstack/react-pacer/persister' + +function Counter() { + const [instantCount, setInstantCount] = useState(0) + const [limitedCount, setLimitedCount] = useState(0) + + // Create a rate limiter that persists its state + const rateLimiter = useRateLimiter(setLimitedCount, { + limit: 5, + window: 5000, // 5 seconds + onReject: (rateLimiter) => { + console.log('Rate limit exceeded. Try again in', rateLimiter.getMsUntilNextWindow(), 'ms') + }, + // Configure the persister to save state to localStorage + persister: useStoragePersister({ + key: 'my-rate-limiter', + storage: localStorage, + maxAge: 1000 * 60, // State expires after 1 minute + buster: 'v1', // Version control for state structure + }), + }) + + function increment() { + setInstantCount((c) => { + const newCount = c + 1 + rateLimiter.maybeExecute(newCount) + return newCount + }) + } + + return ( +
+

Instant Count: {instantCount}

+

Rate Limited Count: {limitedCount}

+

Remaining in Window: {rateLimiter.getRemainingInWindow()}

+ + +
+ ) +} +``` + +In this example, the rate limiter's state (including execution count, rejection count, and execution times) is automatically persisted to localStorage. This means that even if the user refreshes the page or closes and reopens the browser, the rate limit will be maintained. The `maxAge` option ensures that the state doesn't become stale, and the `buster` option allows you to invalidate old state when you make breaking changes to your application. + +### Storage Persister Callbacks + +The `StoragePersister` utility provides several callback functions that help you customize and debug state persistence: + +- `onSaveState`: Called whenever state is successfully saved to storage. Useful for logging or analytics. +- `onLoadState`: Called when state is loaded from storage. Helpful for debugging state restoration. +- `onSaveStateError`: Called if there's an error saving state. Use this to handle storage errors gracefully. This can be useful for handling storage quota limits (localStorage is limited to 5MB), or other storage errors. +- `onLoadStateError`: Called if there's an error loading state. Useful for fallback behavior when state can't be restored/parsed. +- `serialize`: Custom function to transform state before saving to storage. Useful for complex state objects, custom serialization for Dates, etc. +- `deserialize`: Custom function to transform loaded data back into state. Should match your serialize functionality. + +### Framework Integrations + +Several convienient hooks are provided for persisting state in each JavaScript framework that TanStack Pacer supports. You may find hooks like `useStoragePersister` or `useLocalStorageState` useful. + +## Creating Custom Persisters + +While the built-in `StoragePersister` covers many use cases, you might need to integrate with custom storage solutions or add specific functionality. TanStack Pacer makes this easy by providing base classes that you can extend. + +For synchronous operations, you can extend the `Persister` class: + +```ts +import { Persister } from '@tanstack/pacer' + +class CustomPersister extends Persister { + constructor(key: string) { + super(key) + } + + loadState(): TState | undefined { + // Load state from your storage + const stored = customStorage.getItem(this.key) + return stored ? JSON.parse(stored) : undefined + } + + saveState(state: TState): void { + // Save state to your storage + customStorage.setItem(this.key, JSON.stringify(state)) + } +} +``` + +### Async Persister (Client-Side or Server-Side) + +When working with asynchronous storage systems, the `AsyncPersister` class provides a foundation for building custom async persisters. This is particularly useful in Node.js backend APIs where you need to share rate limiting state across multiple server instances or maintain state in a database. + +Here's an example of implementing a Redis-based persister for distributed rate limiting: + +```ts +import { AsyncPersister } from '@tanstack/pacer' +import { createClient } from 'redis' + +class RedisPersister extends AsyncPersister { + private client: ReturnType + + constructor(key: string, redisUrl: string) { + super(key) + this.client = createClient({ url: redisUrl }) + this.client.connect() + } + + async loadState(): Promise { + const stored = await this.client.get(this.key) + if (!stored) return undefined + + const { state, timestamp, buster } = JSON.parse(stored) + const now = Date.now() + + // Check if state is expired (e.g., 1 hour old) + if (now - timestamp > 1000 * 60 * 60) { + await this.client.del(this.key) + return undefined + } + + return state + } + + async saveState(state: TState): Promise { + await this.client.set( + this.key, + JSON.stringify({ + state, + timestamp: Date.now(), + buster: 'v1' // Version control for state structure + }) + ) + } + + async cleanup(): Promise { + await this.client.quit() + } +} + +// Usage in an Express API +import express from 'express' +import { AsyncRateLimiter } from '@tanstack/pacer' + +const app = express() +const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379' + +// Create a rate limiter for API endpoints +const apiRateLimiter = new AsyncRateLimiter( + async (req, res) => { + // Your API endpoint logic here + res.json({ success: true }) + }, + { + limit: 100, + window: 60 * 1000, // 100 requests per minute + persister: new RedisPersister('api-rate-limit', redisUrl) + } +) + +app.get('/api/endpoint', async (req, res) => { + const success = await apiRateLimiter.maybeExecute(req, res) + if (!success) { + res.status(429).json({ + error: 'Too many requests', + retryAfter: apiRateLimiter.getMsUntilNextWindow() + }) + } +}) +``` + +This implementation demonstrates how to use `AsyncPersister` in a backend context with Redis, which is ideal for distributed rate limiting. The key features include: + +- Automatic state expiration using timestamps +- Version control through the `buster` field +- Proper connection management with cleanup methods +- Integration with Express +- Distributed rate limiting across multiple server instances + +The key advantage of using async persisters in a backend context is that they allow you to share rate limiting state across multiple server instances. This is crucial for maintaining consistent rate limits in a clustered environment, where multiple servers might be handling requests for the same client. + From dabfe8b8163ded3e6ba6dc150a943904cdf8cc18 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 17 Jun 2025 17:47:08 -0500 Subject: [PATCH 05/51] separate out persisters into own package --- .../react/useAsyncRateLimiter/package.json | 1 + .../react/useAsyncRateLimiter/src/index.tsx | 2 +- examples/react/useRateLimiter/package.json | 1 + examples/react/useRateLimiter/src/index.tsx | 9 +- package.json | 4 +- packages/pacer/package.json | 33 +----- packages/pacer/src/async-rate-limiter.ts | 3 +- packages/pacer/src/index.ts | 3 - packages/pacer/src/rate-limiter.ts | 4 +- packages/pacer/tsconfig.json | 8 +- packages/persister/CHANGELOG.md | 2 + packages/persister/eslint.config.js | 10 ++ packages/persister/package.json | 103 +++++++++++++++++ .../src/async-persister.ts | 0 packages/{pacer => persister}/src/compare.ts | 0 packages/persister/src/index.ts | 5 + .../{pacer => persister}/src/persister.ts | 15 ++- packages/persister/src/types.ts | 18 +++ packages/persister/src/utils.ts | 27 +++++ .../tests/compare.test.ts | 0 .../tests/persister.test.ts | 0 packages/persister/tests/test-setup.ts | 1 + packages/persister/tsconfig.docs.json | 4 + packages/persister/tsconfig.json | 4 + packages/persister/vite.config.ts | 22 ++++ packages/react-pacer/package.json | 20 ---- .../react-pacer/src/async-persister/index.ts | 1 - packages/react-pacer/src/compare/index.ts | 1 - packages/react-pacer/src/index.ts | 3 - packages/react-pacer/vite.config.ts | 3 - packages/react-persister/CHANGELOG.md | 1 + packages/react-persister/eslint.config.js | 32 ++++++ packages/react-persister/package.json | 108 ++++++++++++++++++ .../src/async-persister/index.ts | 1 + packages/react-persister/src/compare/index.ts | 1 + packages/react-persister/src/index.ts | 12 ++ .../src/persister/index.ts | 2 +- .../src/persister/useStoragePersister.ts | 6 +- .../src/persister/useStorageState.ts | 2 +- packages/react-persister/src/types/index.ts | 1 + packages/react-persister/src/utils/index.ts | 1 + packages/react-persister/tsconfig.docs.json | 9 ++ packages/react-persister/tsconfig.json | 7 ++ packages/react-persister/vite.config.ts | 31 +++++ packages/solid-pacer/package.json | 20 ---- .../solid-pacer/src/async-persister/index.ts | 1 - packages/solid-pacer/src/compare/index.ts | 1 - packages/solid-pacer/src/persister/index.ts | 1 - packages/solid-pacer/vite.config.ts | 3 - pnpm-lock.yaml | 44 ++++++- vitest.workspace.js | 18 ++- 51 files changed, 494 insertions(+), 115 deletions(-) create mode 100644 packages/persister/CHANGELOG.md create mode 100644 packages/persister/eslint.config.js create mode 100644 packages/persister/package.json rename packages/{pacer => persister}/src/async-persister.ts (100%) rename packages/{pacer => persister}/src/compare.ts (100%) create mode 100644 packages/persister/src/index.ts rename packages/{pacer => persister}/src/persister.ts (93%) create mode 100644 packages/persister/src/types.ts create mode 100644 packages/persister/src/utils.ts rename packages/{pacer => persister}/tests/compare.test.ts (100%) rename packages/{pacer => persister}/tests/persister.test.ts (100%) create mode 100644 packages/persister/tests/test-setup.ts create mode 100644 packages/persister/tsconfig.docs.json create mode 100644 packages/persister/tsconfig.json create mode 100644 packages/persister/vite.config.ts delete mode 100644 packages/react-pacer/src/async-persister/index.ts delete mode 100644 packages/react-pacer/src/compare/index.ts create mode 100644 packages/react-persister/CHANGELOG.md create mode 100644 packages/react-persister/eslint.config.js create mode 100644 packages/react-persister/package.json create mode 100644 packages/react-persister/src/async-persister/index.ts create mode 100644 packages/react-persister/src/compare/index.ts create mode 100644 packages/react-persister/src/index.ts rename packages/{react-pacer => react-persister}/src/persister/index.ts (61%) rename packages/{react-pacer => react-persister}/src/persister/useStoragePersister.ts (58%) rename packages/{react-pacer => react-persister}/src/persister/useStorageState.ts (96%) create mode 100644 packages/react-persister/src/types/index.ts create mode 100644 packages/react-persister/src/utils/index.ts create mode 100644 packages/react-persister/tsconfig.docs.json create mode 100644 packages/react-persister/tsconfig.json create mode 100644 packages/react-persister/vite.config.ts delete mode 100644 packages/solid-pacer/src/async-persister/index.ts delete mode 100644 packages/solid-pacer/src/compare/index.ts delete mode 100644 packages/solid-pacer/src/persister/index.ts diff --git a/examples/react/useAsyncRateLimiter/package.json b/examples/react/useAsyncRateLimiter/package.json index deaa8f5e0..3c6dc591c 100644 --- a/examples/react/useAsyncRateLimiter/package.json +++ b/examples/react/useAsyncRateLimiter/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", + "@tanstack/react-persister": "^0.0.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index 486672f00..e33c4f97d 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncRateLimiter } from '@tanstack/react-pacer/async-rate-limiter' -import { useStoragePersister } from '@tanstack/react-pacer/persister' +import { useStoragePersister } from '@tanstack/react-persister/persister' import type { AsyncRateLimiterState } from '@tanstack/react-pacer/async-rate-limiter' interface SearchResult { diff --git a/examples/react/useRateLimiter/package.json b/examples/react/useRateLimiter/package.json index 0925ad554..d71f914b5 100644 --- a/examples/react/useRateLimiter/package.json +++ b/examples/react/useRateLimiter/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", + "@tanstack/react-persister": "^0.0.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index 2805ec4b3..ba6ba6862 100644 --- a/examples/react/useRateLimiter/src/index.tsx +++ b/examples/react/useRateLimiter/src/index.tsx @@ -1,12 +1,12 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useRateLimiter } from '@tanstack/react-pacer/rate-limiter' -import { useStoragePersister } from '@tanstack/react-pacer/persister' +import { useStoragePersister } from '@tanstack/react-persister/persister' function App1() { // Use your state management library of choice - const [instantCount, setInstantCount] = useState(0) - const [limitedCount, setLimitedCount] = useState(0) + const [instantCount, setInstantCount] = useState(0) // not rate-limited + const [limitedCount, setLimitedCount] = useState(0) // rate-limited // Using useRateLimiter with a rate limit of 5 executions per 5 seconds const rateLimiter = useRateLimiter(setLimitedCount, { @@ -19,6 +19,7 @@ function App1() { 'Rejected by rate limiter', rateLimiter.getMsUntilNextWindow(), ), + // optional local storage persister to retain state on page refresh persister: useStoragePersister({ key: 'my-rate-limiter', storage: localStorage, @@ -38,7 +39,7 @@ function App1() { return (
-

TanStack Pacer useRateLimiter Example 1

+

TanStack Pacer useRateLimiter Example 1 (with persister)

diff --git a/package.json b/package.json index 8e2c599e5..3f5866b4b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "nx affected --targets=build --exclude=examples/** && size-limit", "build:all": "nx run-many --targets=build --exclude=examples/** && size-limit", - "build:core": "nx build @tanstack/pacer && size-limit", + "build:core": "nx build @tanstack/pacer @tanstack/persister && size-limit", "changeset": "changeset", "changeset:publish": "changeset publish", "changeset:version": "changeset version && pnpm install --no-frozen-lockfile && pnpm prettier:write", @@ -78,7 +78,9 @@ }, "overrides": { "@tanstack/pacer": "workspace:*", + "@tanstack/persister": "workspace:*", "@tanstack/react-pacer": "workspace:*", + "@tanstack/react-persister": "workspace:*", "@tanstack/solid-pacer": "workspace:*" } } diff --git a/packages/pacer/package.json b/packages/pacer/package.json index 991f00c19..6ca7c13e5 100644 --- a/packages/pacer/package.json +++ b/packages/pacer/package.json @@ -47,16 +47,6 @@ "default": "./dist/cjs/async-debouncer.cjs" } }, - "./async-persister": { - "import": { - "types": "./dist/esm/async-persister.d.ts", - "default": "./dist/esm/async-persister.js" - }, - "require": { - "types": "./dist/cjs/async-persister.d.cts", - "default": "./dist/cjs/async-persister.cjs" - } - }, "./async-queuer": { "import": { "types": "./dist/esm/async-queuer.d.ts", @@ -97,16 +87,6 @@ "default": "./dist/cjs/batcher.cjs" } }, - "./compare": { - "import": { - "types": "./dist/esm/compare.d.ts", - "default": "./dist/esm/compare.js" - }, - "require": { - "types": "./dist/cjs/compare.d.cts", - "default": "./dist/cjs/compare.cjs" - } - }, "./debouncer": { "import": { "types": "./dist/esm/debouncer.d.ts", @@ -117,16 +97,6 @@ "default": "./dist/cjs/debouncer.cjs" } }, - "./persister": { - "import": { - "types": "./dist/esm/persister.d.ts", - "default": "./dist/esm/persister.js" - }, - "require": { - "types": "./dist/cjs/persister.d.cts", - "default": "./dist/cjs/persister.cjs" - } - }, "./queuer": { "import": { "types": "./dist/esm/queuer.d.ts", @@ -189,5 +159,8 @@ "test:types": "tsc", "test:build": "publint --strict", "build": "vite build" + }, + "devDependencies": { + "@tanstack/persister": "workspace:*" } } diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 7996f2272..7b2ab2ac1 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -1,7 +1,6 @@ import { parseFunctionOrValue } from './utils' -import type { Persister } from './persister' import type { AnyAsyncFunction, OptionalKeys } from './types' -import type { AsyncPersister } from './async-persister' +import type { AsyncPersister, Persister } from '@tanstack/persister' /** * State shape for persisting AsyncRateLimiter diff --git a/packages/pacer/src/index.ts b/packages/pacer/src/index.ts index 9f8d62cd3..77e6ffe9e 100644 --- a/packages/pacer/src/index.ts +++ b/packages/pacer/src/index.ts @@ -1,12 +1,9 @@ export * from './async-debouncer' -export * from './async-persister' export * from './async-queuer' export * from './async-rate-limiter' export * from './async-throttler' export * from './batcher' -export * from './compare' export * from './debouncer' -export * from './persister' export * from './queuer' export * from './rate-limiter' export * from './throttler' diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index 918c29c5d..0774de289 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -1,5 +1,5 @@ import { parseFunctionOrValue } from './utils' -import type { Persister } from './persister' +import type { Persister } from '@tanstack/persister/persister' import type { AnyFunction } from './types' /** @@ -122,7 +122,7 @@ export class RateLimiter { /** * Returns the current state for persistence */ - private getState(): RateLimiterState { + getState(): RateLimiterState { return { ...this._state } } diff --git a/packages/pacer/tsconfig.json b/packages/pacer/tsconfig.json index 44533f71c..34449d762 100644 --- a/packages/pacer/tsconfig.json +++ b/packages/pacer/tsconfig.json @@ -1,4 +1,10 @@ { "extends": "../../tsconfig.json", - "include": ["src", "eslint.config.js", "vite.config.ts", "tests"] + "include": [ + "src", + "eslint.config.js", + "vite.config.ts", + "tests", + "../persister/src/compare.ts" + ] } diff --git a/packages/persister/CHANGELOG.md b/packages/persister/CHANGELOG.md new file mode 100644 index 000000000..043e5dc75 --- /dev/null +++ b/packages/persister/CHANGELOG.md @@ -0,0 +1,2 @@ +# @tanstack/persister + diff --git a/packages/persister/eslint.config.js b/packages/persister/eslint.config.js new file mode 100644 index 000000000..e472c69e7 --- /dev/null +++ b/packages/persister/eslint.config.js @@ -0,0 +1,10 @@ +// @ts-check + +import rootConfig from '../../eslint.config.js' + +export default [ + ...rootConfig, + { + rules: {}, + }, +] diff --git a/packages/persister/package.json b/packages/persister/package.json new file mode 100644 index 000000000..e628da8c2 --- /dev/null +++ b/packages/persister/package.json @@ -0,0 +1,103 @@ +{ + "name": "@tanstack/persister", + "version": "0.0.0", + "description": "Utilities for persisting state to local storage, session storage, indexedDB, and more.", + "author": "Tanner Linsley", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/TanStack/persister.git", + "directory": "packages/persister" + }, + "homepage": "https://tanstack.com/persister", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "keywords": [ + "state", + "persister", + "local-storage", + "session-storage", + "indexed-db", + "cache" + ], + "type": "module", + "types": "dist/esm/index.d.ts", + "main": "dist/cjs/index.cjs", + "module": "dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.cts", + "default": "./dist/cjs/index.cjs" + } + }, + "./async-persister": { + "import": { + "types": "./dist/esm/async-persister.d.ts", + "default": "./dist/esm/async-persister.js" + }, + "require": { + "types": "./dist/cjs/async-persister.d.cts", + "default": "./dist/cjs/async-persister.cjs" + } + }, + "./compare": { + "import": { + "types": "./dist/esm/compare.d.ts", + "default": "./dist/esm/compare.js" + }, + "require": { + "types": "./dist/cjs/compare.d.cts", + "default": "./dist/cjs/compare.cjs" + } + }, + "./persister": { + "import": { + "types": "./dist/esm/persister.d.ts", + "default": "./dist/esm/persister.js" + }, + "require": { + "types": "./dist/cjs/persister.d.cts", + "default": "./dist/cjs/persister.cjs" + } + }, + "./types": { + "types": "./dist/esm/types.d.ts" + }, + "./utils": { + "import": { + "types": "./dist/esm/utils.d.ts", + "default": "./dist/esm/utils.js" + }, + "require": { + "types": "./dist/cjs/utils.d.cts", + "default": "./dist/cjs/utils.cjs" + } + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "engines": { + "node": ">=18" + }, + "files": [ + "dist/", + "src" + ], + "scripts": { + "clean": "premove ./build ./dist", + "lint:fix": "eslint ./src --fix", + "test:eslint": "eslint ./src", + "test:lib": "vitest", + "test:lib:dev": "pnpm test:lib --watch", + "test:types": "tsc", + "test:build": "publint --strict", + "build": "vite build" + } +} diff --git a/packages/pacer/src/async-persister.ts b/packages/persister/src/async-persister.ts similarity index 100% rename from packages/pacer/src/async-persister.ts rename to packages/persister/src/async-persister.ts diff --git a/packages/pacer/src/compare.ts b/packages/persister/src/compare.ts similarity index 100% rename from packages/pacer/src/compare.ts rename to packages/persister/src/compare.ts diff --git a/packages/persister/src/index.ts b/packages/persister/src/index.ts new file mode 100644 index 000000000..453c356ab --- /dev/null +++ b/packages/persister/src/index.ts @@ -0,0 +1,5 @@ +export * from './async-persister' +export * from './compare' +export * from './persister' +export * from './types' +export * from './utils' diff --git a/packages/pacer/src/persister.ts b/packages/persister/src/persister.ts similarity index 93% rename from packages/pacer/src/persister.ts rename to packages/persister/src/persister.ts index 8d1fa670b..7cf2d67a8 100644 --- a/packages/pacer/src/persister.ts +++ b/packages/persister/src/persister.ts @@ -1,3 +1,5 @@ +import type { RequiredKeys } from './types' + /** * Abstract class that defines the contract for a state persister implementation. * A persister is responsible for loading and saving state to a storage medium. @@ -88,7 +90,12 @@ export interface StoragePersisterOptions { storage: Storage } -const defaultOptions: Partial> = { +type DefaultOptions = RequiredKeys< + Partial>, + 'deserializer' | 'serializer' +> + +const defaultOptions: DefaultOptions = { deserializer: JSON.parse, serializer: JSON.stringify, } @@ -124,7 +131,7 @@ const defaultOptions: Partial> = { * ``` */ export class StoragePersister extends Persister { - private _options: StoragePersisterOptions + private _options: StoragePersisterOptions & DefaultOptions constructor(options: StoragePersisterOptions) { super(options.key) this._options = { @@ -151,7 +158,7 @@ export class StoragePersister extends Persister { try { this._options.storage.setItem( this.key, - this._options.serializer!({ + this._options.serializer({ buster: this._options.buster, state, timestamp: Date.now(), @@ -171,7 +178,7 @@ export class StoragePersister extends Persister { } try { - const parsed = this._options.deserializer!(stored) + const parsed = this._options.deserializer(stored) const isValid = !this._options.buster || parsed.buster === this._options.buster const isNotExpired = diff --git a/packages/persister/src/types.ts b/packages/persister/src/types.ts new file mode 100644 index 000000000..6e779c0b3 --- /dev/null +++ b/packages/persister/src/types.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/** + * Represents a function that can be called with any arguments and returns any value. + */ +export type AnyFunction = (...args: Array) => any + +/** + * Represents an asynchronous function that can be called with any arguments and returns a promise. + */ +export type AnyAsyncFunction = (...args: Array) => Promise + +export type OptionalKeys = Omit & + Partial> + +export type RequiredKeys = Required< + Pick +> & + Omit diff --git a/packages/persister/src/utils.ts b/packages/persister/src/utils.ts new file mode 100644 index 000000000..9026e0570 --- /dev/null +++ b/packages/persister/src/utils.ts @@ -0,0 +1,27 @@ +import type { AnyFunction } from './types' + +export function isFunction(value: any): value is T { + return typeof value === 'function' +} + +export function parseFunctionOrValue>( + value: T | ((...args: TArgs) => T), + ...args: TArgs +): T { + return isFunction(value) ? value(...args) : value +} + +export function bindInstanceMethods>( + instance: T, +): T { + return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).reduce( + (acc: any, key) => { + const method = instance[key as keyof T] + if (isFunction(method)) { + acc[key] = method.bind(instance) + } + return acc + }, + instance, + ) +} diff --git a/packages/pacer/tests/compare.test.ts b/packages/persister/tests/compare.test.ts similarity index 100% rename from packages/pacer/tests/compare.test.ts rename to packages/persister/tests/compare.test.ts diff --git a/packages/pacer/tests/persister.test.ts b/packages/persister/tests/persister.test.ts similarity index 100% rename from packages/pacer/tests/persister.test.ts rename to packages/persister/tests/persister.test.ts diff --git a/packages/persister/tests/test-setup.ts b/packages/persister/tests/test-setup.ts new file mode 100644 index 000000000..a9d0dd31a --- /dev/null +++ b/packages/persister/tests/test-setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/vitest' diff --git a/packages/persister/tsconfig.docs.json b/packages/persister/tsconfig.docs.json new file mode 100644 index 000000000..2880b4dfa --- /dev/null +++ b/packages/persister/tsconfig.docs.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["tests", "src"] +} diff --git a/packages/persister/tsconfig.json b/packages/persister/tsconfig.json new file mode 100644 index 000000000..44533f71c --- /dev/null +++ b/packages/persister/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "eslint.config.js", "vite.config.ts", "tests"] +} diff --git a/packages/persister/vite.config.ts b/packages/persister/vite.config.ts new file mode 100644 index 000000000..ed5d11383 --- /dev/null +++ b/packages/persister/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/config/vite' +import packageJson from './package.json' + +const config = defineConfig({ + test: { + name: packageJson.name, + dir: './', + watch: false, + environment: 'jsdom', + setupFiles: ['./tests/test-setup.ts'], + globals: true, + }, +}) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: ['./src/index.ts'], + srcDir: './src', + }), +) diff --git a/packages/react-pacer/package.json b/packages/react-pacer/package.json index 9c8d164ca..b55562472 100644 --- a/packages/react-pacer/package.json +++ b/packages/react-pacer/package.json @@ -47,16 +47,6 @@ "default": "./dist/cjs/async-debouncer/index.cjs" } }, - "./async-persister": { - "import": { - "types": "./dist/esm/async-persister/index.d.ts", - "default": "./dist/esm/async-persister/index.js" - }, - "require": { - "types": "./dist/cjs/async-persister/index.d.cts", - "default": "./dist/cjs/async-persister/index.cjs" - } - }, "./async-queuer": { "import": { "types": "./dist/esm/async-queuer/index.d.ts", @@ -107,16 +97,6 @@ "default": "./dist/cjs/debouncer/index.cjs" } }, - "./persister": { - "import": { - "types": "./dist/esm/persister/index.d.ts", - "default": "./dist/esm/persister/index.js" - }, - "require": { - "types": "./dist/cjs/persister/index.d.cts", - "default": "./dist/cjs/persister/index.cjs" - } - }, "./queuer": { "import": { "types": "./dist/esm/queuer/index.d.ts", diff --git a/packages/react-pacer/src/async-persister/index.ts b/packages/react-pacer/src/async-persister/index.ts deleted file mode 100644 index bfbfc5627..000000000 --- a/packages/react-pacer/src/async-persister/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@tanstack/pacer/async-persister' diff --git a/packages/react-pacer/src/compare/index.ts b/packages/react-pacer/src/compare/index.ts deleted file mode 100644 index c33052fd9..000000000 --- a/packages/react-pacer/src/compare/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@tanstack/pacer/compare' diff --git a/packages/react-pacer/src/index.ts b/packages/react-pacer/src/index.ts index 346804cd0..f1c4a7d1b 100644 --- a/packages/react-pacer/src/index.ts +++ b/packages/react-pacer/src/index.ts @@ -30,9 +30,6 @@ export * from './debouncer/useDebouncedState' export * from './debouncer/useDebouncedValue' export * from './debouncer/useDebouncer' -export * from './persister/useStoragePersister' -export * from './persister/useStorageState' - // queuer export * from './queuer/useQueuer' export * from './queuer/useQueuedState' diff --git a/packages/react-pacer/vite.config.ts b/packages/react-pacer/vite.config.ts index bbfa011e7..b5617ae61 100644 --- a/packages/react-pacer/vite.config.ts +++ b/packages/react-pacer/vite.config.ts @@ -20,15 +20,12 @@ export default mergeConfig( tanstackViteConfig({ entry: [ './src/async-debouncer/index.ts', - './src/async-persister/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', './src/async-throttler/index.ts', './src/batcher/index.ts', - './src/compare/index.ts', './src/debouncer/index.ts', './src/index.ts', - './src/persister/index.ts', './src/queuer/index.ts', './src/rate-limiter/index.ts', './src/throttler/index.ts', diff --git a/packages/react-persister/CHANGELOG.md b/packages/react-persister/CHANGELOG.md new file mode 100644 index 000000000..1a138582a --- /dev/null +++ b/packages/react-persister/CHANGELOG.md @@ -0,0 +1 @@ +# @tanstack/react-persister diff --git a/packages/react-persister/eslint.config.js b/packages/react-persister/eslint.config.js new file mode 100644 index 000000000..3b8dc1504 --- /dev/null +++ b/packages/react-persister/eslint.config.js @@ -0,0 +1,32 @@ +// @ts-check + +import pluginReact from '@eslint-react/eslint-plugin' +import pluginReactCompiler from 'eslint-plugin-react-compiler' +import pluginReactHooks from 'eslint-plugin-react-hooks' +import rootConfig from '../../eslint.config.js' + +export default [ + ...rootConfig, + { + files: ['**/*.{ts,tsx}'], + ...pluginReact.configs.recommended, + }, + { + plugins: { + 'react-hooks': pluginReactHooks, + 'react-compiler': pluginReactCompiler, + }, + rules: { + '@eslint-react/dom/no-missing-button-type': 'off', + 'react-compiler/react-compiler': 'error', + 'react-hooks/exhaustive-deps': 'error', + 'react-hooks/rules-of-hooks': 'error', + }, + }, + { + files: ['**/__tests__/**'], + rules: { + // 'react-compiler/react-compiler': 'off', + }, + }, +] diff --git a/packages/react-persister/package.json b/packages/react-persister/package.json new file mode 100644 index 000000000..fe9bbcfa4 --- /dev/null +++ b/packages/react-persister/package.json @@ -0,0 +1,108 @@ +{ + "name": "@tanstack/react-persister", + "version": "0.0.0", + "description": "Utilities for persisting state to local storage, session storage, indexedDB, and more.", + "author": "Tanner Linsley", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/TanStack/pacer.git", + "directory": "packages/react-persister" + }, + "homepage": "https://tanstack.com/persister", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "keywords": [ + "react", + "debounce", + "throttle", + "rate-limit", + "queue", + "async" + ], + "type": "module", + "types": "dist/esm/index.d.ts", + "main": "dist/cjs/index.cjs", + "module": "dist/esm/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.cts", + "default": "./dist/cjs/index.cjs" + } + }, + "./async-persister": { + "import": { + "types": "./dist/esm/async-persister/index.d.ts", + "default": "./dist/esm/async-persister/index.js" + }, + "require": { + "types": "./dist/cjs/async-persister/index.d.cts", + "default": "./dist/cjs/async-persister/index.cjs" + } + }, + "./persister": { + "import": { + "types": "./dist/esm/persister/index.d.ts", + "default": "./dist/esm/persister/index.js" + }, + "require": { + "types": "./dist/cjs/persister/index.d.cts", + "default": "./dist/cjs/persister/index.cjs" + } + }, + "./types": { + "types": "./dist/esm/types/index.d.ts", + "default": "./dist/esm/types/index.js" + }, + "./utils": { + "import": { + "types": "./dist/esm/utils/index.d.ts", + "default": "./dist/esm/utils/index.js" + }, + "require": { + "types": "./dist/cjs/utils/index.d.cts", + "default": "./dist/cjs/utils/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "sideEffects": false, + "engines": { + "node": ">=18" + }, + "files": [ + "dist", + "src" + ], + "scripts": { + "clean": "premove ./build ./dist", + "test:eslint": "eslint ./src", + "test:lib": "vitest --passWithNoTests", + "test:lib:dev": "pnpm test:lib --watch", + "test:types": "tsc", + "test:build": "publint --strict", + "build": "vite build" + }, + "dependencies": { + "@tanstack/persister": "workspace:*" + }, + "devDependencies": { + "@eslint-react/eslint-plugin": "^1.51.2", + "@types/react": "^19.1.6", + "@vitejs/plugin-react": "^4.5.1", + "eslint-plugin-react-compiler": "19.1.0-rc.2", + "eslint-plugin-react-hooks": "^5.2.0", + "react": "^19.1.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } +} diff --git a/packages/react-persister/src/async-persister/index.ts b/packages/react-persister/src/async-persister/index.ts new file mode 100644 index 000000000..22fb4da1b --- /dev/null +++ b/packages/react-persister/src/async-persister/index.ts @@ -0,0 +1 @@ +export * from '@tanstack/persister/async-persister' diff --git a/packages/react-persister/src/compare/index.ts b/packages/react-persister/src/compare/index.ts new file mode 100644 index 000000000..7509b6895 --- /dev/null +++ b/packages/react-persister/src/compare/index.ts @@ -0,0 +1 @@ +export * from '@tanstack/persister/compare' diff --git a/packages/react-persister/src/index.ts b/packages/react-persister/src/index.ts new file mode 100644 index 000000000..92b29c821 --- /dev/null +++ b/packages/react-persister/src/index.ts @@ -0,0 +1,12 @@ +// re-export everything from the core pacer package +export * from '@tanstack/persister' + +/** + * Export every hook individually - DON'T export from barrel files + */ + +// async-debouncer + +// persister +export * from './persister/useStoragePersister' +export * from './persister/useStorageState' diff --git a/packages/react-pacer/src/persister/index.ts b/packages/react-persister/src/persister/index.ts similarity index 61% rename from packages/react-pacer/src/persister/index.ts rename to packages/react-persister/src/persister/index.ts index d51c1265b..e77900092 100644 --- a/packages/react-pacer/src/persister/index.ts +++ b/packages/react-persister/src/persister/index.ts @@ -1,4 +1,4 @@ -export * from '@tanstack/pacer/persister' +export * from '@tanstack/persister/persister' export * from './useStoragePersister' export * from './useStorageState' diff --git a/packages/react-pacer/src/persister/useStoragePersister.ts b/packages/react-persister/src/persister/useStoragePersister.ts similarity index 58% rename from packages/react-pacer/src/persister/useStoragePersister.ts rename to packages/react-persister/src/persister/useStoragePersister.ts index 41423a5d3..7b4ff76bf 100644 --- a/packages/react-pacer/src/persister/useStoragePersister.ts +++ b/packages/react-persister/src/persister/useStoragePersister.ts @@ -1,7 +1,7 @@ import { useState } from 'react' -import { StoragePersister } from '@tanstack/pacer/persister' -import { bindInstanceMethods } from '@tanstack/pacer/utils' -import type { StoragePersisterOptions } from '@tanstack/pacer/persister' +import { StoragePersister } from '@tanstack/persister/persister' +import { bindInstanceMethods } from '@tanstack/persister/utils' +import type { StoragePersisterOptions } from '@tanstack/persister/persister' export function useStoragePersister( options: StoragePersisterOptions, diff --git a/packages/react-pacer/src/persister/useStorageState.ts b/packages/react-persister/src/persister/useStorageState.ts similarity index 96% rename from packages/react-pacer/src/persister/useStorageState.ts rename to packages/react-persister/src/persister/useStorageState.ts index 50d584e0b..811a49787 100644 --- a/packages/react-pacer/src/persister/useStorageState.ts +++ b/packages/react-persister/src/persister/useStorageState.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { useStoragePersister } from './useStoragePersister' -import type { StoragePersisterOptions } from '@tanstack/pacer/persister' +import type { StoragePersisterOptions } from '@tanstack/persister/persister' function useStorageState( initialValue: TValue, diff --git a/packages/react-persister/src/types/index.ts b/packages/react-persister/src/types/index.ts new file mode 100644 index 000000000..294d1807e --- /dev/null +++ b/packages/react-persister/src/types/index.ts @@ -0,0 +1 @@ +export * from '@tanstack/persister/types' diff --git a/packages/react-persister/src/utils/index.ts b/packages/react-persister/src/utils/index.ts new file mode 100644 index 000000000..477d7b75a --- /dev/null +++ b/packages/react-persister/src/utils/index.ts @@ -0,0 +1 @@ +export * from '@tanstack/persister/utils' diff --git a/packages/react-persister/tsconfig.docs.json b/packages/react-persister/tsconfig.docs.json new file mode 100644 index 000000000..20ccb038c --- /dev/null +++ b/packages/react-persister/tsconfig.docs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@tanstack/pacer": ["../pacer/src"] + } + }, + "include": ["src"] +} diff --git a/packages/react-persister/tsconfig.json b/packages/react-persister/tsconfig.json new file mode 100644 index 000000000..84562bb4a --- /dev/null +++ b/packages/react-persister/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "jsx": "react" + }, + "include": ["src", "eslint.config.js", "vite.config.ts", "tests"] +} diff --git a/packages/react-persister/vite.config.ts b/packages/react-persister/vite.config.ts new file mode 100644 index 000000000..abdc16855 --- /dev/null +++ b/packages/react-persister/vite.config.ts @@ -0,0 +1,31 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/config/vite' +import react from '@vitejs/plugin-react' +import packageJson from './package.json' + +const config = defineConfig({ + plugins: [react()], + test: { + name: packageJson.name, + dir: './tests', + watch: false, + environment: 'jsdom', + // setupFiles: ['./tests/test-setup.ts'], + globals: true, + }, +}) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: [ + './src/async-persister/index.ts', + './src/compare/index.ts', + './src/index.ts', + './src/persister/index.ts', + './src/types/index.ts', + './src/utils/index.ts', + ], + srcDir: './src', + }), +) diff --git a/packages/solid-pacer/package.json b/packages/solid-pacer/package.json index c89d90387..82320aab1 100644 --- a/packages/solid-pacer/package.json +++ b/packages/solid-pacer/package.json @@ -47,16 +47,6 @@ "default": "./dist/cjs/async-debouncer/index.cjs" } }, - "./async-persister": { - "import": { - "types": "./dist/esm/async-persister/index.d.ts", - "default": "./dist/esm/async-persister/index.js" - }, - "require": { - "types": "./dist/cjs/async-persister/index.d.cts", - "default": "./dist/cjs/async-persister/index.cjs" - } - }, "./async-queuer": { "import": { "types": "./dist/esm/async-queuer/index.d.ts", @@ -107,16 +97,6 @@ "default": "./dist/cjs/debouncer/index.cjs" } }, - "./persister": { - "import": { - "types": "./dist/esm/persister/index.d.ts", - "default": "./dist/esm/persister/index.js" - }, - "require": { - "types": "./dist/cjs/persister/index.d.cts", - "default": "./dist/cjs/persister/index.cjs" - } - }, "./queuer": { "import": { "types": "./dist/esm/queuer/index.d.ts", diff --git a/packages/solid-pacer/src/async-persister/index.ts b/packages/solid-pacer/src/async-persister/index.ts deleted file mode 100644 index bfbfc5627..000000000 --- a/packages/solid-pacer/src/async-persister/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@tanstack/pacer/async-persister' diff --git a/packages/solid-pacer/src/compare/index.ts b/packages/solid-pacer/src/compare/index.ts deleted file mode 100644 index c33052fd9..000000000 --- a/packages/solid-pacer/src/compare/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@tanstack/pacer/compare' diff --git a/packages/solid-pacer/src/persister/index.ts b/packages/solid-pacer/src/persister/index.ts deleted file mode 100644 index 75f962e34..000000000 --- a/packages/solid-pacer/src/persister/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@tanstack/pacer/persister' diff --git a/packages/solid-pacer/vite.config.ts b/packages/solid-pacer/vite.config.ts index 19f6f07c2..a0ae9fea1 100644 --- a/packages/solid-pacer/vite.config.ts +++ b/packages/solid-pacer/vite.config.ts @@ -20,15 +20,12 @@ export default mergeConfig( tanstackViteConfig({ entry: [ './src/async-debouncer/index.ts', - './src/async-persister/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', './src/async-throttler/index.ts', './src/batcher/index.ts', - './src/compare/index.ts', './src/debouncer/index.ts', './src/index.ts', - './src/persister/index.ts', './src/queuer/index.ts', './src/rate-limiter/index.ts', './src/throttler/index.ts', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdb21f969..3d5cad020 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -451,6 +451,9 @@ importers: '@tanstack/react-pacer': specifier: ^0.8.0 version: link:../../../packages/react-pacer + '@tanstack/react-persister': + specifier: ^0.0.0 + version: link:../../../packages/react-persister react: specifier: ^19.1.0 version: 19.1.0 @@ -776,6 +779,9 @@ importers: '@tanstack/react-pacer': specifier: ^0.8.0 version: link:../../../packages/react-pacer + '@tanstack/react-persister': + specifier: ^0.0.0 + version: link:../../../packages/react-persister react: specifier: ^19.1.0 version: 19.1.0 @@ -1264,7 +1270,13 @@ importers: specifier: ^2.11.6 version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - packages/pacer: {} + packages/pacer: + devDependencies: + '@tanstack/persister': + specifier: workspace:* + version: link:../persister + + packages/persister: {} packages/react-pacer: dependencies: @@ -1294,6 +1306,34 @@ importers: specifier: ^19.1.0 version: 19.1.0 + packages/react-persister: + dependencies: + '@tanstack/persister': + specifier: workspace:* + version: link:../persister + react-dom: + specifier: '>=16.8' + version: 19.1.0(react@19.1.0) + devDependencies: + '@eslint-react/eslint-plugin': + specifier: ^1.51.2 + version: 1.51.2(eslint@9.28.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + '@types/react': + specifier: ^19.1.6 + version: 19.1.6 + '@vitejs/plugin-react': + specifier: ^4.5.1 + version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + eslint-plugin-react-compiler: + specifier: 19.1.0-rc.2 + version: 19.1.0-rc.2(eslint@9.28.0(jiti@2.4.2)) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.28.0(jiti@2.4.2)) + react: + specifier: ^19.1.0 + version: 19.1.0 + packages/solid-pacer: dependencies: '@tanstack/pacer': @@ -8068,7 +8108,7 @@ snapshots: tsx@4.19.3: dependencies: esbuild: 0.25.0 - get-tsconfig: 4.10.0 + get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 optional: true diff --git a/vitest.workspace.js b/vitest.workspace.js index ea394c9e2..fcb88e206 100644 --- a/vitest.workspace.js +++ b/vitest.workspace.js @@ -1,7 +1,13 @@ -import { defineWorkspace } from 'vitest/config' +import { defineConfig } from 'vitest/config' -export default defineWorkspace([ - './packages/pacer/vite.config.ts', - './packages/react-pacer/vite.config.ts', - './packages/solid-pacer/vite.config.ts', -]) +export default defineConfig({ + test: { + projects: [ + './packages/pacer/vite.config.ts', + './packages/persister/vite.config.ts', + './packages/react-pacer/vite.config.ts', + './packages/react-persister/vite.config.ts', + './packages/solid-pacer/vite.config.ts', + ], + }, +}) From 2f2da24ccb1e9ebf274272481605626451c19e25 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 19 Jun 2025 19:49:33 -0500 Subject: [PATCH 06/51] refactor to external state management instead of internal persisters --- .../reference/functions/useasyncdebouncer.md | 85 --- .../functions/useasyncqueuedstate.md | 77 --- .../reference/functions/useasyncqueuer.md | 77 --- .../functions/useasyncratelimiter.md | 92 --- .../reference/functions/useasyncthrottler.md | 80 --- .../react/reference/functions/usebatcher.md | 69 --- .../functions/usedebouncedcallback.md | 78 --- .../reference/functions/usedebouncedstate.md | 65 -- .../reference/functions/usedebouncedvalue.md | 68 --- .../react/reference/functions/usedebouncer.md | 67 --- .../functions/uselocalstoragestate.md | 2 +- .../reference/functions/usequeuedstate.md | 81 --- .../reference/functions/usequeuedvalue.md | 67 --- .../react/reference/functions/usequeuer.md | 70 --- .../functions/useratelimitedcallback.md | 97 --- .../functions/useratelimitedstate.md | 90 --- .../functions/useratelimitedvalue.md | 79 --- .../reference/functions/useratelimiter.md | 80 --- .../functions/usesessionstoragestate.md | 2 +- .../functions/usestoragepersister.md | 2 +- .../functions/usethrottledcallback.md | 81 --- .../reference/functions/usethrottledstate.md | 66 --- .../reference/functions/usethrottledvalue.md | 59 -- .../react/reference/functions/usethrottler.md | 67 --- docs/framework/react/reference/index.md | 27 +- docs/reference/classes/asyncdebouncer.md | 293 --------- docs/reference/classes/asyncpersister.md | 10 +- docs/reference/classes/asyncqueuer.md | 558 ------------------ docs/reference/classes/asyncratelimiter.md | 360 ----------- docs/reference/classes/asyncthrottler.md | 326 ---------- docs/reference/classes/batcher.md | 278 --------- docs/reference/classes/debouncer.md | 202 ------- docs/reference/classes/persister.md | 10 +- docs/reference/classes/queuer.md | 498 ---------------- docs/reference/classes/ratelimiter.md | 270 --------- docs/reference/classes/storagepersister.md | 14 +- docs/reference/classes/throttler.md | 263 --------- docs/reference/functions/asyncdebounce.md | 92 --- docs/reference/functions/asyncqueue.md | 79 --- docs/reference/functions/asyncratelimit.md | 127 ---- docs/reference/functions/asyncthrottle.md | 91 --- docs/reference/functions/batch.md | 60 -- .../functions/bindinstancemethods.md | 2 +- docs/reference/functions/debounce.md | 62 -- docs/reference/functions/isfunction.md | 2 +- docs/reference/functions/isplainarray.md | 2 +- docs/reference/functions/isplainobject.md | 2 +- .../functions/parsefunctionorvalue.md | 2 +- docs/reference/functions/queue.md | 86 --- docs/reference/functions/ratelimit.md | 94 --- docs/reference/functions/replaceequaldeep.md | 2 +- .../functions/shallowequalobjects.md | 2 +- docs/reference/functions/throttle.md | 90 --- docs/reference/index.md | 37 +- .../interfaces/asyncdebounceroptions.md | 160 ----- .../interfaces/asyncqueueroptions.md | 374 ------------ .../interfaces/asyncratelimiteroptions.md | 197 ------- .../interfaces/asyncratelimiterstate.md | 86 --- .../interfaces/asyncthrottleroptions.md | 160 ----- docs/reference/interfaces/batcheroptions.md | 165 ------ docs/reference/interfaces/debounceroptions.md | 93 --- docs/reference/interfaces/persistedstorage.md | 8 +- docs/reference/interfaces/queueroptions.md | 294 --------- .../interfaces/ratelimiteroptions.md | 126 ---- docs/reference/interfaces/ratelimiterstate.md | 42 -- .../interfaces/storagepersisteroptions.md | 22 +- docs/reference/interfaces/throttleroptions.md | 92 --- .../type-aliases/anyasyncfunction.md | 2 +- docs/reference/type-aliases/anyfunction.md | 2 +- docs/reference/type-aliases/optionalkeys.md | 2 +- docs/reference/type-aliases/queueposition.md | 19 - docs/reference/type-aliases/requiredkeys.md | 20 + .../react/useAsyncRateLimiter/src/index.tsx | 17 +- examples/react/useRateLimiter/src/index.tsx | 16 +- package.json | 2 +- packages/pacer/package.json | 3 - packages/pacer/src/async-rate-limiter.ts | 199 ++++--- packages/pacer/src/rate-limiter.ts | 142 ++--- packages/persister/CHANGELOG.md | 1 - packages/react-persister/package.json | 11 +- pnpm-lock.yaml | 6 +- scripts/generateDocs.js | 18 + 82 files changed, 297 insertions(+), 7524 deletions(-) delete mode 100644 docs/framework/react/reference/functions/useasyncdebouncer.md delete mode 100644 docs/framework/react/reference/functions/useasyncqueuedstate.md delete mode 100644 docs/framework/react/reference/functions/useasyncqueuer.md delete mode 100644 docs/framework/react/reference/functions/useasyncratelimiter.md delete mode 100644 docs/framework/react/reference/functions/useasyncthrottler.md delete mode 100644 docs/framework/react/reference/functions/usebatcher.md delete mode 100644 docs/framework/react/reference/functions/usedebouncedcallback.md delete mode 100644 docs/framework/react/reference/functions/usedebouncedstate.md delete mode 100644 docs/framework/react/reference/functions/usedebouncedvalue.md delete mode 100644 docs/framework/react/reference/functions/usedebouncer.md delete mode 100644 docs/framework/react/reference/functions/usequeuedstate.md delete mode 100644 docs/framework/react/reference/functions/usequeuedvalue.md delete mode 100644 docs/framework/react/reference/functions/usequeuer.md delete mode 100644 docs/framework/react/reference/functions/useratelimitedcallback.md delete mode 100644 docs/framework/react/reference/functions/useratelimitedstate.md delete mode 100644 docs/framework/react/reference/functions/useratelimitedvalue.md delete mode 100644 docs/framework/react/reference/functions/useratelimiter.md delete mode 100644 docs/framework/react/reference/functions/usethrottledcallback.md delete mode 100644 docs/framework/react/reference/functions/usethrottledstate.md delete mode 100644 docs/framework/react/reference/functions/usethrottledvalue.md delete mode 100644 docs/framework/react/reference/functions/usethrottler.md delete mode 100644 docs/reference/classes/asyncdebouncer.md delete mode 100644 docs/reference/classes/asyncqueuer.md delete mode 100644 docs/reference/classes/asyncratelimiter.md delete mode 100644 docs/reference/classes/asyncthrottler.md delete mode 100644 docs/reference/classes/batcher.md delete mode 100644 docs/reference/classes/debouncer.md delete mode 100644 docs/reference/classes/queuer.md delete mode 100644 docs/reference/classes/ratelimiter.md delete mode 100644 docs/reference/classes/throttler.md delete mode 100644 docs/reference/functions/asyncdebounce.md delete mode 100644 docs/reference/functions/asyncqueue.md delete mode 100644 docs/reference/functions/asyncratelimit.md delete mode 100644 docs/reference/functions/asyncthrottle.md delete mode 100644 docs/reference/functions/batch.md delete mode 100644 docs/reference/functions/debounce.md delete mode 100644 docs/reference/functions/queue.md delete mode 100644 docs/reference/functions/ratelimit.md delete mode 100644 docs/reference/functions/throttle.md delete mode 100644 docs/reference/interfaces/asyncdebounceroptions.md delete mode 100644 docs/reference/interfaces/asyncqueueroptions.md delete mode 100644 docs/reference/interfaces/asyncratelimiteroptions.md delete mode 100644 docs/reference/interfaces/asyncratelimiterstate.md delete mode 100644 docs/reference/interfaces/asyncthrottleroptions.md delete mode 100644 docs/reference/interfaces/batcheroptions.md delete mode 100644 docs/reference/interfaces/debounceroptions.md delete mode 100644 docs/reference/interfaces/queueroptions.md delete mode 100644 docs/reference/interfaces/ratelimiteroptions.md delete mode 100644 docs/reference/interfaces/ratelimiterstate.md delete mode 100644 docs/reference/interfaces/throttleroptions.md delete mode 100644 docs/reference/type-aliases/queueposition.md create mode 100644 docs/reference/type-aliases/requiredkeys.md diff --git a/docs/framework/react/reference/functions/useasyncdebouncer.md b/docs/framework/react/reference/functions/useasyncdebouncer.md deleted file mode 100644 index d3f709478..000000000 --- a/docs/framework/react/reference/functions/useasyncdebouncer.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -id: useAsyncDebouncer -title: useAsyncDebouncer ---- - - - -# Function: useAsyncDebouncer() - -```ts -function useAsyncDebouncer(fn, options): AsyncDebouncer -``` - -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:60](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L60) - -A low-level React hook that creates an `AsyncDebouncer` instance to delay execution of an async function. - -This hook is designed to be flexible and state-management agnostic - it simply returns a debouncer instance that -you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). - -Async debouncing ensures that an async function only executes 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. - -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 -- 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 AsyncDebouncer instance - -## Type Parameters - -• **TFn** *extends* `AnyAsyncFunction` - -## Parameters - -### fn - -`TFn` - -### options - -`AsyncDebouncerOptions`\<`TFn`\> - -## Returns - -`AsyncDebouncer`\<`TFn`\> - -## Example - -```tsx -// Basic API call debouncing -const { maybeExecute } = useAsyncDebouncer( - async (query: string) => { - const results = await api.search(query); - return results; - }, - { wait: 500 } -); - -// With state management -const [results, setResults] = useState([]); -const { maybeExecute } = useAsyncDebouncer( - async (searchTerm) => { - const data = await searchAPI(searchTerm); - setResults(data); - }, - { - wait: 300, - leading: true, // Execute immediately on first call - trailing: false, // Skip trailing edge updates - onError: (error) => { - console.error('API call failed:', error); - } - } -); -``` diff --git a/docs/framework/react/reference/functions/useasyncqueuedstate.md b/docs/framework/react/reference/functions/useasyncqueuedstate.md deleted file mode 100644 index c159eef39..000000000 --- a/docs/framework/react/reference/functions/useasyncqueuedstate.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: useAsyncQueuedState -title: useAsyncQueuedState ---- - - - -# Function: useAsyncQueuedState() - -```ts -function useAsyncQueuedState(fn, options): [TValue[], AsyncQueuer] -``` - -Defined in: [react-pacer/src/async-queuer/useAsyncQueuedState.ts:53](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuedState.ts#L53) - -A higher-level React hook that creates an `AsyncQueuer` instance with built-in state management. - -This hook combines an AsyncQueuer with React state to automatically track the queue items. -It returns a tuple containing: -- The current array of queued items as React state -- The queuer instance with methods to control the queue - -The queue can be configured with: -- Maximum concurrent operations -- Maximum queue size -- Processing function for queue items -- Various lifecycle callbacks - -The state will automatically update whenever items are: -- Added to the queue -- Removed from the queue -- Started processing -- Completed processing - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`value`) => `Promise`\<`any`\> - -### options - -`AsyncQueuerOptions`\<`TValue`\> = `{}` - -## Returns - -\[`TValue`[], `AsyncQueuer`\<`TValue`\>\] - -## Example - -```tsx -// Create a queue with state management -const [queueItems, asyncQueuer] = useAsyncQueuedState({ - concurrency: 2, - maxSize: 100, - started: true -}); - -// Add items to queue - state updates automatically -asyncQueuer.addItem(async () => { - const result = await fetchData(); - return result; -}); - -// Start processing -asyncQueuer.start(); - -// Stop processing -asyncQueuer.stop(); - -// queueItems reflects current queue state -const pendingCount = asyncQueuer.peekPendingItems().length; -``` diff --git a/docs/framework/react/reference/functions/useasyncqueuer.md b/docs/framework/react/reference/functions/useasyncqueuer.md deleted file mode 100644 index 18e5cea0c..000000000 --- a/docs/framework/react/reference/functions/useasyncqueuer.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -id: useAsyncQueuer -title: useAsyncQueuer ---- - - - -# Function: useAsyncQueuer() - -```ts -function useAsyncQueuer(fn, options): AsyncQueuer -``` - -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:51](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L51) - -A lower-level React hook that creates an `AsyncQueuer` instance for managing an async queue of items. - -Features: -- Priority queue support via getPriority option -- Configurable concurrency limit -- Task success/error/completion callbacks -- FIFO (First In First Out) or LIFO (Last In First Out) queue behavior -- Pause/resume task processing -- Task cancellation -- Item expiration to clear 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 below the concurrency limit. - -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 -- 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 AsyncQueuer instance - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`value`) => `Promise`\<`any`\> - -### options - -`AsyncQueuerOptions`\<`TValue`\> = `{}` - -## Returns - -`AsyncQueuer`\<`TValue`\> - -## Example - -```tsx -// Basic async queuer for API requests -const asyncQueuer = useAsyncQueuer({ - initialItems: [], - concurrency: 2, - maxSize: 100, - started: false, - onSuccess: (result) => { - console.log('Item processed:', result); - }, - onError: (error) => { - console.error('Processing failed:', error); - } -}); - -// Add items to queue -asyncQueuer.addItem(newItem); - -// Start processing -asyncQueuer.start(); -``` diff --git a/docs/framework/react/reference/functions/useasyncratelimiter.md b/docs/framework/react/reference/functions/useasyncratelimiter.md deleted file mode 100644 index fdbfeef74..000000000 --- a/docs/framework/react/reference/functions/useasyncratelimiter.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -id: useAsyncRateLimiter -title: useAsyncRateLimiter ---- - - - -# Function: useAsyncRateLimiter() - -```ts -function useAsyncRateLimiter(fn, options): AsyncRateLimiter -``` - -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:67](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L67) - -A low-level React hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. - -This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that -you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). - -Rate limiting allows an async function to execute up to a specified limit within a time window, -then blocks subsequent calls until the window passes. This is useful for respecting API rate limits, -managing resource constraints, or controlling bursts of async operations. - -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. - -The rate limiter supports two types of windows: -- '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. - -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` - -## Parameters - -### fn - -`TFn` - -### options - -`AsyncRateLimiterOptions`\<`TFn`\> - -## Returns - -`AsyncRateLimiter`\<`TFn`\> - -## Example - -```tsx -// Basic API call rate limiting with return value -const { maybeExecute } = useAsyncRateLimiter( - async (id: string) => { - const data = await api.fetchData(id); - return data; // Return value is preserved - }, - { limit: 5, window: 1000 } // 5 calls per second -); - -// With state management and return value -const [data, setData] = useState(null); -const { maybeExecute } = useAsyncRateLimiter( - async (query) => { - const result = await searchAPI(query); - setData(result); - return result; // Return value can be used by the caller - }, - { - limit: 10, - window: 60000, // 10 calls per minute - onReject: (rateLimiter) => { - console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); - }, - onError: (error) => { - console.error('API call failed:', error); - } - } -); -``` diff --git a/docs/framework/react/reference/functions/useasyncthrottler.md b/docs/framework/react/reference/functions/useasyncthrottler.md deleted file mode 100644 index 11e17fc7c..000000000 --- a/docs/framework/react/reference/functions/useasyncthrottler.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -id: useAsyncThrottler -title: useAsyncThrottler ---- - - - -# Function: useAsyncThrottler() - -```ts -function useAsyncThrottler(fn, options): AsyncThrottler -``` - -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:55](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L55) - -A low-level React hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. - -This hook is designed to be flexible and state-management agnostic - it simply returns a throttler instance that -you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). - -Async throttling ensures an async 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 API calls, -database operations, or other async tasks. - -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. - -Error Handling: -- If an `onError` handler is provided, it will be called with the error and throttler 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 AsyncThrottler instance - -## Type Parameters - -• **TFn** *extends* `AnyAsyncFunction` - -## Parameters - -### fn - -`TFn` - -### options - -`AsyncThrottlerOptions`\<`TFn`\> - -## Returns - -`AsyncThrottler`\<`TFn`\> - -## Example - -```tsx -// Basic API call throttling with return value -const { maybeExecute } = useAsyncThrottler( - async (id: string) => { - const data = await api.fetchData(id); - return data; // Return value is preserved - }, - { wait: 1000 } -); - -// With state management and return value -const [data, setData] = useState(null); -const { maybeExecute } = useAsyncThrottler( - async (query) => { - const result = await searchAPI(query); - setData(result); - return result; // Return value can be used by the caller - }, - { - wait: 2000, - leading: true, // Execute immediately on first call - trailing: false // Skip trailing edge updates - } -); -``` diff --git a/docs/framework/react/reference/functions/usebatcher.md b/docs/framework/react/reference/functions/usebatcher.md deleted file mode 100644 index e17ad2508..000000000 --- a/docs/framework/react/reference/functions/usebatcher.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -id: useBatcher -title: useBatcher ---- - - - -# Function: useBatcher() - -```ts -function useBatcher(fn, options): Batcher -``` - -Defined in: [react-pacer/src/batcher/useBatcher.ts:43](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L43) - -A React hook that creates and manages a Batcher instance. - -This is a lower-level hook that provides direct access to the Batcher's functionality without -any built-in state management. This allows you to integrate it with any state management solution -you prefer (useState, Redux, Zustand, etc.) by utilizing the onItemsChange callback. - -The Batcher collects items and processes them in batches based on configurable conditions: -- Maximum batch size -- Time-based batching (process after X milliseconds) -- Custom batch processing logic via getShouldExecute - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`items`) => `void` - -### options - -`BatcherOptions`\<`TValue`\> = `{}` - -## Returns - -`Batcher`\<`TValue`\> - -## Example - -```tsx -// Example with custom state management and batching -const [items, setItems] = useState([]); - -const batcher = useBatcher( - (items) => console.log('Processing batch:', items), - { - maxSize: 5, - wait: 2000, - onItemsChange: (batcher) => setItems(batcher.peekAllItems()), - getShouldExecute: (items) => items.length >= 3 - } -); - -// Add items to batch - they'll be processed when conditions are met -batcher.addItem(1); -batcher.addItem(2); -batcher.addItem(3); // Triggers batch processing - -// Control the batcher -batcher.stop(); // Pause batching -batcher.start(); // Resume batching -``` diff --git a/docs/framework/react/reference/functions/usedebouncedcallback.md b/docs/framework/react/reference/functions/usedebouncedcallback.md deleted file mode 100644 index d9da81160..000000000 --- a/docs/framework/react/reference/functions/usedebouncedcallback.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -id: useDebouncedCallback -title: useDebouncedCallback ---- - - - -# Function: useDebouncedCallback() - -```ts -function useDebouncedCallback(fn, options): (...args) => void -``` - -Defined in: [react-pacer/src/debouncer/useDebouncedCallback.ts:42](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncedCallback.ts#L42) - -A React hook that creates a debounced version of a callback function. -This hook is essentially a wrapper around the basic `debounce` function -that is exported from `@tanstack/pacer`, -but optimized for React with reactive options and a stable function reference. - -The debounced function will only execute after the specified wait time has elapsed -since its last invocation. If called again before the wait time expires, the timer -resets and starts waiting again. - -This hook provides a simpler API compared to `useDebouncer`, making it ideal for basic -debouncing needs. However, it does not expose the underlying Debouncer instance. - -For advanced usage requiring features like: -- Manual cancellation -- Access to execution counts -- Custom useCallback dependencies - -Consider using the `useDebouncer` hook instead. - -## Type Parameters - -• **TFn** *extends* `AnyFunction` - -## Parameters - -### fn - -`TFn` - -### options - -`DebouncerOptions`\<`TFn`\> - -## Returns - -`Function` - -### Parameters - -#### args - -...`Parameters`\<`TFn`\> - -### Returns - -`void` - -## Example - -```tsx -// Debounce a search handler -const handleSearch = useDebouncedCallback((query: string) => { - fetchSearchResults(query); -}, { - wait: 500 // Wait 500ms between executions -}); - -// Use in an input - handleSearch(e.target.value)} -/> -``` diff --git a/docs/framework/react/reference/functions/usedebouncedstate.md b/docs/framework/react/reference/functions/usedebouncedstate.md deleted file mode 100644 index c2be1d8d6..000000000 --- a/docs/framework/react/reference/functions/usedebouncedstate.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -id: useDebouncedState -title: useDebouncedState ---- - - - -# Function: useDebouncedState() - -```ts -function useDebouncedState(value, options): [TValue, Dispatch>, Debouncer>>] -``` - -Defined in: [react-pacer/src/debouncer/useDebouncedState.ts:38](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncedState.ts#L38) - -A React hook that creates a debounced state value, combining React's useState with debouncing functionality. -This hook provides both the current debounced value and methods to update it. - -The state value is only updated after the specified wait time has elapsed since the last update attempt. -If another update is attempted before the wait time expires, the timer resets and starts waiting again. -This is useful for handling frequent state updates that should be throttled, like search input values -or window resize dimensions. - -The hook returns a tuple containing: -- The current debounced value -- A function to update the debounced value -- The debouncer instance with additional control methods - -## Type Parameters - -• **TValue** - -## Parameters - -### value - -`TValue` - -### options - -`DebouncerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> - -## Returns - -\[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>, `Debouncer`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\>\] - -## Example - -```tsx -// Debounced search input -const [searchTerm, setSearchTerm, debouncer] = useDebouncedState('', { - wait: 500 // Wait 500ms after last keystroke -}); - -// Update value - will be debounced -const handleChange = (e) => { - setSearchTerm(e.target.value); -}; - -// Get number of times the debounced function has executed -const executionCount = debouncer.getExecutionCount(); - -// Get the pending state -const isPending = debouncer.getIsPending(); -``` diff --git a/docs/framework/react/reference/functions/usedebouncedvalue.md b/docs/framework/react/reference/functions/usedebouncedvalue.md deleted file mode 100644 index 6c7871ba2..000000000 --- a/docs/framework/react/reference/functions/usedebouncedvalue.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -id: useDebouncedValue -title: useDebouncedValue ---- - - - -# Function: useDebouncedValue() - -```ts -function useDebouncedValue(value, options): [TValue, Debouncer>>] -``` - -Defined in: [react-pacer/src/debouncer/useDebouncedValue.ts:41](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncedValue.ts#L41) - -A React hook that creates a debounced value that updates only after a specified delay. -Unlike useDebouncedState, this hook automatically tracks changes to the input value -and updates the debounced value accordingly. - -The debounced value will only update after the specified wait time has elapsed since -the last change to the input value. If the input value changes again before the wait -time expires, the timer resets and starts waiting again. - -This is useful for deriving debounced values from props or state that change frequently, -like search queries or form inputs, where you want to limit how often downstream effects -or calculations occur. - -The hook returns the current debounced value and the underlying debouncer instance. -The debouncer instance can be used to access additional functionality like cancellation -and execution counts. - -## Type Parameters - -• **TValue** - -## Parameters - -### value - -`TValue` - -### options - -`DebouncerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> - -## Returns - -\[`TValue`, `Debouncer`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\>\] - -## Example - -```tsx -// Debounce a search query -const [searchQuery, setSearchQuery] = useState(''); -const [debouncedQuery, debouncer] = useDebouncedValue(searchQuery, { - wait: 500 // Wait 500ms after last change -}); - -// debouncedQuery will update 500ms after searchQuery stops changing -useEffect(() => { - fetchSearchResults(debouncedQuery); -}, [debouncedQuery]); - -// Handle input changes -const handleChange = (e) => { - setSearchQuery(e.target.value); -}; -``` diff --git a/docs/framework/react/reference/functions/usedebouncer.md b/docs/framework/react/reference/functions/usedebouncer.md deleted file mode 100644 index 22c162551..000000000 --- a/docs/framework/react/reference/functions/usedebouncer.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -id: useDebouncer -title: useDebouncer ---- - - - -# Function: useDebouncer() - -```ts -function useDebouncer(fn, options): Debouncer -``` - -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:42](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L42) - -A React hook that creates and manages a Debouncer instance. - -This is a lower-level hook that provides direct access to the Debouncer's functionality without -any built-in state management. This allows you to integrate it with any state management solution -you prefer (useState, Redux, Zustand, etc.). - -This hook provides debouncing functionality to limit how often a function can be called, -waiting for a specified delay before executing the latest call. This is useful for handling -frequent events like window resizing, scroll events, or real-time search inputs. - -The debouncer will only execute the function after the specified wait time has elapsed -since the last call. If the function is called again before the wait time expires, the -timer resets and starts waiting again. - -## Type Parameters - -• **TFn** *extends* `AnyFunction` - -## Parameters - -### fn - -`TFn` - -### options - -`DebouncerOptions`\<`TFn`\> - -## Returns - -`Debouncer`\<`TFn`\> - -## Example - -```tsx -// Debounce a search function to limit API calls -const searchDebouncer = useDebouncer( - (query: string) => fetchSearchResults(query), - { wait: 500 } // Wait 500ms after last keystroke -); - -// In an event handler -const handleChange = (e) => { - searchDebouncer.maybeExecute(e.target.value); -}; - -// Get number of times the debounced function has executed -const executionCount = searchDebouncer.getExecutionCount(); - -// Get the pending state -const isPending = searchDebouncer.getIsPending(); -``` diff --git a/docs/framework/react/reference/functions/uselocalstoragestate.md b/docs/framework/react/reference/functions/uselocalstoragestate.md index 9944c4d87..7141b0ae3 100644 --- a/docs/framework/react/reference/functions/uselocalstoragestate.md +++ b/docs/framework/react/reference/functions/uselocalstoragestate.md @@ -14,7 +14,7 @@ function useLocalStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: [react-pacer/src/persister/useStorageState.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L46) +Defined in: [useStorageState.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/persister/useStorageState.ts#L46) A hook that persists state to localStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usequeuedstate.md b/docs/framework/react/reference/functions/usequeuedstate.md deleted file mode 100644 index e27b54337..000000000 --- a/docs/framework/react/reference/functions/usequeuedstate.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -id: useQueuedState -title: useQueuedState ---- - - - -# Function: useQueuedState() - -```ts -function useQueuedState(fn, options): [TValue[], (item, position?, runOnUpdate?) => boolean, Queuer] -``` - -Defined in: [react-pacer/src/queuer/useQueuedState.ts:54](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuedState.ts#L54) - -A React hook that creates a queuer with managed state, combining React's useState with queuing functionality. -This hook provides both the current queue state and queue control methods. - -The queue state is automatically updated whenever items are added, removed, or reordered in the queue. -All queue operations are reflected in the state array returned by the hook. - -The queue can be started and stopped to automatically process items at a specified interval, -making it useful as a scheduler. When started, it will process one item per tick, with an -optional wait time between ticks. - -The hook returns a tuple containing: -- The current queue state as an array -- The queue instance with methods for queue manipulation - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`item`) => `void` - -### options - -`QueuerOptions`\<`TValue`\> = `{}` - -## Returns - -\[`TValue`[], (`item`, `position`?, `runOnUpdate`?) => `boolean`, `Queuer`\<`TValue`\>\] - -## Example - -```tsx -// Basic queue with initial items and priority -const [items, queue] = useQueuedState({ - initialItems: ['item1', 'item2'], - started: true, - wait: 1000, - getPriority: (item) => item.priority -}); - -// Add items to queue -const handleAdd = (item) => { - queue.addItem(item); -}; - -// Start automatic processing -const startProcessing = () => { - queue.start(); -}; - -// Stop automatic processing -const stopProcessing = () => { - queue.stop(); -}; - -// Manual processing still available -const handleProcess = () => { - const nextItem = queue.getNextItem(); - if (nextItem) { - processItem(nextItem); - } -}; -``` diff --git a/docs/framework/react/reference/functions/usequeuedvalue.md b/docs/framework/react/reference/functions/usequeuedvalue.md deleted file mode 100644 index b2b84c35c..000000000 --- a/docs/framework/react/reference/functions/usequeuedvalue.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -id: useQueuedValue -title: useQueuedValue ---- - - - -# Function: useQueuedValue() - -```ts -function useQueuedValue(initialValue, options): [TValue, Queuer] -``` - -Defined in: [react-pacer/src/queuer/useQueuedValue.ts:40](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuedValue.ts#L40) - -A React hook that creates a queued value that processes state changes in order with an optional delay. -This hook uses useQueuer internally to manage a queue of state changes and apply them sequentially. - -The queued value will process changes in the order they are received, with optional delays between -processing each change. This is useful for handling state updates that need to be processed -in a specific order, like animations or sequential UI updates. - -The hook returns a tuple containing: -- The current queued value -- The queuer instance with control methods - -## Type Parameters - -• **TValue** - -## Parameters - -### initialValue - -`TValue` - -### options - -`QueuerOptions`\<`TValue`\> = `{}` - -## Returns - -\[`TValue`, `Queuer`\<`TValue`\>\] - -## Example - -```tsx -// Queue state changes with a delay between each -const [value, queuer] = useQueuedValue(initialValue, { - wait: 500, // Wait 500ms between processing each change - started: true // Start processing immediately -}); - -// Add changes to the queue -const handleChange = (newValue) => { - queuer.addItem(newValue); -}; - -// Control the queue -const pauseProcessing = () => { - queuer.stop(); -}; - -const resumeProcessing = () => { - queuer.start(); -}; -``` diff --git a/docs/framework/react/reference/functions/usequeuer.md b/docs/framework/react/reference/functions/usequeuer.md deleted file mode 100644 index b4a03ca32..000000000 --- a/docs/framework/react/reference/functions/usequeuer.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -id: useQueuer -title: useQueuer ---- - - - -# Function: useQueuer() - -```ts -function useQueuer(fn, options): Queuer -``` - -Defined in: [react-pacer/src/queuer/useQueuer.ts:44](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L44) - -A React hook that creates and manages a Queuer instance. - -This is a lower-level hook that provides direct access to the Queuer's functionality without -any built-in state management. This allows you to integrate it with any state management solution -you prefer (useState, Redux, Zustand, etc.) by utilizing the onItemsChange callback. - -For a hook with built-in state management, see useQueuedState. - -The Queuer extends the base Queue to add processing capabilities. Items are processed -synchronously in order, with optional delays between processing each item. The queuer includes -an internal tick mechanism that can be started and stopped, making it useful as a scheduler. -When started, it will process one item per tick, with an optional wait time between ticks. - -By default uses FIFO (First In First Out) behavior, but can be configured for LIFO -(Last In First Out) by specifying 'front' position when adding items. - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`item`) => `void` - -### options - -`QueuerOptions`\<`TValue`\> = `{}` - -## Returns - -`Queuer`\<`TValue`\> - -## Example - -```tsx -// Example with custom state management and scheduling -const [items, setItems] = useState([]); - -const queue = useQueuer({ - started: true, // Start processing immediately - wait: 1000, // Process one item every second - onItemsChange: (queue) => setItems(queue.peekAllItems()), - getPriority: (item) => item.priority // Process higher priority items first -}); - -// Add items to process - they'll be handled automatically -queue.addItem('task1'); -queue.addItem('task2'); - -// Control the scheduler -queue.stop(); // Pause processing -queue.start(); // Resume processing -``` diff --git a/docs/framework/react/reference/functions/useratelimitedcallback.md b/docs/framework/react/reference/functions/useratelimitedcallback.md deleted file mode 100644 index 7b1287a3f..000000000 --- a/docs/framework/react/reference/functions/useratelimitedcallback.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: useRateLimitedCallback -title: useRateLimitedCallback ---- - - - -# Function: useRateLimitedCallback() - -```ts -function useRateLimitedCallback(fn, options): (...args) => boolean -``` - -Defined in: [react-pacer/src/rate-limiter/useRateLimitedCallback.ts:59](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts#L59) - -A React hook that creates a rate-limited version of a callback function. -This hook is essentially a wrapper around the basic `rateLimiter` function -that is exported from `@tanstack/pacer`, -but optimized for React with reactive options and a stable function reference. - -Rate limiting is a simple "hard limit" approach - it allows all calls until the limit -is reached, then blocks subsequent calls until the window resets. Unlike throttling -or debouncing, it does not attempt to space out or intelligently collapse calls. -This can lead to bursts of rapid executions followed by periods where all calls -are blocked. - -The rate limiter supports two types of windows: -- '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. - -For smoother execution patterns, consider: -- useThrottledCallback: When you want consistent spacing between executions (e.g. UI updates) -- useDebouncedCallback: When you want to collapse rapid calls into a single execution (e.g. search input) - -Rate limiting should primarily be used when you need to enforce strict limits, -like API rate limits or other scenarios requiring hard caps on execution frequency. - -This hook provides a simpler API compared to `useRateLimiter`, making it ideal for basic -rate limiting needs. However, it does not expose the underlying RateLimiter instance. - -For advanced usage requiring features like: -- Manual cancellation -- Access to execution counts -- Custom useCallback dependencies - -Consider using the `useRateLimiter` hook instead. - -## Type Parameters - -• **TFn** *extends* `AnyFunction` - -• **TArgs** *extends* `any`[] - -## Parameters - -### fn - -`TFn` - -### options - -`RateLimiterOptions`\<`TFn`\> - -## Returns - -`Function` - -### Parameters - -#### args - -...`TArgs` - -### Returns - -`boolean` - -## Example - -```tsx -// Rate limit API calls to maximum 5 calls per minute with a sliding window -const makeApiCall = useRateLimitedCallback( - (data: ApiData) => { - return fetch('/api/endpoint', { method: 'POST', body: JSON.stringify(data) }); - }, - { - limit: 5, - window: 60000, // 1 minute - windowType: 'sliding', - onReject: () => { - console.warn('API rate limit reached. Please wait before trying again.'); - } - } -); -``` diff --git a/docs/framework/react/reference/functions/useratelimitedstate.md b/docs/framework/react/reference/functions/useratelimitedstate.md deleted file mode 100644 index 655e09aa6..000000000 --- a/docs/framework/react/reference/functions/useratelimitedstate.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -id: useRateLimitedState -title: useRateLimitedState ---- - - - -# Function: useRateLimitedState() - -```ts -function useRateLimitedState(value, options): [TValue, Dispatch>, RateLimiter>>] -``` - -Defined in: [react-pacer/src/rate-limiter/useRateLimitedState.ts:66](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimitedState.ts#L66) - -A React hook that creates a rate-limited state value that enforces a hard limit on state updates within a time window. -This hook combines React's useState with rate limiting functionality to provide controlled state updates. - -Rate limiting is a simple "hard limit" approach - it allows all updates until the limit is reached, then blocks -subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out -or intelligently collapse updates. This can lead to bursts of rapid updates followed by periods of no updates. - -The rate limiter supports two types of windows: -- 'fixed': A strict window that resets after the window period. All updates within the window count - towards the limit, and the window resets completely after the period. -- 'sliding': A rolling window that allows updates as old ones expire. This provides a more - consistent rate of updates over time. - -For smoother update patterns, consider: -- useThrottledState: When you want consistent spacing between updates (e.g. UI changes) -- useDebouncedState: When you want to collapse rapid updates into a single update (e.g. search input) - -Rate limiting should primarily be used when you need to enforce strict limits, like API rate limits. - -The hook returns a tuple containing: -- The rate-limited state value -- A rate-limited setter function that respects the configured limits -- The rateLimiter instance for additional control - -For more direct control over rate limiting without state management, -consider using the lower-level useRateLimiter hook instead. - -## Type Parameters - -• **TValue** - -## Parameters - -### value - -`TValue` - -### options - -`RateLimiterOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> - -## Returns - -\[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>, `RateLimiter`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\>\] - -## Example - -```tsx -// Basic rate limiting - update state at most 5 times per minute with a sliding window -const [value, setValue, rateLimiter] = useRateLimitedState(0, { - limit: 5, - window: 60000, - windowType: 'sliding' -}); - -// With rejection callback and fixed window -const [value, setValue] = useRateLimitedState(0, { - limit: 3, - window: 5000, - windowType: 'fixed', - onReject: (rateLimiter) => { - alert(`Rate limit reached. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); - } -}); - -// Access rateLimiter methods if needed -const handleSubmit = () => { - const remaining = rateLimiter.getRemainingInWindow(); - if (remaining > 0) { - setValue(newValue); - } else { - showRateLimitWarning(); - } -}; -``` diff --git a/docs/framework/react/reference/functions/useratelimitedvalue.md b/docs/framework/react/reference/functions/useratelimitedvalue.md deleted file mode 100644 index 2194cd3f1..000000000 --- a/docs/framework/react/reference/functions/useratelimitedvalue.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -id: useRateLimitedValue -title: useRateLimitedValue ---- - - - -# Function: useRateLimitedValue() - -```ts -function useRateLimitedValue(value, options): [TValue, RateLimiter>>] -``` - -Defined in: [react-pacer/src/rate-limiter/useRateLimitedValue.ts:55](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimitedValue.ts#L55) - -A high-level React hook that creates a rate-limited version of a value that updates at most a certain number of times within a time window. -This hook uses React's useState internally to manage the rate-limited state. - -Rate limiting is a simple "hard limit" approach - it allows all updates until the limit is reached, then blocks -subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out -or intelligently collapse updates. This can lead to bursts of rapid updates followed by periods of no updates. - -The rate limiter supports two types of windows: -- 'fixed': A strict window that resets after the window period. All updates within the window count - towards the limit, and the window resets completely after the period. -- 'sliding': A rolling window that allows updates as old ones expire. This provides a more - consistent rate of updates over time. - -For smoother update patterns, consider: -- useThrottledValue: When you want consistent spacing between updates (e.g. UI changes) -- useDebouncedValue: When you want to collapse rapid updates into a single update (e.g. search input) - -Rate limiting should primarily be used when you need to enforce strict limits, like API rate limits. - -The hook returns a tuple containing: -- The rate-limited value that updates according to the configured rate limit -- The rate limiter instance with control methods - -For more direct control over rate limiting behavior without React state management, -consider using the lower-level useRateLimiter hook instead. - -## Type Parameters - -• **TValue** - -## Parameters - -### value - -`TValue` - -### options - -`RateLimiterOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> - -## Returns - -\[`TValue`, `RateLimiter`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\>\] - -## Example - -```tsx -// Basic rate limiting - update at most 5 times per minute with a sliding window -const [rateLimitedValue, rateLimiter] = useRateLimitedValue(rawValue, { - limit: 5, - window: 60000, - windowType: 'sliding' -}); - -// With rejection callback and fixed window -const [rateLimitedValue, rateLimiter] = useRateLimitedValue(rawValue, { - limit: 3, - window: 5000, - windowType: 'fixed', - onReject: (rateLimiter) => { - console.log(`Update rejected. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); - } -}); -``` diff --git a/docs/framework/react/reference/functions/useratelimiter.md b/docs/framework/react/reference/functions/useratelimiter.md deleted file mode 100644 index 1ca948af9..000000000 --- a/docs/framework/react/reference/functions/useratelimiter.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -id: useRateLimiter -title: useRateLimiter ---- - - - -# Function: useRateLimiter() - -```ts -function useRateLimiter(fn, options): RateLimiter -``` - -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:55](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L55) - -A low-level React hook that creates a `RateLimiter` instance to enforce rate limits on function execution. - -This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that -you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). - -Rate limiting is a simple "hard limit" approach that allows executions until a maximum count is reached within -a time window, then blocks all subsequent calls until the window resets. Unlike throttling or debouncing, -it does not attempt to space out or collapse executions intelligently. - -The rate limiter supports two types of windows: -- '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. - -For smoother execution patterns: -- Use throttling when you want consistent spacing between executions (e.g. UI updates) -- Use debouncing when you want to collapse rapid-fire events (e.g. search input) -- Use rate limiting only when you need to enforce hard limits (e.g. API rate limits) - -The hook returns an object containing: -- maybeExecute: The rate-limited function that respects the configured limits -- getExecutionCount: Returns the number of successful executions -- getRejectionCount: Returns the number of rejected executions due to rate limiting -- getRemainingInWindow: Returns how many more executions are allowed in the current window -- reset: Resets the execution counts and window timing - -## Type Parameters - -• **TFn** *extends* `AnyFunction` - -## Parameters - -### fn - -`TFn` - -### options - -`RateLimiterOptions`\<`TFn`\> - -## Returns - -`RateLimiter`\<`TFn`\> - -## Example - -```tsx -// Basic rate limiting - max 5 calls per minute with a sliding window -const { maybeExecute } = useRateLimiter(apiCall, { - limit: 5, - window: 60000, - windowType: 'sliding', -}); - -// Monitor rate limit status -const handleClick = () => { - const remaining = getRemainingInWindow(); - if (remaining > 0) { - maybeExecute(data); - } else { - showRateLimitWarning(); - } -}; -``` diff --git a/docs/framework/react/reference/functions/usesessionstoragestate.md b/docs/framework/react/reference/functions/usesessionstoragestate.md index d5f2f90be..8d04ca23c 100644 --- a/docs/framework/react/reference/functions/usesessionstoragestate.md +++ b/docs/framework/react/reference/functions/usesessionstoragestate.md @@ -14,7 +14,7 @@ function useSessionStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: [react-pacer/src/persister/useStorageState.ts:69](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStorageState.ts#L69) +Defined in: [useStorageState.ts:69](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/persister/useStorageState.ts#L69) A hook that persists state to sessionStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usestoragepersister.md b/docs/framework/react/reference/functions/usestoragepersister.md index 1ac19868b..42da73edb 100644 --- a/docs/framework/react/reference/functions/usestoragepersister.md +++ b/docs/framework/react/reference/functions/usestoragepersister.md @@ -11,7 +11,7 @@ title: useStoragePersister function useStoragePersister(options): StoragePersister ``` -Defined in: [react-pacer/src/persister/useStoragePersister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/persister/useStoragePersister.ts#L6) +Defined in: [useStoragePersister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/persister/useStoragePersister.ts#L6) ## Type Parameters diff --git a/docs/framework/react/reference/functions/usethrottledcallback.md b/docs/framework/react/reference/functions/usethrottledcallback.md deleted file mode 100644 index 429ede263..000000000 --- a/docs/framework/react/reference/functions/usethrottledcallback.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -id: useThrottledCallback -title: useThrottledCallback ---- - - - -# Function: useThrottledCallback() - -```ts -function useThrottledCallback(fn, options): (...args) => void -``` - -Defined in: [react-pacer/src/throttler/useThrottledCallback.ts:43](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottledCallback.ts#L43) - -A React hook that creates a throttled version of a callback function. -This hook is essentially a wrapper around the basic `throttle` function -that is exported from `@tanstack/pacer`, -but optimized for React with reactive options and a stable function reference. - -The throttled function will execute at most once within the specified wait time period, -regardless of how many times it is called. If called multiple times during the wait period, -only the first invocation will execute, and subsequent calls will be ignored until -the wait period has elapsed. - -This hook provides a simpler API compared to `useThrottler`, making it ideal for basic -throttling needs. However, it does not expose the underlying Throttler instance. - -For advanced usage requiring features like: -- Manual cancellation -- Access to execution counts -- Custom useCallback dependencies - -Consider using the `useThrottler` hook instead. - -## Type Parameters - -• **TFn** *extends* `AnyFunction` - -• **TArgs** *extends* `any`[] - -## Parameters - -### fn - -`TFn` - -### options - -`ThrottlerOptions`\<`TFn`\> - -## Returns - -`Function` - -### Parameters - -#### args - -...`TArgs` - -### Returns - -`void` - -## Example - -```tsx -// Throttle a window resize handler -const handleResize = useThrottledCallback(() => { - updateLayoutMeasurements(); -}, { - wait: 100 // Execute at most once every 100ms -}); - -// Use in an event listener -useEffect(() => { - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); -}, [handleResize]); -``` diff --git a/docs/framework/react/reference/functions/usethrottledstate.md b/docs/framework/react/reference/functions/usethrottledstate.md deleted file mode 100644 index 1e4f7e845..000000000 --- a/docs/framework/react/reference/functions/usethrottledstate.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -id: useThrottledState -title: useThrottledState ---- - - - -# Function: useThrottledState() - -```ts -function useThrottledState(value, options): [TValue, Dispatch>, Throttler>>] -``` - -Defined in: [react-pacer/src/throttler/useThrottledState.ts:40](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottledState.ts#L40) - -A React hook that creates a throttled state value that updates at most once within a specified time window. -This hook combines React's useState with throttling functionality to provide controlled state updates. - -Throttling ensures state updates occur at a controlled rate regardless of how frequently the setter is called. -This is useful for rate-limiting expensive re-renders or operations that depend on rapidly changing state. - -The hook returns a tuple containing: -- The throttled state value -- A throttled setter function that respects the configured wait time -- The throttler instance for additional control - -For more direct control over throttling without state management, -consider using the lower-level useThrottler hook instead. - -## Type Parameters - -• **TValue** - -## Parameters - -### value - -`TValue` - -### options - -`ThrottlerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> - -## Returns - -\[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>, `Throttler`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\>\] - -## Example - -```tsx -// Basic throttling - update state at most once per second -const [value, setValue, throttler] = useThrottledState(0, { wait: 1000 }); - -// With custom leading/trailing behavior -const [value, setValue] = useThrottledState(0, { - wait: 1000, - leading: true, // Update immediately on first change - trailing: false // Skip trailing edge updates -}); - -// Access throttler methods if needed -const handleReset = () => { - setValue(0); - throttler.cancel(); // Cancel any pending updates -}; -``` diff --git a/docs/framework/react/reference/functions/usethrottledvalue.md b/docs/framework/react/reference/functions/usethrottledvalue.md deleted file mode 100644 index db3d90c3a..000000000 --- a/docs/framework/react/reference/functions/usethrottledvalue.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -id: useThrottledValue -title: useThrottledValue ---- - - - -# Function: useThrottledValue() - -```ts -function useThrottledValue(value, options): [TValue, Throttler>>] -``` - -Defined in: [react-pacer/src/throttler/useThrottledValue.ts:32](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottledValue.ts#L32) - -A high-level React hook that creates a throttled version of a value that updates at most once within a specified time window. -This hook uses React's useState internally to manage the throttled state. - -Throttling ensures the value updates occur at a controlled rate regardless of how frequently the input value changes. -This is useful for rate-limiting expensive re-renders or API calls that depend on rapidly changing values. - -The hook returns a tuple containing: -- The throttled value that updates according to the leading/trailing edge behavior specified in the options -- The throttler instance with control methods - -For more direct control over throttling behavior without React state management, -consider using the lower-level useThrottler hook instead. - -## Type Parameters - -• **TValue** - -## Parameters - -### value - -`TValue` - -### options - -`ThrottlerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> - -## Returns - -\[`TValue`, `Throttler`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\>\] - -## Example - -```tsx -// Basic throttling - update at most once per second -const [throttledValue, throttler] = useThrottledValue(rawValue, { wait: 1000 }); - -// With custom leading/trailing behavior -const [throttledValue, throttler] = useThrottledValue(rawValue, { - wait: 1000, - leading: true, // Update immediately on first change - trailing: false // Skip trailing edge updates -}); -``` diff --git a/docs/framework/react/reference/functions/usethrottler.md b/docs/framework/react/reference/functions/usethrottler.md deleted file mode 100644 index 24ba9e71e..000000000 --- a/docs/framework/react/reference/functions/usethrottler.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -id: useThrottler -title: useThrottler ---- - - - -# Function: useThrottler() - -```ts -function useThrottler(fn, options): Throttler -``` - -Defined in: [react-pacer/src/throttler/useThrottler.ts:42](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L42) - -A low-level React hook that creates a `Throttler` instance that limits how often the provided function can execute. - -This hook is designed to be flexible and state-management agnostic - it simply returns a throttler instance that -you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). For a simpler and higher-level hook that -integrates directly with React's useState, see useThrottledState. - -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. - -## Type Parameters - -• **TFn** *extends* `AnyFunction` - -## Parameters - -### fn - -`TFn` - -### options - -`ThrottlerOptions`\<`TFn`\> - -## Returns - -`Throttler`\<`TFn`\> - -## Example - -```tsx -// Basic throttling with custom state -const [value, setValue] = useState(0); -const throttler = useThrottler(setValue, { wait: 1000 }); - -// With Redux -const dispatch = useDispatch(); -const throttler = useThrottler( - (value) => dispatch(updateAction(value)), - { wait: 1000 } -); - -// With any state manager -const throttler = useThrottler( - (value) => stateManager.setState(value), - { - wait: 2000, - leading: true, // Execute immediately on first call - trailing: false // Skip trailing edge updates - } -); -``` diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md index bde6e4642..47a0c1d82 100644 --- a/docs/framework/react/reference/index.md +++ b/docs/framework/react/reference/index.md @@ -1,35 +1,14 @@ --- -id: "@tanstack/react-pacer" -title: "@tanstack/react-pacer" +id: "@tanstack/react-persister" +title: "@tanstack/react-persister" --- -# @tanstack/react-pacer +# @tanstack/react-persister ## Functions -- [useAsyncDebouncer](../functions/useasyncdebouncer.md) -- [useAsyncQueuedState](../functions/useasyncqueuedstate.md) -- [useAsyncQueuer](../functions/useasyncqueuer.md) -- [useAsyncRateLimiter](../functions/useasyncratelimiter.md) -- [useAsyncThrottler](../functions/useasyncthrottler.md) -- [useBatcher](../functions/usebatcher.md) -- [useDebouncedCallback](../functions/usedebouncedcallback.md) -- [useDebouncedState](../functions/usedebouncedstate.md) -- [useDebouncedValue](../functions/usedebouncedvalue.md) -- [useDebouncer](../functions/usedebouncer.md) - [useLocalStorageState](../functions/uselocalstoragestate.md) -- [useQueuedState](../functions/usequeuedstate.md) -- [useQueuedValue](../functions/usequeuedvalue.md) -- [useQueuer](../functions/usequeuer.md) -- [useRateLimitedCallback](../functions/useratelimitedcallback.md) -- [useRateLimitedState](../functions/useratelimitedstate.md) -- [useRateLimitedValue](../functions/useratelimitedvalue.md) -- [useRateLimiter](../functions/useratelimiter.md) - [useSessionStorageState](../functions/usesessionstoragestate.md) - [useStoragePersister](../functions/usestoragepersister.md) -- [useThrottledCallback](../functions/usethrottledcallback.md) -- [useThrottledState](../functions/usethrottledstate.md) -- [useThrottledValue](../functions/usethrottledvalue.md) -- [useThrottler](../functions/usethrottler.md) diff --git a/docs/reference/classes/asyncdebouncer.md b/docs/reference/classes/asyncdebouncer.md deleted file mode 100644 index c69fa50d5..000000000 --- a/docs/reference/classes/asyncdebouncer.md +++ /dev/null @@ -1,293 +0,0 @@ ---- -id: AsyncDebouncer -title: AsyncDebouncer ---- - - - -# Class: AsyncDebouncer\ - -Defined in: [async-debouncer.ts:101](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L101) - -A class that creates an async debounced function. - -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. - -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 error occurs during execution and no `onError` handler is provided, the error will be thrown and propagate up to the caller. -- If an `onError` handler is provided, errors will be caught and passed to the handler instead of being thrown. -- The error count can be tracked using `getErrorCount()`. -- The debouncer maintains its state and can continue to be used after an error occurs. - -## Example - -```ts -const asyncDebouncer = new AsyncDebouncer(async (value: string) => { - const results = await searchAPI(value); - return results; // Return value is preserved -}, { - wait: 500, - onError: (error) => { - console.error('Search failed:', error); - } -}); - -// Called on each keystroke but only executes after 500ms of no typing -// Returns the API response directly -const results = await asyncDebouncer.maybeExecute(inputElement.value); -``` - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Constructors - -### new AsyncDebouncer() - -```ts -new AsyncDebouncer(fn, initialOptions): AsyncDebouncer -``` - -Defined in: [async-debouncer.ts:117](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L117) - -#### Parameters - -##### fn - -`TFn` - -##### initialOptions - -[`AsyncDebouncerOptions`](../../interfaces/asyncdebounceroptions.md)\<`TFn`\> - -#### Returns - -[`AsyncDebouncer`](../asyncdebouncer.md)\<`TFn`\> - -## Methods - -### cancel() - -```ts -cancel(): void -``` - -Defined in: [async-debouncer.ts:259](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L259) - -Cancels any pending execution or aborts any execution in progress - -#### Returns - -`void` - -*** - -### getEnabled() - -```ts -getEnabled(): boolean -``` - -Defined in: [async-debouncer.ts:150](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L150) - -Returns the current debouncer enabled state - -#### Returns - -`boolean` - -*** - -### getErrorCount() - -```ts -getErrorCount(): number -``` - -Defined in: [async-debouncer.ts:288](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L288) - -Returns the number of times the function has errored - -#### Returns - -`number` - -*** - -### getIsExecuting() - -```ts -getIsExecuting(): boolean -``` - -Defined in: [async-debouncer.ts:302](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L302) - -Returns `true` if there is currently an execution in progress - -#### Returns - -`boolean` - -*** - -### getIsPending() - -```ts -getIsPending(): boolean -``` - -Defined in: [async-debouncer.ts:295](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L295) - -Returns `true` if there is a pending execution queued up for trailing execution - -#### Returns - -`boolean` - -*** - -### getLastResult() - -```ts -getLastResult(): undefined | ReturnType -``` - -Defined in: [async-debouncer.ts:267](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L267) - -Returns the last result of the debounced function - -#### Returns - -`undefined` \| `ReturnType`\<`TFn`\> - -*** - -### getOptions() - -```ts -getOptions(): AsyncDebouncerOptions -``` - -Defined in: [async-debouncer.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L143) - -Returns the current debouncer options - -#### Returns - -[`AsyncDebouncerOptions`](../../interfaces/asyncdebounceroptions.md)\<`TFn`\> - -*** - -### getSettleCount() - -```ts -getSettleCount(): number -``` - -Defined in: [async-debouncer.ts:281](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L281) - -Returns the number of times the function has settled (completed or errored) - -#### Returns - -`number` - -*** - -### getSuccessCount() - -```ts -getSuccessCount(): number -``` - -Defined in: [async-debouncer.ts:274](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L274) - -Returns the number of times the function has been executed successfully - -#### Returns - -`number` - -*** - -### getWait() - -```ts -getWait(): number -``` - -Defined in: [async-debouncer.ts:157](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L157) - -Returns the current debouncer wait state - -#### Returns - -`number` - -*** - -### maybeExecute() - -```ts -maybeExecute(...args): Promise> -``` - -Defined in: [async-debouncer.ts:175](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L175) - -Attempts to execute the debounced function. -If a call is already in progress, it will be queued. - -Error Handling: -- If the debounced function throws and no `onError` handler is configured, - the error will be thrown from this method. -- If an `onError` handler is configured, errors will be caught and passed to the handler, - and this method will return undefined. -- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. - -#### Parameters - -##### args - -...`Parameters`\<`TFn`\> - -#### Returns - -`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> - -A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError - -#### Throws - -The error from the debounced function if no onError handler is configured - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [async-debouncer.ts:131](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L131) - -Updates the debouncer options - -#### Parameters - -##### newOptions - -`Partial`\<[`AsyncDebouncerOptions`](../../interfaces/asyncdebounceroptions.md)\<`TFn`\>\> - -#### Returns - -`void` diff --git a/docs/reference/classes/asyncpersister.md b/docs/reference/classes/asyncpersister.md index 8e1a1f6e0..a9e7eb063 100644 --- a/docs/reference/classes/asyncpersister.md +++ b/docs/reference/classes/asyncpersister.md @@ -7,7 +7,7 @@ title: AsyncPersister # Class: `abstract` AsyncPersister\ -Defined in: [async-persister.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L4) +Defined in: [async-persister.ts:4](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L4) Interface for an async persister that can save/load state for a given type @@ -23,7 +23,7 @@ Interface for an async persister that can save/load state for a given type new AsyncPersister(key): AsyncPersister ``` -Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L5) +Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L5) #### Parameters @@ -43,7 +43,7 @@ Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/p readonly key: string; ``` -Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L5) +Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L5) ## Methods @@ -53,7 +53,7 @@ Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/p abstract loadState(): Promise ``` -Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L7) +Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L7) #### Returns @@ -67,7 +67,7 @@ Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/p abstract saveState(state): Promise ``` -Defined in: [async-persister.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-persister.ts#L8) +Defined in: [async-persister.ts:8](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L8) #### Parameters diff --git a/docs/reference/classes/asyncqueuer.md b/docs/reference/classes/asyncqueuer.md deleted file mode 100644 index b1170cdeb..000000000 --- a/docs/reference/classes/asyncqueuer.md +++ /dev/null @@ -1,558 +0,0 @@ ---- -id: AsyncQueuer -title: AsyncQueuer ---- - - - -# Class: AsyncQueuer\ - -Defined in: [async-queuer.ts:157](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L157) - -A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration. - -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 -- 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 AsyncQueuer instance - -Example usage: -```ts -const asyncQueuer = new AsyncQueuer(async (item) => { - // process item - return item.toUpperCase(); -}, { - concurrency: 2, - onSuccess: (result) => { - console.log(result); - } -}); - -asyncQueuer.addItem('hello'); -asyncQueuer.start(); -``` - -## Type Parameters - -• **TValue** - -## Constructors - -### new AsyncQueuer() - -```ts -new AsyncQueuer(fn, initialOptions): AsyncQueuer -``` - -Defined in: [async-queuer.ts:171](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L171) - -#### Parameters - -##### fn - -(`value`) => `Promise`\<`any`\> - -##### initialOptions - -[`AsyncQueuerOptions`](../../interfaces/asyncqueueroptions.md)\<`TValue`\> - -#### Returns - -[`AsyncQueuer`](../asyncqueuer.md)\<`TValue`\> - -## Methods - -### addItem() - -```ts -addItem( - item, - position, - runOnItemsChange): void -``` - -Defined in: [async-queuer.ts:312](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L312) - -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. - -#### Parameters - -##### item - -`TValue` & `object` - -##### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `...` - -##### runOnItemsChange - -`boolean` = `true` - -#### Returns - -`void` - -#### Example - -```ts -queuer.addItem({ value: 'task', priority: 10 }); -queuer.addItem('task2', 'front'); -``` - -*** - -### clear() - -```ts -clear(): void -``` - -Defined in: [async-queuer.ts:282](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L282) - -Removes all pending items from the queue. Does not affect active tasks. - -#### Returns - -`void` - -*** - -### execute() - -```ts -execute(position?): Promise -``` - -Defined in: [async-queuer.ts:410](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L410) - -Removes and returns the next item from the queue and executes the task function with it. - -#### Parameters - -##### position? - -[`QueuePosition`](../../type-aliases/queueposition.md) - -#### Returns - -`Promise`\<`any`\> - -#### Example - -```ts -queuer.execute(); -// LIFO -queuer.execute('back'); -``` - -*** - -### getConcurrency() - -```ts -getConcurrency(): number -``` - -Defined in: [async-queuer.ts:215](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L215) - -Returns the current concurrency limit for processing items. -If a function is provided, it is called with the queuer instance. - -#### Returns - -`number` - -*** - -### getErrorCount() - -```ts -getErrorCount(): number -``` - -Defined in: [async-queuer.ts:552](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L552) - -Returns the number of items that have failed processing. - -#### Returns - -`number` - -*** - -### getExpirationCount() - -```ts -getExpirationCount(): number -``` - -Defined in: [async-queuer.ts:587](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L587) - -Returns the number of items that have expired and been removed from the queue. - -#### Returns - -`number` - -*** - -### getIsEmpty() - -```ts -getIsEmpty(): boolean -``` - -Defined in: [async-queuer.ts:503](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L503) - -Returns true if the queue is empty (no pending items). - -#### Returns - -`boolean` - -*** - -### getIsFull() - -```ts -getIsFull(): boolean -``` - -Defined in: [async-queuer.ts:510](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L510) - -Returns true if the queue is full (reached maxSize). - -#### Returns - -`boolean` - -*** - -### getIsIdle() - -```ts -getIsIdle(): boolean -``` - -Defined in: [async-queuer.ts:580](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L580) - -Returns true if the queuer is running but has no items to process and no active tasks. - -#### Returns - -`boolean` - -*** - -### getIsRunning() - -```ts -getIsRunning(): boolean -``` - -Defined in: [async-queuer.ts:573](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L573) - -Returns true if the queuer is currently running (processing items). - -#### Returns - -`boolean` - -*** - -### getNextItem() - -```ts -getNextItem(position): undefined | TValue -``` - -Defined in: [async-queuer.ts:380](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L380) - -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. - -#### Parameters - -##### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `...` - -#### Returns - -`undefined` \| `TValue` - -#### Example - -```ts -// FIFO -queuer.getNextItem(); -// LIFO -queuer.getNextItem('back'); -``` - -*** - -### getOptions() - -```ts -getOptions(): AsyncQueuerOptions -``` - -Defined in: [async-queuer.ts:199](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L199) - -Returns the current queuer options, including defaults and any overrides. - -#### Returns - -[`AsyncQueuerOptions`](../../interfaces/asyncqueueroptions.md)\<`TValue`\> - -*** - -### getRejectionCount() - -```ts -getRejectionCount(): number -``` - -Defined in: [async-queuer.ts:566](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L566) - -Returns the number of items that have been rejected from being added to the queue. - -#### Returns - -`number` - -*** - -### getSettledCount() - -```ts -getSettledCount(): number -``` - -Defined in: [async-queuer.ts:559](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L559) - -Returns the number of items that have completed processing (success or error). - -#### Returns - -`number` - -*** - -### getSize() - -```ts -getSize(): number -``` - -Defined in: [async-queuer.ts:517](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L517) - -Returns the number of pending items in the queue. - -#### Returns - -`number` - -*** - -### getSuccessCount() - -```ts -getSuccessCount(): number -``` - -Defined in: [async-queuer.ts:545](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L545) - -Returns the number of items that have been successfully processed. - -#### Returns - -`number` - -*** - -### getWait() - -```ts -getWait(): number -``` - -Defined in: [async-queuer.ts:207](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L207) - -Returns the current wait time (in milliseconds) between processing items. -If a function is provided, it is called with the queuer instance. - -#### Returns - -`number` - -*** - -### peekActiveItems() - -```ts -peekActiveItems(): TValue[] -``` - -Defined in: [async-queuer.ts:531](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L531) - -Returns the items currently being processed (active tasks). - -#### Returns - -`TValue`[] - -*** - -### peekAllItems() - -```ts -peekAllItems(): TValue[] -``` - -Defined in: [async-queuer.ts:524](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L524) - -Returns a copy of all items in the queue, including active and pending items. - -#### Returns - -`TValue`[] - -*** - -### peekNextItem() - -```ts -peekNextItem(position): undefined | TValue -``` - -Defined in: [async-queuer.ts:493](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L493) - -Returns the next item in the queue without removing it. - -#### Parameters - -##### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `'front'` - -#### Returns - -`undefined` \| `TValue` - -#### Example - -```ts -queuer.peekNextItem(); // front -queuer.peekNextItem('back'); // back -``` - -*** - -### peekPendingItems() - -```ts -peekPendingItems(): TValue[] -``` - -Defined in: [async-queuer.ts:538](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L538) - -Returns the items waiting to be processed (pending tasks). - -#### Returns - -`TValue`[] - -*** - -### reset() - -```ts -reset(withInitialItems?): void -``` - -Defined in: [async-queuer.ts:291](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L291) - -Resets the queuer to its initial state. Optionally repopulates with initial items. -Does not affect callbacks or options. - -#### Parameters - -##### withInitialItems? - -`boolean` - -#### Returns - -`void` - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [async-queuer.ts:192](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L192) - -Updates the queuer options. New options are merged with existing options. - -#### Parameters - -##### newOptions - -`Partial`\<[`AsyncQueuerOptions`](../../interfaces/asyncqueueroptions.md)\<`TValue`\>\> - -#### Returns - -`void` - -*** - -### start() - -```ts -start(): void -``` - -Defined in: [async-queuer.ts:261](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L261) - -Starts processing items in the queue. If already running, does nothing. - -#### Returns - -`void` - -*** - -### stop() - -```ts -stop(): void -``` - -Defined in: [async-queuer.ts:273](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L273) - -Stops processing items in the queue. Does not clear the queue. - -#### Returns - -`void` diff --git a/docs/reference/classes/asyncratelimiter.md b/docs/reference/classes/asyncratelimiter.md deleted file mode 100644 index 4ab512922..000000000 --- a/docs/reference/classes/asyncratelimiter.md +++ /dev/null @@ -1,360 +0,0 @@ ---- -id: AsyncRateLimiter -title: AsyncRateLimiter ---- - - - -# Class: AsyncRateLimiter\ - -Defined in: [async-rate-limiter.ts:148](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L148) - -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, -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: -- '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) - -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. - -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 -const rateLimiter = new AsyncRateLimiter( - async (id: string) => await api.getData(id), - { - limit: 5, - window: 1000, - windowType: 'sliding', - onError: (error) => { - console.error('API call failed:', error); - }, - onReject: (limiter) => { - console.log(`Rate limit exceeded. Try again in ${limiter.getMsUntilNextWindow()}ms`); - } - } -); - -// Will execute immediately until limit reached, then block -// Returns the API response directly -const data = await rateLimiter.maybeExecute('123'); -``` - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Constructors - -### new AsyncRateLimiter() - -```ts -new AsyncRateLimiter(fn, initialOptions): AsyncRateLimiter -``` - -Defined in: [async-rate-limiter.ts:163](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L163) - -#### Parameters - -##### fn - -`TFn` - -##### initialOptions - -[`AsyncRateLimiterOptions`](../../interfaces/asyncratelimiteroptions.md)\<`TFn`\> - -#### Returns - -[`AsyncRateLimiter`](../asyncratelimiter.md)\<`TFn`\> - -## Methods - -### getEnabled() - -```ts -getEnabled(): boolean -``` - -Defined in: [async-rate-limiter.ts:225](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L225) - -Returns the current enabled state of the rate limiter - -#### Returns - -`boolean` - -*** - -### getErrorCount() - -```ts -getErrorCount(): number -``` - -Defined in: [async-rate-limiter.ts:395](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L395) - -Returns the number of times the function has errored - -#### Returns - -`number` - -*** - -### getIsExecuting() - -```ts -getIsExecuting(): boolean -``` - -Defined in: [async-rate-limiter.ts:409](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L409) - -Returns whether the function is currently executing - -#### Returns - -`boolean` - -*** - -### getLimit() - -```ts -getLimit(): number -``` - -Defined in: [async-rate-limiter.ts:232](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L232) - -Returns the current limit of executions allowed within the time window - -#### Returns - -`number` - -*** - -### getMsUntilNextWindow() - -```ts -getMsUntilNextWindow(): number -``` - -Defined in: [async-rate-limiter.ts:370](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L370) - -Returns the number of milliseconds until the next execution will be possible -For fixed windows, this is the time until the current window resets -For sliding windows, this is the time until the oldest execution expires - -#### Returns - -`number` - -*** - -### getOptions() - -```ts -getOptions(): AsyncRateLimiterOptions -``` - -Defined in: [async-rate-limiter.ts:218](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L218) - -Returns the current rate limiter options - -#### Returns - -[`AsyncRateLimiterOptions`](../../interfaces/asyncratelimiteroptions.md)\<`TFn`\> - -*** - -### getRejectionCount() - -```ts -getRejectionCount(): number -``` - -Defined in: [async-rate-limiter.ts:402](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L402) - -Returns the number of times the function has been rejected - -#### Returns - -`number` - -*** - -### getRemainingInWindow() - -```ts -getRemainingInWindow(): number -``` - -Defined in: [async-rate-limiter.ts:360](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L360) - -Returns the number of remaining executions allowed in the current window - -#### Returns - -`number` - -*** - -### getSettleCount() - -```ts -getSettleCount(): number -``` - -Defined in: [async-rate-limiter.ts:388](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L388) - -Returns the number of times the function has been settled - -#### Returns - -`number` - -*** - -### getSuccessCount() - -```ts -getSuccessCount(): number -``` - -Defined in: [async-rate-limiter.ts:381](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L381) - -Returns the number of times the function has been executed - -#### Returns - -`number` - -*** - -### getWindow() - -```ts -getWindow(): number -``` - -Defined in: [async-rate-limiter.ts:239](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L239) - -Returns the current time window in milliseconds - -#### Returns - -`number` - -*** - -### maybeExecute() - -```ts -maybeExecute(...args): Promise> -``` - -Defined in: [async-rate-limiter.ts:272](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L272) - -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. -If execution is allowed, waits for any previous execution to complete before proceeding. - -Error Handling: -- If the rate-limited function throws and no `onError` handler is configured, - the error will be thrown from this method. -- If an `onError` handler is configured, errors will be caught and passed to the handler, - and this method will return undefined. -- If the rate limit is exceeded, the execution will be rejected and the `onReject` handler - will be called if configured. -- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. -- Rate limit rejections can be tracked using `getRejectionCount()`. - -#### Parameters - -##### args - -...`Parameters`\<`TFn`\> - -#### Returns - -`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> - -A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError - -#### Throws - -The error from the rate-limited function if no onError handler is configured - -#### Example - -```ts -const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 }); - -// First 5 calls will execute -await rateLimiter.maybeExecute('arg1', 'arg2'); - -// Additional calls within the window will be rejected -await rateLimiter.maybeExecute('arg1', 'arg2'); // Rejected -``` - -*** - -### reset() - -```ts -reset(): void -``` - -Defined in: [async-rate-limiter.ts:416](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L416) - -Resets the rate limiter state - -#### Returns - -`void` - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [async-rate-limiter.ts:211](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L211) - -Updates the rate limiter options - -#### Parameters - -##### newOptions - -`Partial`\<[`AsyncRateLimiterOptions`](../../interfaces/asyncratelimiteroptions.md)\<`TFn`\>\> - -#### Returns - -`void` diff --git a/docs/reference/classes/asyncthrottler.md b/docs/reference/classes/asyncthrottler.md deleted file mode 100644 index d5bef03ff..000000000 --- a/docs/reference/classes/asyncthrottler.md +++ /dev/null @@ -1,326 +0,0 @@ ---- -id: AsyncThrottler -title: AsyncThrottler ---- - - - -# Class: AsyncThrottler\ - -Defined in: [async-throttler.ts:105](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L105) - -A class that creates an async throttled function. - -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. - -Error Handling: -- If an `onError` handler is provided, it will be called with the error and throttler 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 AsyncThrottler instance - -## Example - -```ts -const throttler = new AsyncThrottler(async (value: string) => { - const result = await saveToAPI(value); - return result; // Return value is preserved -}, { - wait: 1000, - onError: (error) => { - console.error('API call failed:', error); - } -}); - -// Will only execute once per second no matter how often called -// Returns the API response directly -const result = await throttler.maybeExecute(inputElement.value); -``` - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Constructors - -### new AsyncThrottler() - -```ts -new AsyncThrottler(fn, initialOptions): AsyncThrottler -``` - -Defined in: [async-throttler.ts:121](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L121) - -#### Parameters - -##### fn - -`TFn` - -##### initialOptions - -[`AsyncThrottlerOptions`](../../interfaces/asyncthrottleroptions.md)\<`TFn`\> - -#### Returns - -[`AsyncThrottler`](../asyncthrottler.md)\<`TFn`\> - -## Methods - -### cancel() - -```ts -cancel(): void -``` - -Defined in: [async-throttler.ts:260](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L260) - -Cancels any pending execution or aborts any execution in progress - -#### Returns - -`void` - -*** - -### getEnabled() - -```ts -getEnabled(): boolean -``` - -Defined in: [async-throttler.ts:154](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L154) - -Returns the current enabled state of the throttler - -#### Returns - -`boolean` - -*** - -### getErrorCount() - -```ts -getErrorCount(): number -``` - -Defined in: [async-throttler.ts:311](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L311) - -Returns the number of times the function has errored - -#### Returns - -`number` - -*** - -### getIsExecuting() - -```ts -getIsExecuting(): boolean -``` - -Defined in: [async-throttler.ts:325](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L325) - -Returns the current executing state - -#### Returns - -`boolean` - -*** - -### getIsPending() - -```ts -getIsPending(): boolean -``` - -Defined in: [async-throttler.ts:318](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L318) - -Returns the current pending state - -#### Returns - -`boolean` - -*** - -### getLastExecutionTime() - -```ts -getLastExecutionTime(): number -``` - -Defined in: [async-throttler.ts:276](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L276) - -Returns the last execution time - -#### Returns - -`number` - -*** - -### getLastResult() - -```ts -getLastResult(): undefined | ReturnType -``` - -Defined in: [async-throttler.ts:290](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L290) - -Returns the last result of the debounced function - -#### Returns - -`undefined` \| `ReturnType`\<`TFn`\> - -*** - -### getNextExecutionTime() - -```ts -getNextExecutionTime(): number -``` - -Defined in: [async-throttler.ts:283](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L283) - -Returns the next execution time - -#### Returns - -`number` - -*** - -### getOptions() - -```ts -getOptions(): AsyncThrottlerOptions -``` - -Defined in: [async-throttler.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L147) - -Returns the current options - -#### Returns - -[`AsyncThrottlerOptions`](../../interfaces/asyncthrottleroptions.md)\<`TFn`\> - -*** - -### getSettleCount() - -```ts -getSettleCount(): number -``` - -Defined in: [async-throttler.ts:304](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L304) - -Returns the number of times the function has settled (completed or errored) - -#### Returns - -`number` - -*** - -### getSuccessCount() - -```ts -getSuccessCount(): number -``` - -Defined in: [async-throttler.ts:297](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L297) - -Returns the number of times the function has been executed successfully - -#### Returns - -`number` - -*** - -### getWait() - -```ts -getWait(): number -``` - -Defined in: [async-throttler.ts:161](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L161) - -Returns the current wait time in milliseconds - -#### Returns - -`number` - -*** - -### maybeExecute() - -```ts -maybeExecute(...args): Promise> -``` - -Defined in: [async-throttler.ts:179](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L179) - -Attempts to execute the throttled function. -If a call is already in progress, it may be blocked or queued depending on the `wait` option. - -Error Handling: -- If the throttled function throws and no `onError` handler is configured, - the error will be thrown from this method. -- If an `onError` handler is configured, errors will be caught and passed to the handler, - and this method will return undefined. -- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. - -#### Parameters - -##### args - -...`Parameters`\<`TFn`\> - -#### Returns - -`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> - -A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError - -#### Throws - -The error from the throttled function if no onError handler is configured - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [async-throttler.ts:135](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L135) - -Updates the throttler options - -#### Parameters - -##### newOptions - -`Partial`\<[`AsyncThrottlerOptions`](../../interfaces/asyncthrottleroptions.md)\<`TFn`\>\> - -#### Returns - -`void` diff --git a/docs/reference/classes/batcher.md b/docs/reference/classes/batcher.md deleted file mode 100644 index 97d8432b0..000000000 --- a/docs/reference/classes/batcher.md +++ /dev/null @@ -1,278 +0,0 @@ ---- -id: Batcher -title: Batcher ---- - - - -# Class: Batcher\ - -Defined in: [batcher.ts:84](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L84) - -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. - -The Batcher provides a flexible way to implement batching with configurable: -- Maximum batch size (number of items per batch) -- Time-based batching (process after X milliseconds) -- Custom batch processing logic via getShouldExecute -- Event callbacks for monitoring batch operations - -## Example - -```ts -const batcher = new Batcher( - (items) => console.log('Processing batch:', items), - { - maxSize: 5, - wait: 2000, - onExecuteBatch: (items) => console.log('Batch executed:', items) - } -); - -batcher.addItem(1); -batcher.addItem(2); -// After 2 seconds or when 5 items are added, whichever comes first, -// the batch will be processed -// batcher.execute() // manually trigger a batch -``` - -## Type Parameters - -• **TValue** - -## Constructors - -### new Batcher() - -```ts -new Batcher(fn, initialOptions): Batcher -``` - -Defined in: [batcher.ts:92](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L92) - -#### Parameters - -##### fn - -(`items`) => `void` - -##### initialOptions - -[`BatcherOptions`](../../interfaces/batcheroptions.md)\<`TValue`\> - -#### Returns - -[`Batcher`](../batcher.md)\<`TValue`\> - -## Methods - -### addItem() - -```ts -addItem(item): void -``` - -Defined in: [batcher.ts:118](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L118) - -Adds an item to the batcher -If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed - -#### Parameters - -##### item - -`TValue` - -#### Returns - -`void` - -*** - -### execute() - -```ts -execute(): void -``` - -Defined in: [batcher.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L146) - -Processes the current batch of items. -This method will automatically be triggered if the batcher is running and any of these conditions are met: -- The number of items reaches batchSize -- The wait duration has elapsed -- The getShouldExecute function returns true upon adding an item - -You can also call this method manually to process the current batch at any time. - -#### Returns - -`void` - -*** - -### getBatchExecutionCount() - -```ts -getBatchExecutionCount(): number -``` - -Defined in: [batcher.ts:220](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L220) - -Returns the number of times batches have been processed - -#### Returns - -`number` - -*** - -### getIsEmpty() - -```ts -getIsEmpty(): boolean -``` - -Defined in: [batcher.ts:199](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L199) - -Returns true if the batcher is empty - -#### Returns - -`boolean` - -*** - -### getIsRunning() - -```ts -getIsRunning(): boolean -``` - -Defined in: [batcher.ts:206](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L206) - -Returns true if the batcher is running - -#### Returns - -`boolean` - -*** - -### getItemExecutionCount() - -```ts -getItemExecutionCount(): number -``` - -Defined in: [batcher.ts:227](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L227) - -Returns the total number of individual items that have been processed - -#### Returns - -`number` - -*** - -### getOptions() - -```ts -getOptions(): BatcherOptions -``` - -Defined in: [batcher.ts:110](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L110) - -Returns the current batcher options - -#### Returns - -[`BatcherOptions`](../../interfaces/batcheroptions.md)\<`TValue`\> - -*** - -### getSize() - -```ts -getSize(): number -``` - -Defined in: [batcher.ts:192](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L192) - -Returns the current number of items in the batcher - -#### Returns - -`number` - -*** - -### peekAllItems() - -```ts -peekAllItems(): TValue[] -``` - -Defined in: [batcher.ts:213](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L213) - -Returns a copy of all items currently in the batcher - -#### Returns - -`TValue`[] - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [batcher.ts:103](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L103) - -Updates the batcher options - -#### Parameters - -##### newOptions - -`Partial`\<[`BatcherOptions`](../../interfaces/batcheroptions.md)\<`TValue`\>\> - -#### Returns - -`void` - -*** - -### start() - -```ts -start(): void -``` - -Defined in: [batcher.ts:181](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L181) - -Starts the batcher and processes any pending items - -#### Returns - -`void` - -*** - -### stop() - -```ts -stop(): void -``` - -Defined in: [batcher.ts:169](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L169) - -Stops the batcher from processing batches - -#### Returns - -`void` diff --git a/docs/reference/classes/debouncer.md b/docs/reference/classes/debouncer.md deleted file mode 100644 index 51d2c77e1..000000000 --- a/docs/reference/classes/debouncer.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -id: Debouncer -title: Debouncer ---- - - - -# Class: Debouncer\ - -Defined in: [debouncer.ts:68](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L68) - -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. - -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 -will reset the timer. - -## Example - -```ts -const debouncer = new Debouncer((value: string) => { - saveToDatabase(value); -}, { wait: 500 }); - -// Will only save after 500ms of no new input -inputElement.addEventListener('input', () => { - debouncer.maybeExecute(inputElement.value); -}); -``` - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Constructors - -### new Debouncer() - -```ts -new Debouncer(fn, initialOptions): Debouncer -``` - -Defined in: [debouncer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L75) - -#### Parameters - -##### fn - -`TFn` - -##### initialOptions - -[`DebouncerOptions`](../../interfaces/debounceroptions.md)\<`TFn`\> - -#### Returns - -[`Debouncer`](../debouncer.md)\<`TFn`\> - -## Methods - -### cancel() - -```ts -cancel(): void -``` - -Defined in: [debouncer.ts:160](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L160) - -Cancels any pending execution - -#### Returns - -`void` - -*** - -### getEnabled() - -```ts -getEnabled(): boolean -``` - -Defined in: [debouncer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L107) - -Returns the current enabled state of the debouncer - -#### Returns - -`boolean` - -*** - -### getExecutionCount() - -```ts -getExecutionCount(): number -``` - -Defined in: [debouncer.ts:171](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L171) - -Returns the number of times the function has been executed - -#### Returns - -`number` - -*** - -### getIsPending() - -```ts -getIsPending(): boolean -``` - -Defined in: [debouncer.ts:178](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L178) - -Returns `true` if debouncing - -#### Returns - -`boolean` - -*** - -### getOptions() - -```ts -getOptions(): Required> -``` - -Defined in: [debouncer.ts:100](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L100) - -Returns the current debouncer options - -#### Returns - -`Required`\<[`DebouncerOptions`](../../interfaces/debounceroptions.md)\<`TFn`\>\> - -*** - -### getWait() - -```ts -getWait(): number -``` - -Defined in: [debouncer.ts:114](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L114) - -Returns the current wait time in milliseconds - -#### Returns - -`number` - -*** - -### maybeExecute() - -```ts -maybeExecute(...args): void -``` - -Defined in: [debouncer.ts:122](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L122) - -Attempts to execute the debounced function -If a call is already in progress, it will be queued - -#### Parameters - -##### args - -...`Parameters`\<`TFn`\> - -#### Returns - -`void` - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [debouncer.ts:88](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L88) - -Updates the debouncer options - -#### Parameters - -##### newOptions - -`Partial`\<[`DebouncerOptions`](../../interfaces/debounceroptions.md)\<`TFn`\>\> - -#### Returns - -`void` diff --git a/docs/reference/classes/persister.md b/docs/reference/classes/persister.md index c6756d581..16fdd0dc9 100644 --- a/docs/reference/classes/persister.md +++ b/docs/reference/classes/persister.md @@ -7,7 +7,7 @@ title: Persister # Class: `abstract` Persister\ -Defined in: [persister.ts:23](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L23) +Defined in: [persister.ts:25](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L25) Abstract class that defines the contract for a state persister implementation. A persister is responsible for loading and saving state to a storage medium. @@ -47,7 +47,7 @@ class MyPersister extends Persister { new Persister(key): Persister ``` -Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L24) +Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) #### Parameters @@ -67,7 +67,7 @@ Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packag readonly key: string; ``` -Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L24) +Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) ## Methods @@ -77,7 +77,7 @@ Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packag abstract loadState(): undefined | TState ``` -Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L26) +Defined in: [persister.ts:28](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L28) #### Returns @@ -91,7 +91,7 @@ Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packag abstract saveState(state): void ``` -Defined in: [persister.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L27) +Defined in: [persister.ts:29](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L29) #### Parameters diff --git a/docs/reference/classes/queuer.md b/docs/reference/classes/queuer.md deleted file mode 100644 index a076a53dc..000000000 --- a/docs/reference/classes/queuer.md +++ /dev/null @@ -1,498 +0,0 @@ ---- -id: Queuer -title: Queuer ---- - - - -# Class: Queuer\ - -Defined in: [queuer.ts:160](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L160) - -A flexible queue that processes items with configurable wait times, expiration, and priority. - -Features: -- Automatic or manual processing of items -- FIFO (First In First Out), LIFO (Last In First Out), or double-ended queue behavior -- Priority-based ordering when getPriority is provided -- Item expiration and removal of stale items -- Callbacks for queue state changes, execution, rejection, and expiration - -Running behavior: -- `start()`: Begins automatically processing items in the queue (defaults to running) -- `stop()`: Pauses processing but maintains queue state -- `wait`: Configurable delay between processing items -- `onItemsChange`/`onExecute`: Callbacks for monitoring queue state - -Manual processing is also supported when automatic processing is disabled: -- `execute()`: Processes the next item using the provided function -- `getNextItem()`: Removes and returns the next item without processing - -Queue behavior defaults to FIFO: -- `addItem(item)`: Adds to the back of the queue -- Items processed from the front of the queue - -Priority queue: -- Provide a `getPriority` function; higher values are processed first - -Stack (LIFO): -- `addItem(item, 'back')`: Adds to the back -- `getNextItem('back')`: Removes from the back - -Double-ended queue: -- `addItem(item, position)`: Adds to specified position ('front'/'back') -- `getNextItem(position)`: Removes from specified position - -Item expiration: -- `expirationDuration`: Maximum time items can stay in the queue -- `getIsExpired`: Function to override default expiration -- `onExpire`: Callback for expired items - -Example usage: -```ts -// Auto-processing queue with wait time -const autoQueue = new Queuer((n) => console.log(n), { - started: true, // Begin processing immediately - wait: 1000, // Wait 1s between items - onExecute: (item) => console.log(`Processed ${item}`) -}); -autoQueue.addItem(1); // Will process after 1s -autoQueue.addItem(2); // Will process 1s after first item - -// Manual processing queue -const manualQueue = new Queuer((n) => console.log(n), { - started: false -}); -manualQueue.addItem(1); // [1] -manualQueue.addItem(2); // [1, 2] -manualQueue.execute(); // logs 1, queue is [2] -manualQueue.getNextItem(); // returns 2, queue is empty -``` - -## Type Parameters - -• **TValue** - -## Constructors - -### new Queuer() - -```ts -new Queuer(fn, initialOptions): Queuer -``` - -Defined in: [queuer.ts:171](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L171) - -#### Parameters - -##### fn - -(`item`) => `void` - -##### initialOptions - -[`QueuerOptions`](../../interfaces/queueroptions.md)\<`TValue`\> = `{}` - -#### Returns - -[`Queuer`](../queuer.md)\<`TValue`\> - -## Methods - -### addItem() - -```ts -addItem( - item, - position, - runOnUpdate): boolean -``` - -Defined in: [queuer.ts:343](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L343) - -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. - -Returns true if the item was added, false if the queue is full. - -Example usage: -```ts -queuer.addItem('task'); -queuer.addItem('task2', 'front'); -``` - -#### Parameters - -##### item - -`TValue` - -##### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `...` - -##### runOnUpdate - -`boolean` = `true` - -#### Returns - -`boolean` - -*** - -### clear() - -```ts -clear(): void -``` - -Defined in: [queuer.ts:313](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L313) - -Removes all pending items from the queue. Does not affect items being processed. - -#### Returns - -`void` - -*** - -### execute() - -```ts -execute(position?): undefined | TValue -``` - -Defined in: [queuer.ts:431](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L431) - -Removes and returns the next item from the queue and processes it using the provided function. - -Example usage: -```ts -queuer.execute(); -// LIFO -queuer.execute('back'); -``` - -#### Parameters - -##### position? - -[`QueuePosition`](../../type-aliases/queueposition.md) - -#### Returns - -`undefined` \| `TValue` - -*** - -### getExecutionCount() - -```ts -getExecutionCount(): number -``` - -Defined in: [queuer.ts:490](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L490) - -Returns the number of items that have been processed and removed from the queue. - -#### Returns - -`number` - -*** - -### getExpirationCount() - -```ts -getExpirationCount(): number -``` - -Defined in: [queuer.ts:504](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L504) - -Returns the number of items that have expired and been removed from the queue. - -#### Returns - -`number` - -*** - -### getIsEmpty() - -```ts -getIsEmpty(): boolean -``` - -Defined in: [queuer.ts:462](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L462) - -Returns true if the queue is empty (no pending items). - -#### Returns - -`boolean` - -*** - -### getIsFull() - -```ts -getIsFull(): boolean -``` - -Defined in: [queuer.ts:469](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L469) - -Returns true if the queue is full (reached maxSize). - -#### Returns - -`boolean` - -*** - -### getIsIdle() - -```ts -getIsIdle(): boolean -``` - -Defined in: [queuer.ts:518](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L518) - -Returns true if the queuer is running but has no items to process. - -#### Returns - -`boolean` - -*** - -### getIsRunning() - -```ts -getIsRunning(): boolean -``` - -Defined in: [queuer.ts:511](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L511) - -Returns true if the queuer is currently running (processing items). - -#### Returns - -`boolean` - -*** - -### getNextItem() - -```ts -getNextItem(position): undefined | TValue -``` - -Defined in: [queuer.ts:401](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L401) - -Removes and returns the next item from the queue without executing the function. -Use for manual queue management. Normally, use execute() to process items. - -Example usage: -```ts -// FIFO -queuer.getNextItem(); -// LIFO -queuer.getNextItem('back'); -``` - -#### Parameters - -##### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `...` - -#### Returns - -`undefined` \| `TValue` - -*** - -### getOptions() - -```ts -getOptions(): Required> -``` - -Defined in: [queuer.ts:195](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L195) - -Returns the current queuer options, including defaults and any overrides. - -#### Returns - -`Required`\<[`QueuerOptions`](../../interfaces/queueroptions.md)\<`TValue`\>\> - -*** - -### getRejectionCount() - -```ts -getRejectionCount(): number -``` - -Defined in: [queuer.ts:497](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L497) - -Returns the number of items that have been rejected from being added to the queue. - -#### Returns - -`number` - -*** - -### getSize() - -```ts -getSize(): number -``` - -Defined in: [queuer.ts:476](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L476) - -Returns the number of pending items in the queue. - -#### Returns - -`number` - -*** - -### getWait() - -```ts -getWait(): number -``` - -Defined in: [queuer.ts:203](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L203) - -Returns the current wait time (in milliseconds) between processing items. -If a function is provided, it is called with the queuer instance. - -#### Returns - -`number` - -*** - -### peekAllItems() - -```ts -peekAllItems(): TValue[] -``` - -Defined in: [queuer.ts:483](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L483) - -Returns a copy of all items in the queue. - -#### Returns - -`TValue`[] - -*** - -### peekNextItem() - -```ts -peekNextItem(position): undefined | TValue -``` - -Defined in: [queuer.ts:450](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L450) - -Returns the next item in the queue without removing it. - -Example usage: -```ts -queuer.peekNextItem(); // front -queuer.peekNextItem('back'); // back -``` - -#### Parameters - -##### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `...` - -#### Returns - -`undefined` \| `TValue` - -*** - -### reset() - -```ts -reset(withInitialItems?): void -``` - -Defined in: [queuer.ts:322](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L322) - -Resets the queuer to its initial state. Optionally repopulates with initial items. -Does not affect callbacks or options. - -#### Parameters - -##### withInitialItems? - -`boolean` - -#### Returns - -`void` - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [queuer.ts:188](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L188) - -Updates the queuer options. New options are merged with existing options. - -#### Parameters - -##### newOptions - -`Partial`\<[`QueuerOptions`](../../interfaces/queueroptions.md)\<`TValue`\>\> - -#### Returns - -`void` - -*** - -### start() - -```ts -start(): void -``` - -Defined in: [queuer.ts:301](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L301) - -Starts processing items in the queue. If already running, does nothing. - -#### Returns - -`void` - -*** - -### stop() - -```ts -stop(): void -``` - -Defined in: [queuer.ts:292](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L292) - -Stops processing items in the queue. Does not clear the queue. - -#### Returns - -`void` diff --git a/docs/reference/classes/ratelimiter.md b/docs/reference/classes/ratelimiter.md deleted file mode 100644 index bc60b4418..000000000 --- a/docs/reference/classes/ratelimiter.md +++ /dev/null @@ -1,270 +0,0 @@ ---- -id: RateLimiter -title: RateLimiter ---- - - - -# Class: RateLimiter\ - -Defined in: [rate-limiter.ts:95](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L95) - -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. - -The rate limiter supports two types of windows: -- '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. - -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) - -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. - -## Example - -```ts -const rateLimiter = new RateLimiter( - (id: string) => api.getData(id), - { limit: 5, window: 1000, windowType: 'sliding' } // 5 calls per second with sliding window -); - -// Will execute immediately until limit reached, then block -rateLimiter.maybeExecute('123'); -``` - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Constructors - -### new RateLimiter() - -```ts -new RateLimiter(fn, initialOptions): RateLimiter -``` - -Defined in: [rate-limiter.ts:104](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L104) - -#### Parameters - -##### fn - -`TFn` - -##### initialOptions - -[`RateLimiterOptions`](../../interfaces/ratelimiteroptions.md)\<`TFn`\> - -#### Returns - -[`RateLimiter`](../ratelimiter.md)\<`TFn`\> - -## Methods - -### getEnabled() - -```ts -getEnabled(): boolean -``` - -Defined in: [rate-limiter.ts:159](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L159) - -Returns the current enabled state of the rate limiter - -#### Returns - -`boolean` - -*** - -### getExecutionCount() - -```ts -getExecutionCount(): number -``` - -Defined in: [rate-limiter.ts:248](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L248) - -Returns the number of times the function has been executed - -#### Returns - -`number` - -*** - -### getLimit() - -```ts -getLimit(): number -``` - -Defined in: [rate-limiter.ts:166](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L166) - -Returns the current limit of executions allowed within the time window - -#### Returns - -`number` - -*** - -### getMsUntilNextWindow() - -```ts -getMsUntilNextWindow(): number -``` - -Defined in: [rate-limiter.ts:270](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L270) - -Returns the number of milliseconds until the next execution will be possible - -#### Returns - -`number` - -*** - -### getOptions() - -```ts -getOptions(): Required> -``` - -Defined in: [rate-limiter.ts:152](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L152) - -Returns the current rate limiter options - -#### Returns - -`Required`\<[`RateLimiterOptions`](../../interfaces/ratelimiteroptions.md)\<`TFn`\>\> - -*** - -### getRejectionCount() - -```ts -getRejectionCount(): number -``` - -Defined in: [rate-limiter.ts:255](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L255) - -Returns the number of times the function has been rejected - -#### Returns - -`number` - -*** - -### getRemainingInWindow() - -```ts -getRemainingInWindow(): number -``` - -Defined in: [rate-limiter.ts:262](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L262) - -Returns the number of remaining executions allowed in the current window - -#### Returns - -`number` - -*** - -### getWindow() - -```ts -getWindow(): number -``` - -Defined in: [rate-limiter.ts:173](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L173) - -Returns the current time window in milliseconds - -#### Returns - -`number` - -*** - -### maybeExecute() - -```ts -maybeExecute(...args): boolean -``` - -Defined in: [rate-limiter.ts:192](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L192) - -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. - -#### Parameters - -##### args - -...`Parameters`\<`TFn`\> - -#### Returns - -`boolean` - -#### Example - -```ts -const rateLimiter = new RateLimiter(fn, { limit: 5, window: 1000 }); - -// First 5 calls will return true -rateLimiter.maybeExecute('arg1', 'arg2'); // true - -// Additional calls within the window will return false -rateLimiter.maybeExecute('arg1', 'arg2'); // false -``` - -*** - -### reset() - -```ts -reset(): void -``` - -Defined in: [rate-limiter.ts:281](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L281) - -Resets the rate limiter state - -#### Returns - -`void` - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [rate-limiter.ts:145](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L145) - -Updates the rate limiter options - -#### Parameters - -##### newOptions - -`Partial`\<[`RateLimiterOptions`](../../interfaces/ratelimiteroptions.md)\<`TFn`\>\> - -#### Returns - -`void` diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md index 88d1322ff..2a1bac972 100644 --- a/docs/reference/classes/storagepersister.md +++ b/docs/reference/classes/storagepersister.md @@ -7,7 +7,7 @@ title: StoragePersister # Class: StoragePersister\ -Defined in: [persister.ts:126](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L126) +Defined in: [persister.ts:133](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L133) A persister that saves state to browser local/session storage. @@ -55,7 +55,7 @@ const rateLimiter = new RateLimiter(fn, { new StoragePersister(options): StoragePersister ``` -Defined in: [persister.ts:128](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L128) +Defined in: [persister.ts:135](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L135) #### Parameters @@ -79,7 +79,7 @@ Defined in: [persister.ts:128](https://github.com/TanStack/pacer/blob/main/packa readonly key: string; ``` -Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L24) +Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) #### Inherited from @@ -93,7 +93,7 @@ Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packag getOptions(): StoragePersisterOptions ``` -Defined in: [persister.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L146) +Defined in: [persister.ts:153](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L153) Returns the current persister options @@ -109,7 +109,7 @@ Returns the current persister options loadState(): undefined | TState ``` -Defined in: [persister.ts:167](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L167) +Defined in: [persister.ts:174](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L174) #### Returns @@ -127,7 +127,7 @@ Defined in: [persister.ts:167](https://github.com/TanStack/pacer/blob/main/packa saveState(state): void ``` -Defined in: [persister.ts:150](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L150) +Defined in: [persister.ts:157](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L157) #### Parameters @@ -151,7 +151,7 @@ Defined in: [persister.ts:150](https://github.com/TanStack/pacer/blob/main/packa setOptions(newOptions): void ``` -Defined in: [persister.ts:139](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L139) +Defined in: [persister.ts:146](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L146) Updates the persister options diff --git a/docs/reference/classes/throttler.md b/docs/reference/classes/throttler.md deleted file mode 100644 index c4126eb9c..000000000 --- a/docs/reference/classes/throttler.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -id: Throttler -title: Throttler ---- - - - -# Class: Throttler\ - -Defined in: [throttler.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L71) - -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. - -Supports both leading and trailing edge execution: -- Leading: Execute immediately on first call (default: true) -- Trailing: Execute after wait period if called during throttle (default: true) - -For collapsing rapid-fire events where you only care about the last call, consider using Debouncer. - -## Example - -```ts -const throttler = new Throttler( - (id: string) => api.getData(id), - { wait: 1000 } // Execute at most once per second -); - -// First call executes immediately -throttler.maybeExecute('123'); - -// Subsequent calls within 1000ms are throttled -throttler.maybeExecute('123'); // Throttled -``` - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Constructors - -### new Throttler() - -```ts -new Throttler(fn, initialOptions): Throttler -``` - -Defined in: [throttler.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L78) - -#### Parameters - -##### fn - -`TFn` - -##### initialOptions - -[`ThrottlerOptions`](../../interfaces/throttleroptions.md)\<`TFn`\> - -#### Returns - -[`Throttler`](../throttler.md)\<`TFn`\> - -## Methods - -### cancel() - -```ts -cancel(): void -``` - -Defined in: [throttler.ts:189](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L189) - -Cancels any pending trailing execution and clears internal state. - -If a trailing execution is scheduled (due to throttling with trailing=true), -this will prevent that execution from occurring. The internal timeout and -stored arguments will be cleared. - -Has no effect if there is no pending execution. - -#### Returns - -`void` - -*** - -### getEnabled() - -```ts -getEnabled(): boolean -``` - -Defined in: [throttler.ts:110](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L110) - -Returns the current enabled state of the throttler - -#### Returns - -`boolean` - -*** - -### getExecutionCount() - -```ts -getExecutionCount(): number -``` - -Defined in: [throttler.ts:214](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L214) - -Returns the number of times the function has been executed - -#### Returns - -`number` - -*** - -### getIsPending() - -```ts -getIsPending(): boolean -``` - -Defined in: [throttler.ts:221](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L221) - -Returns `true` if there is a pending execution - -#### Returns - -`boolean` - -*** - -### getLastExecutionTime() - -```ts -getLastExecutionTime(): number -``` - -Defined in: [throttler.ts:200](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L200) - -Returns the last execution time - -#### Returns - -`number` - -*** - -### getNextExecutionTime() - -```ts -getNextExecutionTime(): number -``` - -Defined in: [throttler.ts:207](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L207) - -Returns the next execution time - -#### Returns - -`number` - -*** - -### getOptions() - -```ts -getOptions(): Required> -``` - -Defined in: [throttler.ts:103](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L103) - -Returns the current throttler options - -#### Returns - -`Required`\<[`ThrottlerOptions`](../../interfaces/throttleroptions.md)\<`TFn`\>\> - -*** - -### getWait() - -```ts -getWait(): number -``` - -Defined in: [throttler.ts:117](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L117) - -Returns the current wait time in milliseconds - -#### Returns - -`number` - -*** - -### maybeExecute() - -```ts -maybeExecute(...args): void -``` - -Defined in: [throttler.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L143) - -Attempts to execute the throttled function. The execution behavior depends on the throttler options: - -- If enough time has passed since the last execution (>= wait period): - - With leading=true: Executes immediately - - With leading=false: Waits for the next trailing execution - -- If within the wait period: - - With trailing=true: Schedules execution for end of wait period - - With trailing=false: Drops the execution - -#### Parameters - -##### args - -...`Parameters`\<`TFn`\> - -#### Returns - -`void` - -#### Example - -```ts -const throttled = new Throttler(fn, { wait: 1000 }); - -// First call executes immediately -throttled.maybeExecute('a', 'b'); - -// Call during wait period - gets throttled -throttled.maybeExecute('c', 'd'); -``` - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [throttler.ts:91](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L91) - -Updates the throttler options - -#### Parameters - -##### newOptions - -`Partial`\<[`ThrottlerOptions`](../../interfaces/throttleroptions.md)\<`TFn`\>\> - -#### Returns - -`void` diff --git a/docs/reference/functions/asyncdebounce.md b/docs/reference/functions/asyncdebounce.md deleted file mode 100644 index ab8f81b78..000000000 --- a/docs/reference/functions/asyncdebounce.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -id: asyncDebounce -title: asyncDebounce ---- - - - -# Function: asyncDebounce() - -```ts -function asyncDebounce(fn, initialOptions): (...args) => Promise> -``` - -Defined in: [async-debouncer.ts:341](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L341) - -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. - -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 -- If `throwOnError` is false (default when onError handler is provided), the error will be swallowed -- The error state can be checked using the underlying AsyncDebouncer instance -- Both onError and throwOnError can be used together - the handler will be called before any error is thrown - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Parameters - -### fn - -`TFn` - -### initialOptions - -[`AsyncDebouncerOptions`](../../interfaces/asyncdebounceroptions.md)\<`TFn`\> - -## Returns - -`Function` - -Attempts to execute the debounced function. -If a call is already in progress, it will be queued. - -Error Handling: -- If the debounced function throws and no `onError` handler is configured, - the error will be thrown from this method. -- If an `onError` handler is configured, errors will be caught and passed to the handler, - and this method will return undefined. -- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. - -### Parameters - -#### args - -...`Parameters`\<`TFn`\> - -### Returns - -`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> - -A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError - -### Throws - -The error from the debounced function if no onError handler is configured - -## Example - -```ts -const debounced = asyncDebounce(async (value: string) => { - const result = await saveToAPI(value); - return result; // Return value is preserved -}, { - wait: 1000, - onError: (error) => { - console.error('API call failed:', error); - }, - throwOnError: true // Will both log the error and throw it -}); - -// Will only execute once, 1 second after the last call -// Returns the API response directly -const result = await debounced("third"); -``` diff --git a/docs/reference/functions/asyncqueue.md b/docs/reference/functions/asyncqueue.md deleted file mode 100644 index 686b77452..000000000 --- a/docs/reference/functions/asyncqueue.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -id: asyncQueue -title: asyncQueue ---- - - - -# Function: asyncQueue() - -```ts -function asyncQueue(fn, initialOptions): (item, position, runOnItemsChange) => void -``` - -Defined in: [async-queuer.ts:612](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L612) - -Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks. -The queuer is started automatically and ready to process items. - -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 -- 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 AsyncQueuer instance - -Example usage: -```ts -const enqueue = asyncQueue(async (item) => { - return item.toUpperCase(); -}, {...options}); - -enqueue('hello'); -``` - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`value`) => `Promise`\<`any`\> - -### initialOptions - -[`AsyncQueuerOptions`](../../interfaces/asyncqueueroptions.md)\<`TValue`\> - -## Returns - -`Function` - -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. - -### Parameters - -#### item - -`TValue` & `object` - -#### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `...` - -#### runOnItemsChange - -`boolean` = `true` - -### Returns - -`void` - -### Example - -```ts -queuer.addItem({ value: 'task', priority: 10 }); -queuer.addItem('task2', 'front'); -``` diff --git a/docs/reference/functions/asyncratelimit.md b/docs/reference/functions/asyncratelimit.md deleted file mode 100644 index fa1129639..000000000 --- a/docs/reference/functions/asyncratelimit.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -id: asyncRateLimit -title: asyncRateLimit ---- - - - -# Function: asyncRateLimit() - -```ts -function asyncRateLimit(fn, initialOptions): (...args) => Promise> -``` - -Defined in: [async-rate-limiter.ts:482](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L482) - -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. - -The rate limiter supports two types of windows: -- '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: -- 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 - -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) - -## Parameters - -### fn - -`TFn` - -### initialOptions - -[`AsyncRateLimiterOptions`](../../interfaces/asyncratelimiteroptions.md)\<`TFn`\> - -## Returns - -`Function` - -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. -If execution is allowed, waits for any previous execution to complete before proceeding. - -Error Handling: -- If the rate-limited function throws and no `onError` handler is configured, - the error will be thrown from this method. -- If an `onError` handler is configured, errors will be caught and passed to the handler, - and this method will return undefined. -- If the rate limit is exceeded, the execution will be rejected and the `onReject` handler - will be called if configured. -- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. -- Rate limit rejections can be tracked using `getRejectionCount()`. - -### Parameters - -#### args - -...`Parameters`\<`TFn`\> - -### Returns - -`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> - -A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError - -### Throws - -The error from the rate-limited function if no onError handler is configured - -### Example - -```ts -const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 }); - -// First 5 calls will execute -await rateLimiter.maybeExecute('arg1', 'arg2'); - -// Additional calls within the window will be rejected -await rateLimiter.maybeExecute('arg1', 'arg2'); // Rejected -``` - -## Example - -```ts -// Rate limit to 5 calls per minute with a sliding window -const rateLimited = asyncRateLimit(makeApiCall, { - limit: 5, - window: 60000, - windowType: 'sliding', - onError: (error) => { - console.error('API call failed:', error); - }, - onReject: (rateLimiter) => { - console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); - } -}); - -// First 5 calls will execute immediately -// Additional calls will be rejected until the minute window resets -// Returns the API response directly -const result = await rateLimited(); - -// For more even execution, consider using throttle instead: -const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds -``` diff --git a/docs/reference/functions/asyncthrottle.md b/docs/reference/functions/asyncthrottle.md deleted file mode 100644 index 24f7c0190..000000000 --- a/docs/reference/functions/asyncthrottle.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -id: asyncThrottle -title: asyncThrottle ---- - - - -# Function: asyncThrottle() - -```ts -function asyncThrottle(fn, initialOptions): (...args) => Promise> -``` - -Defined in: [async-throttler.ts:363](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L363) - -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. - -Error Handling: -- If an `onError` handler is provided, it will be called with the error and throttler 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 AsyncThrottler instance - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Parameters - -### fn - -`TFn` - -### initialOptions - -[`AsyncThrottlerOptions`](../../interfaces/asyncthrottleroptions.md)\<`TFn`\> - -## Returns - -`Function` - -Attempts to execute the throttled function. -If a call is already in progress, it may be blocked or queued depending on the `wait` option. - -Error Handling: -- If the throttled function throws and no `onError` handler is configured, - the error will be thrown from this method. -- If an `onError` handler is configured, errors will be caught and passed to the handler, - and this method will return undefined. -- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. - -### Parameters - -#### args - -...`Parameters`\<`TFn`\> - -### Returns - -`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> - -A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError - -### Throws - -The error from the throttled function if no onError handler is configured - -## Example - -```ts -const throttled = asyncThrottle(async (value: string) => { - const result = await saveToAPI(value); - return result; // Return value is preserved -}, { - wait: 1000, - onError: (error) => { - console.error('API call failed:', error); - } -}); - -// This will execute at most once per second -// Returns the API response directly -const result = await throttled(inputElement.value); -``` diff --git a/docs/reference/functions/batch.md b/docs/reference/functions/batch.md deleted file mode 100644 index c3dfc8fc0..000000000 --- a/docs/reference/functions/batch.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -id: batch -title: batch ---- - - - -# Function: batch() - -```ts -function batch(fn, options): (item) => void -``` - -Defined in: [batcher.ts:247](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L247) - -Creates a batcher that processes items in batches - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`items`) => `void` - -### options - -[`BatcherOptions`](../../interfaces/batcheroptions.md)\<`TValue`\> - -## Returns - -`Function` - -Adds an item to the batcher -If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed - -### Parameters - -#### item - -`TValue` - -### Returns - -`void` - -## Example - -```ts -const batchItems = batch({ - batchSize: 3, - processBatch: (items) => console.log('Processing:', items) -}); - -batchItems(1); -batchItems(2); -batchItems(3); // Triggers batch processing -``` diff --git a/docs/reference/functions/bindinstancemethods.md b/docs/reference/functions/bindinstancemethods.md index d8579bc89..f96bc3569 100644 --- a/docs/reference/functions/bindinstancemethods.md +++ b/docs/reference/functions/bindinstancemethods.md @@ -11,7 +11,7 @@ title: bindInstanceMethods function bindInstanceMethods(instance): T ``` -Defined in: [utils.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/utils.ts#L14) +Defined in: [utils.ts:14](https://github.com/TanStack/pacer/blob/main/packages/persister/src/utils.ts#L14) ## Type Parameters diff --git a/docs/reference/functions/debounce.md b/docs/reference/functions/debounce.md deleted file mode 100644 index 8856e4e31..000000000 --- a/docs/reference/functions/debounce.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -id: debounce -title: debounce ---- - - - -# Function: debounce() - -```ts -function debounce(fn, initialOptions): (...args) => void -``` - -Defined in: [debouncer.ts:203](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L203) - -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. - -If leading option is true, the function will execute immediately on the first call, then wait the delay -before allowing another execution. - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Parameters - -### fn - -`TFn` - -### initialOptions - -[`DebouncerOptions`](../../interfaces/debounceroptions.md)\<`TFn`\> - -## Returns - -`Function` - -### Parameters - -#### args - -...`Parameters`\<`TFn`\> - -### Returns - -`void` - -## Example - -```ts -const debounced = debounce(() => { - saveChanges(); -}, { wait: 1000 }); - -// Called repeatedly but executes at most once per second -inputElement.addEventListener('input', debounced); -``` diff --git a/docs/reference/functions/isfunction.md b/docs/reference/functions/isfunction.md index 96879e1e1..634a1cbbe 100644 --- a/docs/reference/functions/isfunction.md +++ b/docs/reference/functions/isfunction.md @@ -11,7 +11,7 @@ title: isFunction function isFunction(value): value is T ``` -Defined in: [utils.ts:3](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/utils.ts#L3) +Defined in: [utils.ts:3](https://github.com/TanStack/pacer/blob/main/packages/persister/src/utils.ts#L3) ## Type Parameters diff --git a/docs/reference/functions/isplainarray.md b/docs/reference/functions/isplainarray.md index ceaaf8e1c..1941990fb 100644 --- a/docs/reference/functions/isplainarray.md +++ b/docs/reference/functions/isplainarray.md @@ -11,7 +11,7 @@ title: isPlainArray function isPlainArray(value): boolean ``` -Defined in: [compare.ts:66](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/compare.ts#L66) +Defined in: [compare.ts:66](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L66) ## Parameters diff --git a/docs/reference/functions/isplainobject.md b/docs/reference/functions/isplainobject.md index 6156327b2..ae1815628 100644 --- a/docs/reference/functions/isplainobject.md +++ b/docs/reference/functions/isplainobject.md @@ -11,7 +11,7 @@ title: isPlainObject function isPlainObject(o): o is Object ``` -Defined in: [compare.ts:72](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/compare.ts#L72) +Defined in: [compare.ts:72](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L72) ## Parameters diff --git a/docs/reference/functions/parsefunctionorvalue.md b/docs/reference/functions/parsefunctionorvalue.md index 1629cd6c5..6de77017e 100644 --- a/docs/reference/functions/parsefunctionorvalue.md +++ b/docs/reference/functions/parsefunctionorvalue.md @@ -11,7 +11,7 @@ title: parseFunctionOrValue function parseFunctionOrValue(value, ...args): T ``` -Defined in: [utils.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/utils.ts#L7) +Defined in: [utils.ts:7](https://github.com/TanStack/pacer/blob/main/packages/persister/src/utils.ts#L7) ## Type Parameters diff --git a/docs/reference/functions/queue.md b/docs/reference/functions/queue.md deleted file mode 100644 index 74b088820..000000000 --- a/docs/reference/functions/queue.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: queue -title: queue ---- - - - -# Function: queue() - -```ts -function queue(fn, options): (item, position, runOnUpdate) => boolean -``` - -Defined in: [queuer.ts:549](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L549) - -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 running and will process items as they are added. -For more control over queue processing, use the Queuer class directly. - -Example usage: -```ts -// Basic sequential processing -const processItems = queue((n) => console.log(n), { - wait: 1000, - onItemsChange: (queuer) => console.log(queuer.peekAllItems()) -}); -processItems(1); // Logs: 1 -processItems(2); // Logs: 2 after 1 completes - -// Priority queue -const processPriority = queue((n) => console.log(n), { - getPriority: n => n // Higher numbers processed first -}); -processPriority(1); -processPriority(3); // Processed before 1 -``` - -## Type Parameters - -• **TValue** - -## Parameters - -### fn - -(`item`) => `void` - -### options - -[`QueuerOptions`](../../interfaces/queueroptions.md)\<`TValue`\> - -## Returns - -`Function` - -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. - -Returns true if the item was added, false if the queue is full. - -Example usage: -```ts -queuer.addItem('task'); -queuer.addItem('task2', 'front'); -``` - -### Parameters - -#### item - -`TValue` - -#### position - -[`QueuePosition`](../../type-aliases/queueposition.md) = `...` - -#### runOnUpdate - -`boolean` = `true` - -### Returns - -`boolean` diff --git a/docs/reference/functions/ratelimit.md b/docs/reference/functions/ratelimit.md deleted file mode 100644 index 6c998336a..000000000 --- a/docs/reference/functions/ratelimit.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -id: rateLimit -title: rateLimit ---- - - - -# Function: rateLimit() - -```ts -function rateLimit(fn, initialOptions): (...args) => boolean -``` - -Defined in: [rate-limiter.ts:327](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L327) - -Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window. - -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 -- A debouncer collapses multiple calls into one, which is better for handling bursts of events - -The rate limiter supports two types of windows: -- '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. - -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. - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Parameters - -### fn - -`TFn` - -### initialOptions - -[`RateLimiterOptions`](../../interfaces/ratelimiteroptions.md)\<`TFn`\> - -## Returns - -`Function` - -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. - -### Parameters - -#### args - -...`Parameters`\<`TFn`\> - -### Returns - -`boolean` - -### Example - -```ts -const rateLimiter = new RateLimiter(fn, { limit: 5, window: 1000 }); - -// First 5 calls will return true -rateLimiter.maybeExecute('arg1', 'arg2'); // true - -// Additional calls within the window will return false -rateLimiter.maybeExecute('arg1', 'arg2'); // false -``` - -## Example - -```ts -// Rate limit to 5 calls per minute with a sliding window -const rateLimited = rateLimit(makeApiCall, { - limit: 5, - window: 60000, - windowType: 'sliding', - onReject: (rateLimiter) => { - console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); - } -}); - -// First 5 calls will execute immediately -// Additional calls will be rejected until the minute window resets -rateLimited(); - -// For more even execution, consider using throttle instead: -const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds -``` diff --git a/docs/reference/functions/replaceequaldeep.md b/docs/reference/functions/replaceequaldeep.md index 5a2e5a77c..1f4590da7 100644 --- a/docs/reference/functions/replaceequaldeep.md +++ b/docs/reference/functions/replaceequaldeep.md @@ -11,7 +11,7 @@ title: replaceEqualDeep function replaceEqualDeep(a, b): T ``` -Defined in: [compare.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/compare.ts#L6) +Defined in: [compare.ts:6](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L6) This function returns `a` if `b` is deeply equal. If not, it will replace any deeply equal children of `b` with those of `a`. diff --git a/docs/reference/functions/shallowequalobjects.md b/docs/reference/functions/shallowequalobjects.md index cf37d6dc1..4962906a4 100644 --- a/docs/reference/functions/shallowequalobjects.md +++ b/docs/reference/functions/shallowequalobjects.md @@ -11,7 +11,7 @@ title: shallowEqualObjects function shallowEqualObjects(a, b): boolean ``` -Defined in: [compare.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/compare.ts#L49) +Defined in: [compare.ts:49](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L49) Shallow compare objects. diff --git a/docs/reference/functions/throttle.md b/docs/reference/functions/throttle.md deleted file mode 100644 index 0f12a6976..000000000 --- a/docs/reference/functions/throttle.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -id: throttle -title: throttle ---- - - - -# Function: throttle() - -```ts -function throttle(fn, initialOptions): (...args) => void -``` - -Defined in: [throttler.ts:252](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L252) - -Creates a throttled function that limits how often the provided function can execute. - -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. - -The throttled function can be configured to execute on the leading and/or trailing -edge of the throttle window via options. - -For handling bursts of events, consider using debounce() instead. For hard execution -limits, consider using rateLimit(). - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Parameters - -### fn - -`TFn` - -### initialOptions - -[`ThrottlerOptions`](../../interfaces/throttleroptions.md)\<`TFn`\> - -## Returns - -`Function` - -Attempts to execute the throttled function. The execution behavior depends on the throttler options: - -- If enough time has passed since the last execution (>= wait period): - - With leading=true: Executes immediately - - With leading=false: Waits for the next trailing execution - -- If within the wait period: - - With trailing=true: Schedules execution for end of wait period - - With trailing=false: Drops the execution - -### Parameters - -#### args - -...`Parameters`\<`TFn`\> - -### Returns - -`void` - -### Example - -```ts -const throttled = new Throttler(fn, { wait: 1000 }); - -// First call executes immediately -throttled.maybeExecute('a', 'b'); - -// Call during wait period - gets throttled -throttled.maybeExecute('c', 'd'); -``` - -## Example - -```ts -// Basic throttling - max once per second -const throttled = throttle(updateUI, { wait: 1000 }); - -// Configure leading/trailing execution -const throttled = throttle(saveData, { - wait: 2000, - leading: true, // Execute immediately on first call - trailing: true // Execute again after delay if called during wait -}); -``` diff --git a/docs/reference/index.md b/docs/reference/index.md index a0655006c..85f9f1d0a 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,65 +1,36 @@ --- -id: "@tanstack/pacer" -title: "@tanstack/pacer" +id: "@tanstack/persister" +title: "@tanstack/persister" --- -# @tanstack/pacer +# @tanstack/persister ## Classes -- [AsyncDebouncer](../classes/asyncdebouncer.md) - [AsyncPersister](../classes/asyncpersister.md) -- [AsyncQueuer](../classes/asyncqueuer.md) -- [AsyncRateLimiter](../classes/asyncratelimiter.md) -- [AsyncThrottler](../classes/asyncthrottler.md) -- [Batcher](../classes/batcher.md) -- [Debouncer](../classes/debouncer.md) - [Persister](../classes/persister.md) -- [Queuer](../classes/queuer.md) -- [RateLimiter](../classes/ratelimiter.md) - [StoragePersister](../classes/storagepersister.md) -- [Throttler](../classes/throttler.md) ## Interfaces -- [AsyncDebouncerOptions](../interfaces/asyncdebounceroptions.md) -- [AsyncQueuerOptions](../interfaces/asyncqueueroptions.md) -- [AsyncRateLimiterOptions](../interfaces/asyncratelimiteroptions.md) -- [AsyncRateLimiterState](../interfaces/asyncratelimiterstate.md) -- [AsyncThrottlerOptions](../interfaces/asyncthrottleroptions.md) -- [BatcherOptions](../interfaces/batcheroptions.md) -- [DebouncerOptions](../interfaces/debounceroptions.md) - [PersistedStorage](../interfaces/persistedstorage.md) -- [QueuerOptions](../interfaces/queueroptions.md) -- [RateLimiterOptions](../interfaces/ratelimiteroptions.md) -- [RateLimiterState](../interfaces/ratelimiterstate.md) - [StoragePersisterOptions](../interfaces/storagepersisteroptions.md) -- [ThrottlerOptions](../interfaces/throttleroptions.md) ## Type Aliases - [AnyAsyncFunction](../type-aliases/anyasyncfunction.md) - [AnyFunction](../type-aliases/anyfunction.md) - [OptionalKeys](../type-aliases/optionalkeys.md) -- [QueuePosition](../type-aliases/queueposition.md) +- [RequiredKeys](../type-aliases/requiredkeys.md) ## Functions -- [asyncDebounce](../functions/asyncdebounce.md) -- [asyncQueue](../functions/asyncqueue.md) -- [asyncRateLimit](../functions/asyncratelimit.md) -- [asyncThrottle](../functions/asyncthrottle.md) -- [batch](../functions/batch.md) - [bindInstanceMethods](../functions/bindinstancemethods.md) -- [debounce](../functions/debounce.md) - [isFunction](../functions/isfunction.md) - [isPlainArray](../functions/isplainarray.md) - [isPlainObject](../functions/isplainobject.md) - [parseFunctionOrValue](../functions/parsefunctionorvalue.md) -- [queue](../functions/queue.md) -- [rateLimit](../functions/ratelimit.md) - [replaceEqualDeep](../functions/replaceequaldeep.md) - [shallowEqualObjects](../functions/shallowequalobjects.md) -- [throttle](../functions/throttle.md) diff --git a/docs/reference/interfaces/asyncdebounceroptions.md b/docs/reference/interfaces/asyncdebounceroptions.md deleted file mode 100644 index 21a71de5b..000000000 --- a/docs/reference/interfaces/asyncdebounceroptions.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -id: AsyncDebouncerOptions -title: AsyncDebouncerOptions ---- - - - -# Interface: AsyncDebouncerOptions\ - -Defined in: [async-debouncer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L7) - -Options for configuring an async debounced function - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Properties - -### enabled? - -```ts -optional enabled: boolean | (debouncer) => boolean; -``` - -Defined in: [async-debouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L13) - -Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions. -Can be a boolean or a function that returns a boolean. -Defaults to true. - -*** - -### leading? - -```ts -optional leading: boolean; -``` - -Defined in: [async-debouncer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L18) - -Whether to execute on the leading edge of the timeout. -Defaults to false. - -*** - -### onError()? - -```ts -optional onError: (error, debouncer) => void; -``` - -Defined in: [async-debouncer.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L24) - -Optional error handler for when the debounced function throws. -If provided, the handler will be called with the error and debouncer instance. -This can be used alongside throwOnError - the handler will be called before any error is thrown. - -#### Parameters - -##### error - -`unknown` - -##### debouncer - -[`AsyncDebouncer`](../../classes/asyncdebouncer.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onSettled()? - -```ts -optional onSettled: (debouncer) => void; -``` - -Defined in: [async-debouncer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L28) - -Optional callback to call when the debounced function is executed - -#### Parameters - -##### debouncer - -[`AsyncDebouncer`](../../classes/asyncdebouncer.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onSuccess()? - -```ts -optional onSuccess: (result, debouncer) => void; -``` - -Defined in: [async-debouncer.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L32) - -Optional callback to call when the debounced function is executed - -#### Parameters - -##### result - -`ReturnType`\<`TFn`\> - -##### debouncer - -[`AsyncDebouncer`](../../classes/asyncdebouncer.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### throwOnError? - -```ts -optional throwOnError: boolean; -``` - -Defined in: [async-debouncer.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L38) - -Whether to throw errors when they occur. -Defaults to true if no onError handler is provided, false if an onError handler is provided. -Can be explicitly set to override these defaults. - -*** - -### trailing? - -```ts -optional trailing: boolean; -``` - -Defined in: [async-debouncer.ts:43](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L43) - -Whether to execute on the trailing edge of the timeout. -Defaults to true. - -*** - -### wait - -```ts -wait: number | (debouncer) => number; -``` - -Defined in: [async-debouncer.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L49) - -Delay in milliseconds to wait after the last call before executing. -Can be a number or a function that returns a number. -Defaults to 0ms diff --git a/docs/reference/interfaces/asyncqueueroptions.md b/docs/reference/interfaces/asyncqueueroptions.md deleted file mode 100644 index 43246585a..000000000 --- a/docs/reference/interfaces/asyncqueueroptions.md +++ /dev/null @@ -1,374 +0,0 @@ ---- -id: AsyncQueuerOptions -title: AsyncQueuerOptions ---- - - - -# Interface: AsyncQueuerOptions\ - -Defined in: [async-queuer.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L5) - -## Type Parameters - -• **TValue** - -## Properties - -### addItemsTo? - -```ts -optional addItemsTo: QueuePosition; -``` - -Defined in: [async-queuer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L10) - -Default position to add items to the queuer - -#### Default - -```ts -'back' -``` - -*** - -### concurrency? - -```ts -optional concurrency: number | (queuer) => number; -``` - -Defined in: [async-queuer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L16) - -Maximum number of concurrent tasks to process. -Can be a number or a function that returns a number. - -#### Default - -```ts -1 -``` - -*** - -### expirationDuration? - -```ts -optional expirationDuration: number; -``` - -Defined in: [async-queuer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L21) - -Maximum time in milliseconds that an item can stay in the queue -If not provided, items will never expire - -*** - -### getIsExpired()? - -```ts -optional getIsExpired: (item, addedAt) => boolean; -``` - -Defined in: [async-queuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L26) - -Function to determine if an item has expired -If provided, this overrides the expirationDuration behavior - -#### Parameters - -##### item - -`TValue` - -##### addedAt - -`number` - -#### Returns - -`boolean` - -*** - -### getItemsFrom? - -```ts -optional getItemsFrom: QueuePosition; -``` - -Defined in: [async-queuer.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L31) - -Default position to get items from during processing - -#### Default - -```ts -'front' -``` - -*** - -### getPriority()? - -```ts -optional getPriority: (item) => number; -``` - -Defined in: [async-queuer.ts:37](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L37) - -Function to determine priority of items in the queuer -Higher priority items will be processed first -If not provided, will use static priority values attached to tasks - -#### Parameters - -##### item - -`TValue` - -#### Returns - -`number` - -*** - -### initialItems? - -```ts -optional initialItems: TValue[]; -``` - -Defined in: [async-queuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L41) - -Initial items to populate the queuer with - -*** - -### maxSize? - -```ts -optional maxSize: number; -``` - -Defined in: [async-queuer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L45) - -Maximum number of items allowed in the queuer - -*** - -### onError()? - -```ts -optional onError: (error, queuer) => void; -``` - -Defined in: [async-queuer.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L51) - -Optional error handler for when a task throws. -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. - -#### Parameters - -##### error - -`unknown` - -##### queuer - -[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onExpire()? - -```ts -optional onExpire: (item, queuer) => void; -``` - -Defined in: [async-queuer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L55) - -Callback fired whenever an item expires in the queuer - -#### Parameters - -##### item - -`TValue` - -##### queuer - -[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onIsRunningChange()? - -```ts -optional onIsRunningChange: (queuer) => void; -``` - -Defined in: [async-queuer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L59) - -Callback fired whenever the queuer's running state changes - -#### Parameters - -##### queuer - -[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onItemsChange()? - -```ts -optional onItemsChange: (queuer) => void; -``` - -Defined in: [async-queuer.ts:63](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L63) - -Callback fired whenever an item is added or removed from the queuer - -#### Parameters - -##### queuer - -[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onReject()? - -```ts -optional onReject: (item, queuer) => void; -``` - -Defined in: [async-queuer.ts:67](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L67) - -Callback fired whenever an item is rejected from being added to the queuer - -#### Parameters - -##### item - -`TValue` - -##### queuer - -[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onSettled()? - -```ts -optional onSettled: (queuer) => void; -``` - -Defined in: [async-queuer.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L71) - -Optional callback to call when a task is settled - -#### Parameters - -##### queuer - -[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onSuccess()? - -```ts -optional onSuccess: (result, queuer) => void; -``` - -Defined in: [async-queuer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L75) - -Optional callback to call when a task succeeds - -#### Parameters - -##### result - -`TValue` - -##### queuer - -[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### started? - -```ts -optional started: boolean; -``` - -Defined in: [async-queuer.ts:79](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L79) - -Whether the queuer should start processing tasks immediately or not. - -*** - -### throwOnError? - -```ts -optional throwOnError: boolean; -``` - -Defined in: [async-queuer.ts:85](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L85) - -Whether to throw errors when they occur. -Defaults to true if no onError handler is provided, false if an onError handler is provided. -Can be explicitly set to override these defaults. - -*** - -### wait? - -```ts -optional wait: number | (queuer) => number; -``` - -Defined in: [async-queuer.ts:91](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L91) - -Time in milliseconds to wait between processing items. -Can be a number or a function that returns a number. - -#### Default - -```ts -0 -``` diff --git a/docs/reference/interfaces/asyncratelimiteroptions.md b/docs/reference/interfaces/asyncratelimiteroptions.md deleted file mode 100644 index d37cfc439..000000000 --- a/docs/reference/interfaces/asyncratelimiteroptions.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -id: AsyncRateLimiterOptions -title: AsyncRateLimiterOptions ---- - - - -# Interface: AsyncRateLimiterOptions\ - -Defined in: [async-rate-limiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L22) - -Options for configuring an async rate-limited function - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Properties - -### enabled? - -```ts -optional enabled: boolean | (rateLimiter) => boolean; -``` - -Defined in: [async-rate-limiter.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L28) - -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. -Defaults to true. - -*** - -### limit - -```ts -limit: number | (rateLimiter) => number; -``` - -Defined in: [async-rate-limiter.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L33) - -Maximum number of executions allowed within the time window. -Can be a number or a function that returns a number. - -*** - -### onError()? - -```ts -optional onError: (error, rateLimiter) => void; -``` - -Defined in: [async-rate-limiter.ts:39](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L39) - -Optional error handler for when the rate-limited function throws. -If provided, the handler will be called with the error and rate limiter instance. -This can be used alongside throwOnError - the handler will be called before any error is thrown. - -#### Parameters - -##### error - -`unknown` - -##### rateLimiter - -[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onReject()? - -```ts -optional onReject: (rateLimiter) => void; -``` - -Defined in: [async-rate-limiter.ts:43](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L43) - -Optional callback function that is called when an execution is rejected due to rate limiting - -#### Parameters - -##### rateLimiter - -[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onSettled()? - -```ts -optional onSettled: (rateLimiter) => void; -``` - -Defined in: [async-rate-limiter.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L47) - -Optional function to call when the rate-limited function is executed - -#### Parameters - -##### rateLimiter - -[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onSuccess()? - -```ts -optional onSuccess: (result, rateLimiter) => void; -``` - -Defined in: [async-rate-limiter.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L51) - -Optional function to call when the rate-limited function is executed - -#### Parameters - -##### result - -`ReturnType`\<`TFn`\> - -##### rateLimiter - -[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### persister? - -```ts -optional persister: - | AsyncPersister> -| Persister>; -``` - -Defined in: [async-rate-limiter.ts:58](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L58) - -Optional persister for saving/loading rate limiter state - -*** - -### throwOnError? - -```ts -optional throwOnError: boolean; -``` - -Defined in: [async-rate-limiter.ts:66](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L66) - -Whether to throw errors when they occur. -Defaults to true if no onError handler is provided, false if an onError handler is provided. -Can be explicitly set to override these defaults. - -*** - -### window - -```ts -window: number | (rateLimiter) => number; -``` - -Defined in: [async-rate-limiter.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L71) - -Time window in milliseconds within which the limit applies. -Can be a number or a function that returns a number. - -*** - -### windowType? - -```ts -optional windowType: "fixed" | "sliding"; -``` - -Defined in: [async-rate-limiter.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L78) - -Type of window to use for rate limiting -- 'fixed': Uses a fixed window that resets after the window period -- 'sliding': Uses a sliding window that allows executions as old ones expire -Defaults to 'fixed' diff --git a/docs/reference/interfaces/asyncratelimiterstate.md b/docs/reference/interfaces/asyncratelimiterstate.md deleted file mode 100644 index 81b2b2b6e..000000000 --- a/docs/reference/interfaces/asyncratelimiterstate.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -id: AsyncRateLimiterState -title: AsyncRateLimiterState ---- - - - -# Interface: AsyncRateLimiterState\ - -Defined in: [async-rate-limiter.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L9) - -State shape for persisting AsyncRateLimiter - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Properties - -### errorCount - -```ts -errorCount: number; -``` - -Defined in: [async-rate-limiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L10) - -*** - -### executionTimes - -```ts -executionTimes: number[]; -``` - -Defined in: [async-rate-limiter.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L11) - -*** - -### isExecuting - -```ts -isExecuting: boolean; -``` - -Defined in: [async-rate-limiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L12) - -*** - -### lastResult - -```ts -lastResult: undefined | ReturnType; -``` - -Defined in: [async-rate-limiter.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L13) - -*** - -### rejectionCount - -```ts -rejectionCount: number; -``` - -Defined in: [async-rate-limiter.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L14) - -*** - -### settleCount - -```ts -settleCount: number; -``` - -Defined in: [async-rate-limiter.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L15) - -*** - -### successCount - -```ts -successCount: number; -``` - -Defined in: [async-rate-limiter.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L16) diff --git a/docs/reference/interfaces/asyncthrottleroptions.md b/docs/reference/interfaces/asyncthrottleroptions.md deleted file mode 100644 index acac7dc5b..000000000 --- a/docs/reference/interfaces/asyncthrottleroptions.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -id: AsyncThrottlerOptions -title: AsyncThrottlerOptions ---- - - - -# Interface: AsyncThrottlerOptions\ - -Defined in: [async-throttler.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L7) - -Options for configuring an async throttled function - -## Type Parameters - -• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) - -## Properties - -### enabled? - -```ts -optional enabled: boolean | (throttler) => boolean; -``` - -Defined in: [async-throttler.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L13) - -Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions. -Can be a boolean or a function that returns a boolean. -Defaults to true. - -*** - -### leading? - -```ts -optional leading: boolean; -``` - -Defined in: [async-throttler.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L18) - -Whether to execute the function immediately when called -Defaults to true - -*** - -### onError()? - -```ts -optional onError: (error, asyncThrottler) => void; -``` - -Defined in: [async-throttler.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L24) - -Optional error handler for when the throttled function throws. -If provided, the handler will be called with the error and throttler instance. -This can be used alongside throwOnError - the handler will be called before any error is thrown. - -#### Parameters - -##### error - -`unknown` - -##### asyncThrottler - -[`AsyncThrottler`](../../classes/asyncthrottler.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onSettled()? - -```ts -optional onSettled: (asyncThrottler) => void; -``` - -Defined in: [async-throttler.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L28) - -Optional function to call when the throttled function is executed - -#### Parameters - -##### asyncThrottler - -[`AsyncThrottler`](../../classes/asyncthrottler.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onSuccess()? - -```ts -optional onSuccess: (result, asyncThrottler) => void; -``` - -Defined in: [async-throttler.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L32) - -Optional function to call when the throttled function is executed - -#### Parameters - -##### result - -`ReturnType`\<`TFn`\> - -##### asyncThrottler - -[`AsyncThrottler`](../../classes/asyncthrottler.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### throwOnError? - -```ts -optional throwOnError: boolean; -``` - -Defined in: [async-throttler.ts:41](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L41) - -Whether to throw errors when they occur. -Defaults to true if no onError handler is provided, false if an onError handler is provided. -Can be explicitly set to override these defaults. - -*** - -### trailing? - -```ts -optional trailing: boolean; -``` - -Defined in: [async-throttler.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L46) - -Whether to execute the function on the trailing edge of the wait period -Defaults to true - -*** - -### wait - -```ts -wait: number | (throttler) => number; -``` - -Defined in: [async-throttler.ts:52](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L52) - -Time window in milliseconds during which the function can only be executed once. -Can be a number or a function that returns a number. -Defaults to 0ms diff --git a/docs/reference/interfaces/batcheroptions.md b/docs/reference/interfaces/batcheroptions.md deleted file mode 100644 index 4ea283744..000000000 --- a/docs/reference/interfaces/batcheroptions.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -id: BatcherOptions -title: BatcherOptions ---- - - - -# Interface: BatcherOptions\ - -Defined in: [batcher.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L6) - -Options for configuring a Batcher instance - -## Type Parameters - -• **TValue** - -## Properties - -### getShouldExecute()? - -```ts -optional getShouldExecute: (items, batcher) => boolean; -``` - -Defined in: [batcher.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L11) - -Custom function to determine if a batch should be processed -Return true to process the batch immediately - -#### Parameters - -##### items - -`TValue`[] - -##### batcher - -[`Batcher`](../../classes/batcher.md)\<`TValue`\> - -#### Returns - -`boolean` - -*** - -### maxSize? - -```ts -optional maxSize: number; -``` - -Defined in: [batcher.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L16) - -Maximum number of items in a batch - -#### Default - -```ts -Infinity -``` - -*** - -### onExecute()? - -```ts -optional onExecute: (batcher) => void; -``` - -Defined in: [batcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L20) - -Callback fired after a batch is processed - -#### Parameters - -##### batcher - -[`Batcher`](../../classes/batcher.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onIsRunningChange()? - -```ts -optional onIsRunningChange: (batcher) => void; -``` - -Defined in: [batcher.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L24) - -Callback fired when the batcher's running state changes - -#### Parameters - -##### batcher - -[`Batcher`](../../classes/batcher.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onItemsChange()? - -```ts -optional onItemsChange: (batcher) => void; -``` - -Defined in: [batcher.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L28) - -Callback fired after items are added to the batcher - -#### Parameters - -##### batcher - -[`Batcher`](../../classes/batcher.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### started? - -```ts -optional started: boolean; -``` - -Defined in: [batcher.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L33) - -Whether the batcher should start processing immediately - -#### Default - -```ts -true -``` - -*** - -### wait? - -```ts -optional wait: number; -``` - -Defined in: [batcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L40) - -Maximum time in milliseconds to wait before processing a batch. -If the wait duration has elapsed, the batch will be processed. -If not provided, the batch will not be triggered by a timeout. - -#### Default - -```ts -Infinity -``` diff --git a/docs/reference/interfaces/debounceroptions.md b/docs/reference/interfaces/debounceroptions.md deleted file mode 100644 index 4ec4b9290..000000000 --- a/docs/reference/interfaces/debounceroptions.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -id: DebouncerOptions -title: DebouncerOptions ---- - - - -# Interface: DebouncerOptions\ - -Defined in: [debouncer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L7) - -Options for configuring a debounced function - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Properties - -### enabled? - -```ts -optional enabled: boolean | (debouncer) => boolean; -``` - -Defined in: [debouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L13) - -Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions. -Can be a boolean or a function that returns a boolean. -Defaults to true. - -*** - -### leading? - -```ts -optional leading: boolean; -``` - -Defined in: [debouncer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L19) - -Whether to execute on the leading edge of the timeout. -The first call will execute immediately and the rest will wait the delay. -Defaults to false. - -*** - -### onExecute()? - -```ts -optional onExecute: (debouncer) => void; -``` - -Defined in: [debouncer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L23) - -Callback function that is called after the function is executed - -#### Parameters - -##### debouncer - -[`Debouncer`](../../classes/debouncer.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### trailing? - -```ts -optional trailing: boolean; -``` - -Defined in: [debouncer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L28) - -Whether to execute on the trailing edge of the timeout. -Defaults to true. - -*** - -### wait - -```ts -wait: number | (debouncer) => number; -``` - -Defined in: [debouncer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L34) - -Delay in milliseconds before executing the function. -Can be a number or a function that returns a number. -Defaults to 0ms diff --git a/docs/reference/interfaces/persistedstorage.md b/docs/reference/interfaces/persistedstorage.md index a09e20e17..c6897bd7b 100644 --- a/docs/reference/interfaces/persistedstorage.md +++ b/docs/reference/interfaces/persistedstorage.md @@ -7,7 +7,7 @@ title: PersistedStorage # Interface: PersistedStorage\ -Defined in: [persister.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L30) +Defined in: [persister.ts:32](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L32) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [persister.ts:30](https://github.com/TanStack/pacer/blob/main/packag optional buster: string; ``` -Defined in: [persister.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L31) +Defined in: [persister.ts:33](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L33) *** @@ -31,7 +31,7 @@ Defined in: [persister.ts:31](https://github.com/TanStack/pacer/blob/main/packag state: undefined | TState; ``` -Defined in: [persister.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L32) +Defined in: [persister.ts:34](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L34) *** @@ -41,4 +41,4 @@ Defined in: [persister.ts:32](https://github.com/TanStack/pacer/blob/main/packag timestamp: number; ``` -Defined in: [persister.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L33) +Defined in: [persister.ts:35](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L35) diff --git a/docs/reference/interfaces/queueroptions.md b/docs/reference/interfaces/queueroptions.md deleted file mode 100644 index f2ba260e1..000000000 --- a/docs/reference/interfaces/queueroptions.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -id: QueuerOptions -title: QueuerOptions ---- - - - -# Interface: QueuerOptions\ - -Defined in: [queuer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L8) - -Options for configuring a Queuer instance. - -These options control queue behavior, item expiration, callbacks, and more. - -## Type Parameters - -• **TValue** - -## Properties - -### addItemsTo? - -```ts -optional addItemsTo: QueuePosition; -``` - -Defined in: [queuer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L13) - -Default position to add items to the queuer - -#### Default - -```ts -'back' -``` - -*** - -### expirationDuration? - -```ts -optional expirationDuration: number; -``` - -Defined in: [queuer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L18) - -Maximum time in milliseconds that an item can stay in the queue -If not provided, items will never expire - -*** - -### getIsExpired()? - -```ts -optional getIsExpired: (item, addedAt) => boolean; -``` - -Defined in: [queuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L23) - -Function to determine if an item has expired -If provided, this overrides the expirationDuration behavior - -#### Parameters - -##### item - -`TValue` - -##### addedAt - -`number` - -#### Returns - -`boolean` - -*** - -### getItemsFrom? - -```ts -optional getItemsFrom: QueuePosition; -``` - -Defined in: [queuer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L28) - -Default position to get items from during processing - -#### Default - -```ts -'front' -``` - -*** - -### getPriority()? - -```ts -optional getPriority: (item) => number; -``` - -Defined in: [queuer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L33) - -Function to determine priority of items in the queuer -Higher priority items will be processed first - -#### Parameters - -##### item - -`TValue` - -#### Returns - -`number` - -*** - -### initialItems? - -```ts -optional initialItems: TValue[]; -``` - -Defined in: [queuer.ts:37](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L37) - -Initial items to populate the queuer with - -*** - -### maxSize? - -```ts -optional maxSize: number; -``` - -Defined in: [queuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L41) - -Maximum number of items allowed in the queuer - -*** - -### onExecute()? - -```ts -optional onExecute: (item, queuer) => void; -``` - -Defined in: [queuer.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L49) - -Callback fired whenever an item is removed from the queuer - -#### Parameters - -##### item - -`TValue` - -##### queuer - -[`Queuer`](../../classes/queuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onExpire()? - -```ts -optional onExpire: (item, queuer) => void; -``` - -Defined in: [queuer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L45) - -Callback fired whenever an item expires in the queuer - -#### Parameters - -##### item - -`TValue` - -##### queuer - -[`Queuer`](../../classes/queuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onIsRunningChange()? - -```ts -optional onIsRunningChange: (queuer) => void; -``` - -Defined in: [queuer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L53) - -Callback fired whenever the queuer's running state changes - -#### Parameters - -##### queuer - -[`Queuer`](../../classes/queuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onItemsChange()? - -```ts -optional onItemsChange: (queuer) => void; -``` - -Defined in: [queuer.ts:57](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L57) - -Callback fired whenever an item is added or removed from the queuer - -#### Parameters - -##### queuer - -[`Queuer`](../../classes/queuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### onReject()? - -```ts -optional onReject: (item, queuer) => void; -``` - -Defined in: [queuer.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L61) - -Callback fired whenever an item is rejected from being added to the queuer - -#### Parameters - -##### item - -`TValue` - -##### queuer - -[`Queuer`](../../classes/queuer.md)\<`TValue`\> - -#### Returns - -`void` - -*** - -### started? - -```ts -optional started: boolean; -``` - -Defined in: [queuer.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L65) - -Whether the queuer should start processing tasks immediately - -*** - -### wait? - -```ts -optional wait: number | (queuer) => number; -``` - -Defined in: [queuer.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L71) - -Time in milliseconds to wait between processing items. -Can be a number or a function that returns a number. - -#### Default - -```ts -0 -``` diff --git a/docs/reference/interfaces/ratelimiteroptions.md b/docs/reference/interfaces/ratelimiteroptions.md deleted file mode 100644 index 1ba3140a3..000000000 --- a/docs/reference/interfaces/ratelimiteroptions.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -id: RateLimiterOptions -title: RateLimiterOptions ---- - - - -# Interface: RateLimiterOptions\ - -Defined in: [rate-limiter.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L17) - -Options for configuring a rate-limited function - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Properties - -### enabled? - -```ts -optional enabled: boolean | (rateLimiter) => boolean; -``` - -Defined in: [rate-limiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L22) - -Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions. -Defaults to true. - -*** - -### limit - -```ts -limit: number | (rateLimiter) => number; -``` - -Defined in: [rate-limiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L27) - -Maximum number of executions allowed within the time window. -Can be a number or a callback function that receives the rate limiter instance and returns a number. - -*** - -### onExecute()? - -```ts -optional onExecute: (rateLimiter) => void; -``` - -Defined in: [rate-limiter.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L31) - -Callback function that is called after the function is executed - -#### Parameters - -##### rateLimiter - -[`RateLimiter`](../../classes/ratelimiter.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### onReject()? - -```ts -optional onReject: (rateLimiter) => void; -``` - -Defined in: [rate-limiter.ts:35](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L35) - -Optional callback function that is called when an execution is rejected due to rate limiting - -#### Parameters - -##### rateLimiter - -[`RateLimiter`](../../classes/ratelimiter.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### persister? - -```ts -optional persister: Persister; -``` - -Defined in: [rate-limiter.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L51) - -Optional persister for saving/loading rate limiter state - -*** - -### window - -```ts -window: number | (rateLimiter) => number; -``` - -Defined in: [rate-limiter.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L40) - -Time window in milliseconds within which the limit applies. -Can be a number or a callback function that receives the rate limiter instance and returns a number. - -*** - -### windowType? - -```ts -optional windowType: "fixed" | "sliding"; -``` - -Defined in: [rate-limiter.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L47) - -Type of window to use for rate limiting -- 'fixed': Uses a fixed window that resets after the window period -- 'sliding': Uses a sliding window that allows executions as old ones expire -Defaults to 'fixed' diff --git a/docs/reference/interfaces/ratelimiterstate.md b/docs/reference/interfaces/ratelimiterstate.md deleted file mode 100644 index b7de2981a..000000000 --- a/docs/reference/interfaces/ratelimiterstate.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -id: RateLimiterState -title: RateLimiterState ---- - - - -# Interface: RateLimiterState - -Defined in: [rate-limiter.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L8) - -State shape for persisting RateLimiter - -## Properties - -### executionCount - -```ts -executionCount: number; -``` - -Defined in: [rate-limiter.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L9) - -*** - -### executionTimes - -```ts -executionTimes: number[]; -``` - -Defined in: [rate-limiter.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L11) - -*** - -### rejectionCount - -```ts -rejectionCount: number; -``` - -Defined in: [rate-limiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L10) diff --git a/docs/reference/interfaces/storagepersisteroptions.md b/docs/reference/interfaces/storagepersisteroptions.md index 867aa574b..1ed5704f7 100644 --- a/docs/reference/interfaces/storagepersisteroptions.md +++ b/docs/reference/interfaces/storagepersisteroptions.md @@ -7,7 +7,7 @@ title: StoragePersisterOptions # Interface: StoragePersisterOptions\ -Defined in: [persister.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L42) +Defined in: [persister.ts:44](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L44) Configuration options for creating a browser-based state persister. @@ -26,7 +26,7 @@ sessionStorage (cleared when browser tab/window closes) to store serialized stat optional buster: string; ``` -Defined in: [persister.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L47) +Defined in: [persister.ts:49](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L49) A version string used to invalidate cached state. When changed, any existing stored state will be considered invalid and cleared. @@ -39,7 +39,7 @@ stored state will be considered invalid and cleared. optional deserializer: (state) => PersistedStorage; ``` -Defined in: [persister.ts:52](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L52) +Defined in: [persister.ts:54](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L54) Optional function to customize how state is deserialized after loading from storage. By default, JSON.parse is used. @@ -62,7 +62,7 @@ By default, JSON.parse is used. key: string; ``` -Defined in: [persister.ts:56](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L56) +Defined in: [persister.ts:58](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L58) Unique identifier used as the storage key for persisting state. @@ -74,7 +74,7 @@ Unique identifier used as the storage key for persisting state. optional maxAge: number; ``` -Defined in: [persister.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L61) +Defined in: [persister.ts:63](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L63) Maximum age in milliseconds before stored state is considered expired. When exceeded, the state will be cleared and treated as if it doesn't exist. @@ -87,7 +87,7 @@ When exceeded, the state will be cleared and treated as if it doesn't exist. optional onLoadState: (state) => void; ``` -Defined in: [persister.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L65) +Defined in: [persister.ts:67](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L67) Optional callback that runs after state is successfully loaded. @@ -109,7 +109,7 @@ Optional callback that runs after state is successfully loaded. optional onLoadStateError: (error) => void; ``` -Defined in: [persister.ts:69](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L69) +Defined in: [persister.ts:71](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L71) Optional callback that runs after state is unable to be loaded. @@ -131,7 +131,7 @@ Optional callback that runs after state is unable to be loaded. optional onSaveState: (state) => void; ``` -Defined in: [persister.ts:73](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L73) +Defined in: [persister.ts:75](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L75) Optional callback that runs after state is successfully saved. @@ -153,7 +153,7 @@ Optional callback that runs after state is successfully saved. optional onSaveStateError: (error) => void; ``` -Defined in: [persister.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L78) +Defined in: [persister.ts:80](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L80) Optional callback that runs after state is unable to be saved. For example, if the storage is full (localStorage >= 5MB) @@ -176,7 +176,7 @@ For example, if the storage is full (localStorage >= 5MB) optional serializer: (state) => string; ``` -Defined in: [persister.ts:83](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L83) +Defined in: [persister.ts:85](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L85) Optional function to customize how state is serialized before saving to storage. By default, JSON.stringify is used. @@ -199,7 +199,7 @@ By default, JSON.stringify is used. storage: Storage; ``` -Defined in: [persister.ts:88](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/persister.ts#L88) +Defined in: [persister.ts:90](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L90) The browser storage implementation to use for persisting state. Typically window.localStorage or window.sessionStorage. diff --git a/docs/reference/interfaces/throttleroptions.md b/docs/reference/interfaces/throttleroptions.md deleted file mode 100644 index 761ca59db..000000000 --- a/docs/reference/interfaces/throttleroptions.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -id: ThrottlerOptions -title: ThrottlerOptions ---- - - - -# Interface: ThrottlerOptions\ - -Defined in: [throttler.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L7) - -Options for configuring a throttled function - -## Type Parameters - -• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) - -## Properties - -### enabled? - -```ts -optional enabled: boolean | (throttler) => boolean; -``` - -Defined in: [throttler.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L13) - -Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions. -Can be a boolean or a function that returns a boolean. -Defaults to true. - -*** - -### leading? - -```ts -optional leading: boolean; -``` - -Defined in: [throttler.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L18) - -Whether to execute on the leading edge of the timeout. -Defaults to true. - -*** - -### onExecute()? - -```ts -optional onExecute: (throttler) => void; -``` - -Defined in: [throttler.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L22) - -Callback function that is called after the function is executed - -#### Parameters - -##### throttler - -[`Throttler`](../../classes/throttler.md)\<`TFn`\> - -#### Returns - -`void` - -*** - -### trailing? - -```ts -optional trailing: boolean; -``` - -Defined in: [throttler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L27) - -Whether to execute on the trailing edge of the timeout. -Defaults to true. - -*** - -### wait - -```ts -wait: number | (throttler) => number; -``` - -Defined in: [throttler.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L33) - -Time window in milliseconds during which the function can only be executed once. -Can be a number or a function that returns a number. -Defaults to 0ms diff --git a/docs/reference/type-aliases/anyasyncfunction.md b/docs/reference/type-aliases/anyasyncfunction.md index 19b6a17d7..fa911f5d8 100644 --- a/docs/reference/type-aliases/anyasyncfunction.md +++ b/docs/reference/type-aliases/anyasyncfunction.md @@ -11,7 +11,7 @@ title: AnyAsyncFunction type AnyAsyncFunction = (...args) => Promise; ``` -Defined in: [types.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/types.ts#L9) +Defined in: [types.ts:10](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L10) Represents an asynchronous function that can be called with any arguments and returns a promise. diff --git a/docs/reference/type-aliases/anyfunction.md b/docs/reference/type-aliases/anyfunction.md index 039ac0e6e..586abc705 100644 --- a/docs/reference/type-aliases/anyfunction.md +++ b/docs/reference/type-aliases/anyfunction.md @@ -11,7 +11,7 @@ title: AnyFunction type AnyFunction = (...args) => any; ``` -Defined in: [types.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/types.ts#L4) +Defined in: [types.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L5) Represents a function that can be called with any arguments and returns any value. diff --git a/docs/reference/type-aliases/optionalkeys.md b/docs/reference/type-aliases/optionalkeys.md index e09cbfd2f..d18e7c887 100644 --- a/docs/reference/type-aliases/optionalkeys.md +++ b/docs/reference/type-aliases/optionalkeys.md @@ -11,7 +11,7 @@ title: OptionalKeys type OptionalKeys = Omit & Partial>; ``` -Defined in: [types.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/types.ts#L11) +Defined in: [types.ts:12](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L12) ## Type Parameters diff --git a/docs/reference/type-aliases/queueposition.md b/docs/reference/type-aliases/queueposition.md deleted file mode 100644 index e8ae3e53c..000000000 --- a/docs/reference/type-aliases/queueposition.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -id: QueuePosition -title: QueuePosition ---- - - - -# Type Alias: QueuePosition - -```ts -type QueuePosition = "front" | "back"; -``` - -Defined in: [queuer.ts:97](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L97) - -Position type for addItem and getNextItem operations. - -- 'front': Operate on the front of the queue (FIFO) -- 'back': Operate on the back of the queue (LIFO) diff --git a/docs/reference/type-aliases/requiredkeys.md b/docs/reference/type-aliases/requiredkeys.md new file mode 100644 index 000000000..3940894fb --- /dev/null +++ b/docs/reference/type-aliases/requiredkeys.md @@ -0,0 +1,20 @@ +--- +id: RequiredKeys +title: RequiredKeys +--- + + + +# Type Alias: RequiredKeys\ + +```ts +type RequiredKeys = Required> & Omit; +``` + +Defined in: [types.ts:15](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L15) + +## Type Parameters + +• **T** *extends* `object` + +• **K** *extends* keyof `T` diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index e33c4f97d..be5fb81f5 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -40,6 +40,15 @@ function App() { console.log(setSearchAsyncRateLimiter.getSuccessCount()) } + const rateLimiterPersister = useStoragePersister< + AsyncRateLimiterState + >({ + key: 'my-async-rate-limiter', + storage: localStorage, + maxAge: 1000 * 60, // 1 minute + buster: 'v1', + }) + // hook that gives you an async rate limiter instance const setSearchAsyncRateLimiter = useAsyncRateLimiter(handleSearch, { // windowType: 'sliding', // default is 'fixed' @@ -57,12 +66,8 @@ function App() { setResults([]) }, // optionally, you can persist the rate limiter state to localStorage - persister: useStoragePersister>({ - key: 'my-async-rate-limiter', - storage: localStorage, - maxAge: 1000 * 60, // 1 minute - buster: 'v1', - }), + initialState: rateLimiterPersister.loadState(), + onStateChange: (state) => rateLimiterPersister.saveState(state), }) // get and name our rate limited function diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index ba6ba6862..ef24e595f 100644 --- a/examples/react/useRateLimiter/src/index.tsx +++ b/examples/react/useRateLimiter/src/index.tsx @@ -2,12 +2,20 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useRateLimiter } from '@tanstack/react-pacer/rate-limiter' import { useStoragePersister } from '@tanstack/react-persister/persister' +import type { RateLimiterState } from '@tanstack/react-pacer/rate-limiter' function App1() { // Use your state management library of choice const [instantCount, setInstantCount] = useState(0) // not rate-limited const [limitedCount, setLimitedCount] = useState(0) // rate-limited + const rateLimiterPersister = useStoragePersister({ + key: 'my-rate-limiter', + storage: localStorage, + maxAge: 1000 * 60, // 1 minute + buster: 'v1', + }) + // Using useRateLimiter with a rate limit of 5 executions per 5 seconds const rateLimiter = useRateLimiter(setLimitedCount, { // enabled: () => instantCount > 2, @@ -20,12 +28,8 @@ function App1() { rateLimiter.getMsUntilNextWindow(), ), // optional local storage persister to retain state on page refresh - persister: useStoragePersister({ - key: 'my-rate-limiter', - storage: localStorage, - maxAge: 1000 * 60, // 1 minute - buster: 'v1', - }), + initialState: rateLimiterPersister.loadState(), + onStateChange: (state) => rateLimiterPersister.saveState(state), }) function increment() { diff --git a/package.json b/package.json index 3f5866b4b..f41f5a6f8 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "nx affected --targets=build --exclude=examples/** && size-limit", "build:all": "nx run-many --targets=build --exclude=examples/** && size-limit", - "build:core": "nx build @tanstack/pacer @tanstack/persister && size-limit", + "build:core": "nx run-many --targets=build --projects=packages/pacer,packages/persister && size-limit", "changeset": "changeset", "changeset:publish": "changeset publish", "changeset:version": "changeset version && pnpm install --no-frozen-lockfile && pnpm prettier:write", diff --git a/packages/pacer/package.json b/packages/pacer/package.json index 6ca7c13e5..2e4be0526 100644 --- a/packages/pacer/package.json +++ b/packages/pacer/package.json @@ -159,8 +159,5 @@ "test:types": "tsc", "test:build": "publint --strict", "build": "vite build" - }, - "devDependencies": { - "@tanstack/persister": "workspace:*" } } diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 7b2ab2ac1..0a2d4b7a2 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -1,6 +1,5 @@ import { parseFunctionOrValue } from './utils' -import type { AnyAsyncFunction, OptionalKeys } from './types' -import type { AsyncPersister, Persister } from '@tanstack/persister' +import type { AnyAsyncFunction } from './types' /** * State shape for persisting AsyncRateLimiter @@ -25,6 +24,12 @@ export interface AsyncRateLimiterOptions { * Defaults to true. */ enabled?: boolean | ((rateLimiter: AsyncRateLimiter) => boolean) + /** + * Initial state for the rate limiter + */ + initialState?: + | Partial> + | Promise>> /** * Maximum number of executions allowed within the time window. * Can be a number or a function that returns a number. @@ -52,11 +57,9 @@ export interface AsyncRateLimiterOptions { rateLimiter: AsyncRateLimiter, ) => void /** - * Optional persister for saving/loading rate limiter state + * Callback function that is called when the state of the rate limiter is updated */ - persister?: - | AsyncPersister> - | Persister> + onStateChange?: (state: AsyncRateLimiterState) => void /** * Whether to throw errors when they occur. * Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -77,17 +80,20 @@ export interface AsyncRateLimiterOptions { windowType?: 'fixed' | 'sliding' } -type AsyncRateLimiterOptionsWithOptionalCallbacks = OptionalKeys< - AsyncRateLimiterOptions, - 'onError' | 'onReject' | 'onSettled' | 'onSuccess' -> - const defaultOptions: Omit< - AsyncRateLimiterOptionsWithOptionalCallbacks, - 'limit' | 'window' + Required>, + | 'initialState' + | 'onStateChange' + | 'onError' + | 'onReject' + | 'onSettled' + | 'onSuccess' > = { enabled: true, + limit: 1, + window: 0, windowType: 'fixed', + throwOnError: true, } /** @@ -114,6 +120,13 @@ const defaultOptions: Omit< * 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. * + * State Management: + * - Use `initialState` to provide initial state values when creating the rate limiter + * - `initialState` can be a partial state object or a Promise that resolves to a partial state + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution times, success/error counts, and current execution status + * - State can be retrieved using `getState()` method + * * 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 @@ -145,98 +158,86 @@ const defaultOptions: Omit< * ``` */ export class AsyncRateLimiter { - private _state: AsyncRateLimiterState = { - rejectionCount: 0, - executionTimes: [], + #options: AsyncRateLimiterOptions + #state: AsyncRateLimiterState = { errorCount: 0, - settleCount: 0, - successCount: 0, + executionTimes: [], isExecuting: false, lastResult: undefined as ReturnType | undefined, + rejectionCount: 0, + settleCount: 0, + successCount: 0, } - private _options: AsyncRateLimiterOptionsWithOptionalCallbacks - private _persister?: - | AsyncPersister> - | Persister> constructor( private fn: TFn, initialOptions: AsyncRateLimiterOptions, ) { - this._options = { + this.#options = { ...defaultOptions, ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } - this._persister = this._options.persister - if (this._persister) { - // Load state - const loadedState = this._persister.loadState() - if (loadedState instanceof Promise) { - loadedState.then((state) => { - if (state) { - this.setState(state, false) - } - }) - } else if (loadedState) { - this.setState(loadedState, false) + if (this.#options.initialState instanceof Promise) { + this.#options.initialState.then((state) => { + this.#setState(state) + }) + } else { + this.#state = { + ...this.#state, + ...this.#options.initialState, } } } /** - * Returns the current state for persistence + * Updates the rate limiter options */ - private getState(): AsyncRateLimiterState { - return { ...this._state } + setOptions(newOptions: Partial>): void { + this.#options = { ...this.#options, ...newOptions } } /** - * Loads state from a persisted object or updates state with a partial + * Returns the current rate limiter options */ - private setState( - state: Partial>, - save: boolean = true, - ): void { - this._state = { ...this._state, ...state } - if (save) { - this._persister?.saveState(this.getState()) - } + getOptions(): Required> { + return this.#options as Required> } /** - * Updates the rate limiter options + * Returns the current state for persistence */ - setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + getState(): AsyncRateLimiterState { + return { ...this.#state } } /** - * Returns the current rate limiter options + * Loads state from a persisted object or updates state with a partial */ - getOptions(): AsyncRateLimiterOptions { - return this._options + #setState(state: Partial>): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state) } /** * Returns the current enabled state of the rate limiter */ getEnabled(): boolean { - return !!parseFunctionOrValue(this._options.enabled, this) + return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current limit of executions allowed within the time window */ getLimit(): number { - return parseFunctionOrValue(this._options.limit, this) + return parseFunctionOrValue(this.#options.limit, this) } /** * Returns the current time window in milliseconds */ getWindow(): number { - return parseFunctionOrValue(this._options.window, this) + return parseFunctionOrValue(this.#options.window, this) } /** @@ -271,83 +272,83 @@ export class AsyncRateLimiter { async maybeExecute( ...args: Parameters ): Promise | undefined> { - this.cleanupOldExecutions() + this.#cleanupOldExecutions() const limit = this.getLimit() const window = this.getWindow() - if (this._options.windowType === 'sliding') { + if (this.#options.windowType === 'sliding') { // For sliding window, we can execute if we have capacity in the current window - if (this._state.executionTimes.length < limit) { - await this.execute(...args) - return this._state.lastResult + if (this.#state.executionTimes.length < limit) { + await this.#execute(...args) + return this.#state.lastResult } } else { // For fixed window, we need to check if we're in a new window const now = Date.now() - const oldestExecution = Math.min(...this._state.executionTimes) + const oldestExecution = Math.min(...this.#state.executionTimes) const isNewWindow = oldestExecution + window <= now - if (isNewWindow || this._state.executionTimes.length < limit) { - await this.execute(...args) - return this._state.lastResult + if (isNewWindow || this.#state.executionTimes.length < limit) { + await this.#execute(...args) + return this.#state.lastResult } } - this.rejectFunction() + this.#rejectFunction() return undefined } - private async execute( + async #execute( ...args: Parameters ): Promise | undefined> { if (!this.getEnabled()) return const now = Date.now() - this._state.executionTimes.push(now) // mutate state directly for performance - this.setState({ + this.#state.executionTimes.push(now) // mutate state directly for performance + this.#setState({ isExecuting: true, }) try { const result = await this.fn(...args) - this.setState({ - successCount: this._state.successCount + 1, + this.#setState({ + successCount: this.#state.successCount + 1, lastResult: result, }) - this._options.onSuccess?.(result, this) + this.#options.onSuccess?.(result, this) } catch (error) { - this.setState({ - errorCount: this._state.errorCount + 1, + this.#setState({ + errorCount: this.#state.errorCount + 1, }) - this._options.onError?.(error, this) - if (this._options.throwOnError) { + this.#options.onError?.(error, this) + if (this.#options.throwOnError) { throw error } else { console.error(error) } } finally { - this.setState({ + this.#setState({ isExecuting: false, - settleCount: this._state.settleCount + 1, + settleCount: this.#state.settleCount + 1, }) - this._options.onSettled?.(this) + this.#options.onSettled?.(this) } - return this._state.lastResult + return this.#state.lastResult } - private rejectFunction(): void { - this.setState({ - rejectionCount: this._state.rejectionCount + 1, + #rejectFunction(): void { + this.#setState({ + rejectionCount: this.#state.rejectionCount + 1, }) - this._options.onReject?.(this) + this.#options.onReject?.(this) } - private cleanupOldExecutions(): void { + #cleanupOldExecutions(): void { const now = Date.now() const windowStart = now - this.getWindow() - this.setState({ - executionTimes: this._state.executionTimes.filter( + this.#setState({ + executionTimes: this.#state.executionTimes.filter( (time) => time > windowStart, ), }) @@ -357,8 +358,8 @@ export class AsyncRateLimiter { * Returns the number of remaining executions allowed in the current window */ getRemainingInWindow(): number { - this.cleanupOldExecutions() - return Math.max(0, this.getLimit() - this._state.executionTimes.length) + this.#cleanupOldExecutions() + return Math.max(0, this.getLimit() - this.#state.executionTimes.length) } /** @@ -370,7 +371,7 @@ export class AsyncRateLimiter { if (this.getRemainingInWindow() > 0) { return 0 } - const oldestExecution = this._state.executionTimes[0] ?? Infinity + const oldestExecution = this.#state.executionTimes[0] ?? Infinity return oldestExecution + this.getWindow() - Date.now() } @@ -378,42 +379,42 @@ export class AsyncRateLimiter { * Returns the number of times the function has been executed */ getSuccessCount(): number { - return this._state.successCount + return this.#state.successCount } /** * Returns the number of times the function has been settled */ getSettleCount(): number { - return this._state.settleCount + return this.#state.settleCount } /** * Returns the number of times the function has errored */ getErrorCount(): number { - return this._state.errorCount + return this.#state.errorCount } /** * Returns the number of times the function has been rejected */ getRejectionCount(): number { - return this._state.rejectionCount + return this.#state.rejectionCount } /** * Returns whether the function is currently executing */ getIsExecuting(): boolean { - return this._state.isExecuting + return this.#state.isExecuting } /** * Resets the rate limiter state */ reset(): void { - this.setState({ + this.#setState({ executionTimes: [], rejectionCount: 0, errorCount: 0, @@ -443,6 +444,12 @@ export class AsyncRateLimiter { * - 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 * + * State Management: + * - Use `initialState` to provide initial state values when creating the rate limiter + * - `initialState` can be a partial state object or a Promise that resolves to a partial state + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution times, success/error counts, and current execution status + * * 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. * diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index 0774de289..8b8ab8408 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -1,5 +1,4 @@ import { parseFunctionOrValue } from './utils' -import type { Persister } from '@tanstack/persister/persister' import type { AnyFunction } from './types' /** @@ -7,8 +6,8 @@ import type { AnyFunction } from './types' */ export interface RateLimiterState { executionCount: number - rejectionCount: number executionTimes: Array + rejectionCount: number } /** @@ -20,6 +19,10 @@ export interface RateLimiterOptions { * Defaults to true. */ enabled?: boolean | ((rateLimiter: RateLimiter) => boolean) + /** + * Initial state for the rate limiter + */ + initialState?: Partial /** * Maximum number of executions allowed within the time window. * Can be a number or a callback function that receives the rate limiter instance and returns a number. @@ -33,6 +36,10 @@ export interface RateLimiterOptions { * Optional callback function that is called when an execution is rejected due to rate limiting */ onReject?: (rateLimiter: RateLimiter) => void + /** + * Callback function that is called when the state of the rate limiter is updated + */ + onStateChange?: (state: RateLimiterState) => void /** * Time window in milliseconds within which the limit applies. * Can be a number or a callback function that receives the rate limiter instance and returns a number. @@ -45,15 +52,11 @@ export interface RateLimiterOptions { * Defaults to 'fixed' */ windowType?: 'fixed' | 'sliding' - /** - * Optional persister for saving/loading rate limiter state - */ - persister?: Persister } const defaultOptions: Omit< Required>, - 'persister' | 'onExecute' | 'onReject' + 'initialState' | 'onStateChange' | 'onExecute' | 'onReject' > = { enabled: true, limit: 1, @@ -81,11 +84,21 @@ const defaultOptions: Omit< * 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. * + * State Management: + * - Use `initialState` to provide initial state values when creating the rate limiter + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution count, execution times, and rejection count + * - State can be retrieved using `getState()` method + * * @example * ```ts * const rateLimiter = new RateLimiter( * (id: string) => api.getData(id), - * { limit: 5, window: 1000, windowType: 'sliding' } // 5 calls per second with sliding window + * { + * limit: 5, + * window: 1000, + * windowType: 'sliding', + * } * ); * * // Will execute immediately until limit reached, then block @@ -93,85 +106,75 @@ const defaultOptions: Omit< * ``` */ export class RateLimiter { - private _state: RateLimiterState = { + #options: RateLimiterOptions + #state: RateLimiterState = { executionCount: 0, - rejectionCount: 0, executionTimes: [], + rejectionCount: 0, } - private _options: RateLimiterOptions - private _persister?: Persister constructor( private fn: TFn, initialOptions: RateLimiterOptions, ) { - this._options = { + this.#options = { ...defaultOptions, ...initialOptions, } - this._persister = this._options.persister - if (this._persister) { - // Load state - const loadedState = this._persister.loadState() - if (loadedState) { - this.setState(loadedState, false) - } + this.#state = { + ...this.#state, + ...this.#options.initialState, } } /** - * Returns the current state for persistence + * Updates the rate limiter options */ - getState(): RateLimiterState { - return { ...this._state } + setOptions(newOptions: Partial>): void { + this.#options = { ...this.#options, ...newOptions } } /** - * Loads state from a persisted object or updates state with a partial + * Returns the current rate limiter options */ - private setState( - state: Partial, - save: boolean = true, - ): void { - this._state = { ...this._state, ...state } - if (save) { - this._persister?.saveState(this.getState()) - } + getOptions(): Required> { + return this.#options as Required> } /** - * Updates the rate limiter options + * Returns the current state for persistence */ - setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + getState(): RateLimiterState { + return { ...this.#state } } /** - * Returns the current rate limiter options + * Loads state from a persisted object or updates state with a partial */ - getOptions(): Required> { - return this._options as Required> + #setState(state: Partial): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state) } /** * Returns the current enabled state of the rate limiter */ getEnabled(): boolean { - return parseFunctionOrValue(this._options.enabled, this)! + return parseFunctionOrValue(this.#options.enabled, this)! } /** * Returns the current limit of executions allowed within the time window */ getLimit(): number { - return parseFunctionOrValue(this._options.limit, this) + return parseFunctionOrValue(this.#options.limit, this) } /** * Returns the current time window in milliseconds */ getWindow(): number { - return parseFunctionOrValue(this._options.window, this) + return parseFunctionOrValue(this.#options.window, this) } /** @@ -190,53 +193,53 @@ export class RateLimiter { * ``` */ maybeExecute(...args: Parameters): boolean { - this.cleanupOldExecutions() + this.#cleanupOldExecutions() - if (this._options.windowType === 'sliding') { + if (this.#options.windowType === 'sliding') { // For sliding window, we can execute if we have capacity in the current window - if (this._state.executionTimes.length < this.getLimit()) { - this.execute(...args) + if (this.#state.executionTimes.length < this.getLimit()) { + this.#execute(...args) return true } } else { // For fixed window, we need to check if we're in a new window const now = Date.now() - const oldestExecution = Math.min(...this._state.executionTimes) + const oldestExecution = Math.min(...this.#state.executionTimes) const isNewWindow = oldestExecution + this.getWindow() <= now - if (isNewWindow || this._state.executionTimes.length < this.getLimit()) { - this.execute(...args) + if (isNewWindow || this.#state.executionTimes.length < this.getLimit()) { + this.#execute(...args) return true } } - this.rejectFunction() + this.#rejectFunction() return false } - private execute(...args: Parameters): void { + #execute(...args: Parameters): void { if (!this.getEnabled()) return const now = Date.now() this.fn(...args) // EXECUTE! - this._state.executionTimes.push(now) // mutate state directly for performance - this.setState({ - executionCount: this._state.executionCount + 1, + this.#state.executionTimes.push(now) // mutate state directly for performance + this.#setState({ + executionCount: this.#state.executionCount + 1, }) - this._options.onExecute?.(this) + this.#options.onExecute?.(this) } - private rejectFunction(): void { - this.setState({ - rejectionCount: this._state.rejectionCount + 1, + #rejectFunction(): void { + this.#setState({ + rejectionCount: this.#state.rejectionCount + 1, }) - this._options.onReject?.(this) + this.#options.onReject?.(this) } - private cleanupOldExecutions(): void { + #cleanupOldExecutions(): void { const now = Date.now() const windowStart = now - this.getWindow() - this.setState({ - executionTimes: this._state.executionTimes.filter( + this.#setState({ + executionTimes: this.#state.executionTimes.filter( (time) => time > windowStart, ), }) @@ -246,22 +249,22 @@ export class RateLimiter { * Returns the number of times the function has been executed */ getExecutionCount(): number { - return this._state.executionCount + return this.#state.executionCount } /** * Returns the number of times the function has been rejected */ getRejectionCount(): number { - return this._state.rejectionCount + return this.#state.rejectionCount } /** * Returns the number of remaining executions allowed in the current window */ getRemainingInWindow(): number { - this.cleanupOldExecutions() - return Math.max(0, this.getLimit() - this._state.executionTimes.length) + this.#cleanupOldExecutions() + return Math.max(0, this.getLimit() - this.#state.executionTimes.length) } /** @@ -271,7 +274,7 @@ export class RateLimiter { if (this.getRemainingInWindow() > 0) { return 0 } - const oldestExecution = this._state.executionTimes[0] ?? Infinity + const oldestExecution = this.#state.executionTimes[0] ?? Infinity return oldestExecution + this.getWindow() - Date.now() } @@ -279,7 +282,7 @@ export class RateLimiter { * Resets the rate limiter state */ reset(): void { - this.setState({ + this.#setState({ executionTimes: [], executionCount: 0, rejectionCount: 0, @@ -301,6 +304,11 @@ export class RateLimiter { * - 'sliding': A rolling window that allows executions as old ones expire. This provides a more * consistent rate of execution over time. * + * State Management: + * - Use `initialState` to provide initial state values when creating the rate limiter + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution count, execution times, and rejection count + * * 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. * diff --git a/packages/persister/CHANGELOG.md b/packages/persister/CHANGELOG.md index 043e5dc75..827695e12 100644 --- a/packages/persister/CHANGELOG.md +++ b/packages/persister/CHANGELOG.md @@ -1,2 +1 @@ # @tanstack/persister - diff --git a/packages/react-persister/package.json b/packages/react-persister/package.json index fe9bbcfa4..77bac1306 100644 --- a/packages/react-persister/package.json +++ b/packages/react-persister/package.json @@ -16,11 +16,12 @@ }, "keywords": [ "react", - "debounce", - "throttle", - "rate-limit", - "queue", - "async" + "persister", + "persist", + "storage", + "localstorage", + "sessionstorage", + "indexeddb" ], "type": "module", "types": "dist/esm/index.d.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d5cad020..7a9ddd856 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1270,11 +1270,7 @@ importers: specifier: ^2.11.6 version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - packages/pacer: - devDependencies: - '@tanstack/persister': - specifier: workspace:* - version: link:../persister + packages/pacer: {} packages/persister: {} diff --git a/scripts/generateDocs.js b/scripts/generateDocs.js index 5a0834a40..9ec0a4090 100644 --- a/scripts/generateDocs.js +++ b/scripts/generateDocs.js @@ -15,6 +15,12 @@ const packages = [ tsconfig: resolve(__dirname, '../packages/pacer/tsconfig.docs.json'), outputDir: resolve(__dirname, '../docs/reference'), }, + { + name: 'persister', + entryPoints: [resolve(__dirname, '../packages/persister/src/index.ts')], + tsconfig: resolve(__dirname, '../packages/persister/tsconfig.docs.json'), + outputDir: resolve(__dirname, '../docs/reference'), + }, { name: 'react-pacer', entryPoints: [resolve(__dirname, '../packages/react-pacer/src/index.ts')], @@ -22,6 +28,18 @@ const packages = [ outputDir: resolve(__dirname, '../docs/framework/react/reference'), exclude: ['packages/pacer/**/*'], }, + { + name: 'react-persister', + entryPoints: [ + resolve(__dirname, '../packages/react-persister/src/index.ts'), + ], + tsconfig: resolve( + __dirname, + '../packages/react-persister/tsconfig.docs.json', + ), + outputDir: resolve(__dirname, '../docs/framework/react/reference'), + exclude: ['packages/persister/**/*'], + }, { name: 'solid-pacer', entryPoints: [resolve(__dirname, '../packages/solid-pacer/src/index.ts')], From 61746345a21d858a1c90e9d3253d690db45e6f4c Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 19 Jun 2025 21:27:25 -0500 Subject: [PATCH 07/51] split storage persister to own import section --- .cursor/rules/commands.mdc | 6 + .../functions/uselocalstoragestate.md | 2 +- .../functions/usesessionstoragestate.md | 2 +- .../functions/usestoragepersister.md | 2 +- docs/reference/classes/persister.md | 10 +- docs/reference/classes/storagepersister.md | 27 +-- docs/reference/interfaces/persistedstorage.md | 8 +- .../interfaces/storagepersisteroptions.md | 48 ++++- .../react/useAsyncRateLimiter/src/index.tsx | 2 +- examples/react/useRateLimiter/src/index.tsx | 2 +- knip.json | 2 +- packages/persister/package.json | 10 + packages/persister/src/index.ts | 1 + packages/persister/src/persister.ts | 174 ---------------- packages/persister/src/storage-persister.ts | 189 ++++++++++++++++++ packages/persister/tests/persister.test.ts | 2 +- packages/react-persister/package.json | 10 + packages/react-persister/src/index.ts | 4 +- .../react-persister/src/persister/index.ts | 3 - .../src/storage-persister/index.ts | 4 + .../useStoragePersister.ts | 6 +- .../useStorageState.ts | 2 +- packages/react-persister/vite.config.ts | 1 + 23 files changed, 295 insertions(+), 222 deletions(-) create mode 100644 .cursor/rules/commands.mdc create mode 100644 packages/persister/src/storage-persister.ts create mode 100644 packages/react-persister/src/storage-persister/index.ts rename packages/react-persister/src/{persister => storage-persister}/useStoragePersister.ts (69%) rename packages/react-persister/src/{persister => storage-persister}/useStorageState.ts (98%) diff --git a/.cursor/rules/commands.mdc b/.cursor/rules/commands.mdc new file mode 100644 index 000000000..8fbc97816 --- /dev/null +++ b/.cursor/rules/commands.mdc @@ -0,0 +1,6 @@ +--- +description: running commands +globs: +alwaysApply: false +--- +- use pnpm instead of npm \ No newline at end of file diff --git a/docs/framework/react/reference/functions/uselocalstoragestate.md b/docs/framework/react/reference/functions/uselocalstoragestate.md index 7141b0ae3..7705293d6 100644 --- a/docs/framework/react/reference/functions/uselocalstoragestate.md +++ b/docs/framework/react/reference/functions/uselocalstoragestate.md @@ -14,7 +14,7 @@ function useLocalStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: [useStorageState.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/persister/useStorageState.ts#L46) +Defined in: useStorageState.ts:46 A hook that persists state to localStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usesessionstoragestate.md b/docs/framework/react/reference/functions/usesessionstoragestate.md index 8d04ca23c..f03b64513 100644 --- a/docs/framework/react/reference/functions/usesessionstoragestate.md +++ b/docs/framework/react/reference/functions/usesessionstoragestate.md @@ -14,7 +14,7 @@ function useSessionStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: [useStorageState.ts:69](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/persister/useStorageState.ts#L69) +Defined in: useStorageState.ts:69 A hook that persists state to sessionStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usestoragepersister.md b/docs/framework/react/reference/functions/usestoragepersister.md index 42da73edb..3c2889726 100644 --- a/docs/framework/react/reference/functions/usestoragepersister.md +++ b/docs/framework/react/reference/functions/usestoragepersister.md @@ -11,7 +11,7 @@ title: useStoragePersister function useStoragePersister(options): StoragePersister ``` -Defined in: [useStoragePersister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/persister/useStoragePersister.ts#L6) +Defined in: useStoragePersister.ts:6 ## Type Parameters diff --git a/docs/reference/classes/persister.md b/docs/reference/classes/persister.md index 16fdd0dc9..5ff0065ca 100644 --- a/docs/reference/classes/persister.md +++ b/docs/reference/classes/persister.md @@ -7,7 +7,7 @@ title: Persister # Class: `abstract` Persister\ -Defined in: [persister.ts:25](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L25) +Defined in: [persister.ts:23](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L23) Abstract class that defines the contract for a state persister implementation. A persister is responsible for loading and saving state to a storage medium. @@ -47,7 +47,7 @@ class MyPersister extends Persister { new Persister(key): Persister ``` -Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) +Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L24) #### Parameters @@ -67,7 +67,7 @@ Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packag readonly key: string; ``` -Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) +Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L24) ## Methods @@ -77,7 +77,7 @@ Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packag abstract loadState(): undefined | TState ``` -Defined in: [persister.ts:28](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L28) +Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) #### Returns @@ -91,7 +91,7 @@ Defined in: [persister.ts:28](https://github.com/TanStack/pacer/blob/main/packag abstract saveState(state): void ``` -Defined in: [persister.ts:29](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L29) +Defined in: [persister.ts:27](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L27) #### Parameters diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md index 2a1bac972..b64c43228 100644 --- a/docs/reference/classes/storagepersister.md +++ b/docs/reference/classes/storagepersister.md @@ -7,7 +7,7 @@ title: StoragePersister # Class: StoragePersister\ -Defined in: [persister.ts:133](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L133) +Defined in: storage-persister.ts:116 A persister that saves state to browser local/session storage. @@ -23,20 +23,23 @@ Optionally, callbacks can be provided to run after state is saved or loaded. ```ts const persister = new StoragePersister({ + // required key: 'my-rate-limiter', storage: window.localStorage, + // optional buster: 'v2', maxAge: 1000 * 60 * 60, // 1 hour + stateTransform: (state) => ({ + // Only persist specific parts of the state + count: state.count, + lastReset: state.lastReset, + // Exclude sensitive or temporary data + }), onSaveState: (key, state) => console.log('State saved:', key, state), onLoadState: (key, state) => console.log('State loaded:', key, state), onLoadStateError: (key, error) => console.error('Error loading state:', key, error), onSaveStateError: (key, error) => console.error('Error saving state:', key, error) }) -const rateLimiter = new RateLimiter(fn, { - persister, - limit: 5, - window: 1000 -}) ``` ## Extends @@ -55,7 +58,7 @@ const rateLimiter = new RateLimiter(fn, { new StoragePersister(options): StoragePersister ``` -Defined in: [persister.ts:135](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L135) +Defined in: storage-persister.ts:119 #### Parameters @@ -79,7 +82,7 @@ Defined in: [persister.ts:135](https://github.com/TanStack/pacer/blob/main/packa readonly key: string; ``` -Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) +Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L24) #### Inherited from @@ -93,7 +96,7 @@ Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packag getOptions(): StoragePersisterOptions ``` -Defined in: [persister.ts:153](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L153) +Defined in: storage-persister.ts:137 Returns the current persister options @@ -109,7 +112,7 @@ Returns the current persister options loadState(): undefined | TState ``` -Defined in: [persister.ts:174](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L174) +Defined in: storage-persister.ts:161 #### Returns @@ -127,7 +130,7 @@ Defined in: [persister.ts:174](https://github.com/TanStack/pacer/blob/main/packa saveState(state): void ``` -Defined in: [persister.ts:157](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L157) +Defined in: storage-persister.ts:141 #### Parameters @@ -151,7 +154,7 @@ Defined in: [persister.ts:157](https://github.com/TanStack/pacer/blob/main/packa setOptions(newOptions): void ``` -Defined in: [persister.ts:146](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L146) +Defined in: storage-persister.ts:130 Updates the persister options diff --git a/docs/reference/interfaces/persistedstorage.md b/docs/reference/interfaces/persistedstorage.md index c6897bd7b..9b2da6dfb 100644 --- a/docs/reference/interfaces/persistedstorage.md +++ b/docs/reference/interfaces/persistedstorage.md @@ -7,7 +7,7 @@ title: PersistedStorage # Interface: PersistedStorage\ -Defined in: [persister.ts:32](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L32) +Defined in: storage-persister.ts:4 ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [persister.ts:32](https://github.com/TanStack/pacer/blob/main/packag optional buster: string; ``` -Defined in: [persister.ts:33](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L33) +Defined in: storage-persister.ts:5 *** @@ -31,7 +31,7 @@ Defined in: [persister.ts:33](https://github.com/TanStack/pacer/blob/main/packag state: undefined | TState; ``` -Defined in: [persister.ts:34](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L34) +Defined in: storage-persister.ts:6 *** @@ -41,4 +41,4 @@ Defined in: [persister.ts:34](https://github.com/TanStack/pacer/blob/main/packag timestamp: number; ``` -Defined in: [persister.ts:35](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L35) +Defined in: storage-persister.ts:7 diff --git a/docs/reference/interfaces/storagepersisteroptions.md b/docs/reference/interfaces/storagepersisteroptions.md index 1ed5704f7..db447ffa7 100644 --- a/docs/reference/interfaces/storagepersisteroptions.md +++ b/docs/reference/interfaces/storagepersisteroptions.md @@ -7,7 +7,7 @@ title: StoragePersisterOptions # Interface: StoragePersisterOptions\ -Defined in: [persister.ts:44](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L44) +Defined in: storage-persister.ts:16 Configuration options for creating a browser-based state persister. @@ -26,7 +26,7 @@ sessionStorage (cleared when browser tab/window closes) to store serialized stat optional buster: string; ``` -Defined in: [persister.ts:49](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L49) +Defined in: storage-persister.ts:21 A version string used to invalidate cached state. When changed, any existing stored state will be considered invalid and cleared. @@ -39,7 +39,7 @@ stored state will be considered invalid and cleared. optional deserializer: (state) => PersistedStorage; ``` -Defined in: [persister.ts:54](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L54) +Defined in: storage-persister.ts:26 Optional function to customize how state is deserialized after loading from storage. By default, JSON.parse is used. @@ -62,7 +62,7 @@ By default, JSON.parse is used. key: string; ``` -Defined in: [persister.ts:58](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L58) +Defined in: storage-persister.ts:30 Unique identifier used as the storage key for persisting state. @@ -74,7 +74,7 @@ Unique identifier used as the storage key for persisting state. optional maxAge: number; ``` -Defined in: [persister.ts:63](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L63) +Defined in: storage-persister.ts:35 Maximum age in milliseconds before stored state is considered expired. When exceeded, the state will be cleared and treated as if it doesn't exist. @@ -87,7 +87,7 @@ When exceeded, the state will be cleared and treated as if it doesn't exist. optional onLoadState: (state) => void; ``` -Defined in: [persister.ts:67](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L67) +Defined in: storage-persister.ts:39 Optional callback that runs after state is successfully loaded. @@ -109,7 +109,7 @@ Optional callback that runs after state is successfully loaded. optional onLoadStateError: (error) => void; ``` -Defined in: [persister.ts:71](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L71) +Defined in: storage-persister.ts:43 Optional callback that runs after state is unable to be loaded. @@ -131,7 +131,7 @@ Optional callback that runs after state is unable to be loaded. optional onSaveState: (state) => void; ``` -Defined in: [persister.ts:75](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L75) +Defined in: storage-persister.ts:47 Optional callback that runs after state is successfully saved. @@ -153,7 +153,7 @@ Optional callback that runs after state is successfully saved. optional onSaveStateError: (error) => void; ``` -Defined in: [persister.ts:80](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L80) +Defined in: storage-persister.ts:52 Optional callback that runs after state is unable to be saved. For example, if the storage is full (localStorage >= 5MB) @@ -176,7 +176,7 @@ For example, if the storage is full (localStorage >= 5MB) optional serializer: (state) => string; ``` -Defined in: [persister.ts:85](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L85) +Defined in: storage-persister.ts:57 Optional function to customize how state is serialized before saving to storage. By default, JSON.stringify is used. @@ -193,13 +193,39 @@ By default, JSON.stringify is used. *** +### stateTransform()? + +```ts +optional stateTransform: (state) => Partial; +``` + +Defined in: storage-persister.ts:65 + +Optional function to filter which parts of the state are persisted and loaded, or to otherwise transform the state before saving or loading. +When provided, only the filtered state will be saved to storage and returned when loading. +This is useful for excluding sensitive or temporary data from persistence. + +Note: Don't use this to replace the serialization. Use the `serializer` option instead for that. + +#### Parameters + +##### state + +`TState` + +#### Returns + +`Partial`\<`TState`\> + +*** + ### storage ```ts storage: Storage; ``` -Defined in: [persister.ts:90](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L90) +Defined in: storage-persister.ts:70 The browser storage implementation to use for persisting state. Typically window.localStorage or window.sessionStorage. diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index be5fb81f5..74949e3fa 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import ReactDOM from 'react-dom/client' import { useAsyncRateLimiter } from '@tanstack/react-pacer/async-rate-limiter' -import { useStoragePersister } from '@tanstack/react-persister/persister' +import { useStoragePersister } from '@tanstack/react-persister/storage-persister' import type { AsyncRateLimiterState } from '@tanstack/react-pacer/async-rate-limiter' interface SearchResult { diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index ef24e595f..aa3c8c240 100644 --- a/examples/react/useRateLimiter/src/index.tsx +++ b/examples/react/useRateLimiter/src/index.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useRateLimiter } from '@tanstack/react-pacer/rate-limiter' -import { useStoragePersister } from '@tanstack/react-persister/persister' +import { useStoragePersister } from '@tanstack/react-persister/storage-persister' import type { RateLimiterState } from '@tanstack/react-pacer/rate-limiter' function App1() { diff --git a/knip.json b/knip.json index e4fc4b1c1..dfc6c27a8 100644 --- a/knip.json +++ b/knip.json @@ -1,6 +1,6 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", - "ignoreDependencies": ["@size-limit/preset-small-lib", "@faker-js/faker"], + "ignoreDependencies": ["@faker-js/faker"], "ignoreWorkspaces": ["examples/**"], "workspaces": { "packages/react-pacer": { diff --git a/packages/persister/package.json b/packages/persister/package.json index e628da8c2..eda886cfe 100644 --- a/packages/persister/package.json +++ b/packages/persister/package.json @@ -67,6 +67,16 @@ "default": "./dist/cjs/persister.cjs" } }, + "./storage-persister": { + "import": { + "types": "./dist/esm/storage-persister.d.ts", + "default": "./dist/esm/storage-persister.js" + }, + "require": { + "types": "./dist/cjs/storage-persister.d.cts", + "default": "./dist/cjs/storage-persister.cjs" + } + }, "./types": { "types": "./dist/esm/types.d.ts" }, diff --git a/packages/persister/src/index.ts b/packages/persister/src/index.ts index 453c356ab..572726e66 100644 --- a/packages/persister/src/index.ts +++ b/packages/persister/src/index.ts @@ -1,5 +1,6 @@ export * from './async-persister' export * from './compare' export * from './persister' +export * from './storage-persister' export * from './types' export * from './utils' diff --git a/packages/persister/src/persister.ts b/packages/persister/src/persister.ts index 7cf2d67a8..7d324cc67 100644 --- a/packages/persister/src/persister.ts +++ b/packages/persister/src/persister.ts @@ -1,5 +1,3 @@ -import type { RequiredKeys } from './types' - /** * Abstract class that defines the contract for a state persister implementation. * A persister is responsible for loading and saving state to a storage medium. @@ -28,175 +26,3 @@ export abstract class Persister { abstract loadState(): TState | undefined abstract saveState(state: TState): void } - -export interface PersistedStorage { - buster?: string - state: TState | undefined - timestamp: number -} - -/** - * Configuration options for creating a browser-based state persister. - * - * The persister can use either localStorage (persists across browser sessions) or - * sessionStorage (cleared when browser tab/window closes) to store serialized state. - */ -export interface StoragePersisterOptions { - /** - * A version string used to invalidate cached state. When changed, any existing - * stored state will be considered invalid and cleared. - */ - buster?: string - /** - * Optional function to customize how state is deserialized after loading from storage. - * By default, JSON.parse is used. - */ - deserializer?: (state: string) => PersistedStorage - /** - * Unique identifier used as the storage key for persisting state. - */ - key: string - /** - * Maximum age in milliseconds before stored state is considered expired. - * When exceeded, the state will be cleared and treated as if it doesn't exist. - */ - maxAge?: number - /** - * Optional callback that runs after state is successfully loaded. - */ - onLoadState?: (state: TState | undefined) => void - /** - * Optional callback that runs after state is unable to be loaded. - */ - onLoadStateError?: (error: Error) => void - /** - * Optional callback that runs after state is successfully saved. - */ - onSaveState?: (state: TState) => void - /** - * Optional callback that runs after state is unable to be saved. - * For example, if the storage is full (localStorage >= 5MB) - */ - onSaveStateError?: (error: Error) => void - /** - * Optional function to customize how state is serialized before saving to storage. - * By default, JSON.stringify is used. - */ - serializer?: (state: PersistedStorage) => string - /** - * The browser storage implementation to use for persisting state. - * Typically window.localStorage or window.sessionStorage. - */ - storage: Storage -} - -type DefaultOptions = RequiredKeys< - Partial>, - 'deserializer' | 'serializer' -> - -const defaultOptions: DefaultOptions = { - deserializer: JSON.parse, - serializer: JSON.stringify, -} - -/** - * A persister that saves state to browser local/session storage. - * - * The persister can use either localStorage (persists across browser sessions) or - * sessionStorage (cleared when browser tab/window closes). State is automatically - * serialized to JSON when saving and deserialized when loading. - * - * Optionally, a `buster` string can be provided to force cache busting by storing it in the value. - * Optionally, a `maxAge` (in ms) can be provided to expire the stored state after a certain duration. - * Optionally, callbacks can be provided to run after state is saved or loaded. - * - * @example - * ```ts - * const persister = new StoragePersister({ - * key: 'my-rate-limiter', - * storage: window.localStorage, - * buster: 'v2', - * maxAge: 1000 * 60 * 60, // 1 hour - * onSaveState: (key, state) => console.log('State saved:', key, state), - * onLoadState: (key, state) => console.log('State loaded:', key, state), - * onLoadStateError: (key, error) => console.error('Error loading state:', key, error), - * onSaveStateError: (key, error) => console.error('Error saving state:', key, error) - * }) - * const rateLimiter = new RateLimiter(fn, { - * persister, - * limit: 5, - * window: 1000 - * }) - * ``` - */ -export class StoragePersister extends Persister { - private _options: StoragePersisterOptions & DefaultOptions - constructor(options: StoragePersisterOptions) { - super(options.key) - this._options = { - ...defaultOptions, - ...options, - } - } - - /** - * Updates the persister options - */ - setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } - } - - /** - * Returns the current persister options - */ - getOptions(): StoragePersisterOptions { - return this._options - } - - saveState(state: TState): void { - try { - this._options.storage.setItem( - this.key, - this._options.serializer({ - buster: this._options.buster, - state, - timestamp: Date.now(), - }), - ) - this._options.onSaveState?.(state) - } catch (error) { - console.error(error) - this._options.onSaveStateError?.(error as Error) - } - } - - loadState(): TState | undefined { - const stored = this._options.storage.getItem(this.key) - if (!stored) { - return undefined - } - - try { - const parsed = this._options.deserializer(stored) - const isValid = - !this._options.buster || parsed.buster === this._options.buster - const isNotExpired = - !this._options.maxAge || - !parsed.timestamp || - Date.now() - parsed.timestamp <= this._options.maxAge - - if (!isValid || !isNotExpired) { - return undefined - } - - const state = parsed.state as TState - this._options.onLoadState?.(state) - return state - } catch (error) { - console.error(error) - this._options.onLoadStateError?.(error as Error) - return undefined - } - } -} diff --git a/packages/persister/src/storage-persister.ts b/packages/persister/src/storage-persister.ts new file mode 100644 index 000000000..fd2646e6c --- /dev/null +++ b/packages/persister/src/storage-persister.ts @@ -0,0 +1,189 @@ +import { Persister } from './persister' +import type { RequiredKeys } from './types' + +export interface PersistedStorage { + buster?: string + state: TState | undefined + timestamp: number +} + +/** + * Configuration options for creating a browser-based state persister. + * + * The persister can use either localStorage (persists across browser sessions) or + * sessionStorage (cleared when browser tab/window closes) to store serialized state. + */ +export interface StoragePersisterOptions { + /** + * A version string used to invalidate cached state. When changed, any existing + * stored state will be considered invalid and cleared. + */ + buster?: string + /** + * Optional function to customize how state is deserialized after loading from storage. + * By default, JSON.parse is used. + */ + deserializer?: (state: string) => PersistedStorage + /** + * Unique identifier used as the storage key for persisting state. + */ + key: string + /** + * Maximum age in milliseconds before stored state is considered expired. + * When exceeded, the state will be cleared and treated as if it doesn't exist. + */ + maxAge?: number + /** + * Optional callback that runs after state is successfully loaded. + */ + onLoadState?: (state: TState | undefined) => void + /** + * Optional callback that runs after state is unable to be loaded. + */ + onLoadStateError?: (error: Error) => void + /** + * Optional callback that runs after state is successfully saved. + */ + onSaveState?: (state: TState) => void + /** + * Optional callback that runs after state is unable to be saved. + * For example, if the storage is full (localStorage >= 5MB) + */ + onSaveStateError?: (error: Error) => void + /** + * Optional function to customize how state is serialized before saving to storage. + * By default, JSON.stringify is used. + */ + serializer?: (state: PersistedStorage) => string + /** + * Optional function to filter which parts of the state are persisted and loaded, or to otherwise transform the state before saving or loading. + * When provided, only the filtered state will be saved to storage and returned when loading. + * This is useful for excluding sensitive or temporary data from persistence. + * + * Note: Don't use this to replace the serialization. Use the `serializer` option instead for that. + */ + stateTransform?: (state: TState) => Partial + /** + * The browser storage implementation to use for persisting state. + * Typically window.localStorage or window.sessionStorage. + */ + storage: Storage +} + +type DefaultOptions = RequiredKeys< + Partial>, + 'deserializer' | 'serializer' +> + +const defaultOptions: DefaultOptions = { + deserializer: JSON.parse, + serializer: JSON.stringify, +} + +/** + * A persister that saves state to browser local/session storage. + * + * The persister can use either localStorage (persists across browser sessions) or + * sessionStorage (cleared when browser tab/window closes). State is automatically + * serialized to JSON when saving and deserialized when loading. + * + * Optionally, a `buster` string can be provided to force cache busting by storing it in the value. + * Optionally, a `maxAge` (in ms) can be provided to expire the stored state after a certain duration. + * Optionally, callbacks can be provided to run after state is saved or loaded. + * + * @example + * ```ts + * const persister = new StoragePersister({ + * // required + * key: 'my-rate-limiter', + * storage: window.localStorage, + * // optional + * buster: 'v2', + * maxAge: 1000 * 60 * 60, // 1 hour + * stateTransform: (state) => ({ + * // Only persist specific parts of the state + * count: state.count, + * lastReset: state.lastReset, + * // Exclude sensitive or temporary data + * }), + * onSaveState: (key, state) => console.log('State saved:', key, state), + * onLoadState: (key, state) => console.log('State loaded:', key, state), + * onLoadStateError: (key, error) => console.error('Error loading state:', key, error), + * onSaveStateError: (key, error) => console.error('Error saving state:', key, error) + * }) + * ``` + */ +export class StoragePersister extends Persister { + private _options: StoragePersisterOptions & DefaultOptions + + constructor(options: StoragePersisterOptions) { + super(options.key) + this._options = { + ...defaultOptions, + ...options, + } + } + + /** + * Updates the persister options + */ + setOptions(newOptions: Partial>): void { + this._options = { ...this._options, ...newOptions } + } + + /** + * Returns the current persister options + */ + getOptions(): StoragePersisterOptions { + return this._options + } + + saveState(state: TState): void { + try { + const stateToSave = this._options.stateTransform + ? this._options.stateTransform(state) + : state + this._options.storage.setItem( + this.key, + this._options.serializer({ + buster: this._options.buster, + state: stateToSave, + timestamp: Date.now(), + }), + ) + this._options.onSaveState?.(state) + } catch (error) { + console.error(error) + this._options.onSaveStateError?.(error as Error) + } + } + + loadState(): TState | undefined { + const stored = this._options.storage.getItem(this.key) + if (!stored) { + return undefined + } + + try { + const parsed = this._options.deserializer(stored) + const isValid = + !this._options.buster || parsed.buster === this._options.buster + const isNotExpired = + !this._options.maxAge || + !parsed.timestamp || + Date.now() - parsed.timestamp <= this._options.maxAge + + if (!isValid || !isNotExpired) { + return undefined + } + + const state = parsed.state as TState + this._options.onLoadState?.(state) + return state + } catch (error) { + console.error(error) + this._options.onLoadStateError?.(error as Error) + return undefined + } + } +} diff --git a/packages/persister/tests/persister.test.ts b/packages/persister/tests/persister.test.ts index cea69f71d..8ebef9f1b 100644 --- a/packages/persister/tests/persister.test.ts +++ b/packages/persister/tests/persister.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { StoragePersister } from '../src/persister' +import { StoragePersister } from '../src/storage-persister' describe('createStoragePersister', () => { let storage: Storage diff --git a/packages/react-persister/package.json b/packages/react-persister/package.json index 77bac1306..c1f2f5609 100644 --- a/packages/react-persister/package.json +++ b/packages/react-persister/package.json @@ -58,6 +58,16 @@ "default": "./dist/cjs/persister/index.cjs" } }, + "./storage-persister": { + "import": { + "types": "./dist/esm/storage-persister/index.d.ts", + "default": "./dist/esm/storage-persister/index.js" + }, + "require": { + "types": "./dist/cjs/storage-persister/index.d.cts", + "default": "./dist/cjs/storage-persister/index.cjs" + } + }, "./types": { "types": "./dist/esm/types/index.d.ts", "default": "./dist/esm/types/index.js" diff --git a/packages/react-persister/src/index.ts b/packages/react-persister/src/index.ts index 92b29c821..80c3e3afc 100644 --- a/packages/react-persister/src/index.ts +++ b/packages/react-persister/src/index.ts @@ -8,5 +8,5 @@ export * from '@tanstack/persister' // async-debouncer // persister -export * from './persister/useStoragePersister' -export * from './persister/useStorageState' +export * from './storage-persister/useStoragePersister' +export * from './storage-persister/useStorageState' diff --git a/packages/react-persister/src/persister/index.ts b/packages/react-persister/src/persister/index.ts index e77900092..358c27bd1 100644 --- a/packages/react-persister/src/persister/index.ts +++ b/packages/react-persister/src/persister/index.ts @@ -1,4 +1 @@ export * from '@tanstack/persister/persister' - -export * from './useStoragePersister' -export * from './useStorageState' diff --git a/packages/react-persister/src/storage-persister/index.ts b/packages/react-persister/src/storage-persister/index.ts new file mode 100644 index 000000000..8065353cf --- /dev/null +++ b/packages/react-persister/src/storage-persister/index.ts @@ -0,0 +1,4 @@ +export * from '@tanstack/persister/storage-persister' + +export * from './useStoragePersister' +export * from './useStorageState' diff --git a/packages/react-persister/src/persister/useStoragePersister.ts b/packages/react-persister/src/storage-persister/useStoragePersister.ts similarity index 69% rename from packages/react-persister/src/persister/useStoragePersister.ts rename to packages/react-persister/src/storage-persister/useStoragePersister.ts index 7b4ff76bf..223e1a148 100644 --- a/packages/react-persister/src/persister/useStoragePersister.ts +++ b/packages/react-persister/src/storage-persister/useStoragePersister.ts @@ -1,7 +1,7 @@ import { useState } from 'react' -import { StoragePersister } from '@tanstack/persister/persister' -import { bindInstanceMethods } from '@tanstack/persister/utils' -import type { StoragePersisterOptions } from '@tanstack/persister/persister' +import { StoragePersister } from '@tanstack/persister/storage-persister' +import { bindInstanceMethods } from '@tanstack/persister' +import type { StoragePersisterOptions } from '@tanstack/persister/storage-persister' export function useStoragePersister( options: StoragePersisterOptions, diff --git a/packages/react-persister/src/persister/useStorageState.ts b/packages/react-persister/src/storage-persister/useStorageState.ts similarity index 98% rename from packages/react-persister/src/persister/useStorageState.ts rename to packages/react-persister/src/storage-persister/useStorageState.ts index 811a49787..16c12a4b2 100644 --- a/packages/react-persister/src/persister/useStorageState.ts +++ b/packages/react-persister/src/storage-persister/useStorageState.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { useStoragePersister } from './useStoragePersister' -import type { StoragePersisterOptions } from '@tanstack/persister/persister' +import type { StoragePersisterOptions } from '@tanstack/persister/storage-persister' function useStorageState( initialValue: TValue, diff --git a/packages/react-persister/vite.config.ts b/packages/react-persister/vite.config.ts index abdc16855..40249e7be 100644 --- a/packages/react-persister/vite.config.ts +++ b/packages/react-persister/vite.config.ts @@ -23,6 +23,7 @@ export default mergeConfig( './src/compare/index.ts', './src/index.ts', './src/persister/index.ts', + './src/storage-persister/index.ts', './src/types/index.ts', './src/utils/index.ts', ], From 6b98699a9b8dce4bf1fe66731089e73e1527469f Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Jun 2025 10:47:10 -0500 Subject: [PATCH 08/51] onStateChange signature --- .../functions/uselocalstoragestate.md | 2 +- .../functions/usesessionstoragestate.md | 2 +- .../functions/usestoragepersister.md | 2 +- docs/reference/classes/storagepersister.md | 12 +++++----- docs/reference/interfaces/persistedstorage.md | 8 +++---- .../interfaces/storagepersisteroptions.md | 24 +++++++++---------- packages/pacer/src/async-rate-limiter.ts | 7 ++++-- packages/pacer/src/rate-limiter.ts | 7 ++++-- 8 files changed, 35 insertions(+), 29 deletions(-) diff --git a/docs/framework/react/reference/functions/uselocalstoragestate.md b/docs/framework/react/reference/functions/uselocalstoragestate.md index 7705293d6..1c3c1b060 100644 --- a/docs/framework/react/reference/functions/uselocalstoragestate.md +++ b/docs/framework/react/reference/functions/uselocalstoragestate.md @@ -14,7 +14,7 @@ function useLocalStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: useStorageState.ts:46 +Defined in: [useStorageState.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStorageState.ts#L46) A hook that persists state to localStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usesessionstoragestate.md b/docs/framework/react/reference/functions/usesessionstoragestate.md index f03b64513..5378cca11 100644 --- a/docs/framework/react/reference/functions/usesessionstoragestate.md +++ b/docs/framework/react/reference/functions/usesessionstoragestate.md @@ -14,7 +14,7 @@ function useSessionStorageState( options?): readonly [TValue, Dispatch>] ``` -Defined in: useStorageState.ts:69 +Defined in: [useStorageState.ts:69](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStorageState.ts#L69) A hook that persists state to sessionStorage and syncs it across tabs diff --git a/docs/framework/react/reference/functions/usestoragepersister.md b/docs/framework/react/reference/functions/usestoragepersister.md index 3c2889726..45e457d21 100644 --- a/docs/framework/react/reference/functions/usestoragepersister.md +++ b/docs/framework/react/reference/functions/usestoragepersister.md @@ -11,7 +11,7 @@ title: useStoragePersister function useStoragePersister(options): StoragePersister ``` -Defined in: useStoragePersister.ts:6 +Defined in: [useStoragePersister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStoragePersister.ts#L6) ## Type Parameters diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md index b64c43228..64c497bbc 100644 --- a/docs/reference/classes/storagepersister.md +++ b/docs/reference/classes/storagepersister.md @@ -7,7 +7,7 @@ title: StoragePersister # Class: StoragePersister\ -Defined in: storage-persister.ts:116 +Defined in: [storage-persister.ts:116](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L116) A persister that saves state to browser local/session storage. @@ -58,7 +58,7 @@ const persister = new StoragePersister({ new StoragePersister(options): StoragePersister ``` -Defined in: storage-persister.ts:119 +Defined in: [storage-persister.ts:119](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L119) #### Parameters @@ -96,7 +96,7 @@ Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packag getOptions(): StoragePersisterOptions ``` -Defined in: storage-persister.ts:137 +Defined in: [storage-persister.ts:137](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L137) Returns the current persister options @@ -112,7 +112,7 @@ Returns the current persister options loadState(): undefined | TState ``` -Defined in: storage-persister.ts:161 +Defined in: [storage-persister.ts:161](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L161) #### Returns @@ -130,7 +130,7 @@ Defined in: storage-persister.ts:161 saveState(state): void ``` -Defined in: storage-persister.ts:141 +Defined in: [storage-persister.ts:141](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L141) #### Parameters @@ -154,7 +154,7 @@ Defined in: storage-persister.ts:141 setOptions(newOptions): void ``` -Defined in: storage-persister.ts:130 +Defined in: [storage-persister.ts:130](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L130) Updates the persister options diff --git a/docs/reference/interfaces/persistedstorage.md b/docs/reference/interfaces/persistedstorage.md index 9b2da6dfb..b95aba3e3 100644 --- a/docs/reference/interfaces/persistedstorage.md +++ b/docs/reference/interfaces/persistedstorage.md @@ -7,7 +7,7 @@ title: PersistedStorage # Interface: PersistedStorage\ -Defined in: storage-persister.ts:4 +Defined in: [storage-persister.ts:4](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L4) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: storage-persister.ts:4 optional buster: string; ``` -Defined in: storage-persister.ts:5 +Defined in: [storage-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L5) *** @@ -31,7 +31,7 @@ Defined in: storage-persister.ts:5 state: undefined | TState; ``` -Defined in: storage-persister.ts:6 +Defined in: [storage-persister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L6) *** @@ -41,4 +41,4 @@ Defined in: storage-persister.ts:6 timestamp: number; ``` -Defined in: storage-persister.ts:7 +Defined in: [storage-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L7) diff --git a/docs/reference/interfaces/storagepersisteroptions.md b/docs/reference/interfaces/storagepersisteroptions.md index db447ffa7..79123bdbc 100644 --- a/docs/reference/interfaces/storagepersisteroptions.md +++ b/docs/reference/interfaces/storagepersisteroptions.md @@ -7,7 +7,7 @@ title: StoragePersisterOptions # Interface: StoragePersisterOptions\ -Defined in: storage-persister.ts:16 +Defined in: [storage-persister.ts:16](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L16) Configuration options for creating a browser-based state persister. @@ -26,7 +26,7 @@ sessionStorage (cleared when browser tab/window closes) to store serialized stat optional buster: string; ``` -Defined in: storage-persister.ts:21 +Defined in: [storage-persister.ts:21](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L21) A version string used to invalidate cached state. When changed, any existing stored state will be considered invalid and cleared. @@ -39,7 +39,7 @@ stored state will be considered invalid and cleared. optional deserializer: (state) => PersistedStorage; ``` -Defined in: storage-persister.ts:26 +Defined in: [storage-persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L26) Optional function to customize how state is deserialized after loading from storage. By default, JSON.parse is used. @@ -62,7 +62,7 @@ By default, JSON.parse is used. key: string; ``` -Defined in: storage-persister.ts:30 +Defined in: [storage-persister.ts:30](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L30) Unique identifier used as the storage key for persisting state. @@ -74,7 +74,7 @@ Unique identifier used as the storage key for persisting state. optional maxAge: number; ``` -Defined in: storage-persister.ts:35 +Defined in: [storage-persister.ts:35](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L35) Maximum age in milliseconds before stored state is considered expired. When exceeded, the state will be cleared and treated as if it doesn't exist. @@ -87,7 +87,7 @@ When exceeded, the state will be cleared and treated as if it doesn't exist. optional onLoadState: (state) => void; ``` -Defined in: storage-persister.ts:39 +Defined in: [storage-persister.ts:39](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L39) Optional callback that runs after state is successfully loaded. @@ -109,7 +109,7 @@ Optional callback that runs after state is successfully loaded. optional onLoadStateError: (error) => void; ``` -Defined in: storage-persister.ts:43 +Defined in: [storage-persister.ts:43](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L43) Optional callback that runs after state is unable to be loaded. @@ -131,7 +131,7 @@ Optional callback that runs after state is unable to be loaded. optional onSaveState: (state) => void; ``` -Defined in: storage-persister.ts:47 +Defined in: [storage-persister.ts:47](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L47) Optional callback that runs after state is successfully saved. @@ -153,7 +153,7 @@ Optional callback that runs after state is successfully saved. optional onSaveStateError: (error) => void; ``` -Defined in: storage-persister.ts:52 +Defined in: [storage-persister.ts:52](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L52) Optional callback that runs after state is unable to be saved. For example, if the storage is full (localStorage >= 5MB) @@ -176,7 +176,7 @@ For example, if the storage is full (localStorage >= 5MB) optional serializer: (state) => string; ``` -Defined in: storage-persister.ts:57 +Defined in: [storage-persister.ts:57](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L57) Optional function to customize how state is serialized before saving to storage. By default, JSON.stringify is used. @@ -199,7 +199,7 @@ By default, JSON.stringify is used. optional stateTransform: (state) => Partial; ``` -Defined in: storage-persister.ts:65 +Defined in: [storage-persister.ts:65](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L65) Optional function to filter which parts of the state are persisted and loaded, or to otherwise transform the state before saving or loading. When provided, only the filtered state will be saved to storage and returned when loading. @@ -225,7 +225,7 @@ Note: Don't use this to replace the serialization. Use the `serializer` option i storage: Storage; ``` -Defined in: storage-persister.ts:70 +Defined in: [storage-persister.ts:70](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L70) The browser storage implementation to use for persisting state. Typically window.localStorage or window.sessionStorage. diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 0a2d4b7a2..b5163983c 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -59,7 +59,10 @@ export interface AsyncRateLimiterOptions { /** * Callback function that is called when the state of the rate limiter is updated */ - onStateChange?: (state: AsyncRateLimiterState) => void + onStateChange?: ( + state: AsyncRateLimiterState, + rateLimiter: AsyncRateLimiter, + ) => void /** * Whether to throw errors when they occur. * Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -216,7 +219,7 @@ export class AsyncRateLimiter { */ #setState(state: Partial>): void { this.#state = { ...this.#state, ...state } - this.#options.onStateChange?.(this.#state) + this.#options.onStateChange?.(this.#state, this) } /** diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index 8b8ab8408..c28b89e36 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -39,7 +39,10 @@ export interface RateLimiterOptions { /** * Callback function that is called when the state of the rate limiter is updated */ - onStateChange?: (state: RateLimiterState) => void + onStateChange?: ( + state: RateLimiterState, + rateLimiter: RateLimiter, + ) => void /** * Time window in milliseconds within which the limit applies. * Can be a number or a callback function that receives the rate limiter instance and returns a number. @@ -153,7 +156,7 @@ export class RateLimiter { */ #setState(state: Partial): void { this.#state = { ...this.#state, ...state } - this.#options.onStateChange?.(this.#state) + this.#options.onStateChange?.(this.#state, this) } /** From ff76c7bd03ffbc9a8eeef6ff94f19722b1250655 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Jun 2025 10:58:17 -0500 Subject: [PATCH 09/51] refactor debouner with state --- packages/pacer/src/debouncer.ts | 123 +++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 34 deletions(-) diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index e420a4dfd..3b4e406b3 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -1,6 +1,15 @@ import { parseFunctionOrValue } from './utils' import type { AnyFunction } from './types' +/** + * State shape for persisting Debouncer + */ +export interface DebouncerState { + canLeadingExecute: boolean + executionCount: number + isPending: boolean +} + /** * Options for configuring a debounced function */ @@ -11,6 +20,10 @@ export interface DebouncerOptions { * Defaults to true. */ enabled?: boolean | ((debouncer: Debouncer) => boolean) + /** + * Initial state for the debouncer + */ + initialState?: Partial /** * Whether to execute on the leading edge of the timeout. * The first call will execute immediately and the rest will wait the delay. @@ -21,6 +34,10 @@ export interface DebouncerOptions { * Callback function that is called after the function is executed */ onExecute?: (debouncer: Debouncer) => void + /** + * Callback function that is called when the state of the debouncer is updated + */ + onStateChange?: (state: DebouncerState, debouncer: Debouncer) => void /** * Whether to execute on the trailing edge of the timeout. * Defaults to true. @@ -34,10 +51,12 @@ export interface DebouncerOptions { wait: number | ((debouncer: Debouncer) => number) } -const defaultOptions: Required> = { +const defaultOptions: Omit< + Required>, + 'initialState' | 'onStateChange' | 'onExecute' +> = { enabled: true, leading: false, - onExecute: () => {}, trailing: true, wait: 0, } @@ -53,6 +72,12 @@ const defaultOptions: Required> = { * (leading edge) or at the end (trailing edge, default). Each new call during the wait period * will reset the timer. * + * State Management: + * - Use `initialState` to provide initial state values when creating the debouncer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes canLeadingExecute, execution count, and isPending status + * - State can be retrieved using `getState()` method + * * @example * ```ts * const debouncer = new Debouncer((value: string) => { @@ -66,31 +91,37 @@ const defaultOptions: Required> = { * ``` */ export class Debouncer { - private _canLeadingExecute = true - private _executionCount = 0 - private _isPending = false - private _options: Required> - private _timeoutId: NodeJS.Timeout | undefined + #options: DebouncerOptions + #state: DebouncerState = { + canLeadingExecute: true, + executionCount: 0, + isPending: false, + } + #timeoutId: NodeJS.Timeout | undefined constructor( private fn: TFn, initialOptions: DebouncerOptions, ) { - this._options = { + this.#options = { ...defaultOptions, ...initialOptions, } + this.#state = { + ...this.#state, + ...this.#options.initialState, + } } /** * Updates the debouncer options */ setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + this.#options = { ...this.#options, ...newOptions } // End the pending state if the debouncer is disabled - if (!this._options.enabled) { - this._isPending = false + if (!this.getEnabled()) { + this.#setState({ isPending: false }) } } @@ -98,21 +129,36 @@ export class Debouncer { * Returns the current debouncer options */ getOptions(): Required> { - return this._options + return this.#options as Required> + } + + /** + * Returns the current state for persistence + */ + getState(): DebouncerState { + return { ...this.#state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + #setState(state: Partial): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state, this) } /** * Returns the current enabled state of the debouncer */ getEnabled(): boolean { - return parseFunctionOrValue(this._options.enabled, this) + return parseFunctionOrValue(this.#options.enabled, this)! } /** * Returns the current wait time in milliseconds */ getWait(): number { - return parseFunctionOrValue(this._options.wait, this) + return parseFunctionOrValue(this.#options.wait, this) } /** @@ -123,45 +169,49 @@ export class Debouncer { let _didLeadingExecute = false // Handle leading execution - if (this._options.leading && this._canLeadingExecute) { - this._canLeadingExecute = false + if (this.#options.leading && this.#state.canLeadingExecute) { + this.#setState({ canLeadingExecute: false }) _didLeadingExecute = true - this.execute(...args) + this.#execute(...args) } // Start pending state to indicate that the debouncer is waiting for the trailing edge - if (this._options.trailing) { - this._isPending = true + if (this.#options.trailing) { + this.#setState({ isPending: true }) } // Clear any existing timeout - if (this._timeoutId) clearTimeout(this._timeoutId) + if (this.#timeoutId) clearTimeout(this.#timeoutId) // Set new timeout that will reset canLeadingExecute and execute trailing only if enabled and did not execute leading - this._timeoutId = setTimeout(() => { - this._canLeadingExecute = true - if (this._options.trailing && !_didLeadingExecute) { - this.execute(...args) + this.#timeoutId = setTimeout(() => { + this.#setState({ canLeadingExecute: true }) + if (this.#options.trailing && !_didLeadingExecute) { + this.#execute(...args) } }, this.getWait()) } - private execute(...args: Parameters): void { + #execute(...args: Parameters): void { if (!this.getEnabled()) return undefined this.fn(...args) // EXECUTE! - this._isPending = false - this._executionCount++ - this._options.onExecute(this) + this.#setState({ + isPending: false, + executionCount: this.#state.executionCount + 1, + }) + this.#options.onExecute?.(this) } /** * Cancels any pending execution */ cancel(): void { - if (this._timeoutId) { - clearTimeout(this._timeoutId) - this._canLeadingExecute = true - this._isPending = false + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#setState({ + canLeadingExecute: true, + isPending: false, + }) } } @@ -169,14 +219,14 @@ export class Debouncer { * Returns the number of times the function has been executed */ getExecutionCount(): number { - return this._executionCount + return this.#state.executionCount } /** * Returns `true` if debouncing */ getIsPending(): boolean { - return this.getEnabled() && this._isPending + return this.getEnabled() && this.#state.isPending } } @@ -190,6 +240,11 @@ export class Debouncer { * If leading option is true, the function will execute immediately on the first call, then wait the delay * before allowing another execution. * + * State Management: + * - Use `initialState` to provide initial state values when creating the debouncer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes canLeadingExecute, execution count, and isPending status + * * @example * ```ts * const debounced = debounce(() => { From 503809b33949687f35b70d2ea04e66062c2733e6 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Jun 2025 12:22:00 -0500 Subject: [PATCH 10/51] refactor all utils to have state objects with initialState and onStateChange options --- examples/react/useQueuer/package.json | 1 + examples/react/useQueuer/src/index.tsx | 14 +- packages/pacer/src/async-debouncer.ts | 204 ++++++++----- packages/pacer/src/async-queuer.ts | 347 +++++++++++++++-------- packages/pacer/src/async-rate-limiter.ts | 3 - packages/pacer/src/async-throttler.ts | 211 +++++++++----- packages/pacer/src/batcher.ts | 144 +++++++--- packages/pacer/src/debouncer.ts | 21 +- packages/pacer/src/queuer.ts | 277 +++++++++++------- packages/pacer/src/rate-limiter.ts | 3 - packages/pacer/src/throttler.ts | 136 ++++++--- packages/pacer/tests/queuer.test.ts | 12 - pnpm-lock.yaml | 3 + 13 files changed, 903 insertions(+), 473 deletions(-) diff --git a/examples/react/useQueuer/package.json b/examples/react/useQueuer/package.json index 6fc5c3d90..09b3f04e0 100644 --- a/examples/react/useQueuer/package.json +++ b/examples/react/useQueuer/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", + "@tanstack/react-persister": "^0.0.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/examples/react/useQueuer/src/index.tsx b/examples/react/useQueuer/src/index.tsx index f41b7ceff..20ac8f517 100644 --- a/examples/react/useQueuer/src/index.tsx +++ b/examples/react/useQueuer/src/index.tsx @@ -1,11 +1,21 @@ import { useState } from 'react' import ReactDOM from 'react-dom/client' import { useQueuer } from '@tanstack/react-pacer/queuer' +import { useStoragePersister } from '@tanstack/react-persister' +import type { QueuerState } from '@tanstack/react-pacer/queuer' function App1() { // Use your state management library of choice const [queueItems, setQueueItems] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + // optional session storage persister to retain state on page refresh + const queuerPersister = useStoragePersister>({ + key: 'my-queuer', + storage: sessionStorage, + maxAge: 1000 * 60, // 1 minute + buster: 'v1', + }) + // The function that we will be queuing function processItem(item: number) { console.log('processing item', item) @@ -19,11 +29,13 @@ function App1() { onItemsChange: (queue) => { setQueueItems(queue.peekAllItems()) }, + initialState: queuerPersister.loadState(), + onStateChange: (state) => queuerPersister.saveState(state), }) return (
-

TanStack Pacer useQueuer Example 1

+

TanStack Pacer useQueuer Example 1 (with persister)

Queue Size: {queuer.getSize()}
Queue Max Size: {25}
Queue Full: {queuer.getIsFull() ? 'Yes' : 'No'}
diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 59787261f..f393a9f5f 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -1,6 +1,17 @@ import { parseFunctionOrValue } from './utils' import type { AnyAsyncFunction, OptionalKeys } from './types' +export interface AsyncDebouncerState { + canLeadingExecute: boolean + errorCount: number + isExecuting: boolean + isPending: boolean + lastArgs: Parameters | undefined + lastResult: ReturnType | undefined + settleCount: number + successCount: number +} + /** * Options for configuring an async debounced function */ @@ -11,6 +22,10 @@ export interface AsyncDebouncerOptions { * Defaults to true. */ enabled?: boolean | ((debouncer: AsyncDebouncer) => boolean) + /** + * Initial state for the async debouncer + */ + initialState?: Partial> /** * Whether to execute on the leading edge of the timeout. * Defaults to false. @@ -30,6 +45,13 @@ export interface AsyncDebouncerOptions { * Optional callback to call when the debounced function is executed */ onSuccess?: (result: ReturnType, debouncer: AsyncDebouncer) => void + /** + * Callback function that is called when the state of the async debouncer is updated + */ + onStateChange?: ( + state: AsyncDebouncerState, + debouncer: AsyncDebouncer, + ) => void /** * Whether to throw errors when they occur. * Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -51,7 +73,7 @@ export interface AsyncDebouncerOptions { type AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys< AsyncDebouncerOptions, - 'onError' | 'onSettled' | 'onSuccess' + 'initialState' | 'onError' | 'onSettled' | 'onSuccess' | 'onStateChange' > const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { @@ -81,6 +103,12 @@ const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { * - The error count can be tracked using `getErrorCount()`. * - The debouncer maintains its state and can continue to be used after an error occurs. * + * State Management: + * - Use `initialState` to provide initial state values when creating the async debouncer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes canLeadingExecute, error count, execution status, and success/settle counts + * - State can be retrieved using `getState()` method + * * @example * ```ts * const asyncDebouncer = new AsyncDebouncer(async (value: string) => { @@ -99,18 +127,20 @@ const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { * ``` */ export class AsyncDebouncer { - private _options: AsyncDebouncerOptionsWithOptionalCallbacks - private _abortController: AbortController | null = null - private _canLeadingExecute = true - private _errorCount = 0 - private _isExecuting = false - private _isPending = false - private _lastArgs: Parameters | undefined - private _lastResult: ReturnType | undefined - private _settleCount = 0 - private _successCount = 0 - private _timeoutId: NodeJS.Timeout | null = null - private _resolvePreviousPromise: + #options: AsyncDebouncerOptions + #state: AsyncDebouncerState = { + canLeadingExecute: true, + errorCount: 0, + isExecuting: false, + isPending: false, + lastArgs: undefined, + lastResult: undefined, + settleCount: 0, + successCount: 0, + } + #abortController: AbortController | null = null + #timeoutId: NodeJS.Timeout | null = null + #resolvePreviousPromise: | ((value?: ReturnType | undefined) => void) | null = null @@ -118,22 +148,26 @@ export class AsyncDebouncer { private fn: TFn, initialOptions: AsyncDebouncerOptions, ) { - this._options = { + this.#options = { ...defaultOptions, ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } + this.#state = { + ...this.#state, + ...this.#options.initialState, + } } /** * Updates the debouncer options */ setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + this.#options = { ...this.#options, ...newOptions } // End the pending state if the debouncer is disabled - if (!this._options.enabled) { - this._isPending = false + if (!this.getEnabled()) { + this.#setState({ isPending: false }) } } @@ -141,21 +175,36 @@ export class AsyncDebouncer { * Returns the current debouncer options */ getOptions(): AsyncDebouncerOptions { - return this._options + return this.#options + } + + /** + * Returns the current state for persistence + */ + getState(): AsyncDebouncerState { + return { ...this.#state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + #setState(state: Partial>): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state, this) } /** * Returns the current debouncer enabled state */ getEnabled(): boolean { - return !!parseFunctionOrValue(this._options.enabled, this) + return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current debouncer wait state */ getWait(): number { - return parseFunctionOrValue(this._options.wait, this) + return parseFunctionOrValue(this.#options.wait, this) } /** @@ -175,132 +224,138 @@ export class AsyncDebouncer { async maybeExecute( ...args: Parameters ): Promise | undefined> { - this._cancel() - this._lastArgs = args + this.#cancel() + this.#setState({ lastArgs: args }) // Handle leading execution - if (this._options.leading && this._canLeadingExecute) { - this._canLeadingExecute = false - await this.execute(...args) - return this._lastResult + if (this.#options.leading && this.#state.canLeadingExecute) { + this.#setState({ canLeadingExecute: false }) + await this.#execute(...args) + return this.#state.lastResult } // Handle trailing execution - if (this._options.trailing) { - this._isPending = true + if (this.#options.trailing) { + this.#setState({ isPending: true }) } return new Promise((resolve) => { - this._resolvePreviousPromise = resolve - this._timeoutId = setTimeout(async () => { + this.#resolvePreviousPromise = resolve + this.#timeoutId = setTimeout(async () => { // Execute trailing if enabled - if (this._options.trailing && this._lastArgs) { - await this.execute(...this._lastArgs) + if (this.#options.trailing && this.#state.lastArgs) { + await this.#execute(...this.#state.lastArgs) } // Reset state and resolve - this._canLeadingExecute = true - this._resolvePreviousPromise = null - resolve(this._lastResult) + this.#setState({ canLeadingExecute: true }) + this.#resolvePreviousPromise = null + resolve(this.#state.lastResult) }, this.getWait()) }) } - private async execute( + async #execute( ...args: Parameters ): Promise | undefined> { if (!this.getEnabled()) return undefined - this._abortController = new AbortController() + this.#abortController = new AbortController() try { - this._isExecuting = true - this._lastResult = await this.fn(...args) // EXECUTE! - this._successCount++ - this._options.onSuccess?.(this._lastResult!, this) + this.#setState({ isExecuting: true }) + const result = await this.fn(...args) // EXECUTE! + this.#setState({ + lastResult: result, + successCount: this.#state.successCount + 1, + }) + this.#options.onSuccess?.(result, this) } catch (error) { - this._errorCount++ - this._options.onError?.(error, this) - if (this._options.throwOnError) { + this.#setState({ + errorCount: this.#state.errorCount + 1, + }) + this.#options.onError?.(error, this) + if (this.#options.throwOnError) { throw error } } finally { - this._isExecuting = false - this._isPending = false - this._settleCount++ - this._abortController = null - this._options.onSettled?.(this) + this.#setState({ + isExecuting: false, + isPending: false, + settleCount: this.#state.settleCount + 1, + }) + this.#abortController = null + this.#options.onSettled?.(this) } - return this._lastResult + return this.#state.lastResult } /** - * Cancel without resetting _canLeadingExecute + * Cancel without resetting canLeadingExecute */ - private _cancel(): void { - if (this._timeoutId) { - clearTimeout(this._timeoutId) - this._timeoutId = null + #cancel(): void { + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null } - if (this._abortController) { - this._abortController.abort() - this._abortController = null + if (this.#abortController) { + this.#abortController.abort() + this.#abortController = null } - if (this._resolvePreviousPromise) { - this._resolvePreviousPromise(this._lastResult) - this._resolvePreviousPromise = null + if (this.#resolvePreviousPromise) { + this.#resolvePreviousPromise(this.#state.lastResult) + this.#resolvePreviousPromise = null } - this._lastArgs = undefined - this._isPending = false - this._isExecuting = false + this.#state.lastArgs = undefined + this.#setState({ isPending: false, isExecuting: false }) } /** * Cancels any pending execution or aborts any execution in progress */ cancel(): void { - this._canLeadingExecute = true - this._cancel() + this.#setState({ canLeadingExecute: true }) + this.#cancel() } /** * Returns the last result of the debounced function */ getLastResult(): ReturnType | undefined { - return this._lastResult + return this.#state.lastResult } /** * Returns the number of times the function has been executed successfully */ getSuccessCount(): number { - return this._successCount + return this.#state.successCount } /** * Returns the number of times the function has settled (completed or errored) */ getSettleCount(): number { - return this._settleCount + return this.#state.settleCount } /** * Returns the number of times the function has errored */ getErrorCount(): number { - return this._errorCount + return this.#state.errorCount } /** * Returns `true` if there is a pending execution queued up for trailing execution */ getIsPending(): boolean { - return this.getEnabled() && this._isPending + return this.getEnabled() && this.#state.isPending } /** * Returns `true` if there is currently an execution in progress */ getIsExecuting(): boolean { - return this._isExecuting + return this.#state.isExecuting } } @@ -320,6 +375,11 @@ export class AsyncDebouncer { * - The error state can be checked using the underlying AsyncDebouncer instance * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown * + * State Management: + * - Use `initialState` to provide initial state values when creating the async debouncer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes canLeadingExecute, error count, execution status, and success/settle counts + * * @example * ```ts * const debounced = asyncDebounce(async (value: string) => { diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index 478911792..bcef343fa 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -2,6 +2,19 @@ import { parseFunctionOrValue } from './utils' import type { OptionalKeys } from './types' import type { QueuePosition } from './queuer' +export interface AsyncQueuerState { + activeItems: Array + errorCount: number + expirationCount: number + items: Array + itemTimestamps: Array + lastResult: any + rejectionCount: number + running: boolean + settledCount: number + successCount: number +} + export interface AsyncQueuerOptions { /** * Default position to add items to the queuer @@ -39,6 +52,10 @@ export interface AsyncQueuerOptions { * Initial items to populate the queuer with */ initialItems?: Array + /** + * Initial state for the async queuer + */ + initialState?: Partial> /** * Maximum number of items allowed in the queuer */ @@ -73,6 +90,13 @@ export interface AsyncQueuerOptions { * Optional callback to call when a task succeeds */ onSuccess?: (result: TValue, queuer: AsyncQueuer) => void + /** + * Callback function that is called when the state of the async queuer is updated + */ + onStateChange?: ( + state: AsyncQueuerState, + queuer: AsyncQueuer, + ) => void /** * Whether the queuer should start processing tasks immediately or not. */ @@ -93,6 +117,7 @@ export interface AsyncQueuerOptions { type AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys< Required>, + | 'initialState' | 'throwOnError' | 'onSuccess' | 'onSettled' @@ -101,6 +126,7 @@ type AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys< | 'onIsRunningChange' | 'onExpire' | 'onError' + | 'onStateChange' > const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { @@ -138,6 +164,11 @@ const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { * - 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 AsyncQueuer instance * + * State Management: + * - Use `initialState` to provide initial state values when creating the async queuer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes error count, expiration count, rejection count, running status, and success/settle counts + * * Example usage: * ```ts * const asyncQueuer = new AsyncQueuer(async (item) => { @@ -155,34 +186,48 @@ const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { * ``` */ export class AsyncQueuer { - private _options: AsyncQueuerOptionsWithOptionalCallbacks - private _activeItems: Set = new Set() - private _successCount = 0 - private _errorCount = 0 - private _settledCount = 0 - private _rejectionCount = 0 - private _expirationCount = 0 - private _items: Array = [] - private _itemTimestamps: Array = [] - private _pendingTick = false - private _running: boolean - private _lastResult: any + #options: AsyncQueuerOptions + #state: AsyncQueuerState = { + activeItems: [], + errorCount: 0, + expirationCount: 0, + items: [], + itemTimestamps: [], + lastResult: null, + rejectionCount: 0, + running: true, + settledCount: 0, + successCount: 0, + } + #pendingTick = false constructor( private fn: (value: TValue) => Promise, initialOptions: AsyncQueuerOptions, ) { - this._options = { + this.#options = { ...defaultOptions, ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } - this._running = this._options.started + this.#state = { + ...this.#state, + ...this.#options.initialState, + running: + this.#options.initialState?.running ?? this.#options.started ?? true, + } - for (let i = 0; i < this._options.initialItems.length; i++) { - const item = this._options.initialItems[i]! - const isLast = i === this._options.initialItems.length - 1 - this.addItem(item, this._options.addItemsTo, isLast) + if (this.#options.initialState?.items) { + this.#options.onItemsChange?.(this) + if (this.#state.running) { + this.#tick() + } + } else { + for (let i = 0; i < (this.#options.initialItems?.length ?? 0); i++) { + const item = this.#options.initialItems![i]! + const isLast = i === (this.#options.initialItems?.length ?? 0) - 1 + this.addItem(item, this.#options.addItemsTo ?? 'back', isLast) + } } } @@ -190,14 +235,29 @@ export class AsyncQueuer { * Updates the queuer options. New options are merged with existing options. */ setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + this.#options = { ...this.#options, ...newOptions } } /** * Returns the current queuer options, including defaults and any overrides. */ getOptions(): AsyncQueuerOptions { - return this._options + return this.#options + } + + /** + * Returns the current state for persistence + */ + getState(): AsyncQueuerState { + return { ...this.#state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + #setState(state: Partial>): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state, this) } /** @@ -205,7 +265,7 @@ export class AsyncQueuer { * If a function is provided, it is called with the queuer instance. */ getWait(): number { - return parseFunctionOrValue(this._options.wait, this) + return parseFunctionOrValue(this.#options.wait ?? 0, this) } /** @@ -213,75 +273,78 @@ export class AsyncQueuer { * If a function is provided, it is called with the queuer instance. */ getConcurrency(): number { - return parseFunctionOrValue(this._options.concurrency, this) + return parseFunctionOrValue(this.#options.concurrency ?? 1, this) } /** * Processes items in the queue up to the concurrency limit. Internal use only. */ - private tick() { - if (!this._running) { - this._pendingTick = false + #tick() { + if (!this.#state.running) { + this.#pendingTick = false return } // Check for expired items - this.checkExpiredItems() + this.#checkExpiredItems() // Process items concurrently up to the concurrency limit while ( - this._activeItems.size < this.getConcurrency() && + this.#state.activeItems.length < this.getConcurrency() && !this.getIsEmpty() ) { const nextItem = this.peekNextItem() if (!nextItem) { break } - this._activeItems.add(nextItem) - this._options.onItemsChange?.(this) + this.#setState({ + activeItems: [...this.#state.activeItems, nextItem], + }) + this.#options.onItemsChange?.(this) ;(async () => { - this._lastResult = await this.execute() + const result = await this.execute() + this.#setState({ lastResult: result }) const wait = this.getWait() if (wait > 0) { - setTimeout(() => this.tick(), wait) + setTimeout(() => this.#tick(), wait) return } - this.tick() + this.#tick() })() } - this._pendingTick = false + this.#pendingTick = false } /** * Starts processing items in the queue. If already running, does nothing. */ start(): void { - this._running = true - if (!this._pendingTick && !this.getIsEmpty()) { - this._pendingTick = true - this.tick() + this.#setState({ running: true }) + if (!this.#pendingTick && !this.getIsEmpty()) { + this.#pendingTick = true + this.#tick() } - this._options.onIsRunningChange?.(this) + this.#options.onIsRunningChange?.(this) } /** * Stops processing items in the queue. Does not clear the queue. */ stop(): void { - this._running = false - this._pendingTick = false - this._options.onIsRunningChange?.(this) + this.#setState({ running: false }) + this.#pendingTick = false + this.#options.onIsRunningChange?.(this) } /** * Removes all pending items from the queue. Does not affect active tasks. */ clear(): void { - this._items = [] - this._options.onItemsChange?.(this) + this.#setState({ items: [], itemTimestamps: [] }) + this.#options.onItemsChange?.(this) } /** @@ -290,13 +353,23 @@ export class AsyncQueuer { */ reset(withInitialItems?: boolean): void { this.clear() - this._successCount = 0 - this._errorCount = 0 - this._settledCount = 0 + this.#setState({ + activeItems: [], + errorCount: 0, + expirationCount: 0, + lastResult: null, + rejectionCount: 0, + settledCount: 0, + successCount: 0, + running: this.#options.started ?? true, + }) if (withInitialItems) { - this._items = [...this._options.initialItems] + const items = [...(this.#options.initialItems ?? [])] + this.#setState({ + items, + itemTimestamps: items.map(() => Date.now()), + }) } - this._running = this._options.started } /** @@ -311,57 +384,71 @@ export class AsyncQueuer { */ addItem( item: TValue & { priority?: number }, - position: QueuePosition = this._options.addItemsTo, + position: QueuePosition = this.#options.addItemsTo ?? 'back', runOnItemsChange: boolean = true, ): void { if (this.getIsFull()) { - this._rejectionCount++ - this._options.onReject?.(item, this) + this.#setState({ + rejectionCount: this.#state.rejectionCount + 1, + }) + this.#options.onReject?.(item, this) return } // Get priority either from the function or from getPriority option const priority = - this._options.getPriority !== defaultOptions.getPriority - ? this._options.getPriority(item) + this.#options.getPriority !== defaultOptions.getPriority + ? this.#options.getPriority!(item) : item.priority if (priority !== undefined) { // Insert based on priority - higher priority items go to front - const insertIndex = this._items.findIndex((existing) => { + const insertIndex = this.#state.items.findIndex((existing) => { const existingPriority = - this._options.getPriority !== defaultOptions.getPriority - ? this._options.getPriority(existing) + this.#options.getPriority !== defaultOptions.getPriority + ? this.#options.getPriority!(existing) : (existing as any).priority return existingPriority < priority }) if (insertIndex === -1) { - this._items.push(item) - this._itemTimestamps.push(Date.now()) + this.#setState({ + items: [...this.#state.items, item], + itemTimestamps: [...this.#state.itemTimestamps, Date.now()], + }) } else { - this._items.splice(insertIndex, 0, item) - this._itemTimestamps.splice(insertIndex, 0, Date.now()) + const newItems = [...this.#state.items] + const newTimestamps = [...this.#state.itemTimestamps] + newItems.splice(insertIndex, 0, item) + newTimestamps.splice(insertIndex, 0, Date.now()) + this.#setState({ + items: newItems, + itemTimestamps: newTimestamps, + }) } } else { if (position === 'front') { // Default FIFO/LIFO behavior - this._items.unshift(item) - this._itemTimestamps.unshift(Date.now()) + this.#setState({ + items: [item, ...this.#state.items], + itemTimestamps: [Date.now(), ...this.#state.itemTimestamps], + }) } else { // LIFO - this._items.push(item) - this._itemTimestamps.push(Date.now()) + this.#setState({ + items: [...this.#state.items, item], + itemTimestamps: [...this.#state.itemTimestamps, Date.now()], + }) } } if (runOnItemsChange) { - this._options.onItemsChange?.(this) + this.#options.onItemsChange?.(this) } - if (this._running && !this._pendingTick) { - this._pendingTick = true - this.tick() + if (this.#state.running && !this.#pendingTick) { + this.#pendingTick = true + this.#tick() } } @@ -378,20 +465,30 @@ export class AsyncQueuer { * ``` */ getNextItem( - position: QueuePosition = this._options.getItemsFrom, + position: QueuePosition = this.#options.getItemsFrom ?? 'front', ): TValue | undefined { let item: TValue | undefined if (position === 'front') { - item = this._items.shift() - this._itemTimestamps.shift() + item = this.#state.items[0] + if (item !== undefined) { + this.#setState({ + items: this.#state.items.slice(1), + itemTimestamps: this.#state.itemTimestamps.slice(1), + }) + } } else { - item = this._items.pop() - this._itemTimestamps.pop() + item = this.#state.items[this.#state.items.length - 1] + if (item !== undefined) { + this.#setState({ + items: this.#state.items.slice(0, -1), + itemTimestamps: this.#state.itemTimestamps.slice(0, -1), + }) + } } if (item !== undefined) { - this._options.onItemsChange?.(this) + this.#options.onItemsChange?.(this) } return item @@ -411,20 +508,29 @@ export class AsyncQueuer { const item = this.getNextItem(position) if (item !== undefined) { try { - this._lastResult = await this.fn(item) - this._successCount++ - this._options.onSuccess?.(this._lastResult, this) + const result = await this.fn(item) + this.#setState({ + lastResult: result, + successCount: this.#state.successCount + 1, + }) + this.#options.onSuccess?.(result, this) } catch (error) { - this._errorCount++ - this._options.onError?.(error, this) - if (this._options.throwOnError) { + this.#setState({ + errorCount: this.#state.errorCount + 1, + }) + this.#options.onError?.(error, this) + if (this.#options.throwOnError) { throw error } } finally { - this._settledCount++ - this._activeItems.delete(item) - this._options.onItemsChange?.(this) - this._options.onSettled?.(this) + this.#setState({ + activeItems: this.#state.activeItems.filter( + (activeItem) => activeItem !== item, + ), + settledCount: this.#state.settledCount + 1, + }) + this.#options.onItemsChange?.(this) + this.#options.onSettled?.(this) } } return item @@ -434,10 +540,10 @@ export class AsyncQueuer { * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. * Internal use only. */ - private checkExpiredItems(): void { + #checkExpiredItems(): void { if ( - this._options.expirationDuration === Infinity && - this._options.getIsExpired === defaultOptions.getIsExpired + (this.#options.expirationDuration ?? Infinity) === Infinity && + this.#options.getIsExpired === defaultOptions.getIsExpired ) return @@ -445,17 +551,17 @@ export class AsyncQueuer { const expiredIndices: Array = [] // Find indices of expired items - for (let i = 0; i < this._items.length; i++) { - const timestamp = this._itemTimestamps[i] + for (let i = 0; i < this.#state.items.length; i++) { + const timestamp = this.#state.itemTimestamps[i] if (timestamp === undefined) continue - const item = this._items[i] + const item = this.#state.items[i] if (item === undefined) continue const isExpired = - this._options.getIsExpired !== defaultOptions.getIsExpired - ? this._options.getIsExpired(item, timestamp) - : now - timestamp > this._options.expirationDuration + this.#options.getIsExpired !== defaultOptions.getIsExpired + ? this.#options.getIsExpired!(item, timestamp) + : now - timestamp > (this.#options.expirationDuration ?? Infinity) if (isExpired) { expiredIndices.push(i) @@ -467,17 +573,23 @@ export class AsyncQueuer { const index = expiredIndices[i] if (index === undefined) continue - const expiredItem = this._items[index] + const expiredItem = this.#state.items[index] if (expiredItem === undefined) continue - this._items.splice(index, 1) - this._itemTimestamps.splice(index, 1) - this._expirationCount++ - this._options.onExpire?.(expiredItem, this) + const newItems = [...this.#state.items] + const newTimestamps = [...this.#state.itemTimestamps] + newItems.splice(index, 1) + newTimestamps.splice(index, 1) + this.#setState({ + items: newItems, + itemTimestamps: newTimestamps, + expirationCount: this.#state.expirationCount + 1, + }) + this.#options.onExpire?.(expiredItem, this) } if (expiredIndices.length > 0) { - this._options.onItemsChange?.(this) + this.#options.onItemsChange?.(this) } } @@ -492,30 +604,30 @@ export class AsyncQueuer { */ peekNextItem(position: QueuePosition = 'front'): TValue | undefined { if (position === 'front') { - return this._items[0] + return this.#state.items[0] } - return this._items[this._items.length - 1] + return this.#state.items[this.#state.items.length - 1] } /** * Returns true if the queue is empty (no pending items). */ getIsEmpty(): boolean { - return this._items.length === 0 + return this.#state.items.length === 0 } /** * Returns true if the queue is full (reached maxSize). */ getIsFull(): boolean { - return this._items.length >= this._options.maxSize + return this.#state.items.length >= (this.#options.maxSize ?? Infinity) } /** * Returns the number of pending items in the queue. */ getSize(): number { - return this._items.length + return this.#state.items.length } /** @@ -529,63 +641,67 @@ export class AsyncQueuer { * Returns the items currently being processed (active tasks). */ peekActiveItems(): Array { - return Array.from(this._activeItems) + return [...this.#state.activeItems] } /** * Returns the items waiting to be processed (pending tasks). */ peekPendingItems(): Array { - return [...this._items] + return [...this.#state.items] } /** * Returns the number of items that have been successfully processed. */ getSuccessCount(): number { - return this._successCount + return this.#state.successCount } /** * Returns the number of items that have failed processing. */ getErrorCount(): number { - return this._errorCount + return this.#state.errorCount } /** * Returns the number of items that have completed processing (success or error). */ getSettledCount(): number { - return this._settledCount + return this.#state.settledCount } /** * Returns the number of items that have been rejected from being added to the queue. */ getRejectionCount(): number { - return this._rejectionCount + return this.#state.rejectionCount } /** * Returns true if the queuer is currently running (processing items). */ getIsRunning(): boolean { - return this._running + return this.#state.running } /** * Returns true if the queuer is running but has no items to process and no active tasks. */ getIsIdle(): boolean { - return this._running && this.getIsEmpty() && this._activeItems.size === 0 + return ( + this.#state.running && + this.getIsEmpty() && + this.#state.activeItems.length === 0 + ) } /** * Returns the number of items that have expired and been removed from the queue. */ getExpirationCount(): number { - return this._expirationCount + return this.#state.expirationCount } } @@ -600,6 +716,11 @@ export class AsyncQueuer { * - 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 AsyncQueuer instance * + * State Management: + * - Use `initialState` to provide initial state values when creating the async queuer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes error count, expiration count, rejection count, running status, and success/settle counts + * * Example usage: * ```ts * const enqueue = asyncQueue(async (item) => { diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index b5163983c..114ce5c93 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -1,9 +1,6 @@ import { parseFunctionOrValue } from './utils' import type { AnyAsyncFunction } from './types' -/** - * State shape for persisting AsyncRateLimiter - */ export interface AsyncRateLimiterState { errorCount: number executionTimes: Array diff --git a/packages/pacer/src/async-throttler.ts b/packages/pacer/src/async-throttler.ts index 839f78fab..3cf207ee7 100644 --- a/packages/pacer/src/async-throttler.ts +++ b/packages/pacer/src/async-throttler.ts @@ -1,6 +1,17 @@ import { parseFunctionOrValue } from './utils' import type { AnyAsyncFunction, OptionalKeys } from './types' +export interface AsyncThrottlerState { + errorCount: number + isExecuting: boolean + lastArgs: Parameters | undefined + lastExecutionTime: number + lastResult: ReturnType | undefined + nextExecutionTime: number + settleCount: number + successCount: number +} + /** * Options for configuring an async throttled function */ @@ -11,6 +22,10 @@ export interface AsyncThrottlerOptions { * Defaults to true. */ enabled?: boolean | ((throttler: AsyncThrottler) => boolean) + /** + * Initial state for the async throttler + */ + initialState?: Partial> /** * Whether to execute the function immediately when called * Defaults to true @@ -33,6 +48,13 @@ export interface AsyncThrottlerOptions { result: ReturnType, asyncThrottler: AsyncThrottler, ) => void + /** + * Callback function that is called when the state of the async throttler is updated + */ + onStateChange?: ( + state: AsyncThrottlerState, + throttler: AsyncThrottler, + ) => void /** * Whether to throw errors when they occur. * Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -54,7 +76,7 @@ export interface AsyncThrottlerOptions { type AsyncThrottlerOptionsWithOptionalCallbacks = OptionalKeys< AsyncThrottlerOptions, - 'onError' | 'onSettled' | 'onSuccess' + 'initialState' | 'onError' | 'onSettled' | 'onSuccess' | 'onStateChange' > const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = { @@ -85,6 +107,12 @@ const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = { * - 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 AsyncThrottler instance * + * State Management: + * - Use `initialState` to provide initial state values when creating the async throttler + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes error count, execution status, last execution time, and success/settle counts + * - State can be retrieved using `getState()` method + * * @example * ```ts * const throttler = new AsyncThrottler(async (value: string) => { @@ -103,18 +131,20 @@ const defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = { * ``` */ export class AsyncThrottler { - private _options: AsyncThrottlerOptionsWithOptionalCallbacks - private _abortController: AbortController | null = null - private _errorCount = 0 - private _isExecuting = false - private _lastArgs: Parameters | undefined - private _lastExecutionTime = 0 - private _lastResult: ReturnType | undefined - private _nextExecutionTime = 0 - private _settleCount = 0 - private _successCount = 0 - private _timeoutId: NodeJS.Timeout | null = null - private _resolvePreviousPromise: + #options: AsyncThrottlerOptions + #state: AsyncThrottlerState = { + errorCount: 0, + isExecuting: false, + lastArgs: undefined, + lastExecutionTime: 0, + lastResult: undefined, + nextExecutionTime: 0, + settleCount: 0, + successCount: 0, + } + #abortController: AbortController | null = null + #timeoutId: NodeJS.Timeout | null = null + #resolvePreviousPromise: | ((value?: ReturnType | undefined) => void) | null = null @@ -122,21 +152,25 @@ export class AsyncThrottler { private fn: TFn, initialOptions: AsyncThrottlerOptions, ) { - this._options = { + this.#options = { ...defaultOptions, ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } + this.#state = { + ...this.#state, + ...this.#options.initialState, + } } /** * Updates the throttler options */ setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + this.#options = { ...this.#options, ...newOptions } - // End the pending state if the debouncer is disabled - if (!this._options.enabled) { + // End the pending state if the throttler is disabled + if (!this.getEnabled()) { this.cancel() } } @@ -145,21 +179,36 @@ export class AsyncThrottler { * Returns the current options */ getOptions(): AsyncThrottlerOptions { - return this._options + return this.#options + } + + /** + * Returns the current state for persistence + */ + getState(): AsyncThrottlerState { + return { ...this.#state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + #setState(state: Partial>): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state, this) } /** * Returns the current enabled state of the throttler */ getEnabled(): boolean { - return !!parseFunctionOrValue(this._options.enabled, this) + return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current wait time in milliseconds */ getWait(): number { - return parseFunctionOrValue(this._options.wait, this) + return parseFunctionOrValue(this.#options.wait, this) } /** @@ -180,77 +229,84 @@ export class AsyncThrottler { ...args: Parameters ): Promise | undefined> { const now = Date.now() - const timeSinceLastExecution = now - this._lastExecutionTime + const timeSinceLastExecution = now - this.#state.lastExecutionTime const wait = this.getWait() - this.resolvePreviousPromise() + this.#resolvePreviousPromiseInternal() // Handle leading execution - if (this._options.leading && timeSinceLastExecution >= wait) { - await this.execute(...args) - return this._lastResult + if (this.#options.leading && timeSinceLastExecution >= wait) { + await this.#execute(...args) + return this.#state.lastResult } else { // Store the most recent arguments for potential trailing execution - this._lastArgs = args + this.#state.lastArgs = args return new Promise((resolve) => { - this._resolvePreviousPromise = resolve + this.#resolvePreviousPromise = resolve // Clear any existing timeout to ensure we use the latest arguments - if (this._timeoutId) { - clearTimeout(this._timeoutId) + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) } // Set up trailing execution if enabled - if (this._options.trailing) { - const _timeSinceLastExecution = this._lastExecutionTime - ? now - this._lastExecutionTime + if (this.#options.trailing) { + const _timeSinceLastExecution = this.#state.lastExecutionTime + ? now - this.#state.lastExecutionTime : 0 const timeoutDuration = wait - _timeSinceLastExecution - this._timeoutId = setTimeout(async () => { - if (this._lastArgs !== undefined) { - await this.execute(...this._lastArgs) + this.#timeoutId = setTimeout(async () => { + if (this.#state.lastArgs !== undefined) { + await this.#execute(...this.#state.lastArgs) } - this._resolvePreviousPromise = null - resolve(this._lastResult) + this.#resolvePreviousPromise = null + resolve(this.#state.lastResult) }, timeoutDuration) } }) } } - private async execute( + async #execute( ...args: Parameters ): Promise | undefined> { - if (!this.getEnabled() || this._isExecuting) return undefined - this._abortController = new AbortController() + if (!this.getEnabled() || this.#state.isExecuting) return undefined + this.#abortController = new AbortController() try { - this._isExecuting = true - this._lastResult = await this.fn(...args) // EXECUTE! - this._successCount++ - this._options.onSuccess?.(this._lastResult!, this) + this.#setState({ isExecuting: true }) + this.#state.lastResult = await this.fn(...args) // EXECUTE! + this.#setState({ + successCount: this.#state.successCount + 1, + }) + this.#options.onSuccess?.(this.#state.lastResult!, this) } catch (error) { - this._errorCount++ - this._options.onError?.(error, this) - if (this._options.throwOnError) { + this.#setState({ + errorCount: this.#state.errorCount + 1, + }) + this.#options.onError?.(error, this) + if (this.#options.throwOnError) { throw error } else { console.error(error) } } finally { - this._isExecuting = false - this._settleCount++ - this._abortController = null - this._lastExecutionTime = Date.now() - this._nextExecutionTime = this._lastExecutionTime + this.getWait() - this._options.onSettled?.(this) + this.#setState({ + isExecuting: false, + settleCount: this.#state.settleCount + 1, + lastExecutionTime: Date.now(), + }) + this.#state.nextExecutionTime = + this.#state.lastExecutionTime + this.getWait() + this.#abortController = null + this.#options.onSettled?.(this) } - return this._lastResult + return this.#state.lastResult } - private resolvePreviousPromise(): void { - if (this._resolvePreviousPromise) { - this._resolvePreviousPromise(this._lastResult) - this._resolvePreviousPromise = null + #resolvePreviousPromiseInternal(): void { + if (this.#resolvePreviousPromise) { + this.#resolvePreviousPromise(this.#state.lastResult) + this.#resolvePreviousPromise = null } } @@ -258,72 +314,72 @@ export class AsyncThrottler { * Cancels any pending execution or aborts any execution in progress */ cancel(): void { - if (this._timeoutId) { - clearTimeout(this._timeoutId) - this._timeoutId = null + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null } - if (this._abortController) { - this._abortController.abort() - this._abortController = null + if (this.#abortController) { + this.#abortController.abort() + this.#abortController = null } - this.resolvePreviousPromise() - this._lastArgs = undefined + this.#resolvePreviousPromiseInternal() + this.#state.lastArgs = undefined } /** * Returns the last execution time */ getLastExecutionTime(): number { - return this._lastExecutionTime + return this.#state.lastExecutionTime } /** * Returns the next execution time */ getNextExecutionTime(): number { - return this._nextExecutionTime + return this.#state.nextExecutionTime } /** - * Returns the last result of the debounced function + * Returns the last result of the throttled function */ getLastResult(): ReturnType | undefined { - return this._lastResult + return this.#state.lastResult } /** * Returns the number of times the function has been executed successfully */ getSuccessCount(): number { - return this._successCount + return this.#state.successCount } /** * Returns the number of times the function has settled (completed or errored) */ getSettleCount(): number { - return this._settleCount + return this.#state.settleCount } /** * Returns the number of times the function has errored */ getErrorCount(): number { - return this._errorCount + return this.#state.errorCount } /** * Returns the current pending state */ getIsPending(): boolean { - return this.getEnabled() && !!this._timeoutId + return this.getEnabled() && !!this.#timeoutId } /** * Returns the current executing state */ getIsExecuting(): boolean { - return this._isExecuting + return this.#state.isExecuting } } @@ -343,6 +399,11 @@ export class AsyncThrottler { * - 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 AsyncThrottler instance * + * State Management: + * - Use `initialState` to provide initial state values when creating the async throttler + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes error count, execution status, last execution time, and success/settle counts + * * @example * ```ts * const throttled = asyncThrottle(async (value: string) => { diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index de3a35166..94dfd0306 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -1,5 +1,12 @@ import type { OptionalKeys } from './types' +export interface BatcherState { + batchExecutionCount: number + itemExecutionCount: number + items: Array + running: boolean +} + /** * Options for configuring a Batcher instance */ @@ -9,6 +16,10 @@ export interface BatcherOptions { * Return true to process the batch immediately */ getShouldExecute?: (items: Array, batcher: Batcher) => boolean + /** + * Initial state for the batcher + */ + initialState?: Partial> /** * Maximum number of items in a batch * @default Infinity @@ -26,6 +37,13 @@ export interface BatcherOptions { * Callback fired after items are added to the batcher */ onItemsChange?: (batcher: Batcher) => void + /** + * Callback function that is called when the state of the batcher is updated + */ + onStateChange?: ( + state: BatcherState, + batcher: Batcher, + ) => void /** * Whether the batcher should start processing immediately * @default true @@ -42,7 +60,11 @@ export interface BatcherOptions { type BatcherOptionsWithOptionalCallbacks = OptionalKeys< Required>, - 'onExecute' | 'onItemsChange' | 'onIsRunningChange' + | 'initialState' + | 'onExecute' + | 'onItemsChange' + | 'onIsRunningChange' + | 'onStateChange' > const defaultOptions: BatcherOptionsWithOptionalCallbacks = { @@ -63,6 +85,12 @@ const defaultOptions: BatcherOptionsWithOptionalCallbacks = { * - Custom batch processing logic via getShouldExecute * - Event callbacks for monitoring batch operations * + * State Management: + * - Use `initialState` to provide initial state values when creating the batcher + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes batch execution count, item execution count, items, and running status + * - State can be retrieved using `getState()` method + * * @example * ```ts * const batcher = new Batcher( @@ -82,33 +110,53 @@ const defaultOptions: BatcherOptionsWithOptionalCallbacks = { * ``` */ export class Batcher { - private _options: BatcherOptionsWithOptionalCallbacks - private _batchExecutionCount = 0 - private _itemExecutionCount = 0 - private _items: Array = [] - private _running: boolean - private _timeoutId: NodeJS.Timeout | null = null + #options: BatcherOptionsWithOptionalCallbacks + #state: BatcherState = { + batchExecutionCount: 0, + itemExecutionCount: 0, + items: [], + running: true, + } + #timeoutId: NodeJS.Timeout | null = null constructor( private fn: (items: Array) => void, initialOptions: BatcherOptions, ) { - this._options = { ...defaultOptions, ...initialOptions } - this._running = this._options.started + this.#options = { ...defaultOptions, ...initialOptions } + this.#state = { + ...this.#state, + ...this.#options.initialState, + } } /** * Updates the batcher options */ setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + this.#options = { ...this.#options, ...newOptions } } /** * Returns the current batcher options */ getOptions(): BatcherOptions { - return this._options + return this.#options + } + + /** + * Returns the current state for persistence + */ + getState(): BatcherState { + return { ...this.#state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + #setState(state: Partial>): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state, this) } /** @@ -116,21 +164,23 @@ export class Batcher { * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed */ addItem(item: TValue): void { - this._items.push(item) - this._options.onItemsChange?.(this) + this.#setState({ + items: [...this.#state.items, item], + }) + this.#options.onItemsChange?.(this) const shouldProcess = - this._items.length >= this._options.maxSize || - this._options.getShouldExecute(this._items, this) + this.#state.items.length >= this.#options.maxSize || + this.#options.getShouldExecute(this.#state.items, this) if (shouldProcess) { this.execute() } else if ( - this._running && - !this._timeoutId && - this._options.wait !== Infinity + this.#state.running && + !this.#timeoutId && + this.#options.wait !== Infinity ) { - this._timeoutId = setTimeout(() => this.execute(), this._options.wait) + this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) } } @@ -144,34 +194,36 @@ export class Batcher { * You can also call this method manually to process the current batch at any time. */ execute(): void { - if (this._timeoutId) { - clearTimeout(this._timeoutId) - this._timeoutId = null + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null } - if (this._items.length === 0) { + if (this.#state.items.length === 0) { return } const batch = this.peekAllItems() // copy of the items to be processed (to prevent race conditions) - this._items = [] // Clear items before processing to prevent race conditions - this._options.onItemsChange?.(this) // Call onItemsChange to notify listeners that the items have changed + this.#setState({ items: [] }) // Clear items before processing to prevent race conditions + this.#options.onItemsChange?.(this) // Call onItemsChange to notify listeners that the items have changed this.fn(batch) - this._batchExecutionCount++ - this._itemExecutionCount += batch.length - this._options.onExecute?.(this) + this.#setState({ + batchExecutionCount: this.#state.batchExecutionCount + 1, + itemExecutionCount: this.#state.itemExecutionCount + batch.length, + }) + this.#options.onExecute?.(this) } /** * Stops the batcher from processing batches */ stop(): void { - this._running = false - this._options.onIsRunningChange?.(this) - if (this._timeoutId) { - clearTimeout(this._timeoutId) - this._timeoutId = null + this.#setState({ running: false }) + this.#options.onIsRunningChange?.(this) + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null } } @@ -179,10 +231,10 @@ export class Batcher { * Starts the batcher and processes any pending items */ start(): void { - this._running = true - this._options.onIsRunningChange?.(this) - if (this._items.length > 0 && !this._timeoutId) { - this._timeoutId = setTimeout(() => this.execute(), this._options.wait) + this.#setState({ running: true }) + this.#options.onIsRunningChange?.(this) + if (this.#state.items.length > 0 && !this.#timeoutId) { + this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) } } @@ -190,42 +242,42 @@ export class Batcher { * Returns the current number of items in the batcher */ getSize(): number { - return this._items.length + return this.#state.items.length } /** * Returns true if the batcher is empty */ getIsEmpty(): boolean { - return this._items.length === 0 + return this.#state.items.length === 0 } /** * Returns true if the batcher is running */ getIsRunning(): boolean { - return this._running + return this.#state.running } /** - * Returns a copy of all items currently in the batcher + * Returns a copy of all items in the batcher */ peekAllItems(): Array { - return [...this._items] + return [...this.#state.items] } /** - * Returns the number of times batches have been processed + * Returns the number of batches that have been processed */ getBatchExecutionCount(): number { - return this._batchExecutionCount + return this.#state.batchExecutionCount } /** - * Returns the total number of individual items that have been processed + * Returns the total number of items that have been processed */ getItemExecutionCount(): number { - return this._itemExecutionCount + return this.#state.itemExecutionCount } } diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index 3b4e406b3..b928964c4 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -1,13 +1,11 @@ import { parseFunctionOrValue } from './utils' import type { AnyFunction } from './types' -/** - * State shape for persisting Debouncer - */ -export interface DebouncerState { +export interface DebouncerState { canLeadingExecute: boolean executionCount: number isPending: boolean + lastArgs: Parameters | undefined } /** @@ -23,7 +21,7 @@ export interface DebouncerOptions { /** * Initial state for the debouncer */ - initialState?: Partial + initialState?: Partial> /** * Whether to execute on the leading edge of the timeout. * The first call will execute immediately and the rest will wait the delay. @@ -37,7 +35,10 @@ export interface DebouncerOptions { /** * Callback function that is called when the state of the debouncer is updated */ - onStateChange?: (state: DebouncerState, debouncer: Debouncer) => void + onStateChange?: ( + state: DebouncerState, + debouncer: Debouncer, + ) => void /** * Whether to execute on the trailing edge of the timeout. * Defaults to true. @@ -92,10 +93,11 @@ const defaultOptions: Omit< */ export class Debouncer { #options: DebouncerOptions - #state: DebouncerState = { + #state: DebouncerState = { canLeadingExecute: true, executionCount: 0, isPending: false, + lastArgs: undefined, } #timeoutId: NodeJS.Timeout | undefined @@ -135,14 +137,14 @@ export class Debouncer { /** * Returns the current state for persistence */ - getState(): DebouncerState { + getState(): DebouncerState { return { ...this.#state } } /** * Loads state from a persisted object or updates state with a partial */ - #setState(state: Partial): void { + #setState(state: Partial>): void { this.#state = { ...this.#state, ...state } this.#options.onStateChange?.(this.#state, this) } @@ -198,6 +200,7 @@ export class Debouncer { this.#setState({ isPending: false, executionCount: this.#state.executionCount + 1, + lastArgs: args, }) this.#options.onExecute?.(this) } diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index e0fdb5474..3f9debd7c 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -1,5 +1,14 @@ import { parseFunctionOrValue } from './utils' +export interface QueuerState { + executionCount: number + expirationCount: number + items: Array + itemTimestamps: Array + rejectionCount: number + running: boolean +} + /** * Options for configuring a Queuer instance. * @@ -35,6 +44,10 @@ export interface QueuerOptions { * Initial items to populate the queuer with */ initialItems?: Array + /** + * Initial state for the queuer + */ + initialState?: Partial> /** * Maximum number of items allowed in the queuer */ @@ -59,6 +72,10 @@ export interface QueuerOptions { * Callback fired whenever an item is rejected from being added to the queuer */ onReject?: (item: TValue, queuer: Queuer) => void + /** + * Callback function that is called when the state of the queuer is updated + */ + onStateChange?: (state: QueuerState, queuer: Queuer) => void /** * Whether the queuer should start processing tasks immediately */ @@ -71,7 +88,16 @@ export interface QueuerOptions { wait?: number | ((queuer: Queuer) => number) } -const defaultOptions: Required> = { +const defaultOptions: Omit< + Required>, + | 'initialState' + | 'onStateChange' + | 'onExecute' + | 'onIsRunningChange' + | 'onItemsChange' + | 'onReject' + | 'onExpire' +> = { addItemsTo: 'back', getItemsFrom: 'front', getPriority: (item) => item?.priority ?? 0, @@ -79,11 +105,6 @@ const defaultOptions: Required> = { expirationDuration: Infinity, initialItems: [], maxSize: Infinity, - onExecute: () => {}, - onIsRunningChange: () => {}, - onItemsChange: () => {}, - onReject: () => {}, - onExpire: () => {}, started: true, wait: 0, } @@ -136,6 +157,12 @@ export type QueuePosition = 'front' | 'back' * - `getIsExpired`: Function to override default expiration * - `onExpire`: Callback for expired items * + * State Management: + * - Use `initialState` to provide initial state values when creating the queuer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution count, expiration count, rejection count, and running status + * - State can be retrieved using `getState()` method + * * Example usage: * ```ts * // Auto-processing queue with wait time @@ -158,27 +185,41 @@ export type QueuePosition = 'front' | 'back' * ``` */ export class Queuer { - private _options: Required> - private _items: Array = [] - private _itemTimestamps: Array = [] - private _executionCount = 0 - private _rejectionCount = 0 - private _expirationCount = 0 - private _onItemsChanges: Array<(item: TValue) => void> = [] - private _running: boolean - private _pendingTick = false + #options: QueuerOptions + #state: QueuerState = { + executionCount: 0, + expirationCount: 0, + items: [], + itemTimestamps: [], + rejectionCount: 0, + running: true, + } + #onItemsChanges: Array<(item: TValue) => void> = [] + #pendingTick = false constructor( private fn: (item: TValue) => void, initialOptions: QueuerOptions = {}, ) { - this._options = { ...defaultOptions, ...initialOptions } - this._running = this._options.started + this.#options = { ...defaultOptions, ...initialOptions } + this.#state = { + ...this.#state, + ...this.#options.initialState, + running: + this.#options.initialState?.running ?? this.#options.started ?? true, + } - for (let i = 0; i < this._options.initialItems.length; i++) { - const item = this._options.initialItems[i]! - const isLast = i === this._options.initialItems.length - 1 - this.addItem(item, this._options.addItemsTo, isLast) + if (this.#options.initialState?.items) { + this.#options.onItemsChange?.(this) + if (this.#state.running) { + this.#tick() + } + } else { + for (let i = 0; i < (this.#options.initialItems?.length ?? 0); i++) { + const item = this.#options.initialItems![i]! + const isLast = i === (this.#options.initialItems?.length ?? 0) - 1 + this.addItem(item, this.#options.addItemsTo ?? 'back', isLast) + } } } @@ -186,14 +227,29 @@ export class Queuer { * Updates the queuer options. New options are merged with existing options. */ setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + this.#options = { ...this.#options, ...newOptions } } /** * Returns the current queuer options, including defaults and any overrides. */ getOptions(): Required> { - return this._options + return this.#options as Required> + } + + /** + * Returns the current state for persistence + */ + getState(): QueuerState { + return { ...this.#state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + #setState(state: Partial>): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state, this) } /** @@ -201,48 +257,48 @@ export class Queuer { * If a function is provided, it is called with the queuer instance. */ getWait(): number { - return parseFunctionOrValue(this._options.wait, this) + return parseFunctionOrValue(this.#options.wait ?? 0, this) } /** * Processes items in the queue up to the wait interval. Internal use only. */ - private tick() { - if (!this._running) { - this._pendingTick = false + #tick() { + if (!this.#state.running) { + this.#pendingTick = false return } // Check for expired items - this.checkExpiredItems() + this.#checkExpiredItems() while (!this.getIsEmpty()) { - const nextItem = this.execute(this._options.getItemsFrom) + const nextItem = this.execute(this.#options.getItemsFrom ?? 'front') if (nextItem === undefined) { break } - this._onItemsChanges.forEach((cb) => cb(nextItem)) + this.#onItemsChanges.forEach((cb) => cb(nextItem)) const wait = this.getWait() if (wait > 0) { // Use setTimeout to wait before processing next item - setTimeout(() => this.tick(), wait) + setTimeout(() => this.#tick(), wait) return } - this.tick() + this.#tick() } - this._pendingTick = false + this.#pendingTick = false } /** * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. * Internal use only. */ - private checkExpiredItems() { + #checkExpiredItems() { if ( - this._options.expirationDuration === Infinity && - this._options.getIsExpired === defaultOptions.getIsExpired + (this.#options.expirationDuration ?? Infinity) === Infinity && + this.#options.getIsExpired === defaultOptions.getIsExpired ) return @@ -250,17 +306,17 @@ export class Queuer { const expiredIndices: Array = [] // Find indices of expired items - for (let i = 0; i < this._items.length; i++) { - const timestamp = this._itemTimestamps[i] + for (let i = 0; i < this.#state.items.length; i++) { + const timestamp = this.#state.itemTimestamps[i] if (timestamp === undefined) continue - const item = this._items[i] + const item = this.#state.items[i] if (item === undefined) continue const isExpired = - this._options.getIsExpired !== defaultOptions.getIsExpired - ? this._options.getIsExpired(item, timestamp) - : now - timestamp > this._options.expirationDuration + this.#options.getIsExpired !== defaultOptions.getIsExpired + ? this.#options.getIsExpired!(item, timestamp) + : now - timestamp > (this.#options.expirationDuration ?? Infinity) if (isExpired) { expiredIndices.push(i) @@ -272,17 +328,19 @@ export class Queuer { const index = expiredIndices[i] if (index === undefined) continue - const expiredItem = this._items[index] + const expiredItem = this.#state.items[index] if (expiredItem === undefined) continue - this._items.splice(index, 1) - this._itemTimestamps.splice(index, 1) - this._expirationCount++ - this._options.onExpire(expiredItem, this) + this.#state.items.splice(index, 1) + this.#state.itemTimestamps.splice(index, 1) + this.#setState({ + expirationCount: this.#state.expirationCount + 1, + }) + this.#options.onExpire?.(expiredItem, this) } if (expiredIndices.length > 0) { - this._options.onItemsChange(this) + this.#options.onItemsChange?.(this) } } @@ -290,29 +348,30 @@ export class Queuer { * Stops processing items in the queue. Does not clear the queue. */ stop() { - this._running = false - this._pendingTick = false - this._options.onIsRunningChange(this) + this.#setState({ running: false }) + this.#pendingTick = false + this.#options.onIsRunningChange?.(this) } /** * Starts processing items in the queue. If already running, does nothing. */ start() { - this._running = true - if (!this._pendingTick && !this.getIsEmpty()) { - this._pendingTick = true - this.tick() + this.#setState({ running: true }) + if (!this.#pendingTick && !this.getIsEmpty()) { + this.#pendingTick = true + this.#tick() } - this._options.onIsRunningChange(this) + this.#options.onIsRunningChange?.(this) } /** * Removes all pending items from the queue. Does not affect items being processed. */ clear(): void { - this._items = [] - this._options.onItemsChange(this) + this.#state.items = [] + this.#state.itemTimestamps = [] + this.#options.onItemsChange?.(this) } /** @@ -321,11 +380,16 @@ export class Queuer { */ reset(withInitialItems?: boolean): void { this.clear() - this._executionCount = 0 + this.#setState({ + executionCount: 0, + expirationCount: 0, + rejectionCount: 0, + running: this.#options.started ?? true, + }) if (withInitialItems) { - this._items = [...this._options.initialItems] + this.#state.items = [...(this.#options.initialItems ?? [])] + this.#state.itemTimestamps = this.#state.items.map(() => Date.now()) } - this._running = this._options.started } /** @@ -342,47 +406,53 @@ export class Queuer { */ addItem( item: TValue, - position: QueuePosition = this._options.addItemsTo, + position: QueuePosition = this.#options.addItemsTo ?? 'back', runOnUpdate: boolean = true, ): boolean { if (this.getIsFull()) { - this._rejectionCount++ - this._options.onReject(item, this) + this.#setState({ + rejectionCount: this.#state.rejectionCount + 1, + }) + this.#options.onReject?.(item, this) return false } - if (this._options.getPriority !== defaultOptions.getPriority) { + if (this.#options.getPriority !== defaultOptions.getPriority) { // If custom priority function is provided, insert based on priority - const priority = this._options.getPriority(item) - const insertIndex = this._items.findIndex( - (existing) => this._options.getPriority(existing) < priority, + const priority = this.#options.getPriority!(item) + const insertIndex = this.#state.items.findIndex( + (existing) => this.#options.getPriority!(existing) < priority, ) if (insertIndex === -1) { - this._items.push(item) - this._itemTimestamps.push(Date.now()) + this.#state.items.push(item) + this.#state.itemTimestamps.push(Date.now()) } else { - this._items.splice(insertIndex, 0, item) - this._itemTimestamps.splice(insertIndex, 0, Date.now()) + this.#state.items.splice(insertIndex, 0, item) + this.#state.itemTimestamps.splice(insertIndex, 0, Date.now()) } } else { // Default FIFO/LIFO behavior if (position === 'front') { - this._items.unshift(item) - this._itemTimestamps.unshift(Date.now()) + this.#state.items.unshift(item) + this.#state.itemTimestamps.unshift(Date.now()) } else { - this._items.push(item) - this._itemTimestamps.push(Date.now()) + this.#state.items.push(item) + this.#state.itemTimestamps.push(Date.now()) } } - if (this._running && !this._pendingTick) { - this._pendingTick = true - this.tick() + if (this.#state.running && !this.#pendingTick) { + this.#pendingTick = true + this.#tick() } if (runOnUpdate) { - this._options.onItemsChange(this) + this.#options.onItemsChange?.(this) } + this.#setState({ + items: this.#state.items, + itemTimestamps: this.#state.itemTimestamps, + }) return true } @@ -399,20 +469,20 @@ export class Queuer { * ``` */ getNextItem( - position: QueuePosition = this._options.getItemsFrom, + position: QueuePosition = this.#options.getItemsFrom ?? 'front', ): TValue | undefined { let item: TValue | undefined if (position === 'front') { - item = this._items.shift() - this._itemTimestamps.shift() + item = this.#state.items.shift() + this.#state.itemTimestamps.shift() } else { - item = this._items.pop() - this._itemTimestamps.pop() + item = this.#state.items.pop() + this.#state.itemTimestamps.pop() } if (item !== undefined) { - this._options.onItemsChange(this) + this.#options.onItemsChange?.(this) } return item @@ -432,8 +502,10 @@ export class Queuer { const item = this.getNextItem(position) if (item !== undefined) { this.fn(item) - this._executionCount++ - this._options.onExecute(item, this) + this.#setState({ + executionCount: this.#state.executionCount + 1, + }) + this.#options.onExecute?.(item, this) } return item } @@ -448,75 +520,75 @@ export class Queuer { * ``` */ peekNextItem( - position: QueuePosition = this._options.getItemsFrom, + position: QueuePosition = this.#options.getItemsFrom ?? 'front', ): TValue | undefined { if (position === 'front') { - return this._items[0] + return this.#state.items[0] } - return this._items[this._items.length - 1] + return this.#state.items[this.#state.items.length - 1] } /** * Returns true if the queue is empty (no pending items). */ getIsEmpty(): boolean { - return this._items.length === 0 + return this.#state.items.length === 0 } /** * Returns true if the queue is full (reached maxSize). */ getIsFull(): boolean { - return this._items.length >= this._options.maxSize + return this.#state.items.length >= (this.#options.maxSize ?? Infinity) } /** * Returns the number of pending items in the queue. */ getSize(): number { - return this._items.length + return this.#state.items.length } /** * Returns a copy of all items in the queue. */ peekAllItems(): Array { - return [...this._items] + return [...this.#state.items] } /** * Returns the number of items that have been processed and removed from the queue. */ getExecutionCount(): number { - return this._executionCount + return this.#state.executionCount } /** * Returns the number of items that have been rejected from being added to the queue. */ getRejectionCount(): number { - return this._rejectionCount + return this.#state.rejectionCount } /** * Returns the number of items that have expired and been removed from the queue. */ getExpirationCount(): number { - return this._expirationCount + return this.#state.expirationCount } /** * Returns true if the queuer is currently running (processing items). */ getIsRunning() { - return this._running + return this.#state.running } /** * Returns true if the queuer is running but has no items to process. */ getIsIdle() { - return this._running && this.getIsEmpty() + return this.#state.running && this.getIsEmpty() } } @@ -528,6 +600,11 @@ export class Queuer { * `addItem` method. The queue is always running and will process items as they are added. * For more control over queue processing, use the Queuer class directly. * + * State Management: + * - Use `initialState` to provide initial state values when creating the queuer + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution count, expiration count, rejection count, and running status + * * Example usage: * ```ts * // Basic sequential processing diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index c28b89e36..6bad8cb00 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -1,9 +1,6 @@ import { parseFunctionOrValue } from './utils' import type { AnyFunction } from './types' -/** - * State shape for persisting RateLimiter - */ export interface RateLimiterState { executionCount: number executionTimes: Array diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index bc4552b88..f830c2a91 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -1,6 +1,12 @@ import { parseFunctionOrValue } from './utils' import type { AnyFunction } from './types' +export interface ThrottlerState { + executionCount: number + lastArgs: Parameters | undefined + lastExecutionTime: number +} + /** * Options for configuring a throttled function */ @@ -11,6 +17,10 @@ export interface ThrottlerOptions { * Defaults to true. */ enabled?: boolean | ((throttler: Throttler) => boolean) + /** + * Initial state for the throttler + */ + initialState?: Partial> /** * Whether to execute on the leading edge of the timeout. * Defaults to true. @@ -20,6 +30,13 @@ export interface ThrottlerOptions { * Callback function that is called after the function is executed */ onExecute?: (throttler: Throttler) => void + /** + * Callback function that is called when the state of the throttler is updated + */ + onStateChange?: ( + state: ThrottlerState, + throttler: Throttler, + ) => void /** * Whether to execute on the trailing edge of the timeout. * Defaults to true. @@ -33,10 +50,12 @@ export interface ThrottlerOptions { wait: number | ((throttler: Throttler) => number) } -const defaultOptions: Required> = { +const defaultOptions: Omit< + Required>, + 'initialState' | 'onStateChange' | 'onExecute' +> = { enabled: true, leading: true, - onExecute: () => {}, trailing: true, wait: 0, } @@ -54,6 +73,12 @@ const defaultOptions: Required> = { * * For collapsing rapid-fire events where you only care about the last call, consider using Debouncer. * + * State Management: + * - Use `initialState` to provide initial state values when creating the throttler + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution count and last execution time + * - State can be retrieved using `getState()` method + * * @example * ```ts * const throttler = new Throttler( @@ -69,30 +94,36 @@ const defaultOptions: Required> = { * ``` */ export class Throttler { - private _executionCount = 0 - private _lastArgs: Parameters | undefined - private _lastExecutionTime = 0 - private _options: Required> - private _timeoutId: NodeJS.Timeout | undefined + #options: ThrottlerOptions + #state: ThrottlerState = { + executionCount: 0, + lastArgs: undefined, + lastExecutionTime: 0, + } + #timeoutId: NodeJS.Timeout | undefined constructor( private fn: TFn, initialOptions: ThrottlerOptions, ) { - this._options = { + this.#options = { ...defaultOptions, ...initialOptions, } + this.#state = { + ...this.#state, + ...this.#options.initialState, + } } /** * Updates the throttler options */ setOptions(newOptions: Partial>): void { - this._options = { ...this._options, ...newOptions } + this.#options = { ...this.#options, ...newOptions } - // End the pending state if the debouncer is disabled - if (!this._options.enabled) { + // End the pending state if the throttler is disabled + if (!this.getEnabled()) { this.cancel() } } @@ -101,21 +132,36 @@ export class Throttler { * Returns the current throttler options */ getOptions(): Required> { - return this._options + return this.#options as Required> + } + + /** + * Returns the current state for persistence + */ + getState(): ThrottlerState { + return { ...this.#state } + } + + /** + * Loads state from a persisted object or updates state with a partial + */ + #setState(state: Partial>): void { + this.#state = { ...this.#state, ...state } + this.#options.onStateChange?.(this.#state, this) } /** * Returns the current enabled state of the throttler */ getEnabled(): boolean { - return parseFunctionOrValue(this._options.enabled, this) + return parseFunctionOrValue(this.#options.enabled, this)! } /** * Returns the current wait time in milliseconds */ getWait(): number { - return parseFunctionOrValue(this._options.wait, this) + return parseFunctionOrValue(this.#options.wait, this) } /** @@ -142,39 +188,44 @@ export class Throttler { */ maybeExecute(...args: Parameters): void { const now = Date.now() - const timeSinceLastExecution = now - this._lastExecutionTime + const timeSinceLastExecution = now - this.#state.lastExecutionTime const wait = this.getWait() // Handle leading execution - if (this._options.leading && timeSinceLastExecution >= wait) { - this.execute(...args) + if (this.#options.leading && timeSinceLastExecution >= wait) { + this.#execute(...args) } else { // Store the most recent arguments for potential trailing execution - this._lastArgs = args - + this.#setState({ + lastArgs: args, + }) // Set up trailing execution if not already scheduled - if (!this._timeoutId && this._options.trailing) { - const _timeSinceLastExecution = this._lastExecutionTime - ? now - this._lastExecutionTime + if (!this.#timeoutId && this.#options.trailing) { + const _timeSinceLastExecution = this.#state.lastExecutionTime + ? now - this.#state.lastExecutionTime : 0 const timeoutDuration = wait - _timeSinceLastExecution - this._timeoutId = setTimeout(() => { - if (this._lastArgs !== undefined) { - this.execute(...this._lastArgs) + this.#timeoutId = setTimeout(() => { + if (this.#state.lastArgs !== undefined) { + this.#execute(...this.#state.lastArgs) } }, timeoutDuration) } } } - private execute(...args: Parameters): void { + #execute(...args: Parameters): void { if (!this.getEnabled()) return this.fn(...args) // EXECUTE! - this._executionCount++ - this._lastExecutionTime = Date.now() - this._timeoutId = undefined - this._lastArgs = undefined - this._options.onExecute(this) + this.#setState({ + executionCount: this.#state.executionCount + 1, + lastExecutionTime: Date.now(), + }) + this.#timeoutId = undefined + this.#setState({ + lastArgs: undefined, + }) + this.#options.onExecute?.(this) } /** @@ -187,10 +238,12 @@ export class Throttler { * Has no effect if there is no pending execution. */ cancel(): void { - if (this._timeoutId) { - clearTimeout(this._timeoutId) - this._timeoutId = undefined - this._lastArgs = undefined + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = undefined + this.#setState({ + lastArgs: undefined, + }) } } @@ -198,28 +251,28 @@ export class Throttler { * Returns the last execution time */ getLastExecutionTime(): number { - return this._lastExecutionTime + return this.#state.lastExecutionTime } /** * Returns the next execution time */ getNextExecutionTime(): number { - return this._lastExecutionTime + this.getWait() + return this.#state.lastExecutionTime + this.getWait() } /** * Returns the number of times the function has been executed */ getExecutionCount(): number { - return this._executionCount + return this.#state.executionCount } /** * Returns `true` if there is a pending execution */ getIsPending(): boolean { - return this.getEnabled() && !!this._timeoutId + return this.getEnabled() && !!this.#timeoutId } } @@ -236,6 +289,11 @@ export class Throttler { * For handling bursts of events, consider using debounce() instead. For hard execution * limits, consider using rateLimit(). * + * State Management: + * - Use `initialState` to provide initial state values when creating the throttler + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes execution count and last execution time + * * @example * ```ts * // Basic throttling - max once per second diff --git a/packages/pacer/tests/queuer.test.ts b/packages/pacer/tests/queuer.test.ts index 65036e474..099ff82da 100644 --- a/packages/pacer/tests/queuer.test.ts +++ b/packages/pacer/tests/queuer.test.ts @@ -404,18 +404,6 @@ describe('Queuer', () => { queuer.addItem(2) expect(onReject).toHaveBeenCalledWith(2, queuer) }) - it('should call onExpire when an item expires', () => { - const onExpire = vi.fn() - const fn = vi.fn() - const queuer = new Queuer(fn, { - expirationDuration: 0, - onExpire, - started: false, - }) - queuer.addItem(1) - queuer['checkExpiredItems']() - expect(onExpire).not.toHaveBeenCalled() - }) it('should call onExecute when an item is executed', () => { const onExecute = vi.fn() const fn = vi.fn() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a9ddd856..958b87433 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -679,6 +679,9 @@ importers: '@tanstack/react-pacer': specifier: ^0.8.0 version: link:../../../packages/react-pacer + '@tanstack/react-persister': + specifier: ^0.0.0 + version: link:../../../packages/react-persister react: specifier: ^19.1.0 version: 19.1.0 From acae915a9025db70aea8980055aab060f9353ade Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Jun 2025 19:22:20 -0500 Subject: [PATCH 11/51] jsdoc updates --- packages/pacer/src/async-debouncer.ts | 10 ++-------- packages/pacer/src/async-queuer.ts | 10 ++-------- packages/pacer/src/async-rate-limiter.ts | 10 ++-------- packages/pacer/src/async-throttler.ts | 10 ++-------- packages/pacer/src/batcher.ts | 10 ++-------- packages/pacer/src/debouncer.ts | 10 ++-------- packages/pacer/src/queuer.ts | 10 ++-------- packages/pacer/src/rate-limiter.ts | 10 ++-------- packages/pacer/src/throttler.ts | 10 ++-------- packages/persister/src/storage-persister.ts | 6 ++++++ 10 files changed, 24 insertions(+), 72 deletions(-) diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index f393a9f5f..9ee7f5eb8 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -178,18 +178,12 @@ export class AsyncDebouncer { return this.#options } - /** - * Returns the current state for persistence - */ getState(): AsyncDebouncerState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index bcef343fa..3fe474b2f 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -245,18 +245,12 @@ export class AsyncQueuer { return this.#options } - /** - * Returns the current state for persistence - */ getState(): AsyncQueuerState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 114ce5c93..d2e373b90 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -204,18 +204,12 @@ export class AsyncRateLimiter { return this.#options as Required> } - /** - * Returns the current state for persistence - */ getState(): AsyncRateLimiterState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/async-throttler.ts b/packages/pacer/src/async-throttler.ts index 3cf207ee7..22c2c4012 100644 --- a/packages/pacer/src/async-throttler.ts +++ b/packages/pacer/src/async-throttler.ts @@ -182,18 +182,12 @@ export class AsyncThrottler { return this.#options } - /** - * Returns the current state for persistence - */ getState(): AsyncThrottlerState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index 94dfd0306..b897053e4 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -144,18 +144,12 @@ export class Batcher { return this.#options } - /** - * Returns the current state for persistence - */ getState(): BatcherState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index b928964c4..0ccd7b6ac 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -134,18 +134,12 @@ export class Debouncer { return this.#options as Required> } - /** - * Returns the current state for persistence - */ getState(): DebouncerState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index 3f9debd7c..316031866 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -237,18 +237,12 @@ export class Queuer { return this.#options as Required> } - /** - * Returns the current state for persistence - */ getState(): QueuerState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index 6bad8cb00..22abff4ec 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -141,18 +141,12 @@ export class RateLimiter { return this.#options as Required> } - /** - * Returns the current state for persistence - */ getState(): RateLimiterState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index f830c2a91..51ec6b28e 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -135,18 +135,12 @@ export class Throttler { return this.#options as Required> } - /** - * Returns the current state for persistence - */ getState(): ThrottlerState { return { ...this.#state } } - /** - * Loads state from a persisted object or updates state with a partial - */ - #setState(state: Partial>): void { - this.#state = { ...this.#state, ...state } + #setState(newState: Partial>): void { + this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } diff --git a/packages/persister/src/storage-persister.ts b/packages/persister/src/storage-persister.ts index fd2646e6c..382c4464f 100644 --- a/packages/persister/src/storage-persister.ts +++ b/packages/persister/src/storage-persister.ts @@ -138,6 +138,9 @@ export class StoragePersister extends Persister { return this._options } + /** + * Saves the state to storage + */ saveState(state: TState): void { try { const stateToSave = this._options.stateTransform @@ -158,6 +161,9 @@ export class StoragePersister extends Persister { } } + /** + * Loads the state from storage + */ loadState(): TState | undefined { const stored = this._options.storage.getItem(this.key) if (!stored) { From 5613df7bdcc96ab353fec4976b079d2cd3489b6d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 22 Jun 2025 00:23:27 +0000 Subject: [PATCH 12/51] ci: apply automated fixes --- docs/reference/classes/storagepersister.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md index 64c497bbc..7353d7ac1 100644 --- a/docs/reference/classes/storagepersister.md +++ b/docs/reference/classes/storagepersister.md @@ -112,7 +112,9 @@ Returns the current persister options loadState(): undefined | TState ``` -Defined in: [storage-persister.ts:161](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L161) +Defined in: [storage-persister.ts:167](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L167) + +Loads the state from storage #### Returns @@ -130,7 +132,9 @@ Defined in: [storage-persister.ts:161](https://github.com/TanStack/pacer/blob/ma saveState(state): void ``` -Defined in: [storage-persister.ts:141](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L141) +Defined in: [storage-persister.ts:144](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L144) + +Saves the state to storage #### Parameters From d37d647085acf3b1fa7e305cc2535b7d6c0e4d35 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 21 Jun 2025 19:36:53 -0500 Subject: [PATCH 13/51] fix truty to boolean --- examples/react/useAsyncThrottler/src/index.tsx | 4 ++-- examples/react/useThrottler/src/index.tsx | 7 ++++--- packages/pacer/src/throttler.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/react/useAsyncThrottler/src/index.tsx b/examples/react/useAsyncThrottler/src/index.tsx index c5d28640e..1e305da6f 100644 --- a/examples/react/useAsyncThrottler/src/index.tsx +++ b/examples/react/useAsyncThrottler/src/index.tsx @@ -40,8 +40,8 @@ function App() { // hook that gives you an async throttler instance const setSearchAsyncThrottler = useAsyncThrottler(handleSearch, { - // leading: false, - // trailing: false, + // leading: true, // default + // trailing: true, // default wait: 1000, // Wait 1 second between API calls onError: (error) => { // optional error handler diff --git a/examples/react/useThrottler/src/index.tsx b/examples/react/useThrottler/src/index.tsx index 2c4e9fb17..b101ffa81 100644 --- a/examples/react/useThrottler/src/index.tsx +++ b/examples/react/useThrottler/src/index.tsx @@ -10,8 +10,8 @@ function App1() { // Lower-level useThrottler hook - requires you to manage your own state const setCountThrottler = useThrottler(setThrottledCount, { wait: 1000, - leading: false, - // trailing: true, + // leading: true, // default + // trailing: true, // default // enabled: () => instantCount > 2, }) @@ -107,7 +107,8 @@ function App3() { // Lower-level useThrottler hook - requires you to manage your own state const setValueThrottler = useThrottler(setThrottledValue, { wait: 250, - leading: false, + // leading: true, // default + // trailing: true, // default }) function handleRangeChange(e: React.ChangeEvent) { diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index 51ec6b28e..5a7ae4a48 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -148,7 +148,7 @@ export class Throttler { * Returns the current enabled state of the throttler */ getEnabled(): boolean { - return parseFunctionOrValue(this.#options.enabled, this)! + return !!parseFunctionOrValue(this.#options.enabled, this) } /** From 652f226eddfd53ad7bdf12dff8ecaed8527e83f9 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 23 Jun 2025 08:38:12 -0500 Subject: [PATCH 14/51] clean up state mutations and generated changesets --- .changeset/quick-teams-happen.md | 7 +++++++ .changeset/sharp-mirrors-clap.md | 6 ++++++ packages/pacer/src/async-debouncer.ts | 7 +++++-- packages/pacer/src/async-throttler.ts | 14 ++++++++------ packages/pacer/src/queuer.ts | 12 ++++++++---- 5 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 .changeset/quick-teams-happen.md create mode 100644 .changeset/sharp-mirrors-clap.md diff --git a/.changeset/quick-teams-happen.md b/.changeset/quick-teams-happen.md new file mode 100644 index 000000000..77b8b20da --- /dev/null +++ b/.changeset/quick-teams-happen.md @@ -0,0 +1,7 @@ +--- +'@tanstack/react-pacer': minor +'@tanstack/solid-pacer': minor +'@tanstack/pacer': minor +--- + +Introduced initialState and onStateChange options to allow for state persistence diff --git a/.changeset/sharp-mirrors-clap.md b/.changeset/sharp-mirrors-clap.md new file mode 100644 index 000000000..f9fe10b4a --- /dev/null +++ b/.changeset/sharp-mirrors-clap.md @@ -0,0 +1,6 @@ +--- +'@tanstack/react-persister': minor +'@tanstack/persister': minor +--- + +Created TanStack Persister packages diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 9ee7f5eb8..eebd950d7 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -298,8 +298,11 @@ export class AsyncDebouncer { this.#resolvePreviousPromise(this.#state.lastResult) this.#resolvePreviousPromise = null } - this.#state.lastArgs = undefined - this.#setState({ isPending: false, isExecuting: false }) + this.#setState({ + isPending: false, + isExecuting: false, + lastArgs: undefined, + }) } /** diff --git a/packages/pacer/src/async-throttler.ts b/packages/pacer/src/async-throttler.ts index 22c2c4012..497abc100 100644 --- a/packages/pacer/src/async-throttler.ts +++ b/packages/pacer/src/async-throttler.ts @@ -234,7 +234,7 @@ export class AsyncThrottler { return this.#state.lastResult } else { // Store the most recent arguments for potential trailing execution - this.#state.lastArgs = args + this.#setState({ lastArgs: args }) return new Promise((resolve) => { this.#resolvePreviousPromise = resolve @@ -268,8 +268,9 @@ export class AsyncThrottler { this.#abortController = new AbortController() try { this.#setState({ isExecuting: true }) - this.#state.lastResult = await this.fn(...args) // EXECUTE! + const result = await this.fn(...args) // EXECUTE! this.#setState({ + lastResult: result, successCount: this.#state.successCount + 1, }) this.#options.onSuccess?.(this.#state.lastResult!, this) @@ -284,13 +285,14 @@ export class AsyncThrottler { console.error(error) } } finally { + const lastExecutionTime = Date.now() + const nextExecutionTime = lastExecutionTime + this.getWait() this.#setState({ isExecuting: false, settleCount: this.#state.settleCount + 1, - lastExecutionTime: Date.now(), + lastExecutionTime, + nextExecutionTime, }) - this.#state.nextExecutionTime = - this.#state.lastExecutionTime + this.getWait() this.#abortController = null this.#options.onSettled?.(this) } @@ -317,7 +319,7 @@ export class AsyncThrottler { this.#abortController = null } this.#resolvePreviousPromiseInternal() - this.#state.lastArgs = undefined + this.#setState({ lastArgs: undefined }) } /** diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index 316031866..ff243e13b 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -363,8 +363,10 @@ export class Queuer { * Removes all pending items from the queue. Does not affect items being processed. */ clear(): void { - this.#state.items = [] - this.#state.itemTimestamps = [] + this.#setState({ + items: [], + itemTimestamps: [], + }) this.#options.onItemsChange?.(this) } @@ -381,8 +383,10 @@ export class Queuer { running: this.#options.started ?? true, }) if (withInitialItems) { - this.#state.items = [...(this.#options.initialItems ?? [])] - this.#state.itemTimestamps = this.#state.items.map(() => Date.now()) + this.#setState({ + items: [...(this.#options.initialItems ?? [])], + itemTimestamps: this.#state.items.map(() => Date.now()), + }) } } From 03a5737c7c03bd2a869162bed97ee6f803a05ca0 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 24 Jun 2025 20:55:01 -0500 Subject: [PATCH 15/51] check --- docs/guides/async-debouncing.md | 2 +- docs/guides/debouncing.md | 2 +- .../react/useAsyncDebouncer/src/index.tsx | 19 +-- .../react/useDebouncedState/src/index.tsx | 6 +- .../react/useDebouncedValue/src/index.tsx | 2 +- examples/react/useDebouncer/src/index.tsx | 6 +- .../solid/createAsyncDebouncer/src/index.tsx | 22 ++-- examples/solid/createDebouncer/src/index.tsx | 10 +- examples/solid/createThrottler/src/index.tsx | 10 +- packages/pacer/src/async-debouncer.ts | 71 ++-------- packages/pacer/src/throttler.ts | 20 ++- packages/pacer/tests/async-debouncer.test.ts | 124 +++++++++--------- packages/pacer/tests/debouncer.test.ts | 30 ++--- .../src/async-debouncer/useAsyncDebouncer.ts | 55 +++++++- .../src/debouncer/useDebouncedState.ts | 2 +- .../react-pacer/src/debouncer/useDebouncer.ts | 2 +- .../async-debouncer/createAsyncDebouncer.ts | 59 +++------ .../src/debouncer/createDebouncer.ts | 34 ++--- .../src/throttler/createThrottler.ts | 52 +++----- 19 files changed, 242 insertions(+), 286 deletions(-) diff --git a/docs/guides/async-debouncing.md b/docs/guides/async-debouncing.md index a1ac4abca..5e1aaa394 100644 --- a/docs/guides/async-debouncing.md +++ b/docs/guides/async-debouncing.md @@ -78,7 +78,7 @@ const asyncDebouncer = new AsyncDebouncer(async (value) => { wait: 500, onSuccess: (result, debouncer) => { // Called after each successful execution - console.log('Async function executed', debouncer.getSuccessCount()) + console.log('Async function executed', debouncer.getState().successCount) }, onSettled: (debouncer) => { // Called after each execution attempt diff --git a/docs/guides/debouncing.md b/docs/guides/debouncing.md index 096b0d123..4bcb87eff 100644 --- a/docs/guides/debouncing.md +++ b/docs/guides/debouncing.md @@ -72,7 +72,7 @@ const searchDebouncer = new Debouncer( // Get information about current state console.log(searchDebouncer.getExecutionCount()) // Number of successful executions -console.log(searchDebouncer.getIsPending()) // Whether a call is pending +console.log(searchdebouncer.getState().isPending) // Whether a call is pending // Update options dynamically searchDebouncer.setOptions({ wait: 1000 }) // Increase wait time diff --git a/examples/react/useAsyncDebouncer/src/index.tsx b/examples/react/useAsyncDebouncer/src/index.tsx index 8942276b4..41f60fcb5 100644 --- a/examples/react/useAsyncDebouncer/src/index.tsx +++ b/examples/react/useAsyncDebouncer/src/index.tsx @@ -20,8 +20,6 @@ const fakeApi = async (term: string): Promise> => { function App() { const [searchTerm, setSearchTerm] = useState('') const [results, setResults] = useState>([]) - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState(null) // The function that will become debounced const handleSearch = async (term: string) => { @@ -32,14 +30,8 @@ function App() { // throw new Error('Test error') // you don't have to catch errors here (though you still can). The onError optional handler will catch it - if (!results.length) { - setIsLoading(true) - } - const data = await fakeApi(term) setResults(data) - setIsLoading(false) - setError(null) return data // this could alternatively be a void function without a return } @@ -47,11 +39,11 @@ function App() { // hook that gives you an async debouncer instance const setSearchAsyncDebouncer = useAsyncDebouncer(handleSearch, { // leading: true, // optional leading execution + // enableStateRerenders: false, // turn off state rerenders if you don't care about the state wait: 500, // Wait 500ms between API calls onError: (error) => { // optional error handler console.error('Search failed:', error) - setError(error as Error) setResults([]) }, }) @@ -81,9 +73,8 @@ function App() { autoComplete="new-password" />
- {error &&
Error: {error.message}
}
-

API calls made: {setSearchAsyncDebouncer.getSuccessCount()}

+

API calls made: {setSearchAsyncDebouncer.state.successCount}

{results.length > 0 && (
    {results.map((item) => ( @@ -91,7 +82,11 @@ function App() { ))}
)} - {isLoading &&

Loading...

} + {setSearchAsyncDebouncer.state.isPending &&

Pending...

} + {setSearchAsyncDebouncer.state.isExecuting &&

Executing...

} +
+          {JSON.stringify({ state: setSearchAsyncDebouncer.state }, null, 2)}
+        
) diff --git a/examples/react/useDebouncedState/src/index.tsx b/examples/react/useDebouncedState/src/index.tsx index d03db81a7..0fe239af1 100644 --- a/examples/react/useDebouncedState/src/index.tsx +++ b/examples/react/useDebouncedState/src/index.tsx @@ -36,7 +36,7 @@ function App1() {
- + @@ -103,7 +103,7 @@ function App2() { - + @@ -186,7 +186,7 @@ function App3() { - + diff --git a/examples/react/useDebouncedValue/src/index.tsx b/examples/react/useDebouncedValue/src/index.tsx index 4ff2dfa90..0fa25886a 100644 --- a/examples/react/useDebouncedValue/src/index.tsx +++ b/examples/react/useDebouncedValue/src/index.tsx @@ -135,7 +135,7 @@ function App3() { - + diff --git a/examples/react/useDebouncer/src/index.tsx b/examples/react/useDebouncer/src/index.tsx index 4a149cb2f..b01a37ecc 100644 --- a/examples/react/useDebouncer/src/index.tsx +++ b/examples/react/useDebouncer/src/index.tsx @@ -34,7 +34,7 @@ function App1() { - + @@ -99,7 +99,7 @@ function App2() { - + @@ -180,7 +180,7 @@ function App3() { - + diff --git a/examples/solid/createAsyncDebouncer/src/index.tsx b/examples/solid/createAsyncDebouncer/src/index.tsx index 7412c719d..0c11fc1b3 100644 --- a/examples/solid/createAsyncDebouncer/src/index.tsx +++ b/examples/solid/createAsyncDebouncer/src/index.tsx @@ -20,8 +20,6 @@ const fakeApi = async (term: string): Promise> => { function App() { const [searchTerm, setSearchTerm] = createSignal('') const [results, setResults] = createSignal>([]) - const [isLoading, setIsLoading] = createSignal(false) - const [error, setError] = createSignal(null) // The function that will become debounced const handleSearch = async (term: string) => { @@ -32,14 +30,8 @@ function App() { // throw new Error('Test error') // you don't have to catch errors here (though you still can). The onError optional handler will catch it - if (!results.length) { - setIsLoading(true) - } - const data = await fakeApi(term) setResults(data) // option 1: set results immediately - setIsLoading(false) - setError(null) return data // option 2: return data if you need to } @@ -51,7 +43,6 @@ function App() { onError: (error) => { // optional error handler console.error('Search failed:', error) - setError(error as Error) setResults([]) }, }) @@ -81,15 +72,22 @@ function App() { autocomplete="new-password" /> - {error() &&
Error: {error()?.message}
} + {setSearchAsyncDebouncer.store.errorCount > 0 && ( +
Errors: {setSearchAsyncDebouncer.store.errorCount}
+ )}
-

API calls made: {setSearchAsyncDebouncer.successCount()}

+

API calls made: {setSearchAsyncDebouncer.store.successCount}

{results().length > 0 && (
    {(item) =>
  • {item.title}
  • }
)} - {isLoading() &&

Loading...

} + {setSearchAsyncDebouncer.store.isExecuting &&

Executing...

} + {setSearchAsyncDebouncer.store.isPending &&

Pending...

} +
+
+          {JSON.stringify({ store: setSearchAsyncDebouncer.store }, null, 2)}
+        
) diff --git a/examples/solid/createDebouncer/src/index.tsx b/examples/solid/createDebouncer/src/index.tsx index 8349c7dda..8cb7fecb7 100644 --- a/examples/solid/createDebouncer/src/index.tsx +++ b/examples/solid/createDebouncer/src/index.tsx @@ -30,7 +30,7 @@ function App1() {
- + - + - + @@ -181,7 +181,7 @@ function App3() { ? '0' : Math.round( ((instantExecutionCount() - - setValueDebouncer.executionCount()) / + setValueDebouncer.store.executionCount) / instantExecutionCount()) * 100, )} diff --git a/examples/solid/createThrottler/src/index.tsx b/examples/solid/createThrottler/src/index.tsx index 5a23d5d80..f57d0a41e 100644 --- a/examples/solid/createThrottler/src/index.tsx +++ b/examples/solid/createThrottler/src/index.tsx @@ -29,7 +29,7 @@ function App1() { - + @@ -82,7 +82,7 @@ function App2() { - + @@ -155,12 +155,12 @@ function App3() { - + @@ -170,7 +170,7 @@ function App3() { ? '0' : Math.round( ((instantExecutionCount() - - setValueThrottler.executionCount()) / + setValueThrottler.store.executionCount) / instantExecutionCount()) * 100, )} diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index eebd950d7..9ba15a24d 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -166,7 +166,7 @@ export class AsyncDebouncer { this.#options = { ...this.#options, ...newOptions } // End the pending state if the debouncer is disabled - if (!this.getEnabled()) { + if (!this.#getEnabled()) { this.#setState({ isPending: false }) } } @@ -190,14 +190,14 @@ export class AsyncDebouncer { /** * Returns the current debouncer enabled state */ - getEnabled(): boolean { + #getEnabled(): boolean { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current debouncer wait state */ - getWait(): number { + #getWait(): number { return parseFunctionOrValue(this.#options.wait, this) } @@ -218,7 +218,7 @@ export class AsyncDebouncer { async maybeExecute( ...args: Parameters ): Promise | undefined> { - this.#cancel() + this.#cancelPendingExecution() this.#setState({ lastArgs: args }) // Handle leading execution @@ -229,7 +229,7 @@ export class AsyncDebouncer { } // Handle trailing execution - if (this.#options.trailing) { + if (this.#options.trailing && this.#getEnabled()) { this.#setState({ isPending: true }) } @@ -245,14 +245,14 @@ export class AsyncDebouncer { this.#setState({ canLeadingExecute: true }) this.#resolvePreviousPromise = null resolve(this.#state.lastResult) - }, this.getWait()) + }, this.#getWait()) }) } async #execute( ...args: Parameters ): Promise | undefined> { - if (!this.getEnabled()) return undefined + if (!this.#getEnabled()) return undefined this.#abortController = new AbortController() try { this.#setState({ isExecuting: true }) @@ -282,18 +282,11 @@ export class AsyncDebouncer { return this.#state.lastResult } - /** - * Cancel without resetting canLeadingExecute - */ - #cancel(): void { + #cancelPendingExecution(): void { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = null } - if (this.#abortController) { - this.#abortController.abort() - this.#abortController = null - } if (this.#resolvePreviousPromise) { this.#resolvePreviousPromise(this.#state.lastResult) this.#resolvePreviousPromise = null @@ -309,50 +302,12 @@ export class AsyncDebouncer { * Cancels any pending execution or aborts any execution in progress */ cancel(): void { + this.#cancelPendingExecution() + if (this.#abortController) { + this.#abortController.abort() + this.#abortController = null + } this.#setState({ canLeadingExecute: true }) - this.#cancel() - } - - /** - * Returns the last result of the debounced function - */ - getLastResult(): ReturnType | undefined { - return this.#state.lastResult - } - - /** - * Returns the number of times the function has been executed successfully - */ - getSuccessCount(): number { - return this.#state.successCount - } - - /** - * Returns the number of times the function has settled (completed or errored) - */ - getSettleCount(): number { - return this.#state.settleCount - } - - /** - * Returns the number of times the function has errored - */ - getErrorCount(): number { - return this.#state.errorCount - } - - /** - * Returns `true` if there is a pending execution queued up for trailing execution - */ - getIsPending(): boolean { - return this.getEnabled() && this.#state.isPending - } - - /** - * Returns `true` if there is currently an execution in progress - */ - getIsExecuting(): boolean { - return this.#state.isExecuting } } diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index 5a7ae4a48..737840a47 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -5,6 +5,8 @@ export interface ThrottlerState { executionCount: number lastArgs: Parameters | undefined lastExecutionTime: number + nextExecutionTime: number + isPending: boolean } /** @@ -96,9 +98,11 @@ const defaultOptions: Omit< export class Throttler { #options: ThrottlerOptions #state: ThrottlerState = { + isPending: false, executionCount: 0, lastArgs: undefined, lastExecutionTime: 0, + nextExecutionTime: 0, } #timeoutId: NodeJS.Timeout | undefined @@ -210,13 +214,16 @@ export class Throttler { #execute(...args: Parameters): void { if (!this.getEnabled()) return + this.#setState({ isPending: true }) this.fn(...args) // EXECUTE! - this.#setState({ - executionCount: this.#state.executionCount + 1, - lastExecutionTime: Date.now(), - }) + const lastExecutionTime = Date.now() + const nextExecutionTime = lastExecutionTime + this.getWait() this.#timeoutId = undefined this.#setState({ + executionCount: this.#state.executionCount + 1, + lastExecutionTime, + nextExecutionTime, + isPending: false, lastArgs: undefined, }) this.#options.onExecute?.(this) @@ -237,6 +244,7 @@ export class Throttler { this.#timeoutId = undefined this.#setState({ lastArgs: undefined, + isPending: false, }) } } @@ -252,7 +260,7 @@ export class Throttler { * Returns the next execution time */ getNextExecutionTime(): number { - return this.#state.lastExecutionTime + this.getWait() + return this.#state.nextExecutionTime } /** @@ -266,7 +274,7 @@ export class Throttler { * Returns `true` if there is a pending execution */ getIsPending(): boolean { - return this.getEnabled() && !!this.#timeoutId + return this.#state.isPending } } diff --git a/packages/pacer/tests/async-debouncer.test.ts b/packages/pacer/tests/async-debouncer.test.ts index 934753fb1..15cf86985 100644 --- a/packages/pacer/tests/async-debouncer.test.ts +++ b/packages/pacer/tests/async-debouncer.test.ts @@ -345,9 +345,9 @@ describe('AsyncDebouncer', () => { expect(onError).toBeCalledWith(error, debouncer) expect(onSettled).toBeCalledWith(debouncer) - expect(debouncer.getErrorCount()).toBe(1) - expect(debouncer.getSettleCount()).toBe(1) - expect(debouncer.getSuccessCount()).toBe(0) + expect(debouncer.getState().errorCount).toBe(1) + expect(debouncer.getState().settleCount).toBe(1) + expect(debouncer.getState().successCount).toBe(0) }) it('should not break debouncing chain on error', async () => { @@ -367,9 +367,9 @@ describe('AsyncDebouncer', () => { vi.advanceTimersByTime(1000) await promise1 expect(onError).toBeCalledWith(error, debouncer) - expect(debouncer.getErrorCount()).toBe(1) - expect(debouncer.getSettleCount()).toBe(1) - expect(debouncer.getSuccessCount()).toBe(0) + expect(debouncer.getState().errorCount).toBe(1) + expect(debouncer.getState().settleCount).toBe(1) + expect(debouncer.getState().successCount).toBe(0) // Second call - should succeed const promise2 = debouncer.maybeExecute() @@ -377,9 +377,9 @@ describe('AsyncDebouncer', () => { const result = await promise2 expect(result).toBe('success') expect(mockFn).toBeCalledTimes(2) - expect(debouncer.getErrorCount()).toBe(1) - expect(debouncer.getSettleCount()).toBe(2) - expect(debouncer.getSuccessCount()).toBe(1) + expect(debouncer.getState().errorCount).toBe(1) + expect(debouncer.getState().settleCount).toBe(2) + expect(debouncer.getState().successCount).toBe(1) }) it('should handle multiple errors in sequence', async () => { @@ -400,18 +400,18 @@ describe('AsyncDebouncer', () => { vi.advanceTimersByTime(1000) await promise1 expect(onError).toHaveBeenNthCalledWith(1, error1, debouncer) - expect(debouncer.getErrorCount()).toBe(1) - expect(debouncer.getSettleCount()).toBe(1) - expect(debouncer.getSuccessCount()).toBe(0) + expect(debouncer.getState().errorCount).toBe(1) + expect(debouncer.getState().settleCount).toBe(1) + expect(debouncer.getState().successCount).toBe(0) // Second call - should error again const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 expect(onError).toHaveBeenNthCalledWith(2, error2, debouncer) - expect(debouncer.getErrorCount()).toBe(2) - expect(debouncer.getSettleCount()).toBe(2) - expect(debouncer.getSuccessCount()).toBe(0) + expect(debouncer.getState().errorCount).toBe(2) + expect(debouncer.getState().settleCount).toBe(2) + expect(debouncer.getState().successCount).toBe(0) }) it('should handle errors during leading execution', async () => { @@ -431,9 +431,9 @@ describe('AsyncDebouncer', () => { await promise expect(onError).toBeCalledWith(error, debouncer) expect(onSettled).toBeCalledWith(debouncer) - expect(debouncer.getErrorCount()).toBe(1) - expect(debouncer.getSettleCount()).toBe(1) - expect(debouncer.getSuccessCount()).toBe(0) + expect(debouncer.getState().errorCount).toBe(1) + expect(debouncer.getState().settleCount).toBe(1) + expect(debouncer.getState().successCount).toBe(0) }) it('should handle errors during trailing execution', async () => { @@ -453,9 +453,9 @@ describe('AsyncDebouncer', () => { await promise expect(onError).toBeCalledWith(error, debouncer) expect(onSettled).toBeCalledWith(debouncer) - expect(debouncer.getErrorCount()).toBe(1) - expect(debouncer.getSettleCount()).toBe(1) - expect(debouncer.getSuccessCount()).toBe(0) + expect(debouncer.getState().errorCount).toBe(1) + expect(debouncer.getState().settleCount).toBe(1) + expect(debouncer.getState().successCount).toBe(0) }) it('should maintain state after error', async () => { @@ -473,10 +473,10 @@ describe('AsyncDebouncer', () => { await promise1 // Verify state is maintained - expect(debouncer.getErrorCount()).toBe(1) - expect(debouncer.getSettleCount()).toBe(1) - expect(debouncer.getSuccessCount()).toBe(0) - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().errorCount).toBe(1) + expect(debouncer.getState().settleCount).toBe(1) + expect(debouncer.getState().successCount).toBe(0) + expect(debouncer.getState().isPending).toBe(false) }) }) @@ -591,11 +591,11 @@ describe('AsyncDebouncer', () => { // Start execution debouncer.maybeExecute() - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Cancel before wait period ends debouncer.cancel() - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -616,7 +616,7 @@ describe('AsyncDebouncer', () => { // Cancel and verify canLeadingExecute is reset debouncer.cancel() - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) // Next call should execute immediately again const promise2 = debouncer.maybeExecute('second') @@ -640,7 +640,7 @@ describe('AsyncDebouncer', () => { // Cancel during leading execution debouncer.cancel() - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) // Next call should execute immediately again const promise2 = debouncer.maybeExecute('second') @@ -660,7 +660,7 @@ describe('AsyncDebouncer', () => { vi.advanceTimersByTime(1000) await promise - expect(debouncer.getLastResult()).toBe('result') + expect(debouncer.getState().lastResult).toBe('result') }) it('should return last result when execution is pending', async () => { @@ -671,11 +671,11 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getLastResult()).toBe('result') + expect(debouncer.getState().lastResult).toBe('result') // Second execution - should still return last result while pending const promise2 = debouncer.maybeExecute() - expect(debouncer.getLastResult()).toBe('result') + expect(debouncer.getState().lastResult).toBe('result') vi.advanceTimersByTime(1000) await promise2 }) @@ -688,18 +688,18 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getLastResult()).toBe('result') + expect(debouncer.getState().lastResult).toBe('result') // Second execution with cancellation debouncer.maybeExecute() debouncer.cancel() - expect(debouncer.getLastResult()).toBe('result') // Should still have last result + expect(debouncer.getState().lastResult).toBe('result') // Should still have last result // Third execution const promise3 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise3 - expect(debouncer.getLastResult()).toBe('result') + expect(debouncer.getState().lastResult).toBe('result') }) it('should maintain last result across multiple executions', async () => { @@ -714,19 +714,19 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getLastResult()).toBe('first') + expect(debouncer.getState().lastResult).toBe('first') // Second execution const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 - expect(debouncer.getLastResult()).toBe('second') + expect(debouncer.getState().lastResult).toBe('second') // Third execution const promise3 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise3 - expect(debouncer.getLastResult()).toBe('third') + expect(debouncer.getState().lastResult).toBe('third') }) it('should handle undefined/null results', async () => { @@ -740,13 +740,13 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getLastResult()).toBeUndefined() + expect(debouncer.getState().lastResult).toBeUndefined() // Test null result const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 - expect(debouncer.getLastResult()).toBeNull() + expect(debouncer.getState().lastResult).toBeNull() }) it('should maintain last result after error', async () => { @@ -763,13 +763,13 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getLastResult()).toBe('success') + expect(debouncer.getState().lastResult).toBe('success') // Second execution - error const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 - expect(debouncer.getLastResult()).toBe('success') // Should maintain last successful result + expect(debouncer.getState().lastResult).toBe('success') // Should maintain last successful result }) }) @@ -783,8 +783,8 @@ describe('AsyncDebouncer', () => { // Try to execute while disabled const promise = debouncer.maybeExecute() - expect(debouncer.getIsPending()).toBe(false) - expect(debouncer.getIsExecuting()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.getState().isExecuting).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -802,8 +802,8 @@ describe('AsyncDebouncer', () => { // Try to execute while disabled const promise = debouncer.maybeExecute() - expect(debouncer.getIsPending()).toBe(false) - expect(debouncer.getIsExecuting()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.getState().isExecuting).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -817,7 +817,7 @@ describe('AsyncDebouncer', () => { // Should execute by default const promise = debouncer.maybeExecute() - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Advance time and verify execution vi.advanceTimersByTime(1000) @@ -831,11 +831,11 @@ describe('AsyncDebouncer', () => { // Start execution const promise = debouncer.maybeExecute() - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Disable during wait debouncer.setOptions({ enabled: false }) - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -858,7 +858,7 @@ describe('AsyncDebouncer', () => { const promise = debouncer.maybeExecute() // Should only have one pending execution - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Advance time and verify single execution vi.advanceTimersByTime(1000) @@ -874,17 +874,17 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getLastResult()).toBe('result') + expect(debouncer.getState().lastResult).toBe('result') // Disable and verify state is maintained debouncer.setOptions({ enabled: false }) - expect(debouncer.getLastResult()).toBe('result') - expect(debouncer.getIsPending()).toBe(false) - expect(debouncer.getIsExecuting()).toBe(false) + expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.getState().isExecuting).toBe(false) // Re-enable and verify state is still maintained debouncer.setOptions({ enabled: true }) - expect(debouncer.getLastResult()).toBe('result') + expect(debouncer.getState().lastResult).toBe('result') }) }) @@ -924,7 +924,7 @@ describe('AsyncDebouncer', () => { // Start execution const promise = debouncer.maybeExecute() - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Change options during wait debouncer.setOptions({ onSuccess }) @@ -944,22 +944,22 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getLastResult()).toBe('result') - expect(debouncer.getSuccessCount()).toBe(1) + expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.getState().successCount).toBe(1) // Change options debouncer.setOptions({ wait: 500, leading: true }) // Verify state is maintained - expect(debouncer.getLastResult()).toBe('result') - expect(debouncer.getSuccessCount()).toBe(1) + expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.getState().successCount).toBe(1) // Second execution with new options const promise2 = debouncer.maybeExecute() expect(mockFn).toBeCalledTimes(2) // Leading execution vi.advanceTimersByTime(500) await promise2 - expect(debouncer.getSuccessCount()).toBe(2) + expect(debouncer.getState().successCount).toBe(2) }) it('should handle callback option changes', async () => { @@ -1018,7 +1018,7 @@ describe('AsyncDebouncer', () => { // Start execution const promise = debouncer.maybeExecute() - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Change callbacks during wait debouncer.setOptions({ diff --git a/packages/pacer/tests/debouncer.test.ts b/packages/pacer/tests/debouncer.test.ts index e1a8aeec3..c6b362eb5 100644 --- a/packages/pacer/tests/debouncer.test.ts +++ b/packages/pacer/tests/debouncer.test.ts @@ -358,7 +358,7 @@ describe('Debouncer', () => { }) debouncer.maybeExecute('test') - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Call again before wait expires vi.advanceTimersByTime(500) @@ -366,10 +366,10 @@ describe('Debouncer', () => { // Time is almost up vi.advanceTimersByTime(900) - expect(debouncer.getIsPending()).toBe(true) // Still pending + expect(debouncer.getState().isPending).toBe(true) // Still pending vi.advanceTimersByTime(100) - expect(debouncer.getIsPending()).toBe(false) // Now it's done + expect(debouncer.getState().isPending).toBe(false) // Now it's done }) it('should never be pending when trailing is false', () => { @@ -381,7 +381,7 @@ describe('Debouncer', () => { }) debouncer.maybeExecute('test1') - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) // Call again before wait expires vi.advanceTimersByTime(500) @@ -389,10 +389,10 @@ describe('Debouncer', () => { // Time is almost up vi.advanceTimersByTime(900) - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) vi.advanceTimersByTime(100) - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) }) it('should not be pending when leading and trailing are both false', () => { @@ -404,10 +404,10 @@ describe('Debouncer', () => { }) debouncer.maybeExecute('test') - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) vi.advanceTimersByTime(1000) - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) }) it('should not be pending when disabled', () => { @@ -415,10 +415,10 @@ describe('Debouncer', () => { const debouncer = new Debouncer(mockFn, { wait: 1000, enabled: false }) debouncer.maybeExecute('test') - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) vi.advanceTimersByTime(1000) - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) }) it('should update pending when enabling/disabling', () => { @@ -426,15 +426,15 @@ describe('Debouncer', () => { const debouncer = new Debouncer(mockFn, { wait: 1000 }) debouncer.maybeExecute('test') - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) // Disable while there is a pending execution debouncer.setOptions({ enabled: false }) - expect(debouncer.getIsPending()).toBe(false) // Should be false now + expect(debouncer.getState().isPending).toBe(false) // Should be false now // Re-enable debouncer.setOptions({ enabled: true }) - expect(debouncer.getIsPending()).toBe(false) // Should still be false + expect(debouncer.getState().isPending).toBe(false) // Should still be false }) it('should set pending to false when canceled', () => { @@ -442,10 +442,10 @@ describe('Debouncer', () => { const debouncer = new Debouncer(mockFn, { wait: 1000 }) debouncer.maybeExecute('test') - expect(debouncer.getIsPending()).toBe(true) + expect(debouncer.getState().isPending).toBe(true) debouncer.cancel() - expect(debouncer.getIsPending()).toBe(false) + expect(debouncer.getState().isPending).toBe(false) }) }) diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index ff7ba18f5..0629c3d25 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -1,8 +1,21 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { AnyAsyncFunction } from '@tanstack/pacer/types' -import type { AsyncDebouncerOptions } from '@tanstack/pacer/async-debouncer' +import type { + AsyncDebouncerOptions, + AsyncDebouncerState, +} from '@tanstack/pacer/async-debouncer' + +interface ReactAsyncDebouncerOptions + extends AsyncDebouncerOptions { + enableStateRerenders?: boolean +} + +interface ReactAsyncDebouncer + extends Omit, 'getState'> { + state: AsyncDebouncerState +} /** * A low-level React hook that creates an `AsyncDebouncer` instance to delay execution of an async function. @@ -59,13 +72,35 @@ import type { AsyncDebouncerOptions } from '@tanstack/pacer/async-debouncer' */ export function useAsyncDebouncer( fn: TFn, - options: AsyncDebouncerOptions, -): AsyncDebouncer { + { + enableStateRerenders = true, + onStateChange, + ...options + }: ReactAsyncDebouncerOptions, +): ReactAsyncDebouncer { const [asyncDebouncer] = useState(() => bindInstanceMethods(new AsyncDebouncer(fn, options)), ) - asyncDebouncer.setOptions(options) + const [state, setState] = useState(asyncDebouncer.getState()) + + const setOptions = useCallback( + (newOptions: Partial>) => { + asyncDebouncer.setOptions({ + ...newOptions, + onStateChange: (state, asyncDebouncer) => { + if (enableStateRerenders) { + setState(state) + } + const _onStateChange = newOptions.onStateChange ?? onStateChange + _onStateChange?.(state, asyncDebouncer) + }, + }) + }, + [asyncDebouncer, enableStateRerenders, onStateChange], + ) + + setOptions(options) useEffect(() => { return () => { @@ -73,5 +108,13 @@ export function useAsyncDebouncer( } }, [asyncDebouncer]) - return asyncDebouncer + return useMemo( + () => + ({ + ...asyncDebouncer, + state, + setOptions, + }) as ReactAsyncDebouncer, + [asyncDebouncer, state, setOptions], + ) } diff --git a/packages/react-pacer/src/debouncer/useDebouncedState.ts b/packages/react-pacer/src/debouncer/useDebouncedState.ts index 24ac87a0d..1d3d85418 100644 --- a/packages/react-pacer/src/debouncer/useDebouncedState.ts +++ b/packages/react-pacer/src/debouncer/useDebouncedState.ts @@ -32,7 +32,7 @@ import type { Debouncer, DebouncerOptions } from '@tanstack/pacer/debouncer' * const executionCount = debouncer.getExecutionCount(); * * // Get the pending state - * const isPending = debouncer.getIsPending(); + * const isPending = debouncer.getState().isPending; * ``` */ export function useDebouncedState( diff --git a/packages/react-pacer/src/debouncer/useDebouncer.ts b/packages/react-pacer/src/debouncer/useDebouncer.ts index 81bc3c56c..6771bad6e 100644 --- a/packages/react-pacer/src/debouncer/useDebouncer.ts +++ b/packages/react-pacer/src/debouncer/useDebouncer.ts @@ -36,7 +36,7 @@ import type { AnyFunction } from '@tanstack/pacer/types' * const executionCount = searchDebouncer.getExecutionCount(); * * // Get the pending state - * const isPending = searchDebouncer.getIsPending(); + * const isPending = searchdebouncer.getState().isPending; * ``` */ export function useDebouncer( diff --git a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts index 65255c922..85dd34267 100644 --- a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts +++ b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts @@ -1,24 +1,16 @@ import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' -import { createSignal } from 'solid-js' import { bindInstanceMethods } from '@tanstack/pacer/utils' -import type { AsyncDebouncerOptions } from '@tanstack/pacer/async-debouncer' +import { createStore } from 'solid-js/store' +import type { Store } from 'solid-js/store' +import type { + AsyncDebouncerOptions, + AsyncDebouncerState, +} from '@tanstack/pacer/async-debouncer' import type { AnyAsyncFunction } from '@tanstack/pacer/types' -import type { Accessor } from 'solid-js' export interface SolidAsyncDebouncer - extends Omit< - AsyncDebouncer, - | 'getErrorCount' - | 'getIsPending' - | 'getLastResult' - | 'getSettleCount' - | 'getSuccessCount' - > { - errorCount: Accessor - isPending: Accessor - lastResult: Accessor | undefined> - settleCount: Accessor - successCount: Accessor + extends Omit, 'getState'> { + store: Store> } /** @@ -79,32 +71,19 @@ export function createAsyncDebouncer( initialOptions: AsyncDebouncerOptions, ): SolidAsyncDebouncer { const asyncDebouncer = new AsyncDebouncer(fn, initialOptions) - - const [errorCount, setErrorCount] = createSignal( - asyncDebouncer.getErrorCount(), - ) - const [settleCount, setSettleCount] = createSignal( - asyncDebouncer.getSettleCount(), - ) - const [successCount, setSuccessCount] = createSignal( - asyncDebouncer.getSuccessCount(), - ) - const [isPending, setIsPending] = createSignal(asyncDebouncer.getIsPending()) - const [lastResult, setLastResult] = createSignal( - asyncDebouncer.getLastResult(), + + const [store, setStore] = createStore>( + asyncDebouncer.getState(), ) function setOptions(newOptions: Partial>) { asyncDebouncer.setOptions({ ...newOptions, - onSettled: (asyncDebouncer) => { - setSuccessCount(asyncDebouncer.getSuccessCount()) - setErrorCount(asyncDebouncer.getErrorCount()) - setSettleCount(asyncDebouncer.getSettleCount()) - setIsPending(asyncDebouncer.getIsPending()) - setLastResult(asyncDebouncer.getLastResult()) - const onSettled = newOptions.onSettled ?? initialOptions.onSettled - onSettled?.(asyncDebouncer) + onStateChange: (state, asyncDebouncer) => { + setStore(state) + const onStateChange = + newOptions.onStateChange ?? initialOptions.onStateChange + onStateChange?.(state, asyncDebouncer) }, }) } @@ -113,11 +92,7 @@ export function createAsyncDebouncer( return { ...bindInstanceMethods(asyncDebouncer), - errorCount, - isPending, - lastResult, - settleCount, - successCount, + store, setOptions, } as SolidAsyncDebouncer } diff --git a/packages/solid-pacer/src/debouncer/createDebouncer.ts b/packages/solid-pacer/src/debouncer/createDebouncer.ts index 6ad07ffe2..494078640 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncer.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncer.ts @@ -1,17 +1,20 @@ import { Debouncer } from '@tanstack/pacer/debouncer' -import { createEffect, createSignal, onCleanup } from 'solid-js' +import { createEffect, onCleanup } from 'solid-js' import { bindInstanceMethods } from '@tanstack/pacer/utils' -import type { Accessor } from 'solid-js' +import { createStore } from 'solid-js/store' import type { AnyFunction } from '@tanstack/pacer/types' -import type { DebouncerOptions } from '@tanstack/pacer/debouncer' +import type { + DebouncerOptions, + DebouncerState, +} from '@tanstack/pacer/debouncer' +import type { Store } from 'solid-js/store' /** * An extension of the Debouncer class that adds Solid signals to access the internal state of the debouncer */ export interface SolidDebouncer - extends Omit, 'getExecutionCount' | 'getIsPending'> { - executionCount: Accessor - isPending: Accessor + extends Omit, 'getState'> { + store: Store> } /** @@ -55,21 +58,19 @@ export function createDebouncer( initialOptions: DebouncerOptions, ): SolidDebouncer { const debouncer = bindInstanceMethods(new Debouncer(fn, initialOptions)) - - const [executionCount, setExecutionCount] = createSignal( - debouncer.getExecutionCount(), + const [store, setStore] = createStore>( + debouncer.getState(), ) - const [isPending, setIsPending] = createSignal(debouncer.getIsPending()) function setOptions(newOptions: Partial>) { debouncer.setOptions({ ...newOptions, - onExecute: (debouncer) => { - setExecutionCount(debouncer.getExecutionCount()) - setIsPending(debouncer.getIsPending()) + onStateChange: (state, debouncer) => { + setStore(state) - const onExecute = newOptions.onExecute ?? initialOptions.onExecute - onExecute?.(debouncer) + const onStateChange = + newOptions.onStateChange ?? initialOptions.onStateChange + onStateChange?.(state, debouncer) }, }) } @@ -84,8 +85,7 @@ export function createDebouncer( return { ...debouncer, - executionCount, - isPending, + store, setOptions, } as SolidDebouncer } diff --git a/packages/solid-pacer/src/throttler/createThrottler.ts b/packages/solid-pacer/src/throttler/createThrottler.ts index ad4f73e4e..1d474c803 100644 --- a/packages/solid-pacer/src/throttler/createThrottler.ts +++ b/packages/solid-pacer/src/throttler/createThrottler.ts @@ -1,25 +1,20 @@ import { Throttler } from '@tanstack/pacer/throttler' -import { createEffect, createSignal, onCleanup } from 'solid-js' +import { createEffect, onCleanup } from 'solid-js' import { bindInstanceMethods } from '@tanstack/pacer/utils' -import type { Accessor } from 'solid-js' +import { createStore } from 'solid-js/store' +import type { Store } from 'solid-js/store' import type { AnyFunction } from '@tanstack/pacer/types' -import type { ThrottlerOptions } from '@tanstack/pacer/throttler' +import type { + ThrottlerOptions, + ThrottlerState, +} from '@tanstack/pacer/throttler' /** * An extension of the Throttler class that adds Solid signals to access the internal state of the throttler */ export interface SolidThrottler - extends Omit< - Throttler, - | 'getExecutionCount' - | 'getIsPending' - | 'getLastExecutionTime' - | 'getNextExecutionTime' - > { - executionCount: Accessor - isPending: Accessor - lastExecutionTime: Accessor - nextExecutionTime: Accessor + extends Omit, 'getState'> { + store: Store> } /** @@ -61,29 +56,19 @@ export function createThrottler( initialOptions: ThrottlerOptions, ): SolidThrottler { const throttler = bindInstanceMethods(new Throttler(fn, initialOptions)) - - const [executionCount, setExecutionCount] = createSignal( - throttler.getExecutionCount(), - ) - const [isPending, setIsPending] = createSignal(throttler.getIsPending()) - const [lastExecutionTime, setLastExecutionTime] = createSignal( - throttler.getLastExecutionTime(), - ) - const [nextExecutionTime, setNextExecutionTime] = createSignal( - throttler.getNextExecutionTime(), + const [store, setStore] = createStore>( + throttler.getState(), ) function setOptions(newOptions: Partial>) { throttler.setOptions({ ...newOptions, - onExecute: (throttler) => { - setExecutionCount(throttler.getExecutionCount()) - setIsPending(throttler.getIsPending()) - setLastExecutionTime(throttler.getLastExecutionTime()) - setNextExecutionTime(throttler.getNextExecutionTime()) + onStateChange: (state, throttler) => { + setStore(state) - const onExecute = newOptions.onExecute ?? initialOptions.onExecute - onExecute?.(throttler) + const onStateChange = + newOptions.onStateChange ?? initialOptions.onStateChange + onStateChange?.(state, throttler) }, }) } @@ -98,10 +83,7 @@ export function createThrottler( return { ...throttler, - executionCount, - isPending, - lastExecutionTime, - nextExecutionTime, + store, setOptions, } as SolidThrottler } From 54c6552353aac7e441909551135d62edfeeb02fc Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 26 Jun 2025 20:08:14 -0500 Subject: [PATCH 16/51] refactor async debouncer to tanstack store --- .../react/useAsyncDebouncer/src/index.tsx | 39 +++--- .../solid/createAsyncDebouncer/src/index.tsx | 39 +++--- packages/pacer/package.json | 3 + packages/pacer/src/async-debouncer.ts | 72 ++++------ packages/pacer/tests/async-debouncer.test.ts | 124 +++++++++--------- packages/react-pacer/package.json | 3 +- .../src/async-debouncer/useAsyncDebouncer.ts | 60 ++++----- packages/solid-pacer/package.json | 3 +- .../async-debouncer/createAsyncDebouncer.ts | 52 ++++---- pnpm-lock.yaml | 66 +++++++--- 10 files changed, 239 insertions(+), 222 deletions(-) diff --git a/examples/react/useAsyncDebouncer/src/index.tsx b/examples/react/useAsyncDebouncer/src/index.tsx index 41f60fcb5..66e241aa3 100644 --- a/examples/react/useAsyncDebouncer/src/index.tsx +++ b/examples/react/useAsyncDebouncer/src/index.tsx @@ -27,7 +27,6 @@ function App() { setResults([]) 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 const data = await fakeApi(term) @@ -37,19 +36,27 @@ function App() { } // hook that gives you an async debouncer instance - const setSearchAsyncDebouncer = useAsyncDebouncer(handleSearch, { - // leading: true, // optional leading execution - // enableStateRerenders: false, // turn off state rerenders if you don't care about the state - wait: 500, // Wait 500ms between API calls - onError: (error) => { - // optional error handler - console.error('Search failed:', error) - setResults([]) + const asyncDebouncer = useAsyncDebouncer( + handleSearch, + { + // leading: true, // optional leading execution + wait: 500, // Wait 500ms between API calls + onError: (error) => { + // optional error handler + console.error('Search failed:', error) + setResults([]) + }, }, - }) + // optionally subscribe to only the react state re-renders you care about + // (state) => ({ + // isExecuting: state.isExecuting, + // isPending: state.isPending, + // successCount: state.successCount, + // }), + ) // get and name our debounced function - const handleSearchDebounced = setSearchAsyncDebouncer.maybeExecute + const handleSearchDebounced = asyncDebouncer.maybeExecute // instant event handler that calls both the instant local state setter and the debounced function async function onSearchChange(e: React.ChangeEvent) { @@ -74,7 +81,7 @@ function App() { />
-

API calls made: {setSearchAsyncDebouncer.state.successCount}

+

API calls made: {asyncDebouncer.state.successCount}

{results.length > 0 && (
    {results.map((item) => ( @@ -82,11 +89,9 @@ function App() { ))}
)} - {setSearchAsyncDebouncer.state.isPending &&

Pending...

} - {setSearchAsyncDebouncer.state.isExecuting &&

Executing...

} -
-          {JSON.stringify({ state: setSearchAsyncDebouncer.state }, null, 2)}
-        
+ {asyncDebouncer.state.isPending &&

Pending...

} + {asyncDebouncer.state.isExecuting &&

Executing...

} +
{JSON.stringify({ state: asyncDebouncer.state }, null, 2)}
) diff --git a/examples/solid/createAsyncDebouncer/src/index.tsx b/examples/solid/createAsyncDebouncer/src/index.tsx index 0c11fc1b3..43ff1d0f9 100644 --- a/examples/solid/createAsyncDebouncer/src/index.tsx +++ b/examples/solid/createAsyncDebouncer/src/index.tsx @@ -37,18 +37,27 @@ function App() { } // hook that gives you an async debouncer instance - const setSearchAsyncDebouncer = createAsyncDebouncer(handleSearch, { - // leading: true, // optional leading execution - wait: 500, // Wait 500ms between API calls - onError: (error) => { - // optional error handler - console.error('Search failed:', error) - setResults([]) + const asyncDebouncer = createAsyncDebouncer( + handleSearch, + { + // leading: true, // optional leading execution + wait: 500, // Wait 500ms between API calls + onError: (error) => { + // optional error handler + console.error('Search failed:', error) + setResults([]) + }, }, - }) + // optionally subscribe to only the solid state changes you care about + // (state) => ({ + // isExecuting: state.isExecuting, + // isPending: state.isPending, + // successCount: state.successCount, + // }), + ) // get and name our debounced function - const handleSearchDebounced = setSearchAsyncDebouncer.maybeExecute + const handleSearchDebounced = asyncDebouncer.maybeExecute // instant event handler that calls both the instant local state setter and the debounced function async function onSearchChange(e: Event) { @@ -72,21 +81,21 @@ function App() { autocomplete="new-password" /> - {setSearchAsyncDebouncer.store.errorCount > 0 && ( -
Errors: {setSearchAsyncDebouncer.store.errorCount}
+ {asyncDebouncer.state().errorCount > 0 && ( +
Errors: {asyncDebouncer.state().errorCount}
)}
-

API calls made: {setSearchAsyncDebouncer.store.successCount}

+

API calls made: {asyncDebouncer.state().successCount}

{results().length > 0 && (
    {(item) =>
  • {item.title}
  • }
)} - {setSearchAsyncDebouncer.store.isExecuting &&

Executing...

} - {setSearchAsyncDebouncer.store.isPending &&

Pending...

} + {asyncDebouncer.state().isExecuting &&

Executing...

} + {asyncDebouncer.state().isPending &&

Pending...

}
-          {JSON.stringify({ store: setSearchAsyncDebouncer.store }, null, 2)}
+          {JSON.stringify({ state: asyncDebouncer.state() }, null, 2)}
         
diff --git a/packages/pacer/package.json b/packages/pacer/package.json index 2e4be0526..5c424848d 100644 --- a/packages/pacer/package.json +++ b/packages/pacer/package.json @@ -159,5 +159,8 @@ "test:types": "tsc", "test:build": "publint --strict", "build": "vite build" + }, + "dependencies": { + "@tanstack/store": "^0.7.1" } } diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 9ba15a24d..81385cbb3 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -1,3 +1,4 @@ +import { Store } from '@tanstack/store' import { parseFunctionOrValue } from './utils' import type { AnyAsyncFunction, OptionalKeys } from './types' @@ -45,13 +46,6 @@ export interface AsyncDebouncerOptions { * Optional callback to call when the debounced function is executed */ onSuccess?: (result: ReturnType, debouncer: AsyncDebouncer) => void - /** - * Callback function that is called when the state of the async debouncer is updated - */ - onStateChange?: ( - state: AsyncDebouncerState, - debouncer: AsyncDebouncer, - ) => void /** * Whether to throw errors when they occur. * Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -73,7 +67,7 @@ export interface AsyncDebouncerOptions { type AsyncDebouncerOptionsWithOptionalCallbacks = OptionalKeys< AsyncDebouncerOptions, - 'initialState' | 'onError' | 'onSettled' | 'onSuccess' | 'onStateChange' + 'initialState' | 'onError' | 'onSettled' | 'onSuccess' > const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { @@ -98,16 +92,18 @@ const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { * instead of setting the result on a state variable from within the debounced function. * * Error Handling: - * - If an error occurs during execution and no `onError` handler is provided, the error will be thrown and propagate up to the caller. - * - If an `onError` handler is provided, errors will be caught and passed to the handler instead of being thrown. - * - The error count can be tracked using `getErrorCount()`. - * - The debouncer maintains its state and can continue to be used after an error occurs. + * - 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 + * - 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 store * * State Management: + * - The debouncer uses a reactive store for state management * - Use `initialState` to provide initial state values when creating the async debouncer - * - Use `onStateChange` callback to react to state changes and implement custom persistence * - The state includes canLeadingExecute, error count, execution status, and success/settle counts - * - State can be retrieved using `getState()` method + * - State can be accessed via the `store` property and its `state` getter + * - The store is reactive and will notify subscribers of state changes * * @example * ```ts @@ -127,8 +123,9 @@ const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { * ``` */ export class AsyncDebouncer { - #options: AsyncDebouncerOptions - #state: AsyncDebouncerState = { + readonly store: Store> = new Store< + AsyncDebouncerState + >({ canLeadingExecute: true, errorCount: 0, isExecuting: false, @@ -137,7 +134,8 @@ export class AsyncDebouncer { lastResult: undefined, settleCount: 0, successCount: 0, - } + }) + #options: AsyncDebouncerOptions #abortController: AbortController | null = null #timeoutId: NodeJS.Timeout | null = null #resolvePreviousPromise: @@ -153,10 +151,10 @@ export class AsyncDebouncer { ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } - this.#state = { - ...this.#state, + this.store.setState((state) => ({ + ...state, ...this.#options.initialState, - } + })) } /** @@ -171,20 +169,8 @@ export class AsyncDebouncer { } } - /** - * Returns the current debouncer options - */ - getOptions(): AsyncDebouncerOptions { - return this.#options - } - - getState(): AsyncDebouncerState { - return { ...this.#state } - } - #setState(newState: Partial>): void { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + this.store.setState((state) => ({ ...state, ...newState })) } /** @@ -222,10 +208,10 @@ export class AsyncDebouncer { this.#setState({ lastArgs: args }) // Handle leading execution - if (this.#options.leading && this.#state.canLeadingExecute) { + if (this.#options.leading && this.store.state.canLeadingExecute) { this.#setState({ canLeadingExecute: false }) await this.#execute(...args) - return this.#state.lastResult + return this.store.state.lastResult } // Handle trailing execution @@ -237,14 +223,14 @@ export class AsyncDebouncer { this.#resolvePreviousPromise = resolve this.#timeoutId = setTimeout(async () => { // Execute trailing if enabled - if (this.#options.trailing && this.#state.lastArgs) { - await this.#execute(...this.#state.lastArgs) + if (this.#options.trailing && this.store.state.lastArgs) { + await this.#execute(...this.store.state.lastArgs) } // Reset state and resolve this.#setState({ canLeadingExecute: true }) this.#resolvePreviousPromise = null - resolve(this.#state.lastResult) + resolve(this.store.state.lastResult) }, this.#getWait()) }) } @@ -259,12 +245,12 @@ export class AsyncDebouncer { const result = await this.fn(...args) // EXECUTE! this.#setState({ lastResult: result, - successCount: this.#state.successCount + 1, + successCount: this.store.state.successCount + 1, }) this.#options.onSuccess?.(result, this) } catch (error) { this.#setState({ - errorCount: this.#state.errorCount + 1, + errorCount: this.store.state.errorCount + 1, }) this.#options.onError?.(error, this) if (this.#options.throwOnError) { @@ -274,12 +260,12 @@ export class AsyncDebouncer { this.#setState({ isExecuting: false, isPending: false, - settleCount: this.#state.settleCount + 1, + settleCount: this.store.state.settleCount + 1, }) this.#abortController = null this.#options.onSettled?.(this) } - return this.#state.lastResult + return this.store.state.lastResult } #cancelPendingExecution(): void { @@ -288,7 +274,7 @@ export class AsyncDebouncer { this.#timeoutId = null } if (this.#resolvePreviousPromise) { - this.#resolvePreviousPromise(this.#state.lastResult) + this.#resolvePreviousPromise(this.store.state.lastResult) this.#resolvePreviousPromise = null } this.#setState({ diff --git a/packages/pacer/tests/async-debouncer.test.ts b/packages/pacer/tests/async-debouncer.test.ts index 15cf86985..39cd890a5 100644 --- a/packages/pacer/tests/async-debouncer.test.ts +++ b/packages/pacer/tests/async-debouncer.test.ts @@ -345,9 +345,9 @@ describe('AsyncDebouncer', () => { expect(onError).toBeCalledWith(error, debouncer) expect(onSettled).toBeCalledWith(debouncer) - expect(debouncer.getState().errorCount).toBe(1) - expect(debouncer.getState().settleCount).toBe(1) - expect(debouncer.getState().successCount).toBe(0) + expect(debouncer.store.state.errorCount).toBe(1) + expect(debouncer.store.state.settleCount).toBe(1) + expect(debouncer.store.state.successCount).toBe(0) }) it('should not break debouncing chain on error', async () => { @@ -367,9 +367,9 @@ describe('AsyncDebouncer', () => { vi.advanceTimersByTime(1000) await promise1 expect(onError).toBeCalledWith(error, debouncer) - expect(debouncer.getState().errorCount).toBe(1) - expect(debouncer.getState().settleCount).toBe(1) - expect(debouncer.getState().successCount).toBe(0) + expect(debouncer.store.state.errorCount).toBe(1) + expect(debouncer.store.state.settleCount).toBe(1) + expect(debouncer.store.state.successCount).toBe(0) // Second call - should succeed const promise2 = debouncer.maybeExecute() @@ -377,9 +377,9 @@ describe('AsyncDebouncer', () => { const result = await promise2 expect(result).toBe('success') expect(mockFn).toBeCalledTimes(2) - expect(debouncer.getState().errorCount).toBe(1) - expect(debouncer.getState().settleCount).toBe(2) - expect(debouncer.getState().successCount).toBe(1) + expect(debouncer.store.state.errorCount).toBe(1) + expect(debouncer.store.state.settleCount).toBe(2) + expect(debouncer.store.state.successCount).toBe(1) }) it('should handle multiple errors in sequence', async () => { @@ -400,18 +400,18 @@ describe('AsyncDebouncer', () => { vi.advanceTimersByTime(1000) await promise1 expect(onError).toHaveBeenNthCalledWith(1, error1, debouncer) - expect(debouncer.getState().errorCount).toBe(1) - expect(debouncer.getState().settleCount).toBe(1) - expect(debouncer.getState().successCount).toBe(0) + expect(debouncer.store.state.errorCount).toBe(1) + expect(debouncer.store.state.settleCount).toBe(1) + expect(debouncer.store.state.successCount).toBe(0) // Second call - should error again const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 expect(onError).toHaveBeenNthCalledWith(2, error2, debouncer) - expect(debouncer.getState().errorCount).toBe(2) - expect(debouncer.getState().settleCount).toBe(2) - expect(debouncer.getState().successCount).toBe(0) + expect(debouncer.store.state.errorCount).toBe(2) + expect(debouncer.store.state.settleCount).toBe(2) + expect(debouncer.store.state.successCount).toBe(0) }) it('should handle errors during leading execution', async () => { @@ -431,9 +431,9 @@ describe('AsyncDebouncer', () => { await promise expect(onError).toBeCalledWith(error, debouncer) expect(onSettled).toBeCalledWith(debouncer) - expect(debouncer.getState().errorCount).toBe(1) - expect(debouncer.getState().settleCount).toBe(1) - expect(debouncer.getState().successCount).toBe(0) + expect(debouncer.store.state.errorCount).toBe(1) + expect(debouncer.store.state.settleCount).toBe(1) + expect(debouncer.store.state.successCount).toBe(0) }) it('should handle errors during trailing execution', async () => { @@ -453,9 +453,9 @@ describe('AsyncDebouncer', () => { await promise expect(onError).toBeCalledWith(error, debouncer) expect(onSettled).toBeCalledWith(debouncer) - expect(debouncer.getState().errorCount).toBe(1) - expect(debouncer.getState().settleCount).toBe(1) - expect(debouncer.getState().successCount).toBe(0) + expect(debouncer.store.state.errorCount).toBe(1) + expect(debouncer.store.state.settleCount).toBe(1) + expect(debouncer.store.state.successCount).toBe(0) }) it('should maintain state after error', async () => { @@ -473,10 +473,10 @@ describe('AsyncDebouncer', () => { await promise1 // Verify state is maintained - expect(debouncer.getState().errorCount).toBe(1) - expect(debouncer.getState().settleCount).toBe(1) - expect(debouncer.getState().successCount).toBe(0) - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.errorCount).toBe(1) + expect(debouncer.store.state.settleCount).toBe(1) + expect(debouncer.store.state.successCount).toBe(0) + expect(debouncer.store.state.isPending).toBe(false) }) }) @@ -591,11 +591,11 @@ describe('AsyncDebouncer', () => { // Start execution debouncer.maybeExecute() - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Cancel before wait period ends debouncer.cancel() - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -616,7 +616,7 @@ describe('AsyncDebouncer', () => { // Cancel and verify canLeadingExecute is reset debouncer.cancel() - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) // Next call should execute immediately again const promise2 = debouncer.maybeExecute('second') @@ -640,7 +640,7 @@ describe('AsyncDebouncer', () => { // Cancel during leading execution debouncer.cancel() - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) // Next call should execute immediately again const promise2 = debouncer.maybeExecute('second') @@ -660,7 +660,7 @@ describe('AsyncDebouncer', () => { vi.advanceTimersByTime(1000) await promise - expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.store.state.lastResult).toBe('result') }) it('should return last result when execution is pending', async () => { @@ -671,11 +671,11 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.store.state.lastResult).toBe('result') // Second execution - should still return last result while pending const promise2 = debouncer.maybeExecute() - expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.store.state.lastResult).toBe('result') vi.advanceTimersByTime(1000) await promise2 }) @@ -688,18 +688,18 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.store.state.lastResult).toBe('result') // Second execution with cancellation debouncer.maybeExecute() debouncer.cancel() - expect(debouncer.getState().lastResult).toBe('result') // Should still have last result + expect(debouncer.store.state.lastResult).toBe('result') // Should still have last result // Third execution const promise3 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise3 - expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.store.state.lastResult).toBe('result') }) it('should maintain last result across multiple executions', async () => { @@ -714,19 +714,19 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getState().lastResult).toBe('first') + expect(debouncer.store.state.lastResult).toBe('first') // Second execution const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 - expect(debouncer.getState().lastResult).toBe('second') + expect(debouncer.store.state.lastResult).toBe('second') // Third execution const promise3 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise3 - expect(debouncer.getState().lastResult).toBe('third') + expect(debouncer.store.state.lastResult).toBe('third') }) it('should handle undefined/null results', async () => { @@ -740,13 +740,13 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getState().lastResult).toBeUndefined() + expect(debouncer.store.state.lastResult).toBeUndefined() // Test null result const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 - expect(debouncer.getState().lastResult).toBeNull() + expect(debouncer.store.state.lastResult).toBeNull() }) it('should maintain last result after error', async () => { @@ -763,13 +763,13 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getState().lastResult).toBe('success') + expect(debouncer.store.state.lastResult).toBe('success') // Second execution - error const promise2 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise2 - expect(debouncer.getState().lastResult).toBe('success') // Should maintain last successful result + expect(debouncer.store.state.lastResult).toBe('success') // Should maintain last successful result }) }) @@ -783,8 +783,8 @@ describe('AsyncDebouncer', () => { // Try to execute while disabled const promise = debouncer.maybeExecute() - expect(debouncer.getState().isPending).toBe(false) - expect(debouncer.getState().isExecuting).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) + expect(debouncer.store.state.isExecuting).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -802,8 +802,8 @@ describe('AsyncDebouncer', () => { // Try to execute while disabled const promise = debouncer.maybeExecute() - expect(debouncer.getState().isPending).toBe(false) - expect(debouncer.getState().isExecuting).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) + expect(debouncer.store.state.isExecuting).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -817,7 +817,7 @@ describe('AsyncDebouncer', () => { // Should execute by default const promise = debouncer.maybeExecute() - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Advance time and verify execution vi.advanceTimersByTime(1000) @@ -831,11 +831,11 @@ describe('AsyncDebouncer', () => { // Start execution const promise = debouncer.maybeExecute() - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Disable during wait debouncer.setOptions({ enabled: false }) - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) // Advance time and verify no execution vi.advanceTimersByTime(1000) @@ -858,7 +858,7 @@ describe('AsyncDebouncer', () => { const promise = debouncer.maybeExecute() // Should only have one pending execution - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Advance time and verify single execution vi.advanceTimersByTime(1000) @@ -874,17 +874,17 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.store.state.lastResult).toBe('result') // Disable and verify state is maintained debouncer.setOptions({ enabled: false }) - expect(debouncer.getState().lastResult).toBe('result') - expect(debouncer.getState().isPending).toBe(false) - expect(debouncer.getState().isExecuting).toBe(false) + expect(debouncer.store.state.lastResult).toBe('result') + expect(debouncer.store.state.isPending).toBe(false) + expect(debouncer.store.state.isExecuting).toBe(false) // Re-enable and verify state is still maintained debouncer.setOptions({ enabled: true }) - expect(debouncer.getState().lastResult).toBe('result') + expect(debouncer.store.state.lastResult).toBe('result') }) }) @@ -924,7 +924,7 @@ describe('AsyncDebouncer', () => { // Start execution const promise = debouncer.maybeExecute() - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Change options during wait debouncer.setOptions({ onSuccess }) @@ -944,22 +944,22 @@ describe('AsyncDebouncer', () => { const promise1 = debouncer.maybeExecute() vi.advanceTimersByTime(1000) await promise1 - expect(debouncer.getState().lastResult).toBe('result') - expect(debouncer.getState().successCount).toBe(1) + expect(debouncer.store.state.lastResult).toBe('result') + expect(debouncer.store.state.successCount).toBe(1) // Change options debouncer.setOptions({ wait: 500, leading: true }) // Verify state is maintained - expect(debouncer.getState().lastResult).toBe('result') - expect(debouncer.getState().successCount).toBe(1) + expect(debouncer.store.state.lastResult).toBe('result') + expect(debouncer.store.state.successCount).toBe(1) // Second execution with new options const promise2 = debouncer.maybeExecute() expect(mockFn).toBeCalledTimes(2) // Leading execution vi.advanceTimersByTime(500) await promise2 - expect(debouncer.getState().successCount).toBe(2) + expect(debouncer.store.state.successCount).toBe(2) }) it('should handle callback option changes', async () => { @@ -1018,7 +1018,7 @@ describe('AsyncDebouncer', () => { // Start execution const promise = debouncer.maybeExecute() - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Change callbacks during wait debouncer.setOptions({ diff --git a/packages/react-pacer/package.json b/packages/react-pacer/package.json index b55562472..0167c2843 100644 --- a/packages/react-pacer/package.json +++ b/packages/react-pacer/package.json @@ -161,7 +161,8 @@ "build": "vite build" }, "dependencies": { - "@tanstack/pacer": "workspace:*" + "@tanstack/pacer": "workspace:*", + "@tanstack/react-store": "^0.7.1" }, "devDependencies": { "@eslint-react/eslint-plugin": "^1.51.2", diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index 0629c3d25..92dc26370 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -1,20 +1,23 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' import { bindInstanceMethods } from '@tanstack/pacer/utils' +import { useStore } from '@tanstack/react-store' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { AsyncDebouncerOptions, AsyncDebouncerState, } from '@tanstack/pacer/async-debouncer' -interface ReactAsyncDebouncerOptions - extends AsyncDebouncerOptions { - enableStateRerenders?: boolean -} - -interface ReactAsyncDebouncer - extends Omit, 'getState'> { - state: AsyncDebouncerState +interface ReactAsyncDebouncer< + TFn extends AnyAsyncFunction, + TSelected = AsyncDebouncerState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the debouncer state changes + * + * Use this instead of `debouncer.store.state` + */ + state: TSelected } /** @@ -70,37 +73,19 @@ interface ReactAsyncDebouncer * ); * ``` */ -export function useAsyncDebouncer( +export function useAsyncDebouncer< + TFn extends AnyAsyncFunction, + TSelected = AsyncDebouncerState, +>( fn: TFn, - { - enableStateRerenders = true, - onStateChange, - ...options - }: ReactAsyncDebouncerOptions, -): ReactAsyncDebouncer { + options: AsyncDebouncerOptions, + selector?: (state: AsyncDebouncerState) => TSelected, +): ReactAsyncDebouncer { const [asyncDebouncer] = useState(() => bindInstanceMethods(new AsyncDebouncer(fn, options)), ) - const [state, setState] = useState(asyncDebouncer.getState()) - - const setOptions = useCallback( - (newOptions: Partial>) => { - asyncDebouncer.setOptions({ - ...newOptions, - onStateChange: (state, asyncDebouncer) => { - if (enableStateRerenders) { - setState(state) - } - const _onStateChange = newOptions.onStateChange ?? onStateChange - _onStateChange?.(state, asyncDebouncer) - }, - }) - }, - [asyncDebouncer, enableStateRerenders, onStateChange], - ) - - setOptions(options) + const state = useStore(asyncDebouncer.store, selector) useEffect(() => { return () => { @@ -113,8 +98,7 @@ export function useAsyncDebouncer( ({ ...asyncDebouncer, state, - setOptions, - }) as ReactAsyncDebouncer, - [asyncDebouncer, state, setOptions], + }) as unknown as ReactAsyncDebouncer, // omit `store` in favor of `state` + [asyncDebouncer, state], ) } diff --git a/packages/solid-pacer/package.json b/packages/solid-pacer/package.json index 82320aab1..de59dcaa5 100644 --- a/packages/solid-pacer/package.json +++ b/packages/solid-pacer/package.json @@ -161,7 +161,8 @@ "build": "vite build" }, "dependencies": { - "@tanstack/pacer": "workspace:*" + "@tanstack/pacer": "workspace:*", + "@tanstack/solid-store": "^0.7.1" }, "devDependencies": { "solid-js": "^1.9.7", diff --git a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts index 85dd34267..79f6c9dfe 100644 --- a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts +++ b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts @@ -1,16 +1,23 @@ import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' import { bindInstanceMethods } from '@tanstack/pacer/utils' -import { createStore } from 'solid-js/store' -import type { Store } from 'solid-js/store' +import { useStore } from '@tanstack/solid-store' +import type { Accessor } from 'solid-js' import type { AsyncDebouncerOptions, AsyncDebouncerState, } from '@tanstack/pacer/async-debouncer' import type { AnyAsyncFunction } from '@tanstack/pacer/types' -export interface SolidAsyncDebouncer - extends Omit, 'getState'> { - store: Store> +export interface SolidAsyncDebouncer< + TFn extends AnyAsyncFunction, + TSelected = AsyncDebouncerState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated when the debouncer state changes + * + * Use this instead of `debouncer.store.state` + */ + state: Accessor } /** @@ -66,33 +73,22 @@ export interface SolidAsyncDebouncer * ); * ``` */ -export function createAsyncDebouncer( +export function createAsyncDebouncer< + TFn extends AnyAsyncFunction, + TSelected = AsyncDebouncerState, +>( fn: TFn, initialOptions: AsyncDebouncerOptions, -): SolidAsyncDebouncer { - const asyncDebouncer = new AsyncDebouncer(fn, initialOptions) - - const [store, setStore] = createStore>( - asyncDebouncer.getState(), + selector?: (state: AsyncDebouncerState) => TSelected, +): SolidAsyncDebouncer { + const asyncDebouncer = bindInstanceMethods( + new AsyncDebouncer(fn, initialOptions), ) - function setOptions(newOptions: Partial>) { - asyncDebouncer.setOptions({ - ...newOptions, - onStateChange: (state, asyncDebouncer) => { - setStore(state) - const onStateChange = - newOptions.onStateChange ?? initialOptions.onStateChange - onStateChange?.(state, asyncDebouncer) - }, - }) - } - - setOptions(initialOptions) + const state = useStore(asyncDebouncer.store, selector) return { - ...bindInstanceMethods(asyncDebouncer), - store, - setOptions, - } as SolidAsyncDebouncer + ...asyncDebouncer, + state, + } as unknown as SolidAsyncDebouncer // omit `store` in favor of `state` } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 958b87433..9d00c2f74 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1273,7 +1273,11 @@ importers: specifier: ^2.11.6 version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - packages/pacer: {} + packages/pacer: + dependencies: + '@tanstack/store': + specifier: ^0.7.1 + version: 0.7.1 packages/persister: {} @@ -1282,9 +1286,12 @@ importers: '@tanstack/pacer': specifier: workspace:* version: link:../pacer + '@tanstack/react-store': + specifier: ^0.7.1 + version: 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-dom: specifier: '>=16.8' - version: 19.0.0(react@19.1.0) + version: 19.1.0(react@19.1.0) devDependencies: '@eslint-react/eslint-plugin': specifier: ^1.51.2 @@ -1338,6 +1345,9 @@ importers: '@tanstack/pacer': specifier: workspace:* version: link:../pacer + '@tanstack/solid-store': + specifier: ^0.7.1 + version: 0.7.1(solid-js@1.9.7) devDependencies: solid-js: specifier: ^1.9.7 @@ -2263,6 +2273,20 @@ packages: peerDependencies: react: ^18 || ^19 + '@tanstack/react-store@0.7.1': + resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==} + peerDependencies: + 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-store@0.7.1': + resolution: {integrity: sha512-chcElOdXhDTcJnMrY02Q+EPyI11C6/vKIinxd7ALW5iGWaGEpo+Re5S1cIP4YyWYUqZE8nWU9O/imSnxZ+9W8A==} + peerDependencies: + solid-js: ^1.6.0 + + '@tanstack/store@0.7.1': + resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==} + '@tanstack/typedoc-config@0.2.0': resolution: {integrity: sha512-1ak0ZirlLRxd3dNNOFnMoYORBeC83nK4C+OiXpE0dxsO8ZVrBqCtNCKr8SG+W9zICXcWGiFu9qYLsgNKTayOqw==} engines: {node: '>=18'} @@ -4000,11 +4024,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@19.0.0: - resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} - peerDependencies: - react: ^19.0.0 - react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -4106,9 +4125,6 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.25.0: - resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} - scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -4495,6 +4511,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5820,6 +5841,20 @@ snapshots: '@tanstack/query-core': 5.80.6 react: 19.1.0 + '@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/store': 0.7.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + + '@tanstack/solid-store@0.7.1(solid-js@1.9.7)': + dependencies: + '@tanstack/store': 0.7.1 + solid-js: 1.9.7 + + '@tanstack/store@0.7.1': {} + '@tanstack/typedoc-config@0.2.0(typescript@5.8.3)': dependencies: typedoc: 0.27.9(typescript@5.8.3) @@ -7724,11 +7759,6 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@19.0.0(react@19.1.0): - dependencies: - react: 19.1.0 - scheduler: 0.25.0 - react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 @@ -7836,8 +7866,6 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.25.0: {} - scheduler@0.26.0: {} semver@6.3.1: {} @@ -8192,6 +8220,10 @@ snapshots: dependencies: punycode: 2.3.1 + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + util-deprecate@1.0.2: {} validate-html-nesting@1.2.2: {} From 0d141452b503815a1e96a2ae31630093bd90c8e8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 01:09:18 +0000 Subject: [PATCH 17/51] ci: apply automated fixes --- .../functions/createasyncdebouncer.md | 15 ++++- .../reference/functions/createdebouncer.md | 2 +- .../reference/functions/createthrottler.md | 2 +- .../interfaces/solidasyncdebouncer.md | 57 +++---------------- .../reference/interfaces/soliddebouncer.md | 20 ++----- .../reference/interfaces/solidthrottler.md | 44 ++------------ .../solid/createAsyncDebouncer/src/index.tsx | 4 +- 7 files changed, 34 insertions(+), 110 deletions(-) diff --git a/docs/framework/solid/reference/functions/createasyncdebouncer.md b/docs/framework/solid/reference/functions/createasyncdebouncer.md index bcc35c571..4bf8a2972 100644 --- a/docs/framework/solid/reference/functions/createasyncdebouncer.md +++ b/docs/framework/solid/reference/functions/createasyncdebouncer.md @@ -8,10 +8,13 @@ title: createAsyncDebouncer # Function: createAsyncDebouncer() ```ts -function createAsyncDebouncer(fn, initialOptions): SolidAsyncDebouncer +function createAsyncDebouncer( + fn, + initialOptions, +selector?): SolidAsyncDebouncer ``` -Defined in: [async-debouncer/createAsyncDebouncer.ts:77](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L77) +Defined in: [async-debouncer/createAsyncDebouncer.ts:76](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L76) A low-level Solid hook that creates an `AsyncDebouncer` instance to delay execution of an async function. @@ -40,6 +43,8 @@ Error Handling: • **TFn** *extends* `AnyAsyncFunction` +• **TSelected** = `AsyncDebouncerState`\<`TFn`\> + ## Parameters ### fn @@ -50,9 +55,13 @@ Error Handling: `AsyncDebouncerOptions`\<`TFn`\> +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidAsyncDebouncer`](../../interfaces/solidasyncdebouncer.md)\<`TFn`\> +[`SolidAsyncDebouncer`](../../interfaces/solidasyncdebouncer.md)\<`TFn`, `TSelected`\> ## Example diff --git a/docs/framework/solid/reference/functions/createdebouncer.md b/docs/framework/solid/reference/functions/createdebouncer.md index 0c2c3b497..687b73cfe 100644 --- a/docs/framework/solid/reference/functions/createdebouncer.md +++ b/docs/framework/solid/reference/functions/createdebouncer.md @@ -11,7 +11,7 @@ title: createDebouncer function createDebouncer(fn, initialOptions): SolidDebouncer ``` -Defined in: [debouncer/createDebouncer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L53) +Defined in: [debouncer/createDebouncer.ts:56](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L56) A Solid hook that creates and manages a Debouncer instance. diff --git a/docs/framework/solid/reference/functions/createthrottler.md b/docs/framework/solid/reference/functions/createthrottler.md index 42be3f6bc..1e1b905ff 100644 --- a/docs/framework/solid/reference/functions/createthrottler.md +++ b/docs/framework/solid/reference/functions/createthrottler.md @@ -11,7 +11,7 @@ title: createThrottler function createThrottler(fn, initialOptions): SolidThrottler ``` -Defined in: [throttler/createThrottler.ts:59](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L59) +Defined in: [throttler/createThrottler.ts:54](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L54) A low-level Solid hook that creates a `Throttler` instance that limits how often the provided function can execute. diff --git a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md index ee5169ed4..1fe93f678 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md +++ b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md @@ -5,69 +5,30 @@ title: SolidAsyncDebouncer -# Interface: SolidAsyncDebouncer\ +# Interface: SolidAsyncDebouncer\ -Defined in: [async-debouncer/createAsyncDebouncer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L8) +Defined in: [async-debouncer/createAsyncDebouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L11) ## Extends -- `Omit`\<`AsyncDebouncer`\<`TFn`\>, - \| `"getErrorCount"` - \| `"getIsPending"` - \| `"getLastResult"` - \| `"getSettleCount"` - \| `"getSuccessCount"`\> +- `Omit`\<`AsyncDebouncer`\<`TFn`\>, `"store"`\> ## Type Parameters • **TFn** *extends* `AnyAsyncFunction` -## Properties - -### errorCount - -```ts -errorCount: Accessor; -``` - -Defined in: [async-debouncer/createAsyncDebouncer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L17) - -*** - -### isPending - -```ts -isPending: Accessor; -``` - -Defined in: [async-debouncer/createAsyncDebouncer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L18) +• **TSelected** = `AsyncDebouncerState`\<`TFn`\> -*** - -### lastResult - -```ts -lastResult: Accessor>; -``` - -Defined in: [async-debouncer/createAsyncDebouncer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L19) - -*** +## Properties -### settleCount +### state ```ts -settleCount: Accessor; +state: Accessor; ``` Defined in: [async-debouncer/createAsyncDebouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L20) -*** - -### successCount - -```ts -successCount: Accessor; -``` +Reactive state that will be updated when the debouncer state changes -Defined in: [async-debouncer/createAsyncDebouncer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L21) +Use this instead of `debouncer.store.state` diff --git a/docs/framework/solid/reference/interfaces/soliddebouncer.md b/docs/framework/solid/reference/interfaces/soliddebouncer.md index fa3285f86..afd90bbf6 100644 --- a/docs/framework/solid/reference/interfaces/soliddebouncer.md +++ b/docs/framework/solid/reference/interfaces/soliddebouncer.md @@ -7,13 +7,13 @@ title: SolidDebouncer # Interface: SolidDebouncer\ -Defined in: [debouncer/createDebouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L11) +Defined in: [debouncer/createDebouncer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L15) An extension of the Debouncer class that adds Solid signals to access the internal state of the debouncer ## Extends -- `Omit`\<`Debouncer`\<`TFn`\>, `"getExecutionCount"` \| `"getIsPending"`\> +- `Omit`\<`Debouncer`\<`TFn`\>, `"getState"`\> ## Type Parameters @@ -21,20 +21,10 @@ An extension of the Debouncer class that adds Solid signals to access the intern ## Properties -### executionCount +### store ```ts -executionCount: Accessor; +store: DebouncerState; ``` -Defined in: [debouncer/createDebouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L13) - -*** - -### isPending - -```ts -isPending: Accessor; -``` - -Defined in: [debouncer/createDebouncer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L14) +Defined in: [debouncer/createDebouncer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L17) diff --git a/docs/framework/solid/reference/interfaces/solidthrottler.md b/docs/framework/solid/reference/interfaces/solidthrottler.md index e7b17ad12..02c1a555d 100644 --- a/docs/framework/solid/reference/interfaces/solidthrottler.md +++ b/docs/framework/solid/reference/interfaces/solidthrottler.md @@ -7,17 +7,13 @@ title: SolidThrottler # Interface: SolidThrottler\ -Defined in: [throttler/createThrottler.ts:11](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L11) +Defined in: [throttler/createThrottler.ts:15](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L15) An extension of the Throttler class that adds Solid signals to access the internal state of the throttler ## Extends -- `Omit`\<`Throttler`\<`TFn`\>, - \| `"getExecutionCount"` - \| `"getIsPending"` - \| `"getLastExecutionTime"` - \| `"getNextExecutionTime"`\> +- `Omit`\<`Throttler`\<`TFn`\>, `"getState"`\> ## Type Parameters @@ -25,40 +21,10 @@ An extension of the Throttler class that adds Solid signals to access the intern ## Properties -### executionCount +### store ```ts -executionCount: Accessor; +store: ThrottlerState; ``` -Defined in: [throttler/createThrottler.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L19) - -*** - -### isPending - -```ts -isPending: Accessor; -``` - -Defined in: [throttler/createThrottler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L20) - -*** - -### lastExecutionTime - -```ts -lastExecutionTime: Accessor; -``` - -Defined in: [throttler/createThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L21) - -*** - -### nextExecutionTime - -```ts -nextExecutionTime: Accessor; -``` - -Defined in: [throttler/createThrottler.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L22) +Defined in: [throttler/createThrottler.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L17) diff --git a/examples/solid/createAsyncDebouncer/src/index.tsx b/examples/solid/createAsyncDebouncer/src/index.tsx index 43ff1d0f9..a59130ee9 100644 --- a/examples/solid/createAsyncDebouncer/src/index.tsx +++ b/examples/solid/createAsyncDebouncer/src/index.tsx @@ -94,9 +94,7 @@ function App() { {asyncDebouncer.state().isExecuting &&

Executing...

} {asyncDebouncer.state().isPending &&

Pending...

}
-
-          {JSON.stringify({ state: asyncDebouncer.state() }, null, 2)}
-        
+
{JSON.stringify({ state: asyncDebouncer.state() }, null, 2)}
) From 780f5801afa9a822174552060d15083f9db1e0b3 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sat, 28 Jun 2025 21:04:08 -0500 Subject: [PATCH 18/51] refactor async queuer with store --- .../react/useAsyncQueuedState/src/index.tsx | 70 ++-- examples/react/useAsyncQueuer/src/index.tsx | 82 ++--- .../solid/createAsyncQueuer/src/index.tsx | 89 +++-- packages/pacer/src/async-debouncer.ts | 25 +- packages/pacer/src/async-queuer.ts | 319 +++++++----------- packages/pacer/tests/async-queuer.test.ts | 156 ++------- .../src/async-debouncer/useAsyncDebouncer.ts | 2 +- .../src/async-queuer/useAsyncQueuedState.ts | 31 +- .../src/async-queuer/useAsyncQueuer.ts | 34 +- .../src/async-queuer/createAsyncQueuer.ts | 156 +-------- 10 files changed, 357 insertions(+), 607 deletions(-) diff --git a/examples/react/useAsyncQueuedState/src/index.tsx b/examples/react/useAsyncQueuedState/src/index.tsx index fcb4f7a49..157c6426f 100644 --- a/examples/react/useAsyncQueuedState/src/index.tsx +++ b/examples/react/useAsyncQueuedState/src/index.tsx @@ -10,49 +10,53 @@ function App() { // Use your state management library of choice const [concurrency, setConcurrency] = useState(2) - const [, rerender] = useState(0) // demo - rerender when start/stop changes - // The function to process each item (now a number) async function processItem(item: Item): Promise { await new Promise((resolve) => setTimeout(resolve, fakeWaitTime)) console.log(`Processed ${item}`) } - const [queueItems, asyncQueuer] = useAsyncQueuedState(processItem, { - maxSize: 25, - initialItems: Array.from({ length: 10 }, (_, i) => i + 1), - concurrency, // Process 2 items concurrently - started: false, - wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency - onIsRunningChange: (_asyncQueuer) => { - rerender((prev) => prev + 1) - }, - onReject: (item: Item, asyncQueuer) => { - console.log( - 'Queue is full, rejecting item', - item, - asyncQueuer.getRejectionCount(), - ) + const [queueItems, asyncQueuer] = useAsyncQueuedState( + processItem, // your function to queue/process items + { + maxSize: 25, + initialItems: Array.from({ length: 10 }, (_, i) => i + 1), + concurrency, // Process 2 items concurrently + started: false, + wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency + onReject: (item: Item, asyncQueuer) => { + console.log( + 'Queue is full, rejecting item', + item, + asyncQueuer.store.state.rejectionCount, + ) + }, + onError: (error: unknown, asyncQueuer) => { + console.error( + 'Error processing item', + error, + asyncQueuer.store.state.errorCount, + ) // optionally, handle errors here instead of your own try/catch + }, }, - onError: (error: unknown, _asyncQueuer) => { - console.error('Error processing item', error, asyncQueuer.getErrorCount()) // optionally, handle errors here instead of your own try/catch - }, - }) + // optionally, you can select a subset of the state to re-render when it changes + // (state) => ({ }), + ) return (

TanStack Pacer useAsyncQueuer Example

-
Queue Size: {asyncQueuer.getSize()}
+
Queue Size: {asyncQueuer.state.size}
Queue Max Size: {25}
-
Queue Full: {asyncQueuer.getIsFull() ? 'Yes' : 'No'}
-
Queue Empty: {asyncQueuer.getIsEmpty() ? 'Yes' : 'No'}
-
Queue Idle: {asyncQueuer.getIsIdle() ? 'Yes' : 'No'}
+
Queue Full: {asyncQueuer.state.isFull ? 'Yes' : 'No'}
+
Queue Empty: {asyncQueuer.state.isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {asyncQueuer.state.isIdle ? 'Yes' : 'No'}
- Queuer Status: {asyncQueuer.getIsRunning() ? 'Running' : 'Stopped'} + Queuer Status: {asyncQueuer.state.status ? 'Running' : 'Stopped'}
-
Items Processed: {asyncQueuer.getSuccessCount()}
-
Items Rejected: {asyncQueuer.getRejectionCount()}
+
Items Processed: {asyncQueuer.state.successCount}
+
Items Rejected: {asyncQueuer.state.rejectionCount}
Active Tasks: {asyncQueuer.peekActiveItems().length}
Pending Tasks: {asyncQueuer.peekPendingItems().length}
@@ -91,27 +95,27 @@ function App() { : 1 asyncQueuer.addItem(nextNumber) }} - disabled={asyncQueuer.getIsFull()} + disabled={asyncQueuer.state.isFull} > Add Async Task - +
diff --git a/examples/react/useAsyncQueuer/src/index.tsx b/examples/react/useAsyncQueuer/src/index.tsx index a4ac7d3f2..0d5642488 100644 --- a/examples/react/useAsyncQueuer/src/index.tsx +++ b/examples/react/useAsyncQueuer/src/index.tsx @@ -11,54 +11,56 @@ function App() { const [queueItems, setQueueItems] = useState>([]) const [concurrency, setConcurrency] = useState(2) - const [, rerender] = useState(0) // demo - rerender when start/stop changes - // The function to process each item (now a number) async function processItem(item: Item): Promise { await new Promise((resolve) => setTimeout(resolve, fakeWaitTime)) console.log(`Processed ${item}`) } - const asyncQueuer = useAsyncQueuer(processItem, { - maxSize: 25, - initialItems: Array.from({ length: 10 }, (_, i) => i + 1), - concurrency, // Process 2 items concurrently - started: false, - wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency - onItemsChange: (asyncQueuer) => { - setQueueItems(asyncQueuer.peekAllItems()) - }, - onIsRunningChange: (_asyncQueuer) => { - rerender((prev) => prev + 1) - }, - onReject: (item: Item, asyncQueuer) => { - console.log( - 'Queue is full, rejecting item', - item, - asyncQueuer.getRejectionCount(), - ) - }, - onError: (error: unknown, _asyncQueuer) => { - console.error('Error processing item', error, asyncQueuer.getErrorCount()) // optionally, handle errors here instead of your own try/catch + const asyncQueuer = useAsyncQueuer( + processItem, // your function to queue/process items + { + maxSize: 25, + initialItems: Array.from({ length: 10 }, (_, i) => i + 1), + concurrency, // Process 2 items concurrently + started: false, + wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency + onItemsChange: (asyncQueuer) => { + setQueueItems(asyncQueuer.peekAllItems()) + }, + onReject: (item, asyncQueuer) => { + console.log( + 'Queue is full, rejecting item', + item, + asyncQueuer.store.state.rejectionCount, + ) + }, + onError: (error: unknown, asyncQueuer) => { + console.error( + 'Error processing item', + error, + asyncQueuer.store.state.errorCount, + ) // optionally, handle errors here instead of your own try/catch + }, }, - }) + // optionally, you can select a subset of the state to re-render when it changes + // (state) => ({ }), + ) return (

TanStack Pacer useAsyncQueuer Example

-
Queue Size: {asyncQueuer.getSize()}
+
Queue Size: {asyncQueuer.state.size}
Queue Max Size: {25}
-
Queue Full: {asyncQueuer.getIsFull() ? 'Yes' : 'No'}
-
Queue Empty: {asyncQueuer.getIsEmpty() ? 'Yes' : 'No'}
-
Queue Idle: {asyncQueuer.getIsIdle() ? 'Yes' : 'No'}
-
- Queuer Status: {asyncQueuer.getIsRunning() ? 'Running' : 'Stopped'} -
-
Items Processed: {asyncQueuer.getSuccessCount()}
-
Items Rejected: {asyncQueuer.getRejectionCount()}
-
Active Tasks: {asyncQueuer.peekActiveItems().length}
-
Pending Tasks: {asyncQueuer.peekPendingItems().length}
+
Queue Full: {asyncQueuer.state.isFull ? 'Yes' : 'No'}
+
Queue Empty: {asyncQueuer.state.isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {asyncQueuer.state.isIdle ? 'Yes' : 'No'}
+
Queuer Status: {asyncQueuer.state.status}
+
Items Processed: {asyncQueuer.state.successCount}
+
Items Rejected: {asyncQueuer.state.rejectionCount}
+
Active Tasks: {asyncQueuer.state.activeItems.length}
+
Pending Tasks: {asyncQueuer.state.items.length}
Concurrency:{' '} Add Async Task - +
diff --git a/examples/solid/createAsyncQueuer/src/index.tsx b/examples/solid/createAsyncQueuer/src/index.tsx index b6e56acca..c75b71431 100644 --- a/examples/solid/createAsyncQueuer/src/index.tsx +++ b/examples/solid/createAsyncQueuer/src/index.tsx @@ -8,7 +8,6 @@ type Item = number function App() { // Use your state management library of choice - const [queueItems, setQueueItems] = createSignal>([]) const [concurrency, setConcurrency] = createSignal(2) // The function to process each item (now a number) @@ -17,41 +16,47 @@ function App() { console.log(`Processed ${item}`) } - const queuer = createAsyncQueuer(processItem, { - maxSize: 25, - initialItems: Array.from({ length: 10 }, (_, i) => i + 1), - concurrency: () => concurrency(), // Process 2 items concurrently - started: false, - wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency - onItemsChange: (queuer) => { - setQueueItems(queuer.peekAllItems()) + const queuer = createAsyncQueuer( + processItem, + { + maxSize: 25, + initialItems: Array.from({ length: 10 }, (_, i) => i + 1), + concurrency: () => concurrency(), // Process 2 items concurrently + started: false, + wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency + onReject: (item, queuer) => { + console.log( + 'Queue is full, rejecting item', + item, + queuer.store.state.rejectionCount, + ) + }, + onError: (error, queuer) => { + console.error( + 'Error processing item', + error, + queuer.store.state.errorCount, + ) // optionally, handle errors here instead of your own try/catch + }, }, - onReject: (item, queuer) => { - console.log( - 'Queue is full, rejecting item', - item, - queuer.getRejectionCount(), - ) - }, - onError: (error, queuer) => { - console.error('Error processing item', error, queuer.getErrorCount()) // optionally, handle errors here instead of your own try/catch - }, - }) + // optionally, you can select a subset of the state to re-render when it changes + // (state) => ({}), + ) return (

TanStack Pacer createAsyncQueuer Example

-
Queue Size: {queuer.size()}
+
Queue Size: {queuer.state().size}
Queue Max Size: {25}
-
Queue Full: {queuer.isFull() ? 'Yes' : 'No'}
-
Queue Empty: {queuer.isEmpty() ? 'Yes' : 'No'}
-
Queue Idle: {queuer.isIdle() ? 'Yes' : 'No'}
-
Queuer Status: {queuer.isRunning() ? 'Running' : 'Stopped'}
-
Items Processed: {queuer.successCount()}
-
Items Rejected: {queuer.rejectionCount()}
-
Active Tasks: {queuer.activeItems().length}
-
Pending Tasks: {queuer.pendingItems().length}
+
Queue Full: {queuer.state().isFull ? 'Yes' : 'No'}
+
Queue Empty: {queuer.state().isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {queuer.state().isIdle ? 'Yes' : 'No'}
+
Queuer Status: {queuer.state().status}
+
Items Processed: {queuer.state().successCount}
+
Items Rejected: {queuer.state().rejectionCount}
+
Active Tasks: {queuer.state().activeItems.length}
+
Pending Tasks: {queuer.state().items.length}
Concurrency:{' '}
Queue Items: - + {(item, index) => (
{index()}: {item} @@ -85,23 +90,33 @@ function App() { > - - - -
diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 81385cbb3..b1eb508d5 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -10,6 +10,7 @@ export interface AsyncDebouncerState { lastArgs: Parameters | undefined lastResult: ReturnType | undefined settleCount: number + status: 'idle' | 'pending' | 'executing' | 'settled' successCount: number } @@ -134,6 +135,7 @@ export class AsyncDebouncer { lastResult: undefined, settleCount: 0, successCount: 0, + status: 'idle', }) #options: AsyncDebouncerOptions #abortController: AbortController | null = null @@ -151,10 +153,7 @@ export class AsyncDebouncer { ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } - this.store.setState((state) => ({ - ...state, - ...this.#options.initialState, - })) + this.#setState(this.#options.initialState ?? {}) } /** @@ -170,7 +169,23 @@ export class AsyncDebouncer { } #setState(newState: Partial>): void { - this.store.setState((state) => ({ ...state, ...newState })) + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + const { isPending, isExecuting, settleCount } = combinedState + return { + ...combinedState, + status: isPending + ? 'pending' + : isExecuting + ? 'executing' + : settleCount > 0 + ? 'settled' + : 'idle', + } + }) } /** diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index 3fe474b2f..098072c21 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -1,3 +1,4 @@ +import { Store } from '@tanstack/store' import { parseFunctionOrValue } from './utils' import type { OptionalKeys } from './types' import type { QueuePosition } from './queuer' @@ -6,12 +7,18 @@ export interface AsyncQueuerState { activeItems: Array errorCount: number expirationCount: number - items: Array + isEmpty: boolean + isFull: boolean + isIdle: boolean + isRunning: boolean itemTimestamps: Array + items: Array lastResult: any + pendingTick: boolean rejectionCount: number - running: boolean settledCount: number + size: number + status: 'idle' | 'running' | 'stopped' successCount: number } @@ -70,10 +77,6 @@ export interface AsyncQueuerOptions { * Callback fired whenever an item expires in the queuer */ onExpire?: (item: TValue, queuer: AsyncQueuer) => void - /** - * Callback fired whenever the queuer's running state changes - */ - onIsRunningChange?: (queuer: AsyncQueuer) => void /** * Callback fired whenever an item is added or removed from the queuer */ @@ -90,13 +93,6 @@ export interface AsyncQueuerOptions { * Optional callback to call when a task succeeds */ onSuccess?: (result: TValue, queuer: AsyncQueuer) => void - /** - * Callback function that is called when the state of the async queuer is updated - */ - onStateChange?: ( - state: AsyncQueuerState, - queuer: AsyncQueuer, - ) => void /** * Whether the queuer should start processing tasks immediately or not. */ @@ -123,10 +119,8 @@ type AsyncQueuerOptionsWithOptionalCallbacks = OptionalKeys< | 'onSettled' | 'onReject' | 'onItemsChange' - | 'onIsRunningChange' | 'onExpire' | 'onError' - | 'onStateChange' > const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { @@ -186,20 +180,27 @@ const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { * ``` */ export class AsyncQueuer { - #options: AsyncQueuerOptions - #state: AsyncQueuerState = { + readonly store: Store> = new Store< + AsyncQueuerState + >({ activeItems: [], errorCount: 0, expirationCount: 0, - items: [], + isEmpty: true, + isFull: false, + isIdle: true, + isRunning: true, itemTimestamps: [], + items: [], lastResult: null, + pendingTick: false, rejectionCount: 0, - running: true, settledCount: 0, + size: 0, + status: 'idle', successCount: 0, - } - #pendingTick = false + }) + #options: AsyncQueuerOptions constructor( private fn: (value: TValue) => Promise, @@ -210,16 +211,15 @@ export class AsyncQueuer { ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } - this.#state = { - ...this.#state, + const isInitiallyRunning = + this.#options.initialState?.isRunning ?? this.#options.started ?? true + this.#setState({ ...this.#options.initialState, - running: - this.#options.initialState?.running ?? this.#options.started ?? true, - } + isRunning: isInitiallyRunning, + }) if (this.#options.initialState?.items) { - this.#options.onItemsChange?.(this) - if (this.#state.running) { + if (this.store.state.isRunning) { this.#tick() } } else { @@ -238,27 +238,38 @@ export class AsyncQueuer { this.#options = { ...this.#options, ...newOptions } } - /** - * Returns the current queuer options, including defaults and any overrides. - */ - getOptions(): AsyncQueuerOptions { - return this.#options - } + #setState(newState: Partial>): void { + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } - getState(): AsyncQueuerState { - return { ...this.#state } - } + const { activeItems, items, isRunning } = combinedState - #setState(newState: Partial>): void { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + const size = items.length + const isFull = size >= (this.#options.maxSize ?? Infinity) + const isEmpty = size === 0 + const isIdle = isRunning && isEmpty && activeItems.length === 0 + + const status = isIdle ? 'idle' : isRunning ? 'running' : 'stopped' + + return { + ...combinedState, + isEmpty, + isFull, + isIdle, + size, + status, + } + }) } /** * Returns the current wait time (in milliseconds) between processing items. * If a function is provided, it is called with the queuer instance. */ - getWait(): number { + #getWait(): number { return parseFunctionOrValue(this.#options.wait ?? 0, this) } @@ -266,7 +277,7 @@ export class AsyncQueuer { * Returns the current concurrency limit for processing items. * If a function is provided, it is called with the queuer instance. */ - getConcurrency(): number { + #getConcurrency(): number { return parseFunctionOrValue(this.#options.concurrency ?? 1, this) } @@ -274,8 +285,8 @@ export class AsyncQueuer { * Processes items in the queue up to the concurrency limit. Internal use only. */ #tick() { - if (!this.#state.running) { - this.#pendingTick = false + if (!this.store.state.isRunning) { + this.#setState({ pendingTick: false }) return } @@ -283,23 +294,24 @@ export class AsyncQueuer { this.#checkExpiredItems() // Process items concurrently up to the concurrency limit + const activeItems = this.store.state.activeItems while ( - this.#state.activeItems.length < this.getConcurrency() && - !this.getIsEmpty() + activeItems.length < this.#getConcurrency() && + !this.store.state.isEmpty ) { const nextItem = this.peekNextItem() if (!nextItem) { break } + activeItems.push(nextItem) this.#setState({ - activeItems: [...this.#state.activeItems, nextItem], + activeItems, }) - this.#options.onItemsChange?.(this) ;(async () => { const result = await this.execute() this.#setState({ lastResult: result }) - const wait = this.getWait() + const wait = this.#getWait() if (wait > 0) { setTimeout(() => this.#tick(), wait) return @@ -309,28 +321,25 @@ export class AsyncQueuer { })() } - this.#pendingTick = false + this.#setState({ pendingTick: false }) } /** * Starts processing items in the queue. If already running, does nothing. */ start(): void { - this.#setState({ running: true }) - if (!this.#pendingTick && !this.getIsEmpty()) { - this.#pendingTick = true + this.#setState({ isRunning: true }) + if (!this.store.state.pendingTick && !this.store.state.isEmpty) { + this.#setState({ pendingTick: true }) this.#tick() } - this.#options.onIsRunningChange?.(this) } /** * Stops processing items in the queue. Does not clear the queue. */ stop(): void { - this.#setState({ running: false }) - this.#pendingTick = false - this.#options.onIsRunningChange?.(this) + this.#setState({ isRunning: false, pendingTick: false }) } /** @@ -341,31 +350,6 @@ export class AsyncQueuer { this.#options.onItemsChange?.(this) } - /** - * Resets the queuer to its initial state. Optionally repopulates with initial items. - * Does not affect callbacks or options. - */ - reset(withInitialItems?: boolean): void { - this.clear() - this.#setState({ - activeItems: [], - errorCount: 0, - expirationCount: 0, - lastResult: null, - rejectionCount: 0, - settledCount: 0, - successCount: 0, - running: this.#options.started ?? true, - }) - if (withInitialItems) { - const items = [...(this.#options.initialItems ?? [])] - this.#setState({ - items, - itemTimestamps: items.map(() => Date.now()), - }) - } - } - /** * 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. @@ -381,9 +365,9 @@ export class AsyncQueuer { position: QueuePosition = this.#options.addItemsTo ?? 'back', runOnItemsChange: boolean = true, ): void { - if (this.getIsFull()) { + if (this.store.state.isFull) { this.#setState({ - rejectionCount: this.#state.rejectionCount + 1, + rejectionCount: this.store.state.rejectionCount + 1, }) this.#options.onReject?.(item, this) return @@ -395,9 +379,12 @@ export class AsyncQueuer { ? this.#options.getPriority!(item) : item.priority + const items = this.store.state.items + const itemTimestamps = this.store.state.itemTimestamps + if (priority !== undefined) { // Insert based on priority - higher priority items go to front - const insertIndex = this.#state.items.findIndex((existing) => { + const insertIndex = items.findIndex((existing) => { const existingPriority = this.#options.getPriority !== defaultOptions.getPriority ? this.#options.getPriority!(existing) @@ -406,42 +393,35 @@ export class AsyncQueuer { }) if (insertIndex === -1) { - this.#setState({ - items: [...this.#state.items, item], - itemTimestamps: [...this.#state.itemTimestamps, Date.now()], - }) + items.push(item) + itemTimestamps.push(Date.now()) } else { - const newItems = [...this.#state.items] - const newTimestamps = [...this.#state.itemTimestamps] - newItems.splice(insertIndex, 0, item) - newTimestamps.splice(insertIndex, 0, Date.now()) - this.#setState({ - items: newItems, - itemTimestamps: newTimestamps, - }) + items.splice(insertIndex, 0, item) + itemTimestamps.splice(insertIndex, 0, Date.now()) } } else { if (position === 'front') { // Default FIFO/LIFO behavior - this.#setState({ - items: [item, ...this.#state.items], - itemTimestamps: [Date.now(), ...this.#state.itemTimestamps], - }) + items.unshift(item) + itemTimestamps.unshift(Date.now()) } else { // LIFO - this.#setState({ - items: [...this.#state.items, item], - itemTimestamps: [...this.#state.itemTimestamps, Date.now()], - }) + items.push(item) + itemTimestamps.push(Date.now()) } } + this.#setState({ + items, + itemTimestamps, + }) + if (runOnItemsChange) { this.#options.onItemsChange?.(this) } - if (this.#state.running && !this.#pendingTick) { - this.#pendingTick = true + if (this.store.state.isRunning && !this.store.state.pendingTick) { + this.#setState({ pendingTick: true }) this.#tick() } } @@ -461,22 +441,24 @@ export class AsyncQueuer { getNextItem( position: QueuePosition = this.#options.getItemsFrom ?? 'front', ): TValue | undefined { + const items = this.store.state.items + const itemTimestamps = this.store.state.itemTimestamps let item: TValue | undefined if (position === 'front') { - item = this.#state.items[0] + item = items[0] if (item !== undefined) { this.#setState({ - items: this.#state.items.slice(1), - itemTimestamps: this.#state.itemTimestamps.slice(1), + items: items.slice(1), + itemTimestamps: itemTimestamps.slice(1), }) } } else { - item = this.#state.items[this.#state.items.length - 1] + item = items[items.length - 1] if (item !== undefined) { this.#setState({ - items: this.#state.items.slice(0, -1), - itemTimestamps: this.#state.itemTimestamps.slice(0, -1), + items: items.slice(0, -1), + itemTimestamps: itemTimestamps.slice(0, -1), }) } } @@ -502,15 +484,15 @@ export class AsyncQueuer { const item = this.getNextItem(position) if (item !== undefined) { try { - const result = await this.fn(item) + const lastResult = await this.fn(item) this.#setState({ - lastResult: result, - successCount: this.#state.successCount + 1, + successCount: this.store.state.successCount + 1, + lastResult, }) - this.#options.onSuccess?.(result, this) + this.#options.onSuccess?.(lastResult, this) } catch (error) { this.#setState({ - errorCount: this.#state.errorCount + 1, + errorCount: this.store.state.errorCount + 1, }) this.#options.onError?.(error, this) if (this.#options.throwOnError) { @@ -518,12 +500,11 @@ export class AsyncQueuer { } } finally { this.#setState({ - activeItems: this.#state.activeItems.filter( + activeItems: this.store.state.activeItems.filter( (activeItem) => activeItem !== item, ), - settledCount: this.#state.settledCount + 1, + settledCount: this.store.state.settledCount + 1, }) - this.#options.onItemsChange?.(this) this.#options.onSettled?.(this) } } @@ -545,11 +526,11 @@ export class AsyncQueuer { const expiredIndices: Array = [] // Find indices of expired items - for (let i = 0; i < this.#state.items.length; i++) { - const timestamp = this.#state.itemTimestamps[i] + for (let i = 0; i < this.store.state.size; i++) { + const timestamp = this.store.state.itemTimestamps[i] if (timestamp === undefined) continue - const item = this.#state.items[i] + const item = this.store.state.items[i] if (item === undefined) continue const isExpired = @@ -567,17 +548,17 @@ export class AsyncQueuer { const index = expiredIndices[i] if (index === undefined) continue - const expiredItem = this.#state.items[index] + const expiredItem = this.store.state.items[index] if (expiredItem === undefined) continue - const newItems = [...this.#state.items] - const newTimestamps = [...this.#state.itemTimestamps] + const newItems = [...this.store.state.items] + const newTimestamps = [...this.store.state.itemTimestamps] newItems.splice(index, 1) newTimestamps.splice(index, 1) this.#setState({ items: newItems, itemTimestamps: newTimestamps, - expirationCount: this.#state.expirationCount + 1, + expirationCount: this.store.state.expirationCount + 1, }) this.#options.onExpire?.(expiredItem, this) } @@ -598,30 +579,9 @@ export class AsyncQueuer { */ peekNextItem(position: QueuePosition = 'front'): TValue | undefined { if (position === 'front') { - return this.#state.items[0] + return this.store.state.items[0] } - return this.#state.items[this.#state.items.length - 1] - } - - /** - * Returns true if the queue is empty (no pending items). - */ - getIsEmpty(): boolean { - return this.#state.items.length === 0 - } - - /** - * Returns true if the queue is full (reached maxSize). - */ - getIsFull(): boolean { - return this.#state.items.length >= (this.#options.maxSize ?? Infinity) - } - - /** - * Returns the number of pending items in the queue. - */ - getSize(): number { - return this.#state.items.length + return this.store.state.items[this.store.state.size - 1] } /** @@ -635,67 +595,14 @@ export class AsyncQueuer { * Returns the items currently being processed (active tasks). */ peekActiveItems(): Array { - return [...this.#state.activeItems] + return [...this.store.state.activeItems] } /** * Returns the items waiting to be processed (pending tasks). */ peekPendingItems(): Array { - return [...this.#state.items] - } - - /** - * Returns the number of items that have been successfully processed. - */ - getSuccessCount(): number { - return this.#state.successCount - } - - /** - * Returns the number of items that have failed processing. - */ - getErrorCount(): number { - return this.#state.errorCount - } - - /** - * Returns the number of items that have completed processing (success or error). - */ - getSettledCount(): number { - return this.#state.settledCount - } - - /** - * Returns the number of items that have been rejected from being added to the queue. - */ - getRejectionCount(): number { - return this.#state.rejectionCount - } - - /** - * Returns true if the queuer is currently running (processing items). - */ - getIsRunning(): boolean { - return this.#state.running - } - - /** - * Returns true if the queuer is running but has no items to process and no active tasks. - */ - getIsIdle(): boolean { - return ( - this.#state.running && - this.getIsEmpty() && - this.#state.activeItems.length === 0 - ) - } - - /** - * Returns the number of items that have expired and been removed from the queue. - */ - getExpirationCount(): number { - return this.#state.expirationCount + return [...this.store.state.items] } } diff --git a/packages/pacer/tests/async-queuer.test.ts b/packages/pacer/tests/async-queuer.test.ts index a950194d1..572558d0d 100644 --- a/packages/pacer/tests/async-queuer.test.ts +++ b/packages/pacer/tests/async-queuer.test.ts @@ -11,17 +11,17 @@ describe('AsyncQueuer', () => { it('should create an empty async queuer and be started by default', () => { const asyncQueuer = new AsyncQueuer((item) => Promise.resolve(item), {}) - expect(asyncQueuer.getIsRunning()).toBe(true) - expect(asyncQueuer.getIsIdle()).toBe(true) - expect(asyncQueuer.getSize()).toBe(0) + expect(asyncQueuer.store.state.isRunning).toBe(true) + expect(asyncQueuer.store.state.isIdle).toBe(true) + expect(asyncQueuer.store.state.items.length).toBe(0) }) it('should respect started option', () => { const asyncQueuer = new AsyncQueuer((item) => Promise.resolve(item), { started: false, }) - expect(asyncQueuer.getIsRunning()).toBe(false) - expect(asyncQueuer.getIsIdle()).toBe(false) + expect(asyncQueuer.store.state.isRunning).toBe(false) + expect(asyncQueuer.store.state.isIdle).toBe(false) }) it('should respect maxSize option', () => { @@ -32,10 +32,10 @@ describe('AsyncQueuer', () => { started: false, }, ) - expect(asyncQueuer.getSize()).toBe(0) + expect(asyncQueuer.store.state.items.length).toBe(0) asyncQueuer.addItem('test') - expect(asyncQueuer.getSize()).toBe(1) - expect(asyncQueuer.getIsFull()).toBe(true) + expect(asyncQueuer.store.state.items.length).toBe(1) + expect(asyncQueuer.store.state.isFull).toBe(true) }) describe('addItem', () => { @@ -47,7 +47,7 @@ describe('AsyncQueuer', () => { }, ) asyncQueuer.addItem('test') - expect(asyncQueuer.getSize()).toBe(1) + expect(asyncQueuer.store.state.items.length).toBe(1) }) it('should reject items when full and call onReject', () => { const onReject = vi.fn() @@ -60,11 +60,11 @@ describe('AsyncQueuer', () => { }, ) asyncQueuer.addItem('test') - expect(asyncQueuer.getSize()).toBe(1) - expect(asyncQueuer.getIsFull()).toBe(true) + expect(asyncQueuer.store.state.items.length).toBe(1) + expect(asyncQueuer.store.state.isFull).toBe(true) asyncQueuer.addItem('test2') expect(onReject).toHaveBeenCalledTimes(1) - expect(asyncQueuer.getSize()).toBe(1) + expect(asyncQueuer.store.state.items.length).toBe(1) }) }) @@ -163,7 +163,7 @@ describe('AsyncQueuer', () => { started: false, }, ) - expect(asyncQueuer.getIsEmpty()).toBe(true) + expect(asyncQueuer.store.state.isEmpty).toBe(true) }) it('should return false when queuer has items', () => { const asyncQueuer = new AsyncQueuer( @@ -173,7 +173,7 @@ describe('AsyncQueuer', () => { }, ) asyncQueuer.addItem('test') - expect(asyncQueuer.getIsEmpty()).toBe(false) + expect(asyncQueuer.store.state.isEmpty).toBe(false) }) }) @@ -187,7 +187,7 @@ describe('AsyncQueuer', () => { }, ) asyncQueuer.addItem('test') - expect(asyncQueuer.getIsFull()).toBe(true) + expect(asyncQueuer.store.state.isFull).toBe(true) }) it('should return false when queuer is not full', () => { const asyncQueuer = new AsyncQueuer( @@ -197,7 +197,7 @@ describe('AsyncQueuer', () => { }, ) asyncQueuer.addItem('test') - expect(asyncQueuer.getIsFull()).toBe(false) + expect(asyncQueuer.store.state.isFull).toBe(false) }) }) @@ -211,7 +211,7 @@ describe('AsyncQueuer', () => { ) asyncQueuer.addItem('test') asyncQueuer.clear() - expect(asyncQueuer.getSize()).toBe(0) + expect(asyncQueuer.store.state.items.length).toBe(0) }) }) @@ -225,7 +225,7 @@ describe('AsyncQueuer', () => { initialItems: ['test'], }, ) - expect(asyncQueuer.getSize()).toBe(1) + expect(asyncQueuer.store.state.items.length).toBe(1) expect(asyncQueuer.getNextItem()).toBe('test') }) @@ -249,7 +249,7 @@ describe('AsyncQueuer', () => { }, }, ) - expect(asyncQueuer.getSize()).toBe(3) + expect(asyncQueuer.store.state.items.length).toBe(3) expect(asyncQueuer.getNextItem()).toBe('high') expect(asyncQueuer.getNextItem()).toBe('medium') expect(asyncQueuer.getNextItem()).toBe('low') @@ -263,8 +263,8 @@ describe('AsyncQueuer', () => { initialItems: [], }, ) - expect(asyncQueuer.getSize()).toBe(0) - expect(asyncQueuer.getIsEmpty()).toBe(true) + expect(asyncQueuer.store.state.items.length).toBe(0) + expect(asyncQueuer.store.state.isEmpty).toBe(true) }) }) @@ -491,85 +491,6 @@ describe('AsyncQueuer', () => { expect(asyncQueuer.getNextItem('front')).toBeUndefined() }) - describe('setOptions', () => { - it('should update queuer options', () => { - const asyncQueuer = new AsyncQueuer( - (item) => Promise.resolve(item), - { - started: false, - concurrency: 1, - }, - ) - asyncQueuer.setOptions({ concurrency: 2, started: true }) - expect(asyncQueuer.getOptions()).toEqual( - expect.objectContaining({ - concurrency: 2, - started: true, - }), - ) - }) - }) - - describe('getOptions', () => { - it('should return current queuer options', () => { - const options = { - started: false, - concurrency: 2, - maxSize: 10, - } - const asyncQueuer = new AsyncQueuer( - (item) => Promise.resolve(item), - options, - ) - expect(asyncQueuer.getOptions()).toEqual(expect.objectContaining(options)) - }) - }) - - describe('getWait', () => { - it('should return the current wait time', () => { - const asyncQueuer = new AsyncQueuer( - (item) => Promise.resolve(item), - { - wait: 100, - }, - ) - expect(asyncQueuer.getWait()).toBe(100) - - // Test with function - const asyncQueuer2 = new AsyncQueuer( - (item) => Promise.resolve(item), - { - wait: () => 200, - }, - ) - expect(asyncQueuer2.getWait()).toBe(200) - }) - }) - - describe('reset', () => { - it('should reset the queuer to its initial state', () => { - const asyncQueuer = new AsyncQueuer( - (item) => Promise.resolve(item), - { - started: false, - initialItems: ['initial'], - }, - ) - asyncQueuer.addItem('added') - expect(asyncQueuer.getSize()).toBe(2) - - asyncQueuer.reset() - expect(asyncQueuer.getSize()).toBe(0) - expect(asyncQueuer.getSuccessCount()).toBe(0) - expect(asyncQueuer.getErrorCount()).toBe(0) - expect(asyncQueuer.getSettledCount()).toBe(0) - - asyncQueuer.reset(true) - expect(asyncQueuer.getSize()).toBe(1) - expect(asyncQueuer.getNextItem()).toBe('initial') - }) - }) - describe('start', () => { it('should start the queuer', () => { const asyncQueuer = new AsyncQueuer( @@ -578,9 +499,9 @@ describe('AsyncQueuer', () => { started: false, }, ) - expect(asyncQueuer.getIsRunning()).toBe(false) + expect(asyncQueuer.store.state.isRunning).toBe(false) asyncQueuer.start() - expect(asyncQueuer.getIsRunning()).toBe(true) + expect(asyncQueuer.store.state.isRunning).toBe(true) }) }) @@ -592,9 +513,9 @@ describe('AsyncQueuer', () => { started: true, }, ) - expect(asyncQueuer.getIsRunning()).toBe(true) + expect(asyncQueuer.store.state.isRunning).toBe(true) asyncQueuer.stop() - expect(asyncQueuer.getIsRunning()).toBe(false) + expect(asyncQueuer.store.state.isRunning).toBe(false) }) }) @@ -608,7 +529,7 @@ describe('AsyncQueuer', () => { asyncQueuer.addItem('test2') await asyncQueuer.execute() await asyncQueuer.execute() - expect(asyncQueuer.getSuccessCount()).toBe(2) + expect(asyncQueuer.store.state.successCount).toBe(2) }) }) @@ -622,7 +543,7 @@ describe('AsyncQueuer', () => { asyncQueuer.addItem('test2') await asyncQueuer.execute() await asyncQueuer.execute() - expect(asyncQueuer.getErrorCount()).toBe(2) + expect(asyncQueuer.store.state.errorCount).toBe(2) }) }) @@ -636,7 +557,7 @@ describe('AsyncQueuer', () => { asyncQueuer.addItem('test2') await asyncQueuer.execute() await asyncQueuer.execute() - expect(asyncQueuer.getSettledCount()).toBe(2) + expect(asyncQueuer.store.state.settledCount).toBe(2) }) }) @@ -649,7 +570,7 @@ describe('AsyncQueuer', () => { asyncQueuer.addItem('test1') asyncQueuer.addItem('test2') asyncQueuer.addItem('test3') - expect(asyncQueuer.getRejectionCount()).toBe(2) + expect(asyncQueuer.store.state.rejectionCount).toBe(2) }) }) @@ -668,7 +589,7 @@ describe('AsyncQueuer', () => { await vi.advanceTimersByTimeAsync(200) asyncQueuer.start() await vi.advanceTimersByTimeAsync(0) - expect(asyncQueuer.getExpirationCount()).toBe(2) + expect(asyncQueuer.store.state.expirationCount).toBe(2) }) }) @@ -725,18 +646,6 @@ describe('AsyncQueuer', () => { expect(onExpire).toHaveBeenCalledWith('test', asyncQueuer) }) - it('should call onIsRunningChange when running state changes', () => { - const onIsRunningChange = vi.fn() - const asyncQueuer = new AsyncQueuer( - (item) => Promise.resolve(item), - { started: false, onIsRunningChange }, - ) - asyncQueuer.start() - expect(onIsRunningChange).toHaveBeenCalledWith(asyncQueuer) - asyncQueuer.stop() - expect(onIsRunningChange).toHaveBeenCalledWith(asyncQueuer) - }) - it('should call onReject when an item is rejected', () => { const onReject = vi.fn() const asyncQueuer = new AsyncQueuer( @@ -814,7 +723,8 @@ describe('AsyncQueuer', () => { return item }, { - concurrency: (queuer) => (queuer.getSize() > 1 ? 2 : 1), + concurrency: (queuer) => + queuer.store.state.items.length > 1 ? 2 : 1, started: false, }, ) @@ -875,7 +785,7 @@ describe('AsyncQueuer', () => { return item }, { - wait: (queuer) => (queuer.getSize() > 1 ? 100 : 50), + wait: (queuer) => (queuer.store.state.items.length > 1 ? 100 : 50), started: false, }, ) diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index 92dc26370..9bb94aee1 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -8,7 +8,7 @@ import type { AsyncDebouncerState, } from '@tanstack/pacer/async-debouncer' -interface ReactAsyncDebouncer< +export interface ReactAsyncDebouncer< TFn extends AnyAsyncFunction, TSelected = AsyncDebouncerState, > extends Omit, 'store'> { diff --git a/packages/react-pacer/src/async-queuer/useAsyncQueuedState.ts b/packages/react-pacer/src/async-queuer/useAsyncQueuedState.ts index a8533f4c5..35cc0283b 100644 --- a/packages/react-pacer/src/async-queuer/useAsyncQueuedState.ts +++ b/packages/react-pacer/src/async-queuer/useAsyncQueuedState.ts @@ -1,8 +1,8 @@ -import { useState } from 'react' import { useAsyncQueuer } from './useAsyncQueuer' +import type { ReactAsyncQueuer } from './useAsyncQueuer' import type { - AsyncQueuer, AsyncQueuerOptions, + AsyncQueuerState, } from '@tanstack/pacer/async-queuer' /** @@ -50,23 +50,18 @@ import type { * const pendingCount = asyncQueuer.peekPendingItems().length; * ``` */ -export function useAsyncQueuedState( +export function useAsyncQueuedState< + TValue, + TSelected extends Pick< + AsyncQueuerState, + 'items' + > = AsyncQueuerState, +>( fn: (value: TValue) => Promise, options: AsyncQueuerOptions = {}, -): [Array, AsyncQueuer] { - const [items, setItems] = useState>(options.initialItems ?? []) + selector?: (state: AsyncQueuerState) => TSelected, +): [Array, ReactAsyncQueuer] { + const asyncQueuer = useAsyncQueuer(fn, options, selector) - const asyncQueuer = useAsyncQueuer(fn, { - ...options, - onItemsChange: (asyncQueuer) => { - setItems(asyncQueuer.peekAllItems()) - options.onItemsChange?.(asyncQueuer) - }, - onIsRunningChange: (queue) => { - setItems((prev) => [...prev]) // rerender - options.onIsRunningChange?.(queue) - }, - }) - - return [items, asyncQueuer] + return [asyncQueuer.state.items, asyncQueuer] } diff --git a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts index ab3859157..6da51b938 100644 --- a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts +++ b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts @@ -1,7 +1,21 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { AsyncQueuer } from '@tanstack/pacer/async-queuer' import { bindInstanceMethods } from '@tanstack/pacer/utils' -import type { AsyncQueuerOptions } from '@tanstack/pacer/async-queuer' +import { useStore } from '@tanstack/react-store' +import type { + AsyncQueuerOptions, + AsyncQueuerState, +} from '@tanstack/pacer/async-queuer' + +export interface ReactAsyncQueuer> + extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the queuer state changes + * + * Use this instead of `queuer.store.state` + */ + state: TSelected +} /** * A lower-level React hook that creates an `AsyncQueuer` instance for managing an async queue of items. @@ -48,15 +62,25 @@ import type { AsyncQueuerOptions } from '@tanstack/pacer/async-queuer' * asyncQueuer.start(); * ``` */ -export function useAsyncQueuer( +export function useAsyncQueuer>( fn: (value: TValue) => Promise, options: AsyncQueuerOptions = {}, -): AsyncQueuer { + selector?: (state: AsyncQueuerState) => TSelected, +): ReactAsyncQueuer { const [asyncQueuer] = useState(() => bindInstanceMethods(new AsyncQueuer(fn, options)), ) + const state = useStore(asyncQueuer.store, selector) + asyncQueuer.setOptions(options) - return asyncQueuer + return useMemo( + () => + ({ + ...asyncQueuer, + state, + }) as unknown as ReactAsyncQueuer, // omit `store` in favor of `state` + [asyncQueuer, state], + ) } diff --git a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts index 598c7f557..6cd920653 100644 --- a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts +++ b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts @@ -1,78 +1,20 @@ import { AsyncQueuer } from '@tanstack/pacer/async-queuer' -import { createSignal } from 'solid-js' import { bindInstanceMethods } from '@tanstack/pacer/utils' +import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' -import type { AsyncQueuerOptions } from '@tanstack/pacer/async-queuer' +import type { + AsyncQueuerOptions, + AsyncQueuerState, +} from '@tanstack/pacer/async-queuer' -export interface SolidAsyncQueuer - extends Omit< - AsyncQueuer, - | 'getErrorCount' - | 'getIsEmpty' - | 'getIsFull' - | 'getIsIdle' - | 'getIsRunning' - | 'getRejectionCount' - | 'getSettledCount' - | 'getSize' - | 'getSuccessCount' - | 'peekActiveItems' - | 'peekAllItems' - | 'peekNextItem' - | 'peekPendingItems' - > { +export interface SolidAsyncQueuer> + extends Omit, 'store'> { /** - * Signal version of `peekActiveItems` + * Reactive state that will be updated and re-rendered when the queuer state changes + * + * Use this instead of `queuer.store.state` */ - activeItems: Accessor> - /** - * Signal version of `peekAllItems` - */ - allItems: Accessor> - /** - * Signal version of `getErrorCount` - */ - errorCount: Accessor - /** - * Signal version of `getIsEmpty` - */ - isEmpty: Accessor - /** - * Signal version of `getIsFull` - */ - isFull: Accessor - /** - * Signal version of `getIsIdle` - */ - isIdle: Accessor - /** - * Signal version of `getIsRunning` - */ - isRunning: Accessor - /** - * Signal version of `peekNextItem` - */ - nextItem: Accessor - /** - * Signal version of `peekPendingItems` - */ - pendingItems: Accessor> - /** - * Signal version of `getRejectionCount` - */ - rejectionCount: Accessor - /** - * Signal version of `getSettledCount` - */ - settledCount: Accessor - /** - * Signal version of `getSize` - */ - size: Accessor - /** - * Signal version of `getSuccessCount` - */ - successCount: Accessor + state: Accessor } /** @@ -127,81 +69,17 @@ export interface SolidAsyncQueuer * const pending = asyncQueuer.pendingItems(); * ``` */ -export function createAsyncQueuer( +export function createAsyncQueuer>( fn: (value: TValue) => Promise, initialOptions: AsyncQueuerOptions = {}, -): SolidAsyncQueuer { + selector?: (state: AsyncQueuerState) => TSelected, +): SolidAsyncQueuer { const asyncQueuer = new AsyncQueuer(fn, initialOptions) - const [successCount, setSuccessCount] = createSignal( - asyncQueuer.getSuccessCount(), - ) - const [errorCount, setErrorCount] = createSignal(asyncQueuer.getErrorCount()) - const [settledCount, setSettledCount] = createSignal( - asyncQueuer.getSettledCount(), - ) - const [rejectionCount, setRejectionCount] = createSignal( - asyncQueuer.getRejectionCount(), - ) - const [isEmpty, setIsEmpty] = createSignal(asyncQueuer.getIsEmpty()) - const [isFull, setIsFull] = createSignal(asyncQueuer.getIsFull()) - const [isIdle, setIsIdle] = createSignal(asyncQueuer.getIsIdle()) - const [isRunning, setIsRunning] = createSignal(asyncQueuer.getIsRunning()) - const [allItems, setAllItems] = createSignal>( - asyncQueuer.peekAllItems(), - ) - const [activeItems, setActiveItems] = createSignal>( - asyncQueuer.peekActiveItems(), - ) - const [pendingItems, setPendingItems] = createSignal>( - asyncQueuer.peekPendingItems(), - ) - const [nextItem, setNextItem] = createSignal( - asyncQueuer.peekNextItem(), - ) - const [size, setSize] = createSignal(asyncQueuer.getSize()) - - asyncQueuer.setOptions({ - onItemsChange: (queuer) => { - setActiveItems(queuer.peekActiveItems()) - setAllItems(queuer.peekAllItems()) - setErrorCount(queuer.getErrorCount()) - setIsEmpty(queuer.getIsEmpty()) - setIsFull(queuer.getIsFull()) - setIsIdle(queuer.getIsIdle()) - setNextItem(() => queuer.peekNextItem()) - setPendingItems(queuer.peekPendingItems()) - setRejectionCount(queuer.getRejectionCount()) - setSettledCount(queuer.getSettledCount()) - setSize(queuer.getSize()) - setSuccessCount(queuer.getSuccessCount()) - initialOptions.onItemsChange?.(queuer) - }, - onIsRunningChange: (queuer) => { - setIsRunning(queuer.getIsRunning()) - setIsIdle(queuer.getIsIdle()) - initialOptions.onIsRunningChange?.(queuer) - }, - onReject: (item, queuer) => { - setRejectionCount(queuer.getRejectionCount()) - initialOptions.onReject?.(item, queuer) - }, - }) + const state = useStore(asyncQueuer.store, selector) return { ...bindInstanceMethods(asyncQueuer), - activeItems, - allItems, - errorCount, - isEmpty, - isFull, - isIdle, - isRunning, - nextItem, - pendingItems, - rejectionCount, - settledCount, - size, - successCount, - } as SolidAsyncQueuer + state, + } as unknown as SolidAsyncQueuer } From f5c478d8faf26a1703fd65535d65f9869786464d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 02:05:08 +0000 Subject: [PATCH 19/51] ci: apply automated fixes --- .../reference/functions/createasyncqueuer.md | 15 +- .../reference/interfaces/solidasyncqueuer.md | 173 +----------------- 2 files changed, 22 insertions(+), 166 deletions(-) diff --git a/docs/framework/solid/reference/functions/createasyncqueuer.md b/docs/framework/solid/reference/functions/createasyncqueuer.md index 024061aa3..bc1313db9 100644 --- a/docs/framework/solid/reference/functions/createasyncqueuer.md +++ b/docs/framework/solid/reference/functions/createasyncqueuer.md @@ -8,10 +8,13 @@ title: createAsyncQueuer # Function: createAsyncQueuer() ```ts -function createAsyncQueuer(fn, initialOptions): SolidAsyncQueuer +function createAsyncQueuer( + fn, + initialOptions, +selector?): SolidAsyncQueuer ``` -Defined in: [async-queuer/createAsyncQueuer.ts:130](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L130) +Defined in: [async-queuer/createAsyncQueuer.ts:72](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L72) Creates a Solid-compatible AsyncQueuer instance for managing an asynchronous queue of items, exposing Solid signals for all stateful properties. @@ -68,6 +71,8 @@ const pending = asyncQueuer.pendingItems(); • **TValue** +• **TSelected** = `AsyncQueuerState`\<`TValue`\> + ## Parameters ### fn @@ -78,6 +83,10 @@ const pending = asyncQueuer.pendingItems(); `AsyncQueuerOptions`\<`TValue`\> = `{}` +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidAsyncQueuer`](../../interfaces/solidasyncqueuer.md)\<`TValue`\> +[`SolidAsyncQueuer`](../../interfaces/solidasyncqueuer.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/solid/reference/interfaces/solidasyncqueuer.md b/docs/framework/solid/reference/interfaces/solidasyncqueuer.md index f7e816be8..538535932 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncqueuer.md +++ b/docs/framework/solid/reference/interfaces/solidasyncqueuer.md @@ -5,183 +5,30 @@ title: SolidAsyncQueuer -# Interface: SolidAsyncQueuer\ +# Interface: SolidAsyncQueuer\ -Defined in: [async-queuer/createAsyncQueuer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L7) +Defined in: [async-queuer/createAsyncQueuer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L10) ## Extends -- `Omit`\<`AsyncQueuer`\<`TValue`\>, - \| `"getErrorCount"` - \| `"getIsEmpty"` - \| `"getIsFull"` - \| `"getIsIdle"` - \| `"getIsRunning"` - \| `"getRejectionCount"` - \| `"getSettledCount"` - \| `"getSize"` - \| `"getSuccessCount"` - \| `"peekActiveItems"` - \| `"peekAllItems"` - \| `"peekNextItem"` - \| `"peekPendingItems"`\> +- `Omit`\<`AsyncQueuer`\<`TValue`\>, `"store"`\> ## Type Parameters • **TValue** -## Properties - -### activeItems - -```ts -activeItems: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L27) - -Signal version of `peekActiveItems` - -*** - -### allItems - -```ts -allItems: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:31](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L31) - -Signal version of `peekAllItems` - -*** - -### errorCount - -```ts -errorCount: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:35](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L35) - -Signal version of `getErrorCount` - -*** - -### isEmpty - -```ts -isEmpty: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L39) - -Signal version of `getIsEmpty` - -*** - -### isFull - -```ts -isFull: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:43](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L43) - -Signal version of `getIsFull` - -*** - -### isIdle - -```ts -isIdle: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:47](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L47) - -Signal version of `getIsIdle` +• **TSelected** = `AsyncQueuerState`\<`TValue`\> -*** - -### isRunning - -```ts -isRunning: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:51](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L51) - -Signal version of `getIsRunning` - -*** - -### nextItem - -```ts -nextItem: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L55) - -Signal version of `peekNextItem` - -*** - -### pendingItems - -```ts -pendingItems: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L59) - -Signal version of `peekPendingItems` - -*** - -### rejectionCount - -```ts -rejectionCount: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:63](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L63) - -Signal version of `getRejectionCount` - -*** - -### settledCount - -```ts -settledCount: Accessor; -``` - -Defined in: [async-queuer/createAsyncQueuer.ts:67](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L67) - -Signal version of `getSettledCount` - -*** +## Properties -### size +### state ```ts -size: Accessor; +state: Accessor; ``` -Defined in: [async-queuer/createAsyncQueuer.ts:71](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L71) - -Signal version of `getSize` - -*** - -### successCount - -```ts -successCount: Accessor; -``` +Defined in: [async-queuer/createAsyncQueuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L17) -Defined in: [async-queuer/createAsyncQueuer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L75) +Reactive state that will be updated and re-rendered when the queuer state changes -Signal version of `getSuccessCount` +Use this instead of `queuer.store.state` From 288f63f4acffcbb7886a883a0e6dcc16746e4006 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 29 Jun 2025 22:26:48 -0500 Subject: [PATCH 20/51] update rate limiter with tanstack store --- .../react/useRateLimitedState/src/index.tsx | 17 +-- .../react/useRateLimitedValue/src/index.tsx | 9 +- examples/react/useRateLimiter/src/index.tsx | 24 ++-- .../createRateLimitedSignal/src/index.tsx | 19 +-- .../createRateLimitedValue/src/index.tsx | 7 +- .../solid/createRateLimiter/src/index.tsx | 19 +-- packages/pacer/src/async-debouncer.ts | 35 +++-- packages/pacer/src/async-queuer.ts | 99 +++++++------ packages/pacer/src/rate-limiter.ts | 134 ++++++++---------- packages/pacer/tests/rate-limiter.test.ts | 6 +- .../rate-limiter/useRateLimitedCallback.ts | 20 +-- .../src/rate-limiter/useRateLimitedState.ts | 12 +- .../src/rate-limiter/useRateLimitedValue.ts | 13 +- .../src/rate-limiter/useRateLimiter.ts | 39 ++++- .../rate-limiter/createRateLimitedSignal.ts | 20 ++- .../rate-limiter/createRateLimitedValue.ts | 12 +- .../src/rate-limiter/createRateLimiter.ts | 82 ++++------- 17 files changed, 301 insertions(+), 266 deletions(-) diff --git a/examples/react/useRateLimitedState/src/index.tsx b/examples/react/useRateLimitedState/src/index.tsx index 904eb6273..371990bfb 100644 --- a/examples/react/useRateLimitedState/src/index.tsx +++ b/examples/react/useRateLimitedState/src/index.tsx @@ -36,11 +36,11 @@ function App1() {
- + - + @@ -104,11 +104,11 @@ function App2() { - + - + @@ -190,11 +190,11 @@ function App3() { - + - + @@ -210,7 +210,7 @@ function App3() { - + @@ -218,7 +218,8 @@ function App3() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - rateLimiter.getExecutionCount()) / + ((instantExecutionCount - + rateLimiter.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/react/useRateLimitedValue/src/index.tsx b/examples/react/useRateLimitedValue/src/index.tsx index f7c07c10c..3dd0d8a58 100644 --- a/examples/react/useRateLimitedValue/src/index.tsx +++ b/examples/react/useRateLimitedValue/src/index.tsx @@ -148,11 +148,11 @@ function App3() { - + - + @@ -168,7 +168,7 @@ function App3() { - + @@ -176,7 +176,8 @@ function App3() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - rateLimiter.getExecutionCount()) / + ((instantExecutionCount - + rateLimiter.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index aa3c8c240..6039c2093 100644 --- a/examples/react/useRateLimiter/src/index.tsx +++ b/examples/react/useRateLimiter/src/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import ReactDOM from 'react-dom/client' import { useRateLimiter } from '@tanstack/react-pacer/rate-limiter' import { useStoragePersister } from '@tanstack/react-persister/storage-persister' @@ -29,9 +29,12 @@ function App1() { ), // optional local storage persister to retain state on page refresh initialState: rateLimiterPersister.loadState(), - onStateChange: (state) => rateLimiterPersister.saveState(state), }) + useEffect(() => { + rateLimiterPersister.saveState(rateLimiter.state) + }, [rateLimiter.state]) + function increment() { // this pattern helps avoid common bugs with stale closures and state setInstantCount((c) => { @@ -48,11 +51,11 @@ function App1() { - + - + @@ -125,11 +128,11 @@ function App2() { - + - + @@ -218,11 +221,11 @@ function App3() { - + - + @@ -238,7 +241,7 @@ function App3() { - + @@ -246,7 +249,8 @@ function App3() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - rateLimiter.getExecutionCount()) / + ((instantExecutionCount - + rateLimiter.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/solid/createRateLimitedSignal/src/index.tsx b/examples/solid/createRateLimitedSignal/src/index.tsx index e51b13675..46ecdea0b 100644 --- a/examples/solid/createRateLimitedSignal/src/index.tsx +++ b/examples/solid/createRateLimitedSignal/src/index.tsx @@ -30,11 +30,11 @@ function App1() { - + - + @@ -48,7 +48,7 @@ function App1() {
Is Pending:{debouncer.getIsPending().toString()}{debouncer.getState().isPending.toString()}
Execution Count:
Is Pending:{debouncer.getIsPending().toString()}{debouncer.getState().isPending.toString()}
Execution Count:
Is Pending:{debouncer.getIsPending().toString()}{debouncer.getState().isPending.toString()}
Instant Executions:
Is Pending:{debouncer.getIsPending().toString()}{debouncer.getState().isPending.toString()}
Instant Executions:
Is Pending:{setCountDebouncer.getIsPending().toString()}{setCountdebouncer.getState().isPending.toString()}
Execution Count:
Is Pending:{setSearchDebouncer.getIsPending().toString()}{setSearchdebouncer.getState().isPending.toString()}
Execution Count:
Is Pending:{setValueDebouncer.getIsPending().toString()}{setValuedebouncer.getState().isPending.toString()}
Instant Executions:
Execution Count:{setCountDebouncer.executionCount()}{setCountDebouncer.store.executionCount}
@@ -88,7 +88,7 @@ function App2() {
Execution Count:{setSearchDebouncer.executionCount()}{setSearchDebouncer.store.executionCount}
@@ -166,12 +166,12 @@ function App3() {
Debounced Executions:{setValueDebouncer.executionCount()}{setValueDebouncer.store.executionCount}
Saved Executions: - {instantExecutionCount() - setValueDebouncer.executionCount()} + {instantExecutionCount() - setValueDebouncer.store.executionCount}
Execution Count:{setCountThrottler.executionCount()}{setCountThrottler.store.executionCount}
Instant Count:
Execution Count:{setSearchThrottler.executionCount()}{setSearchThrottler.store.executionCount}
Instant Search:
Throttled Executions:{setValueThrottler.executionCount()}{setValueThrottler.store.executionCount}
Saved Executions: - {instantExecutionCount() - setValueThrottler.executionCount()} + {instantExecutionCount() - setValueThrottler.store.executionCount}
Execution Count:{rateLimiter.getExecutionCount()}{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.getRejectionCount()}{rateLimiter.state.rejectionCount}
Instant Count:
Execution Count:{rateLimiter.getExecutionCount()}{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.getRejectionCount()}{rateLimiter.state.rejectionCount}
Instant Search:
Execution Count:{rateLimiter.getExecutionCount()}{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.getRejectionCount()}{rateLimiter.state.rejectionCount}
Remaining in Window:
Saved Executions:{instantExecutionCount - rateLimiter.getExecutionCount()}{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction:
Execution Count:{rateLimiter.getExecutionCount()}{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.getRejectionCount()}{rateLimiter.state.rejectionCount}
Remaining in Window:
Saved Executions:{instantExecutionCount - rateLimiter.getExecutionCount()}{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction:
Execution Count:{rateLimiter.getExecutionCount()}{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.getRejectionCount()}{rateLimiter.state.rejectionCount}
Remaining in Window:
Execution Count:{rateLimiter.getExecutionCount()}{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.getRejectionCount()}{rateLimiter.state.rejectionCount}
Remaining in Window:
Execution Count:{rateLimiter.getExecutionCount()}{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.getRejectionCount()}{rateLimiter.state.rejectionCount}
Remaining in Window:
Saved Executions:{instantExecutionCount - rateLimiter.getExecutionCount()}{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction:
Execution Count:{rateLimiter.executionCount()}{rateLimiter.state().executionCount}
Rejection Count:{rateLimiter.rejectionCount()}{rateLimiter.state().rejectionCount}
Instant Count:
- @@ -91,11 +91,11 @@ function App2() { Execution Count: - {rateLimiter.executionCount()} + {rateLimiter.state().executionCount} Rejection Count: - {rateLimiter.rejectionCount()} + {rateLimiter.state().rejectionCount} Instant Search: @@ -108,7 +108,7 @@ function App2() {
- @@ -183,11 +183,11 @@ function App3() { Rate Limited Executions: - {rateLimiter.executionCount()} + {rateLimiter.state().executionCount} Rejected Executions: - {rateLimiter.rejectionCount()} + {rateLimiter.state().rejectionCount} % Reduction: @@ -195,7 +195,8 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - rateLimiter.executionCount()) / + ((instantExecutionCount() - + rateLimiter.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/examples/solid/createRateLimitedValue/src/index.tsx b/examples/solid/createRateLimitedValue/src/index.tsx index c26c409d7..c82da0807 100644 --- a/examples/solid/createRateLimitedValue/src/index.tsx +++ b/examples/solid/createRateLimitedValue/src/index.tsx @@ -141,11 +141,11 @@ function App3() { Rate Limited Executions: - {rateLimiter.executionCount()} + {rateLimiter.state().executionCount} Rejected Executions: - {rateLimiter.rejectionCount()} + {rateLimiter.state().rejectionCount} % Reduction: @@ -153,7 +153,8 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - rateLimiter.executionCount()) / + ((instantExecutionCount() - + rateLimiter.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/examples/solid/createRateLimiter/src/index.tsx b/examples/solid/createRateLimiter/src/index.tsx index d9877f0c0..26b0653cb 100644 --- a/examples/solid/createRateLimiter/src/index.tsx +++ b/examples/solid/createRateLimiter/src/index.tsx @@ -29,11 +29,11 @@ function App1() { Execution Count: - {rateLimiter.executionCount()} + {rateLimiter.state().executionCount} Rejection Count: - {rateLimiter.rejectionCount()} + {rateLimiter.state().rejectionCount} Instant Count: @@ -47,7 +47,7 @@ function App1() {
- @@ -90,11 +90,11 @@ function App2() { Execution Count: - {rateLimiter.executionCount()} + {rateLimiter.state().executionCount} Rejection Count: - {rateLimiter.rejectionCount()} + {rateLimiter.state().rejectionCount} Instant Search: @@ -107,7 +107,7 @@ function App2() {
- @@ -180,11 +180,11 @@ function App3() { Rate Limited Executions: - {rateLimiter.executionCount()} + {rateLimiter.state().executionCount} Rejected Executions: - {rateLimiter.rejectionCount()} + {rateLimiter.state().rejectionCount} % Reduction: @@ -192,7 +192,8 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - rateLimiter.executionCount()) / + ((instantExecutionCount() - + rateLimiter.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index b1eb508d5..62391d429 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -14,6 +14,22 @@ export interface AsyncDebouncerState { successCount: number } +function getDefaultAsyncDebouncerState< + TFn extends AnyAsyncFunction, +>(): AsyncDebouncerState { + return structuredClone({ + canLeadingExecute: true, + errorCount: 0, + isExecuting: false, + isPending: false, + lastArgs: undefined, + lastResult: undefined, + settleCount: 0, + successCount: 0, + status: 'idle', + }) +} + /** * Options for configuring an async debounced function */ @@ -126,17 +142,7 @@ const defaultOptions: AsyncDebouncerOptionsWithOptionalCallbacks = { export class AsyncDebouncer { readonly store: Store> = new Store< AsyncDebouncerState - >({ - canLeadingExecute: true, - errorCount: 0, - isExecuting: false, - isPending: false, - lastArgs: undefined, - lastResult: undefined, - settleCount: 0, - successCount: 0, - status: 'idle', - }) + >(getDefaultAsyncDebouncerState()) #options: AsyncDebouncerOptions #abortController: AbortController | null = null #timeoutId: NodeJS.Timeout | null = null @@ -310,6 +316,13 @@ export class AsyncDebouncer { } this.#setState({ canLeadingExecute: true }) } + + /** + * Resets the debouncer state to its default values + */ + reset(): void { + this.#setState(getDefaultAsyncDebouncerState()) + } } /** diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index 098072c21..ad88d85dc 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -22,6 +22,27 @@ export interface AsyncQueuerState { successCount: number } +function getDefaultAsyncQueuerState(): AsyncQueuerState { + return structuredClone({ + activeItems: [], + errorCount: 0, + expirationCount: 0, + isEmpty: true, + isFull: false, + isIdle: true, + isRunning: true, + itemTimestamps: [], + items: [], + lastResult: null, + pendingTick: false, + rejectionCount: 0, + settledCount: 0, + size: 0, + status: 'idle', + successCount: 0, + }) +} + export interface AsyncQueuerOptions { /** * Default position to add items to the queuer @@ -182,24 +203,7 @@ const defaultOptions: AsyncQueuerOptionsWithOptionalCallbacks = { export class AsyncQueuer { readonly store: Store> = new Store< AsyncQueuerState - >({ - activeItems: [], - errorCount: 0, - expirationCount: 0, - isEmpty: true, - isFull: false, - isIdle: true, - isRunning: true, - itemTimestamps: [], - items: [], - lastResult: null, - pendingTick: false, - rejectionCount: 0, - settledCount: 0, - size: 0, - status: 'idle', - successCount: 0, - }) + >(getDefaultAsyncQueuerState()) #options: AsyncQueuerOptions constructor( @@ -324,32 +328,6 @@ export class AsyncQueuer { this.#setState({ pendingTick: false }) } - /** - * Starts processing items in the queue. If already running, does nothing. - */ - start(): void { - this.#setState({ isRunning: true }) - if (!this.store.state.pendingTick && !this.store.state.isEmpty) { - this.#setState({ pendingTick: true }) - this.#tick() - } - } - - /** - * Stops processing items in the queue. Does not clear the queue. - */ - stop(): void { - this.#setState({ isRunning: false, pendingTick: false }) - } - - /** - * Removes all pending items from the queue. Does not affect active tasks. - */ - clear(): void { - this.#setState({ items: [], itemTimestamps: [] }) - this.#options.onItemsChange?.(this) - } - /** * 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. @@ -604,6 +582,39 @@ export class AsyncQueuer { peekPendingItems(): Array { return [...this.store.state.items] } + + /** + * Starts processing items in the queue. If already running, does nothing. + */ + start(): void { + this.#setState({ isRunning: true }) + if (!this.store.state.pendingTick && !this.store.state.isEmpty) { + this.#setState({ pendingTick: true }) + this.#tick() + } + } + + /** + * Stops processing items in the queue. Does not clear the queue. + */ + stop(): void { + this.#setState({ isRunning: false, pendingTick: false }) + } + + /** + * Removes all pending items from the queue. Does not affect active tasks. + */ + clear(): void { + this.#setState({ items: [], itemTimestamps: [] }) + this.#options.onItemsChange?.(this) + } + + /** + * Resets the queuer state to its default values + */ + reset(): void { + this.#setState(getDefaultAsyncQueuerState()) + } } /** diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index 22abff4ec..6de5ec11a 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -1,3 +1,4 @@ +import { Store } from '@tanstack/store' import { parseFunctionOrValue } from './utils' import type { AnyFunction } from './types' @@ -7,6 +8,14 @@ export interface RateLimiterState { rejectionCount: number } +function getDefaultRateLimiterState(): RateLimiterState { + return structuredClone({ + executionCount: 0, + executionTimes: [], + rejectionCount: 0, + }) +} + /** * Options for configuring a rate-limited function */ @@ -33,13 +42,6 @@ export interface RateLimiterOptions { * Optional callback function that is called when an execution is rejected due to rate limiting */ onReject?: (rateLimiter: RateLimiter) => void - /** - * Callback function that is called when the state of the rate limiter is updated - */ - onStateChange?: ( - state: RateLimiterState, - rateLimiter: RateLimiter, - ) => void /** * Time window in milliseconds within which the limit applies. * Can be a number or a callback function that receives the rate limiter instance and returns a number. @@ -106,12 +108,10 @@ const defaultOptions: Omit< * ``` */ export class RateLimiter { + readonly store: Store = new Store( + getDefaultRateLimiterState(), + ) #options: RateLimiterOptions - #state: RateLimiterState = { - executionCount: 0, - executionTimes: [], - rejectionCount: 0, - } constructor( private fn: TFn, @@ -121,10 +121,7 @@ export class RateLimiter { ...defaultOptions, ...initialOptions, } - this.#state = { - ...this.#state, - ...this.#options.initialState, - } + this.#setState(this.#options.initialState ?? {}) } /** @@ -134,40 +131,34 @@ export class RateLimiter { this.#options = { ...this.#options, ...newOptions } } - /** - * Returns the current rate limiter options - */ - getOptions(): Required> { - return this.#options as Required> - } - - getState(): RateLimiterState { - return { ...this.#state } - } - #setState(newState: Partial): void { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + return combinedState + }) } /** * Returns the current enabled state of the rate limiter */ - getEnabled(): boolean { - return parseFunctionOrValue(this.#options.enabled, this)! + #getEnabled(): boolean { + return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current limit of executions allowed within the time window */ - getLimit(): number { + #getLimit(): number { return parseFunctionOrValue(this.#options.limit, this) } /** * Returns the current time window in milliseconds */ - getWindow(): number { + #getWindow(): number { return parseFunctionOrValue(this.#options.window, this) } @@ -189,22 +180,11 @@ export class RateLimiter { maybeExecute(...args: Parameters): boolean { this.#cleanupOldExecutions() - if (this.#options.windowType === 'sliding') { - // For sliding window, we can execute if we have capacity in the current window - if (this.#state.executionTimes.length < this.getLimit()) { - this.#execute(...args) - return true - } - } else { - // For fixed window, we need to check if we're in a new window - const now = Date.now() - const oldestExecution = Math.min(...this.#state.executionTimes) - const isNewWindow = oldestExecution + this.getWindow() <= now + const relevantExecutionTimes = this.#getRelevantExecutionTimes() - if (isNewWindow || this.#state.executionTimes.length < this.getLimit()) { - this.#execute(...args) - return true - } + if (relevantExecutionTimes.length < this.#getLimit()) { + this.#execute(...args) + return true } this.#rejectFunction() @@ -212,53 +192,57 @@ export class RateLimiter { } #execute(...args: Parameters): void { - if (!this.getEnabled()) return + if (!this.#getEnabled()) return const now = Date.now() this.fn(...args) // EXECUTE! - this.#state.executionTimes.push(now) // mutate state directly for performance + this.store.state.executionTimes.push(now) // mutate state directly for performance this.#setState({ - executionCount: this.#state.executionCount + 1, + executionCount: this.store.state.executionCount + 1, }) this.#options.onExecute?.(this) } #rejectFunction(): void { this.#setState({ - rejectionCount: this.#state.rejectionCount + 1, + rejectionCount: this.store.state.rejectionCount + 1, }) this.#options.onReject?.(this) } + #getRelevantExecutionTimes(): Array { + if (this.#options.windowType === 'sliding') { + // For sliding window, return all executions within the current window + return this.store.state.executionTimes.filter( + (time) => time > Date.now() - this.#getWindow(), + ) + } else { + // For fixed window, return all executions in the current window + // The window starts from the oldest execution time + const oldestExecution = Math.min(...this.store.state.executionTimes) + const windowStart = oldestExecution + return this.store.state.executionTimes.filter( + (time) => + time >= windowStart && time <= windowStart + this.#getWindow(), + ) + } + } + #cleanupOldExecutions(): void { const now = Date.now() - const windowStart = now - this.getWindow() + const windowStart = now - this.#getWindow() this.#setState({ - executionTimes: this.#state.executionTimes.filter( + executionTimes: this.store.state.executionTimes.filter( (time) => time > windowStart, ), }) } - /** - * Returns the number of times the function has been executed - */ - getExecutionCount(): number { - return this.#state.executionCount - } - - /** - * Returns the number of times the function has been rejected - */ - getRejectionCount(): number { - return this.#state.rejectionCount - } - /** * Returns the number of remaining executions allowed in the current window */ getRemainingInWindow(): number { - this.#cleanupOldExecutions() - return Math.max(0, this.getLimit() - this.#state.executionTimes.length) + const relevantExecutionTimes = this.#getRelevantExecutionTimes() + return Math.max(0, this.#getLimit() - relevantExecutionTimes.length) } /** @@ -268,19 +252,15 @@ export class RateLimiter { if (this.getRemainingInWindow() > 0) { return 0 } - const oldestExecution = this.#state.executionTimes[0] ?? Infinity - return oldestExecution + this.getWindow() - Date.now() + const oldestExecution = this.store.state.executionTimes[0] ?? Infinity + return oldestExecution + this.#getWindow() - Date.now() } /** * Resets the rate limiter state */ reset(): void { - this.#setState({ - executionTimes: [], - executionCount: 0, - rejectionCount: 0, - }) + this.#setState(getDefaultRateLimiterState()) } } diff --git a/packages/pacer/tests/rate-limiter.test.ts b/packages/pacer/tests/rate-limiter.test.ts index 55ac29900..e689acc98 100644 --- a/packages/pacer/tests/rate-limiter.test.ts +++ b/packages/pacer/tests/rate-limiter.test.ts @@ -45,14 +45,14 @@ describe('RateLimiter', () => { const rateLimiter = new RateLimiter(mockFn, { limit: 2, window: 1000 }) rateLimiter.maybeExecute() - expect(rateLimiter.getExecutionCount()).toBe(1) + expect(rateLimiter.store.state.executionCount).toBe(1) rateLimiter.maybeExecute() - expect(rateLimiter.getExecutionCount()).toBe(2) + expect(rateLimiter.store.state.executionCount).toBe(2) // This should not increment the count rateLimiter.maybeExecute() - expect(rateLimiter.getExecutionCount()).toBe(2) + expect(rateLimiter.store.state.executionCount).toBe(2) }) it('should track remaining executions', () => { diff --git a/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts b/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts index c5514cdb6..56531282f 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts @@ -1,7 +1,10 @@ import { useCallback } from 'react' import { useRateLimiter } from './useRateLimiter' import type { AnyFunction } from '@tanstack/pacer/types' -import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' +import type { + RateLimiterOptions, + RateLimiterState, +} from '@tanstack/pacer/rate-limiter' /** * A React hook that creates a rate-limited version of a callback function. @@ -58,11 +61,12 @@ import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' */ export function useRateLimitedCallback< TFn extends AnyFunction, - TArgs extends Parameters, ->(fn: TFn, options: RateLimiterOptions) { - const rateLimitedFn = useRateLimiter(fn, options).maybeExecute - return useCallback( - (...args: TArgs) => rateLimitedFn(...args), - [rateLimitedFn], - ) + TSelected = RateLimiterState, +>( + fn: TFn, + options: RateLimiterOptions, + selector?: (state: RateLimiterState) => TSelected, +): (...args: Parameters) => boolean { + const rateLimitedFn = useRateLimiter(fn, options, selector).maybeExecute + return useCallback((...args) => rateLimitedFn(...args), [rateLimitedFn]) } diff --git a/packages/react-pacer/src/rate-limiter/useRateLimitedState.ts b/packages/react-pacer/src/rate-limiter/useRateLimitedState.ts index f59058734..473d2d7f6 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimitedState.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimitedState.ts @@ -1,8 +1,9 @@ import { useState } from 'react' import { useRateLimiter } from './useRateLimiter' +import type { ReactRateLimiter } from './useRateLimiter' import type { - RateLimiter, RateLimiterOptions, + RateLimiterState, } from '@tanstack/pacer/rate-limiter' /** @@ -63,15 +64,16 @@ import type { * }; * ``` */ -export function useRateLimitedState( +export function useRateLimitedState( value: TValue, options: RateLimiterOptions>>, + selector?: (state: RateLimiterState) => TSelected, ): [ TValue, React.Dispatch>, - RateLimiter>>, + ReactRateLimiter>, TSelected>, ] { - const [rateLimitedValue, setRateLimitedValue] = useState(value) - const rateLimiter = useRateLimiter(setRateLimitedValue, options) + const [rateLimitedValue, setRateLimitedValue] = useState(value) + const rateLimiter = useRateLimiter(setRateLimitedValue, options, selector) return [rateLimitedValue, rateLimiter.maybeExecute, rateLimiter] } diff --git a/packages/react-pacer/src/rate-limiter/useRateLimitedValue.ts b/packages/react-pacer/src/rate-limiter/useRateLimitedValue.ts index 4cc9d50a9..956694d60 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimitedValue.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimitedValue.ts @@ -1,8 +1,9 @@ import { useEffect } from 'react' import { useRateLimitedState } from './useRateLimitedState' +import type { ReactRateLimiter } from './useRateLimiter' import type { - RateLimiter, RateLimiterOptions, + RateLimiterState, } from '@tanstack/pacer/rate-limiter' /** @@ -52,12 +53,16 @@ import type { * }); * ``` */ -export function useRateLimitedValue( +export function useRateLimitedValue( value: TValue, options: RateLimiterOptions>>, -): [TValue, RateLimiter>>] { + selector?: (state: RateLimiterState) => TSelected, +): [ + TValue, + ReactRateLimiter>, TSelected>, +] { const [rateLimitedValue, setRateLimitedValue, rateLimiter] = - useRateLimitedState(value, options) + useRateLimitedState(value, options, selector) useEffect(() => { setRateLimitedValue(value) diff --git a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts index 488f012cd..4221150e0 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts @@ -1,9 +1,25 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { RateLimiter } from '@tanstack/pacer/rate-limiter' import { bindInstanceMethods } from '@tanstack/pacer/utils' -import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' +import { useStore, } from '@tanstack/react-store' +import type { + RateLimiterOptions, + RateLimiterState, +} from '@tanstack/pacer/rate-limiter' import type { AnyFunction } from '@tanstack/pacer/types' +export interface ReactRateLimiter< + TFn extends AnyFunction, + TSelected = RateLimiterState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the rate limiter state changes + * + * Use this instead of `rateLimiter.store.state` + */ + state: TSelected +} + /** * A low-level React hook that creates a `RateLimiter` instance to enforce rate limits on function execution. * @@ -52,15 +68,28 @@ import type { AnyFunction } from '@tanstack/pacer/types' * }; * ``` */ -export function useRateLimiter( +export function useRateLimiter< + TFn extends AnyFunction, + TSelected = RateLimiterState, +>( fn: TFn, options: RateLimiterOptions, -): RateLimiter { + selector?: (state: RateLimiterState) => TSelected, +): ReactRateLimiter { const [rateLimiter] = useState(() => bindInstanceMethods(new RateLimiter(fn, options)), ) + const state = useStore(rateLimiter.store, selector) + rateLimiter.setOptions(options) - return rateLimiter + return useMemo( + () => + ({ + ...rateLimiter, + state, + }) as unknown as ReactRateLimiter, // omit `store` in favor of `state` + [rateLimiter, state], + ) } diff --git a/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts b/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts index 87651d5a9..f39956652 100644 --- a/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts +++ b/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts @@ -2,7 +2,10 @@ import { createSignal } from 'solid-js' import { createRateLimiter } from './createRateLimiter' import type { SolidRateLimiter } from './createRateLimiter' import type { Accessor, Setter } from 'solid-js' -import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' +import type { + RateLimiterOptions, + RateLimiterState, +} from '@tanstack/pacer/rate-limiter' /** * A Solid hook that creates a rate-limited state value that enforces a hard limit on state updates within a time window. @@ -62,13 +65,22 @@ import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' * }; * ``` */ -export function createRateLimitedSignal( +export function createRateLimitedSignal( value: TValue, initialOptions: RateLimiterOptions>, -): [Accessor, Setter, SolidRateLimiter>] { + selector?: (state: RateLimiterState) => TSelected, +): [ + Accessor, + Setter, + SolidRateLimiter, TSelected>, +] { const [rateLimitedValue, setRateLimitedValue] = createSignal(value) - const rateLimiter = createRateLimiter(setRateLimitedValue, initialOptions) + const rateLimiter = createRateLimiter( + setRateLimitedValue, + initialOptions, + selector, + ) return [ rateLimitedValue, diff --git a/packages/solid-pacer/src/rate-limiter/createRateLimitedValue.ts b/packages/solid-pacer/src/rate-limiter/createRateLimitedValue.ts index f4512e817..c2a9187fe 100644 --- a/packages/solid-pacer/src/rate-limiter/createRateLimitedValue.ts +++ b/packages/solid-pacer/src/rate-limiter/createRateLimitedValue.ts @@ -2,7 +2,10 @@ import { createEffect } from 'solid-js' import { createRateLimitedSignal } from './createRateLimitedSignal' import type { SolidRateLimiter } from './createRateLimiter' import type { Accessor, Setter } from 'solid-js' -import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' +import type { + RateLimiterOptions, + RateLimiterState, +} from '@tanstack/pacer/rate-limiter' /** * A high-level Solid hook that creates a rate-limited version of a value that updates at most a certain number of times within a time window. @@ -47,12 +50,13 @@ import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' * rateLimiter.reset(); // Reset the rate limit window * ``` */ -export function createRateLimitedValue( +export function createRateLimitedValue( value: Accessor, initialOptions: RateLimiterOptions>, -): [Accessor, SolidRateLimiter>] { + selector?: (state: RateLimiterState) => TSelected, +): [Accessor, SolidRateLimiter, TSelected>] { const [rateLimitedValue, setRateLimitedValue, rateLimiter] = - createRateLimitedSignal(value(), initialOptions) + createRateLimitedSignal(value(), initialOptions, selector) createEffect(() => { setRateLimitedValue(value() as any) diff --git a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts index e3801f361..253a4e857 100644 --- a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts +++ b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts @@ -1,22 +1,23 @@ import { RateLimiter } from '@tanstack/pacer/rate-limiter' -import { createSignal } from 'solid-js' import { bindInstanceMethods } from '@tanstack/pacer/utils' +import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' import type { AnyFunction } from '@tanstack/pacer/types' -import type { RateLimiterOptions } from '@tanstack/pacer/rate-limiter' +import type { + RateLimiterOptions, + RateLimiterState, +} from '@tanstack/pacer/rate-limiter' -export interface SolidRateLimiter - extends Omit< - RateLimiter, - | 'getExecutionCount' - | 'getMsUntilNextWindow' - | 'getRejectionCount' - | 'getRemainingInWindow' - > { - executionCount: Accessor - msUntilNextWindow: Accessor - rejectionCount: Accessor - remainingInWindow: Accessor +export interface SolidRateLimiter< + TFn extends AnyFunction, + TSelected = RateLimiterState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated when the rate limiter state changes + * + * Use this instead of `rateLimiter.store.state` + */ + state: Accessor } /** @@ -59,57 +60,22 @@ export interface SolidRateLimiter * console.log('Next window in:', rateLimiter.msUntilNextWindow()); * ``` */ -export function createRateLimiter( +export function createRateLimiter< + TFn extends AnyFunction, + TSelected = RateLimiterState, +>( fn: TFn, initialOptions: RateLimiterOptions, -): SolidRateLimiter { + selector?: (state: RateLimiterState) => TSelected, +): SolidRateLimiter { const rateLimiter = bindInstanceMethods( new RateLimiter(fn, initialOptions), ) - const [executionCount, setExecutionCount] = createSignal( - rateLimiter.getExecutionCount(), - ) - const [rejectionCount, setRejectionCount] = createSignal( - rateLimiter.getRejectionCount(), - ) - const [remainingInWindow, setRemainingInWindow] = createSignal( - rateLimiter.getRemainingInWindow(), - ) - const [msUntilNextWindow, setMsUntilNextWindow] = createSignal( - rateLimiter.getMsUntilNextWindow(), - ) - - function setOptions(newOptions: Partial>) { - rateLimiter.setOptions({ - ...newOptions, - onExecute: (rateLimiter) => { - setExecutionCount(rateLimiter.getExecutionCount()) - setRemainingInWindow(rateLimiter.getRemainingInWindow()) - setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow()) - - const onExecute = newOptions.onExecute ?? initialOptions.onExecute - onExecute?.(rateLimiter) - }, - onReject: (rateLimiter) => { - setRejectionCount(rateLimiter.getRejectionCount()) - setRemainingInWindow(rateLimiter.getRemainingInWindow()) - setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow()) - - const onReject = newOptions.onReject ?? initialOptions.onReject - onReject?.(rateLimiter) - }, - }) - } - - setOptions(initialOptions) + const state = useStore(rateLimiter.store, selector) return { ...rateLimiter, - executionCount, - rejectionCount, - remainingInWindow, - msUntilNextWindow, - setOptions, - } as SolidRateLimiter + state, + } as unknown as SolidRateLimiter // omit `store` in favor of `state` } From f5a38b5c9542ad3ef6bd501c9ef73fa1be99ba9e Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 29 Jun 2025 22:57:09 -0500 Subject: [PATCH 21/51] remove bindInstanceMethods util after defining all methods with arrow functions --- .../functions/bindinstancemethods.md | 2 +- examples/react/useDebouncer/src/index.tsx | 6 +- packages/pacer/src/async-debouncer.ts | 26 +++---- packages/pacer/src/async-queuer.ts | 40 +++++----- packages/pacer/src/async-rate-limiter.ts | 64 ++++++++-------- packages/pacer/src/async-throttler.ts | 76 ++++++++++--------- packages/pacer/src/batcher.ts | 30 ++++---- packages/pacer/src/debouncer.ts | 26 +++---- packages/pacer/src/queuer.ts | 65 +++++++++------- packages/pacer/src/rate-limiter.ts | 28 +++---- packages/pacer/src/throttler.ts | 28 +++---- packages/pacer/src/utils.ts | 15 ---- packages/pacer/tests/utils.test.ts | 64 +--------------- packages/persister/src/async-persister.ts | 4 +- packages/persister/src/persister.ts | 4 +- packages/persister/src/storage-persister.ts | 8 +- packages/persister/src/utils.ts | 15 ---- .../src/async-debouncer/useAsyncDebouncer.ts | 5 +- .../src/async-queuer/useAsyncQueuer.ts | 5 +- .../async-rate-limiter/useAsyncRateLimiter.ts | 5 +- .../src/async-throttler/useAsyncThrottler.ts | 5 +- .../react-pacer/src/batcher/useBatcher.ts | 5 +- .../react-pacer/src/debouncer/useDebouncer.ts | 5 +- packages/react-pacer/src/queuer/useQueuer.ts | 5 +- .../src/rate-limiter/useRateLimiter.ts | 7 +- .../react-pacer/src/throttler/useThrottler.ts | 5 +- .../storage-persister/useStoragePersister.ts | 5 +- .../async-debouncer/createAsyncDebouncer.ts | 5 +- .../src/async-queuer/createAsyncQueuer.ts | 3 +- .../createAsyncRateLimiter.ts | 3 +- .../async-throttler/createAsyncThrottler.ts | 5 +- .../solid-pacer/src/batcher/createBatcher.ts | 3 +- .../src/debouncer/createDebouncer.ts | 3 +- .../solid-pacer/src/queuer/createQueuer.ts | 3 +- .../src/rate-limiter/createRateLimiter.ts | 5 +- .../src/throttler/createThrottler.ts | 3 +- 36 files changed, 232 insertions(+), 354 deletions(-) diff --git a/docs/reference/functions/bindinstancemethods.md b/docs/reference/functions/bindinstancemethods.md index f96bc3569..d23dd2ef3 100644 --- a/docs/reference/functions/bindinstancemethods.md +++ b/docs/reference/functions/bindinstancemethods.md @@ -5,7 +5,7 @@ title: bindInstanceMethods -# Function: bindInstanceMethods() +# Function: () ```ts function bindInstanceMethods(instance): T diff --git a/examples/react/useDebouncer/src/index.tsx b/examples/react/useDebouncer/src/index.tsx index b01a37ecc..da7a30741 100644 --- a/examples/react/useDebouncer/src/index.tsx +++ b/examples/react/useDebouncer/src/index.tsx @@ -34,7 +34,7 @@ function App1() { Is Pending: - {setCountdebouncer.getState().isPending.toString()} + {setCountDebouncer.getState().isPending.toString()} Execution Count: @@ -99,7 +99,7 @@ function App2() { Is Pending: - {setSearchdebouncer.getState().isPending.toString()} + {setSearchDebouncer.getState().isPending.toString()} Execution Count: @@ -180,7 +180,7 @@ function App3() { Is Pending: - {setValuedebouncer.getState().isPending.toString()} + {setValueDebouncer.getState().isPending.toString()} Instant Executions: diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 62391d429..1ab3ee109 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -163,9 +163,9 @@ export class AsyncDebouncer { } /** - * Updates the debouncer options + * Updates the async debouncer options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } // End the pending state if the debouncer is disabled @@ -174,7 +174,7 @@ export class AsyncDebouncer { } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.store.setState((state) => { const combinedState = { ...state, @@ -197,14 +197,14 @@ export class AsyncDebouncer { /** * Returns the current debouncer enabled state */ - #getEnabled(): boolean { + #getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current debouncer wait state */ - #getWait(): number { + #getWait = (): number => { return parseFunctionOrValue(this.#options.wait, this) } @@ -222,9 +222,9 @@ export class AsyncDebouncer { * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError * @throws The error from the debounced function if no onError handler is configured */ - async maybeExecute( + maybeExecute = async ( ...args: Parameters - ): Promise | undefined> { + ): Promise | undefined> => { this.#cancelPendingExecution() this.#setState({ lastArgs: args }) @@ -256,9 +256,9 @@ export class AsyncDebouncer { }) } - async #execute( + #execute = async ( ...args: Parameters - ): Promise | undefined> { + ): Promise | undefined> => { if (!this.#getEnabled()) return undefined this.#abortController = new AbortController() try { @@ -289,7 +289,7 @@ export class AsyncDebouncer { return this.store.state.lastResult } - #cancelPendingExecution(): void { + #cancelPendingExecution = (): void => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = null @@ -308,7 +308,7 @@ export class AsyncDebouncer { /** * Cancels any pending execution or aborts any execution in progress */ - cancel(): void { + cancel = (): void => { this.#cancelPendingExecution() if (this.#abortController) { this.#abortController.abort() @@ -320,7 +320,7 @@ export class AsyncDebouncer { /** * Resets the debouncer state to its default values */ - reset(): void { + reset = (): void => { this.#setState(getDefaultAsyncDebouncerState()) } } @@ -369,5 +369,5 @@ export function asyncDebounce( initialOptions: AsyncDebouncerOptions, ) { const asyncDebouncer = new AsyncDebouncer(fn, initialOptions) - return asyncDebouncer.maybeExecute.bind(asyncDebouncer) + return asyncDebouncer.maybeExecute } diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index ad88d85dc..60619948c 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -238,11 +238,11 @@ export class AsyncQueuer { /** * Updates the queuer options. New options are merged with existing options. */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.store.setState((state) => { const combinedState = { ...state, @@ -273,7 +273,7 @@ export class AsyncQueuer { * Returns the current wait time (in milliseconds) between processing items. * If a function is provided, it is called with the queuer instance. */ - #getWait(): number { + #getWait = (): number => { return parseFunctionOrValue(this.#options.wait ?? 0, this) } @@ -281,14 +281,14 @@ export class AsyncQueuer { * Returns the current concurrency limit for processing items. * If a function is provided, it is called with the queuer instance. */ - #getConcurrency(): number { + #getConcurrency = (): number => { return parseFunctionOrValue(this.#options.concurrency ?? 1, this) } /** * Processes items in the queue up to the concurrency limit. Internal use only. */ - #tick() { + #tick = () => { if (!this.store.state.isRunning) { this.#setState({ pendingTick: false }) return @@ -338,11 +338,11 @@ export class AsyncQueuer { * queuer.addItem('task2', 'front'); * ``` */ - addItem( + addItem = ( item: TValue & { priority?: number }, position: QueuePosition = this.#options.addItemsTo ?? 'back', runOnItemsChange: boolean = true, - ): void { + ): void => { if (this.store.state.isFull) { this.#setState({ rejectionCount: this.store.state.rejectionCount + 1, @@ -416,9 +416,9 @@ export class AsyncQueuer { * queuer.getNextItem('back'); * ``` */ - getNextItem( + getNextItem = ( position: QueuePosition = this.#options.getItemsFrom ?? 'front', - ): TValue | undefined { + ): TValue | undefined => { const items = this.store.state.items const itemTimestamps = this.store.state.itemTimestamps let item: TValue | undefined @@ -458,7 +458,7 @@ export class AsyncQueuer { * queuer.execute('back'); * ``` */ - async execute(position?: QueuePosition): Promise { + execute = async (position?: QueuePosition): Promise => { const item = this.getNextItem(position) if (item !== undefined) { try { @@ -493,7 +493,7 @@ export class AsyncQueuer { * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. * Internal use only. */ - #checkExpiredItems(): void { + #checkExpiredItems = (): void => { if ( (this.#options.expirationDuration ?? Infinity) === Infinity && this.#options.getIsExpired === defaultOptions.getIsExpired @@ -555,7 +555,7 @@ export class AsyncQueuer { * queuer.peekNextItem('back'); // back * ``` */ - peekNextItem(position: QueuePosition = 'front'): TValue | undefined { + peekNextItem = (position: QueuePosition = 'front'): TValue | undefined => { if (position === 'front') { return this.store.state.items[0] } @@ -565,28 +565,28 @@ export class AsyncQueuer { /** * Returns a copy of all items in the queue, including active and pending items. */ - peekAllItems(): Array { + peekAllItems = (): Array => { return [...this.peekActiveItems(), ...this.peekPendingItems()] } /** * Returns the items currently being processed (active tasks). */ - peekActiveItems(): Array { + peekActiveItems = (): Array => { return [...this.store.state.activeItems] } /** * Returns the items waiting to be processed (pending tasks). */ - peekPendingItems(): Array { + peekPendingItems = (): Array => { return [...this.store.state.items] } /** * Starts processing items in the queue. If already running, does nothing. */ - start(): void { + start = (): void => { this.#setState({ isRunning: true }) if (!this.store.state.pendingTick && !this.store.state.isEmpty) { this.#setState({ pendingTick: true }) @@ -597,14 +597,14 @@ export class AsyncQueuer { /** * Stops processing items in the queue. Does not clear the queue. */ - stop(): void { + stop = (): void => { this.#setState({ isRunning: false, pendingTick: false }) } /** * Removes all pending items from the queue. Does not affect active tasks. */ - clear(): void { + clear = (): void => { this.#setState({ items: [], itemTimestamps: [] }) this.#options.onItemsChange?.(this) } @@ -612,7 +612,7 @@ export class AsyncQueuer { /** * Resets the queuer state to its default values */ - reset(): void { + reset = (): void => { this.#setState(getDefaultAsyncQueuerState()) } } @@ -647,5 +647,5 @@ export function asyncQueue( initialOptions: AsyncQueuerOptions, ) { const asyncQueuer = new AsyncQueuer(fn, initialOptions) - return asyncQueuer.addItem.bind(asyncQueuer) + return asyncQueuer.addItem } diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index d2e373b90..5473517e3 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -163,7 +163,7 @@ export class AsyncRateLimiter { errorCount: 0, executionTimes: [], isExecuting: false, - lastResult: undefined as ReturnType | undefined, + lastResult: undefined, rejectionCount: 0, settleCount: 0, successCount: 0, @@ -191,63 +191,59 @@ export class AsyncRateLimiter { } /** - * Updates the rate limiter options + * Updates the async rate limiter options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } } /** - * Returns the current rate limiter options + * Returns the current async rate limiter options */ - getOptions(): Required> { + getOptions = (): Required> => { return this.#options as Required> } - getState(): AsyncRateLimiterState { + getState = (): AsyncRateLimiterState => { return { ...this.#state } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } /** - * Returns the current enabled state of the rate limiter + * Returns the current enabled state of the async rate limiter */ - getEnabled(): boolean { + getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current limit of executions allowed within the time window */ - getLimit(): number { + getLimit = (): number => { return parseFunctionOrValue(this.#options.limit, this) } /** * Returns the current time window in milliseconds */ - getWindow(): number { + getWindow = (): number => { return parseFunctionOrValue(this.#options.window, this) } /** * 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. - * If execution is allowed, waits for any previous execution to complete before proceeding. * * Error Handling: * - If the rate-limited function throws and no `onError` handler is configured, * the error will be thrown from this method. * - If an `onError` handler is configured, errors will be caught and passed to the handler, * and this method will return undefined. - * - If the rate limit is exceeded, the execution will be rejected and the `onReject` handler - * will be called if configured. * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`. - * - Rate limit rejections can be tracked using `getRejectionCount()`. * * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError * @throws The error from the rate-limited function if no onError handler is configured @@ -256,16 +252,16 @@ export class AsyncRateLimiter { * ```ts * const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 }); * - * // First 5 calls will execute - * await rateLimiter.maybeExecute('arg1', 'arg2'); + * // First 5 calls will return a promise that resolves with the result + * const result = await rateLimiter.maybeExecute('arg1', 'arg2'); * - * // Additional calls within the window will be rejected - * await rateLimiter.maybeExecute('arg1', 'arg2'); // Rejected + * // Additional calls within the window will return undefined + * const result2 = await rateLimiter.maybeExecute('arg1', 'arg2'); // undefined * ``` */ - async maybeExecute( + maybeExecute = async ( ...args: Parameters - ): Promise | undefined> { + ): Promise | undefined> => { this.#cleanupOldExecutions() const limit = this.getLimit() @@ -293,9 +289,9 @@ export class AsyncRateLimiter { return undefined } - async #execute( + #execute = async ( ...args: Parameters - ): Promise | undefined> { + ): Promise | undefined> => { if (!this.getEnabled()) return const now = Date.now() this.#state.executionTimes.push(now) // mutate state directly for performance @@ -331,14 +327,14 @@ export class AsyncRateLimiter { return this.#state.lastResult } - #rejectFunction(): void { + #rejectFunction = (): void => { this.#setState({ rejectionCount: this.#state.rejectionCount + 1, }) this.#options.onReject?.(this) } - #cleanupOldExecutions(): void { + #cleanupOldExecutions = (): void => { const now = Date.now() const windowStart = now - this.getWindow() this.#setState({ @@ -351,7 +347,7 @@ export class AsyncRateLimiter { /** * Returns the number of remaining executions allowed in the current window */ - getRemainingInWindow(): number { + getRemainingInWindow = (): number => { this.#cleanupOldExecutions() return Math.max(0, this.getLimit() - this.#state.executionTimes.length) } @@ -361,7 +357,7 @@ export class AsyncRateLimiter { * For fixed windows, this is the time until the current window resets * For sliding windows, this is the time until the oldest execution expires */ - getMsUntilNextWindow(): number { + getMsUntilNextWindow = (): number => { if (this.getRemainingInWindow() > 0) { return 0 } @@ -372,42 +368,42 @@ export class AsyncRateLimiter { /** * Returns the number of times the function has been executed */ - getSuccessCount(): number { + getSuccessCount = (): number => { return this.#state.successCount } /** * Returns the number of times the function has been settled */ - getSettleCount(): number { + getSettleCount = (): number => { return this.#state.settleCount } /** * Returns the number of times the function has errored */ - getErrorCount(): number { + getErrorCount = (): number => { return this.#state.errorCount } /** * Returns the number of times the function has been rejected */ - getRejectionCount(): number { + getRejectionCount = (): number => { return this.#state.rejectionCount } /** * Returns whether the function is currently executing */ - getIsExecuting(): boolean { + getIsExecuting = (): boolean => { return this.#state.isExecuting } /** * Resets the rate limiter state */ - reset(): void { + reset = (): void => { this.#setState({ executionTimes: [], rejectionCount: 0, @@ -484,5 +480,5 @@ export function asyncRateLimit( initialOptions: AsyncRateLimiterOptions, ) { const rateLimiter = new AsyncRateLimiter(fn, initialOptions) - return rateLimiter.maybeExecute.bind(rateLimiter) + return rateLimiter.maybeExecute } diff --git a/packages/pacer/src/async-throttler.ts b/packages/pacer/src/async-throttler.ts index 497abc100..ece5a95d1 100644 --- a/packages/pacer/src/async-throttler.ts +++ b/packages/pacer/src/async-throttler.ts @@ -164,9 +164,9 @@ export class AsyncThrottler { } /** - * Updates the throttler options + * Updates the async throttler options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } // End the pending state if the throttler is disabled @@ -176,52 +176,60 @@ export class AsyncThrottler { } /** - * Returns the current options + * Returns the current async throttler options */ - getOptions(): AsyncThrottlerOptions { + getOptions = (): AsyncThrottlerOptions => { return this.#options } - getState(): AsyncThrottlerState { + getState = (): AsyncThrottlerState => { return { ...this.#state } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } /** - * Returns the current enabled state of the throttler + * Returns the current enabled state of the async throttler */ - getEnabled(): boolean { + getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current wait time in milliseconds */ - getWait(): number { + getWait = (): number => { return parseFunctionOrValue(this.#options.wait, this) } /** - * Attempts to execute the throttled function. - * If a call is already in progress, it may be blocked or queued depending on the `wait` option. + * Attempts to execute the throttled function. The execution behavior depends on the throttler options: * - * Error Handling: - * - If the throttled function throws and no `onError` handler is configured, - * the error will be thrown from this method. - * - If an `onError` handler is configured, errors will be caught and passed to the handler, - * and this method will return undefined. - * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`. + * - If enough time has passed since the last execution (>= wait period): + * - With leading=true: Executes immediately + * - With leading=false: Waits for the next trailing execution * - * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError - * @throws The error from the throttled function if no onError handler is configured + * - If within the wait period: + * - With trailing=true: Schedules execution for end of wait period + * - With trailing=false: Drops the execution + * + * @example + * ```ts + * const throttled = new AsyncThrottler(fn, { wait: 1000 }); + * + * // First call executes immediately + * await throttled.maybeExecute('a', 'b'); + * + * // Call during wait period - gets throttled + * await throttled.maybeExecute('c', 'd'); + * ``` */ - async maybeExecute( + maybeExecute = async ( ...args: Parameters - ): Promise | undefined> { + ): Promise | undefined> => { const now = Date.now() const timeSinceLastExecution = now - this.#state.lastExecutionTime const wait = this.getWait() @@ -261,9 +269,9 @@ export class AsyncThrottler { } } - async #execute( + #execute = async ( ...args: Parameters - ): Promise | undefined> { + ): Promise | undefined> => { if (!this.getEnabled() || this.#state.isExecuting) return undefined this.#abortController = new AbortController() try { @@ -299,7 +307,7 @@ export class AsyncThrottler { return this.#state.lastResult } - #resolvePreviousPromiseInternal(): void { + #resolvePreviousPromiseInternal = (): void => { if (this.#resolvePreviousPromise) { this.#resolvePreviousPromise(this.#state.lastResult) this.#resolvePreviousPromise = null @@ -309,7 +317,7 @@ export class AsyncThrottler { /** * Cancels any pending execution or aborts any execution in progress */ - cancel(): void { + cancel = (): void => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = null @@ -325,56 +333,56 @@ export class AsyncThrottler { /** * Returns the last execution time */ - getLastExecutionTime(): number { + getLastExecutionTime = (): number => { return this.#state.lastExecutionTime } /** * Returns the next execution time */ - getNextExecutionTime(): number { + getNextExecutionTime = (): number => { return this.#state.nextExecutionTime } /** * Returns the last result of the throttled function */ - getLastResult(): ReturnType | undefined { + getLastResult = (): ReturnType | undefined => { return this.#state.lastResult } /** * Returns the number of times the function has been executed successfully */ - getSuccessCount(): number { + getSuccessCount = (): number => { return this.#state.successCount } /** * Returns the number of times the function has settled (completed or errored) */ - getSettleCount(): number { + getSettleCount = (): number => { return this.#state.settleCount } /** * Returns the number of times the function has errored */ - getErrorCount(): number { + getErrorCount = (): number => { return this.#state.errorCount } /** * Returns the current pending state */ - getIsPending(): boolean { + getIsPending = (): boolean => { return this.getEnabled() && !!this.#timeoutId } /** * Returns the current executing state */ - getIsExecuting(): boolean { + getIsExecuting = (): boolean => { return this.#state.isExecuting } } @@ -422,5 +430,5 @@ export function asyncThrottle( initialOptions: AsyncThrottlerOptions, ) { const asyncThrottler = new AsyncThrottler(fn, initialOptions) - return asyncThrottler.maybeExecute.bind(asyncThrottler) + return asyncThrottler.maybeExecute } diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index b897053e4..764515585 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -133,22 +133,22 @@ export class Batcher { /** * Updates the batcher options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } } /** * Returns the current batcher options */ - getOptions(): BatcherOptions { + getOptions = (): BatcherOptions => { return this.#options } - getState(): BatcherState { + getState = (): BatcherState => { return { ...this.#state } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } @@ -157,7 +157,7 @@ export class Batcher { * Adds an item to the batcher * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed */ - addItem(item: TValue): void { + addItem = (item: TValue): void => { this.#setState({ items: [...this.#state.items, item], }) @@ -187,7 +187,7 @@ export class Batcher { * * You can also call this method manually to process the current batch at any time. */ - execute(): void { + execute = (): void => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = null @@ -212,7 +212,7 @@ export class Batcher { /** * Stops the batcher from processing batches */ - stop(): void { + stop = (): void => { this.#setState({ running: false }) this.#options.onIsRunningChange?.(this) if (this.#timeoutId) { @@ -224,7 +224,7 @@ export class Batcher { /** * Starts the batcher and processes any pending items */ - start(): void { + start = (): void => { this.#setState({ running: true }) this.#options.onIsRunningChange?.(this) if (this.#state.items.length > 0 && !this.#timeoutId) { @@ -235,42 +235,42 @@ export class Batcher { /** * Returns the current number of items in the batcher */ - getSize(): number { + getSize = (): number => { return this.#state.items.length } /** * Returns true if the batcher is empty */ - getIsEmpty(): boolean { + getIsEmpty = (): boolean => { return this.#state.items.length === 0 } /** * Returns true if the batcher is running */ - getIsRunning(): boolean { + getIsRunning = (): boolean => { return this.#state.running } /** * Returns a copy of all items in the batcher */ - peekAllItems(): Array { + peekAllItems = (): Array => { return [...this.#state.items] } /** * Returns the number of batches that have been processed */ - getBatchExecutionCount(): number { + getBatchExecutionCount = (): number => { return this.#state.batchExecutionCount } /** * Returns the total number of items that have been processed */ - getItemExecutionCount(): number { + getItemExecutionCount = (): number => { return this.#state.itemExecutionCount } } @@ -295,5 +295,5 @@ export function batch( options: BatcherOptions, ) { const batcher = new Batcher(fn, options) - return batcher.addItem.bind(batcher) + return batcher.addItem } diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index 0ccd7b6ac..c45e7a942 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -118,7 +118,7 @@ export class Debouncer { /** * Updates the debouncer options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } // End the pending state if the debouncer is disabled @@ -130,15 +130,15 @@ export class Debouncer { /** * Returns the current debouncer options */ - getOptions(): Required> { + getOptions = (): Required> => { return this.#options as Required> } - getState(): DebouncerState { + getState = (): DebouncerState => { return { ...this.#state } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } @@ -146,14 +146,14 @@ export class Debouncer { /** * Returns the current enabled state of the debouncer */ - getEnabled(): boolean { - return parseFunctionOrValue(this.#options.enabled, this)! + getEnabled = (): boolean => { + return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current wait time in milliseconds */ - getWait(): number { + getWait = (): number => { return parseFunctionOrValue(this.#options.wait, this) } @@ -161,7 +161,7 @@ export class Debouncer { * Attempts to execute the debounced function * If a call is already in progress, it will be queued */ - maybeExecute(...args: Parameters): void { + maybeExecute = (...args: Parameters): void => { let _didLeadingExecute = false // Handle leading execution @@ -188,7 +188,7 @@ export class Debouncer { }, this.getWait()) } - #execute(...args: Parameters): void { + #execute = (...args: Parameters): void => { if (!this.getEnabled()) return undefined this.fn(...args) // EXECUTE! this.#setState({ @@ -202,7 +202,7 @@ export class Debouncer { /** * Cancels any pending execution */ - cancel(): void { + cancel = (): void => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#setState({ @@ -215,14 +215,14 @@ export class Debouncer { /** * Returns the number of times the function has been executed */ - getExecutionCount(): number { + getExecutionCount = (): number => { return this.#state.executionCount } /** * Returns `true` if debouncing */ - getIsPending(): boolean { + getIsPending = (): boolean => { return this.getEnabled() && this.#state.isPending } } @@ -257,5 +257,5 @@ export function debounce( initialOptions: DebouncerOptions, ): (...args: Parameters) => void { const debouncer = new Debouncer(fn, initialOptions) - return debouncer.maybeExecute.bind(debouncer) + return debouncer.maybeExecute } diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index ff243e13b..faa7ff6f3 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -196,6 +196,7 @@ export class Queuer { } #onItemsChanges: Array<(item: TValue) => void> = [] #pendingTick = false + #timeoutId: NodeJS.Timeout | null = null constructor( private fn: (item: TValue) => void, @@ -221,27 +222,31 @@ export class Queuer { this.addItem(item, this.#options.addItemsTo ?? 'back', isLast) } } + + if (this.#options.started) { + this.start() + } } /** * Updates the queuer options. New options are merged with existing options. */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } } /** * Returns the current queuer options, including defaults and any overrides. */ - getOptions(): Required> { + getOptions = (): Required> => { return this.#options as Required> } - getState(): QueuerState { + getState = (): QueuerState => { return { ...this.#state } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } @@ -250,14 +255,14 @@ export class Queuer { * Returns the current wait time (in milliseconds) between processing items. * If a function is provided, it is called with the queuer instance. */ - getWait(): number { + getWait = (): number => { return parseFunctionOrValue(this.#options.wait ?? 0, this) } /** * Processes items in the queue up to the wait interval. Internal use only. */ - #tick() { + #tick = () => { if (!this.#state.running) { this.#pendingTick = false return @@ -276,7 +281,7 @@ export class Queuer { const wait = this.getWait() if (wait > 0) { // Use setTimeout to wait before processing next item - setTimeout(() => this.#tick(), wait) + this.#timeoutId = setTimeout(() => this.#tick(), wait) return } @@ -289,7 +294,7 @@ export class Queuer { * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. * Internal use only. */ - #checkExpiredItems() { + #checkExpiredItems = (): void => { if ( (this.#options.expirationDuration ?? Infinity) === Infinity && this.#options.getIsExpired === defaultOptions.getIsExpired @@ -341,7 +346,11 @@ export class Queuer { /** * Stops processing items in the queue. Does not clear the queue. */ - stop() { + stop = () => { + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null + } this.#setState({ running: false }) this.#pendingTick = false this.#options.onIsRunningChange?.(this) @@ -350,7 +359,7 @@ export class Queuer { /** * Starts processing items in the queue. If already running, does nothing. */ - start() { + start = () => { this.#setState({ running: true }) if (!this.#pendingTick && !this.getIsEmpty()) { this.#pendingTick = true @@ -362,7 +371,7 @@ export class Queuer { /** * Removes all pending items from the queue. Does not affect items being processed. */ - clear(): void { + clear = (): void => { this.#setState({ items: [], itemTimestamps: [], @@ -374,7 +383,7 @@ export class Queuer { * Resets the queuer to its initial state. Optionally repopulates with initial items. * Does not affect callbacks or options. */ - reset(withInitialItems?: boolean): void { + reset = (withInitialItems?: boolean): void => { this.clear() this.#setState({ executionCount: 0, @@ -402,11 +411,11 @@ export class Queuer { * queuer.addItem('task2', 'front'); * ``` */ - addItem( + addItem = ( item: TValue, position: QueuePosition = this.#options.addItemsTo ?? 'back', runOnUpdate: boolean = true, - ): boolean { + ): boolean => { if (this.getIsFull()) { this.#setState({ rejectionCount: this.#state.rejectionCount + 1, @@ -466,9 +475,9 @@ export class Queuer { * queuer.getNextItem('back'); * ``` */ - getNextItem( + getNextItem = ( position: QueuePosition = this.#options.getItemsFrom ?? 'front', - ): TValue | undefined { + ): TValue | undefined => { let item: TValue | undefined if (position === 'front') { @@ -496,7 +505,7 @@ export class Queuer { * queuer.execute('back'); * ``` */ - execute(position?: QueuePosition): TValue | undefined { + execute = (position?: QueuePosition): TValue | undefined => { const item = this.getNextItem(position) if (item !== undefined) { this.fn(item) @@ -517,9 +526,9 @@ export class Queuer { * queuer.peekNextItem('back'); // back * ``` */ - peekNextItem( + peekNextItem = ( position: QueuePosition = this.#options.getItemsFrom ?? 'front', - ): TValue | undefined { + ): TValue | undefined => { if (position === 'front') { return this.#state.items[0] } @@ -529,63 +538,63 @@ export class Queuer { /** * Returns true if the queue is empty (no pending items). */ - getIsEmpty(): boolean { + getIsEmpty = (): boolean => { return this.#state.items.length === 0 } /** * Returns true if the queue is full (reached maxSize). */ - getIsFull(): boolean { + getIsFull = (): boolean => { return this.#state.items.length >= (this.#options.maxSize ?? Infinity) } /** * Returns the number of pending items in the queue. */ - getSize(): number { + getSize = (): number => { return this.#state.items.length } /** * Returns a copy of all items in the queue. */ - peekAllItems(): Array { + peekAllItems = (): Array => { return [...this.#state.items] } /** * Returns the number of items that have been processed and removed from the queue. */ - getExecutionCount(): number { + getExecutionCount = (): number => { return this.#state.executionCount } /** * Returns the number of items that have been rejected from being added to the queue. */ - getRejectionCount(): number { + getRejectionCount = (): number => { return this.#state.rejectionCount } /** * Returns the number of items that have expired and been removed from the queue. */ - getExpirationCount(): number { + getExpirationCount = (): number => { return this.#state.expirationCount } /** * Returns true if the queuer is currently running (processing items). */ - getIsRunning() { + getIsRunning = () => { return this.#state.running } /** * Returns true if the queuer is running but has no items to process. */ - getIsIdle() { + getIsIdle = () => { return this.#state.running && this.getIsEmpty() } } diff --git a/packages/pacer/src/rate-limiter.ts b/packages/pacer/src/rate-limiter.ts index 6de5ec11a..b90d6308d 100644 --- a/packages/pacer/src/rate-limiter.ts +++ b/packages/pacer/src/rate-limiter.ts @@ -127,11 +127,11 @@ export class RateLimiter { /** * Updates the rate limiter options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } } - #setState(newState: Partial): void { + #setState = (newState: Partial): void => { this.store.setState((state) => { const combinedState = { ...state, @@ -144,21 +144,21 @@ export class RateLimiter { /** * Returns the current enabled state of the rate limiter */ - #getEnabled(): boolean { + #getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current limit of executions allowed within the time window */ - #getLimit(): number { + #getLimit = (): number => { return parseFunctionOrValue(this.#options.limit, this) } /** * Returns the current time window in milliseconds */ - #getWindow(): number { + #getWindow = (): number => { return parseFunctionOrValue(this.#options.window, this) } @@ -177,7 +177,7 @@ export class RateLimiter { * rateLimiter.maybeExecute('arg1', 'arg2'); // false * ``` */ - maybeExecute(...args: Parameters): boolean { + maybeExecute = (...args: Parameters): boolean => { this.#cleanupOldExecutions() const relevantExecutionTimes = this.#getRelevantExecutionTimes() @@ -191,7 +191,7 @@ export class RateLimiter { return false } - #execute(...args: Parameters): void { + #execute = (...args: Parameters): void => { if (!this.#getEnabled()) return const now = Date.now() this.fn(...args) // EXECUTE! @@ -202,14 +202,14 @@ export class RateLimiter { this.#options.onExecute?.(this) } - #rejectFunction(): void { + #rejectFunction = (): void => { this.#setState({ rejectionCount: this.store.state.rejectionCount + 1, }) this.#options.onReject?.(this) } - #getRelevantExecutionTimes(): Array { + #getRelevantExecutionTimes = (): Array => { if (this.#options.windowType === 'sliding') { // For sliding window, return all executions within the current window return this.store.state.executionTimes.filter( @@ -227,7 +227,7 @@ export class RateLimiter { } } - #cleanupOldExecutions(): void { + #cleanupOldExecutions = (): void => { const now = Date.now() const windowStart = now - this.#getWindow() this.#setState({ @@ -240,7 +240,7 @@ export class RateLimiter { /** * Returns the number of remaining executions allowed in the current window */ - getRemainingInWindow(): number { + getRemainingInWindow = (): number => { const relevantExecutionTimes = this.#getRelevantExecutionTimes() return Math.max(0, this.#getLimit() - relevantExecutionTimes.length) } @@ -248,7 +248,7 @@ export class RateLimiter { /** * Returns the number of milliseconds until the next execution will be possible */ - getMsUntilNextWindow(): number { + getMsUntilNextWindow = (): number => { if (this.getRemainingInWindow() > 0) { return 0 } @@ -259,7 +259,7 @@ export class RateLimiter { /** * Resets the rate limiter state */ - reset(): void { + reset = (): void => { this.#setState(getDefaultRateLimiterState()) } } @@ -311,5 +311,5 @@ export function rateLimit( initialOptions: RateLimiterOptions, ) { const rateLimiter = new RateLimiter(fn, initialOptions) - return rateLimiter.maybeExecute.bind(rateLimiter) + return rateLimiter.maybeExecute } diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index 737840a47..8a55a6a1a 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -123,7 +123,7 @@ export class Throttler { /** * Updates the throttler options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } // End the pending state if the throttler is disabled @@ -135,15 +135,15 @@ export class Throttler { /** * Returns the current throttler options */ - getOptions(): Required> { + getOptions = (): Required> => { return this.#options as Required> } - getState(): ThrottlerState { + getState = (): ThrottlerState => { return { ...this.#state } } - #setState(newState: Partial>): void { + #setState = (newState: Partial>): void => { this.#state = { ...this.#state, ...newState } this.#options.onStateChange?.(this.#state, this) } @@ -151,14 +151,14 @@ export class Throttler { /** * Returns the current enabled state of the throttler */ - getEnabled(): boolean { + getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current wait time in milliseconds */ - getWait(): number { + getWait = (): number => { return parseFunctionOrValue(this.#options.wait, this) } @@ -184,7 +184,7 @@ export class Throttler { * throttled.maybeExecute('c', 'd'); * ``` */ - maybeExecute(...args: Parameters): void { + maybeExecute = (...args: Parameters): void => { const now = Date.now() const timeSinceLastExecution = now - this.#state.lastExecutionTime const wait = this.getWait() @@ -212,7 +212,7 @@ export class Throttler { } } - #execute(...args: Parameters): void { + #execute = (...args: Parameters): void => { if (!this.getEnabled()) return this.#setState({ isPending: true }) this.fn(...args) // EXECUTE! @@ -238,7 +238,7 @@ export class Throttler { * * Has no effect if there is no pending execution. */ - cancel(): void { + cancel = (): void => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = undefined @@ -252,28 +252,28 @@ export class Throttler { /** * Returns the last execution time */ - getLastExecutionTime(): number { + getLastExecutionTime = (): number => { return this.#state.lastExecutionTime } /** * Returns the next execution time */ - getNextExecutionTime(): number { + getNextExecutionTime = (): number => { return this.#state.nextExecutionTime } /** * Returns the number of times the function has been executed */ - getExecutionCount(): number { + getExecutionCount = (): number => { return this.#state.executionCount } /** * Returns `true` if there is a pending execution */ - getIsPending(): boolean { + getIsPending = (): boolean => { return this.#state.isPending } } @@ -314,5 +314,5 @@ export function throttle( initialOptions: ThrottlerOptions, ) { const throttler = new Throttler(fn, initialOptions) - return throttler.maybeExecute.bind(throttler) + return throttler.maybeExecute } diff --git a/packages/pacer/src/utils.ts b/packages/pacer/src/utils.ts index 9026e0570..3a510d8ec 100644 --- a/packages/pacer/src/utils.ts +++ b/packages/pacer/src/utils.ts @@ -10,18 +10,3 @@ export function parseFunctionOrValue>( ): T { return isFunction(value) ? value(...args) : value } - -export function bindInstanceMethods>( - instance: T, -): T { - return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).reduce( - (acc: any, key) => { - const method = instance[key as keyof T] - if (isFunction(method)) { - acc[key] = method.bind(instance) - } - return acc - }, - instance, - ) -} diff --git a/packages/pacer/tests/utils.test.ts b/packages/pacer/tests/utils.test.ts index f9ec23c20..0450e5e13 100644 --- a/packages/pacer/tests/utils.test.ts +++ b/packages/pacer/tests/utils.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - bindInstanceMethods, - isFunction, - parseFunctionOrValue, -} from '../src/utils' +import { isFunction, parseFunctionOrValue } from '../src/utils' describe('isFunction', () => { it('should return true for function declarations', () => { @@ -60,61 +56,3 @@ describe('parseFunctionValue', () => { expect(await parseFunctionOrValue(asyncFn)).toBe(42) }) }) - -describe('bindInstanceMethods', () => { - it('should bind all instance methods', () => { - class TestClass { - value = 42 - method1() { - return this.value - } - method2() { - return this.value * 2 - } - } - - const instance = new TestClass() - const bound = bindInstanceMethods(instance) - - // Test that methods are bound by calling them without the instance context - const { method1, method2 } = bound - expect(method1()).toBe(42) - expect(method2()).toBe(84) - }) - - it('should not bind non-method properties', () => { - class TestClass { - value = 42 - method() { - return this.value - } - } - - const instance = new TestClass() - const bound = bindInstanceMethods(instance) - - expect(bound.value).toBe(42) - expect(bound.method()).toBe(42) - }) - - it('should work with inheritance', () => { - class Parent { - parentMethod() { - return 'parent' - } - } - - class Child extends Parent { - childMethod() { - return 'child' - } - } - - const instance = new Child() - const bound = bindInstanceMethods(instance) - - const { parentMethod, childMethod } = bound - expect(parentMethod()).toBe('parent') - expect(childMethod()).toBe('child') - }) -}) diff --git a/packages/persister/src/async-persister.ts b/packages/persister/src/async-persister.ts index 9dad5ee81..f7c1336e7 100644 --- a/packages/persister/src/async-persister.ts +++ b/packages/persister/src/async-persister.ts @@ -4,6 +4,6 @@ export abstract class AsyncPersister { constructor(public readonly key: string) {} - abstract loadState(): Promise - abstract saveState(state: TState): Promise + abstract loadState: () => Promise + abstract saveState: (state: TState) => Promise } diff --git a/packages/persister/src/persister.ts b/packages/persister/src/persister.ts index 7d324cc67..0f4e60a40 100644 --- a/packages/persister/src/persister.ts +++ b/packages/persister/src/persister.ts @@ -23,6 +23,6 @@ export abstract class Persister { constructor(public readonly key: string) {} - abstract loadState(): TState | undefined - abstract saveState(state: TState): void + abstract loadState: () => TState | undefined + abstract saveState: (state: TState) => void } diff --git a/packages/persister/src/storage-persister.ts b/packages/persister/src/storage-persister.ts index 382c4464f..0deae6534 100644 --- a/packages/persister/src/storage-persister.ts +++ b/packages/persister/src/storage-persister.ts @@ -127,21 +127,21 @@ export class StoragePersister extends Persister { /** * Updates the persister options */ - setOptions(newOptions: Partial>): void { + setOptions = (newOptions: Partial>): void => { this._options = { ...this._options, ...newOptions } } /** * Returns the current persister options */ - getOptions(): StoragePersisterOptions { + getOptions = (): StoragePersisterOptions => { return this._options } /** * Saves the state to storage */ - saveState(state: TState): void { + saveState = (state: TState): void => { try { const stateToSave = this._options.stateTransform ? this._options.stateTransform(state) @@ -164,7 +164,7 @@ export class StoragePersister extends Persister { /** * Loads the state from storage */ - loadState(): TState | undefined { + loadState = (): TState | undefined => { const stored = this._options.storage.getItem(this.key) if (!stored) { return undefined diff --git a/packages/persister/src/utils.ts b/packages/persister/src/utils.ts index 9026e0570..3a510d8ec 100644 --- a/packages/persister/src/utils.ts +++ b/packages/persister/src/utils.ts @@ -10,18 +10,3 @@ export function parseFunctionOrValue>( ): T { return isFunction(value) ? value(...args) : value } - -export function bindInstanceMethods>( - instance: T, -): T { - return Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).reduce( - (acc: any, key) => { - const method = instance[key as keyof T] - if (isFunction(method)) { - acc[key] = method.bind(instance) - } - return acc - }, - instance, - ) -} diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index 9bb94aee1..71cfcd273 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -1,6 +1,5 @@ import { useEffect, useMemo, useState } from 'react' import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import { useStore } from '@tanstack/react-store' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { @@ -81,9 +80,7 @@ export function useAsyncDebouncer< options: AsyncDebouncerOptions, selector?: (state: AsyncDebouncerState) => TSelected, ): ReactAsyncDebouncer { - const [asyncDebouncer] = useState(() => - bindInstanceMethods(new AsyncDebouncer(fn, options)), - ) + const [asyncDebouncer] = useState(() => new AsyncDebouncer(fn, options)) const state = useStore(asyncDebouncer.store, selector) diff --git a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts index 6da51b938..37c950bb7 100644 --- a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts +++ b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts @@ -1,6 +1,5 @@ import { useMemo, useState } from 'react' import { AsyncQueuer } from '@tanstack/pacer/async-queuer' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import { useStore } from '@tanstack/react-store' import type { AsyncQueuerOptions, @@ -67,9 +66,7 @@ export function useAsyncQueuer>( options: AsyncQueuerOptions = {}, selector?: (state: AsyncQueuerState) => TSelected, ): ReactAsyncQueuer { - const [asyncQueuer] = useState(() => - bindInstanceMethods(new AsyncQueuer(fn, options)), - ) + const [asyncQueuer] = useState(() => new AsyncQueuer(fn, options)) const state = useStore(asyncQueuer.store, selector) diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts index 5881db3e8..537941260 100644 --- a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts @@ -1,6 +1,5 @@ import { useState } from 'react' import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter' @@ -68,8 +67,8 @@ export function useAsyncRateLimiter( fn: TFn, options: AsyncRateLimiterOptions, ): AsyncRateLimiter { - const [asyncRateLimiter] = useState(() => - bindInstanceMethods(new AsyncRateLimiter(fn, options)), + const [asyncRateLimiter] = useState( + () => new AsyncRateLimiter(fn, options), ) asyncRateLimiter.setOptions(options) diff --git a/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts b/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts index 4243332ef..b328e8db7 100644 --- a/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts +++ b/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react' import { AsyncThrottler } from '@tanstack/pacer/async-throttler' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { AsyncThrottlerOptions } from '@tanstack/pacer/async-throttler' @@ -56,9 +55,7 @@ export function useAsyncThrottler( fn: TFn, options: AsyncThrottlerOptions, ): AsyncThrottler { - const [asyncThrottler] = useState(() => - bindInstanceMethods(new AsyncThrottler(fn, options)), - ) + const [asyncThrottler] = useState(() => new AsyncThrottler(fn, options)) asyncThrottler.setOptions(options) diff --git a/packages/react-pacer/src/batcher/useBatcher.ts b/packages/react-pacer/src/batcher/useBatcher.ts index 26eafd4ae..924c586a5 100644 --- a/packages/react-pacer/src/batcher/useBatcher.ts +++ b/packages/react-pacer/src/batcher/useBatcher.ts @@ -1,6 +1,5 @@ import { useState } from 'react' import { Batcher } from '@tanstack/pacer/batcher' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { BatcherOptions } from '@tanstack/pacer/batcher' /** @@ -44,9 +43,7 @@ export function useBatcher( fn: (items: Array) => void, options: BatcherOptions = {}, ): Batcher { - const [batcher] = useState(() => - bindInstanceMethods(new Batcher(fn, options)), - ) + const [batcher] = useState(() => new Batcher(fn, options)) batcher.setOptions(options) diff --git a/packages/react-pacer/src/debouncer/useDebouncer.ts b/packages/react-pacer/src/debouncer/useDebouncer.ts index 6771bad6e..7eb2d69ce 100644 --- a/packages/react-pacer/src/debouncer/useDebouncer.ts +++ b/packages/react-pacer/src/debouncer/useDebouncer.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react' import { Debouncer } from '@tanstack/pacer/debouncer' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { DebouncerOptions } from '@tanstack/pacer/debouncer' import type { AnyFunction } from '@tanstack/pacer/types' @@ -43,9 +42,7 @@ export function useDebouncer( fn: TFn, options: DebouncerOptions, ): Debouncer { - const [debouncer] = useState(() => - bindInstanceMethods(new Debouncer(fn, options)), - ) + const [debouncer] = useState(() => new Debouncer(fn, options)) debouncer.setOptions(options) diff --git a/packages/react-pacer/src/queuer/useQueuer.ts b/packages/react-pacer/src/queuer/useQueuer.ts index 2f421ffcc..f8f06f015 100644 --- a/packages/react-pacer/src/queuer/useQueuer.ts +++ b/packages/react-pacer/src/queuer/useQueuer.ts @@ -1,6 +1,5 @@ import { useState } from 'react' import { Queuer } from '@tanstack/pacer/queuer' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { QueuerOptions } from '@tanstack/pacer/queuer' /** @@ -45,9 +44,7 @@ export function useQueuer( fn: (item: TValue) => void, options: QueuerOptions = {}, ): Queuer { - const [queuer] = useState(() => - bindInstanceMethods(new Queuer(fn, options)), - ) + const [queuer] = useState(() => new Queuer(fn, options)) queuer.setOptions(options) diff --git a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts index 4221150e0..67e70e30a 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts @@ -1,7 +1,6 @@ import { useMemo, useState } from 'react' import { RateLimiter } from '@tanstack/pacer/rate-limiter' -import { bindInstanceMethods } from '@tanstack/pacer/utils' -import { useStore, } from '@tanstack/react-store' +import { useStore } from '@tanstack/react-store' import type { RateLimiterOptions, RateLimiterState, @@ -76,9 +75,7 @@ export function useRateLimiter< options: RateLimiterOptions, selector?: (state: RateLimiterState) => TSelected, ): ReactRateLimiter { - const [rateLimiter] = useState(() => - bindInstanceMethods(new RateLimiter(fn, options)), - ) + const [rateLimiter] = useState(() => new RateLimiter(fn, options)) const state = useStore(rateLimiter.store, selector) diff --git a/packages/react-pacer/src/throttler/useThrottler.ts b/packages/react-pacer/src/throttler/useThrottler.ts index cfefb081b..a634e6429 100644 --- a/packages/react-pacer/src/throttler/useThrottler.ts +++ b/packages/react-pacer/src/throttler/useThrottler.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react' import { Throttler } from '@tanstack/pacer/throttler' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { AnyFunction } from '@tanstack/pacer/types' import type { ThrottlerOptions } from '@tanstack/pacer/throttler' @@ -43,9 +42,7 @@ export function useThrottler( fn: TFn, options: ThrottlerOptions, ): Throttler { - const [throttler] = useState(() => - bindInstanceMethods(new Throttler(fn, options)), - ) + const [throttler] = useState(() => new Throttler(fn, options)) throttler.setOptions(options) diff --git a/packages/react-persister/src/storage-persister/useStoragePersister.ts b/packages/react-persister/src/storage-persister/useStoragePersister.ts index 223e1a148..37e0f2a5f 100644 --- a/packages/react-persister/src/storage-persister/useStoragePersister.ts +++ b/packages/react-persister/src/storage-persister/useStoragePersister.ts @@ -1,14 +1,11 @@ import { useState } from 'react' import { StoragePersister } from '@tanstack/persister/storage-persister' -import { bindInstanceMethods } from '@tanstack/persister' import type { StoragePersisterOptions } from '@tanstack/persister/storage-persister' export function useStoragePersister( options: StoragePersisterOptions, ) { - const [persister] = useState(() => - bindInstanceMethods(new StoragePersister(options)), - ) + const [persister] = useState(() => new StoragePersister(options)) persister.setOptions(options) diff --git a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts index 79f6c9dfe..e12a5349f 100644 --- a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts +++ b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts @@ -1,5 +1,4 @@ import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' import type { @@ -81,9 +80,7 @@ export function createAsyncDebouncer< initialOptions: AsyncDebouncerOptions, selector?: (state: AsyncDebouncerState) => TSelected, ): SolidAsyncDebouncer { - const asyncDebouncer = bindInstanceMethods( - new AsyncDebouncer(fn, initialOptions), - ) + const asyncDebouncer = new AsyncDebouncer(fn, initialOptions) const state = useStore(asyncDebouncer.store, selector) diff --git a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts index 6cd920653..45794b745 100644 --- a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts +++ b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts @@ -1,5 +1,4 @@ import { AsyncQueuer } from '@tanstack/pacer/async-queuer' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' import type { @@ -79,7 +78,7 @@ export function createAsyncQueuer>( const state = useStore(asyncQueuer.store, selector) return { - ...bindInstanceMethods(asyncQueuer), + ...asyncQueuer, state, } as unknown as SolidAsyncQueuer } diff --git a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts index 52787281e..dfc3196a7 100644 --- a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts +++ b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts @@ -1,6 +1,5 @@ import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' import { createSignal } from 'solid-js' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { Accessor } from 'solid-js' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter' @@ -138,7 +137,7 @@ export function createAsyncRateLimiter( setOptions(initialOptions) return { - ...bindInstanceMethods(asyncRateLimiter), + ...asyncRateLimiter, errorCount, remainingInWindow, msUntilNextWindow, diff --git a/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts b/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts index 290a6f4ca..ab4f17d64 100644 --- a/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts +++ b/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts @@ -1,6 +1,5 @@ import { AsyncThrottler } from '@tanstack/pacer/async-throttler' import { createSignal } from 'solid-js' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { Accessor } from 'solid-js' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { AsyncThrottlerOptions } from '@tanstack/pacer/async-throttler' @@ -81,9 +80,7 @@ export function createAsyncThrottler( fn: TFn, initialOptions: AsyncThrottlerOptions, ): SolidAsyncThrottler { - const asyncThrottler = bindInstanceMethods( - new AsyncThrottler(fn, initialOptions), - ) + const asyncThrottler = new AsyncThrottler(fn, initialOptions) const [successCount, setSuccessCount] = createSignal( asyncThrottler.getSuccessCount(), diff --git a/packages/solid-pacer/src/batcher/createBatcher.ts b/packages/solid-pacer/src/batcher/createBatcher.ts index 17cb64c68..649250482 100644 --- a/packages/solid-pacer/src/batcher/createBatcher.ts +++ b/packages/solid-pacer/src/batcher/createBatcher.ts @@ -1,6 +1,5 @@ import { Batcher } from '@tanstack/pacer/batcher' import { createSignal } from 'solid-js' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { Accessor } from 'solid-js' import type { BatcherOptions } from '@tanstack/pacer/batcher' @@ -91,7 +90,7 @@ export function createBatcher( fn: (items: Array) => void, initialOptions: BatcherOptions = {}, ): SolidBatcher { - const batcher = bindInstanceMethods(new Batcher(fn, initialOptions)) + const batcher = new Batcher(fn, initialOptions) const [allItems, setAllItems] = createSignal>( batcher.peekAllItems(), diff --git a/packages/solid-pacer/src/debouncer/createDebouncer.ts b/packages/solid-pacer/src/debouncer/createDebouncer.ts index 494078640..e388d7299 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncer.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncer.ts @@ -1,6 +1,5 @@ import { Debouncer } from '@tanstack/pacer/debouncer' import { createEffect, onCleanup } from 'solid-js' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import { createStore } from 'solid-js/store' import type { AnyFunction } from '@tanstack/pacer/types' import type { @@ -57,7 +56,7 @@ export function createDebouncer( fn: TFn, initialOptions: DebouncerOptions, ): SolidDebouncer { - const debouncer = bindInstanceMethods(new Debouncer(fn, initialOptions)) + const debouncer = new Debouncer(fn, initialOptions) const [store, setStore] = createStore>( debouncer.getState(), ) diff --git a/packages/solid-pacer/src/queuer/createQueuer.ts b/packages/solid-pacer/src/queuer/createQueuer.ts index 406473e89..1daf425d5 100644 --- a/packages/solid-pacer/src/queuer/createQueuer.ts +++ b/packages/solid-pacer/src/queuer/createQueuer.ts @@ -1,6 +1,5 @@ import { Queuer } from '@tanstack/pacer/queuer' import { createSignal } from 'solid-js' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import type { Accessor } from 'solid-js' import type { QueuerOptions } from '@tanstack/pacer/queuer' @@ -109,7 +108,7 @@ export function createQueuer( fn: (item: TValue) => void, initialOptions: QueuerOptions = {}, ): SolidQueuer { - const queuer = bindInstanceMethods(new Queuer(fn, initialOptions)) + const queuer = new Queuer(fn, initialOptions) const [allItems, setAllItems] = createSignal>( queuer.peekAllItems(), diff --git a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts index 253a4e857..3b89ed178 100644 --- a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts +++ b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts @@ -1,5 +1,4 @@ import { RateLimiter } from '@tanstack/pacer/rate-limiter' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' import type { AnyFunction } from '@tanstack/pacer/types' @@ -68,9 +67,7 @@ export function createRateLimiter< initialOptions: RateLimiterOptions, selector?: (state: RateLimiterState) => TSelected, ): SolidRateLimiter { - const rateLimiter = bindInstanceMethods( - new RateLimiter(fn, initialOptions), - ) + const rateLimiter = new RateLimiter(fn, initialOptions) const state = useStore(rateLimiter.store, selector) diff --git a/packages/solid-pacer/src/throttler/createThrottler.ts b/packages/solid-pacer/src/throttler/createThrottler.ts index 1d474c803..5ac30e194 100644 --- a/packages/solid-pacer/src/throttler/createThrottler.ts +++ b/packages/solid-pacer/src/throttler/createThrottler.ts @@ -1,6 +1,5 @@ import { Throttler } from '@tanstack/pacer/throttler' import { createEffect, onCleanup } from 'solid-js' -import { bindInstanceMethods } from '@tanstack/pacer/utils' import { createStore } from 'solid-js/store' import type { Store } from 'solid-js/store' import type { AnyFunction } from '@tanstack/pacer/types' @@ -55,7 +54,7 @@ export function createThrottler( fn: TFn, initialOptions: ThrottlerOptions, ): SolidThrottler { - const throttler = bindInstanceMethods(new Throttler(fn, initialOptions)) + const throttler = new Throttler(fn, initialOptions) const [store, setStore] = createStore>( throttler.getState(), ) From 15938390ecdaf756e78e91d4b8d778966b6b3b50 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 03:58:12 +0000 Subject: [PATCH 22/51] ci: apply automated fixes --- .../functions/usestoragepersister.md | 2 +- .../functions/createasyncdebouncer.md | 2 +- .../reference/functions/createasyncqueuer.md | 2 +- .../functions/createasyncratelimiter.md | 2 +- .../functions/createasyncthrottler.md | 2 +- .../reference/functions/createbatcher.md | 2 +- .../reference/functions/createdebouncer.md | 2 +- .../solid/reference/functions/createqueuer.md | 2 +- .../functions/createratelimitedsignal.md | 15 ++++-- .../functions/createratelimitedvalue.md | 15 ++++-- .../reference/functions/createratelimiter.md | 13 ++++- .../reference/functions/createthrottler.md | 2 +- .../interfaces/solidasyncdebouncer.md | 4 +- .../reference/interfaces/solidasyncqueuer.md | 4 +- .../interfaces/solidasyncratelimiter.md | 14 +++--- .../interfaces/solidasyncthrottler.md | 18 +++---- .../reference/interfaces/solidbatcher.md | 14 +++--- .../reference/interfaces/soliddebouncer.md | 4 +- .../solid/reference/interfaces/solidqueuer.md | 20 ++++---- .../reference/interfaces/solidratelimiter.md | 48 ++++--------------- .../reference/interfaces/solidthrottler.md | 4 +- docs/reference/classes/asyncpersister.md | 6 +-- docs/reference/classes/persister.md | 6 +-- docs/reference/classes/storagepersister.md | 8 +++- .../functions/bindinstancemethods.md | 28 ----------- docs/reference/index.md | 1 - 26 files changed, 107 insertions(+), 133 deletions(-) delete mode 100644 docs/reference/functions/bindinstancemethods.md diff --git a/docs/framework/react/reference/functions/usestoragepersister.md b/docs/framework/react/reference/functions/usestoragepersister.md index 45e457d21..c8965e40f 100644 --- a/docs/framework/react/reference/functions/usestoragepersister.md +++ b/docs/framework/react/reference/functions/usestoragepersister.md @@ -11,7 +11,7 @@ title: useStoragePersister function useStoragePersister(options): StoragePersister ``` -Defined in: [useStoragePersister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStoragePersister.ts#L6) +Defined in: [useStoragePersister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStoragePersister.ts#L5) ## Type Parameters diff --git a/docs/framework/solid/reference/functions/createasyncdebouncer.md b/docs/framework/solid/reference/functions/createasyncdebouncer.md index 4bf8a2972..2d661e504 100644 --- a/docs/framework/solid/reference/functions/createasyncdebouncer.md +++ b/docs/framework/solid/reference/functions/createasyncdebouncer.md @@ -14,7 +14,7 @@ function createAsyncDebouncer( selector?): SolidAsyncDebouncer ``` -Defined in: [async-debouncer/createAsyncDebouncer.ts:76](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L76) +Defined in: [async-debouncer/createAsyncDebouncer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L75) A low-level Solid hook that creates an `AsyncDebouncer` instance to delay execution of an async function. diff --git a/docs/framework/solid/reference/functions/createasyncqueuer.md b/docs/framework/solid/reference/functions/createasyncqueuer.md index bc1313db9..c4c3a05c3 100644 --- a/docs/framework/solid/reference/functions/createasyncqueuer.md +++ b/docs/framework/solid/reference/functions/createasyncqueuer.md @@ -14,7 +14,7 @@ function createAsyncQueuer( selector?): SolidAsyncQueuer ``` -Defined in: [async-queuer/createAsyncQueuer.ts:72](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L72) +Defined in: [async-queuer/createAsyncQueuer.ts:71](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L71) Creates a Solid-compatible AsyncQueuer instance for managing an asynchronous queue of items, exposing Solid signals for all stateful properties. diff --git a/docs/framework/solid/reference/functions/createasyncratelimiter.md b/docs/framework/solid/reference/functions/createasyncratelimiter.md index 2b31347b7..9fff1e021 100644 --- a/docs/framework/solid/reference/functions/createasyncratelimiter.md +++ b/docs/framework/solid/reference/functions/createasyncratelimiter.md @@ -11,7 +11,7 @@ title: createAsyncRateLimiter function createAsyncRateLimiter(fn, initialOptions): SolidAsyncRateLimiter ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:88](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L88) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:87](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L87) A low-level Solid 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/solid/reference/functions/createasyncthrottler.md b/docs/framework/solid/reference/functions/createasyncthrottler.md index e56536867..cafbc5639 100644 --- a/docs/framework/solid/reference/functions/createasyncthrottler.md +++ b/docs/framework/solid/reference/functions/createasyncthrottler.md @@ -11,7 +11,7 @@ title: createAsyncThrottler function createAsyncThrottler(fn, initialOptions): SolidAsyncThrottler ``` -Defined in: [async-throttler/createAsyncThrottler.ts:80](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L80) +Defined in: [async-throttler/createAsyncThrottler.ts:79](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L79) A low-level Solid hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. diff --git a/docs/framework/solid/reference/functions/createbatcher.md b/docs/framework/solid/reference/functions/createbatcher.md index 87c46ca5c..c90013b25 100644 --- a/docs/framework/solid/reference/functions/createbatcher.md +++ b/docs/framework/solid/reference/functions/createbatcher.md @@ -11,7 +11,7 @@ title: createBatcher function createBatcher(fn, initialOptions): SolidBatcher ``` -Defined in: [batcher/createBatcher.ts:90](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L90) +Defined in: [batcher/createBatcher.ts:89](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L89) Creates a Solid-compatible Batcher instance for managing batches of items, exposing Solid signals for all stateful properties. diff --git a/docs/framework/solid/reference/functions/createdebouncer.md b/docs/framework/solid/reference/functions/createdebouncer.md index 687b73cfe..e45056c76 100644 --- a/docs/framework/solid/reference/functions/createdebouncer.md +++ b/docs/framework/solid/reference/functions/createdebouncer.md @@ -11,7 +11,7 @@ title: createDebouncer function createDebouncer(fn, initialOptions): SolidDebouncer ``` -Defined in: [debouncer/createDebouncer.ts:56](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L56) +Defined in: [debouncer/createDebouncer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L55) A Solid hook that creates and manages a Debouncer instance. diff --git a/docs/framework/solid/reference/functions/createqueuer.md b/docs/framework/solid/reference/functions/createqueuer.md index 886859c2c..2f8e23191 100644 --- a/docs/framework/solid/reference/functions/createqueuer.md +++ b/docs/framework/solid/reference/functions/createqueuer.md @@ -11,7 +11,7 @@ title: createQueuer function createQueuer(fn, initialOptions): SolidQueuer ``` -Defined in: [queuer/createQueuer.ts:108](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L108) +Defined in: [queuer/createQueuer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L107) Creates a Solid-compatible Queuer instance for managing a synchronous queue of items, exposing Solid signals for all stateful properties. diff --git a/docs/framework/solid/reference/functions/createratelimitedsignal.md b/docs/framework/solid/reference/functions/createratelimitedsignal.md index 4a5d29c22..68d9067ec 100644 --- a/docs/framework/solid/reference/functions/createratelimitedsignal.md +++ b/docs/framework/solid/reference/functions/createratelimitedsignal.md @@ -8,10 +8,13 @@ title: createRateLimitedSignal # Function: createRateLimitedSignal() ```ts -function createRateLimitedSignal(value, initialOptions): [Accessor, Setter, SolidRateLimiter>] +function createRateLimitedSignal( + value, + initialOptions, + selector?): [Accessor, Setter, SolidRateLimiter, TSelected>] ``` -Defined in: [rate-limiter/createRateLimitedSignal.ts:65](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts#L65) +Defined in: [rate-limiter/createRateLimitedSignal.ts:68](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts#L68) A Solid hook that creates a rate-limited state value that enforces a hard limit on state updates within a time window. This hook combines Solid's createSignal with rate limiting functionality to provide controlled state updates. @@ -44,6 +47,8 @@ consider using the lower-level createRateLimiter hook instead. • **TValue** +• **TSelected** = `RateLimiterState` + ## Parameters ### value @@ -54,9 +59,13 @@ consider using the lower-level createRateLimiter hook instead. `RateLimiterOptions`\<`Setter`\<`TValue`\>\> +### selector? + +(`state`) => `TSelected` + ## Returns -\[`Accessor`\<`TValue`\>, `Setter`\<`TValue`\>, [`SolidRateLimiter`](../../interfaces/solidratelimiter.md)\<`Setter`\<`TValue`\>\>\] +\[`Accessor`\<`TValue`\>, `Setter`\<`TValue`\>, [`SolidRateLimiter`](../../interfaces/solidratelimiter.md)\<`Setter`\<`TValue`\>, `TSelected`\>\] ## Example diff --git a/docs/framework/solid/reference/functions/createratelimitedvalue.md b/docs/framework/solid/reference/functions/createratelimitedvalue.md index c6f047f50..39251a641 100644 --- a/docs/framework/solid/reference/functions/createratelimitedvalue.md +++ b/docs/framework/solid/reference/functions/createratelimitedvalue.md @@ -8,10 +8,13 @@ title: createRateLimitedValue # Function: createRateLimitedValue() ```ts -function createRateLimitedValue(value, initialOptions): [Accessor, SolidRateLimiter>] +function createRateLimitedValue( + value, + initialOptions, + selector?): [Accessor, SolidRateLimiter, TSelected>] ``` -Defined in: [rate-limiter/createRateLimitedValue.ts:50](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimitedValue.ts#L50) +Defined in: [rate-limiter/createRateLimitedValue.ts:53](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimitedValue.ts#L53) A high-level Solid hook that creates a rate-limited version of a value that updates at most a certain number of times within a time window. This hook uses Solid's createSignal internally to manage the rate-limited state. @@ -43,6 +46,8 @@ consider using the lower-level createRateLimiter hook instead. • **TValue** +• **TSelected** = `RateLimiterState` + ## Parameters ### value @@ -53,9 +58,13 @@ consider using the lower-level createRateLimiter hook instead. `RateLimiterOptions`\<`Setter`\<`TValue`\>\> +### selector? + +(`state`) => `TSelected` + ## Returns -\[`Accessor`\<`TValue`\>, [`SolidRateLimiter`](../../interfaces/solidratelimiter.md)\<`Setter`\<`TValue`\>\>\] +\[`Accessor`\<`TValue`\>, [`SolidRateLimiter`](../../interfaces/solidratelimiter.md)\<`Setter`\<`TValue`\>, `TSelected`\>\] ## Example diff --git a/docs/framework/solid/reference/functions/createratelimiter.md b/docs/framework/solid/reference/functions/createratelimiter.md index 2c8a1b33d..6189902d7 100644 --- a/docs/framework/solid/reference/functions/createratelimiter.md +++ b/docs/framework/solid/reference/functions/createratelimiter.md @@ -8,7 +8,10 @@ title: createRateLimiter # Function: createRateLimiter() ```ts -function createRateLimiter(fn, initialOptions): SolidRateLimiter +function createRateLimiter( + fn, + initialOptions, +selector?): SolidRateLimiter ``` Defined in: [rate-limiter/createRateLimiter.ts:62](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L62) @@ -37,6 +40,8 @@ For smoother execution patterns: • **TFn** *extends* `AnyFunction` +• **TSelected** = `RateLimiterState` + ## Parameters ### fn @@ -47,9 +52,13 @@ For smoother execution patterns: `RateLimiterOptions`\<`TFn`\> +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidRateLimiter`](../../interfaces/solidratelimiter.md)\<`TFn`\> +[`SolidRateLimiter`](../../interfaces/solidratelimiter.md)\<`TFn`, `TSelected`\> ## Example diff --git a/docs/framework/solid/reference/functions/createthrottler.md b/docs/framework/solid/reference/functions/createthrottler.md index 1e1b905ff..03c60f6cf 100644 --- a/docs/framework/solid/reference/functions/createthrottler.md +++ b/docs/framework/solid/reference/functions/createthrottler.md @@ -11,7 +11,7 @@ title: createThrottler function createThrottler(fn, initialOptions): SolidThrottler ``` -Defined in: [throttler/createThrottler.ts:54](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L54) +Defined in: [throttler/createThrottler.ts:53](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L53) A low-level Solid hook that creates a `Throttler` instance that limits how often the provided function can execute. diff --git a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md index 1fe93f678..f188e80af 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md +++ b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md @@ -7,7 +7,7 @@ title: SolidAsyncDebouncer # Interface: SolidAsyncDebouncer\ -Defined in: [async-debouncer/createAsyncDebouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L11) +Defined in: [async-debouncer/createAsyncDebouncer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L10) ## Extends @@ -27,7 +27,7 @@ Defined in: [async-debouncer/createAsyncDebouncer.ts:11](https://github.com/TanS state: Accessor; ``` -Defined in: [async-debouncer/createAsyncDebouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L20) +Defined in: [async-debouncer/createAsyncDebouncer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L19) Reactive state that will be updated when the debouncer state changes diff --git a/docs/framework/solid/reference/interfaces/solidasyncqueuer.md b/docs/framework/solid/reference/interfaces/solidasyncqueuer.md index 538535932..02a8c4ff3 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncqueuer.md +++ b/docs/framework/solid/reference/interfaces/solidasyncqueuer.md @@ -7,7 +7,7 @@ title: SolidAsyncQueuer # Interface: SolidAsyncQueuer\ -Defined in: [async-queuer/createAsyncQueuer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L10) +Defined in: [async-queuer/createAsyncQueuer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L9) ## Extends @@ -27,7 +27,7 @@ Defined in: [async-queuer/createAsyncQueuer.ts:10](https://github.com/TanStack/p state: Accessor; ``` -Defined in: [async-queuer/createAsyncQueuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L17) +Defined in: [async-queuer/createAsyncQueuer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L16) Reactive state that will be updated and re-rendered when the queuer state changes diff --git a/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md b/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md index 3cfca7680..8666448a9 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md +++ b/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md @@ -7,7 +7,7 @@ title: SolidAsyncRateLimiter # Interface: SolidAsyncRateLimiter\ -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:8](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L8) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:7](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L7) ## Extends @@ -31,7 +31,7 @@ Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:8](https://github.com/ errorCount: Accessor; ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L20) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L19) *** @@ -41,7 +41,7 @@ Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:20](https://github.com msUntilNextWindow: Accessor; ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L23) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L22) *** @@ -51,7 +51,7 @@ Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:23](https://github.com rejectionCount: Accessor; ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L21) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L20) *** @@ -61,7 +61,7 @@ Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:21](https://github.com remainingInWindow: Accessor; ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L22) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L21) *** @@ -71,7 +71,7 @@ Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:22](https://github.com settleCount: Accessor; ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L19) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:18](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L18) *** @@ -81,4 +81,4 @@ Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:19](https://github.com successCount: Accessor; ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:18](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L18) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L17) diff --git a/docs/framework/solid/reference/interfaces/solidasyncthrottler.md b/docs/framework/solid/reference/interfaces/solidasyncthrottler.md index 2e8462ca9..119f2f419 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncthrottler.md +++ b/docs/framework/solid/reference/interfaces/solidasyncthrottler.md @@ -7,7 +7,7 @@ title: SolidAsyncThrottler # Interface: SolidAsyncThrottler\ -Defined in: [async-throttler/createAsyncThrottler.ts:8](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L8) +Defined in: [async-throttler/createAsyncThrottler.ts:7](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L7) ## Extends @@ -33,7 +33,7 @@ Defined in: [async-throttler/createAsyncThrottler.ts:8](https://github.com/TanSt errorCount: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L22) +Defined in: [async-throttler/createAsyncThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L21) *** @@ -43,7 +43,7 @@ Defined in: [async-throttler/createAsyncThrottler.ts:22](https://github.com/TanS isExecuting: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:24](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L24) +Defined in: [async-throttler/createAsyncThrottler.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L23) *** @@ -53,7 +53,7 @@ Defined in: [async-throttler/createAsyncThrottler.ts:24](https://github.com/TanS isPending: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L23) +Defined in: [async-throttler/createAsyncThrottler.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L22) *** @@ -63,7 +63,7 @@ Defined in: [async-throttler/createAsyncThrottler.ts:23](https://github.com/TanS lastExecutionTime: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L26) +Defined in: [async-throttler/createAsyncThrottler.ts:25](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L25) *** @@ -73,7 +73,7 @@ Defined in: [async-throttler/createAsyncThrottler.ts:26](https://github.com/TanS lastResult: Accessor>; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:25](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L25) +Defined in: [async-throttler/createAsyncThrottler.ts:24](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L24) *** @@ -83,7 +83,7 @@ Defined in: [async-throttler/createAsyncThrottler.ts:25](https://github.com/TanS nextExecutionTime: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L27) +Defined in: [async-throttler/createAsyncThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L26) *** @@ -93,7 +93,7 @@ Defined in: [async-throttler/createAsyncThrottler.ts:27](https://github.com/TanS settleCount: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L21) +Defined in: [async-throttler/createAsyncThrottler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L20) *** @@ -103,4 +103,4 @@ Defined in: [async-throttler/createAsyncThrottler.ts:21](https://github.com/TanS successCount: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L20) +Defined in: [async-throttler/createAsyncThrottler.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L19) diff --git a/docs/framework/solid/reference/interfaces/solidbatcher.md b/docs/framework/solid/reference/interfaces/solidbatcher.md index 063f79b7c..0f974597f 100644 --- a/docs/framework/solid/reference/interfaces/solidbatcher.md +++ b/docs/framework/solid/reference/interfaces/solidbatcher.md @@ -7,7 +7,7 @@ title: SolidBatcher # Interface: SolidBatcher\ -Defined in: [batcher/createBatcher.ts:7](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L7) +Defined in: [batcher/createBatcher.ts:6](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L6) ## Extends @@ -31,7 +31,7 @@ Defined in: [batcher/createBatcher.ts:7](https://github.com/TanStack/pacer/blob/ allItems: Accessor; ``` -Defined in: [batcher/createBatcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L20) +Defined in: [batcher/createBatcher.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L19) Signal version of `peekAllItems` @@ -43,7 +43,7 @@ Signal version of `peekAllItems` batchExecutionCount: Accessor; ``` -Defined in: [batcher/createBatcher.ts:24](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L24) +Defined in: [batcher/createBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L23) Signal version of `getBatchExecutionCount` @@ -55,7 +55,7 @@ Signal version of `getBatchExecutionCount` isEmpty: Accessor; ``` -Defined in: [batcher/createBatcher.ts:28](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L28) +Defined in: [batcher/createBatcher.ts:27](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L27) Signal version of `getIsEmpty` @@ -67,7 +67,7 @@ Signal version of `getIsEmpty` isRunning: Accessor; ``` -Defined in: [batcher/createBatcher.ts:32](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L32) +Defined in: [batcher/createBatcher.ts:31](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L31) Signal version of `getIsRunning` @@ -79,7 +79,7 @@ Signal version of `getIsRunning` itemExecutionCount: Accessor; ``` -Defined in: [batcher/createBatcher.ts:36](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L36) +Defined in: [batcher/createBatcher.ts:35](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L35) Signal version of `getItemExecutionCount` @@ -91,6 +91,6 @@ Signal version of `getItemExecutionCount` size: Accessor; ``` -Defined in: [batcher/createBatcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L40) +Defined in: [batcher/createBatcher.ts:39](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L39) Signal version of `getSize` diff --git a/docs/framework/solid/reference/interfaces/soliddebouncer.md b/docs/framework/solid/reference/interfaces/soliddebouncer.md index afd90bbf6..ca489cb3c 100644 --- a/docs/framework/solid/reference/interfaces/soliddebouncer.md +++ b/docs/framework/solid/reference/interfaces/soliddebouncer.md @@ -7,7 +7,7 @@ title: SolidDebouncer # Interface: SolidDebouncer\ -Defined in: [debouncer/createDebouncer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L15) +Defined in: [debouncer/createDebouncer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L14) An extension of the Debouncer class that adds Solid signals to access the internal state of the debouncer @@ -27,4 +27,4 @@ An extension of the Debouncer class that adds Solid signals to access the intern store: DebouncerState; ``` -Defined in: [debouncer/createDebouncer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L17) +Defined in: [debouncer/createDebouncer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L16) diff --git a/docs/framework/solid/reference/interfaces/solidqueuer.md b/docs/framework/solid/reference/interfaces/solidqueuer.md index 30a564cf1..170cf61e9 100644 --- a/docs/framework/solid/reference/interfaces/solidqueuer.md +++ b/docs/framework/solid/reference/interfaces/solidqueuer.md @@ -7,7 +7,7 @@ title: SolidQueuer # Interface: SolidQueuer\ -Defined in: [queuer/createQueuer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L7) +Defined in: [queuer/createQueuer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L6) ## Extends @@ -33,7 +33,7 @@ Defined in: [queuer/createQueuer.ts:7](https://github.com/TanStack/pacer/blob/ma allItems: Accessor; ``` -Defined in: [queuer/createQueuer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L22) +Defined in: [queuer/createQueuer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L21) Signal version of `peekAllItems` @@ -45,7 +45,7 @@ Signal version of `peekAllItems` executionCount: Accessor; ``` -Defined in: [queuer/createQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L26) +Defined in: [queuer/createQueuer.ts:25](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L25) Signal version of `getExecutionCount` @@ -57,7 +57,7 @@ Signal version of `getExecutionCount` isEmpty: Accessor; ``` -Defined in: [queuer/createQueuer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L30) +Defined in: [queuer/createQueuer.ts:29](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L29) Signal version of `getIsEmpty` @@ -69,7 +69,7 @@ Signal version of `getIsEmpty` isFull: Accessor; ``` -Defined in: [queuer/createQueuer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L34) +Defined in: [queuer/createQueuer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L33) Signal version of `getIsFull` @@ -81,7 +81,7 @@ Signal version of `getIsFull` isIdle: Accessor; ``` -Defined in: [queuer/createQueuer.ts:38](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L38) +Defined in: [queuer/createQueuer.ts:37](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L37) Signal version of `getIsIdle` @@ -93,7 +93,7 @@ Signal version of `getIsIdle` isRunning: Accessor; ``` -Defined in: [queuer/createQueuer.ts:42](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L42) +Defined in: [queuer/createQueuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L41) Signal version of `getIsRunning` @@ -105,7 +105,7 @@ Signal version of `getIsRunning` nextItem: Accessor; ``` -Defined in: [queuer/createQueuer.ts:46](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L46) +Defined in: [queuer/createQueuer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L45) Signal version of `peekNextItem` @@ -117,7 +117,7 @@ Signal version of `peekNextItem` rejectionCount: Accessor; ``` -Defined in: [queuer/createQueuer.ts:50](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L50) +Defined in: [queuer/createQueuer.ts:49](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L49) Signal version of `getRejectionCount` @@ -129,6 +129,6 @@ Signal version of `getRejectionCount` size: Accessor; ``` -Defined in: [queuer/createQueuer.ts:54](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L54) +Defined in: [queuer/createQueuer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L53) Signal version of `getSize` diff --git a/docs/framework/solid/reference/interfaces/solidratelimiter.md b/docs/framework/solid/reference/interfaces/solidratelimiter.md index f8cbc71b6..0c38a573c 100644 --- a/docs/framework/solid/reference/interfaces/solidratelimiter.md +++ b/docs/framework/solid/reference/interfaces/solidratelimiter.md @@ -5,58 +5,30 @@ title: SolidRateLimiter -# Interface: SolidRateLimiter\ +# Interface: SolidRateLimiter\ -Defined in: [rate-limiter/createRateLimiter.ts:8](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L8) +Defined in: [rate-limiter/createRateLimiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L10) ## Extends -- `Omit`\<`RateLimiter`\<`TFn`\>, - \| `"getExecutionCount"` - \| `"getMsUntilNextWindow"` - \| `"getRejectionCount"` - \| `"getRemainingInWindow"`\> +- `Omit`\<`RateLimiter`\<`TFn`\>, `"store"`\> ## Type Parameters • **TFn** *extends* `AnyFunction` -## Properties - -### executionCount - -```ts -executionCount: Accessor; -``` +• **TSelected** = `RateLimiterState` -Defined in: [rate-limiter/createRateLimiter.ts:16](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L16) - -*** - -### msUntilNextWindow - -```ts -msUntilNextWindow: Accessor; -``` - -Defined in: [rate-limiter/createRateLimiter.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L17) - -*** +## Properties -### rejectionCount +### state ```ts -rejectionCount: Accessor; +state: Accessor; ``` -Defined in: [rate-limiter/createRateLimiter.ts:18](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L18) - -*** - -### remainingInWindow +Defined in: [rate-limiter/createRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L19) -```ts -remainingInWindow: Accessor; -``` +Reactive state that will be updated when the rate limiter state changes -Defined in: [rate-limiter/createRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L19) +Use this instead of `rateLimiter.store.state` diff --git a/docs/framework/solid/reference/interfaces/solidthrottler.md b/docs/framework/solid/reference/interfaces/solidthrottler.md index 02c1a555d..ba75a1d98 100644 --- a/docs/framework/solid/reference/interfaces/solidthrottler.md +++ b/docs/framework/solid/reference/interfaces/solidthrottler.md @@ -7,7 +7,7 @@ title: SolidThrottler # Interface: SolidThrottler\ -Defined in: [throttler/createThrottler.ts:15](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L15) +Defined in: [throttler/createThrottler.ts:14](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L14) An extension of the Throttler class that adds Solid signals to access the internal state of the throttler @@ -27,4 +27,4 @@ An extension of the Throttler class that adds Solid signals to access the intern store: ThrottlerState; ``` -Defined in: [throttler/createThrottler.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L17) +Defined in: [throttler/createThrottler.ts:16](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L16) diff --git a/docs/reference/classes/asyncpersister.md b/docs/reference/classes/asyncpersister.md index a9e7eb063..38edb9804 100644 --- a/docs/reference/classes/asyncpersister.md +++ b/docs/reference/classes/asyncpersister.md @@ -45,12 +45,12 @@ readonly key: string; Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L5) -## Methods +*** ### loadState() ```ts -abstract loadState(): Promise +abstract loadState: () => Promise; ``` Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L7) @@ -64,7 +64,7 @@ Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/p ### saveState() ```ts -abstract saveState(state): Promise +abstract saveState: (state) => Promise; ``` Defined in: [async-persister.ts:8](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L8) diff --git a/docs/reference/classes/persister.md b/docs/reference/classes/persister.md index 5ff0065ca..45fa9d9b5 100644 --- a/docs/reference/classes/persister.md +++ b/docs/reference/classes/persister.md @@ -69,12 +69,12 @@ readonly key: string; Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L24) -## Methods +*** ### loadState() ```ts -abstract loadState(): undefined | TState +abstract loadState: () => undefined | TState; ``` Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) @@ -88,7 +88,7 @@ Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packag ### saveState() ```ts -abstract saveState(state): void +abstract saveState: (state) => void; ``` Defined in: [persister.ts:27](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L27) diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md index 7353d7ac1..6122b09ff 100644 --- a/docs/reference/classes/storagepersister.md +++ b/docs/reference/classes/storagepersister.md @@ -122,7 +122,9 @@ Loads the state from storage #### Overrides -[`Persister`](../persister.md).[`loadState`](../Persister.md#loadstate) +```ts +Persister.loadState +``` *** @@ -148,7 +150,9 @@ Saves the state to storage #### Overrides -[`Persister`](../persister.md).[`saveState`](../Persister.md#savestate) +```ts +Persister.saveState +``` *** diff --git a/docs/reference/functions/bindinstancemethods.md b/docs/reference/functions/bindinstancemethods.md deleted file mode 100644 index d23dd2ef3..000000000 --- a/docs/reference/functions/bindinstancemethods.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -id: bindInstanceMethods -title: bindInstanceMethods ---- - - - -# Function: () - -```ts -function bindInstanceMethods(instance): T -``` - -Defined in: [utils.ts:14](https://github.com/TanStack/pacer/blob/main/packages/persister/src/utils.ts#L14) - -## Type Parameters - -• **T** *extends* `Record`\<`string`, `any`\> - -## Parameters - -### instance - -`T` - -## Returns - -`T` diff --git a/docs/reference/index.md b/docs/reference/index.md index 85f9f1d0a..df7099791 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -27,7 +27,6 @@ title: "@tanstack/persister" ## Functions -- [bindInstanceMethods](../functions/bindinstancemethods.md) - [isFunction](../functions/isfunction.md) - [isPlainArray](../functions/isplainarray.md) - [isPlainObject](../functions/isplainobject.md) From 9fb189b86ed04bab3585bd9d459c137dfadf22cb Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 3 Jul 2025 23:27:02 -0500 Subject: [PATCH 23/51] package upgrades and debouncer uses tanstack store --- examples/react/asyncDebounce/package.json | 6 +- examples/react/asyncRateLimit/package.json | 6 +- examples/react/asyncThrottle/package.json | 6 +- examples/react/batch/package.json | 6 +- examples/react/debounce/package.json | 6 +- examples/react/queue/package.json | 6 +- examples/react/rateLimit/package.json | 6 +- .../package.json | 10 +- .../react-query-queued-prefetch/package.json | 10 +- .../package.json | 10 +- examples/react/throttle/package.json | 6 +- examples/react/useAsyncDebouncer/package.json | 6 +- .../react/useAsyncQueuedState/package.json | 6 +- examples/react/useAsyncQueuer/package.json | 6 +- .../react/useAsyncRateLimiter/package.json | 6 +- examples/react/useAsyncThrottler/package.json | 6 +- examples/react/useBatcher/package.json | 6 +- .../react/useDebouncedCallback/package.json | 6 +- examples/react/useDebouncedState/package.json | 6 +- .../react/useDebouncedState/src/index.tsx | 28 +- examples/react/useDebouncedValue/package.json | 6 +- .../react/useDebouncedValue/src/index.tsx | 12 +- examples/react/useDebouncer/package.json | 6 +- examples/react/useDebouncer/src/index.tsx | 28 +- examples/react/useQueuedState/package.json | 6 +- examples/react/useQueuedValue/package.json | 6 +- examples/react/useQueuer/package.json | 6 +- .../react/useRateLimitedCallback/package.json | 6 +- .../react/useRateLimitedState/package.json | 6 +- .../react/useRateLimitedValue/package.json | 6 +- examples/react/useRateLimiter/package.json | 6 +- .../react/useThrottledCallback/package.json | 6 +- examples/react/useThrottledState/package.json | 6 +- examples/react/useThrottledValue/package.json | 6 +- examples/react/useThrottler/package.json | 6 +- examples/solid/asyncDebounce/package.json | 4 +- examples/solid/asyncRateLimit/package.json | 4 +- examples/solid/asyncThrottle/package.json | 4 +- examples/solid/batch/package.json | 4 +- .../solid/createAsyncDebouncer/package.json | 4 +- examples/solid/createAsyncQueuer/package.json | 4 +- .../solid/createAsyncRateLimiter/package.json | 4 +- .../solid/createAsyncThrottler/package.json | 4 +- examples/solid/createBatcher/package.json | 4 +- .../solid/createDebouncedSignal/package.json | 4 +- .../solid/createDebouncedValue/package.json | 4 +- examples/solid/createDebouncer/package.json | 4 +- examples/solid/createQueuer/package.json | 4 +- .../createRateLimitedSignal/package.json | 4 +- .../solid/createRateLimitedValue/package.json | 4 +- examples/solid/createRateLimiter/package.json | 4 +- .../solid/createThrottledSignal/package.json | 4 +- .../solid/createThrottledValue/package.json | 4 +- examples/solid/createThrottler/package.json | 4 +- examples/solid/debounce/package.json | 4 +- examples/solid/queue/package.json | 4 +- examples/solid/rateLimit/package.json | 4 +- examples/solid/throttle/package.json | 4 +- package.json | 22 +- packages/pacer/src/async-debouncer.ts | 1 + packages/pacer/src/debouncer.ts | 80 +- packages/pacer/tests/debouncer.test.ts | 44 +- packages/react-pacer/package.json | 6 +- .../src/async-debouncer/useAsyncDebouncer.ts | 2 + .../src/debouncer/useDebouncedCallback.ts | 20 +- .../src/debouncer/useDebouncedState.ts | 20 +- .../src/debouncer/useDebouncedValue.ts | 20 +- .../react-pacer/src/debouncer/useDebouncer.ts | 41 +- packages/react-persister/package.json | 6 +- packages/solid-pacer/package.json | 2 +- .../async-debouncer/createAsyncDebouncer.ts | 7 + .../src/debouncer/createDebouncedSignal.ts | 19 +- .../src/debouncer/createDebouncedValue.ts | 14 +- .../src/debouncer/createDebouncer.ts | 57 +- pnpm-lock.yaml | 2429 +++++++++-------- 75 files changed, 1714 insertions(+), 1440 deletions(-) diff --git a/examples/react/asyncDebounce/package.json b/examples/react/asyncDebounce/package.json index 4c5a762e5..fa03acaf3 100644 --- a/examples/react/asyncDebounce/package.json +++ b/examples/react/asyncDebounce/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/asyncRateLimit/package.json b/examples/react/asyncRateLimit/package.json index a873edd28..6195b3807 100644 --- a/examples/react/asyncRateLimit/package.json +++ b/examples/react/asyncRateLimit/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/asyncThrottle/package.json b/examples/react/asyncThrottle/package.json index c636088bc..0146a3a3d 100644 --- a/examples/react/asyncThrottle/package.json +++ b/examples/react/asyncThrottle/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/batch/package.json b/examples/react/batch/package.json index 8dd0f936f..283157192 100644 --- a/examples/react/batch/package.json +++ b/examples/react/batch/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/debounce/package.json b/examples/react/debounce/package.json index ec2a01d7f..04192371c 100644 --- a/examples/react/debounce/package.json +++ b/examples/react/debounce/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/queue/package.json b/examples/react/queue/package.json index 2c01eee3d..8d69c5cda 100644 --- a/examples/react/queue/package.json +++ b/examples/react/queue/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/rateLimit/package.json b/examples/react/rateLimit/package.json index 00ccb7776..0a4c41dbc 100644 --- a/examples/react/rateLimit/package.json +++ b/examples/react/rateLimit/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-debounced-prefetch/package.json b/examples/react/react-query-debounced-prefetch/package.json index 40a4d1914..0de4f6741 100644 --- a/examples/react/react-query-debounced-prefetch/package.json +++ b/examples/react/react-query-debounced-prefetch/package.json @@ -10,16 +10,16 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", - "@tanstack/react-query": "^5.80.6", - "@tanstack/react-query-devtools": "^5.80.6", + "@tanstack/react-query": "^5.81.5", + "@tanstack/react-query-devtools": "^5.81.5", "react": "^19.1.0", "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-queued-prefetch/package.json b/examples/react/react-query-queued-prefetch/package.json index 8a3152fa8..16f65edab 100644 --- a/examples/react/react-query-queued-prefetch/package.json +++ b/examples/react/react-query-queued-prefetch/package.json @@ -10,16 +10,16 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", - "@tanstack/react-query": "^5.80.6", - "@tanstack/react-query-devtools": "^5.80.6", + "@tanstack/react-query": "^5.81.5", + "@tanstack/react-query-devtools": "^5.81.5", "react": "^19.1.0", "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-throttled-prefetch/package.json b/examples/react/react-query-throttled-prefetch/package.json index f9fc2ff44..80cf0aa29 100644 --- a/examples/react/react-query-throttled-prefetch/package.json +++ b/examples/react/react-query-throttled-prefetch/package.json @@ -10,16 +10,16 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", - "@tanstack/react-query": "^5.80.6", - "@tanstack/react-query-devtools": "^5.80.6", + "@tanstack/react-query": "^5.81.5", + "@tanstack/react-query-devtools": "^5.81.5", "react": "^19.1.0", "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/throttle/package.json b/examples/react/throttle/package.json index 6ee496e4b..2190d62e1 100644 --- a/examples/react/throttle/package.json +++ b/examples/react/throttle/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncDebouncer/package.json b/examples/react/useAsyncDebouncer/package.json index 825e0bc5f..a76984cf0 100644 --- a/examples/react/useAsyncDebouncer/package.json +++ b/examples/react/useAsyncDebouncer/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncQueuedState/package.json b/examples/react/useAsyncQueuedState/package.json index d5fa0d4f1..9562a84f1 100644 --- a/examples/react/useAsyncQueuedState/package.json +++ b/examples/react/useAsyncQueuedState/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncQueuer/package.json b/examples/react/useAsyncQueuer/package.json index 96b498718..bab13e5f9 100644 --- a/examples/react/useAsyncQueuer/package.json +++ b/examples/react/useAsyncQueuer/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncRateLimiter/package.json b/examples/react/useAsyncRateLimiter/package.json index 3c6dc591c..875ae7d3d 100644 --- a/examples/react/useAsyncRateLimiter/package.json +++ b/examples/react/useAsyncRateLimiter/package.json @@ -15,10 +15,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncThrottler/package.json b/examples/react/useAsyncThrottler/package.json index 56b2fb452..973d9ccb3 100644 --- a/examples/react/useAsyncThrottler/package.json +++ b/examples/react/useAsyncThrottler/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useBatcher/package.json b/examples/react/useBatcher/package.json index f9ca94c0d..95abb4244 100644 --- a/examples/react/useBatcher/package.json +++ b/examples/react/useBatcher/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedCallback/package.json b/examples/react/useDebouncedCallback/package.json index 57b8e60a9..8c3e5346b 100644 --- a/examples/react/useDebouncedCallback/package.json +++ b/examples/react/useDebouncedCallback/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedState/package.json b/examples/react/useDebouncedState/package.json index 8b1d117ff..21804ce86 100644 --- a/examples/react/useDebouncedState/package.json +++ b/examples/react/useDebouncedState/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedState/src/index.tsx b/examples/react/useDebouncedState/src/index.tsx index 0fe239af1..7c672bdb5 100644 --- a/examples/react/useDebouncedState/src/index.tsx +++ b/examples/react/useDebouncedState/src/index.tsx @@ -30,17 +30,13 @@ function App1() {

TanStack Pacer useDebouncedState Example 1

- - - - - + - +
Enabled:{debouncer.getOptions().enabled.toString()}
Is Pending:{debouncer.getState().isPending.toString()}{debouncer.state.isPending.toString()}
Execution Count:{debouncer.getExecutionCount()}{debouncer.state.executionCount}
@@ -97,17 +93,13 @@ function App2() { - - - - - + - +
Enabled:{debouncer.getOptions().enabled.toString()}
Is Pending:{debouncer.getState().isPending.toString()}{debouncer.state.isPending.toString()}
Execution Count:{debouncer.getExecutionCount()}{debouncer.state.executionCount}
@@ -180,13 +172,9 @@ function App3() { - - - - - + @@ -194,11 +182,11 @@ function App3() { - + - + @@ -206,7 +194,7 @@ function App3() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - debouncer.getExecutionCount()) / + ((instantExecutionCount - debouncer.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/react/useDebouncedValue/package.json b/examples/react/useDebouncedValue/package.json index 73cfba0fe..e18bb5cc6 100644 --- a/examples/react/useDebouncedValue/package.json +++ b/examples/react/useDebouncedValue/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedValue/src/index.tsx b/examples/react/useDebouncedValue/src/index.tsx index 0fa25886a..808dbe019 100644 --- a/examples/react/useDebouncedValue/src/index.tsx +++ b/examples/react/useDebouncedValue/src/index.tsx @@ -129,13 +129,9 @@ function App3() {
Enabled:{debouncer.getOptions().enabled.toString()}
Is Pending:{debouncer.getState().isPending.toString()}{debouncer.state.isPending.toString()}
Instant Executions:
Debounced Executions:{debouncer.getExecutionCount()}{debouncer.state.executionCount}
Saved Executions:{instantExecutionCount - debouncer.getExecutionCount()}{instantExecutionCount - debouncer.state.executionCount}
% Reduction:
- - - - - + @@ -143,11 +139,11 @@ function App3() { - + - + @@ -155,7 +151,7 @@ function App3() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - debouncer.getExecutionCount()) / + ((instantExecutionCount - debouncer.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/react/useDebouncer/package.json b/examples/react/useDebouncer/package.json index 90093f292..961509b7b 100644 --- a/examples/react/useDebouncer/package.json +++ b/examples/react/useDebouncer/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncer/src/index.tsx b/examples/react/useDebouncer/src/index.tsx index da7a30741..ad4f48872 100644 --- a/examples/react/useDebouncer/src/index.tsx +++ b/examples/react/useDebouncer/src/index.tsx @@ -28,17 +28,13 @@ function App1() {

TanStack Pacer useDebouncer Example 1

Enabled:{debouncer.getOptions().enabled.toString()}
Is Pending:{debouncer.getState().isPending.toString()}{debouncer.state.isPending.toString()}
Instant Executions:
Debounced Executions:{debouncer.getExecutionCount()}{debouncer.state.executionCount}
Saved Executions:{instantExecutionCount - debouncer.getExecutionCount()}{instantExecutionCount - debouncer.state.executionCount}
% Reduction:
- - - - - + - + - + - + - + - + @@ -181,7 +183,8 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - debouncer.executionCount()) / + ((instantExecutionCount() - + debouncer.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/examples/solid/createDebouncedValue/src/index.tsx b/examples/solid/createDebouncedValue/src/index.tsx index 7ede4c1a2..e11213ffb 100644 --- a/examples/solid/createDebouncedValue/src/index.tsx +++ b/examples/solid/createDebouncedValue/src/index.tsx @@ -135,11 +135,13 @@ function App3() { - + - + @@ -147,7 +149,8 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - debouncer.executionCount()) / + ((instantExecutionCount() - + debouncer.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/examples/solid/createDebouncer/src/index.tsx b/examples/solid/createDebouncer/src/index.tsx index 8cb7fecb7..359e2279f 100644 --- a/examples/solid/createDebouncer/src/index.tsx +++ b/examples/solid/createDebouncer/src/index.tsx @@ -30,7 +30,7 @@ function App1() { - + - + - + @@ -181,7 +181,7 @@ function App3() { ? '0' : Math.round( ((instantExecutionCount() - - setValueDebouncer.store.executionCount) / + setValueDebouncer.state().executionCount) / instantExecutionCount()) * 100, )} From b9596500e9a2d6b4becf204d1fefb70f0b3ceb71 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 6 Jul 2025 22:19:26 -0500 Subject: [PATCH 30/51] upgrade tanstack store version --- examples/react/asyncDebounce/package.json | 2 +- examples/react/asyncRateLimit/package.json | 2 +- examples/react/asyncThrottle/package.json | 2 +- examples/react/batch/package.json | 2 +- examples/react/debounce/package.json | 2 +- examples/react/queue/package.json | 2 +- examples/react/rateLimit/package.json | 2 +- .../package.json | 2 +- .../react-query-queued-prefetch/package.json | 2 +- .../package.json | 2 +- examples/react/throttle/package.json | 2 +- examples/react/useAsyncDebouncer/package.json | 2 +- .../react/useAsyncQueuedState/package.json | 2 +- examples/react/useAsyncQueuer/package.json | 2 +- .../react/useAsyncRateLimiter/package.json | 2 +- examples/react/useAsyncThrottler/package.json | 2 +- examples/react/useBatcher/package.json | 2 +- .../react/useDebouncedCallback/package.json | 2 +- examples/react/useDebouncedState/package.json | 2 +- examples/react/useDebouncedValue/package.json | 2 +- examples/react/useDebouncer/package.json | 2 +- examples/react/useQueuedState/package.json | 2 +- examples/react/useQueuedValue/package.json | 2 +- examples/react/useQueuer/package.json | 2 +- .../react/useRateLimitedCallback/package.json | 2 +- .../react/useRateLimitedState/package.json | 2 +- .../react/useRateLimitedValue/package.json | 2 +- examples/react/useRateLimiter/package.json | 2 +- .../react/useThrottledCallback/package.json | 2 +- examples/react/useThrottledState/package.json | 2 +- examples/react/useThrottledValue/package.json | 2 +- examples/react/useThrottler/package.json | 2 +- examples/solid/asyncDebounce/package.json | 2 +- examples/solid/asyncRateLimit/package.json | 2 +- examples/solid/asyncThrottle/package.json | 2 +- examples/solid/batch/package.json | 2 +- .../solid/createAsyncDebouncer/package.json | 2 +- examples/solid/createAsyncQueuer/package.json | 2 +- .../solid/createAsyncRateLimiter/package.json | 2 +- .../solid/createAsyncThrottler/package.json | 2 +- examples/solid/createBatcher/package.json | 2 +- .../solid/createDebouncedSignal/package.json | 2 +- .../solid/createDebouncedValue/package.json | 2 +- examples/solid/createDebouncer/package.json | 2 +- examples/solid/createQueuer/package.json | 2 +- .../createRateLimitedSignal/package.json | 2 +- .../solid/createRateLimitedValue/package.json | 2 +- examples/solid/createRateLimiter/package.json | 2 +- .../solid/createThrottledSignal/package.json | 2 +- .../solid/createThrottledValue/package.json | 2 +- examples/solid/createThrottler/package.json | 2 +- examples/solid/debounce/package.json | 2 +- examples/solid/queue/package.json | 2 +- examples/solid/rateLimit/package.json | 2 +- examples/solid/throttle/package.json | 2 +- package.json | 2 +- packages/pacer/package.json | 2 +- packages/react-pacer/package.json | 2 +- packages/solid-pacer/package.json | 2 +- pnpm-lock.yaml | 442 +++++++++--------- 60 files changed, 280 insertions(+), 280 deletions(-) diff --git a/examples/react/asyncDebounce/package.json b/examples/react/asyncDebounce/package.json index fa03acaf3..74bc5ec9b 100644 --- a/examples/react/asyncDebounce/package.json +++ b/examples/react/asyncDebounce/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/asyncRateLimit/package.json b/examples/react/asyncRateLimit/package.json index 6195b3807..91dc02bbf 100644 --- a/examples/react/asyncRateLimit/package.json +++ b/examples/react/asyncRateLimit/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/asyncThrottle/package.json b/examples/react/asyncThrottle/package.json index 0146a3a3d..3462e5d40 100644 --- a/examples/react/asyncThrottle/package.json +++ b/examples/react/asyncThrottle/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/batch/package.json b/examples/react/batch/package.json index 283157192..87c4b4a53 100644 --- a/examples/react/batch/package.json +++ b/examples/react/batch/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/debounce/package.json b/examples/react/debounce/package.json index 04192371c..9ac7a3c28 100644 --- a/examples/react/debounce/package.json +++ b/examples/react/debounce/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/queue/package.json b/examples/react/queue/package.json index 8d69c5cda..06c1d1757 100644 --- a/examples/react/queue/package.json +++ b/examples/react/queue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/rateLimit/package.json b/examples/react/rateLimit/package.json index 0a4c41dbc..5403d254a 100644 --- a/examples/react/rateLimit/package.json +++ b/examples/react/rateLimit/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-debounced-prefetch/package.json b/examples/react/react-query-debounced-prefetch/package.json index 0de4f6741..ffc12361a 100644 --- a/examples/react/react-query-debounced-prefetch/package.json +++ b/examples/react/react-query-debounced-prefetch/package.json @@ -19,7 +19,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-queued-prefetch/package.json b/examples/react/react-query-queued-prefetch/package.json index 16f65edab..c96e2a0f9 100644 --- a/examples/react/react-query-queued-prefetch/package.json +++ b/examples/react/react-query-queued-prefetch/package.json @@ -19,7 +19,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-throttled-prefetch/package.json b/examples/react/react-query-throttled-prefetch/package.json index 80cf0aa29..8f5730917 100644 --- a/examples/react/react-query-throttled-prefetch/package.json +++ b/examples/react/react-query-throttled-prefetch/package.json @@ -19,7 +19,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/throttle/package.json b/examples/react/throttle/package.json index 2190d62e1..f0792e05a 100644 --- a/examples/react/throttle/package.json +++ b/examples/react/throttle/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncDebouncer/package.json b/examples/react/useAsyncDebouncer/package.json index a76984cf0..0009f52f7 100644 --- a/examples/react/useAsyncDebouncer/package.json +++ b/examples/react/useAsyncDebouncer/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncQueuedState/package.json b/examples/react/useAsyncQueuedState/package.json index 9562a84f1..038201339 100644 --- a/examples/react/useAsyncQueuedState/package.json +++ b/examples/react/useAsyncQueuedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncQueuer/package.json b/examples/react/useAsyncQueuer/package.json index bab13e5f9..b2890640f 100644 --- a/examples/react/useAsyncQueuer/package.json +++ b/examples/react/useAsyncQueuer/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncRateLimiter/package.json b/examples/react/useAsyncRateLimiter/package.json index 875ae7d3d..24d37136f 100644 --- a/examples/react/useAsyncRateLimiter/package.json +++ b/examples/react/useAsyncRateLimiter/package.json @@ -18,7 +18,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncThrottler/package.json b/examples/react/useAsyncThrottler/package.json index 973d9ccb3..2df7617d2 100644 --- a/examples/react/useAsyncThrottler/package.json +++ b/examples/react/useAsyncThrottler/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useBatcher/package.json b/examples/react/useBatcher/package.json index 95abb4244..4e76a059b 100644 --- a/examples/react/useBatcher/package.json +++ b/examples/react/useBatcher/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedCallback/package.json b/examples/react/useDebouncedCallback/package.json index 8c3e5346b..4eb41c630 100644 --- a/examples/react/useDebouncedCallback/package.json +++ b/examples/react/useDebouncedCallback/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedState/package.json b/examples/react/useDebouncedState/package.json index 21804ce86..90148778b 100644 --- a/examples/react/useDebouncedState/package.json +++ b/examples/react/useDebouncedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedValue/package.json b/examples/react/useDebouncedValue/package.json index e18bb5cc6..a1a6d4936 100644 --- a/examples/react/useDebouncedValue/package.json +++ b/examples/react/useDebouncedValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncer/package.json b/examples/react/useDebouncer/package.json index 961509b7b..b567f0228 100644 --- a/examples/react/useDebouncer/package.json +++ b/examples/react/useDebouncer/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuedState/package.json b/examples/react/useQueuedState/package.json index 13b16c1c3..08ea7e5fd 100644 --- a/examples/react/useQueuedState/package.json +++ b/examples/react/useQueuedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuedValue/package.json b/examples/react/useQueuedValue/package.json index 6959f217b..f7408d6a1 100644 --- a/examples/react/useQueuedValue/package.json +++ b/examples/react/useQueuedValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuer/package.json b/examples/react/useQueuer/package.json index 68088fb41..7ce27e6d7 100644 --- a/examples/react/useQueuer/package.json +++ b/examples/react/useQueuer/package.json @@ -18,7 +18,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedCallback/package.json b/examples/react/useRateLimitedCallback/package.json index 2db553c3d..af710c599 100644 --- a/examples/react/useRateLimitedCallback/package.json +++ b/examples/react/useRateLimitedCallback/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedState/package.json b/examples/react/useRateLimitedState/package.json index 8aaaa97b1..4cdcd2d58 100644 --- a/examples/react/useRateLimitedState/package.json +++ b/examples/react/useRateLimitedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedValue/package.json b/examples/react/useRateLimitedValue/package.json index 15230aabd..0b3189587 100644 --- a/examples/react/useRateLimitedValue/package.json +++ b/examples/react/useRateLimitedValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimiter/package.json b/examples/react/useRateLimiter/package.json index c613142cf..eb095928f 100644 --- a/examples/react/useRateLimiter/package.json +++ b/examples/react/useRateLimiter/package.json @@ -18,7 +18,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledCallback/package.json b/examples/react/useThrottledCallback/package.json index 88f97be33..e6c312202 100644 --- a/examples/react/useThrottledCallback/package.json +++ b/examples/react/useThrottledCallback/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledState/package.json b/examples/react/useThrottledState/package.json index 8c7cb510b..2f4cba569 100644 --- a/examples/react/useThrottledState/package.json +++ b/examples/react/useThrottledState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledValue/package.json b/examples/react/useThrottledValue/package.json index c10a3a8d3..d2b077b2e 100644 --- a/examples/react/useThrottledValue/package.json +++ b/examples/react/useThrottledValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottler/package.json b/examples/react/useThrottler/package.json index 7f018bfae..82c7e4de4 100644 --- a/examples/react/useThrottler/package.json +++ b/examples/react/useThrottler/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.1" + "vite": "^7.0.2" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncDebounce/package.json b/examples/solid/asyncDebounce/package.json index b66087e69..06962ec3b 100644 --- a/examples/solid/asyncDebounce/package.json +++ b/examples/solid/asyncDebounce/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/asyncRateLimit/package.json b/examples/solid/asyncRateLimit/package.json index 80ca0d46e..ec67f709d 100644 --- a/examples/solid/asyncRateLimit/package.json +++ b/examples/solid/asyncRateLimit/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/asyncThrottle/package.json b/examples/solid/asyncThrottle/package.json index 539d7747b..1cd3d765a 100644 --- a/examples/solid/asyncThrottle/package.json +++ b/examples/solid/asyncThrottle/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/batch/package.json b/examples/solid/batch/package.json index 8fc3acf38..8a621345d 100644 --- a/examples/solid/batch/package.json +++ b/examples/solid/batch/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncDebouncer/package.json b/examples/solid/createAsyncDebouncer/package.json index 1a50ed472..453c0ffdb 100644 --- a/examples/solid/createAsyncDebouncer/package.json +++ b/examples/solid/createAsyncDebouncer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncQueuer/package.json b/examples/solid/createAsyncQueuer/package.json index d7d6d9b0a..f206752e7 100644 --- a/examples/solid/createAsyncQueuer/package.json +++ b/examples/solid/createAsyncQueuer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncRateLimiter/package.json b/examples/solid/createAsyncRateLimiter/package.json index 04cebfcc5..c9b634f89 100644 --- a/examples/solid/createAsyncRateLimiter/package.json +++ b/examples/solid/createAsyncRateLimiter/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncThrottler/package.json b/examples/solid/createAsyncThrottler/package.json index 07348989c..4848bfb67 100644 --- a/examples/solid/createAsyncThrottler/package.json +++ b/examples/solid/createAsyncThrottler/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createBatcher/package.json b/examples/solid/createBatcher/package.json index e173a2cec..c4caad188 100644 --- a/examples/solid/createBatcher/package.json +++ b/examples/solid/createBatcher/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createDebouncedSignal/package.json b/examples/solid/createDebouncedSignal/package.json index 92b268bce..5fe689355 100644 --- a/examples/solid/createDebouncedSignal/package.json +++ b/examples/solid/createDebouncedSignal/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createDebouncedValue/package.json b/examples/solid/createDebouncedValue/package.json index cb92de3d6..63aa61a5e 100644 --- a/examples/solid/createDebouncedValue/package.json +++ b/examples/solid/createDebouncedValue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createDebouncer/package.json b/examples/solid/createDebouncer/package.json index dba1f51ca..5aa1de1ad 100644 --- a/examples/solid/createDebouncer/package.json +++ b/examples/solid/createDebouncer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createQueuer/package.json b/examples/solid/createQueuer/package.json index bdbb69ffb..03238147e 100644 --- a/examples/solid/createQueuer/package.json +++ b/examples/solid/createQueuer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createRateLimitedSignal/package.json b/examples/solid/createRateLimitedSignal/package.json index 1dda56a82..926c55bf1 100644 --- a/examples/solid/createRateLimitedSignal/package.json +++ b/examples/solid/createRateLimitedSignal/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createRateLimitedValue/package.json b/examples/solid/createRateLimitedValue/package.json index a3604c308..d7cdae0a0 100644 --- a/examples/solid/createRateLimitedValue/package.json +++ b/examples/solid/createRateLimitedValue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createRateLimiter/package.json b/examples/solid/createRateLimiter/package.json index 05a5dda93..c9bb95bef 100644 --- a/examples/solid/createRateLimiter/package.json +++ b/examples/solid/createRateLimiter/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createThrottledSignal/package.json b/examples/solid/createThrottledSignal/package.json index b6bcf6aa2..063807e77 100644 --- a/examples/solid/createThrottledSignal/package.json +++ b/examples/solid/createThrottledSignal/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createThrottledValue/package.json b/examples/solid/createThrottledValue/package.json index 93cc55c24..11b6ff61a 100644 --- a/examples/solid/createThrottledValue/package.json +++ b/examples/solid/createThrottledValue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createThrottler/package.json b/examples/solid/createThrottler/package.json index 1b470919b..9e2b3d0cb 100644 --- a/examples/solid/createThrottler/package.json +++ b/examples/solid/createThrottler/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/debounce/package.json b/examples/solid/debounce/package.json index cca67fef5..a2bfca341 100644 --- a/examples/solid/debounce/package.json +++ b/examples/solid/debounce/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/queue/package.json b/examples/solid/queue/package.json index 3a0ee1d56..7e77f9133 100644 --- a/examples/solid/queue/package.json +++ b/examples/solid/queue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/rateLimit/package.json b/examples/solid/rateLimit/package.json index e10bbd29b..2d7b11d68 100644 --- a/examples/solid/rateLimit/package.json +++ b/examples/solid/rateLimit/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/throttle/package.json b/examples/solid/throttle/package.json index 02830b259..d3ec057fe 100644 --- a/examples/solid/throttle/package.json +++ b/examples/solid/throttle/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.1", + "vite": "^7.0.2", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/package.json b/package.json index e575aedad..e43e95ab7 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "sherif": "^1.6.1", "size-limit": "^11.2.0", "typescript": "5.8.3", - "vite": "^7.0.1", + "vite": "^7.0.2", "vitest": "^3.2.4" }, "overrides": { diff --git a/packages/pacer/package.json b/packages/pacer/package.json index 5c424848d..ae943bc51 100644 --- a/packages/pacer/package.json +++ b/packages/pacer/package.json @@ -161,6 +161,6 @@ "build": "vite build" }, "dependencies": { - "@tanstack/store": "^0.7.1" + "@tanstack/store": "^0.7.2" } } diff --git a/packages/react-pacer/package.json b/packages/react-pacer/package.json index ee7cf810d..ce0738125 100644 --- a/packages/react-pacer/package.json +++ b/packages/react-pacer/package.json @@ -162,7 +162,7 @@ }, "dependencies": { "@tanstack/pacer": "workspace:*", - "@tanstack/react-store": "^0.7.1" + "@tanstack/react-store": "^0.7.3" }, "devDependencies": { "@eslint-react/eslint-plugin": "^1.52.2", diff --git a/packages/solid-pacer/package.json b/packages/solid-pacer/package.json index 2fb80e0b7..cd87102d0 100644 --- a/packages/solid-pacer/package.json +++ b/packages/solid-pacer/package.json @@ -162,7 +162,7 @@ }, "dependencies": { "@tanstack/pacer": "workspace:*", - "@tanstack/solid-store": "^0.7.1" + "@tanstack/solid-store": "^0.7.3" }, "devDependencies": { "solid-js": "^1.9.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0315f416..aaf4f0cb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,7 +22,7 @@ importers: version: 1.2.0 '@tanstack/config': specifier: 0.19.0 - version: 0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 @@ -72,8 +72,8 @@ importers: specifier: 5.8.3 version: 5.8.3 vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vitest: specifier: ^3.2.4 version: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0) @@ -98,10 +98,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncRateLimit: dependencies: @@ -123,10 +123,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncThrottle: dependencies: @@ -148,10 +148,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/batch: dependencies: @@ -173,10 +173,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/debounce: dependencies: @@ -198,10 +198,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/queue: dependencies: @@ -223,10 +223,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/rateLimit: dependencies: @@ -248,10 +248,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-debounced-prefetch: dependencies: @@ -279,10 +279,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-queued-prefetch: dependencies: @@ -310,10 +310,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-throttled-prefetch: dependencies: @@ -341,10 +341,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/throttle: dependencies: @@ -366,10 +366,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncDebouncer: dependencies: @@ -391,10 +391,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncQueuedState: dependencies: @@ -416,10 +416,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncQueuer: dependencies: @@ -441,10 +441,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncRateLimiter: dependencies: @@ -469,10 +469,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncThrottler: dependencies: @@ -494,10 +494,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useBatcher: dependencies: @@ -519,10 +519,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedCallback: dependencies: @@ -544,10 +544,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedState: dependencies: @@ -569,10 +569,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedValue: dependencies: @@ -594,10 +594,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncer: dependencies: @@ -619,10 +619,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuedState: dependencies: @@ -644,10 +644,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuedValue: dependencies: @@ -669,10 +669,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuer: dependencies: @@ -697,10 +697,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedCallback: dependencies: @@ -722,10 +722,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedState: dependencies: @@ -747,10 +747,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedValue: dependencies: @@ -772,10 +772,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimiter: dependencies: @@ -800,10 +800,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledCallback: dependencies: @@ -825,10 +825,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledState: dependencies: @@ -850,10 +850,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledValue: dependencies: @@ -875,10 +875,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottler: dependencies: @@ -900,10 +900,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/solid/asyncDebounce: dependencies: @@ -915,11 +915,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/asyncRateLimit: dependencies: @@ -931,11 +931,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/asyncThrottle: dependencies: @@ -947,11 +947,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/batch: dependencies: @@ -963,11 +963,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncDebouncer: dependencies: @@ -979,11 +979,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncQueuer: dependencies: @@ -995,11 +995,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncRateLimiter: dependencies: @@ -1011,11 +1011,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncThrottler: dependencies: @@ -1027,11 +1027,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createBatcher: dependencies: @@ -1043,11 +1043,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncedSignal: dependencies: @@ -1059,11 +1059,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncedValue: dependencies: @@ -1075,11 +1075,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncer: dependencies: @@ -1091,11 +1091,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createQueuer: dependencies: @@ -1107,11 +1107,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimitedSignal: dependencies: @@ -1123,11 +1123,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimitedValue: dependencies: @@ -1139,11 +1139,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimiter: dependencies: @@ -1155,11 +1155,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottledSignal: dependencies: @@ -1171,11 +1171,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottledValue: dependencies: @@ -1187,11 +1187,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottler: dependencies: @@ -1203,11 +1203,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/debounce: dependencies: @@ -1219,11 +1219,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/queue: dependencies: @@ -1235,11 +1235,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/rateLimit: dependencies: @@ -1251,11 +1251,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/throttle: dependencies: @@ -1267,17 +1267,17 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.1 - version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) packages/pacer: dependencies: '@tanstack/store': - specifier: ^0.7.1 - version: 0.7.1 + specifier: ^0.7.2 + version: 0.7.2 packages/persister: {} @@ -1287,8 +1287,8 @@ importers: specifier: workspace:* version: link:../pacer '@tanstack/react-store': - specifier: ^0.7.1 - version: 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^0.7.3 + version: 0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-dom: specifier: '>=16.8' version: 19.1.0(react@19.1.0) @@ -1301,7 +1301,7 @@ importers: version: 19.1.8 '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) eslint-plugin-react-compiler: specifier: 19.1.0-rc.2 version: 19.1.0-rc.2(eslint@9.30.1(jiti@2.4.2)) @@ -1329,7 +1329,7 @@ importers: version: 19.1.8 '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) eslint-plugin-react-compiler: specifier: 19.1.0-rc.2 version: 19.1.0-rc.2(eslint@9.30.1(jiti@2.4.2)) @@ -1346,15 +1346,15 @@ importers: specifier: workspace:* version: link:../pacer '@tanstack/solid-store': - specifier: ^0.7.1 - version: 0.7.1(solid-js@1.9.7) + specifier: ^0.7.3 + version: 0.7.3(solid-js@1.9.7) devDependencies: solid-js: specifier: ^1.9.7 version: 1.9.7 vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) packages: @@ -2341,19 +2341,19 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-store@0.7.1': - resolution: {integrity: sha512-qUTEKdId6QPWGiWyKAPf/gkN29scEsz6EUSJ0C3HgLMgaqTAyBsQ2sMCfGVcqb+kkhEXAdjleCgH6LAPD6f2sA==} + '@tanstack/react-store@0.7.3': + resolution: {integrity: sha512-3Dnqtbw9P2P0gw8uUM8WP2fFfg8XMDSZCTsywRPZe/XqqYW8PGkXKZTvP0AHkE4mpqP9Y43GpOg9vwO44azu6Q==} peerDependencies: 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-store@0.7.1': - resolution: {integrity: sha512-chcElOdXhDTcJnMrY02Q+EPyI11C6/vKIinxd7ALW5iGWaGEpo+Re5S1cIP4YyWYUqZE8nWU9O/imSnxZ+9W8A==} + '@tanstack/solid-store@0.7.3': + resolution: {integrity: sha512-fEdiGQdlzG/eS4lOaIWvKp94hopVZiDZ2IgrQ9ntu5QtLPXOABq/2VDSQI7jkx43e3AdLdvlXVrljxw2BVB9tw==} peerDependencies: solid-js: ^1.6.0 - '@tanstack/store@0.7.1': - resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==} + '@tanstack/store@0.7.2': + resolution: {integrity: sha512-RP80Z30BYiPX2Pyo0Nyw4s1SJFH2jyM6f9i3HfX4pA+gm5jsnYryscdq2aIQLnL4TaGuQMO+zXmN9nh1Qck+Pg==} '@tanstack/typedoc-config@0.2.0': resolution: {integrity: sha512-1ak0ZirlLRxd3dNNOFnMoYORBeC83nK4C+OiXpE0dxsO8ZVrBqCtNCKr8SG+W9zICXcWGiFu9qYLsgNKTayOqw==} @@ -4658,8 +4658,8 @@ packages: vite: optional: true - vite@7.0.1: - resolution: {integrity: sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==} + vite@7.0.2: + resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -5951,9 +5951,9 @@ snapshots: - supports-color - typescript - '@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)': + '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': dependencies: - acorn: 8.14.1 + acorn: 8.15.0 '@svitejs/changesets-changelog-github-compact@1.2.0': dependencies: @@ -5962,12 +5962,12 @@ snapshots: transitivePeerDependencies: - encoding - '@tanstack/config@0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@tanstack/config@0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@tanstack/eslint-config': 0.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) '@tanstack/publish-config': 0.2.0 '@tanstack/typedoc-config': 0.2.0(typescript@5.8.3) - '@tanstack/vite-config': 0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + '@tanstack/vite-config': 0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) transitivePeerDependencies: - '@types/node' - '@typescript-eslint/utils' @@ -6018,19 +6018,19 @@ snapshots: '@tanstack/query-core': 5.81.5 react: 19.1.0 - '@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@tanstack/react-store@0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@tanstack/store': 0.7.1 + '@tanstack/store': 0.7.2 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) use-sync-external-store: 1.5.0(react@19.1.0) - '@tanstack/solid-store@0.7.1(solid-js@1.9.7)': + '@tanstack/solid-store@0.7.3(solid-js@1.9.7)': dependencies: - '@tanstack/store': 0.7.1 + '@tanstack/store': 0.7.2 solid-js: 1.9.7 - '@tanstack/store@0.7.1': {} + '@tanstack/store@0.7.2': {} '@tanstack/typedoc-config@0.2.0(typescript@5.8.3)': dependencies: @@ -6040,12 +6040,12 @@ snapshots: transitivePeerDependencies: - typescript - '@tanstack/vite-config@0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@tanstack/vite-config@0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: rollup-plugin-preserve-directives: 0.4.0(rollup@4.44.1) - vite-plugin-dts: 4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - vite-plugin-externalize-deps: 0.9.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - vite-tsconfig-paths: 5.1.4(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-plugin-dts: 4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-plugin-externalize-deps: 0.9.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-tsconfig-paths: 5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) transitivePeerDependencies: - '@types/node' - rollup @@ -6334,7 +6334,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.7.11': optional: true - '@vitejs/plugin-react@4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@vitejs/plugin-react@4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) @@ -6342,7 +6342,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.19 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -6354,13 +6354,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@vitest/mocker@3.2.4(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -7474,7 +7474,7 @@ snapshots: is-reference@3.0.3: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 is-subdir@1.2.0: dependencies: @@ -8249,9 +8249,9 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@jridgewell/sourcemap-codec': 1.5.0 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) - '@types/estree': 1.0.6 - acorn: 8.14.1 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 aria-query: 5.3.2 axobject-query: 4.1.0 clsx: 2.1.1 @@ -8453,7 +8453,7 @@ snapshots: debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - jiti @@ -8468,7 +8468,7 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-dts@4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: '@microsoft/api-extractor': 7.47.7(@types/node@24.0.10) '@rollup/pluginutils': 5.1.4(rollup@4.44.1) @@ -8481,17 +8481,17 @@ snapshots: magic-string: 0.30.17 typescript: 5.8.3 optionalDependencies: - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-externalize-deps@0.9.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-externalize-deps@0.9.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vite-plugin-solid@2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-solid@2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: '@babel/core': 7.26.10 '@types/babel__core': 7.20.5 @@ -8499,25 +8499,25 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.7 solid-refresh: 0.6.3(solid-js@1.9.7) - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vitefu: 1.0.6(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vitefu: 1.0.6(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) optionalDependencies: '@testing-library/jest-dom': 6.6.3 transitivePeerDependencies: - supports-color - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.8.3) optionalDependencies: - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color - typescript - vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): + vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: esbuild: 0.25.0 fdir: 6.4.6(picomatch@4.0.2) @@ -8532,15 +8532,15 @@ snapshots: tsx: 4.19.3 yaml: 2.7.0 - vitefu@1.0.6(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vitefu@1.0.6(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): optionalDependencies: - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + '@vitest/mocker': 3.2.4(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -8558,7 +8558,7 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-node: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: From fb51ac0ee4e72553f410cc2b77e454e778d3d8e4 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 6 Jul 2025 22:19:56 -0500 Subject: [PATCH 31/51] format --- examples/solid/createDebouncer/src/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/solid/createDebouncer/src/index.tsx b/examples/solid/createDebouncer/src/index.tsx index 359e2279f..eb78b7ca9 100644 --- a/examples/solid/createDebouncer/src/index.tsx +++ b/examples/solid/createDebouncer/src/index.tsx @@ -171,7 +171,8 @@ function App3() { From 5effd780c3b8424009f0af274866f57e6d585470 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 6 Jul 2025 23:14:06 -0500 Subject: [PATCH 32/51] update async rate limiter utils to tanstack store --- .../functions/createasyncratelimiter.md | 15 +- .../interfaces/solidasyncratelimiter.md | 70 +------ .../react/useAsyncRateLimiter/src/index.tsx | 13 +- .../createAsyncRateLimiter/src/index.tsx | 22 +- packages/pacer/src/async-rate-limiter.ts | 196 ++++++------------ .../pacer/tests/async-rate-limiter.test.ts | 12 +- .../async-rate-limiter/useAsyncRateLimiter.ts | 39 +++- .../src/rate-limiter/useRateLimiter.ts | 2 +- .../createAsyncRateLimiter.ts | 92 ++------ 9 files changed, 166 insertions(+), 295 deletions(-) diff --git a/docs/framework/solid/reference/functions/createasyncratelimiter.md b/docs/framework/solid/reference/functions/createasyncratelimiter.md index 9fff1e021..0c5fb12f9 100644 --- a/docs/framework/solid/reference/functions/createasyncratelimiter.md +++ b/docs/framework/solid/reference/functions/createasyncratelimiter.md @@ -8,10 +8,13 @@ title: createAsyncRateLimiter # Function: createAsyncRateLimiter() ```ts -function createAsyncRateLimiter(fn, initialOptions): SolidAsyncRateLimiter +function createAsyncRateLimiter( + fn, + initialOptions, +selector?): SolidAsyncRateLimiter ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:87](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L87) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:79](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L79) A low-level Solid hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. @@ -51,6 +54,8 @@ Error Handling: • **TFn** *extends* `AnyAsyncFunction` +• **TSelected** = `AsyncRateLimiterState`\<`TFn`\> + ## Parameters ### fn @@ -61,9 +66,13 @@ Error Handling: `AsyncRateLimiterOptions`\<`TFn`\> +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidAsyncRateLimiter`](../../interfaces/solidasyncratelimiter.md)\<`TFn`\> +[`SolidAsyncRateLimiter`](../../interfaces/solidasyncratelimiter.md)\<`TFn`, `TSelected`\> ## Example diff --git a/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md b/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md index 8666448a9..f906907f7 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md +++ b/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md @@ -5,80 +5,26 @@ title: SolidAsyncRateLimiter -# Interface: SolidAsyncRateLimiter\ +# Interface: SolidAsyncRateLimiter\ -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:7](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L7) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L10) ## Extends -- `Omit`\<`AsyncRateLimiter`\<`TFn`\>, - \| `"getSuccessCount"` - \| `"getSettleCount"` - \| `"getErrorCount"` - \| `"getRejectionCount"` - \| `"getRemainingInWindow"` - \| `"getMsUntilNextWindow"`\> +- `Omit`\<`AsyncRateLimiter`\<`TFn`\>, `"store"`\> ## Type Parameters • **TFn** *extends* `AnyAsyncFunction` -## Properties - -### errorCount - -```ts -errorCount: Accessor; -``` - -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L19) - -*** - -### msUntilNextWindow +• **TSelected** = `AsyncRateLimiterState`\<`TFn`\> -```ts -msUntilNextWindow: Accessor; -``` - -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L22) - -*** - -### rejectionCount - -```ts -rejectionCount: Accessor; -``` - -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L20) - -*** - -### remainingInWindow - -```ts -remainingInWindow: Accessor; -``` - -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L21) - -*** - -### settleCount - -```ts -settleCount: Accessor; -``` - -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:18](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L18) - -*** +## Properties -### successCount +### state ```ts -successCount: Accessor; +state: Accessor; ``` -Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L17) +Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:14](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L14) diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index 74949e3fa..b9ab5b093 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -37,7 +37,7 @@ function App() { setResults(data) setError(null) - console.log(setSearchAsyncRateLimiter.getSuccessCount()) + console.log(setSearchAsyncRateLimiter.state.successCount) } const rateLimiterPersister = useStoragePersister< @@ -67,9 +67,12 @@ function App() { }, // optionally, you can persist the rate limiter state to localStorage initialState: rateLimiterPersister.loadState(), - onStateChange: (state) => rateLimiterPersister.saveState(state), }) + useEffect(() => { + rateLimiterPersister.saveState(setSearchAsyncRateLimiter.state) + }, [setSearchAsyncRateLimiter.state]) + // get and name our rate limited function const handleSearchRateLimited = setSearchAsyncRateLimiter.maybeExecute @@ -108,16 +111,16 @@ function App() { - + - + diff --git a/examples/solid/createAsyncRateLimiter/src/index.tsx b/examples/solid/createAsyncRateLimiter/src/index.tsx index 788d800a0..1b8c35243 100644 --- a/examples/solid/createAsyncRateLimiter/src/index.tsx +++ b/examples/solid/createAsyncRateLimiter/src/index.tsx @@ -20,8 +20,6 @@ const fakeApi = async (term: string): Promise> => { function App() { const [searchTerm, setSearchTerm] = createSignal('') const [results, setResults] = createSignal>([]) - const [isLoading, setIsLoading] = createSignal(false) - const [error, setError] = createSignal(null) // The function that will become rate limited const handleSearch = async (term: string) => { @@ -32,16 +30,10 @@ function App() { // throw new Error('Test error') // you don't have to catch errors here (though you still can). The onError optional handler will catch it - if (!results.length) { - setIsLoading(true) - } - const data = await fakeApi(term) setResults(data) - setIsLoading(false) - setError(null) - console.log(setSearchAsyncRateLimiter.successCount()) + console.log(setSearchAsyncRateLimiter.state().successCount) } // hook that gives you an async rate limiter instance @@ -51,9 +43,14 @@ function App() { onError: (error) => { // optional error handler console.error('Search failed:', error) - setError(error as Error) setResults([]) }, + onReject: (rateLimiter) => { + console.log( + 'Rate limit exceeded:', + rateLimiter.store.state.rejectionCount, + ) + }, }) // get and name our rate limited function @@ -80,15 +77,14 @@ function App() { autocomplete="new-password" /> - {error() &&
Error: {error()?.message}
}
-

API calls made: {setSearchAsyncRateLimiter.successCount()}

+

API calls made: {setSearchAsyncRateLimiter.state().successCount}

{results().length > 0 && (
    {(item) =>
  • {item.title}
  • }
)} - {isLoading() &&

Loading...

} + {setSearchAsyncRateLimiter.state().isExecuting &&

Loading...

}
) diff --git a/packages/pacer/src/async-rate-limiter.ts b/packages/pacer/src/async-rate-limiter.ts index 5473517e3..87204a613 100644 --- a/packages/pacer/src/async-rate-limiter.ts +++ b/packages/pacer/src/async-rate-limiter.ts @@ -1,3 +1,4 @@ +import { Store } from '@tanstack/store' import { parseFunctionOrValue } from './utils' import type { AnyAsyncFunction } from './types' @@ -11,6 +12,20 @@ export interface AsyncRateLimiterState { successCount: number } +function getDefaultAsyncRateLimiterState< + TFn extends AnyAsyncFunction, +>(): AsyncRateLimiterState { + return { + errorCount: 0, + executionTimes: [], + isExecuting: false, + lastResult: undefined, + rejectionCount: 0, + settleCount: 0, + successCount: 0, + } +} + /** * Options for configuring an async rate-limited function */ @@ -24,9 +39,7 @@ export interface AsyncRateLimiterOptions { /** * Initial state for the rate limiter */ - initialState?: - | Partial> - | Promise>> + initialState?: Partial> /** * Maximum number of executions allowed within the time window. * Can be a number or a function that returns a number. @@ -53,13 +66,6 @@ export interface AsyncRateLimiterOptions { result: ReturnType, rateLimiter: AsyncRateLimiter, ) => void - /** - * Callback function that is called when the state of the rate limiter is updated - */ - onStateChange?: ( - state: AsyncRateLimiterState, - rateLimiter: AsyncRateLimiter, - ) => void /** * Whether to throw errors when they occur. * Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -158,16 +164,10 @@ const defaultOptions: Omit< * ``` */ export class AsyncRateLimiter { + readonly store: Store> = new Store< + AsyncRateLimiterState + >(getDefaultAsyncRateLimiterState()) #options: AsyncRateLimiterOptions - #state: AsyncRateLimiterState = { - errorCount: 0, - executionTimes: [], - isExecuting: false, - lastResult: undefined, - rejectionCount: 0, - settleCount: 0, - successCount: 0, - } constructor( private fn: TFn, @@ -178,16 +178,7 @@ export class AsyncRateLimiter { ...initialOptions, throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, } - if (this.#options.initialState instanceof Promise) { - this.#options.initialState.then((state) => { - this.#setState(state) - }) - } else { - this.#state = { - ...this.#state, - ...this.#options.initialState, - } - } + this.#setState(this.#options.initialState ?? {}) } /** @@ -197,40 +188,34 @@ export class AsyncRateLimiter { this.#options = { ...this.#options, ...newOptions } } - /** - * Returns the current async rate limiter options - */ - getOptions = (): Required> => { - return this.#options as Required> - } - - getState = (): AsyncRateLimiterState => { - return { ...this.#state } - } - #setState = (newState: Partial>): void => { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + return combinedState + }) } /** * Returns the current enabled state of the async rate limiter */ - getEnabled = (): boolean => { + #getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current limit of executions allowed within the time window */ - getLimit = (): number => { + #getLimit = (): number => { return parseFunctionOrValue(this.#options.limit, this) } /** * Returns the current time window in milliseconds */ - getWindow = (): number => { + #getWindow = (): number => { return parseFunctionOrValue(this.#options.window, this) } @@ -264,81 +249,81 @@ export class AsyncRateLimiter { ): Promise | undefined> => { this.#cleanupOldExecutions() - const limit = this.getLimit() - const window = this.getWindow() - - if (this.#options.windowType === 'sliding') { - // For sliding window, we can execute if we have capacity in the current window - if (this.#state.executionTimes.length < limit) { - await this.#execute(...args) - return this.#state.lastResult - } - } else { - // For fixed window, we need to check if we're in a new window - const now = Date.now() - const oldestExecution = Math.min(...this.#state.executionTimes) - const isNewWindow = oldestExecution + window <= now + const relevantExecutionTimes = this.#getRelevantExecutionTimes() - if (isNewWindow || this.#state.executionTimes.length < limit) { - await this.#execute(...args) - return this.#state.lastResult - } + if (relevantExecutionTimes.length < this.#getLimit()) { + await this.#execute(...args) + return this.store.state.lastResult } - this.#rejectFunction() + this.#setState({ + rejectionCount: this.store.state.rejectionCount + 1, + }) + this.#options.onReject?.(this) return undefined } #execute = async ( ...args: Parameters ): Promise | undefined> => { - if (!this.getEnabled()) return + if (!this.#getEnabled()) return + const now = Date.now() - this.#state.executionTimes.push(now) // mutate state directly for performance + const executionTimes = [...this.store.state.executionTimes, now] this.#setState({ isExecuting: true, + executionTimes, }) try { const result = await this.fn(...args) this.#setState({ - successCount: this.#state.successCount + 1, + successCount: this.store.state.successCount + 1, lastResult: result, }) this.#options.onSuccess?.(result, this) } catch (error) { this.#setState({ - errorCount: this.#state.errorCount + 1, + errorCount: this.store.state.errorCount + 1, }) this.#options.onError?.(error, this) if (this.#options.throwOnError) { throw error - } else { - console.error(error) } } finally { this.#setState({ isExecuting: false, - settleCount: this.#state.settleCount + 1, + settleCount: this.store.state.settleCount + 1, }) this.#options.onSettled?.(this) } - return this.#state.lastResult + return this.store.state.lastResult } - #rejectFunction = (): void => { - this.#setState({ - rejectionCount: this.#state.rejectionCount + 1, - }) - this.#options.onReject?.(this) + #getRelevantExecutionTimes = (): Array => { + if (this.#options.windowType === 'sliding') { + // For sliding window, return all executions within the current window + return this.store.state.executionTimes.filter( + (time) => time > Date.now() - this.#getWindow(), + ) + } else { + // For fixed window, return all executions in the current window + // The window starts from the oldest execution time + const oldestExecution = Math.min(...this.store.state.executionTimes) + const windowStart = oldestExecution + return this.store.state.executionTimes.filter( + (time) => + time >= windowStart && time <= windowStart + this.#getWindow(), + ) + } } #cleanupOldExecutions = (): void => { const now = Date.now() - const windowStart = now - this.getWindow() + const windowStart = now - this.#getWindow() this.#setState({ - executionTimes: this.#state.executionTimes.filter( + executionTimes: this.store.state.executionTimes.filter( (time) => time > windowStart, ), }) @@ -348,8 +333,8 @@ export class AsyncRateLimiter { * Returns the number of remaining executions allowed in the current window */ getRemainingInWindow = (): number => { - this.#cleanupOldExecutions() - return Math.max(0, this.getLimit() - this.#state.executionTimes.length) + const relevantExecutionTimes = this.#getRelevantExecutionTimes() + return Math.max(0, this.#getLimit() - relevantExecutionTimes.length) } /** @@ -361,58 +346,15 @@ export class AsyncRateLimiter { if (this.getRemainingInWindow() > 0) { return 0 } - const oldestExecution = this.#state.executionTimes[0] ?? Infinity - return oldestExecution + this.getWindow() - Date.now() - } - - /** - * Returns the number of times the function has been executed - */ - getSuccessCount = (): number => { - return this.#state.successCount - } - - /** - * Returns the number of times the function has been settled - */ - getSettleCount = (): number => { - return this.#state.settleCount - } - - /** - * Returns the number of times the function has errored - */ - getErrorCount = (): number => { - return this.#state.errorCount - } - - /** - * Returns the number of times the function has been rejected - */ - getRejectionCount = (): number => { - return this.#state.rejectionCount - } - - /** - * Returns whether the function is currently executing - */ - getIsExecuting = (): boolean => { - return this.#state.isExecuting + const oldestExecution = this.store.state.executionTimes[0] ?? Infinity + return oldestExecution + this.#getWindow() - Date.now() } /** * Resets the rate limiter state */ reset = (): void => { - this.#setState({ - executionTimes: [], - rejectionCount: 0, - errorCount: 0, - settleCount: 0, - successCount: 0, - isExecuting: false, - lastResult: undefined, - }) + this.#setState(getDefaultAsyncRateLimiterState()) } } diff --git a/packages/pacer/tests/async-rate-limiter.test.ts b/packages/pacer/tests/async-rate-limiter.test.ts index 727a4a77e..d112e5c22 100644 --- a/packages/pacer/tests/async-rate-limiter.test.ts +++ b/packages/pacer/tests/async-rate-limiter.test.ts @@ -58,16 +58,16 @@ describe('AsyncRateLimiter', () => { }) await rateLimiter.maybeExecute() - expect(rateLimiter.getSuccessCount()).toBe(1) - expect(rateLimiter.getErrorCount()).toBe(0) + expect(rateLimiter.store.state.successCount).toBe(1) + expect(rateLimiter.store.state.errorCount).toBe(0) await rateLimiter.maybeExecute().catch(() => {}) - expect(rateLimiter.getSuccessCount()).toBe(1) - expect(rateLimiter.getErrorCount()).toBe(1) + expect(rateLimiter.store.state.successCount).toBe(1) + expect(rateLimiter.store.state.errorCount).toBe(1) await rateLimiter.maybeExecute() - expect(rateLimiter.getSuccessCount()).toBe(2) - expect(rateLimiter.getErrorCount()).toBe(1) + expect(rateLimiter.store.state.successCount).toBe(2) + expect(rateLimiter.store.state.errorCount).toBe(1) }) it('should track remaining executions', async () => { diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts index 537941260..ca2835267 100644 --- a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts @@ -1,7 +1,23 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' +import { useStore } from '@tanstack/react-store' import type { AnyAsyncFunction } from '@tanstack/pacer/types' -import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter' +import type { + AsyncRateLimiterOptions, + AsyncRateLimiterState, +} from '@tanstack/pacer/async-rate-limiter' + +export interface ReactAsyncRateLimiter< + TFn extends AnyAsyncFunction, + TSelected = AsyncRateLimiterState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the rate limiter state changes + * + * Use this instead of `rateLimiter.store.state` + */ + state: TSelected +} /** * A low-level React hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. @@ -63,15 +79,28 @@ import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter * ); * ``` */ -export function useAsyncRateLimiter( +export function useAsyncRateLimiter< + TFn extends AnyAsyncFunction, + TSelected = AsyncRateLimiterState, +>( fn: TFn, options: AsyncRateLimiterOptions, -): AsyncRateLimiter { + selector?: (state: AsyncRateLimiterState) => TSelected, +): ReactAsyncRateLimiter { const [asyncRateLimiter] = useState( () => new AsyncRateLimiter(fn, options), ) + const state = useStore(asyncRateLimiter.store, selector) + asyncRateLimiter.setOptions(options) - return asyncRateLimiter + return useMemo( + () => + ({ + ...asyncRateLimiter, + state, + }) as unknown as ReactAsyncRateLimiter, // omit `store` in favor of `state` + [asyncRateLimiter, state], + ) } diff --git a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts index 67e70e30a..d084315da 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts @@ -86,7 +86,7 @@ export function useRateLimiter< ({ ...rateLimiter, state, - }) as unknown as ReactRateLimiter, // omit `store` in favor of `state` + }) as ReactRateLimiter, // omit `store` in favor of `state` [rateLimiter, state], ) } diff --git a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts index dfc3196a7..0b225633b 100644 --- a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts +++ b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts @@ -1,25 +1,17 @@ import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' -import { createSignal } from 'solid-js' +import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' import type { AnyAsyncFunction } from '@tanstack/pacer/types' -import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter' +import type { + AsyncRateLimiterOptions, + AsyncRateLimiterState, +} from '@tanstack/pacer/async-rate-limiter' -export interface SolidAsyncRateLimiter - extends Omit< - AsyncRateLimiter, - | 'getSuccessCount' - | 'getSettleCount' - | 'getErrorCount' - | 'getRejectionCount' - | 'getRemainingInWindow' - | 'getMsUntilNextWindow' - > { - successCount: Accessor - settleCount: Accessor - errorCount: Accessor - rejectionCount: Accessor - remainingInWindow: Accessor - msUntilNextWindow: Accessor +export interface SolidAsyncRateLimiter< + TFn extends AnyAsyncFunction, + TSelected = AsyncRateLimiterState, +> extends Omit, 'store'> { + state: Accessor } /** @@ -84,66 +76,20 @@ export interface SolidAsyncRateLimiter * ); * ``` */ -export function createAsyncRateLimiter( +export function createAsyncRateLimiter< + TFn extends AnyAsyncFunction, + TSelected = AsyncRateLimiterState, +>( fn: TFn, initialOptions: AsyncRateLimiterOptions, -): SolidAsyncRateLimiter { + selector?: (state: AsyncRateLimiterState) => TSelected, +): SolidAsyncRateLimiter { const asyncRateLimiter = new AsyncRateLimiter(fn, initialOptions) - const [successCount, setSuccessCount] = createSignal( - asyncRateLimiter.getSuccessCount(), - ) - const [rejectionCount, setRejectionCount] = createSignal( - asyncRateLimiter.getRejectionCount(), - ) - const [errorCount, setErrorCount] = createSignal( - asyncRateLimiter.getErrorCount(), - ) - const [settleCount, setSettleCount] = createSignal( - asyncRateLimiter.getSettleCount(), - ) - const [remainingInWindow, setRemainingInWindow] = createSignal( - asyncRateLimiter.getRemainingInWindow(), - ) - const [msUntilNextWindow, setMsUntilNextWindow] = createSignal( - asyncRateLimiter.getMsUntilNextWindow(), - ) - - function setOptions(newOptions: Partial>) { - asyncRateLimiter.setOptions({ - ...newOptions, - onSettled: (rateLimiter) => { - setSuccessCount(rateLimiter.getSuccessCount()) - setSettleCount(rateLimiter.getSettleCount()) - setErrorCount(rateLimiter.getErrorCount()) - setRejectionCount(rateLimiter.getRejectionCount()) - setRemainingInWindow(rateLimiter.getRemainingInWindow()) - setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow()) - - const onSettled = newOptions.onSettled ?? initialOptions.onSettled - onSettled?.(rateLimiter) - }, - onReject: (rateLimiter) => { - setRejectionCount(rateLimiter.getRejectionCount()) - setRemainingInWindow(rateLimiter.getRemainingInWindow()) - setMsUntilNextWindow(rateLimiter.getMsUntilNextWindow()) - - const onReject = newOptions.onReject ?? initialOptions.onReject - onReject?.(rateLimiter) - }, - }) - } - - setOptions(initialOptions) + const state = useStore(asyncRateLimiter.store, selector) return { ...asyncRateLimiter, - errorCount, - remainingInWindow, - msUntilNextWindow, - rejectionCount, - setOptions, - settleCount, - successCount, - } as SolidAsyncRateLimiter + state, + } as SolidAsyncRateLimiter } From f9c57ec9a3f200987da13b7fe17f0c3a416576df Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 7 Jul 2025 22:19:19 -0500 Subject: [PATCH 33/51] update batcher utils to tanstack store --- .../reference/functions/createbatcher.md | 15 +- .../interfaces/solidasyncdebouncer.md | 2 +- .../reference/interfaces/solidasyncqueuer.md | 2 +- .../interfaces/solidasyncratelimiter.md | 2 +- .../reference/interfaces/solidbatcher.md | 80 ++-------- .../reference/interfaces/soliddebouncer.md | 2 +- .../solid/reference/interfaces/solidqueuer.md | 2 +- .../reference/interfaces/solidratelimiter.md | 2 +- .../reference/interfaces/solidthrottler.md | 2 +- examples/react/useBatcher/src/index.tsx | 16 +- examples/solid/createBatcher/src/index.tsx | 28 ++-- packages/pacer/src/batcher.ts | 144 ++++++++---------- .../src/async-debouncer/useAsyncDebouncer.ts | 2 +- .../src/async-queuer/useAsyncQueuer.ts | 2 +- .../async-rate-limiter/useAsyncRateLimiter.ts | 2 +- .../react-pacer/src/batcher/useBatcher.ts | 30 +++- .../react-pacer/src/debouncer/useDebouncer.ts | 2 +- packages/react-pacer/src/queuer/useQueuer.ts | 2 +- .../src/rate-limiter/useRateLimiter.ts | 2 +- .../react-pacer/src/throttler/useThrottler.ts | 2 +- .../async-debouncer/createAsyncDebouncer.ts | 2 +- .../src/async-queuer/createAsyncQueuer.ts | 2 +- .../createAsyncRateLimiter.ts | 2 +- .../solid-pacer/src/batcher/createBatcher.ts | 106 ++----------- .../src/debouncer/createDebouncer.ts | 2 +- .../solid-pacer/src/queuer/createQueuer.ts | 2 +- .../src/rate-limiter/createRateLimiter.ts | 2 +- .../src/throttler/createThrottler.ts | 2 +- 28 files changed, 169 insertions(+), 292 deletions(-) diff --git a/docs/framework/solid/reference/functions/createbatcher.md b/docs/framework/solid/reference/functions/createbatcher.md index c90013b25..974f7080c 100644 --- a/docs/framework/solid/reference/functions/createbatcher.md +++ b/docs/framework/solid/reference/functions/createbatcher.md @@ -8,10 +8,13 @@ title: createBatcher # Function: createBatcher() ```ts -function createBatcher(fn, initialOptions): SolidBatcher +function createBatcher( + fn, + initialOptions, +selector?): SolidBatcher ``` -Defined in: [batcher/createBatcher.ts:89](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L89) +Defined in: [batcher/createBatcher.ts:63](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L63) Creates a Solid-compatible Batcher instance for managing batches of items, exposing Solid signals for all stateful properties. @@ -63,6 +66,8 @@ console.log('Item count:', batcher.itemExecutionCount()); • **TValue** +• **TSelected** = `BatcherState`\<`TValue`\> + ## Parameters ### fn @@ -73,6 +78,10 @@ console.log('Item count:', batcher.itemExecutionCount()); `BatcherOptions`\<`TValue`\> = `{}` +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidBatcher`](../../interfaces/solidbatcher.md)\<`TValue`\> +[`SolidBatcher`](../../interfaces/solidbatcher.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md index 1fe93f678..0fdd4f818 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md +++ b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md @@ -24,7 +24,7 @@ Defined in: [async-debouncer/createAsyncDebouncer.ts:11](https://github.com/TanS ### state ```ts -state: Accessor; +readonly state: Accessor; ``` Defined in: [async-debouncer/createAsyncDebouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L20) diff --git a/docs/framework/solid/reference/interfaces/solidasyncqueuer.md b/docs/framework/solid/reference/interfaces/solidasyncqueuer.md index 02a8c4ff3..99322fa58 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncqueuer.md +++ b/docs/framework/solid/reference/interfaces/solidasyncqueuer.md @@ -24,7 +24,7 @@ Defined in: [async-queuer/createAsyncQueuer.ts:9](https://github.com/TanStack/pa ### state ```ts -state: Accessor; +readonly state: Accessor; ``` Defined in: [async-queuer/createAsyncQueuer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L16) diff --git a/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md b/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md index f906907f7..4587c9694 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md +++ b/docs/framework/solid/reference/interfaces/solidasyncratelimiter.md @@ -24,7 +24,7 @@ Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:10](https://github.com ### state ```ts -state: Accessor; +readonly state: Accessor; ``` Defined in: [async-rate-limiter/createAsyncRateLimiter.ts:14](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L14) diff --git a/docs/framework/solid/reference/interfaces/solidbatcher.md b/docs/framework/solid/reference/interfaces/solidbatcher.md index 0f974597f..2dc0d7b3e 100644 --- a/docs/framework/solid/reference/interfaces/solidbatcher.md +++ b/docs/framework/solid/reference/interfaces/solidbatcher.md @@ -5,92 +5,30 @@ title: SolidBatcher -# Interface: SolidBatcher\ +# Interface: SolidBatcher\ Defined in: [batcher/createBatcher.ts:6](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L6) ## Extends -- `Omit`\<`Batcher`\<`TValue`\>, - \| `"peekAllItems"` - \| `"getBatchExecutionCount"` - \| `"getIsEmpty"` - \| `"getIsRunning"` - \| `"getItemExecutionCount"` - \| `"getSize"`\> +- `Omit`\<`Batcher`\<`TValue`\>, `"store"`\> ## Type Parameters • **TValue** -## Properties - -### allItems - -```ts -allItems: Accessor; -``` - -Defined in: [batcher/createBatcher.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L19) - -Signal version of `peekAllItems` - -*** - -### batchExecutionCount +• **TSelected** = `BatcherState`\<`TValue`\> -```ts -batchExecutionCount: Accessor; -``` - -Defined in: [batcher/createBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L23) - -Signal version of `getBatchExecutionCount` - -*** - -### isEmpty - -```ts -isEmpty: Accessor; -``` - -Defined in: [batcher/createBatcher.ts:27](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L27) - -Signal version of `getIsEmpty` - -*** - -### isRunning - -```ts -isRunning: Accessor; -``` - -Defined in: [batcher/createBatcher.ts:31](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L31) - -Signal version of `getIsRunning` - -*** +## Properties -### itemExecutionCount +### state ```ts -itemExecutionCount: Accessor; +readonly state: Accessor; ``` -Defined in: [batcher/createBatcher.ts:35](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L35) - -Signal version of `getItemExecutionCount` - -*** - -### size - -```ts -size: Accessor; -``` +Defined in: [batcher/createBatcher.ts:13](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L13) -Defined in: [batcher/createBatcher.ts:39](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L39) +Reactive state that will be updated when the batcher state changes -Signal version of `getSize` +Use this instead of `batcher.store.state` diff --git a/docs/framework/solid/reference/interfaces/soliddebouncer.md b/docs/framework/solid/reference/interfaces/soliddebouncer.md index 61c5b5a0d..8dd0843ed 100644 --- a/docs/framework/solid/reference/interfaces/soliddebouncer.md +++ b/docs/framework/solid/reference/interfaces/soliddebouncer.md @@ -24,7 +24,7 @@ Defined in: [debouncer/createDebouncer.ts:11](https://github.com/TanStack/pacer/ ### state ```ts -state: Accessor; +readonly state: Accessor; ``` Defined in: [debouncer/createDebouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L20) diff --git a/docs/framework/solid/reference/interfaces/solidqueuer.md b/docs/framework/solid/reference/interfaces/solidqueuer.md index fd365a917..b10006451 100644 --- a/docs/framework/solid/reference/interfaces/solidqueuer.md +++ b/docs/framework/solid/reference/interfaces/solidqueuer.md @@ -24,7 +24,7 @@ Defined in: [queuer/createQueuer.ts:6](https://github.com/TanStack/pacer/blob/ma ### state ```ts -state: Accessor; +readonly state: Accessor; ``` Defined in: [queuer/createQueuer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L13) diff --git a/docs/framework/solid/reference/interfaces/solidratelimiter.md b/docs/framework/solid/reference/interfaces/solidratelimiter.md index 0c38a573c..8aae40184 100644 --- a/docs/framework/solid/reference/interfaces/solidratelimiter.md +++ b/docs/framework/solid/reference/interfaces/solidratelimiter.md @@ -24,7 +24,7 @@ Defined in: [rate-limiter/createRateLimiter.ts:10](https://github.com/TanStack/p ### state ```ts -state: Accessor; +readonly state: Accessor; ``` Defined in: [rate-limiter/createRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L19) diff --git a/docs/framework/solid/reference/interfaces/solidthrottler.md b/docs/framework/solid/reference/interfaces/solidthrottler.md index 5138a7d45..8239553c4 100644 --- a/docs/framework/solid/reference/interfaces/solidthrottler.md +++ b/docs/framework/solid/reference/interfaces/solidthrottler.md @@ -24,7 +24,7 @@ Defined in: [throttler/createThrottler.ts:11](https://github.com/TanStack/pacer/ ### state ```ts -state: Accessor; +readonly state: Accessor; ``` Defined in: [throttler/createThrottler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L20) diff --git a/examples/react/useBatcher/src/index.tsx b/examples/react/useBatcher/src/index.tsx index fb2c6d3e8..22ad9a616 100644 --- a/examples/react/useBatcher/src/index.tsx +++ b/examples/react/useBatcher/src/index.tsx @@ -28,11 +28,11 @@ function App1() { return (

TanStack Pacer useBatcher Example 1

-
Batch Size: {batcher.getSize()}
+
Batch Size: {batcher.state.size}
Batch Max Size: {3}
Batch Items: {batchItems.join(', ')}
-
Batches Processed: {batcher.getBatchExecutionCount()}
-
Items Processed: {batcher.getItemExecutionCount()}
+
Batches Processed: {batcher.state.batchExecutionCount}
+
Items Processed: {batcher.state.itemExecutionCount}
Processed Batches:{' '} {processedBatches.map((b, i) => ( @@ -61,7 +61,7 @@ function App1() { Add Number
diff --git a/examples/solid/createBatcher/src/index.tsx b/examples/solid/createBatcher/src/index.tsx index 92cfa75f2..d62831aaa 100644 --- a/examples/solid/createBatcher/src/index.tsx +++ b/examples/solid/createBatcher/src/index.tsx @@ -23,11 +23,11 @@ function App1() { return (

TanStack Pacer createBatcher Example 1

-
Batch Size: {batcher.size()}
+
Batch Size: {batcher.state().size}
Batch Max Size: {5}
-
Batch Items: {batcher.allItems().join(', ')}
-
Batches Processed: {batcher.batchExecutionCount()}
-
Items Processed: {batcher.itemExecutionCount()}
+
Batch Items: {batcher.state().items.join(', ')}
+
Batches Processed: {batcher.state().batchExecutionCount}
+
Items Processed: {batcher.state().itemExecutionCount}
Processed Batches:{' '} @@ -45,8 +45,8 @@ function App1() { > - -
diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index 764515585..d97e7dacc 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -1,10 +1,28 @@ +import { Store } from '@tanstack/store' import type { OptionalKeys } from './types' export interface BatcherState { batchExecutionCount: number + isEmpty: boolean + isPending: boolean + isRunning: boolean itemExecutionCount: number items: Array - running: boolean + size: number + status: 'idle' | 'pending' +} + +function getDefaultBatcherState(): BatcherState { + return { + batchExecutionCount: 0, + isEmpty: true, + isPending: false, + isRunning: true, + itemExecutionCount: 0, + items: [], + size: 0, + status: 'idle', + } } /** @@ -29,21 +47,10 @@ export interface BatcherOptions { * Callback fired after a batch is processed */ onExecute?: (batcher: Batcher) => void - /** - * Callback fired when the batcher's running state changes - */ - onIsRunningChange?: (batcher: Batcher) => void /** * Callback fired after items are added to the batcher */ onItemsChange?: (batcher: Batcher) => void - /** - * Callback function that is called when the state of the batcher is updated - */ - onStateChange?: ( - state: BatcherState, - batcher: Batcher, - ) => void /** * Whether the batcher should start processing immediately * @default true @@ -60,11 +67,7 @@ export interface BatcherOptions { type BatcherOptionsWithOptionalCallbacks = OptionalKeys< Required>, - | 'initialState' - | 'onExecute' - | 'onItemsChange' - | 'onIsRunningChange' - | 'onStateChange' + 'initialState' | 'onExecute' | 'onItemsChange' > const defaultOptions: BatcherOptionsWithOptionalCallbacks = { @@ -110,24 +113,21 @@ const defaultOptions: BatcherOptionsWithOptionalCallbacks = { * ``` */ export class Batcher { + readonly store: Store> = new Store( + getDefaultBatcherState(), + ) #options: BatcherOptionsWithOptionalCallbacks - #state: BatcherState = { - batchExecutionCount: 0, - itemExecutionCount: 0, - items: [], - running: true, - } #timeoutId: NodeJS.Timeout | null = null constructor( private fn: (items: Array) => void, initialOptions: BatcherOptions, ) { - this.#options = { ...defaultOptions, ...initialOptions } - this.#state = { - ...this.#state, - ...this.#options.initialState, + this.#options = { + ...defaultOptions, + ...initialOptions, } + this.#setState(this.#options.initialState ?? {}) } /** @@ -137,20 +137,22 @@ export class Batcher { this.#options = { ...this.#options, ...newOptions } } - /** - * Returns the current batcher options - */ - getOptions = (): BatcherOptions => { - return this.#options - } - - getState = (): BatcherState => { - return { ...this.#state } - } - #setState = (newState: Partial>): void => { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + const { isPending, items } = combinedState + const size = items.length + const isEmpty = size === 0 + return { + ...combinedState, + isEmpty, + size, + status: isPending ? 'pending' : 'idle', + } + }) } /** @@ -159,18 +161,19 @@ export class Batcher { */ addItem = (item: TValue): void => { this.#setState({ - items: [...this.#state.items, item], + items: [...this.store.state.items, item], + isPending: this.#options.wait !== Infinity, }) this.#options.onItemsChange?.(this) const shouldProcess = - this.#state.items.length >= this.#options.maxSize || - this.#options.getShouldExecute(this.#state.items, this) + this.store.state.items.length >= this.#options.maxSize || + this.#options.getShouldExecute(this.store.state.items, this) if (shouldProcess) { this.execute() } else if ( - this.#state.running && + this.store.state.isRunning && !this.#timeoutId && this.#options.wait !== Infinity ) { @@ -193,18 +196,18 @@ export class Batcher { this.#timeoutId = null } - if (this.#state.items.length === 0) { + if (this.store.state.items.length === 0) { return } const batch = this.peekAllItems() // copy of the items to be processed (to prevent race conditions) - this.#setState({ items: [] }) // Clear items before processing to prevent race conditions + this.clear() // Clear items before processing to prevent race conditions this.#options.onItemsChange?.(this) // Call onItemsChange to notify listeners that the items have changed - this.fn(batch) + this.fn(batch) // EXECUTE this.#setState({ - batchExecutionCount: this.#state.batchExecutionCount + 1, - itemExecutionCount: this.#state.itemExecutionCount + batch.length, + batchExecutionCount: this.store.state.batchExecutionCount + 1, + itemExecutionCount: this.store.state.itemExecutionCount + batch.length, }) this.#options.onExecute?.(this) } @@ -213,8 +216,7 @@ export class Batcher { * Stops the batcher from processing batches */ stop = (): void => { - this.#setState({ running: false }) - this.#options.onIsRunningChange?.(this) + this.#setState({ isRunning: false }) if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = null @@ -225,53 +227,31 @@ export class Batcher { * Starts the batcher and processes any pending items */ start = (): void => { - this.#setState({ running: true }) - this.#options.onIsRunningChange?.(this) - if (this.#state.items.length > 0 && !this.#timeoutId) { + this.#setState({ isRunning: true }) + if (this.store.state.items.length > 0 && !this.#timeoutId) { this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) } } - /** - * Returns the current number of items in the batcher - */ - getSize = (): number => { - return this.#state.items.length - } - - /** - * Returns true if the batcher is empty - */ - getIsEmpty = (): boolean => { - return this.#state.items.length === 0 - } - - /** - * Returns true if the batcher is running - */ - getIsRunning = (): boolean => { - return this.#state.running - } - /** * Returns a copy of all items in the batcher */ peekAllItems = (): Array => { - return [...this.#state.items] + return [...this.store.state.items] } /** - * Returns the number of batches that have been processed + * Removes all items from the batcher */ - getBatchExecutionCount = (): number => { - return this.#state.batchExecutionCount + clear = (): void => { + this.#setState({ items: [], isPending: false }) } /** - * Returns the total number of items that have been processed + * Resets the batcher state to its default values */ - getItemExecutionCount = (): number => { - return this.#state.itemExecutionCount + reset = (): void => { + this.#setState(getDefaultBatcherState()) } } diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index a324c7e83..e2974a08e 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -16,7 +16,7 @@ export interface ReactAsyncDebouncer< * * Use this instead of `debouncer.store.state` */ - state: TSelected + readonly state: TSelected } /** diff --git a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts index 37c950bb7..3cb43d8f9 100644 --- a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts +++ b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts @@ -13,7 +13,7 @@ export interface ReactAsyncQueuer> * * Use this instead of `queuer.store.state` */ - state: TSelected + readonly state: TSelected } /** diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts index ca2835267..876ab559b 100644 --- a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts @@ -16,7 +16,7 @@ export interface ReactAsyncRateLimiter< * * Use this instead of `rateLimiter.store.state` */ - state: TSelected + readonly state: TSelected } /** diff --git a/packages/react-pacer/src/batcher/useBatcher.ts b/packages/react-pacer/src/batcher/useBatcher.ts index 924c586a5..5ffc82256 100644 --- a/packages/react-pacer/src/batcher/useBatcher.ts +++ b/packages/react-pacer/src/batcher/useBatcher.ts @@ -1,6 +1,17 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { Batcher } from '@tanstack/pacer/batcher' -import type { BatcherOptions } from '@tanstack/pacer/batcher' +import { useStore } from '@tanstack/react-store' +import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher' + +export interface ReactBatcher> + extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the batcher state changes + * + * Use this instead of `batcher.store.state` + */ + readonly state: TSelected +} /** * A React hook that creates and manages a Batcher instance. @@ -39,13 +50,22 @@ import type { BatcherOptions } from '@tanstack/pacer/batcher' * batcher.start(); // Resume batching * ``` */ -export function useBatcher( +export function useBatcher>( fn: (items: Array) => void, options: BatcherOptions = {}, -): Batcher { + selector?: (state: BatcherState) => TSelected, +): ReactBatcher { const [batcher] = useState(() => new Batcher(fn, options)) + const state = useStore(batcher.store, selector) + batcher.setOptions(options) - return batcher + return useMemo( + () => ({ + ...batcher, + state, + }), + [batcher, state], + ) } diff --git a/packages/react-pacer/src/debouncer/useDebouncer.ts b/packages/react-pacer/src/debouncer/useDebouncer.ts index 4b84b23e5..1639224f6 100644 --- a/packages/react-pacer/src/debouncer/useDebouncer.ts +++ b/packages/react-pacer/src/debouncer/useDebouncer.ts @@ -16,7 +16,7 @@ export interface ReactDebouncer< * * Use this instead of `debouncer.store.state` */ - state: TSelected + readonly state: TSelected } /** diff --git a/packages/react-pacer/src/queuer/useQueuer.ts b/packages/react-pacer/src/queuer/useQueuer.ts index ce5c0696e..050f53171 100644 --- a/packages/react-pacer/src/queuer/useQueuer.ts +++ b/packages/react-pacer/src/queuer/useQueuer.ts @@ -10,7 +10,7 @@ export interface ReactQueuer> * * Use this instead of `queuer.store.state` */ - state: TSelected + readonly state: TSelected } /** diff --git a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts index d084315da..b61483e1a 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts @@ -16,7 +16,7 @@ export interface ReactRateLimiter< * * Use this instead of `rateLimiter.store.state` */ - state: TSelected + readonly state: TSelected } /** diff --git a/packages/react-pacer/src/throttler/useThrottler.ts b/packages/react-pacer/src/throttler/useThrottler.ts index b9717f6de..33d2f1990 100644 --- a/packages/react-pacer/src/throttler/useThrottler.ts +++ b/packages/react-pacer/src/throttler/useThrottler.ts @@ -16,7 +16,7 @@ export interface ReactThrottler< * * Use this instead of `throttler.store.state` */ - state: TSelected + readonly state: TSelected } /** diff --git a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts index b01f05e4d..e69d88f8d 100644 --- a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts +++ b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts @@ -17,7 +17,7 @@ export interface SolidAsyncDebouncer< * * Use this instead of `debouncer.store.state` */ - state: Accessor + readonly state: Accessor } /** diff --git a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts index 8aef21cfd..7bcd29b84 100644 --- a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts +++ b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts @@ -13,7 +13,7 @@ export interface SolidAsyncQueuer> * * Use this instead of `queuer.store.state` */ - state: Accessor + readonly state: Accessor } /** diff --git a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts index 0b225633b..270f41d45 100644 --- a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts +++ b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts @@ -11,7 +11,7 @@ export interface SolidAsyncRateLimiter< TFn extends AnyAsyncFunction, TSelected = AsyncRateLimiterState, > extends Omit, 'store'> { - state: Accessor + readonly state: Accessor } /** diff --git a/packages/solid-pacer/src/batcher/createBatcher.ts b/packages/solid-pacer/src/batcher/createBatcher.ts index 649250482..fea6d63d8 100644 --- a/packages/solid-pacer/src/batcher/createBatcher.ts +++ b/packages/solid-pacer/src/batcher/createBatcher.ts @@ -1,42 +1,16 @@ import { Batcher } from '@tanstack/pacer/batcher' -import { createSignal } from 'solid-js' +import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' -import type { BatcherOptions } from '@tanstack/pacer/batcher' +import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher' -export interface SolidBatcher - extends Omit< - Batcher, - | 'peekAllItems' - | 'getBatchExecutionCount' - | 'getIsEmpty' - | 'getIsRunning' - | 'getItemExecutionCount' - | 'getSize' - > { +export interface SolidBatcher> + extends Omit, 'store'> { /** - * Signal version of `peekAllItems` + * Reactive state that will be updated when the batcher state changes + * + * Use this instead of `batcher.store.state` */ - allItems: Accessor> - /** - * Signal version of `getBatchExecutionCount` - */ - batchExecutionCount: Accessor - /** - * Signal version of `getIsEmpty` - */ - isEmpty: Accessor - /** - * Signal version of `getIsRunning` - */ - isRunning: Accessor - /** - * Signal version of `getItemExecutionCount` - */ - itemExecutionCount: Accessor - /** - * Signal version of `getSize` - */ - size: Accessor + readonly state: Accessor } /** @@ -86,66 +60,16 @@ export interface SolidBatcher * console.log('Item count:', batcher.itemExecutionCount()); * ``` */ -export function createBatcher( +export function createBatcher>( fn: (items: Array) => void, initialOptions: BatcherOptions = {}, -): SolidBatcher { - const batcher = new Batcher(fn, initialOptions) - - const [allItems, setAllItems] = createSignal>( - batcher.peekAllItems(), - ) - const [batchExecutionCount, setBatchExecutionCount] = createSignal( - batcher.getBatchExecutionCount(), - ) - const [itemExecutionCount, setItemExecutionCount] = createSignal( - batcher.getItemExecutionCount(), - ) - const [isEmpty, setIsEmpty] = createSignal(batcher.getIsEmpty()) - const [isRunning, setIsRunning] = createSignal(batcher.getIsRunning()) - const [size, setSize] = createSignal(batcher.getSize()) - - function setOptions(newOptions: Partial>) { - batcher.setOptions({ - ...newOptions, - onItemsChange: (batcher) => { - setAllItems(batcher.peekAllItems()) - setBatchExecutionCount(batcher.getBatchExecutionCount()) - setItemExecutionCount(batcher.getItemExecutionCount()) - setIsEmpty(batcher.getIsEmpty()) - setSize(batcher.getSize()) - - const onItemsChange = - newOptions.onItemsChange ?? initialOptions.onItemsChange - onItemsChange?.(batcher) - }, - onExecute: (batcher) => { - setBatchExecutionCount(batcher.getBatchExecutionCount()) - setItemExecutionCount(batcher.getItemExecutionCount()) - - const onExecute = newOptions.onExecute ?? initialOptions.onExecute - onExecute?.(batcher) - }, - onIsRunningChange: (batcher) => { - setIsRunning(batcher.getIsRunning()) - - const onIsRunningChange = - newOptions.onIsRunningChange ?? initialOptions.onIsRunningChange - onIsRunningChange?.(batcher) - }, - }) - } - - setOptions(initialOptions) + selector?: (state: BatcherState) => TSelected, +): SolidBatcher { + const batcher = new Batcher(fn, initialOptions) + const state = useStore(batcher.store, selector) return { ...batcher, - allItems, - batchExecutionCount, - isEmpty, - isRunning, - itemExecutionCount, - size, - setOptions, - } as SolidBatcher + state, + } as SolidBatcher } diff --git a/packages/solid-pacer/src/debouncer/createDebouncer.ts b/packages/solid-pacer/src/debouncer/createDebouncer.ts index 5d59614eb..243571396 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncer.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncer.ts @@ -17,7 +17,7 @@ export interface SolidDebouncer< * * Use this instead of `debouncer.store.state` */ - state: Accessor + readonly state: Accessor } /** diff --git a/packages/solid-pacer/src/queuer/createQueuer.ts b/packages/solid-pacer/src/queuer/createQueuer.ts index 9c8b31c0a..131ed7e77 100644 --- a/packages/solid-pacer/src/queuer/createQueuer.ts +++ b/packages/solid-pacer/src/queuer/createQueuer.ts @@ -10,7 +10,7 @@ export interface SolidQueuer> * * Use this instead of `queuer.store.state` */ - state: Accessor + readonly state: Accessor } /** diff --git a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts index 3b89ed178..32cb82e54 100644 --- a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts +++ b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts @@ -16,7 +16,7 @@ export interface SolidRateLimiter< * * Use this instead of `rateLimiter.store.state` */ - state: Accessor + readonly state: Accessor } /** diff --git a/packages/solid-pacer/src/throttler/createThrottler.ts b/packages/solid-pacer/src/throttler/createThrottler.ts index d30826d8a..3044ac712 100644 --- a/packages/solid-pacer/src/throttler/createThrottler.ts +++ b/packages/solid-pacer/src/throttler/createThrottler.ts @@ -17,7 +17,7 @@ export interface SolidThrottler< * * Use this instead of `throttler.store.state` */ - state: Accessor + readonly state: Accessor } /** From 291184bd279db55dec94efa172ac3853678989d3 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 7 Jul 2025 23:27:02 -0500 Subject: [PATCH 34/51] create async batcher and examples --- .../reference/functions/createbatcher.md | 2 +- docs/guides/batching.md | 4 +- examples/react/useAsyncBatcher/.eslintrc.cjs | 13 + examples/react/useAsyncBatcher/.gitignore | 27 ++ examples/react/useAsyncBatcher/README.md | 6 + examples/react/useAsyncBatcher/index.html | 16 + examples/react/useAsyncBatcher/package.json | 34 ++ .../useAsyncBatcher/public/emblem-light.svg | 13 + examples/react/useAsyncBatcher/src/index.tsx | 227 ++++++++++ examples/react/useAsyncBatcher/tsconfig.json | 23 ++ examples/react/useAsyncBatcher/vite.config.ts | 13 + examples/react/useBatcher/src/index.tsx | 4 +- examples/solid/createAsyncBatcher/.gitignore | 27 ++ examples/solid/createAsyncBatcher/README.md | 6 + examples/solid/createAsyncBatcher/index.html | 16 + .../solid/createAsyncBatcher/package.json | 31 ++ .../public/emblem-light.svg | 13 + .../solid/createAsyncBatcher/src/index.tsx | 224 ++++++++++ .../solid/createAsyncBatcher/tsconfig.json | 24 ++ .../solid/createAsyncBatcher/vite.config.ts | 7 + examples/solid/createBatcher/src/index.tsx | 4 +- packages/pacer/package.json | 10 + packages/pacer/src/async-batcher.ts | 390 ++++++++++++++++++ packages/pacer/src/batcher.ts | 14 +- packages/pacer/src/index.ts | 1 + packages/react-pacer/package.json | 10 + .../react-pacer/src/async-batcher/index.ts | 3 + .../src/async-batcher/useAsyncBatcher.ts | 96 +++++ packages/react-pacer/src/index.ts | 2 +- packages/react-pacer/vite.config.ts | 1 + packages/solid-pacer/package.json | 10 + .../src/async-batcher/createAsyncBatcher.ts | 98 +++++ .../solid-pacer/src/async-batcher/index.ts | 3 + .../solid-pacer/src/batcher/createBatcher.ts | 4 +- packages/solid-pacer/src/index.ts | 3 + packages/solid-pacer/vite.config.ts | 1 + pnpm-lock.yaml | 41 ++ 37 files changed, 1404 insertions(+), 17 deletions(-) create mode 100644 examples/react/useAsyncBatcher/.eslintrc.cjs create mode 100644 examples/react/useAsyncBatcher/.gitignore create mode 100644 examples/react/useAsyncBatcher/README.md create mode 100644 examples/react/useAsyncBatcher/index.html create mode 100644 examples/react/useAsyncBatcher/package.json create mode 100644 examples/react/useAsyncBatcher/public/emblem-light.svg create mode 100644 examples/react/useAsyncBatcher/src/index.tsx create mode 100644 examples/react/useAsyncBatcher/tsconfig.json create mode 100644 examples/react/useAsyncBatcher/vite.config.ts create mode 100644 examples/solid/createAsyncBatcher/.gitignore create mode 100644 examples/solid/createAsyncBatcher/README.md create mode 100644 examples/solid/createAsyncBatcher/index.html create mode 100644 examples/solid/createAsyncBatcher/package.json create mode 100644 examples/solid/createAsyncBatcher/public/emblem-light.svg create mode 100644 examples/solid/createAsyncBatcher/src/index.tsx create mode 100644 examples/solid/createAsyncBatcher/tsconfig.json create mode 100644 examples/solid/createAsyncBatcher/vite.config.ts create mode 100644 packages/pacer/src/async-batcher.ts create mode 100644 packages/react-pacer/src/async-batcher/index.ts create mode 100644 packages/react-pacer/src/async-batcher/useAsyncBatcher.ts create mode 100644 packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts create mode 100644 packages/solid-pacer/src/async-batcher/index.ts diff --git a/docs/framework/solid/reference/functions/createbatcher.md b/docs/framework/solid/reference/functions/createbatcher.md index 974f7080c..dc1f96aaf 100644 --- a/docs/framework/solid/reference/functions/createbatcher.md +++ b/docs/framework/solid/reference/functions/createbatcher.md @@ -58,7 +58,7 @@ console.log('Items:', batcher.allItems()); console.log('Size:', batcher.size()); console.log('Is empty:', batcher.isEmpty()); console.log('Is running:', batcher.isRunning()); -console.log('Batch count:', batcher.batchExecutionCount()); +console.log('Batch count:', batcher.executionCount()); console.log('Item count:', batcher.itemExecutionCount()); ``` diff --git a/docs/guides/batching.md b/docs/guides/batching.md index c7b3a8a36..018a9ee2a 100644 --- a/docs/guides/batching.md +++ b/docs/guides/batching.md @@ -142,7 +142,7 @@ batcher.getSize() // Get current batch size batcher.getIsEmpty() // Check if batch is empty batcher.getIsRunning() // Check if batcher is running batcher.peekAllItems() // Get all items in the current batch -batcher.getBatchExecutionCount()// Number of batches processed +batcher.getexecutionCount()// Number of batches processed batcher.getItemExecutionCount() // Number of items processed batcher.setOptions(opts) // Update batcher options batcher.getOptions() // Get current options @@ -183,7 +183,7 @@ console.log(options.maxSize) // 10 Batcher provides methods to monitor its performance: ```ts -console.log(batcher.getBatchExecutionCount()) // Number of batches processed +console.log(batcher.getexecutionCount()) // Number of batches processed console.log(batcher.getItemExecutionCount()) // Number of items processed ``` diff --git a/examples/react/useAsyncBatcher/.eslintrc.cjs b/examples/react/useAsyncBatcher/.eslintrc.cjs new file mode 100644 index 000000000..9ff0b9fc9 --- /dev/null +++ b/examples/react/useAsyncBatcher/.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/useAsyncBatcher/.gitignore b/examples/react/useAsyncBatcher/.gitignore new file mode 100644 index 000000000..872082d17 --- /dev/null +++ b/examples/react/useAsyncBatcher/.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/useAsyncBatcher/README.md b/examples/react/useAsyncBatcher/README.md new file mode 100644 index 000000000..14522a7f2 --- /dev/null +++ b/examples/react/useAsyncBatcher/README.md @@ -0,0 +1,6 @@ +# useAsyncBatcher Example + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/useAsyncBatcher/index.html b/examples/react/useAsyncBatcher/index.html new file mode 100644 index 000000000..a1a24961b --- /dev/null +++ b/examples/react/useAsyncBatcher/index.html @@ -0,0 +1,16 @@ + + + + + + + + + TanStack Pacer useAsyncBatcher Example + + + +
+ + + diff --git a/examples/react/useAsyncBatcher/package.json b/examples/react/useAsyncBatcher/package.json new file mode 100644 index 000000000..92cbd787d --- /dev/null +++ b/examples/react/useAsyncBatcher/package.json @@ -0,0 +1,34 @@ +{ + "name": "@tanstack/pacer-example-react-use-async-batcher", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3005", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-pacer": "^0.8.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.2" + }, + "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/useAsyncBatcher/public/emblem-light.svg b/examples/react/useAsyncBatcher/public/emblem-light.svg new file mode 100644 index 000000000..16d574897 --- /dev/null +++ b/examples/react/useAsyncBatcher/public/emblem-light.svg @@ -0,0 +1,13 @@ + + + + emblem-light + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/react/useAsyncBatcher/src/index.tsx b/examples/react/useAsyncBatcher/src/index.tsx new file mode 100644 index 000000000..2e0d2a0ca --- /dev/null +++ b/examples/react/useAsyncBatcher/src/index.tsx @@ -0,0 +1,227 @@ +import { useState } from 'react' +import ReactDOM from 'react-dom/client' +import { useAsyncBatcher } from '@tanstack/react-pacer/async-batcher' + +const fakeProcessingTime = 1000 + +type Item = { + id: number + value: string + timestamp: number +} + +function App() { + // Use your state management library of choice + const [batchItems, setBatchItems] = useState>([]) + const [processedBatches, setProcessedBatches] = useState< + 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 { + console.log('Processing batch of', items.length, 'items:', items) + + // Simulate async processing time + 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`) + } + + // Return a result from the batch processing + const result = `Processed ${items.length} items: ${items.map((item) => item.value).join(', ')}` + + setProcessedBatches((prev) => [ + ...prev, + { items, result, timestamp: Date.now() }, + ]) + + return result + } + + const asyncBatcher = useAsyncBatcher(processBatch, { + maxSize: 5, // Process in batches of 3 (if reached before wait time) + wait: 2000, // Wait up to 2 seconds before processing a batch + getShouldExecute: (items) => + items.some((item) => item.value.includes('urgent')), // Process immediately if any item is marked urgent + throwOnError: false, // Don't throw errors, handle them via onError + onItemsChange: (batcher) => { + setBatchItems(batcher.peekAllItems()) + }, + onSuccess: (result, batcher) => { + console.log('Batch succeeded:', result) + console.log('Total successful batches:', batcher.store.state.successCount) + }, + onError: (error: any, _batcher) => { + console.error('Batch failed:', error) + setErrors((prev) => [ + ...prev, + `Error: ${error.message} (${new Date().toLocaleTimeString()})`, + ]) + }, + onSettled: (batcher) => { + console.log( + 'Batch settled. Total processed items:', + batcher.store.state.totalItemsProcessed, + ) + }, + }) + + const addItem = (isUrgent = false) => { + const nextId = Date.now() + const item: Item = { + id: nextId, + value: isUrgent ? `urgent-${nextId}` : `item-${nextId}`, + timestamp: nextId, + } + asyncBatcher.addItem(item) + } + + const executeCurrentBatch = async () => { + try { + const result = await asyncBatcher.execute() + console.log('Manual execution result:', result) + } catch (error) { + console.error('Manual execution failed:', error) + } + } + + return ( +
+

TanStack Pacer useAsyncBatcher Example

+ +
+

Batch Status

+
Current Batch Size: {asyncBatcher.state.size}
+
Max Batch Size: 5
+
Is Executing: {asyncBatcher.state.isExecuting ? 'Yes' : 'No'}
+
Is Running: {asyncBatcher.state.isRunning ? 'Yes' : 'No'}
+
Status: {asyncBatcher.state.status}
+
Successful Batches: {asyncBatcher.state.successCount}
+
Failed Batches: {asyncBatcher.state.errorCount}
+
+ Total Items Processed: {asyncBatcher.state.totalItemsProcessed} +
+
+ +
+

Current Batch Items

+
+ {batchItems.length === 0 ? ( + No items in current batch + ) : ( + batchItems.map((item, index) => ( +
+ {index + 1}: {item.value} (added at{' '} + {new Date(item.timestamp).toLocaleTimeString()}) +
+ )) + )} +
+
+ +
+

Controls

+
+ + + + + + +
+ +
+ +
+
+ +
+

Processed Batches ({processedBatches.length})

+
+ {processedBatches.length === 0 ? ( + No batches processed yet + ) : ( + processedBatches.map((batch, index) => ( +
+ Batch {index + 1} (processed at{' '} + {new Date(batch.timestamp).toLocaleTimeString()}) +
{batch.result}
+
+ )) + )} +
+
+ + {errors.length > 0 && ( +
+

Errors ({errors.length})

+
+ {errors.map((error, index) => ( +
{error}
+ ))} +
+ +
+ )} + +
+

How it works:

+
    +
  • Items are batched up to 3 at a time
  • +
  • Batches are processed after 2 seconds if not full
  • +
  • Items marked "urgent" trigger immediate processing
  • +
  • Processing takes 1 second to simulate async work
  • +
  • Each batch returns a result showing what was processed
  • +
  • Errors are handled gracefully and don't stop the batcher
  • +
+
+
+ ) +} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render() diff --git a/examples/react/useAsyncBatcher/tsconfig.json b/examples/react/useAsyncBatcher/tsconfig.json new file mode 100644 index 000000000..6e9088d67 --- /dev/null +++ b/examples/react/useAsyncBatcher/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/useAsyncBatcher/vite.config.ts b/examples/react/useAsyncBatcher/vite.config.ts new file mode 100644 index 000000000..4e1943662 --- /dev/null +++ b/examples/react/useAsyncBatcher/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/useBatcher/src/index.tsx b/examples/react/useBatcher/src/index.tsx index 22ad9a616..f2c022c21 100644 --- a/examples/react/useBatcher/src/index.tsx +++ b/examples/react/useBatcher/src/index.tsx @@ -31,8 +31,8 @@ function App1() {
Batch Size: {batcher.state.size}
Batch Max Size: {3}
Batch Items: {batchItems.join(', ')}
-
Batches Processed: {batcher.state.batchExecutionCount}
-
Items Processed: {batcher.state.itemExecutionCount}
+
Batches Processed: {batcher.state.executionCount}
+
Items Processed: {batcher.state.totalItemsProcessed}
Processed Batches:{' '} {processedBatches.map((b, i) => ( diff --git a/examples/solid/createAsyncBatcher/.gitignore b/examples/solid/createAsyncBatcher/.gitignore new file mode 100644 index 000000000..872082d17 --- /dev/null +++ b/examples/solid/createAsyncBatcher/.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/solid/createAsyncBatcher/README.md b/examples/solid/createAsyncBatcher/README.md new file mode 100644 index 000000000..1f6128ebf --- /dev/null +++ b/examples/solid/createAsyncBatcher/README.md @@ -0,0 +1,6 @@ +# createAsyncBatcher Example + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/createAsyncBatcher/index.html b/examples/solid/createAsyncBatcher/index.html new file mode 100644 index 000000000..a0413a0cf --- /dev/null +++ b/examples/solid/createAsyncBatcher/index.html @@ -0,0 +1,16 @@ + + + + + + + + + TanStack Pacer createAsyncBatcher Example + + + +
+ + + diff --git a/examples/solid/createAsyncBatcher/package.json b/examples/solid/createAsyncBatcher/package.json new file mode 100644 index 000000000..095a0aec6 --- /dev/null +++ b/examples/solid/createAsyncBatcher/package.json @@ -0,0 +1,31 @@ +{ + "name": "@tanstack/pacer-example-solid-create-async-batcher", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3005", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/solid-pacer": "^0.8.0", + "solid-js": "^1.9.7" + }, + "devDependencies": { + "vite": "^7.0.2", + "vite-plugin-solid": "^2.11.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/solid/createAsyncBatcher/public/emblem-light.svg b/examples/solid/createAsyncBatcher/public/emblem-light.svg new file mode 100644 index 000000000..16d574897 --- /dev/null +++ b/examples/solid/createAsyncBatcher/public/emblem-light.svg @@ -0,0 +1,13 @@ + + + + emblem-light + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/solid/createAsyncBatcher/src/index.tsx b/examples/solid/createAsyncBatcher/src/index.tsx new file mode 100644 index 000000000..d7529c034 --- /dev/null +++ b/examples/solid/createAsyncBatcher/src/index.tsx @@ -0,0 +1,224 @@ +import { For, createSignal } from 'solid-js' +import { render } from 'solid-js/web' +import { createAsyncBatcher } from '@tanstack/solid-pacer/async-batcher' + +const fakeProcessingTime = 1000 + +type Item = { + id: number + value: string + timestamp: number +} + +function App() { + // Use your state management library of choice + const [processedBatches, setProcessedBatches] = createSignal< + Array<{ items: Array; result: string; timestamp: number }> + >([]) + const [errors, setErrors] = createSignal>([]) + const [shouldFail, setShouldFail] = createSignal(false) + + // The async function that will process a batch of items + async function processBatch(items: Array): Promise { + console.log('Processing batch of', items.length, 'items:', items) + + // Simulate async processing time + 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`) + } + + // Return a result from the batch processing + const result = `Processed ${items.length} items: ${items.map((item) => item.value).join(', ')}` + + setProcessedBatches((prev) => [ + ...prev, + { items, result, timestamp: Date.now() }, + ]) + + return result + } + + const batcher = createAsyncBatcher(processBatch, { + maxSize: 5, // Process in batches of 5 (if reached before wait time) + wait: 2000, // Wait up to 2 seconds before processing a batch + getShouldExecute: (items) => + items.some((item) => item.value.includes('urgent')), // Process immediately if any item is marked urgent + throwOnError: false, // Don't throw errors, handle them via onError + onSuccess: (result, batcher) => { + console.log('Batch succeeded:', result) + console.log('Total successful batches:', batcher.store.state.successCount) + }, + onError: (error: any, _batcher) => { + console.error('Batch failed:', error) + setErrors((prev) => [ + ...prev, + `Error: ${error.message} (${new Date().toLocaleTimeString()})`, + ]) + }, + onSettled: (batcher) => { + console.log( + 'Batch settled. Total processed items:', + batcher.store.state.totalItemsProcessed, + ) + }, + }) + + const addItem = (isUrgent = false) => { + const nextId = Date.now() + const item: Item = { + id: nextId, + value: isUrgent ? `urgent-${nextId}` : `item-${nextId}`, + timestamp: nextId, + } + batcher.addItem(item) + } + + const executeCurrentBatch = async () => { + try { + const result = await batcher.execute() + console.log('Manual execution result:', result) + } catch (error) { + console.error('Manual execution failed:', error) + } + } + + return ( +
+

TanStack Pacer createAsyncBatcher Example

+ +
+

Batch Status

+
Current Batch Size: {batcher.state().size}
+
Max Batch Size: 5
+
Is Executing: {batcher.state().isExecuting ? 'Yes' : 'No'}
+
Is Running: {batcher.state().isRunning ? 'Yes' : 'No'}
+
Status: {batcher.state().status}
+
Successful Batches: {batcher.state().successCount}
+
Failed Batches: {batcher.state().errorCount}
+
Total Items Processed: {batcher.state().totalItemsProcessed}
+
+ +
+

Current Batch Items

+
+ {batcher.state().items.length === 0 ? ( + No items in current batch + ) : ( + + {(item, index) => ( +
+ {index() + 1}: {item.value} (added at{' '} + {new Date(item.timestamp).toLocaleTimeString()}) +
+ )} +
+ )} +
+
+ +
+

Controls

+
+ + + + + + +
+ +
+ +
+
+ +
+

Processed Batches ({processedBatches().length})

+
+ {processedBatches().length === 0 ? ( + No batches processed yet + ) : ( + + {(batch, index) => ( +
+ Batch {index() + 1} (processed at{' '} + {new Date(batch.timestamp).toLocaleTimeString()}) +
{batch.result}
+
+ )} +
+ )} +
+
+ + {errors().length > 0 && ( +
+

Errors ({errors().length})

+
+ + {(error, index) => ( +
+ {index() + 1}: {error} +
+ )} +
+
+ +
+ )} + +
+

How it works:

+
    +
  • Items are batched up to 5 at a time
  • +
  • Batches are processed after 2 seconds if not full
  • +
  • Items marked "urgent" trigger immediate processing
  • +
  • Processing takes 1 second to simulate async work
  • +
  • Each batch returns a result showing what was processed
  • +
  • Errors are handled gracefully and don't stop the batcher
  • +
+
+
+ ) +} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/createAsyncBatcher/tsconfig.json b/examples/solid/createAsyncBatcher/tsconfig.json new file mode 100644 index 000000000..a0784796d --- /dev/null +++ b/examples/solid/createAsyncBatcher/tsconfig.json @@ -0,0 +1,24 @@ +{ + "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": "preserve", + "jsxImportSource": "solid-js", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/createAsyncBatcher/vite.config.ts b/examples/solid/createAsyncBatcher/vite.config.ts new file mode 100644 index 000000000..6d47c41e0 --- /dev/null +++ b/examples/solid/createAsyncBatcher/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [solid({})], +}) diff --git a/examples/solid/createBatcher/src/index.tsx b/examples/solid/createBatcher/src/index.tsx index d62831aaa..564d5fbcc 100644 --- a/examples/solid/createBatcher/src/index.tsx +++ b/examples/solid/createBatcher/src/index.tsx @@ -26,8 +26,8 @@ function App1() {
Batch Size: {batcher.state().size}
Batch Max Size: {5}
Batch Items: {batcher.state().items.join(', ')}
-
Batches Processed: {batcher.state().batchExecutionCount}
-
Items Processed: {batcher.state().itemExecutionCount}
+
Batches Processed: {batcher.state().executionCount}
+
Items Processed: {batcher.state().totalItemsProcessed}
Processed Batches:{' '} diff --git a/packages/pacer/package.json b/packages/pacer/package.json index ae943bc51..63e89f25d 100644 --- a/packages/pacer/package.json +++ b/packages/pacer/package.json @@ -37,6 +37,16 @@ "default": "./dist/cjs/index.cjs" } }, + "./async-batcher": { + "import": { + "types": "./dist/esm/async-batcher.d.ts", + "default": "./dist/esm/async-batcher.js" + }, + "require": { + "types": "./dist/cjs/async-batcher.d.cts", + "default": "./dist/cjs/async-batcher.cjs" + } + }, "./async-debouncer": { "import": { "types": "./dist/esm/async-debouncer.d.ts", diff --git a/packages/pacer/src/async-batcher.ts b/packages/pacer/src/async-batcher.ts new file mode 100644 index 000000000..d966ee98a --- /dev/null +++ b/packages/pacer/src/async-batcher.ts @@ -0,0 +1,390 @@ +import { Store } from '@tanstack/store' +import type { OptionalKeys } from './types' + +export interface AsyncBatcherState { + errorCount: number + isEmpty: boolean + isExecuting: boolean + isPending: boolean + isRunning: boolean + totalItemsProcessed: number + items: Array + lastResult: any + settleCount: number + size: number + status: 'idle' | 'pending' + successCount: number +} + +function getDefaultAsyncBatcherState(): AsyncBatcherState { + return { + errorCount: 0, + isEmpty: true, + isExecuting: false, + isPending: false, + isRunning: true, + totalItemsProcessed: 0, + items: [], + lastResult: undefined, + settleCount: 0, + size: 0, + status: 'idle', + successCount: 0, + } +} + +/** + * Options for configuring an AsyncBatcher instance + */ +export interface AsyncBatcherOptions { + /** + * Custom function to determine if a batch should be processed + * Return true to process the batch immediately + */ + getShouldExecute?: ( + items: Array, + batcher: AsyncBatcher, + ) => boolean + /** + * Initial state for the async batcher + */ + initialState?: Partial> + /** + * Maximum number of items in a batch + * @default Infinity + */ + maxSize?: number + /** + * Optional error handler for when the batch function throws. + * If provided, the handler will be called with the error and batcher instance. + * This can be used alongside throwOnError - the handler will be called before any error is thrown. + */ + onError?: (error: unknown, batcher: AsyncBatcher) => void + /** + * Callback fired after a batch is processed + */ + onExecute?: (batcher: AsyncBatcher) => void + /** + * Callback fired after items are added to the batcher + */ + onItemsChange?: (batcher: AsyncBatcher) => void + /** + * Optional callback to call when a batch is settled (completed or failed) + */ + onSettled?: (batcher: AsyncBatcher) => void + /** + * Optional callback to call when a batch succeeds + */ + onSuccess?: (result: any, batcher: AsyncBatcher) => void + /** + * Whether the batcher should start processing immediately + * @default true + */ + started?: boolean + /** + * Whether to throw errors when they occur. + * Defaults to true if no onError handler is provided, false if an onError handler is provided. + * Can be explicitly set to override these defaults. + */ + throwOnError?: boolean + /** + * Maximum time in milliseconds to wait before processing a batch. + * If the wait duration has elapsed, the batch will be processed. + * If not provided, the batch will not be triggered by a timeout. + * @default Infinity + */ + wait?: number +} + +type AsyncBatcherOptionsWithOptionalCallbacks = OptionalKeys< + Required>, + | 'initialState' + | 'onError' + | 'onExecute' + | 'onItemsChange' + | 'onSettled' + | 'onSuccess' +> + +const defaultOptions: AsyncBatcherOptionsWithOptionalCallbacks = { + getShouldExecute: () => false, + maxSize: Infinity, + started: true, + throwOnError: true, + wait: Infinity, +} + +/** + * 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 + * + * 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: + * - Maximum batch size (number of items per batch) + * - Time-based batching (process after X milliseconds) + * - Custom batch processing logic via getShouldExecute + * - Event callbacks for monitoring batch operations + * - Error handling for failed batch operations + * + * Error Handling: + * - If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + * + * State Management: + * - Use `initialState` to provide initial state values when creating the async batcher + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes total items processed, success/error counts, and execution status + * - State can be retrieved using `getState()` method + * + * @example + * ```ts + * const batcher = new AsyncBatcher( + * async (items) => { + * const result = await processItems(items); + * console.log('Processing batch:', items); + * return result; + * }, + * { + * maxSize: 5, + * wait: 2000, + * onSuccess: (result) => console.log('Batch succeeded:', result), + * onError: (error) => console.error('Batch failed:', error) + * } + * ); + * + * batcher.addItem(1); + * batcher.addItem(2); + * // After 2 seconds or when 5 items are added, whichever comes first, + * // the batch will be processed and the result will be available + * // batcher.execute() // manually trigger a batch + * ``` + */ +export class AsyncBatcher { + readonly store: Store> = new Store( + getDefaultAsyncBatcherState(), + ) + #options: AsyncBatcherOptionsWithOptionalCallbacks + #timeoutId: NodeJS.Timeout | null = null + + constructor( + private fn: (items: Array) => Promise, + initialOptions: AsyncBatcherOptions, + ) { + this.#options = { + ...defaultOptions, + ...initialOptions, + throwOnError: initialOptions.throwOnError ?? !initialOptions.onError, + } + this.#setState(this.#options.initialState ?? {}) + } + + /** + * Updates the async batcher options + */ + setOptions = (newOptions: Partial>): void => { + this.#options = { ...this.#options, ...newOptions } + } + + #setState = (newState: Partial>): void => { + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + const { isPending, items } = combinedState + const size = items.length + const isEmpty = size === 0 + return { + ...combinedState, + isEmpty, + size, + status: isPending ? 'pending' : 'idle', + } + }) + } + + /** + * Adds an item to the async batcher + * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed + */ + addItem = (item: TValue): void => { + this.#setState({ + items: [...this.store.state.items, item], + isPending: this.#options.wait !== Infinity, + }) + this.#options.onItemsChange?.(this) + + const shouldProcess = + this.store.state.items.length >= this.#options.maxSize || + this.#options.getShouldExecute(this.store.state.items, this) + + if (shouldProcess) { + this.execute() + } else if ( + this.store.state.isRunning && + !this.#timeoutId && + this.#options.wait !== Infinity + ) { + this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) + } + } + + /** + * Processes the current batch of items asynchronously. + * This method will automatically be triggered if the batcher is running and any of these conditions are met: + * - The number of items reaches maxSize + * - The wait duration has elapsed + * - The getShouldExecute function returns true upon adding an item + * + * You can also call this method manually to process the current batch at any time. + * + * @returns A promise that resolves with the result of 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 + */ + execute = async (): Promise => { + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null + } + + if (this.store.state.items.length === 0) { + return undefined + } + + 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) // Call onItemsChange to notify listeners that the items have changed + + this.#setState({ isExecuting: true }) + + try { + const result = await this.fn(batch) // EXECUTE + this.#setState({ + totalItemsProcessed: + this.store.state.totalItemsProcessed + batch.length, + lastResult: result, + successCount: this.store.state.successCount + 1, + }) + this.#options.onSuccess?.(result, this) + return result + } catch (error) { + this.#setState({ + errorCount: this.store.state.errorCount + 1, + }) + this.#options.onError?.(error, this) + if (this.#options.throwOnError) { + throw error + } + return undefined + } finally { + this.#setState({ + isExecuting: false, + settleCount: this.store.state.settleCount + 1, + }) + this.#options.onSettled?.(this) + this.#options.onExecute?.(this) + } + } + + /** + * Stops the async batcher from processing batches + */ + stop = (): void => { + this.#setState({ isRunning: false }) + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null + } + } + + /** + * Starts the async batcher and processes any pending items + */ + start = (): void => { + this.#setState({ isRunning: true }) + if (this.store.state.items.length > 0 && !this.#timeoutId) { + this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) + } + } + + /** + * Returns a copy of all items in the async batcher + */ + peekAllItems = (): Array => { + return [...this.store.state.items] + } + + /** + * Removes all items from the async batcher + */ + clear = (): void => { + this.#setState({ items: [], isPending: false }) + } + + /** + * Resets the async batcher state to its default values + */ + reset = (): void => { + this.#setState(getDefaultAsyncBatcherState()) + } +} + +/** + * 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 + * + * Error Handling: + * - If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + * + * State Management: + * - Use `initialState` to provide initial state values when creating the async batcher + * - Use `onStateChange` callback to react to state changes and implement custom persistence + * - The state includes total items processed, success/error counts, and execution status + * + * @example + * ```ts + * const batchItems = asyncBatch( + * async (items) => { + * const result = await processApiCall(items); + * console.log('Processing:', items); + * return result; + * }, + * { + * maxSize: 3, + * wait: 1000, + * onSuccess: (result) => console.log('Batch succeeded:', result), + * onError: (error) => console.error('Batch failed:', error) + * } + * ); + * + * batchItems(1); + * batchItems(2); + * batchItems(3); // Triggers batch processing + * ``` + */ +export function asyncBatch( + fn: (items: Array) => Promise, + options: AsyncBatcherOptions, +) { + const batcher = new AsyncBatcher(fn, options) + return batcher.addItem +} diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index d97e7dacc..56825a9b1 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -2,11 +2,11 @@ import { Store } from '@tanstack/store' import type { OptionalKeys } from './types' export interface BatcherState { - batchExecutionCount: number + executionCount: number isEmpty: boolean isPending: boolean isRunning: boolean - itemExecutionCount: number + totalItemsProcessed: number items: Array size: number status: 'idle' | 'pending' @@ -14,11 +14,11 @@ export interface BatcherState { function getDefaultBatcherState(): BatcherState { return { - batchExecutionCount: 0, + executionCount: 0, isEmpty: true, isPending: false, isRunning: true, - itemExecutionCount: 0, + totalItemsProcessed: 0, items: [], size: 0, status: 'idle', @@ -91,7 +91,7 @@ const defaultOptions: BatcherOptionsWithOptionalCallbacks = { * State Management: * - Use `initialState` to provide initial state values when creating the batcher * - Use `onStateChange` callback to react to state changes and implement custom persistence - * - The state includes batch execution count, item execution count, items, and running status + * - The state includes batch execution count, total items processed, items, and running status * - State can be retrieved using `getState()` method * * @example @@ -206,8 +206,8 @@ export class Batcher { this.fn(batch) // EXECUTE this.#setState({ - batchExecutionCount: this.store.state.batchExecutionCount + 1, - itemExecutionCount: this.store.state.itemExecutionCount + batch.length, + executionCount: this.store.state.executionCount + 1, + totalItemsProcessed: this.store.state.totalItemsProcessed + batch.length, }) this.#options.onExecute?.(this) } diff --git a/packages/pacer/src/index.ts b/packages/pacer/src/index.ts index 77e6ffe9e..194ea6f7f 100644 --- a/packages/pacer/src/index.ts +++ b/packages/pacer/src/index.ts @@ -1,3 +1,4 @@ +export * from './async-batcher' export * from './async-debouncer' export * from './async-queuer' export * from './async-rate-limiter' diff --git a/packages/react-pacer/package.json b/packages/react-pacer/package.json index ce0738125..dfc0897af 100644 --- a/packages/react-pacer/package.json +++ b/packages/react-pacer/package.json @@ -37,6 +37,16 @@ "default": "./dist/cjs/index.cjs" } }, + "./async-batcher": { + "import": { + "types": "./dist/esm/async-batcher/index.d.ts", + "default": "./dist/esm/async-batcher/index.js" + }, + "require": { + "types": "./dist/cjs/async-batcher/index.d.cts", + "default": "./dist/cjs/async-batcher/index.cjs" + } + }, "./async-debouncer": { "import": { "types": "./dist/esm/async-debouncer/index.d.ts", diff --git a/packages/react-pacer/src/async-batcher/index.ts b/packages/react-pacer/src/async-batcher/index.ts new file mode 100644 index 000000000..f0fe9c2be --- /dev/null +++ b/packages/react-pacer/src/async-batcher/index.ts @@ -0,0 +1,3 @@ +export * from '@tanstack/pacer/async-batcher' + +export * from './useAsyncBatcher' diff --git a/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts b/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts new file mode 100644 index 000000000..177412720 --- /dev/null +++ b/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts @@ -0,0 +1,96 @@ +import { useMemo, useState } from 'react' +import { AsyncBatcher } from '@tanstack/pacer/async-batcher' +import { useStore } from '@tanstack/react-store' +import type { + AsyncBatcherOptions, + AsyncBatcherState, +} from '@tanstack/pacer/async-batcher' + +export interface ReactAsyncBatcher< + TValue, + TSelected = AsyncBatcherState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the batcher state changes + * + * Use this instead of `batcher.store.state` + */ + readonly state: TSelected +} + +/** + * A React hook that creates an `AsyncBatcher` instance for managing asynchronous batches of items. + * + * This is the async version of the useBatcher hook. 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 + * + * Features: + * - Configurable batch size and wait time + * - Custom batch processing logic via getShouldExecute + * - Event callbacks for monitoring batch operations + * - Error handling for failed batch operations + * - Automatic or manual batch processing + * + * The batcher collects items and processes them in batches based on: + * - Maximum batch size (number of items per batch) + * - Time-based batching (process after X milliseconds) + * - Custom batch processing logic via getShouldExecute + * + * Error Handling: + * - If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + * + * @example + * ```tsx + * // Basic async batcher for API requests + * const asyncBatcher = useAsyncBatcher( + * async (items) => { + * const results = await Promise.all(items.map(item => processItem(item))); + * return results; + * }, + * { + * maxSize: 10, + * wait: 2000, + * onSuccess: (result) => { + * console.log('Batch processed successfully:', result); + * }, + * onError: (error) => { + * console.error('Batch processing failed:', error); + * } + * } + * ); + * + * // Add items to batch + * asyncBatcher.addItem(newItem); + * + * // Manually execute batch + * const result = await asyncBatcher.execute(); + * ``` + */ +export function useAsyncBatcher>( + fn: (items: Array) => Promise, + options: AsyncBatcherOptions = {}, + selector?: (state: AsyncBatcherState) => TSelected, +): ReactAsyncBatcher { + const [asyncBatcher] = useState(() => new AsyncBatcher(fn, options)) + + const state = useStore(asyncBatcher.store, selector) + + asyncBatcher.setOptions(options) + + return useMemo( + () => + ({ + ...asyncBatcher, + state, + }) as unknown as ReactAsyncBatcher, // omit `store` in favor of `state` + [asyncBatcher, state], + ) +} diff --git a/packages/react-pacer/src/index.ts b/packages/react-pacer/src/index.ts index f1c4a7d1b..aabb18883 100644 --- a/packages/react-pacer/src/index.ts +++ b/packages/react-pacer/src/index.ts @@ -6,7 +6,7 @@ export * from '@tanstack/pacer' */ // async-batcher -// export * from './async-batcher/useAsyncBatcher' +export * from './async-batcher/useAsyncBatcher' // async-debouncer export * from './async-debouncer/useAsyncDebouncer' diff --git a/packages/react-pacer/vite.config.ts b/packages/react-pacer/vite.config.ts index b5617ae61..2657f80b3 100644 --- a/packages/react-pacer/vite.config.ts +++ b/packages/react-pacer/vite.config.ts @@ -19,6 +19,7 @@ export default mergeConfig( config, tanstackViteConfig({ entry: [ + './src/async-batcher/index.ts', './src/async-debouncer/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', diff --git a/packages/solid-pacer/package.json b/packages/solid-pacer/package.json index cd87102d0..f3e9c34bf 100644 --- a/packages/solid-pacer/package.json +++ b/packages/solid-pacer/package.json @@ -37,6 +37,16 @@ "default": "./dist/cjs/index.cjs" } }, + "./async-batcher": { + "import": { + "types": "./dist/esm/async-batcher/index.d.ts", + "default": "./dist/esm/async-batcher/index.js" + }, + "require": { + "types": "./dist/cjs/async-batcher/index.d.cts", + "default": "./dist/cjs/async-batcher/index.cjs" + } + }, "./async-debouncer": { "import": { "types": "./dist/esm/async-debouncer/index.d.ts", diff --git a/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts b/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts new file mode 100644 index 000000000..d6d3a1c3e --- /dev/null +++ b/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts @@ -0,0 +1,98 @@ +import { AsyncBatcher } from '@tanstack/pacer/async-batcher' +import { useStore } from '@tanstack/solid-store' +import type { Accessor } from 'solid-js' +import type { + AsyncBatcherOptions, + AsyncBatcherState, +} from '@tanstack/pacer/async-batcher' + +export interface SolidAsyncBatcher< + TValue, + TSelected = AsyncBatcherState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated when the batcher state changes + * + * Use this instead of `batcher.store.state` + */ + readonly state: Accessor +} + +/** + * Creates a Solid-compatible AsyncBatcher instance for managing asynchronous batches of items, exposing Solid signals for all stateful properties. + * + * This is the async version of the createBatcher hook. 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 + * + * Features: + * - Configurable batch size and wait time + * - Custom batch processing logic via getShouldExecute + * - Event callbacks for monitoring batch operations + * - Error handling for failed batch operations + * - Automatic or manual batch processing + * - All stateful properties (items, counts, etc.) are exposed as Solid signals for reactivity + * + * The batcher collects items and processes them in batches based on: + * - Maximum batch size (number of items per batch) + * - Time-based batching (process after X milliseconds) + * - Custom batch processing logic via getShouldExecute + * + * Error Handling: + * - If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + * + * Example usage: + * ```tsx + * // Basic async batcher for API requests + * const asyncBatcher = createAsyncBatcher( + * async (items) => { + * const results = await Promise.all(items.map(item => processItem(item))); + * return results; + * }, + * { + * maxSize: 10, + * wait: 2000, + * onSuccess: (result) => { + * console.log('Batch processed successfully:', result); + * }, + * onError: (error) => { + * console.error('Batch processing failed:', error); + * } + * } + * ); + * + * // Add items to batch + * asyncBatcher.addItem(newItem); + * + * // Manually execute batch + * const result = await asyncBatcher.execute(); + * + * // Use Solid signals in your UI + * const items = asyncBatcher.state().items; + * const isExecuting = asyncBatcher.state().isExecuting; + * ``` + */ +export function createAsyncBatcher< + TValue, + TSelected = AsyncBatcherState, +>( + fn: (items: Array) => Promise, + initialOptions: AsyncBatcherOptions = {}, + selector?: (state: AsyncBatcherState) => TSelected, +): SolidAsyncBatcher { + const asyncBatcher = new AsyncBatcher(fn, initialOptions) + + const state = useStore(asyncBatcher.store, selector) + + return { + ...asyncBatcher, + state, + } as unknown as SolidAsyncBatcher // omit `store` in favor of `state` +} diff --git a/packages/solid-pacer/src/async-batcher/index.ts b/packages/solid-pacer/src/async-batcher/index.ts new file mode 100644 index 000000000..7252cd9f2 --- /dev/null +++ b/packages/solid-pacer/src/async-batcher/index.ts @@ -0,0 +1,3 @@ +export * from '@tanstack/pacer/async-batcher' + +export * from './createAsyncBatcher' diff --git a/packages/solid-pacer/src/batcher/createBatcher.ts b/packages/solid-pacer/src/batcher/createBatcher.ts index fea6d63d8..7f37e1877 100644 --- a/packages/solid-pacer/src/batcher/createBatcher.ts +++ b/packages/solid-pacer/src/batcher/createBatcher.ts @@ -56,8 +56,8 @@ export interface SolidBatcher> * console.log('Size:', batcher.size()); * console.log('Is empty:', batcher.isEmpty()); * console.log('Is running:', batcher.isRunning()); - * console.log('Batch count:', batcher.batchExecutionCount()); - * console.log('Item count:', batcher.itemExecutionCount()); + * console.log('Batch count:', batcher.executionCount()); + * console.log('Item count:', batcher.totalItemsProcessed()); * ``` */ export function createBatcher>( diff --git a/packages/solid-pacer/src/index.ts b/packages/solid-pacer/src/index.ts index c88707a8f..9ad6c4de4 100644 --- a/packages/solid-pacer/src/index.ts +++ b/packages/solid-pacer/src/index.ts @@ -5,6 +5,9 @@ export * from '@tanstack/pacer' * Export every hook individually - DON'T export from barrel files */ +// async-batcher +export * from './async-batcher/createAsyncBatcher' + // async-debouncer export * from './async-debouncer/createAsyncDebouncer' diff --git a/packages/solid-pacer/vite.config.ts b/packages/solid-pacer/vite.config.ts index a0ae9fea1..db733e3f1 100644 --- a/packages/solid-pacer/vite.config.ts +++ b/packages/solid-pacer/vite.config.ts @@ -19,6 +19,7 @@ export default mergeConfig( config, tanstackViteConfig({ entry: [ + './src/async-batcher/index.ts', './src/async-debouncer/index.ts', './src/async-queuer/index.ts', './src/async-rate-limiter/index.ts', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aaf4f0cb0..24418c7da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -371,6 +371,31 @@ importers: specifier: ^7.0.2 version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + examples/react/useAsyncBatcher: + dependencies: + '@tanstack/react-pacer': + specifier: ^0.8.0 + version: link:../../../packages/react-pacer + react: + specifier: ^19.1.0 + version: 19.1.0 + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + devDependencies: + '@types/react': + specifier: ^19.1.8 + version: 19.1.8 + '@types/react-dom': + specifier: ^19.1.6 + version: 19.1.6(@types/react@19.1.8) + '@vitejs/plugin-react': + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite: + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + examples/react/useAsyncDebouncer: dependencies: '@tanstack/react-pacer': @@ -969,6 +994,22 @@ importers: specifier: ^2.11.7 version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + examples/solid/createAsyncBatcher: + dependencies: + '@tanstack/solid-pacer': + specifier: ^0.8.0 + version: link:../../../packages/solid-pacer + solid-js: + specifier: ^1.9.7 + version: 1.9.7 + devDependencies: + vite: + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite-plugin-solid: + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + examples/solid/createAsyncDebouncer: dependencies: '@tanstack/solid-pacer': From 85a028b43c3f634ceb422da2ebdf098f188eaea0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 04:27:59 +0000 Subject: [PATCH 35/51] ci: apply automated fixes --- .../reference/functions/createasyncbatcher.md | 101 ++++++++++++++++++ .../reference/functions/createbatcher.md | 2 +- docs/framework/solid/reference/index.md | 2 + .../reference/interfaces/solidasyncbatcher.md | 34 ++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 docs/framework/solid/reference/functions/createasyncbatcher.md create mode 100644 docs/framework/solid/reference/interfaces/solidasyncbatcher.md diff --git a/docs/framework/solid/reference/functions/createasyncbatcher.md b/docs/framework/solid/reference/functions/createasyncbatcher.md new file mode 100644 index 000000000..847b4ea73 --- /dev/null +++ b/docs/framework/solid/reference/functions/createasyncbatcher.md @@ -0,0 +1,101 @@ +--- +id: createAsyncBatcher +title: createAsyncBatcher +--- + + + +# Function: createAsyncBatcher() + +```ts +function createAsyncBatcher( + fn, + initialOptions, +selector?): SolidAsyncBatcher +``` + +Defined in: [async-batcher/createAsyncBatcher.ts:82](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L82) + +Creates a Solid-compatible AsyncBatcher instance for managing asynchronous batches of items, exposing Solid signals for all stateful properties. + +This is the async version of the createBatcher hook. 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 + +Features: +- Configurable batch size and wait time +- Custom batch processing logic via getShouldExecute +- Event callbacks for monitoring batch operations +- Error handling for failed batch operations +- Automatic or manual batch processing +- All stateful properties (items, counts, etc.) are exposed as Solid signals for reactivity + +The batcher collects items and processes them in batches based on: +- Maximum batch size (number of items per batch) +- Time-based batching (process after X milliseconds) +- Custom batch processing logic via getShouldExecute + +Error Handling: +- If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + +Example usage: +```tsx +// Basic async batcher for API requests +const asyncBatcher = createAsyncBatcher( + async (items) => { + const results = await Promise.all(items.map(item => processItem(item))); + return results; + }, + { + maxSize: 10, + wait: 2000, + onSuccess: (result) => { + console.log('Batch processed successfully:', result); + }, + onError: (error) => { + console.error('Batch processing failed:', error); + } + } +); + +// Add items to batch +asyncBatcher.addItem(newItem); + +// Manually execute batch +const result = await asyncBatcher.execute(); + +// Use Solid signals in your UI +const items = asyncBatcher.state().items; +const isExecuting = asyncBatcher.state().isExecuting; +``` + +## Type Parameters + +• **TValue** + +• **TSelected** = `AsyncBatcherState`\<`TValue`\> + +## Parameters + +### fn + +(`items`) => `Promise`\<`any`\> + +### initialOptions + +`AsyncBatcherOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`SolidAsyncBatcher`](../../interfaces/solidasyncbatcher.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/solid/reference/functions/createbatcher.md b/docs/framework/solid/reference/functions/createbatcher.md index dc1f96aaf..ca51efc8b 100644 --- a/docs/framework/solid/reference/functions/createbatcher.md +++ b/docs/framework/solid/reference/functions/createbatcher.md @@ -59,7 +59,7 @@ console.log('Size:', batcher.size()); console.log('Is empty:', batcher.isEmpty()); console.log('Is running:', batcher.isRunning()); console.log('Batch count:', batcher.executionCount()); -console.log('Item count:', batcher.itemExecutionCount()); +console.log('Item count:', batcher.totalItemsProcessed()); ``` ## Type Parameters diff --git a/docs/framework/solid/reference/index.md b/docs/framework/solid/reference/index.md index 875ebdcfd..5ad4b11c6 100644 --- a/docs/framework/solid/reference/index.md +++ b/docs/framework/solid/reference/index.md @@ -9,6 +9,7 @@ title: "@tanstack/solid-pacer" ## Interfaces +- [SolidAsyncBatcher](../interfaces/solidasyncbatcher.md) - [SolidAsyncDebouncer](../interfaces/solidasyncdebouncer.md) - [SolidAsyncQueuer](../interfaces/solidasyncqueuer.md) - [SolidAsyncRateLimiter](../interfaces/solidasyncratelimiter.md) @@ -21,6 +22,7 @@ title: "@tanstack/solid-pacer" ## Functions +- [createAsyncBatcher](../functions/createasyncbatcher.md) - [createAsyncDebouncer](../functions/createasyncdebouncer.md) - [createAsyncQueuer](../functions/createasyncqueuer.md) - [createAsyncRateLimiter](../functions/createasyncratelimiter.md) diff --git a/docs/framework/solid/reference/interfaces/solidasyncbatcher.md b/docs/framework/solid/reference/interfaces/solidasyncbatcher.md new file mode 100644 index 000000000..7cbb950f7 --- /dev/null +++ b/docs/framework/solid/reference/interfaces/solidasyncbatcher.md @@ -0,0 +1,34 @@ +--- +id: SolidAsyncBatcher +title: SolidAsyncBatcher +--- + + + +# Interface: SolidAsyncBatcher\ + +Defined in: [async-batcher/createAsyncBatcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L9) + +## Extends + +- `Omit`\<`AsyncBatcher`\<`TValue`\>, `"store"`\> + +## Type Parameters + +• **TValue** + +• **TSelected** = `AsyncBatcherState`\<`TValue`\> + +## Properties + +### state + +```ts +readonly state: Accessor; +``` + +Defined in: [async-batcher/createAsyncBatcher.ts:18](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L18) + +Reactive state that will be updated when the batcher state changes + +Use this instead of `batcher.store.state` From 97ea3844966fe0ac278e770ef199968155d3025f Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 8 Jul 2025 01:28:30 -0500 Subject: [PATCH 36/51] track failed batch items --- packages/pacer/src/async-batcher.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/pacer/src/async-batcher.ts b/packages/pacer/src/async-batcher.ts index d966ee98a..a7c0a2fa4 100644 --- a/packages/pacer/src/async-batcher.ts +++ b/packages/pacer/src/async-batcher.ts @@ -3,33 +3,37 @@ import type { OptionalKeys } from './types' export interface AsyncBatcherState { errorCount: number + failedItems: Array isEmpty: boolean isExecuting: boolean isPending: boolean isRunning: boolean - totalItemsProcessed: number items: Array lastResult: any settleCount: number size: number status: 'idle' | 'pending' successCount: number + totalItemsProcessed: number + totalItemsFailed: number } function getDefaultAsyncBatcherState(): AsyncBatcherState { return { errorCount: 0, + failedItems: [], isEmpty: true, isExecuting: false, isPending: false, isRunning: true, - totalItemsProcessed: 0, items: [], lastResult: undefined, settleCount: 0, size: 0, status: 'idle', successCount: 0, + totalItemsProcessed: 0, + totalItemsFailed: 0, } } @@ -59,7 +63,11 @@ export interface AsyncBatcherOptions { * If provided, the handler will be called with the error and batcher instance. * This can be used alongside throwOnError - the handler will be called before any error is thrown. */ - onError?: (error: unknown, batcher: AsyncBatcher) => void + onError?: ( + error: unknown, + failedItems: Array, + batcher: AsyncBatcher, + ) => void /** * Callback fired after a batch is processed */ @@ -280,8 +288,10 @@ export class AsyncBatcher { } catch (error) { this.#setState({ errorCount: this.store.state.errorCount + 1, + failedItems: [...this.store.state.failedItems, ...batch], + totalItemsFailed: this.store.state.totalItemsFailed + batch.length, }) - this.#options.onError?.(error, this) + this.#options.onError?.(error, batch, this) if (this.#options.throwOnError) { throw error } @@ -324,11 +334,15 @@ export class AsyncBatcher { return [...this.store.state.items] } + peekFailedItems = (): Array => { + return [...this.store.state.failedItems] + } + /** * Removes all items from the async batcher */ clear = (): void => { - this.#setState({ items: [], isPending: false }) + this.#setState({ items: [], failedItems: [], isPending: false }) } /** From 11f3c911359f9e243f32b4af27f832ea84104eef Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 8 Jul 2025 01:44:02 -0500 Subject: [PATCH 37/51] add flushing to utils --- packages/pacer/src/async-batcher.ts | 15 +++++++++++---- packages/pacer/src/async-debouncer.ts | 22 ++++++++++++++++++---- packages/pacer/src/async-queuer.ts | 13 +++++++++++++ packages/pacer/src/batcher.ts | 15 +++++++++++---- packages/pacer/src/debouncer.ts | 9 +++++++++ packages/pacer/src/queuer.ts | 13 +++++++++++++ packages/pacer/src/throttler.ts | 9 +++++++++ 7 files changed, 84 insertions(+), 12 deletions(-) diff --git a/packages/pacer/src/async-batcher.ts b/packages/pacer/src/async-batcher.ts index a7c0a2fa4..baac27fa5 100644 --- a/packages/pacer/src/async-batcher.ts +++ b/packages/pacer/src/async-batcher.ts @@ -237,13 +237,13 @@ export class AsyncBatcher { this.#options.getShouldExecute(this.store.state.items, this) if (shouldProcess) { - this.execute() + this.#execute() } else if ( this.store.state.isRunning && !this.#timeoutId && this.#options.wait !== Infinity ) { - this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) + this.#timeoutId = setTimeout(() => this.#execute(), this.#options.wait) } } @@ -259,7 +259,7 @@ export class AsyncBatcher { * @returns A promise that resolves with the result of 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 */ - execute = async (): Promise => { + #execute = async (): Promise => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = null @@ -306,6 +306,13 @@ export class AsyncBatcher { } } + /** + * Processes the current batch of items immediately + */ + flush = (): void => { + this.#execute() + } + /** * Stops the async batcher from processing batches */ @@ -323,7 +330,7 @@ export class AsyncBatcher { start = (): void => { this.#setState({ isRunning: true }) if (this.store.state.items.length > 0 && !this.#timeoutId) { - this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) + this.#timeoutId = setTimeout(() => this.#execute(), this.#options.wait) } } diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 15a6c4984..da992ade4 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -290,6 +290,16 @@ export class AsyncDebouncer { return this.store.state.lastResult } + /** + * Processes the current pending execution immediately + */ + flush = (): void => { + if (this.store.state.isPending && this.store.state.lastArgs) { + this.#abortExecution() // abort any current execution + this.#execute(...this.store.state.lastArgs) + } + } + #cancelPendingExecution = (): void => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) @@ -306,15 +316,19 @@ export class AsyncDebouncer { }) } + #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() - if (this.#abortController) { - this.#abortController.abort() - this.#abortController = null - } + this.#abortExecution() this.#setState({ canLeadingExecute: true }) } diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index ea8d34fba..78a87958b 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -492,6 +492,19 @@ export class AsyncQueuer { return item } + /** + * Processes a specified number of items to execute immediately with no wait time + * If no numberOfItems is provided, all items will be processed + */ + flush = ( + numberOfItems: number = this.store.state.items.length, + position?: QueuePosition, + ): void => { + for (let i = 0; i < numberOfItems; i++) { + this.execute(position) + } + } + /** * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. * Internal use only. diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index 56825a9b1..3c4a24b4d 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -171,13 +171,13 @@ export class Batcher { this.#options.getShouldExecute(this.store.state.items, this) if (shouldProcess) { - this.execute() + this.#execute() } else if ( this.store.state.isRunning && !this.#timeoutId && this.#options.wait !== Infinity ) { - this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) + this.#timeoutId = setTimeout(() => this.#execute(), this.#options.wait) } } @@ -190,7 +190,7 @@ export class Batcher { * * You can also call this method manually to process the current batch at any time. */ - execute = (): void => { + #execute = (): void => { if (this.#timeoutId) { clearTimeout(this.#timeoutId) this.#timeoutId = null @@ -212,6 +212,13 @@ export class Batcher { this.#options.onExecute?.(this) } + /** + * Processes the current batch of items immediately + */ + flush = (): void => { + this.#execute() + } + /** * Stops the batcher from processing batches */ @@ -229,7 +236,7 @@ export class Batcher { start = (): void => { this.#setState({ isRunning: true }) if (this.store.state.items.length > 0 && !this.#timeoutId) { - this.#timeoutId = setTimeout(() => this.execute(), this.#options.wait) + this.#timeoutId = setTimeout(() => this.#execute(), this.#options.wait) } } diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index 2209f11c0..725211463 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -199,6 +199,15 @@ export class Debouncer { this.#options.onExecute?.(this) } + /** + * Processes the current pending execution immediately + */ + flush = (): void => { + if (this.store.state.isPending && this.store.state.lastArgs) { + this.#execute(...this.store.state.lastArgs) + } + } + /** * Cancels any pending execution */ diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index d67e17ebf..580e1ac40 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -454,6 +454,19 @@ export class Queuer { return item } + /** + * Processes a specified number of items to execute immediately with no wait time + * If no numberOfItems is provided, all items will be processed + */ + flush = ( + numberOfItems: number = this.store.state.items.length, + position?: QueuePosition, + ): void => { + for (let i = 0; i < numberOfItems; i++) { + this.execute(position) + } + } + /** * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. * Internal use only. diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index 7f76ce009..ed1aa1399 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -224,6 +224,15 @@ export class Throttler { this.#options.onExecute?.(this) } + /** + * Processes the current pending execution immediately + */ + flush = (): void => { + if (this.store.state.isPending && this.store.state.lastArgs) { + this.#execute(...this.store.state.lastArgs) + } + } + /** * Cancels any pending trailing execution and clears internal state. * From b5d3408638ec3b056691629697d3d1ce38b082ad Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Tue, 8 Jul 2025 08:40:55 -0500 Subject: [PATCH 38/51] async batcher and flushing --- .../reference/functions/useasyncbatcher.md | 97 +++++ .../reference/functions/useasyncdebouncer.md | 94 +++++ .../functions/useasyncqueuedstate.md | 86 ++++ .../reference/functions/useasyncqueuer.md | 86 ++++ .../functions/useasyncratelimiter.md | 101 +++++ .../reference/functions/useasyncthrottler.md | 89 ++++ .../react/reference/functions/usebatcher.md | 78 ++++ .../functions/usedebouncedcallback.md | 87 ++++ .../reference/functions/usedebouncedstate.md | 74 ++++ .../reference/functions/usedebouncedvalue.md | 77 ++++ .../react/reference/functions/usedebouncer.md | 76 ++++ .../functions/uselocalstoragestate.md | 53 --- .../reference/functions/usequeuedstate.md | 90 ++++ .../reference/functions/usequeuedvalue.md | 76 ++++ .../react/reference/functions/usequeuer.md | 79 ++++ .../functions/useratelimitedcallback.md | 104 +++++ .../functions/useratelimitedstate.md | 99 +++++ .../functions/useratelimitedvalue.md | 88 ++++ .../reference/functions/useratelimiter.md | 89 ++++ .../functions/usesessionstoragestate.md | 53 --- .../functions/usestoragepersister.md | 28 -- .../functions/usethrottledcallback.md | 88 ++++ .../reference/functions/usethrottledstate.md | 75 ++++ .../reference/functions/usethrottledvalue.md | 68 ++++ .../react/reference/functions/usethrottler.md | 69 ++++ docs/framework/react/reference/index.md | 44 +- .../reference/interfaces/reactasyncbatcher.md | 34 ++ .../interfaces/reactasyncdebouncer.md | 34 ++ .../reference/interfaces/reactasyncqueuer.md | 34 ++ .../interfaces/reactasyncratelimiter.md | 34 ++ .../interfaces/reactasyncthrottler.md | 34 ++ .../reference/interfaces/reactbatcher.md | 34 ++ .../reference/interfaces/reactdebouncer.md | 34 ++ .../react/reference/interfaces/reactqueuer.md | 34 ++ .../reference/interfaces/reactratelimiter.md | 34 ++ .../reference/interfaces/reactthrottler.md | 34 ++ .../functions/createasyncthrottler.md | 15 +- .../interfaces/solidasyncthrottler.md | 92 +---- docs/reference/classes/asyncbatcher.md | 258 ++++++++++++ docs/reference/classes/asyncdebouncer.md | 199 +++++++++ docs/reference/classes/asyncpersister.md | 80 ---- docs/reference/classes/asyncqueuer.md | 383 ++++++++++++++++++ docs/reference/classes/asyncratelimiter.md | 229 +++++++++++ docs/reference/classes/asyncthrottler.md | 204 ++++++++++ docs/reference/classes/batcher.md | 224 ++++++++++ docs/reference/classes/debouncer.md | 170 ++++++++ docs/reference/classes/persister.md | 104 ----- docs/reference/classes/queuer.md | 373 +++++++++++++++++ docs/reference/classes/ratelimiter.md | 194 +++++++++ docs/reference/classes/storagepersister.md | 177 -------- docs/reference/classes/throttler.md | 199 +++++++++ docs/reference/functions/asyncbatch.md | 87 ++++ docs/reference/functions/asyncdebounce.md | 97 +++++ docs/reference/functions/asyncqueue.md | 84 ++++ docs/reference/functions/asyncratelimit.md | 129 ++++++ docs/reference/functions/asyncthrottle.md | 102 +++++ docs/reference/functions/batch.md | 60 +++ docs/reference/functions/debounce.md | 67 +++ docs/reference/functions/isfunction.md | 2 +- docs/reference/functions/isplainarray.md | 24 -- docs/reference/functions/isplainobject.md | 24 -- .../functions/parsefunctionorvalue.md | 2 +- docs/reference/functions/queue.md | 91 +++++ docs/reference/functions/ratelimit.md | 99 +++++ docs/reference/functions/replaceequaldeep.md | 36 -- .../functions/shallowequalobjects.md | 34 -- docs/reference/functions/throttle.md | 95 +++++ docs/reference/index.md | 57 ++- .../interfaces/asyncbatcheroptions.md | 249 ++++++++++++ .../reference/interfaces/asyncbatcherstate.md | 154 +++++++ .../interfaces/asyncdebounceroptions.md | 172 ++++++++ .../interfaces/asyncdebouncerstate.md | 104 +++++ .../interfaces/asyncqueueroptions.md | 364 +++++++++++++++++ docs/reference/interfaces/asyncqueuerstate.md | 174 ++++++++ .../interfaces/asyncratelimiteroptions.md | 195 +++++++++ .../interfaces/asyncratelimiterstate.md | 84 ++++ .../interfaces/asyncthrottleroptions.md | 172 ++++++++ .../interfaces/asyncthrottlerstate.md | 114 ++++++ docs/reference/interfaces/batcheroptions.md | 155 +++++++ docs/reference/interfaces/batcherstate.md | 94 +++++ docs/reference/interfaces/debounceroptions.md | 105 +++++ docs/reference/interfaces/debouncerstate.md | 64 +++ docs/reference/interfaces/persistedstorage.md | 44 -- docs/reference/interfaces/queueroptions.md | 284 +++++++++++++ docs/reference/interfaces/queuerstate.md | 134 ++++++ .../interfaces/ratelimiteroptions.md | 126 ++++++ docs/reference/interfaces/ratelimiterstate.md | 40 ++ .../interfaces/storagepersisteroptions.md | 231 ----------- docs/reference/interfaces/throttleroptions.md | 104 +++++ docs/reference/interfaces/throttlerstate.md | 74 ++++ .../type-aliases/anyasyncfunction.md | 2 +- docs/reference/type-aliases/anyfunction.md | 2 +- docs/reference/type-aliases/optionalkeys.md | 2 +- docs/reference/type-aliases/queueposition.md | 19 + docs/reference/type-aliases/requiredkeys.md | 20 - examples/react/useAsyncBatcher/src/index.tsx | 2 +- .../react/useAsyncThrottler/src/index.tsx | 6 +- examples/react/useBatcher/src/index.tsx | 2 +- .../solid/createAsyncBatcher/src/index.tsx | 2 +- .../solid/createAsyncThrottler/src/index.tsx | 6 +- examples/solid/createBatcher/src/index.tsx | 2 +- packages/pacer/src/async-debouncer.ts | 7 +- packages/pacer/src/async-rate-limiter.ts | 7 +- packages/pacer/src/async-throttler.ts | 211 +++++----- packages/pacer/src/debouncer.ts | 2 +- packages/pacer/src/queuer.ts | 1 - packages/pacer/src/rate-limiter.ts | 2 +- packages/pacer/src/throttler.ts | 2 +- packages/pacer/tests/async-throttler.test.ts | 35 +- .../src/async-throttler/useAsyncThrottler.ts | 38 +- .../async-throttler/createAsyncThrottler.ts | 103 ++--- scripts/generateDocs.js | 18 - 112 files changed, 8838 insertions(+), 1269 deletions(-) create mode 100644 docs/framework/react/reference/functions/useasyncbatcher.md create mode 100644 docs/framework/react/reference/functions/useasyncdebouncer.md create mode 100644 docs/framework/react/reference/functions/useasyncqueuedstate.md create mode 100644 docs/framework/react/reference/functions/useasyncqueuer.md create mode 100644 docs/framework/react/reference/functions/useasyncratelimiter.md create mode 100644 docs/framework/react/reference/functions/useasyncthrottler.md create mode 100644 docs/framework/react/reference/functions/usebatcher.md create mode 100644 docs/framework/react/reference/functions/usedebouncedcallback.md create mode 100644 docs/framework/react/reference/functions/usedebouncedstate.md create mode 100644 docs/framework/react/reference/functions/usedebouncedvalue.md create mode 100644 docs/framework/react/reference/functions/usedebouncer.md delete mode 100644 docs/framework/react/reference/functions/uselocalstoragestate.md create mode 100644 docs/framework/react/reference/functions/usequeuedstate.md create mode 100644 docs/framework/react/reference/functions/usequeuedvalue.md create mode 100644 docs/framework/react/reference/functions/usequeuer.md create mode 100644 docs/framework/react/reference/functions/useratelimitedcallback.md create mode 100644 docs/framework/react/reference/functions/useratelimitedstate.md create mode 100644 docs/framework/react/reference/functions/useratelimitedvalue.md create mode 100644 docs/framework/react/reference/functions/useratelimiter.md delete mode 100644 docs/framework/react/reference/functions/usesessionstoragestate.md delete mode 100644 docs/framework/react/reference/functions/usestoragepersister.md create mode 100644 docs/framework/react/reference/functions/usethrottledcallback.md create mode 100644 docs/framework/react/reference/functions/usethrottledstate.md create mode 100644 docs/framework/react/reference/functions/usethrottledvalue.md create mode 100644 docs/framework/react/reference/functions/usethrottler.md create mode 100644 docs/framework/react/reference/interfaces/reactasyncbatcher.md create mode 100644 docs/framework/react/reference/interfaces/reactasyncdebouncer.md create mode 100644 docs/framework/react/reference/interfaces/reactasyncqueuer.md create mode 100644 docs/framework/react/reference/interfaces/reactasyncratelimiter.md create mode 100644 docs/framework/react/reference/interfaces/reactasyncthrottler.md create mode 100644 docs/framework/react/reference/interfaces/reactbatcher.md create mode 100644 docs/framework/react/reference/interfaces/reactdebouncer.md create mode 100644 docs/framework/react/reference/interfaces/reactqueuer.md create mode 100644 docs/framework/react/reference/interfaces/reactratelimiter.md create mode 100644 docs/framework/react/reference/interfaces/reactthrottler.md create mode 100644 docs/reference/classes/asyncbatcher.md create mode 100644 docs/reference/classes/asyncdebouncer.md delete mode 100644 docs/reference/classes/asyncpersister.md create mode 100644 docs/reference/classes/asyncqueuer.md create mode 100644 docs/reference/classes/asyncratelimiter.md create mode 100644 docs/reference/classes/asyncthrottler.md create mode 100644 docs/reference/classes/batcher.md create mode 100644 docs/reference/classes/debouncer.md delete mode 100644 docs/reference/classes/persister.md create mode 100644 docs/reference/classes/queuer.md create mode 100644 docs/reference/classes/ratelimiter.md delete mode 100644 docs/reference/classes/storagepersister.md create mode 100644 docs/reference/classes/throttler.md create mode 100644 docs/reference/functions/asyncbatch.md create mode 100644 docs/reference/functions/asyncdebounce.md create mode 100644 docs/reference/functions/asyncqueue.md create mode 100644 docs/reference/functions/asyncratelimit.md create mode 100644 docs/reference/functions/asyncthrottle.md create mode 100644 docs/reference/functions/batch.md create mode 100644 docs/reference/functions/debounce.md delete mode 100644 docs/reference/functions/isplainarray.md delete mode 100644 docs/reference/functions/isplainobject.md create mode 100644 docs/reference/functions/queue.md create mode 100644 docs/reference/functions/ratelimit.md delete mode 100644 docs/reference/functions/replaceequaldeep.md delete mode 100644 docs/reference/functions/shallowequalobjects.md create mode 100644 docs/reference/functions/throttle.md create mode 100644 docs/reference/interfaces/asyncbatcheroptions.md create mode 100644 docs/reference/interfaces/asyncbatcherstate.md create mode 100644 docs/reference/interfaces/asyncdebounceroptions.md create mode 100644 docs/reference/interfaces/asyncdebouncerstate.md create mode 100644 docs/reference/interfaces/asyncqueueroptions.md create mode 100644 docs/reference/interfaces/asyncqueuerstate.md create mode 100644 docs/reference/interfaces/asyncratelimiteroptions.md create mode 100644 docs/reference/interfaces/asyncratelimiterstate.md create mode 100644 docs/reference/interfaces/asyncthrottleroptions.md create mode 100644 docs/reference/interfaces/asyncthrottlerstate.md create mode 100644 docs/reference/interfaces/batcheroptions.md create mode 100644 docs/reference/interfaces/batcherstate.md create mode 100644 docs/reference/interfaces/debounceroptions.md create mode 100644 docs/reference/interfaces/debouncerstate.md delete mode 100644 docs/reference/interfaces/persistedstorage.md create mode 100644 docs/reference/interfaces/queueroptions.md create mode 100644 docs/reference/interfaces/queuerstate.md create mode 100644 docs/reference/interfaces/ratelimiteroptions.md create mode 100644 docs/reference/interfaces/ratelimiterstate.md delete mode 100644 docs/reference/interfaces/storagepersisteroptions.md create mode 100644 docs/reference/interfaces/throttleroptions.md create mode 100644 docs/reference/interfaces/throttlerstate.md create mode 100644 docs/reference/type-aliases/queueposition.md delete mode 100644 docs/reference/type-aliases/requiredkeys.md diff --git a/docs/framework/react/reference/functions/useasyncbatcher.md b/docs/framework/react/reference/functions/useasyncbatcher.md new file mode 100644 index 000000000..b9a2c2f60 --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncbatcher.md @@ -0,0 +1,97 @@ +--- +id: useAsyncBatcher +title: useAsyncBatcher +--- + + + +# Function: useAsyncBatcher() + +```ts +function useAsyncBatcher( + fn, + options, +selector?): ReactAsyncBatcher +``` + +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:77](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L77) + +A React hook that creates an `AsyncBatcher` instance for managing asynchronous batches of items. + +This is the async version of the useBatcher hook. 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 + +Features: +- Configurable batch size and wait time +- Custom batch processing logic via getShouldExecute +- Event callbacks for monitoring batch operations +- Error handling for failed batch operations +- Automatic or manual batch processing + +The batcher collects items and processes them in batches based on: +- Maximum batch size (number of items per batch) +- Time-based batching (process after X milliseconds) +- Custom batch processing logic via getShouldExecute + +Error Handling: +- If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + +## Type Parameters + +• **TValue** + +• **TSelected** = `AsyncBatcherState`\<`TValue`\> + +## Parameters + +### fn + +(`items`) => `Promise`\<`any`\> + +### options + +`AsyncBatcherOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactAsyncBatcher`](../../interfaces/reactasyncbatcher.md)\<`TValue`, `TSelected`\> + +## Example + +```tsx +// Basic async batcher for API requests +const asyncBatcher = useAsyncBatcher( + async (items) => { + const results = await Promise.all(items.map(item => processItem(item))); + return results; + }, + { + maxSize: 10, + wait: 2000, + onSuccess: (result) => { + console.log('Batch processed successfully:', result); + }, + onError: (error) => { + console.error('Batch processing failed:', error); + } + } +); + +// Add items to batch +asyncBatcher.addItem(newItem); + +// Manually execute batch +const result = await asyncBatcher.execute(); +``` diff --git a/docs/framework/react/reference/functions/useasyncdebouncer.md b/docs/framework/react/reference/functions/useasyncdebouncer.md new file mode 100644 index 000000000..c44086e68 --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncdebouncer.md @@ -0,0 +1,94 @@ +--- +id: useAsyncDebouncer +title: useAsyncDebouncer +--- + + + +# Function: useAsyncDebouncer() + +```ts +function useAsyncDebouncer( + fn, + options, +selector?): ReactAsyncDebouncer +``` + +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L75) + +A low-level React hook that creates an `AsyncDebouncer` instance to delay execution of an async function. + +This hook is designed to be flexible and state-management agnostic - it simply returns a debouncer instance that +you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). + +Async debouncing ensures that an async function only executes 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. + +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 +- 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 AsyncDebouncer instance + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncDebouncerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`AsyncDebouncerOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactAsyncDebouncer`](../../interfaces/reactasyncdebouncer.md)\<`TFn`, `TSelected`\> + +## Example + +```tsx +// Basic API call debouncing +const { maybeExecute } = useAsyncDebouncer( + async (query: string) => { + const results = await api.search(query); + return results; + }, + { wait: 500 } +); + +// With state management +const [results, setResults] = useState([]); +const { maybeExecute } = useAsyncDebouncer( + async (searchTerm) => { + const data = await searchAPI(searchTerm); + setResults(data); + }, + { + wait: 300, + leading: true, // Execute immediately on first call + trailing: false, // Skip trailing edge updates + onError: (error) => { + console.error('API call failed:', error); + } + } +); +``` diff --git a/docs/framework/react/reference/functions/useasyncqueuedstate.md b/docs/framework/react/reference/functions/useasyncqueuedstate.md new file mode 100644 index 000000000..ebcbca9c0 --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncqueuedstate.md @@ -0,0 +1,86 @@ +--- +id: useAsyncQueuedState +title: useAsyncQueuedState +--- + + + +# Function: useAsyncQueuedState() + +```ts +function useAsyncQueuedState( + fn, + options, + selector?): [TValue[], ReactAsyncQueuer] +``` + +Defined in: [react-pacer/src/async-queuer/useAsyncQueuedState.ts:53](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuedState.ts#L53) + +A higher-level React hook that creates an `AsyncQueuer` instance with built-in state management. + +This hook combines an AsyncQueuer with React state to automatically track the queue items. +It returns a tuple containing: +- The current array of queued items as React state +- The queuer instance with methods to control the queue + +The queue can be configured with: +- Maximum concurrent operations +- Maximum queue size +- Processing function for queue items +- Various lifecycle callbacks + +The state will automatically update whenever items are: +- Added to the queue +- Removed from the queue +- Started processing +- Completed processing + +## Type Parameters + +• **TValue** + +• **TSelected** *extends* `Pick`\<`AsyncQueuerState`\<`TValue`\>, `"items"`\> = `AsyncQueuerState`\<`TValue`\> + +## Parameters + +### fn + +(`value`) => `Promise`\<`any`\> + +### options + +`AsyncQueuerOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`[], [`ReactAsyncQueuer`](../../interfaces/reactasyncqueuer.md)\<`TValue`, `TSelected`\>\] + +## Example + +```tsx +// Create a queue with state management +const [queueItems, asyncQueuer] = useAsyncQueuedState({ + concurrency: 2, + maxSize: 100, + started: true +}); + +// Add items to queue - state updates automatically +asyncQueuer.addItem(async () => { + const result = await fetchData(); + return result; +}); + +// Start processing +asyncQueuer.start(); + +// Stop processing +asyncQueuer.stop(); + +// queueItems reflects current queue state +const pendingCount = asyncQueuer.peekPendingItems().length; +``` diff --git a/docs/framework/react/reference/functions/useasyncqueuer.md b/docs/framework/react/reference/functions/useasyncqueuer.md new file mode 100644 index 000000000..b42084af0 --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncqueuer.md @@ -0,0 +1,86 @@ +--- +id: useAsyncQueuer +title: useAsyncQueuer +--- + + + +# Function: useAsyncQueuer() + +```ts +function useAsyncQueuer( + fn, + options, +selector?): ReactAsyncQueuer +``` + +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:64](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L64) + +A lower-level React hook that creates an `AsyncQueuer` instance for managing an async queue of items. + +Features: +- Priority queue support via getPriority option +- Configurable concurrency limit +- Task success/error/completion callbacks +- FIFO (First In First Out) or LIFO (Last In First Out) queue behavior +- Pause/resume task processing +- Task cancellation +- Item expiration to clear 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 below the concurrency limit. + +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 +- 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 AsyncQueuer instance + +## Type Parameters + +• **TValue** + +• **TSelected** = `AsyncQueuerState`\<`TValue`\> + +## Parameters + +### fn + +(`value`) => `Promise`\<`any`\> + +### options + +`AsyncQueuerOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactAsyncQueuer`](../../interfaces/reactasyncqueuer.md)\<`TValue`, `TSelected`\> + +## Example + +```tsx +// Basic async queuer for API requests +const asyncQueuer = useAsyncQueuer({ + initialItems: [], + concurrency: 2, + maxSize: 100, + started: false, + onSuccess: (result) => { + console.log('Item processed:', result); + }, + onError: (error) => { + console.error('Processing failed:', error); + } +}); + +// Add items to queue +asyncQueuer.addItem(newItem); + +// Start processing +asyncQueuer.start(); +``` diff --git a/docs/framework/react/reference/functions/useasyncratelimiter.md b/docs/framework/react/reference/functions/useasyncratelimiter.md new file mode 100644 index 000000000..62c5b75ca --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncratelimiter.md @@ -0,0 +1,101 @@ +--- +id: useAsyncRateLimiter +title: useAsyncRateLimiter +--- + + + +# Function: useAsyncRateLimiter() + +```ts +function useAsyncRateLimiter( + fn, + options, +selector?): ReactAsyncRateLimiter +``` + +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:82](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L82) + +A low-level React hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. + +This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that +you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). + +Rate limiting allows an async function to execute up to a specified limit within a time window, +then blocks subsequent calls until the window passes. This is useful for respecting API rate limits, +managing resource constraints, or controlling bursts of async operations. + +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. + +The rate limiter supports two types of windows: +- '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. + +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` + +• **TSelected** = `AsyncRateLimiterState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`AsyncRateLimiterOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactAsyncRateLimiter`](../../interfaces/reactasyncratelimiter.md)\<`TFn`, `TSelected`\> + +## Example + +```tsx +// Basic API call rate limiting with return value +const { maybeExecute } = useAsyncRateLimiter( + async (id: string) => { + const data = await api.fetchData(id); + return data; // Return value is preserved + }, + { limit: 5, window: 1000 } // 5 calls per second +); + +// With state management and return value +const [data, setData] = useState(null); +const { maybeExecute } = useAsyncRateLimiter( + async (query) => { + const result = await searchAPI(query); + setData(result); + return result; // Return value can be used by the caller + }, + { + limit: 10, + window: 60000, // 10 calls per minute + onReject: (rateLimiter) => { + console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); + }, + onError: (error) => { + console.error('API call failed:', error); + } + } +); +``` diff --git a/docs/framework/react/reference/functions/useasyncthrottler.md b/docs/framework/react/reference/functions/useasyncthrottler.md new file mode 100644 index 000000000..9338610db --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncthrottler.md @@ -0,0 +1,89 @@ +--- +id: useAsyncThrottler +title: useAsyncThrottler +--- + + + +# Function: useAsyncThrottler() + +```ts +function useAsyncThrottler( + fn, + options, +selector?): ReactAsyncThrottler +``` + +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:70](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L70) + +A low-level React hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. + +This hook is designed to be flexible and state-management agnostic - it simply returns a throttler instance that +you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). + +Async throttling ensures an async 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 API calls, +database operations, or other async tasks. + +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. + +Error Handling: +- If an `onError` handler is provided, it will be called with the error and throttler 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 AsyncThrottler instance + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncThrottlerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`AsyncThrottlerOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactAsyncThrottler`](../../interfaces/reactasyncthrottler.md)\<`TFn`, `TSelected`\> + +## Example + +```tsx +// Basic API call throttling with return value +const { maybeExecute } = useAsyncThrottler( + async (id: string) => { + const data = await api.fetchData(id); + return data; // Return value is preserved + }, + { wait: 1000 } +); + +// With state management and return value +const [data, setData] = useState(null); +const { maybeExecute } = useAsyncThrottler( + async (query) => { + const result = await searchAPI(query); + setData(result); + return result; // Return value can be used by the caller + }, + { + wait: 2000, + leading: true, // Execute immediately on first call + trailing: false // Skip trailing edge updates + } +); +``` diff --git a/docs/framework/react/reference/functions/usebatcher.md b/docs/framework/react/reference/functions/usebatcher.md new file mode 100644 index 000000000..c7cf7e33a --- /dev/null +++ b/docs/framework/react/reference/functions/usebatcher.md @@ -0,0 +1,78 @@ +--- +id: useBatcher +title: useBatcher +--- + + + +# Function: useBatcher() + +```ts +function useBatcher( + fn, + options, +selector?): ReactBatcher +``` + +Defined in: [react-pacer/src/batcher/useBatcher.ts:53](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L53) + +A React hook that creates and manages a Batcher instance. + +This is a lower-level hook that provides direct access to the Batcher's functionality without +any built-in state management. This allows you to integrate it with any state management solution +you prefer (useState, Redux, Zustand, etc.) by utilizing the onItemsChange callback. + +The Batcher collects items and processes them in batches based on configurable conditions: +- Maximum batch size +- Time-based batching (process after X milliseconds) +- Custom batch processing logic via getShouldExecute + +## Type Parameters + +• **TValue** + +• **TSelected** = `BatcherState`\<`TValue`\> + +## Parameters + +### fn + +(`items`) => `void` + +### options + +`BatcherOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactBatcher`](../../interfaces/reactbatcher.md)\<`TValue`, `TSelected`\> + +## Example + +```tsx +// Example with custom state management and batching +const [items, setItems] = useState([]); + +const batcher = useBatcher( + (items) => console.log('Processing batch:', items), + { + maxSize: 5, + wait: 2000, + onItemsChange: (batcher) => setItems(batcher.peekAllItems()), + getShouldExecute: (items) => items.length >= 3 + } +); + +// Add items to batch - they'll be processed when conditions are met +batcher.addItem(1); +batcher.addItem(2); +batcher.addItem(3); // Triggers batch processing + +// Control the batcher +batcher.stop(); // Pause batching +batcher.start(); // Resume batching +``` diff --git a/docs/framework/react/reference/functions/usedebouncedcallback.md b/docs/framework/react/reference/functions/usedebouncedcallback.md new file mode 100644 index 000000000..cf67f61fc --- /dev/null +++ b/docs/framework/react/reference/functions/usedebouncedcallback.md @@ -0,0 +1,87 @@ +--- +id: useDebouncedCallback +title: useDebouncedCallback +--- + + + +# Function: useDebouncedCallback() + +```ts +function useDebouncedCallback( + fn, + options, + selector?): (...args) => void +``` + +Defined in: [react-pacer/src/debouncer/useDebouncedCallback.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncedCallback.ts#L45) + +A React hook that creates a debounced version of a callback function. +This hook is essentially a wrapper around the basic `debounce` function +that is exported from `@tanstack/pacer`, +but optimized for React with reactive options and a stable function reference. + +The debounced function will only execute after the specified wait time has elapsed +since its last invocation. If called again before the wait time expires, the timer +resets and starts waiting again. + +This hook provides a simpler API compared to `useDebouncer`, making it ideal for basic +debouncing needs. However, it does not expose the underlying Debouncer instance. + +For advanced usage requiring features like: +- Manual cancellation +- Access to execution counts +- Custom useCallback dependencies + +Consider using the `useDebouncer` hook instead. + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `DebouncerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`DebouncerOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +`Function` + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`void` + +## Example + +```tsx +// Debounce a search handler +const handleSearch = useDebouncedCallback((query: string) => { + fetchSearchResults(query); +}, { + wait: 500 // Wait 500ms between executions +}); + +// Use in an input + handleSearch(e.target.value)} +/> +``` diff --git a/docs/framework/react/reference/functions/usedebouncedstate.md b/docs/framework/react/reference/functions/usedebouncedstate.md new file mode 100644 index 000000000..b662beaa9 --- /dev/null +++ b/docs/framework/react/reference/functions/usedebouncedstate.md @@ -0,0 +1,74 @@ +--- +id: useDebouncedState +title: useDebouncedState +--- + + + +# Function: useDebouncedState() + +```ts +function useDebouncedState( + value, + options, + selector?): [TValue, Dispatch>, ReactDebouncer>, TSelected>] +``` + +Defined in: [react-pacer/src/debouncer/useDebouncedState.ts:42](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncedState.ts#L42) + +A React hook that creates a debounced state value, combining React's useState with debouncing functionality. +This hook provides both the current debounced value and methods to update it. + +The state value is only updated after the specified wait time has elapsed since the last update attempt. +If another update is attempted before the wait time expires, the timer resets and starts waiting again. +This is useful for handling frequent state updates that should be throttled, like search input values +or window resize dimensions. + +The hook returns a tuple containing: +- The current debounced value +- A function to update the debounced value +- The debouncer instance with additional control methods + +## Type Parameters + +• **TValue** + +• **TSelected** = `DebouncerState`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +## Parameters + +### value + +`TValue` + +### options + +`DebouncerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>, [`ReactDebouncer`](../../interfaces/reactdebouncer.md)\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>, `TSelected`\>\] + +## Example + +```tsx +// Debounced search input +const [searchTerm, setSearchTerm, debouncer] = useDebouncedState('', { + wait: 500 // Wait 500ms after last keystroke +}); + +// Update value - will be debounced +const handleChange = (e) => { + setSearchTerm(e.target.value); +}; + +// Get number of times the debounced function has executed +const executionCount = debouncer.getExecutionCount(); + +// Get the pending state +const isPending = debouncer.getState().isPending; +``` diff --git a/docs/framework/react/reference/functions/usedebouncedvalue.md b/docs/framework/react/reference/functions/usedebouncedvalue.md new file mode 100644 index 000000000..07825a9ba --- /dev/null +++ b/docs/framework/react/reference/functions/usedebouncedvalue.md @@ -0,0 +1,77 @@ +--- +id: useDebouncedValue +title: useDebouncedValue +--- + + + +# Function: useDebouncedValue() + +```ts +function useDebouncedValue( + value, + options, + selector?): [TValue, ReactDebouncer>, TSelected>] +``` + +Defined in: [react-pacer/src/debouncer/useDebouncedValue.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncedValue.ts#L45) + +A React hook that creates a debounced value that updates only after a specified delay. +Unlike useDebouncedState, this hook automatically tracks changes to the input value +and updates the debounced value accordingly. + +The debounced value will only update after the specified wait time has elapsed since +the last change to the input value. If the input value changes again before the wait +time expires, the timer resets and starts waiting again. + +This is useful for deriving debounced values from props or state that change frequently, +like search queries or form inputs, where you want to limit how often downstream effects +or calculations occur. + +The hook returns the current debounced value and the underlying debouncer instance. +The debouncer instance can be used to access additional functionality like cancellation +and execution counts. + +## Type Parameters + +• **TValue** + +• **TSelected** = `DebouncerState`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +## Parameters + +### value + +`TValue` + +### options + +`DebouncerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`, [`ReactDebouncer`](../../interfaces/reactdebouncer.md)\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>, `TSelected`\>\] + +## Example + +```tsx +// Debounce a search query +const [searchQuery, setSearchQuery] = useState(''); +const [debouncedQuery, debouncer] = useDebouncedValue(searchQuery, { + wait: 500 // Wait 500ms after last change +}); + +// debouncedQuery will update 500ms after searchQuery stops changing +useEffect(() => { + fetchSearchResults(debouncedQuery); +}, [debouncedQuery]); + +// Handle input changes +const handleChange = (e) => { + setSearchQuery(e.target.value); +}; +``` diff --git a/docs/framework/react/reference/functions/usedebouncer.md b/docs/framework/react/reference/functions/usedebouncer.md new file mode 100644 index 000000000..b1585af21 --- /dev/null +++ b/docs/framework/react/reference/functions/usedebouncer.md @@ -0,0 +1,76 @@ +--- +id: useDebouncer +title: useDebouncer +--- + + + +# Function: useDebouncer() + +```ts +function useDebouncer( + fn, + options, +selector?): ReactDebouncer +``` + +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:57](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L57) + +A React hook that creates and manages a Debouncer instance. + +This is a lower-level hook that provides direct access to the Debouncer's functionality without +any built-in state management. This allows you to integrate it with any state management solution +you prefer (useState, Redux, Zustand, etc.). + +This hook provides debouncing functionality to limit how often a function can be called, +waiting for a specified delay before executing the latest call. This is useful for handling +frequent events like window resizing, scroll events, or real-time search inputs. + +The debouncer will only execute the function after the specified wait time has elapsed +since the last call. If the function is called again before the wait time expires, the +timer resets and starts waiting again. + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `DebouncerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`DebouncerOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactDebouncer`](../../interfaces/reactdebouncer.md)\<`TFn`, `TSelected`\> + +## Example + +```tsx +// Debounce a search function to limit API calls +const searchDebouncer = useDebouncer( + (query: string) => fetchSearchResults(query), + { wait: 500 } // Wait 500ms after last keystroke +); + +// In an event handler +const handleChange = (e) => { + searchDebouncer.maybeExecute(e.target.value); +}; + +// Get number of times the debounced function has executed +const executionCount = searchDebouncer.getExecutionCount(); + +// Get the pending state +const isPending = searchdebouncer.getState().isPending; +``` diff --git a/docs/framework/react/reference/functions/uselocalstoragestate.md b/docs/framework/react/reference/functions/uselocalstoragestate.md deleted file mode 100644 index 1c3c1b060..000000000 --- a/docs/framework/react/reference/functions/uselocalstoragestate.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: useLocalStorageState -title: useLocalStorageState ---- - - - -# Function: useLocalStorageState() - -```ts -function useLocalStorageState( - key, - initialValue, - options?): readonly [TValue, Dispatch>] -``` - -Defined in: [useStorageState.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStorageState.ts#L46) - -A hook that persists state to localStorage and syncs it across tabs - -## Type Parameters - -• **TValue** - -## Parameters - -### key - -`string` - -### initialValue - -`TValue` - -### options? - -#### buster? - -`string` - -#### maxAge? - -`number` - -## Returns - -readonly \[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>\] - -## Example - -```tsx -const [value, setValue] = useLocalStorageState('my-key', 'initial value') -``` diff --git a/docs/framework/react/reference/functions/usequeuedstate.md b/docs/framework/react/reference/functions/usequeuedstate.md new file mode 100644 index 000000000..e21595471 --- /dev/null +++ b/docs/framework/react/reference/functions/usequeuedstate.md @@ -0,0 +1,90 @@ +--- +id: useQueuedState +title: useQueuedState +--- + + + +# Function: useQueuedState() + +```ts +function useQueuedState( + fn, + options, + selector?): [TValue[], (item, position?, runOnItemsChange?) => boolean, ReactQueuer] +``` + +Defined in: [react-pacer/src/queuer/useQueuedState.ts:54](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuedState.ts#L54) + +A React hook that creates a queuer with managed state, combining React's useState with queuing functionality. +This hook provides both the current queue state and queue control methods. + +The queue state is automatically updated whenever items are added, removed, or reordered in the queue. +All queue operations are reflected in the state array returned by the hook. + +The queue can be started and stopped to automatically process items at a specified interval, +making it useful as a scheduler. When started, it will process one item per tick, with an +optional wait time between ticks. + +The hook returns a tuple containing: +- The current queue state as an array +- The queue instance with methods for queue manipulation + +## Type Parameters + +• **TValue** + +• **TSelected** *extends* `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> = `QueuerState`\<`TValue`\> + +## Parameters + +### fn + +(`item`) => `void` + +### options + +`QueuerOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`[], (`item`, `position`?, `runOnItemsChange`?) => `boolean`, [`ReactQueuer`](../../interfaces/reactqueuer.md)\<`TValue`, `TSelected`\>\] + +## Example + +```tsx +// Basic queue with initial items and priority +const [items, queue] = useQueuedState({ + initialItems: ['item1', 'item2'], + started: true, + wait: 1000, + getPriority: (item) => item.priority +}); + +// Add items to queue +const handleAdd = (item) => { + queue.addItem(item); +}; + +// Start automatic processing +const startProcessing = () => { + queue.start(); +}; + +// Stop automatic processing +const stopProcessing = () => { + queue.stop(); +}; + +// Manual processing still available +const handleProcess = () => { + const nextItem = queue.getNextItem(); + if (nextItem) { + processItem(nextItem); + } +}; +``` diff --git a/docs/framework/react/reference/functions/usequeuedvalue.md b/docs/framework/react/reference/functions/usequeuedvalue.md new file mode 100644 index 000000000..6a69e4018 --- /dev/null +++ b/docs/framework/react/reference/functions/usequeuedvalue.md @@ -0,0 +1,76 @@ +--- +id: useQueuedValue +title: useQueuedValue +--- + + + +# Function: useQueuedValue() + +```ts +function useQueuedValue( + initialValue, + options, + selector?): [TValue, ReactQueuer] +``` + +Defined in: [react-pacer/src/queuer/useQueuedValue.ts:41](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuedValue.ts#L41) + +A React hook that creates a queued value that processes state changes in order with an optional delay. +This hook uses useQueuer internally to manage a queue of state changes and apply them sequentially. + +The queued value will process changes in the order they are received, with optional delays between +processing each change. This is useful for handling state updates that need to be processed +in a specific order, like animations or sequential UI updates. + +The hook returns a tuple containing: +- The current queued value +- The queuer instance with control methods + +## Type Parameters + +• **TValue** + +• **TSelected** *extends* `Pick`\<`QueuerState`\<`TValue`\>, `"items"`\> = `QueuerState`\<`TValue`\> + +## Parameters + +### initialValue + +`TValue` + +### options + +`QueuerOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`, [`ReactQueuer`](../../interfaces/reactqueuer.md)\<`TValue`, `TSelected`\>\] + +## Example + +```tsx +// Queue state changes with a delay between each +const [value, queuer] = useQueuedValue(initialValue, { + wait: 500, // Wait 500ms between processing each change + started: true // Start processing immediately +}); + +// Add changes to the queue +const handleChange = (newValue) => { + queuer.addItem(newValue); +}; + +// Control the queue +const pauseProcessing = () => { + queuer.stop(); +}; + +const resumeProcessing = () => { + queuer.start(); +}; +``` diff --git a/docs/framework/react/reference/functions/usequeuer.md b/docs/framework/react/reference/functions/usequeuer.md new file mode 100644 index 000000000..2ce82d6e2 --- /dev/null +++ b/docs/framework/react/reference/functions/usequeuer.md @@ -0,0 +1,79 @@ +--- +id: useQueuer +title: useQueuer +--- + + + +# Function: useQueuer() + +```ts +function useQueuer( + fn, + options, +selector?): ReactQueuer +``` + +Defined in: [react-pacer/src/queuer/useQueuer.ts:54](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L54) + +A React hook that creates and manages a Queuer instance. + +This is a lower-level hook that provides direct access to the Queuer's functionality without +any built-in state management. This allows you to integrate it with any state management solution +you prefer (useState, Redux, Zustand, etc.) by utilizing the onItemsChange callback. + +For a hook with built-in state management, see useQueuedState. + +The Queuer extends the base Queue to add processing capabilities. Items are processed +synchronously in order, with optional delays between processing each item. The queuer includes +an internal tick mechanism that can be started and stopped, making it useful as a scheduler. +When started, it will process one item per tick, with an optional wait time between ticks. + +By default uses FIFO (First In First Out) behavior, but can be configured for LIFO +(Last In First Out) by specifying 'front' position when adding items. + +## Type Parameters + +• **TValue** + +• **TSelected** = `QueuerState`\<`TValue`\> + +## Parameters + +### fn + +(`item`) => `void` + +### options + +`QueuerOptions`\<`TValue`\> = `{}` + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactQueuer`](../../interfaces/reactqueuer.md)\<`TValue`, `TSelected`\> + +## Example + +```tsx +// Example with custom state management and scheduling +const [items, setItems] = useState([]); + +const queue = useQueuer({ + started: true, // Start processing immediately + wait: 1000, // Process one item every second + onItemsChange: (queue) => setItems(queue.peekAllItems()), + getPriority: (item) => item.priority // Process higher priority items first +}); + +// Add items to process - they'll be handled automatically +queue.addItem('task1'); +queue.addItem('task2'); + +// Control the scheduler +queue.stop(); // Pause processing +queue.start(); // Resume processing +``` diff --git a/docs/framework/react/reference/functions/useratelimitedcallback.md b/docs/framework/react/reference/functions/useratelimitedcallback.md new file mode 100644 index 000000000..e5c1ad5b8 --- /dev/null +++ b/docs/framework/react/reference/functions/useratelimitedcallback.md @@ -0,0 +1,104 @@ +--- +id: useRateLimitedCallback +title: useRateLimitedCallback +--- + + + +# Function: useRateLimitedCallback() + +```ts +function useRateLimitedCallback( + fn, + options, + selector?): (...args) => boolean +``` + +Defined in: [react-pacer/src/rate-limiter/useRateLimitedCallback.ts:62](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts#L62) + +A React hook that creates a rate-limited version of a callback function. +This hook is essentially a wrapper around the basic `rateLimiter` function +that is exported from `@tanstack/pacer`, +but optimized for React with reactive options and a stable function reference. + +Rate limiting is a simple "hard limit" approach - it allows all calls until the limit +is reached, then blocks subsequent calls until the window resets. Unlike throttling +or debouncing, it does not attempt to space out or intelligently collapse calls. +This can lead to bursts of rapid executions followed by periods where all calls +are blocked. + +The rate limiter supports two types of windows: +- '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. + +For smoother execution patterns, consider: +- useThrottledCallback: When you want consistent spacing between executions (e.g. UI updates) +- useDebouncedCallback: When you want to collapse rapid calls into a single execution (e.g. search input) + +Rate limiting should primarily be used when you need to enforce strict limits, +like API rate limits or other scenarios requiring hard caps on execution frequency. + +This hook provides a simpler API compared to `useRateLimiter`, making it ideal for basic +rate limiting needs. However, it does not expose the underlying RateLimiter instance. + +For advanced usage requiring features like: +- Manual cancellation +- Access to execution counts +- Custom useCallback dependencies + +Consider using the `useRateLimiter` hook instead. + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `RateLimiterState` + +## Parameters + +### fn + +`TFn` + +### options + +`RateLimiterOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +`Function` + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`boolean` + +## Example + +```tsx +// Rate limit API calls to maximum 5 calls per minute with a sliding window +const makeApiCall = useRateLimitedCallback( + (data: ApiData) => { + return fetch('/api/endpoint', { method: 'POST', body: JSON.stringify(data) }); + }, + { + limit: 5, + window: 60000, // 1 minute + windowType: 'sliding', + onReject: () => { + console.warn('API rate limit reached. Please wait before trying again.'); + } + } +); +``` diff --git a/docs/framework/react/reference/functions/useratelimitedstate.md b/docs/framework/react/reference/functions/useratelimitedstate.md new file mode 100644 index 000000000..9b044ec2d --- /dev/null +++ b/docs/framework/react/reference/functions/useratelimitedstate.md @@ -0,0 +1,99 @@ +--- +id: useRateLimitedState +title: useRateLimitedState +--- + + + +# Function: useRateLimitedState() + +```ts +function useRateLimitedState( + value, + options, + selector?): [TValue, Dispatch>, ReactRateLimiter>, TSelected>] +``` + +Defined in: [react-pacer/src/rate-limiter/useRateLimitedState.ts:67](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimitedState.ts#L67) + +A React hook that creates a rate-limited state value that enforces a hard limit on state updates within a time window. +This hook combines React's useState with rate limiting functionality to provide controlled state updates. + +Rate limiting is a simple "hard limit" approach - it allows all updates until the limit is reached, then blocks +subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out +or intelligently collapse updates. This can lead to bursts of rapid updates followed by periods of no updates. + +The rate limiter supports two types of windows: +- 'fixed': A strict window that resets after the window period. All updates within the window count + towards the limit, and the window resets completely after the period. +- 'sliding': A rolling window that allows updates as old ones expire. This provides a more + consistent rate of updates over time. + +For smoother update patterns, consider: +- useThrottledState: When you want consistent spacing between updates (e.g. UI changes) +- useDebouncedState: When you want to collapse rapid updates into a single update (e.g. search input) + +Rate limiting should primarily be used when you need to enforce strict limits, like API rate limits. + +The hook returns a tuple containing: +- The rate-limited state value +- A rate-limited setter function that respects the configured limits +- The rateLimiter instance for additional control + +For more direct control over rate limiting without state management, +consider using the lower-level useRateLimiter hook instead. + +## Type Parameters + +• **TValue** + +• **TSelected** = `RateLimiterState` + +## Parameters + +### value + +`TValue` + +### options + +`RateLimiterOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>, [`ReactRateLimiter`](../../interfaces/reactratelimiter.md)\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>, `TSelected`\>\] + +## Example + +```tsx +// Basic rate limiting - update state at most 5 times per minute with a sliding window +const [value, setValue, rateLimiter] = useRateLimitedState(0, { + limit: 5, + window: 60000, + windowType: 'sliding' +}); + +// With rejection callback and fixed window +const [value, setValue] = useRateLimitedState(0, { + limit: 3, + window: 5000, + windowType: 'fixed', + onReject: (rateLimiter) => { + alert(`Rate limit reached. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); + } +}); + +// Access rateLimiter methods if needed +const handleSubmit = () => { + const remaining = rateLimiter.getRemainingInWindow(); + if (remaining > 0) { + setValue(newValue); + } else { + showRateLimitWarning(); + } +}; +``` diff --git a/docs/framework/react/reference/functions/useratelimitedvalue.md b/docs/framework/react/reference/functions/useratelimitedvalue.md new file mode 100644 index 000000000..78f9dcc3b --- /dev/null +++ b/docs/framework/react/reference/functions/useratelimitedvalue.md @@ -0,0 +1,88 @@ +--- +id: useRateLimitedValue +title: useRateLimitedValue +--- + + + +# Function: useRateLimitedValue() + +```ts +function useRateLimitedValue( + value, + options, + selector?): [TValue, ReactRateLimiter>, TSelected>] +``` + +Defined in: [react-pacer/src/rate-limiter/useRateLimitedValue.ts:56](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimitedValue.ts#L56) + +A high-level React hook that creates a rate-limited version of a value that updates at most a certain number of times within a time window. +This hook uses React's useState internally to manage the rate-limited state. + +Rate limiting is a simple "hard limit" approach - it allows all updates until the limit is reached, then blocks +subsequent updates until the window resets. Unlike throttling or debouncing, it does not attempt to space out +or intelligently collapse updates. This can lead to bursts of rapid updates followed by periods of no updates. + +The rate limiter supports two types of windows: +- 'fixed': A strict window that resets after the window period. All updates within the window count + towards the limit, and the window resets completely after the period. +- 'sliding': A rolling window that allows updates as old ones expire. This provides a more + consistent rate of updates over time. + +For smoother update patterns, consider: +- useThrottledValue: When you want consistent spacing between updates (e.g. UI changes) +- useDebouncedValue: When you want to collapse rapid updates into a single update (e.g. search input) + +Rate limiting should primarily be used when you need to enforce strict limits, like API rate limits. + +The hook returns a tuple containing: +- The rate-limited value that updates according to the configured rate limit +- The rate limiter instance with control methods + +For more direct control over rate limiting behavior without React state management, +consider using the lower-level useRateLimiter hook instead. + +## Type Parameters + +• **TValue** + +• **TSelected** = `RateLimiterState` + +## Parameters + +### value + +`TValue` + +### options + +`RateLimiterOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`, [`ReactRateLimiter`](../../interfaces/reactratelimiter.md)\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>, `TSelected`\>\] + +## Example + +```tsx +// Basic rate limiting - update at most 5 times per minute with a sliding window +const [rateLimitedValue, rateLimiter] = useRateLimitedValue(rawValue, { + limit: 5, + window: 60000, + windowType: 'sliding' +}); + +// With rejection callback and fixed window +const [rateLimitedValue, rateLimiter] = useRateLimitedValue(rawValue, { + limit: 3, + window: 5000, + windowType: 'fixed', + onReject: (rateLimiter) => { + console.log(`Update rejected. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); + } +}); +``` diff --git a/docs/framework/react/reference/functions/useratelimiter.md b/docs/framework/react/reference/functions/useratelimiter.md new file mode 100644 index 000000000..eb9d0080a --- /dev/null +++ b/docs/framework/react/reference/functions/useratelimiter.md @@ -0,0 +1,89 @@ +--- +id: useRateLimiter +title: useRateLimiter +--- + + + +# Function: useRateLimiter() + +```ts +function useRateLimiter( + fn, + options, +selector?): ReactRateLimiter +``` + +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:70](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L70) + +A low-level React hook that creates a `RateLimiter` instance to enforce rate limits on function execution. + +This hook is designed to be flexible and state-management agnostic - it simply returns a rate limiter instance that +you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). + +Rate limiting is a simple "hard limit" approach that allows executions until a maximum count is reached within +a time window, then blocks all subsequent calls until the window resets. Unlike throttling or debouncing, +it does not attempt to space out or collapse executions intelligently. + +The rate limiter supports two types of windows: +- '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. + +For smoother execution patterns: +- Use throttling when you want consistent spacing between executions (e.g. UI updates) +- Use debouncing when you want to collapse rapid-fire events (e.g. search input) +- Use rate limiting only when you need to enforce hard limits (e.g. API rate limits) + +The hook returns an object containing: +- maybeExecute: The rate-limited function that respects the configured limits +- getExecutionCount: Returns the number of successful executions +- getRejectionCount: Returns the number of rejected executions due to rate limiting +- getRemainingInWindow: Returns how many more executions are allowed in the current window +- reset: Resets the execution counts and window timing + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `RateLimiterState` + +## Parameters + +### fn + +`TFn` + +### options + +`RateLimiterOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactRateLimiter`](../../interfaces/reactratelimiter.md)\<`TFn`, `TSelected`\> + +## Example + +```tsx +// Basic rate limiting - max 5 calls per minute with a sliding window +const { maybeExecute } = useRateLimiter(apiCall, { + limit: 5, + window: 60000, + windowType: 'sliding', +}); + +// Monitor rate limit status +const handleClick = () => { + const remaining = getRemainingInWindow(); + if (remaining > 0) { + maybeExecute(data); + } else { + showRateLimitWarning(); + } +}; +``` diff --git a/docs/framework/react/reference/functions/usesessionstoragestate.md b/docs/framework/react/reference/functions/usesessionstoragestate.md deleted file mode 100644 index 5378cca11..000000000 --- a/docs/framework/react/reference/functions/usesessionstoragestate.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: useSessionStorageState -title: useSessionStorageState ---- - - - -# Function: useSessionStorageState() - -```ts -function useSessionStorageState( - key, - initialValue, - options?): readonly [TValue, Dispatch>] -``` - -Defined in: [useStorageState.ts:69](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStorageState.ts#L69) - -A hook that persists state to sessionStorage and syncs it across tabs - -## Type Parameters - -• **TValue** - -## Parameters - -### key - -`string` - -### initialValue - -`TValue` - -### options? - -#### buster? - -`string` - -#### maxAge? - -`number` - -## Returns - -readonly \[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>\] - -## Example - -```tsx -const [value, setValue] = useSessionStorageState('my-key', 'initial value') -``` diff --git a/docs/framework/react/reference/functions/usestoragepersister.md b/docs/framework/react/reference/functions/usestoragepersister.md deleted file mode 100644 index c8965e40f..000000000 --- a/docs/framework/react/reference/functions/usestoragepersister.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -id: useStoragePersister -title: useStoragePersister ---- - - - -# Function: useStoragePersister() - -```ts -function useStoragePersister(options): StoragePersister -``` - -Defined in: [useStoragePersister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/react-persister/src/storage-persister/useStoragePersister.ts#L5) - -## Type Parameters - -• **TState** - -## Parameters - -### options - -`StoragePersisterOptions`\<`TState`\> - -## Returns - -`StoragePersister`\<`TState`\> diff --git a/docs/framework/react/reference/functions/usethrottledcallback.md b/docs/framework/react/reference/functions/usethrottledcallback.md new file mode 100644 index 000000000..93b1ca88b --- /dev/null +++ b/docs/framework/react/reference/functions/usethrottledcallback.md @@ -0,0 +1,88 @@ +--- +id: useThrottledCallback +title: useThrottledCallback +--- + + + +# Function: useThrottledCallback() + +```ts +function useThrottledCallback( + fn, + options, + selector?): (...args) => void +``` + +Defined in: [react-pacer/src/throttler/useThrottledCallback.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottledCallback.ts#L46) + +A React hook that creates a throttled version of a callback function. +This hook is essentially a wrapper around the basic `throttle` function +that is exported from `@tanstack/pacer`, +but optimized for React with reactive options and a stable function reference. + +The throttled function will execute at most once within the specified wait time period, +regardless of how many times it is called. If called multiple times during the wait period, +only the first invocation will execute, and subsequent calls will be ignored until +the wait period has elapsed. + +This hook provides a simpler API compared to `useThrottler`, making it ideal for basic +throttling needs. However, it does not expose the underlying Throttler instance. + +For advanced usage requiring features like: +- Manual cancellation +- Access to execution counts +- Custom useCallback dependencies + +Consider using the `useThrottler` hook instead. + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `ThrottlerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`ThrottlerOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +`Function` + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`void` + +## Example + +```tsx +// Throttle a window resize handler +const handleResize = useThrottledCallback(() => { + updateLayoutMeasurements(); +}, { + wait: 100 // Execute at most once every 100ms +}); + +// Use in an event listener +useEffect(() => { + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); +}, [handleResize]); +``` diff --git a/docs/framework/react/reference/functions/usethrottledstate.md b/docs/framework/react/reference/functions/usethrottledstate.md new file mode 100644 index 000000000..5e30ccc07 --- /dev/null +++ b/docs/framework/react/reference/functions/usethrottledstate.md @@ -0,0 +1,75 @@ +--- +id: useThrottledState +title: useThrottledState +--- + + + +# Function: useThrottledState() + +```ts +function useThrottledState( + value, + options, + selector?): [TValue, Dispatch>, ReactThrottler>, TSelected>] +``` + +Defined in: [react-pacer/src/throttler/useThrottledState.ts:44](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottledState.ts#L44) + +A React hook that creates a throttled state value that updates at most once within a specified time window. +This hook combines React's useState with throttling functionality to provide controlled state updates. + +Throttling ensures state updates occur at a controlled rate regardless of how frequently the setter is called. +This is useful for rate-limiting expensive re-renders or operations that depend on rapidly changing state. + +The hook returns a tuple containing: +- The throttled state value +- A throttled setter function that respects the configured wait time +- The throttler instance for additional control + +For more direct control over throttling without state management, +consider using the lower-level useThrottler hook instead. + +## Type Parameters + +• **TValue** + +• **TSelected** = `ThrottlerState`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +## Parameters + +### value + +`TValue` + +### options + +`ThrottlerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`, `Dispatch`\<`SetStateAction`\<`TValue`\>\>, [`ReactThrottler`](../../interfaces/reactthrottler.md)\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>, `TSelected`\>\] + +## Example + +```tsx +// Basic throttling - update state at most once per second +const [value, setValue, throttler] = useThrottledState(0, { wait: 1000 }); + +// With custom leading/trailing behavior +const [value, setValue] = useThrottledState(0, { + wait: 1000, + leading: true, // Update immediately on first change + trailing: false // Skip trailing edge updates +}); + +// Access throttler methods if needed +const handleReset = () => { + setValue(0); + throttler.cancel(); // Cancel any pending updates +}; +``` diff --git a/docs/framework/react/reference/functions/usethrottledvalue.md b/docs/framework/react/reference/functions/usethrottledvalue.md new file mode 100644 index 000000000..614dbf5be --- /dev/null +++ b/docs/framework/react/reference/functions/usethrottledvalue.md @@ -0,0 +1,68 @@ +--- +id: useThrottledValue +title: useThrottledValue +--- + + + +# Function: useThrottledValue() + +```ts +function useThrottledValue( + value, + options, + selector?): [TValue, ReactThrottler>, TSelected>] +``` + +Defined in: [react-pacer/src/throttler/useThrottledValue.ts:36](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottledValue.ts#L36) + +A high-level React hook that creates a throttled version of a value that updates at most once within a specified time window. +This hook uses React's useState internally to manage the throttled state. + +Throttling ensures the value updates occur at a controlled rate regardless of how frequently the input value changes. +This is useful for rate-limiting expensive re-renders or API calls that depend on rapidly changing values. + +The hook returns a tuple containing: +- The throttled value that updates according to the leading/trailing edge behavior specified in the options +- The throttler instance with control methods + +For more direct control over throttling behavior without React state management, +consider using the lower-level useThrottler hook instead. + +## Type Parameters + +• **TValue** + +• **TSelected** = `ThrottlerState`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +## Parameters + +### value + +`TValue` + +### options + +`ThrottlerOptions`\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +\[`TValue`, [`ReactThrottler`](../../interfaces/reactthrottler.md)\<`Dispatch`\<`SetStateAction`\<`TValue`\>\>, `TSelected`\>\] + +## Example + +```tsx +// Basic throttling - update at most once per second +const [throttledValue, throttler] = useThrottledValue(rawValue, { wait: 1000 }); + +// With custom leading/trailing behavior +const [throttledValue, throttler] = useThrottledValue(rawValue, { + wait: 1000, + leading: true, // Update immediately on first change + trailing: false // Skip trailing edge updates +}); +``` diff --git a/docs/framework/react/reference/functions/usethrottler.md b/docs/framework/react/reference/functions/usethrottler.md new file mode 100644 index 000000000..03622e13a --- /dev/null +++ b/docs/framework/react/reference/functions/usethrottler.md @@ -0,0 +1,69 @@ +--- +id: useThrottler +title: useThrottler +--- + + + +# Function: useThrottler() + +```ts +function useThrottler( + fn, + options, +selector?): ReactThrottler +``` + +Defined in: [react-pacer/src/throttler/useThrottler.ts:50](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L50) + +A low-level React hook that creates a `Throttler` instance that limits how often the provided function can execute. + +This hook is designed to be flexible and state-management agnostic - it simply returns a throttler instance that +you can integrate with any state management solution (useState, Redux, Zustand, Jotai, etc). For a simpler and higher-level hook that +integrates directly with React's useState, see useThrottledState. + +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. + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `ThrottlerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`ThrottlerOptions`\<`TFn`\> + +### selector? + +(`state`) => `TSelected` + +## Returns + +[`ReactThrottler`](../../interfaces/reactthrottler.md)\<`TFn`, `TSelected`\> + +## Example + +```tsx +// Basic throttling with custom state +const [value, setValue] = useState(0); +const throttler = useThrottler(setValue, { wait: 1000 }); + +// With any state manager +const throttler = useThrottler( + (value) => stateManager.setState(value), + { + wait: 2000, + leading: true, // Execute immediately on first call + trailing: false // Skip trailing edge updates + } +); +``` diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md index 47a0c1d82..208197fe8 100644 --- a/docs/framework/react/reference/index.md +++ b/docs/framework/react/reference/index.md @@ -1,14 +1,46 @@ --- -id: "@tanstack/react-persister" -title: "@tanstack/react-persister" +id: "@tanstack/react-pacer" +title: "@tanstack/react-pacer" --- -# @tanstack/react-persister +# @tanstack/react-pacer + +## Interfaces + +- [ReactAsyncBatcher](../interfaces/reactasyncbatcher.md) +- [ReactAsyncDebouncer](../interfaces/reactasyncdebouncer.md) +- [ReactAsyncQueuer](../interfaces/reactasyncqueuer.md) +- [ReactAsyncRateLimiter](../interfaces/reactasyncratelimiter.md) +- [ReactAsyncThrottler](../interfaces/reactasyncthrottler.md) +- [ReactBatcher](../interfaces/reactbatcher.md) +- [ReactDebouncer](../interfaces/reactdebouncer.md) +- [ReactQueuer](../interfaces/reactqueuer.md) +- [ReactRateLimiter](../interfaces/reactratelimiter.md) +- [ReactThrottler](../interfaces/reactthrottler.md) ## Functions -- [useLocalStorageState](../functions/uselocalstoragestate.md) -- [useSessionStorageState](../functions/usesessionstoragestate.md) -- [useStoragePersister](../functions/usestoragepersister.md) +- [useAsyncBatcher](../functions/useasyncbatcher.md) +- [useAsyncDebouncer](../functions/useasyncdebouncer.md) +- [useAsyncQueuedState](../functions/useasyncqueuedstate.md) +- [useAsyncQueuer](../functions/useasyncqueuer.md) +- [useAsyncRateLimiter](../functions/useasyncratelimiter.md) +- [useAsyncThrottler](../functions/useasyncthrottler.md) +- [useBatcher](../functions/usebatcher.md) +- [useDebouncedCallback](../functions/usedebouncedcallback.md) +- [useDebouncedState](../functions/usedebouncedstate.md) +- [useDebouncedValue](../functions/usedebouncedvalue.md) +- [useDebouncer](../functions/usedebouncer.md) +- [useQueuedState](../functions/usequeuedstate.md) +- [useQueuedValue](../functions/usequeuedvalue.md) +- [useQueuer](../functions/usequeuer.md) +- [useRateLimitedCallback](../functions/useratelimitedcallback.md) +- [useRateLimitedState](../functions/useratelimitedstate.md) +- [useRateLimitedValue](../functions/useratelimitedvalue.md) +- [useRateLimiter](../functions/useratelimiter.md) +- [useThrottledCallback](../functions/usethrottledcallback.md) +- [useThrottledState](../functions/usethrottledstate.md) +- [useThrottledValue](../functions/usethrottledvalue.md) +- [useThrottler](../functions/usethrottler.md) diff --git a/docs/framework/react/reference/interfaces/reactasyncbatcher.md b/docs/framework/react/reference/interfaces/reactasyncbatcher.md new file mode 100644 index 000000000..62c6d9247 --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactasyncbatcher.md @@ -0,0 +1,34 @@ +--- +id: ReactAsyncBatcher +title: ReactAsyncBatcher +--- + + + +# Interface: ReactAsyncBatcher\ + +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L9) + +## Extends + +- `Omit`\<`AsyncBatcher`\<`TValue`\>, `"store"`\> + +## Type Parameters + +• **TValue** + +• **TSelected** = `AsyncBatcherState`\<`TValue`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +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 + +Use this instead of `batcher.store.state` diff --git a/docs/framework/react/reference/interfaces/reactasyncdebouncer.md b/docs/framework/react/reference/interfaces/reactasyncdebouncer.md new file mode 100644 index 000000000..394bd87ac --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactasyncdebouncer.md @@ -0,0 +1,34 @@ +--- +id: ReactAsyncDebouncer +title: ReactAsyncDebouncer +--- + + + +# Interface: ReactAsyncDebouncer\ + +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L10) + +## Extends + +- `Omit`\<`AsyncDebouncer`\<`TFn`\>, `"store"`\> + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncDebouncerState`\<`TFn`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L19) + +Reactive state that will be updated and re-rendered when the debouncer state changes + +Use this instead of `debouncer.store.state` diff --git a/docs/framework/react/reference/interfaces/reactasyncqueuer.md b/docs/framework/react/reference/interfaces/reactasyncqueuer.md new file mode 100644 index 000000000..096692aac --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactasyncqueuer.md @@ -0,0 +1,34 @@ +--- +id: ReactAsyncQueuer +title: ReactAsyncQueuer +--- + + + +# Interface: ReactAsyncQueuer\ + +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L9) + +## Extends + +- `Omit`\<`AsyncQueuer`\<`TValue`\>, `"store"`\> + +## Type Parameters + +• **TValue** + +• **TSelected** = `AsyncQueuerState`\<`TValue`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L16) + +Reactive state that will be updated and re-rendered when the queuer state changes + +Use this instead of `queuer.store.state` diff --git a/docs/framework/react/reference/interfaces/reactasyncratelimiter.md b/docs/framework/react/reference/interfaces/reactasyncratelimiter.md new file mode 100644 index 000000000..48269c5ae --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactasyncratelimiter.md @@ -0,0 +1,34 @@ +--- +id: ReactAsyncRateLimiter +title: ReactAsyncRateLimiter +--- + + + +# Interface: ReactAsyncRateLimiter\ + +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L10) + +## Extends + +- `Omit`\<`AsyncRateLimiter`\<`TFn`\>, `"store"`\> + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncRateLimiterState`\<`TFn`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:19](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L19) + +Reactive state that will be updated and re-rendered when the rate limiter state changes + +Use this instead of `rateLimiter.store.state` diff --git a/docs/framework/react/reference/interfaces/reactasyncthrottler.md b/docs/framework/react/reference/interfaces/reactasyncthrottler.md new file mode 100644 index 000000000..42e0637f6 --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactasyncthrottler.md @@ -0,0 +1,34 @@ +--- +id: ReactAsyncThrottler +title: ReactAsyncThrottler +--- + + + +# Interface: ReactAsyncThrottler\ + +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L10) + +## Extends + +- `Omit`\<`AsyncThrottler`\<`TFn`\>, `"store"`\> + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncThrottlerState`\<`TFn`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:19](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L19) + +Reactive state that will be updated and re-rendered when the debouncer state changes + +Use this instead of `debouncer.store.state` diff --git a/docs/framework/react/reference/interfaces/reactbatcher.md b/docs/framework/react/reference/interfaces/reactbatcher.md new file mode 100644 index 000000000..b31d099ff --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactbatcher.md @@ -0,0 +1,34 @@ +--- +id: ReactBatcher +title: ReactBatcher +--- + + + +# Interface: ReactBatcher\ + +Defined in: [react-pacer/src/batcher/useBatcher.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L6) + +## Extends + +- `Omit`\<`Batcher`\<`TValue`\>, `"store"`\> + +## Type Parameters + +• **TValue** + +• **TSelected** = `BatcherState`\<`TValue`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +Defined in: [react-pacer/src/batcher/useBatcher.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L13) + +Reactive state that will be updated and re-rendered when the batcher state changes + +Use this instead of `batcher.store.state` diff --git a/docs/framework/react/reference/interfaces/reactdebouncer.md b/docs/framework/react/reference/interfaces/reactdebouncer.md new file mode 100644 index 000000000..8c668b974 --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactdebouncer.md @@ -0,0 +1,34 @@ +--- +id: ReactDebouncer +title: ReactDebouncer +--- + + + +# Interface: ReactDebouncer\ + +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L10) + +## Extends + +- `Omit`\<`Debouncer`\<`TFn`\>, `"store"`\> + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `DebouncerState`\<`TFn`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +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 + +Use this instead of `debouncer.store.state` diff --git a/docs/framework/react/reference/interfaces/reactqueuer.md b/docs/framework/react/reference/interfaces/reactqueuer.md new file mode 100644 index 000000000..f94161eef --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactqueuer.md @@ -0,0 +1,34 @@ +--- +id: ReactQueuer +title: ReactQueuer +--- + + + +# Interface: ReactQueuer\ + +Defined in: [react-pacer/src/queuer/useQueuer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L6) + +## Extends + +- `Omit`\<`Queuer`\<`TValue`\>, `"store"`\> + +## Type Parameters + +• **TValue** + +• **TSelected** = `QueuerState`\<`TValue`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +Defined in: [react-pacer/src/queuer/useQueuer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L13) + +Reactive state that will be updated and re-rendered when the queuer state changes + +Use this instead of `queuer.store.state` diff --git a/docs/framework/react/reference/interfaces/reactratelimiter.md b/docs/framework/react/reference/interfaces/reactratelimiter.md new file mode 100644 index 000000000..17d14694f --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactratelimiter.md @@ -0,0 +1,34 @@ +--- +id: ReactRateLimiter +title: ReactRateLimiter +--- + + + +# Interface: ReactRateLimiter\ + +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L10) + +## Extends + +- `Omit`\<`RateLimiter`\<`TFn`\>, `"store"`\> + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `RateLimiterState` + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +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 + +Use this instead of `rateLimiter.store.state` diff --git a/docs/framework/react/reference/interfaces/reactthrottler.md b/docs/framework/react/reference/interfaces/reactthrottler.md new file mode 100644 index 000000000..d27c92fc1 --- /dev/null +++ b/docs/framework/react/reference/interfaces/reactthrottler.md @@ -0,0 +1,34 @@ +--- +id: ReactThrottler +title: ReactThrottler +--- + + + +# Interface: ReactThrottler\ + +Defined in: [react-pacer/src/throttler/useThrottler.ts:10](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L10) + +## Extends + +- `Omit`\<`Throttler`\<`TFn`\>, `"store"`\> + +## Type Parameters + +• **TFn** *extends* `AnyFunction` + +• **TSelected** = `ThrottlerState`\<`TFn`\> + +## Properties + +### state + +```ts +readonly state: TSelected; +``` + +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 + +Use this instead of `throttler.store.state` diff --git a/docs/framework/solid/reference/functions/createasyncthrottler.md b/docs/framework/solid/reference/functions/createasyncthrottler.md index cafbc5639..57e54807d 100644 --- a/docs/framework/solid/reference/functions/createasyncthrottler.md +++ b/docs/framework/solid/reference/functions/createasyncthrottler.md @@ -8,10 +8,13 @@ title: createAsyncThrottler # Function: createAsyncThrottler() ```ts -function createAsyncThrottler(fn, initialOptions): SolidAsyncThrottler +function createAsyncThrottler( + fn, + initialOptions, +selector?): SolidAsyncThrottler ``` -Defined in: [async-throttler/createAsyncThrottler.ts:79](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L79) +Defined in: [async-throttler/createAsyncThrottler.ts:72](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L72) A low-level Solid hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. @@ -37,6 +40,8 @@ Error Handling: • **TFn** *extends* `AnyAsyncFunction` +• **TSelected** = `AsyncThrottlerState`\<`TFn`\> + ## Parameters ### fn @@ -47,9 +52,13 @@ Error Handling: `AsyncThrottlerOptions`\<`TFn`\> +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidAsyncThrottler`](../../interfaces/solidasyncthrottler.md)\<`TFn`\> +[`SolidAsyncThrottler`](../../interfaces/solidasyncthrottler.md)\<`TFn`, `TSelected`\> ## Example diff --git a/docs/framework/solid/reference/interfaces/solidasyncthrottler.md b/docs/framework/solid/reference/interfaces/solidasyncthrottler.md index 119f2f419..f8c390e48 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncthrottler.md +++ b/docs/framework/solid/reference/interfaces/solidasyncthrottler.md @@ -5,102 +5,30 @@ title: SolidAsyncThrottler -# Interface: SolidAsyncThrottler\ +# Interface: SolidAsyncThrottler\ -Defined in: [async-throttler/createAsyncThrottler.ts:7](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L7) +Defined in: [async-throttler/createAsyncThrottler.ts:10](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L10) ## Extends -- `Omit`\<`AsyncThrottler`\<`TFn`\>, - \| `"getSuccessCount"` - \| `"getSettleCount"` - \| `"getErrorCount"` - \| `"getIsPending"` - \| `"getIsExecuting"` - \| `"getLastResult"` - \| `"getLastExecutionTime"` - \| `"getNextExecutionTime"`\> +- `Omit`\<`AsyncThrottler`\<`TFn`\>, `"store"`\> ## Type Parameters • **TFn** *extends* `AnyAsyncFunction` -## Properties - -### errorCount - -```ts -errorCount: Accessor; -``` - -Defined in: [async-throttler/createAsyncThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L21) - -*** - -### isExecuting - -```ts -isExecuting: Accessor; -``` +• **TSelected** = `AsyncThrottlerState`\<`TFn`\> -Defined in: [async-throttler/createAsyncThrottler.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L23) - -*** - -### isPending - -```ts -isPending: Accessor; -``` - -Defined in: [async-throttler/createAsyncThrottler.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L22) - -*** - -### lastExecutionTime - -```ts -lastExecutionTime: Accessor; -``` - -Defined in: [async-throttler/createAsyncThrottler.ts:25](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L25) - -*** - -### lastResult - -```ts -lastResult: Accessor>; -``` - -Defined in: [async-throttler/createAsyncThrottler.ts:24](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L24) - -*** - -### nextExecutionTime - -```ts -nextExecutionTime: Accessor; -``` - -Defined in: [async-throttler/createAsyncThrottler.ts:26](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L26) - -*** +## Properties -### settleCount +### state ```ts -settleCount: Accessor; +readonly state: Accessor; ``` -Defined in: [async-throttler/createAsyncThrottler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L20) - -*** +Defined in: [async-throttler/createAsyncThrottler.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L19) -### successCount +Reactive state that will be updated and re-rendered when the throttler state changes -```ts -successCount: Accessor; -``` - -Defined in: [async-throttler/createAsyncThrottler.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L19) +Use this instead of `throttler.store.state` diff --git a/docs/reference/classes/asyncbatcher.md b/docs/reference/classes/asyncbatcher.md new file mode 100644 index 000000000..6149c33c8 --- /dev/null +++ b/docs/reference/classes/asyncbatcher.md @@ -0,0 +1,258 @@ +--- +id: AsyncBatcher +title: AsyncBatcher +--- + + + +# Class: AsyncBatcher\ + +Defined in: [async-batcher.ts:180](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L180) + +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 + +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: +- Maximum batch size (number of items per batch) +- Time-based batching (process after X milliseconds) +- Custom batch processing logic via getShouldExecute +- Event callbacks for monitoring batch operations +- Error handling for failed batch operations + +Error Handling: +- If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + +State Management: +- Use `initialState` to provide initial state values when creating the async batcher +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes total items processed, success/error counts, and execution status +- State can be retrieved using `getState()` method + +## Example + +```ts +const batcher = new AsyncBatcher( + async (items) => { + const result = await processItems(items); + console.log('Processing batch:', items); + return result; + }, + { + maxSize: 5, + wait: 2000, + onSuccess: (result) => console.log('Batch succeeded:', result), + onError: (error) => console.error('Batch failed:', error) + } +); + +batcher.addItem(1); +batcher.addItem(2); +// After 2 seconds or when 5 items are added, whichever comes first, +// the batch will be processed and the result will be available +// batcher.execute() // manually trigger a batch +``` + +## Type Parameters + +• **TValue** + +## Constructors + +### new AsyncBatcher() + +```ts +new AsyncBatcher(fn, initialOptions): AsyncBatcher +``` + +Defined in: [async-batcher.ts:187](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L187) + +#### Parameters + +##### fn + +(`items`) => `Promise`\<`any`\> + +##### initialOptions + +[`AsyncBatcherOptions`](../../interfaces/asyncbatcheroptions.md)\<`TValue`\> + +#### Returns + +[`AsyncBatcher`](../asyncbatcher.md)\<`TValue`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [async-batcher.ts:181](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L181) + +## Methods + +### addItem() + +```ts +addItem(item): void +``` + +Defined in: [async-batcher.ts:228](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L228) + +Adds an item to the async batcher +If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed + +#### Parameters + +##### item + +`TValue` + +#### Returns + +`void` + +*** + +### clear() + +```ts +clear(): void +``` + +Defined in: [async-batcher.ts:351](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L351) + +Removes all items from the async batcher + +#### Returns + +`void` + +*** + +### flush() + +```ts +flush(): void +``` + +Defined in: [async-batcher.ts:312](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L312) + +Processes the current batch of items immediately + +#### Returns + +`void` + +*** + +### peekAllItems() + +```ts +peekAllItems(): TValue[] +``` + +Defined in: [async-batcher.ts:340](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L340) + +Returns a copy of all items in the async batcher + +#### Returns + +`TValue`[] + +*** + +### peekFailedItems() + +```ts +peekFailedItems(): TValue[] +``` + +Defined in: [async-batcher.ts:344](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L344) + +#### Returns + +`TValue`[] + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [async-batcher.ts:358](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L358) + +Resets the async batcher state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [async-batcher.ts:202](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L202) + +Updates the async batcher options + +#### Parameters + +##### newOptions + +`Partial`\<[`AsyncBatcherOptions`](../../interfaces/asyncbatcheroptions.md)\<`TValue`\>\> + +#### Returns + +`void` + +*** + +### start() + +```ts +start(): void +``` + +Defined in: [async-batcher.ts:330](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L330) + +Starts the async batcher and processes any pending items + +#### Returns + +`void` + +*** + +### stop() + +```ts +stop(): void +``` + +Defined in: [async-batcher.ts:319](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L319) + +Stops the async batcher from processing batches + +#### Returns + +`void` diff --git a/docs/reference/classes/asyncdebouncer.md b/docs/reference/classes/asyncdebouncer.md new file mode 100644 index 000000000..389105b67 --- /dev/null +++ b/docs/reference/classes/asyncdebouncer.md @@ -0,0 +1,199 @@ +--- +id: AsyncDebouncer +title: AsyncDebouncer +--- + + + +# Class: AsyncDebouncer\ + +Defined in: [async-debouncer.ts:142](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L142) + +A class that creates an async debounced function. + +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. + +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 +- 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 store + +State Management: +- The debouncer uses a reactive store for state management +- Use `initialState` to provide initial state values when creating the async debouncer +- The state includes canLeadingExecute, error count, execution status, and success/settle counts +- State can be accessed via the `store` property and its `state` getter +- The store is reactive and will notify subscribers of state changes + +## Example + +```ts +const asyncDebouncer = new AsyncDebouncer(async (value: string) => { + const results = await searchAPI(value); + return results; // Return value is preserved +}, { + wait: 500, + onError: (error) => { + console.error('Search failed:', error); + } +}); + +// Called on each keystroke but only executes after 500ms of no typing +// Returns the API response directly +const results = await asyncDebouncer.maybeExecute(inputElement.value); +``` + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Constructors + +### new AsyncDebouncer() + +```ts +new AsyncDebouncer(fn, initialOptions): AsyncDebouncer +``` + +Defined in: [async-debouncer.ts:153](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L153) + +#### Parameters + +##### fn + +`TFn` + +##### initialOptions + +[`AsyncDebouncerOptions`](../../interfaces/asyncdebounceroptions.md)\<`TFn`\> + +#### Returns + +[`AsyncDebouncer`](../asyncdebouncer.md)\<`TFn`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [async-debouncer.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L143) + +## Methods + +### cancel() + +```ts +cancel(): void +``` + +Defined in: [async-debouncer.ts:334](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L334) + +Cancels any pending execution or aborts any execution in progress + +#### Returns + +`void` + +*** + +### flush() + +```ts +flush(): void +``` + +Defined in: [async-debouncer.ts:296](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L296) + +Processes the current pending execution immediately + +#### Returns + +`void` + +*** + +### maybeExecute() + +```ts +maybeExecute(...args): Promise> +``` + +Defined in: [async-debouncer.ts:225](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L225) + +Attempts to execute the debounced function. +If a call is already in progress, it will be queued. + +Error Handling: +- If the debounced function throws and no `onError` handler is configured, + the error will be thrown from this method. +- If an `onError` handler is configured, errors will be caught and passed to the handler, + and this method will return undefined. +- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. + +#### Parameters + +##### args + +...`Parameters`\<`TFn`\> + +#### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError + +#### Throws + +The error from the debounced function if no onError handler is configured + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [async-debouncer.ts:343](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L343) + +Resets the debouncer state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [async-debouncer.ts:168](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L168) + +Updates the async debouncer options + +#### Parameters + +##### newOptions + +`Partial`\<[`AsyncDebouncerOptions`](../../interfaces/asyncdebounceroptions.md)\<`TFn`\>\> + +#### Returns + +`void` diff --git a/docs/reference/classes/asyncpersister.md b/docs/reference/classes/asyncpersister.md deleted file mode 100644 index 38edb9804..000000000 --- a/docs/reference/classes/asyncpersister.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -id: AsyncPersister -title: AsyncPersister ---- - - - -# Class: `abstract` AsyncPersister\ - -Defined in: [async-persister.ts:4](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L4) - -Interface for an async persister that can save/load state for a given type - -## Type Parameters - -• **TState** - -## Constructors - -### new AsyncPersister() - -```ts -new AsyncPersister(key): AsyncPersister -``` - -Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L5) - -#### Parameters - -##### key - -`string` - -#### Returns - -[`AsyncPersister`](../asyncpersister.md)\<`TState`\> - -## Properties - -### key - -```ts -readonly key: string; -``` - -Defined in: [async-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L5) - -*** - -### loadState() - -```ts -abstract loadState: () => Promise; -``` - -Defined in: [async-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L7) - -#### Returns - -`Promise`\<`undefined` \| `TState`\> - -*** - -### saveState() - -```ts -abstract saveState: (state) => Promise; -``` - -Defined in: [async-persister.ts:8](https://github.com/TanStack/pacer/blob/main/packages/persister/src/async-persister.ts#L8) - -#### Parameters - -##### state - -`TState` - -#### Returns - -`Promise`\<`void`\> diff --git a/docs/reference/classes/asyncqueuer.md b/docs/reference/classes/asyncqueuer.md new file mode 100644 index 000000000..f0ece5c88 --- /dev/null +++ b/docs/reference/classes/asyncqueuer.md @@ -0,0 +1,383 @@ +--- +id: AsyncQueuer +title: AsyncQueuer +--- + + + +# Class: AsyncQueuer\ + +Defined in: [async-queuer.ts:203](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L203) + +A flexible asynchronous queue for processing tasks with configurable concurrency, priority, and expiration. + +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 +- 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 AsyncQueuer instance + +State Management: +- Use `initialState` to provide initial state values when creating the async queuer +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes error count, expiration count, rejection count, running status, and success/settle counts + +Example usage: +```ts +const asyncQueuer = new AsyncQueuer(async (item) => { + // process item + return item.toUpperCase(); +}, { + concurrency: 2, + onSuccess: (result) => { + console.log(result); + } +}); + +asyncQueuer.addItem('hello'); +asyncQueuer.start(); +``` + +## Type Parameters + +• **TValue** + +## Constructors + +### new AsyncQueuer() + +```ts +new AsyncQueuer(fn, initialOptions): AsyncQueuer +``` + +Defined in: [async-queuer.ts:210](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L210) + +#### Parameters + +##### fn + +(`item`) => `Promise`\<`any`\> + +##### initialOptions + +[`AsyncQueuerOptions`](../../interfaces/asyncqueueroptions.md)\<`TValue`\> = `{}` + +#### Returns + +[`AsyncQueuer`](../asyncqueuer.md)\<`TValue`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [async-queuer.ts:204](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L204) + +## Methods + +### addItem() + +```ts +addItem( + item, + position, + runOnItemsChange): boolean +``` + +Defined in: [async-queuer.ts:344](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L344) + +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. + +#### Parameters + +##### item + +`TValue` + +##### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `...` + +##### runOnItemsChange + +`boolean` = `true` + +#### Returns + +`boolean` + +#### Example + +```ts +queuer.addItem({ value: 'task', priority: 10 }); +queuer.addItem('task2', 'front'); +``` + +*** + +### clear() + +```ts +clear(): void +``` + +Defined in: [async-queuer.ts:625](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L625) + +Removes all pending items from the queue. Does not affect active tasks. + +#### Returns + +`void` + +*** + +### execute() + +```ts +execute(position?): Promise +``` + +Defined in: [async-queuer.ts:464](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L464) + +Removes and returns the next item from the queue and executes the task function with it. + +#### Parameters + +##### position? + +[`QueuePosition`](../../type-aliases/queueposition.md) + +#### Returns + +`Promise`\<`any`\> + +#### Example + +```ts +queuer.execute(); +// LIFO +queuer.execute('back'); +``` + +*** + +### flush() + +```ts +flush(numberOfItems, position?): void +``` + +Defined in: [async-queuer.ts:499](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L499) + +Processes a specified number of items to execute immediately with no wait time +If no numberOfItems is provided, all items will be processed + +#### Parameters + +##### numberOfItems + +`number` = `...` + +##### position? + +[`QueuePosition`](../../type-aliases/queueposition.md) + +#### Returns + +`void` + +*** + +### getNextItem() + +```ts +getNextItem(position): undefined | TValue +``` + +Defined in: [async-queuer.ts:423](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L423) + +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. + +#### Parameters + +##### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `...` + +#### Returns + +`undefined` \| `TValue` + +#### Example + +```ts +// FIFO +queuer.getNextItem(); +// LIFO +queuer.getNextItem('back'); +``` + +*** + +### peekActiveItems() + +```ts +peekActiveItems(): TValue[] +``` + +Defined in: [async-queuer.ts:592](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L592) + +Returns the items currently being processed (active tasks). + +#### Returns + +`TValue`[] + +*** + +### peekAllItems() + +```ts +peekAllItems(): TValue[] +``` + +Defined in: [async-queuer.ts:585](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L585) + +Returns a copy of all items in the queue, including active and pending items. + +#### Returns + +`TValue`[] + +*** + +### peekNextItem() + +```ts +peekNextItem(position): undefined | TValue +``` + +Defined in: [async-queuer.ts:575](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L575) + +Returns the next item in the queue without removing it. + +#### Parameters + +##### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `'front'` + +#### Returns + +`undefined` \| `TValue` + +#### Example + +```ts +queuer.peekNextItem(); // front +queuer.peekNextItem('back'); // back +``` + +*** + +### peekPendingItems() + +```ts +peekPendingItems(): TValue[] +``` + +Defined in: [async-queuer.ts:599](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L599) + +Returns the items waiting to be processed (pending tasks). + +#### Returns + +`TValue`[] + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [async-queuer.ts:633](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L633) + +Resets the queuer state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [async-queuer.ts:242](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L242) + +Updates the queuer options. New options are merged with existing options. + +#### Parameters + +##### newOptions + +`Partial`\<[`AsyncQueuerOptions`](../../interfaces/asyncqueueroptions.md)\<`TValue`\>\> + +#### Returns + +`void` + +*** + +### start() + +```ts +start(): void +``` + +Defined in: [async-queuer.ts:606](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L606) + +Starts processing items in the queue. If already running, does nothing. + +#### Returns + +`void` + +*** + +### stop() + +```ts +stop(): void +``` + +Defined in: [async-queuer.ts:616](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L616) + +Stops processing items in the queue. Does not clear the queue. + +#### Returns + +`void` diff --git a/docs/reference/classes/asyncratelimiter.md b/docs/reference/classes/asyncratelimiter.md new file mode 100644 index 000000000..2654cf94d --- /dev/null +++ b/docs/reference/classes/asyncratelimiter.md @@ -0,0 +1,229 @@ +--- +id: AsyncRateLimiter +title: AsyncRateLimiter +--- + + + +# Class: AsyncRateLimiter\ + +Defined in: [async-rate-limiter.ts:161](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L161) + +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, +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: +- '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) + +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. + +State Management: +- Use `initialState` to provide initial state values when creating the rate limiter +- `initialState` can be a partial state object or a Promise that resolves to a partial state +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution times, success/error counts, and current execution status +- State can be retrieved using `getState()` method + +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 +const rateLimiter = new AsyncRateLimiter( + async (id: string) => await api.getData(id), + { + limit: 5, + window: 1000, + windowType: 'sliding', + onError: (error) => { + console.error('API call failed:', error); + }, + onReject: (limiter) => { + console.log(`Rate limit exceeded. Try again in ${limiter.getMsUntilNextWindow()}ms`); + } + } +); + +// Will execute immediately until limit reached, then block +// Returns the API response directly +const data = await rateLimiter.maybeExecute('123'); +``` + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Constructors + +### new AsyncRateLimiter() + +```ts +new AsyncRateLimiter(fn, initialOptions): AsyncRateLimiter +``` + +Defined in: [async-rate-limiter.ts:167](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L167) + +#### Parameters + +##### fn + +`TFn` + +##### initialOptions + +[`AsyncRateLimiterOptions`](../../interfaces/asyncratelimiteroptions.md)\<`TFn`\> + +#### Returns + +[`AsyncRateLimiter`](../asyncratelimiter.md)\<`TFn`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [async-rate-limiter.ts:162](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L162) + +## Methods + +### getMsUntilNextWindow() + +```ts +getMsUntilNextWindow(): number +``` + +Defined in: [async-rate-limiter.ts:340](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L340) + +Returns the number of milliseconds until the next execution will be possible +For fixed windows, this is the time until the current window resets +For sliding windows, this is the time until the oldest execution expires + +#### Returns + +`number` + +*** + +### getRemainingInWindow() + +```ts +getRemainingInWindow(): number +``` + +Defined in: [async-rate-limiter.ts:330](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L330) + +Returns the number of remaining executions allowed in the current window + +#### Returns + +`number` + +*** + +### maybeExecute() + +```ts +maybeExecute(...args): Promise> +``` + +Defined in: [async-rate-limiter.ts:242](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L242) + +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. + +Error Handling: +- If the rate-limited function throws and no `onError` handler is configured, + the error will be thrown from this method. +- If an `onError` handler is configured, errors will be caught and passed to the handler, + and this method will return undefined. +- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. + +#### Parameters + +##### args + +...`Parameters`\<`TFn`\> + +#### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError + +#### Throws + +The error from the rate-limited function if no onError handler is configured + +#### Example + +```ts +const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 }); + +// First 5 calls will return a promise that resolves with the result +const result = await rateLimiter.maybeExecute('arg1', 'arg2'); + +// Additional calls within the window will return undefined +const result2 = await rateLimiter.maybeExecute('arg1', 'arg2'); // undefined +``` + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [async-rate-limiter.ts:351](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L351) + +Resets the rate limiter state + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [async-rate-limiter.ts:182](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L182) + +Updates the async rate limiter options + +#### Parameters + +##### newOptions + +`Partial`\<[`AsyncRateLimiterOptions`](../../interfaces/asyncratelimiteroptions.md)\<`TFn`\>\> + +#### Returns + +`void` diff --git a/docs/reference/classes/asyncthrottler.md b/docs/reference/classes/asyncthrottler.md new file mode 100644 index 000000000..ed295d5a2 --- /dev/null +++ b/docs/reference/classes/asyncthrottler.md @@ -0,0 +1,204 @@ +--- +id: AsyncThrottler +title: AsyncThrottler +--- + + + +# Class: AsyncThrottler\ + +Defined in: [async-throttler.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L146) + +A class that creates an async throttled function. + +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. + +Error Handling: +- If an `onError` handler is provided, it will be called with the error and throttler 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 AsyncThrottler instance + +State Management: +- Use `initialState` to provide initial state values when creating the async throttler +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes error count, execution status, last execution time, and success/settle counts +- State can be retrieved using `getState()` method + +## Example + +```ts +const throttler = new AsyncThrottler(async (value: string) => { + const result = await saveToAPI(value); + return result; // Return value is preserved +}, { + wait: 1000, + onError: (error) => { + console.error('API call failed:', error); + } +}); + +// Will only execute once per second no matter how often called +// Returns the API response directly +const result = await throttler.maybeExecute(inputElement.value); +``` + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Constructors + +### new AsyncThrottler() + +```ts +new AsyncThrottler(fn, initialOptions): AsyncThrottler +``` + +Defined in: [async-throttler.ts:157](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L157) + +#### Parameters + +##### fn + +`TFn` + +##### initialOptions + +[`AsyncThrottlerOptions`](../../interfaces/asyncthrottleroptions.md)\<`TFn`\> + +#### Returns + +[`AsyncThrottler`](../asyncthrottler.md)\<`TFn`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [async-throttler.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L147) + +## Methods + +### cancel() + +```ts +cancel(): void +``` + +Defined in: [async-throttler.ts:366](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L366) + +Cancels any pending execution or aborts any execution in progress + +#### Returns + +`void` + +*** + +### flush() + +```ts +flush(): void +``` + +Defined in: [async-throttler.ts:321](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L321) + +Processes the current pending execution immediately + +#### Returns + +`void` + +*** + +### maybeExecute() + +```ts +maybeExecute(...args): Promise> +``` + +Defined in: [async-throttler.ts:237](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L237) + +Attempts to execute the throttled function. The execution behavior depends on the throttler options: + +- If enough time has passed since the last execution (>= wait period): + - With leading=true: Executes immediately + - With leading=false: Waits for the next trailing execution + +- If within the wait period: + - With trailing=true: Schedules execution for end of wait period + - With trailing=false: Drops the execution + +#### Parameters + +##### args + +...`Parameters`\<`TFn`\> + +#### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +#### Example + +```ts +const throttled = new AsyncThrottler(fn, { wait: 1000 }); + +// First call executes immediately +await throttled.maybeExecute('a', 'b'); + +// Call during wait period - gets throttled +await throttled.maybeExecute('c', 'd'); +``` + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [async-throttler.ts:374](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L374) + +Resets the debouncer state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [async-throttler.ts:172](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L172) + +Updates the async throttler options + +#### Parameters + +##### newOptions + +`Partial`\<[`AsyncThrottlerOptions`](../../interfaces/asyncthrottleroptions.md)\<`TFn`\>\> + +#### Returns + +`void` diff --git a/docs/reference/classes/batcher.md b/docs/reference/classes/batcher.md new file mode 100644 index 000000000..1654fe4d3 --- /dev/null +++ b/docs/reference/classes/batcher.md @@ -0,0 +1,224 @@ +--- +id: Batcher +title: Batcher +--- + + + +# Class: Batcher\ + +Defined in: [batcher.ts:115](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L115) + +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. + +The Batcher provides a flexible way to implement batching with configurable: +- Maximum batch size (number of items per batch) +- Time-based batching (process after X milliseconds) +- Custom batch processing logic via getShouldExecute +- Event callbacks for monitoring batch operations + +State Management: +- Use `initialState` to provide initial state values when creating the batcher +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes batch execution count, total items processed, items, and running status +- State can be retrieved using `getState()` method + +## Example + +```ts +const batcher = new Batcher( + (items) => console.log('Processing batch:', items), + { + maxSize: 5, + wait: 2000, + onExecuteBatch: (items) => console.log('Batch executed:', items) + } +); + +batcher.addItem(1); +batcher.addItem(2); +// After 2 seconds or when 5 items are added, whichever comes first, +// the batch will be processed +// batcher.execute() // manually trigger a batch +``` + +## Type Parameters + +• **TValue** + +## Constructors + +### new Batcher() + +```ts +new Batcher(fn, initialOptions): Batcher +``` + +Defined in: [batcher.ts:122](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L122) + +#### Parameters + +##### fn + +(`items`) => `void` + +##### initialOptions + +[`BatcherOptions`](../../interfaces/batcheroptions.md)\<`TValue`\> + +#### Returns + +[`Batcher`](../batcher.md)\<`TValue`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [batcher.ts:116](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L116) + +## Methods + +### addItem() + +```ts +addItem(item): void +``` + +Defined in: [batcher.ts:162](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L162) + +Adds an item to the batcher +If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed + +#### Parameters + +##### item + +`TValue` + +#### Returns + +`void` + +*** + +### clear() + +```ts +clear(): void +``` + +Defined in: [batcher.ts:253](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L253) + +Removes all items from the batcher + +#### Returns + +`void` + +*** + +### flush() + +```ts +flush(): void +``` + +Defined in: [batcher.ts:218](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L218) + +Processes the current batch of items immediately + +#### Returns + +`void` + +*** + +### peekAllItems() + +```ts +peekAllItems(): TValue[] +``` + +Defined in: [batcher.ts:246](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L246) + +Returns a copy of all items in the batcher + +#### Returns + +`TValue`[] + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [batcher.ts:260](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L260) + +Resets the batcher state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [batcher.ts:136](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L136) + +Updates the batcher options + +#### Parameters + +##### newOptions + +`Partial`\<[`BatcherOptions`](../../interfaces/batcheroptions.md)\<`TValue`\>\> + +#### Returns + +`void` + +*** + +### start() + +```ts +start(): void +``` + +Defined in: [batcher.ts:236](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L236) + +Starts the batcher and processes any pending items + +#### Returns + +`void` + +*** + +### stop() + +```ts +stop(): void +``` + +Defined in: [batcher.ts:225](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L225) + +Stops the batcher from processing batches + +#### Returns + +`void` diff --git a/docs/reference/classes/debouncer.md b/docs/reference/classes/debouncer.md new file mode 100644 index 000000000..7a1222a5d --- /dev/null +++ b/docs/reference/classes/debouncer.md @@ -0,0 +1,170 @@ +--- +id: Debouncer +title: Debouncer +--- + + + +# Class: Debouncer\ + +Defined in: [debouncer.ts:101](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L101) + +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. + +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 +will reset the timer. + +State Management: +- Use `initialState` to provide initial state values when creating the debouncer +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes canLeadingExecute, execution count, and isPending status +- State can be retrieved using `getState()` method + +## Example + +```ts +const debouncer = new Debouncer((value: string) => { + saveToDatabase(value); +}, { wait: 500 }); + +// Will only save after 500ms of no new input +inputElement.addEventListener('input', () => { + debouncer.maybeExecute(inputElement.value); +}); +``` + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Constructors + +### new Debouncer() + +```ts +new Debouncer(fn, initialOptions): Debouncer +``` + +Defined in: [debouncer.ts:108](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L108) + +#### Parameters + +##### fn + +`TFn` + +##### initialOptions + +[`DebouncerOptions`](../../interfaces/debounceroptions.md)\<`TFn`\> + +#### Returns + +[`Debouncer`](../debouncer.md)\<`TFn`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [debouncer.ts:102](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L102) + +## Methods + +### cancel() + +```ts +cancel(): void +``` + +Defined in: [debouncer.ts:214](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L214) + +Cancels any pending execution + +#### Returns + +`void` + +*** + +### flush() + +```ts +flush(): void +``` + +Defined in: [debouncer.ts:205](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L205) + +Processes the current pending execution immediately + +#### Returns + +`void` + +*** + +### maybeExecute() + +```ts +maybeExecute(...args): void +``` + +Defined in: [debouncer.ts:163](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L163) + +Attempts to execute the debounced function +If a call is already in progress, it will be queued + +#### Parameters + +##### args + +...`Parameters`\<`TFn`\> + +#### Returns + +`void` + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [debouncer.ts:227](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L227) + +Resets the debouncer state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [debouncer.ts:122](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L122) + +Updates the debouncer options + +#### Parameters + +##### newOptions + +`Partial`\<[`DebouncerOptions`](../../interfaces/debounceroptions.md)\<`TFn`\>\> + +#### Returns + +`void` diff --git a/docs/reference/classes/persister.md b/docs/reference/classes/persister.md deleted file mode 100644 index 45fa9d9b5..000000000 --- a/docs/reference/classes/persister.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -id: Persister -title: Persister ---- - - - -# Class: `abstract` Persister\ - -Defined in: [persister.ts:23](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L23) - -Abstract class that defines the contract for a state persister implementation. -A persister is responsible for loading and saving state to a storage medium. - -## Example - -```ts -class MyPersister extends Persister { - constructor() { - super(key) - } - - loadState(): MyState | undefined { - // Load state from storage - return state - } - - saveState(, state: MyState): void { - // Save state to storage - } -} -``` - -## Extended by - -- [`StoragePersister`](../storagepersister.md) - -## Type Parameters - -• **TState** - -## Constructors - -### new Persister() - -```ts -new Persister(key): Persister -``` - -Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L24) - -#### Parameters - -##### key - -`string` - -#### Returns - -[`Persister`](../persister.md)\<`TState`\> - -## Properties - -### key - -```ts -readonly key: string; -``` - -Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L24) - -*** - -### loadState() - -```ts -abstract loadState: () => undefined | TState; -``` - -Defined in: [persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L26) - -#### Returns - -`undefined` \| `TState` - -*** - -### saveState() - -```ts -abstract saveState: (state) => void; -``` - -Defined in: [persister.ts:27](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L27) - -#### Parameters - -##### state - -`TState` - -#### Returns - -`void` diff --git a/docs/reference/classes/queuer.md b/docs/reference/classes/queuer.md new file mode 100644 index 000000000..60563a63c --- /dev/null +++ b/docs/reference/classes/queuer.md @@ -0,0 +1,373 @@ +--- +id: Queuer +title: Queuer +--- + + + +# Class: Queuer\ + +Defined in: [queuer.ts:202](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L202) + +A flexible queue that processes items with configurable wait times, expiration, and priority. + +Features: +- Automatic or manual processing of items +- FIFO (First In First Out), LIFO (Last In First Out), or double-ended queue behavior +- Priority-based ordering when getPriority is provided +- Item expiration and removal of stale items +- Callbacks for queue state changes, execution, rejection, and expiration + +Running behavior: +- `start()`: Begins automatically processing items in the queue (defaults to isRunning) +- `stop()`: Pauses processing but maintains queue state +- `wait`: Configurable delay between processing items +- `onItemsChange`/`onExecute`: Callbacks for monitoring queue state + +Manual processing is also supported when automatic processing is disabled: +- `execute()`: Processes the next item using the provided function +- `getNextItem()`: Removes and returns the next item without processing + +Queue behavior defaults to FIFO: +- `addItem(item)`: Adds to the back of the queue +- Items processed from the front of the queue + +Priority queue: +- Provide a `getPriority` function; higher values are processed first + +Stack (LIFO): +- `addItem(item, 'back')`: Adds to the back +- `getNextItem('back')`: Removes from the back + +Double-ended queue: +- `addItem(item, position)`: Adds to specified position ('front'/'back') +- `getNextItem(position)`: Removes from specified position + +Item expiration: +- `expirationDuration`: Maximum time items can stay in the queue +- `getIsExpired`: Function to override default expiration +- `onExpire`: Callback for expired items + +State Management: +- Use `initialState` to provide initial state values when creating the queuer +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution count, expiration count, rejection count, and isRunning status +- State can be retrieved using `getState()` method + +Example usage: +```ts +// Auto-processing queue with wait time +const autoQueue = new Queuer((n) => console.log(n), { + started: true, // Begin processing immediately + wait: 1000, // Wait 1s between items + onExecute: (item) => console.log(`Processed ${item}`) +}); +autoQueue.addItem(1); // Will process after 1s +autoQueue.addItem(2); // Will process 1s after first item + +// Manual processing queue +const manualQueue = new Queuer((n) => console.log(n), { + started: false +}); +manualQueue.addItem(1); // [1] +manualQueue.addItem(2); // [1, 2] +manualQueue.execute(); // logs 1, queue is [2] +manualQueue.getNextItem(); // returns 2, queue is empty +``` + +## Type Parameters + +• **TValue** + +## Constructors + +### new Queuer() + +```ts +new Queuer(fn, initialOptions): Queuer +``` + +Defined in: [queuer.ts:209](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L209) + +#### Parameters + +##### fn + +(`item`) => `void` + +##### initialOptions + +[`QueuerOptions`](../../interfaces/queueroptions.md)\<`TValue`\> = `{}` + +#### Returns + +[`Queuer`](../queuer.md)\<`TValue`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [queuer.ts:203](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L203) + +## Methods + +### addItem() + +```ts +addItem( + item, + position, + runOnItemsChange): boolean +``` + +Defined in: [queuer.ts:323](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L323) + +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. + +Returns true if the item was added, false if the queue is full. + +Example usage: +```ts +queuer.addItem('task'); +queuer.addItem('task2', 'front'); +``` + +#### Parameters + +##### item + +`TValue` + +##### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `...` + +##### runOnItemsChange + +`boolean` = `true` + +#### Returns + +`boolean` + +*** + +### clear() + +```ts +clear(): void +``` + +Defined in: [queuer.ts:574](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L574) + +Removes all pending items from the queue. Does not affect items being processed. + +#### Returns + +`void` + +*** + +### execute() + +```ts +execute(position?): undefined | TValue +``` + +Defined in: [queuer.ts:444](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L444) + +Removes and returns the next item from the queue and processes it using the provided function. + +Example usage: +```ts +queuer.execute(); +// LIFO +queuer.execute('back'); +``` + +#### Parameters + +##### position? + +[`QueuePosition`](../../type-aliases/queueposition.md) + +#### Returns + +`undefined` \| `TValue` + +*** + +### flush() + +```ts +flush(numberOfItems, position?): void +``` + +Defined in: [queuer.ts:460](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L460) + +Processes a specified number of items to execute immediately with no wait time +If no numberOfItems is provided, all items will be processed + +#### Parameters + +##### numberOfItems + +`number` = `...` + +##### position? + +[`QueuePosition`](../../type-aliases/queueposition.md) + +#### Returns + +`void` + +*** + +### getNextItem() + +```ts +getNextItem(position): undefined | TValue +``` + +Defined in: [queuer.ts:403](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L403) + +Removes and returns the next item from the queue without executing the function. +Use for manual queue management. Normally, use execute() to process items. + +Example usage: +```ts +// FIFO +queuer.getNextItem(); +// LIFO +queuer.getNextItem('back'); +``` + +#### Parameters + +##### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `...` + +#### Returns + +`undefined` \| `TValue` + +*** + +### peekAllItems() + +```ts +peekAllItems(): TValue[] +``` + +Defined in: [queuer.ts:546](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L546) + +Returns a copy of all items in the queue. + +#### Returns + +`TValue`[] + +*** + +### peekNextItem() + +```ts +peekNextItem(position): undefined | TValue +``` + +Defined in: [queuer.ts:536](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L536) + +Returns the next item in the queue without removing it. + +Example usage: +```ts +queuer.peekNextItem(); // front +queuer.peekNextItem('back'); // back +``` + +#### Parameters + +##### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `'front'` + +#### Returns + +`undefined` \| `TValue` + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [queuer.ts:582](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L582) + +Resets the queuer state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [queuer.ts:240](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L240) + +Updates the queuer options. New options are merged with existing options. + +#### Parameters + +##### newOptions + +`Partial`\<[`QueuerOptions`](../../interfaces/queueroptions.md)\<`TValue`\>\> + +#### Returns + +`void` + +*** + +### start() + +```ts +start(): void +``` + +Defined in: [queuer.ts:553](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L553) + +Starts processing items in the queue. If already isRunning, does nothing. + +#### Returns + +`void` + +*** + +### stop() + +```ts +stop(): void +``` + +Defined in: [queuer.ts:563](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L563) + +Stops processing items in the queue. Does not clear the queue. + +#### Returns + +`void` diff --git a/docs/reference/classes/ratelimiter.md b/docs/reference/classes/ratelimiter.md new file mode 100644 index 000000000..b61158d48 --- /dev/null +++ b/docs/reference/classes/ratelimiter.md @@ -0,0 +1,194 @@ +--- +id: RateLimiter +title: RateLimiter +--- + + + +# Class: RateLimiter\ + +Defined in: [rate-limiter.ts:110](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L110) + +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. + +The rate limiter supports two types of windows: +- '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. + +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) + +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. + +State Management: +- Use `initialState` to provide initial state values when creating the rate limiter +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution count, execution times, and rejection count +- State can be retrieved using `getState()` method + +## Example + +```ts +const rateLimiter = new RateLimiter( + (id: string) => api.getData(id), + { + limit: 5, + window: 1000, + windowType: 'sliding', + } +); + +// Will execute immediately until limit reached, then block +rateLimiter.maybeExecute('123'); +``` + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Constructors + +### new RateLimiter() + +```ts +new RateLimiter(fn, initialOptions): RateLimiter +``` + +Defined in: [rate-limiter.ts:116](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L116) + +#### Parameters + +##### fn + +`TFn` + +##### initialOptions + +[`RateLimiterOptions`](../../interfaces/ratelimiteroptions.md)\<`TFn`\> + +#### Returns + +[`RateLimiter`](../ratelimiter.md)\<`TFn`\> + +## Properties + +### store + +```ts +readonly store: Store; +``` + +Defined in: [rate-limiter.ts:111](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L111) + +## Methods + +### getMsUntilNextWindow() + +```ts +getMsUntilNextWindow(): number +``` + +Defined in: [rate-limiter.ts:251](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L251) + +Returns the number of milliseconds until the next execution will be possible + +#### Returns + +`number` + +*** + +### getRemainingInWindow() + +```ts +getRemainingInWindow(): number +``` + +Defined in: [rate-limiter.ts:243](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L243) + +Returns the number of remaining executions allowed in the current window + +#### Returns + +`number` + +*** + +### maybeExecute() + +```ts +maybeExecute(...args): boolean +``` + +Defined in: [rate-limiter.ts:180](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L180) + +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. + +#### Parameters + +##### args + +...`Parameters`\<`TFn`\> + +#### Returns + +`boolean` + +#### Example + +```ts +const rateLimiter = new RateLimiter(fn, { limit: 5, window: 1000 }); + +// First 5 calls will return true +rateLimiter.maybeExecute('arg1', 'arg2'); // true + +// Additional calls within the window will return false +rateLimiter.maybeExecute('arg1', 'arg2'); // false +``` + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [rate-limiter.ts:262](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L262) + +Resets the rate limiter state + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [rate-limiter.ts:130](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L130) + +Updates the rate limiter options + +#### Parameters + +##### newOptions + +`Partial`\<[`RateLimiterOptions`](../../interfaces/ratelimiteroptions.md)\<`TFn`\>\> + +#### Returns + +`void` diff --git a/docs/reference/classes/storagepersister.md b/docs/reference/classes/storagepersister.md deleted file mode 100644 index 6122b09ff..000000000 --- a/docs/reference/classes/storagepersister.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -id: StoragePersister -title: StoragePersister ---- - - - -# Class: StoragePersister\ - -Defined in: [storage-persister.ts:116](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L116) - -A persister that saves state to browser local/session storage. - -The persister can use either localStorage (persists across browser sessions) or -sessionStorage (cleared when browser tab/window closes). State is automatically -serialized to JSON when saving and deserialized when loading. - -Optionally, a `buster` string can be provided to force cache busting by storing it in the value. -Optionally, a `maxAge` (in ms) can be provided to expire the stored state after a certain duration. -Optionally, callbacks can be provided to run after state is saved or loaded. - -## Example - -```ts -const persister = new StoragePersister({ - // required - key: 'my-rate-limiter', - storage: window.localStorage, - // optional - buster: 'v2', - maxAge: 1000 * 60 * 60, // 1 hour - stateTransform: (state) => ({ - // Only persist specific parts of the state - count: state.count, - lastReset: state.lastReset, - // Exclude sensitive or temporary data - }), - onSaveState: (key, state) => console.log('State saved:', key, state), - onLoadState: (key, state) => console.log('State loaded:', key, state), - onLoadStateError: (key, error) => console.error('Error loading state:', key, error), - onSaveStateError: (key, error) => console.error('Error saving state:', key, error) -}) -``` - -## Extends - -- [`Persister`](../persister.md)\<`TState`\> - -## Type Parameters - -• **TState** - -## Constructors - -### new StoragePersister() - -```ts -new StoragePersister(options): StoragePersister -``` - -Defined in: [storage-persister.ts:119](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L119) - -#### Parameters - -##### options - -[`StoragePersisterOptions`](../../interfaces/storagepersisteroptions.md)\<`TState`\> - -#### Returns - -[`StoragePersister`](../storagepersister.md)\<`TState`\> - -#### Overrides - -[`Persister`](../persister.md).[`constructor`](../Persister.md#constructors) - -## Properties - -### key - -```ts -readonly key: string; -``` - -Defined in: [persister.ts:24](https://github.com/TanStack/pacer/blob/main/packages/persister/src/persister.ts#L24) - -#### Inherited from - -[`Persister`](../persister.md).[`key`](../Persister.md#key-1) - -## Methods - -### getOptions() - -```ts -getOptions(): StoragePersisterOptions -``` - -Defined in: [storage-persister.ts:137](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L137) - -Returns the current persister options - -#### Returns - -[`StoragePersisterOptions`](../../interfaces/storagepersisteroptions.md)\<`TState`\> - -*** - -### loadState() - -```ts -loadState(): undefined | TState -``` - -Defined in: [storage-persister.ts:167](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L167) - -Loads the state from storage - -#### Returns - -`undefined` \| `TState` - -#### Overrides - -```ts -Persister.loadState -``` - -*** - -### saveState() - -```ts -saveState(state): void -``` - -Defined in: [storage-persister.ts:144](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L144) - -Saves the state to storage - -#### Parameters - -##### state - -`TState` - -#### Returns - -`void` - -#### Overrides - -```ts -Persister.saveState -``` - -*** - -### setOptions() - -```ts -setOptions(newOptions): void -``` - -Defined in: [storage-persister.ts:130](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L130) - -Updates the persister options - -#### Parameters - -##### newOptions - -`Partial`\<[`StoragePersisterOptions`](../../interfaces/storagepersisteroptions.md)\<`TState`\>\> - -#### Returns - -`void` diff --git a/docs/reference/classes/throttler.md b/docs/reference/classes/throttler.md new file mode 100644 index 000000000..675b32b50 --- /dev/null +++ b/docs/reference/classes/throttler.md @@ -0,0 +1,199 @@ +--- +id: Throttler +title: Throttler +--- + + + +# Class: Throttler\ + +Defined in: [throttler.ts:106](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L106) + +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. + +Supports both leading and trailing edge execution: +- Leading: Execute immediately on first call (default: true) +- Trailing: Execute after wait period if called during throttle (default: true) + +For collapsing rapid-fire events where you only care about the last call, consider using Debouncer. + +State Management: +- Use `initialState` to provide initial state values when creating the throttler +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution count and last execution time +- State can be retrieved using `getState()` method + +## Example + +```ts +const throttler = new Throttler( + (id: string) => api.getData(id), + { wait: 1000 } // Execute at most once per second +); + +// First call executes immediately +throttler.maybeExecute('123'); + +// Subsequent calls within 1000ms are throttled +throttler.maybeExecute('123'); // Throttled +``` + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Constructors + +### new Throttler() + +```ts +new Throttler(fn, initialOptions): Throttler +``` + +Defined in: [throttler.ts:113](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L113) + +#### Parameters + +##### fn + +`TFn` + +##### initialOptions + +[`ThrottlerOptions`](../../interfaces/throttleroptions.md)\<`TFn`\> + +#### Returns + +[`Throttler`](../throttler.md)\<`TFn`\> + +## Properties + +### store + +```ts +readonly store: Store>; +``` + +Defined in: [throttler.ts:107](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L107) + +## Methods + +### cancel() + +```ts +cancel(): void +``` + +Defined in: [throttler.ts:245](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L245) + +Cancels any pending trailing execution and clears internal state. + +If a trailing execution is scheduled (due to throttling with trailing=true), +this will prevent that execution from occurring. The internal timeout and +stored arguments will be cleared. + +Has no effect if there is no pending execution. + +#### Returns + +`void` + +*** + +### flush() + +```ts +flush(): void +``` + +Defined in: [throttler.ts:230](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L230) + +Processes the current pending execution immediately + +#### Returns + +`void` + +*** + +### maybeExecute() + +```ts +maybeExecute(...args): void +``` + +Defined in: [throttler.ts:180](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L180) + +Attempts to execute the throttled function. The execution behavior depends on the throttler options: + +- If enough time has passed since the last execution (>= wait period): + - With leading=true: Executes immediately + - With leading=false: Waits for the next trailing execution + +- If within the wait period: + - With trailing=true: Schedules execution for end of wait period + - With trailing=false: Drops the execution + +#### Parameters + +##### args + +...`Parameters`\<`TFn`\> + +#### Returns + +`void` + +#### Example + +```ts +const throttled = new Throttler(fn, { wait: 1000 }); + +// First call executes immediately +throttled.maybeExecute('a', 'b'); + +// Call during wait period - gets throttled +throttled.maybeExecute('c', 'd'); +``` + +*** + +### reset() + +```ts +reset(): void +``` + +Defined in: [throttler.ts:259](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L259) + +Resets the throttler state to its default values + +#### Returns + +`void` + +*** + +### setOptions() + +```ts +setOptions(newOptions): void +``` + +Defined in: [throttler.ts:127](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L127) + +Updates the throttler options + +#### Parameters + +##### newOptions + +`Partial`\<[`ThrottlerOptions`](../../interfaces/throttleroptions.md)\<`TFn`\>\> + +#### Returns + +`void` diff --git a/docs/reference/functions/asyncbatch.md b/docs/reference/functions/asyncbatch.md new file mode 100644 index 000000000..620d4aeea --- /dev/null +++ b/docs/reference/functions/asyncbatch.md @@ -0,0 +1,87 @@ +--- +id: asyncBatch +title: asyncBatch +--- + + + +# Function: asyncBatch() + +```ts +function asyncBatch(fn, options): (item) => void +``` + +Defined in: [async-batcher.ts:405](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L405) + +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 + +Error Handling: +- If an `onError` handler is provided, it will be called with the error and batcher 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 AsyncBatcher instance + +State Management: +- Use `initialState` to provide initial state values when creating the async batcher +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes total items processed, success/error counts, and execution status + +## Type Parameters + +• **TValue** + +## Parameters + +### fn + +(`items`) => `Promise`\<`any`\> + +### options + +[`AsyncBatcherOptions`](../../interfaces/asyncbatcheroptions.md)\<`TValue`\> + +## Returns + +`Function` + +Adds an item to the async batcher +If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed + +### Parameters + +#### item + +`TValue` + +### Returns + +`void` + +## Example + +```ts +const batchItems = asyncBatch( + async (items) => { + const result = await processApiCall(items); + console.log('Processing:', items); + return result; + }, + { + maxSize: 3, + wait: 1000, + onSuccess: (result) => console.log('Batch succeeded:', result), + onError: (error) => console.error('Batch failed:', error) + } +); + +batchItems(1); +batchItems(2); +batchItems(3); // Triggers batch processing +``` diff --git a/docs/reference/functions/asyncdebounce.md b/docs/reference/functions/asyncdebounce.md new file mode 100644 index 000000000..4993fa82e --- /dev/null +++ b/docs/reference/functions/asyncdebounce.md @@ -0,0 +1,97 @@ +--- +id: asyncDebounce +title: asyncDebounce +--- + + + +# Function: asyncDebounce() + +```ts +function asyncDebounce(fn, initialOptions): (...args) => Promise> +``` + +Defined in: [async-debouncer.ts:387](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L387) + +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. + +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 +- If `throwOnError` is false (default when onError handler is provided), the error will be swallowed +- The error state can be checked using the underlying AsyncDebouncer instance +- Both onError and throwOnError can be used together - the handler will be called before any error is thrown + +State Management: +- Use `initialState` to provide initial state values when creating the async debouncer +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes canLeadingExecute, error count, execution status, and success/settle counts + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Parameters + +### fn + +`TFn` + +### initialOptions + +[`AsyncDebouncerOptions`](../../interfaces/asyncdebounceroptions.md)\<`TFn`\> + +## Returns + +`Function` + +Attempts to execute the debounced function. +If a call is already in progress, it will be queued. + +Error Handling: +- If the debounced function throws and no `onError` handler is configured, + the error will be thrown from this method. +- If an `onError` handler is configured, errors will be caught and passed to the handler, + and this method will return undefined. +- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError + +### Throws + +The error from the debounced function if no onError handler is configured + +## Example + +```ts +const debounced = asyncDebounce(async (value: string) => { + const result = await saveToAPI(value); + return result; // Return value is preserved +}, { + wait: 1000, + onError: (error) => { + console.error('API call failed:', error); + }, + throwOnError: true // Will both log the error and throw it +}); + +// Will only execute once, 1 second after the last call +// Returns the API response directly +const result = await debounced("third"); +``` diff --git a/docs/reference/functions/asyncqueue.md b/docs/reference/functions/asyncqueue.md new file mode 100644 index 000000000..37dca66cb --- /dev/null +++ b/docs/reference/functions/asyncqueue.md @@ -0,0 +1,84 @@ +--- +id: asyncQueue +title: asyncQueue +--- + + + +# Function: asyncQueue() + +```ts +function asyncQueue(fn, initialOptions): (item, position, runOnItemsChange) => boolean +``` + +Defined in: [async-queuer.ts:663](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L663) + +Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks. +The queuer is started automatically and ready to process items. + +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 +- 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 AsyncQueuer instance + +State Management: +- Use `initialState` to provide initial state values when creating the async queuer +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes error count, expiration count, rejection count, running status, and success/settle counts + +Example usage: +```ts +const enqueue = asyncQueue(async (item) => { + return item.toUpperCase(); +}, {...options}); + +enqueue('hello'); +``` + +## Type Parameters + +• **TValue** + +## Parameters + +### fn + +(`value`) => `Promise`\<`any`\> + +### initialOptions + +[`AsyncQueuerOptions`](../../interfaces/asyncqueueroptions.md)\<`TValue`\> + +## Returns + +`Function` + +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. + +### Parameters + +#### item + +`TValue` + +#### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `...` + +#### runOnItemsChange + +`boolean` = `true` + +### Returns + +`boolean` + +### Example + +```ts +queuer.addItem({ value: 'task', priority: 10 }); +queuer.addItem('task2', 'front'); +``` diff --git a/docs/reference/functions/asyncratelimit.md b/docs/reference/functions/asyncratelimit.md new file mode 100644 index 000000000..8e981df32 --- /dev/null +++ b/docs/reference/functions/asyncratelimit.md @@ -0,0 +1,129 @@ +--- +id: asyncRateLimit +title: asyncRateLimit +--- + + + +# Function: asyncRateLimit() + +```ts +function asyncRateLimit(fn, initialOptions): (...args) => Promise> +``` + +Defined in: [async-rate-limiter.ts:415](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L415) + +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. + +The rate limiter supports two types of windows: +- '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: +- 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 + +State Management: +- Use `initialState` to provide initial state values when creating the rate limiter +- `initialState` can be a partial state object or a Promise that resolves to a partial state +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution times, success/error counts, and current execution status + +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) + +## Parameters + +### fn + +`TFn` + +### initialOptions + +[`AsyncRateLimiterOptions`](../../interfaces/asyncratelimiteroptions.md)\<`TFn`\> + +## Returns + +`Function` + +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. + +Error Handling: +- If the rate-limited function throws and no `onError` handler is configured, + the error will be thrown from this method. +- If an `onError` handler is configured, errors will be caught and passed to the handler, + and this method will return undefined. +- The error state can be checked using `getErrorCount()` and `getIsExecuting()`. + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError + +### Throws + +The error from the rate-limited function if no onError handler is configured + +### Example + +```ts +const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 }); + +// First 5 calls will return a promise that resolves with the result +const result = await rateLimiter.maybeExecute('arg1', 'arg2'); + +// Additional calls within the window will return undefined +const result2 = await rateLimiter.maybeExecute('arg1', 'arg2'); // undefined +``` + +## Example + +```ts +// Rate limit to 5 calls per minute with a sliding window +const rateLimited = asyncRateLimit(makeApiCall, { + limit: 5, + window: 60000, + windowType: 'sliding', + onError: (error) => { + console.error('API call failed:', error); + }, + onReject: (rateLimiter) => { + console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); + } +}); + +// First 5 calls will execute immediately +// Additional calls will be rejected until the minute window resets +// Returns the API response directly +const result = await rateLimited(); + +// For more even execution, consider using throttle instead: +const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds +``` diff --git a/docs/reference/functions/asyncthrottle.md b/docs/reference/functions/asyncthrottle.md new file mode 100644 index 000000000..4c48f300d --- /dev/null +++ b/docs/reference/functions/asyncthrottle.md @@ -0,0 +1,102 @@ +--- +id: asyncThrottle +title: asyncThrottle +--- + + + +# Function: asyncThrottle() + +```ts +function asyncThrottle(fn, initialOptions): (...args) => Promise> +``` + +Defined in: [async-throttler.ts:417](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L417) + +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. + +Error Handling: +- If an `onError` handler is provided, it will be called with the error and throttler 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 AsyncThrottler instance + +State Management: +- Use `initialState` to provide initial state values when creating the async throttler +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes error count, execution status, last execution time, and success/settle counts + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Parameters + +### fn + +`TFn` + +### initialOptions + +[`AsyncThrottlerOptions`](../../interfaces/asyncthrottleroptions.md)\<`TFn`\> + +## Returns + +`Function` + +Attempts to execute the throttled function. The execution behavior depends on the throttler options: + +- If enough time has passed since the last execution (>= wait period): + - With leading=true: Executes immediately + - With leading=false: Waits for the next trailing execution + +- If within the wait period: + - With trailing=true: Schedules execution for end of wait period + - With trailing=false: Drops the execution + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`Promise`\<`undefined` \| `ReturnType`\<`TFn`\>\> + +### Example + +```ts +const throttled = new AsyncThrottler(fn, { wait: 1000 }); + +// First call executes immediately +await throttled.maybeExecute('a', 'b'); + +// Call during wait period - gets throttled +await throttled.maybeExecute('c', 'd'); +``` + +## Example + +```ts +const throttled = asyncThrottle(async (value: string) => { + const result = await saveToAPI(value); + return result; // Return value is preserved +}, { + wait: 1000, + onError: (error) => { + console.error('API call failed:', error); + } +}); + +// This will execute at most once per second +// Returns the API response directly +const result = await throttled(inputElement.value); +``` diff --git a/docs/reference/functions/batch.md b/docs/reference/functions/batch.md new file mode 100644 index 000000000..64c3005de --- /dev/null +++ b/docs/reference/functions/batch.md @@ -0,0 +1,60 @@ +--- +id: batch +title: batch +--- + + + +# Function: batch() + +```ts +function batch(fn, options): (item) => void +``` + +Defined in: [batcher.ts:280](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L280) + +Creates a batcher that processes items in batches + +## Type Parameters + +• **TValue** + +## Parameters + +### fn + +(`items`) => `void` + +### options + +[`BatcherOptions`](../../interfaces/batcheroptions.md)\<`TValue`\> + +## Returns + +`Function` + +Adds an item to the batcher +If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed + +### Parameters + +#### item + +`TValue` + +### Returns + +`void` + +## Example + +```ts +const batchItems = batch({ + batchSize: 3, + processBatch: (items) => console.log('Processing:', items) +}); + +batchItems(1); +batchItems(2); +batchItems(3); // Triggers batch processing +``` diff --git a/docs/reference/functions/debounce.md b/docs/reference/functions/debounce.md new file mode 100644 index 000000000..541ca22eb --- /dev/null +++ b/docs/reference/functions/debounce.md @@ -0,0 +1,67 @@ +--- +id: debounce +title: debounce +--- + + + +# Function: debounce() + +```ts +function debounce(fn, initialOptions): (...args) => void +``` + +Defined in: [debouncer.ts:257](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L257) + +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. + +If leading option is true, the function will execute immediately on the first call, then wait the delay +before allowing another execution. + +State Management: +- Use `initialState` to provide initial state values when creating the debouncer +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes canLeadingExecute, execution count, and isPending status + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Parameters + +### fn + +`TFn` + +### initialOptions + +[`DebouncerOptions`](../../interfaces/debounceroptions.md)\<`TFn`\> + +## Returns + +`Function` + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`void` + +## Example + +```ts +const debounced = debounce(() => { + saveChanges(); +}, { wait: 1000 }); + +// Called repeatedly but executes at most once per second +inputElement.addEventListener('input', debounced); +``` diff --git a/docs/reference/functions/isfunction.md b/docs/reference/functions/isfunction.md index 634a1cbbe..96879e1e1 100644 --- a/docs/reference/functions/isfunction.md +++ b/docs/reference/functions/isfunction.md @@ -11,7 +11,7 @@ title: isFunction function isFunction(value): value is T ``` -Defined in: [utils.ts:3](https://github.com/TanStack/pacer/blob/main/packages/persister/src/utils.ts#L3) +Defined in: [utils.ts:3](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/utils.ts#L3) ## Type Parameters diff --git a/docs/reference/functions/isplainarray.md b/docs/reference/functions/isplainarray.md deleted file mode 100644 index 1941990fb..000000000 --- a/docs/reference/functions/isplainarray.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -id: isPlainArray -title: isPlainArray ---- - - - -# Function: isPlainArray() - -```ts -function isPlainArray(value): boolean -``` - -Defined in: [compare.ts:66](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L66) - -## Parameters - -### value - -`unknown` - -## Returns - -`boolean` diff --git a/docs/reference/functions/isplainobject.md b/docs/reference/functions/isplainobject.md deleted file mode 100644 index ae1815628..000000000 --- a/docs/reference/functions/isplainobject.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -id: isPlainObject -title: isPlainObject ---- - - - -# Function: isPlainObject() - -```ts -function isPlainObject(o): o is Object -``` - -Defined in: [compare.ts:72](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L72) - -## Parameters - -### o - -`any` - -## Returns - -`o is Object` diff --git a/docs/reference/functions/parsefunctionorvalue.md b/docs/reference/functions/parsefunctionorvalue.md index 6de77017e..1629cd6c5 100644 --- a/docs/reference/functions/parsefunctionorvalue.md +++ b/docs/reference/functions/parsefunctionorvalue.md @@ -11,7 +11,7 @@ title: parseFunctionOrValue function parseFunctionOrValue(value, ...args): T ``` -Defined in: [utils.ts:7](https://github.com/TanStack/pacer/blob/main/packages/persister/src/utils.ts#L7) +Defined in: [utils.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/utils.ts#L7) ## Type Parameters diff --git a/docs/reference/functions/queue.md b/docs/reference/functions/queue.md new file mode 100644 index 000000000..b53a6842d --- /dev/null +++ b/docs/reference/functions/queue.md @@ -0,0 +1,91 @@ +--- +id: queue +title: queue +--- + + + +# Function: queue() + +```ts +function queue(fn, initialOptions): (item, position, runOnItemsChange) => boolean +``` + +Defined in: [queuer.ts:618](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L618) + +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. + +State Management: +- Use `initialState` to provide initial state values when creating the queuer +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution count, expiration count, rejection count, and isRunning status + +Example usage: +```ts +// Basic sequential processing +const processItems = queue((n) => console.log(n), { + wait: 1000, + onItemsChange: (queuer) => console.log(queuer.peekAllItems()) +}); +processItems(1); // Logs: 1 +processItems(2); // Logs: 2 after 1 completes + +// Priority queue +const processPriority = queue((n) => console.log(n), { + getPriority: n => n // Higher numbers processed first +}); +processPriority(1); +processPriority(3); // Processed before 1 +``` + +## Type Parameters + +• **TValue** + +## Parameters + +### fn + +(`item`) => `void` + +### initialOptions + +[`QueuerOptions`](../../interfaces/queueroptions.md)\<`TValue`\> + +## Returns + +`Function` + +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. + +Returns true if the item was added, false if the queue is full. + +Example usage: +```ts +queuer.addItem('task'); +queuer.addItem('task2', 'front'); +``` + +### Parameters + +#### item + +`TValue` + +#### position + +[`QueuePosition`](../../type-aliases/queueposition.md) = `...` + +#### runOnItemsChange + +`boolean` = `true` + +### Returns + +`boolean` diff --git a/docs/reference/functions/ratelimit.md b/docs/reference/functions/ratelimit.md new file mode 100644 index 000000000..6a4b7b79f --- /dev/null +++ b/docs/reference/functions/ratelimit.md @@ -0,0 +1,99 @@ +--- +id: rateLimit +title: rateLimit +--- + + + +# Function: rateLimit() + +```ts +function rateLimit(fn, initialOptions): (...args) => boolean +``` + +Defined in: [rate-limiter.ts:309](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L309) + +Creates a rate-limited function that will execute the provided function up to a maximum number of times within a time window. + +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 +- A debouncer collapses multiple calls into one, which is better for handling bursts of events + +The rate limiter supports two types of windows: +- '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. + +State Management: +- Use `initialState` to provide initial state values when creating the rate limiter +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution count, execution times, and rejection count + +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. + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Parameters + +### fn + +`TFn` + +### initialOptions + +[`RateLimiterOptions`](../../interfaces/ratelimiteroptions.md)\<`TFn`\> + +## Returns + +`Function` + +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. + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`boolean` + +### Example + +```ts +const rateLimiter = new RateLimiter(fn, { limit: 5, window: 1000 }); + +// First 5 calls will return true +rateLimiter.maybeExecute('arg1', 'arg2'); // true + +// Additional calls within the window will return false +rateLimiter.maybeExecute('arg1', 'arg2'); // false +``` + +## Example + +```ts +// Rate limit to 5 calls per minute with a sliding window +const rateLimited = rateLimit(makeApiCall, { + limit: 5, + window: 60000, + windowType: 'sliding', + onReject: (rateLimiter) => { + console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); + } +}); + +// First 5 calls will execute immediately +// Additional calls will be rejected until the minute window resets +rateLimited(); + +// For more even execution, consider using throttle instead: +const throttled = throttle(makeApiCall, { wait: 12000 }); // One call every 12 seconds +``` diff --git a/docs/reference/functions/replaceequaldeep.md b/docs/reference/functions/replaceequaldeep.md deleted file mode 100644 index 1f4590da7..000000000 --- a/docs/reference/functions/replaceequaldeep.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -id: replaceEqualDeep -title: replaceEqualDeep ---- - - - -# Function: replaceEqualDeep() - -```ts -function replaceEqualDeep(a, b): T -``` - -Defined in: [compare.ts:6](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L6) - -This function returns `a` if `b` is deeply equal. -If not, it will replace any deeply equal children of `b` with those of `a`. -This can be used for structural sharing between JSON values for example. - -## Type Parameters - -• **T** - -## Parameters - -### a - -`unknown` - -### b - -`T` - -## Returns - -`T` diff --git a/docs/reference/functions/shallowequalobjects.md b/docs/reference/functions/shallowequalobjects.md deleted file mode 100644 index 4962906a4..000000000 --- a/docs/reference/functions/shallowequalobjects.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -id: shallowEqualObjects -title: shallowEqualObjects ---- - - - -# Function: shallowEqualObjects() - -```ts -function shallowEqualObjects(a, b): boolean -``` - -Defined in: [compare.ts:49](https://github.com/TanStack/pacer/blob/main/packages/persister/src/compare.ts#L49) - -Shallow compare objects. - -## Type Parameters - -• **T** *extends* `Record`\<`string`, `any`\> - -## Parameters - -### a - -`T` - -### b - -`undefined` | `T` - -## Returns - -`boolean` diff --git a/docs/reference/functions/throttle.md b/docs/reference/functions/throttle.md new file mode 100644 index 000000000..28d7c0ae6 --- /dev/null +++ b/docs/reference/functions/throttle.md @@ -0,0 +1,95 @@ +--- +id: throttle +title: throttle +--- + + + +# Function: throttle() + +```ts +function throttle(fn, initialOptions): (...args) => void +``` + +Defined in: [throttler.ts:295](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L295) + +Creates a throttled function that limits how often the provided function can execute. + +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. + +The throttled function can be configured to execute on the leading and/or trailing +edge of the throttle window via options. + +For handling bursts of events, consider using debounce() instead. For hard execution +limits, consider using rateLimit(). + +State Management: +- Use `initialState` to provide initial state values when creating the throttler +- Use `onStateChange` callback to react to state changes and implement custom persistence +- The state includes execution count and last execution time + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Parameters + +### fn + +`TFn` + +### initialOptions + +[`ThrottlerOptions`](../../interfaces/throttleroptions.md)\<`TFn`\> + +## Returns + +`Function` + +Attempts to execute the throttled function. The execution behavior depends on the throttler options: + +- If enough time has passed since the last execution (>= wait period): + - With leading=true: Executes immediately + - With leading=false: Waits for the next trailing execution + +- If within the wait period: + - With trailing=true: Schedules execution for end of wait period + - With trailing=false: Drops the execution + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`void` + +### Example + +```ts +const throttled = new Throttler(fn, { wait: 1000 }); + +// First call executes immediately +throttled.maybeExecute('a', 'b'); + +// Call during wait period - gets throttled +throttled.maybeExecute('c', 'd'); +``` + +## Example + +```ts +// Basic throttling - max once per second +const throttled = throttle(updateUI, { wait: 1000 }); + +// Configure leading/trailing execution +const throttled = throttle(saveData, { + wait: 2000, + leading: true, // Execute immediately on first call + trailing: true // Execute again after delay if called during wait +}); +``` diff --git a/docs/reference/index.md b/docs/reference/index.md index df7099791..a592ae553 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,35 +1,66 @@ --- -id: "@tanstack/persister" -title: "@tanstack/persister" +id: "@tanstack/pacer" +title: "@tanstack/pacer" --- -# @tanstack/persister +# @tanstack/pacer ## Classes -- [AsyncPersister](../classes/asyncpersister.md) -- [Persister](../classes/persister.md) -- [StoragePersister](../classes/storagepersister.md) +- [AsyncBatcher](../classes/asyncbatcher.md) +- [AsyncDebouncer](../classes/asyncdebouncer.md) +- [AsyncQueuer](../classes/asyncqueuer.md) +- [AsyncRateLimiter](../classes/asyncratelimiter.md) +- [AsyncThrottler](../classes/asyncthrottler.md) +- [Batcher](../classes/batcher.md) +- [Debouncer](../classes/debouncer.md) +- [Queuer](../classes/queuer.md) +- [RateLimiter](../classes/ratelimiter.md) +- [Throttler](../classes/throttler.md) ## Interfaces -- [PersistedStorage](../interfaces/persistedstorage.md) -- [StoragePersisterOptions](../interfaces/storagepersisteroptions.md) +- [AsyncBatcherOptions](../interfaces/asyncbatcheroptions.md) +- [AsyncBatcherState](../interfaces/asyncbatcherstate.md) +- [AsyncDebouncerOptions](../interfaces/asyncdebounceroptions.md) +- [AsyncDebouncerState](../interfaces/asyncdebouncerstate.md) +- [AsyncQueuerOptions](../interfaces/asyncqueueroptions.md) +- [AsyncQueuerState](../interfaces/asyncqueuerstate.md) +- [AsyncRateLimiterOptions](../interfaces/asyncratelimiteroptions.md) +- [AsyncRateLimiterState](../interfaces/asyncratelimiterstate.md) +- [AsyncThrottlerOptions](../interfaces/asyncthrottleroptions.md) +- [AsyncThrottlerState](../interfaces/asyncthrottlerstate.md) +- [BatcherOptions](../interfaces/batcheroptions.md) +- [BatcherState](../interfaces/batcherstate.md) +- [DebouncerOptions](../interfaces/debounceroptions.md) +- [DebouncerState](../interfaces/debouncerstate.md) +- [QueuerOptions](../interfaces/queueroptions.md) +- [QueuerState](../interfaces/queuerstate.md) +- [RateLimiterOptions](../interfaces/ratelimiteroptions.md) +- [RateLimiterState](../interfaces/ratelimiterstate.md) +- [ThrottlerOptions](../interfaces/throttleroptions.md) +- [ThrottlerState](../interfaces/throttlerstate.md) ## Type Aliases - [AnyAsyncFunction](../type-aliases/anyasyncfunction.md) - [AnyFunction](../type-aliases/anyfunction.md) - [OptionalKeys](../type-aliases/optionalkeys.md) -- [RequiredKeys](../type-aliases/requiredkeys.md) +- [QueuePosition](../type-aliases/queueposition.md) ## Functions +- [asyncBatch](../functions/asyncbatch.md) +- [asyncDebounce](../functions/asyncdebounce.md) +- [asyncQueue](../functions/asyncqueue.md) +- [asyncRateLimit](../functions/asyncratelimit.md) +- [asyncThrottle](../functions/asyncthrottle.md) +- [batch](../functions/batch.md) +- [debounce](../functions/debounce.md) - [isFunction](../functions/isfunction.md) -- [isPlainArray](../functions/isplainarray.md) -- [isPlainObject](../functions/isplainobject.md) - [parseFunctionOrValue](../functions/parsefunctionorvalue.md) -- [replaceEqualDeep](../functions/replaceequaldeep.md) -- [shallowEqualObjects](../functions/shallowequalobjects.md) +- [queue](../functions/queue.md) +- [rateLimit](../functions/ratelimit.md) +- [throttle](../functions/throttle.md) diff --git a/docs/reference/interfaces/asyncbatcheroptions.md b/docs/reference/interfaces/asyncbatcheroptions.md new file mode 100644 index 000000000..c5b5ff53a --- /dev/null +++ b/docs/reference/interfaces/asyncbatcheroptions.md @@ -0,0 +1,249 @@ +--- +id: AsyncBatcherOptions +title: AsyncBatcherOptions +--- + + + +# Interface: AsyncBatcherOptions\ + +Defined in: [async-batcher.ts:43](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L43) + +Options for configuring an AsyncBatcher instance + +## Type Parameters + +• **TValue** + +## Properties + +### getShouldExecute()? + +```ts +optional getShouldExecute: (items, batcher) => boolean; +``` + +Defined in: [async-batcher.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L48) + +Custom function to determine if a batch should be processed +Return true to process the batch immediately + +#### Parameters + +##### items + +`TValue`[] + +##### batcher + +[`AsyncBatcher`](../../classes/asyncbatcher.md)\<`TValue`\> + +#### Returns + +`boolean` + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [async-batcher.ts:55](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L55) + +Initial state for the async batcher + +*** + +### maxSize? + +```ts +optional maxSize: number; +``` + +Defined in: [async-batcher.ts:60](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L60) + +Maximum number of items in a batch + +#### Default + +```ts +Infinity +``` + +*** + +### onError()? + +```ts +optional onError: (error, failedItems, batcher) => void; +``` + +Defined in: [async-batcher.ts:66](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L66) + +Optional error handler for when the batch function throws. +If provided, the handler will be called with the error and batcher instance. +This can be used alongside throwOnError - the handler will be called before any error is thrown. + +#### Parameters + +##### error + +`unknown` + +##### failedItems + +`TValue`[] + +##### batcher + +[`AsyncBatcher`](../../classes/asyncbatcher.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onExecute()? + +```ts +optional onExecute: (batcher) => void; +``` + +Defined in: [async-batcher.ts:74](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L74) + +Callback fired after a batch is processed + +#### Parameters + +##### batcher + +[`AsyncBatcher`](../../classes/asyncbatcher.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onItemsChange()? + +```ts +optional onItemsChange: (batcher) => void; +``` + +Defined in: [async-batcher.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L78) + +Callback fired after items are added to the batcher + +#### Parameters + +##### batcher + +[`AsyncBatcher`](../../classes/asyncbatcher.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onSettled()? + +```ts +optional onSettled: (batcher) => void; +``` + +Defined in: [async-batcher.ts:82](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L82) + +Optional callback to call when a batch is settled (completed or failed) + +#### Parameters + +##### batcher + +[`AsyncBatcher`](../../classes/asyncbatcher.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onSuccess()? + +```ts +optional onSuccess: (result, batcher) => void; +``` + +Defined in: [async-batcher.ts:86](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L86) + +Optional callback to call when a batch succeeds + +#### Parameters + +##### result + +`any` + +##### batcher + +[`AsyncBatcher`](../../classes/asyncbatcher.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### started? + +```ts +optional started: boolean; +``` + +Defined in: [async-batcher.ts:91](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L91) + +Whether the batcher should start processing immediately + +#### Default + +```ts +true +``` + +*** + +### throwOnError? + +```ts +optional throwOnError: boolean; +``` + +Defined in: [async-batcher.ts:97](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L97) + +Whether to throw errors when they occur. +Defaults to true if no onError handler is provided, false if an onError handler is provided. +Can be explicitly set to override these defaults. + +*** + +### wait? + +```ts +optional wait: number; +``` + +Defined in: [async-batcher.ts:104](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L104) + +Maximum time in milliseconds to wait before processing a batch. +If the wait duration has elapsed, the batch will be processed. +If not provided, the batch will not be triggered by a timeout. + +#### Default + +```ts +Infinity +``` diff --git a/docs/reference/interfaces/asyncbatcherstate.md b/docs/reference/interfaces/asyncbatcherstate.md new file mode 100644 index 000000000..63284ed91 --- /dev/null +++ b/docs/reference/interfaces/asyncbatcherstate.md @@ -0,0 +1,154 @@ +--- +id: AsyncBatcherState +title: AsyncBatcherState +--- + + + +# Interface: AsyncBatcherState\ + +Defined in: [async-batcher.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L4) + +## Type Parameters + +• **TValue** + +## Properties + +### errorCount + +```ts +errorCount: number; +``` + +Defined in: [async-batcher.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L5) + +*** + +### failedItems + +```ts +failedItems: TValue[]; +``` + +Defined in: [async-batcher.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L6) + +*** + +### isEmpty + +```ts +isEmpty: boolean; +``` + +Defined in: [async-batcher.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L7) + +*** + +### isExecuting + +```ts +isExecuting: boolean; +``` + +Defined in: [async-batcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L8) + +*** + +### isPending + +```ts +isPending: boolean; +``` + +Defined in: [async-batcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L9) + +*** + +### isRunning + +```ts +isRunning: boolean; +``` + +Defined in: [async-batcher.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L10) + +*** + +### items + +```ts +items: TValue[]; +``` + +Defined in: [async-batcher.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L11) + +*** + +### lastResult + +```ts +lastResult: any; +``` + +Defined in: [async-batcher.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L12) + +*** + +### settleCount + +```ts +settleCount: number; +``` + +Defined in: [async-batcher.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L13) + +*** + +### size + +```ts +size: number; +``` + +Defined in: [async-batcher.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L14) + +*** + +### status + +```ts +status: "idle" | "pending"; +``` + +Defined in: [async-batcher.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L15) + +*** + +### successCount + +```ts +successCount: number; +``` + +Defined in: [async-batcher.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L16) + +*** + +### totalItemsFailed + +```ts +totalItemsFailed: number; +``` + +Defined in: [async-batcher.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L18) + +*** + +### totalItemsProcessed + +```ts +totalItemsProcessed: number; +``` + +Defined in: [async-batcher.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L17) diff --git a/docs/reference/interfaces/asyncdebounceroptions.md b/docs/reference/interfaces/asyncdebounceroptions.md new file mode 100644 index 000000000..e2705ab2d --- /dev/null +++ b/docs/reference/interfaces/asyncdebounceroptions.md @@ -0,0 +1,172 @@ +--- +id: AsyncDebouncerOptions +title: AsyncDebouncerOptions +--- + + + +# Interface: AsyncDebouncerOptions\ + +Defined in: [async-debouncer.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L36) + +Options for configuring an async debounced function + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### enabled? + +```ts +optional enabled: boolean | (debouncer) => boolean; +``` + +Defined in: [async-debouncer.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L42) + +Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions. +Can be a boolean or a function that returns a boolean. +Defaults to true. + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [async-debouncer.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L46) + +Initial state for the async debouncer + +*** + +### leading? + +```ts +optional leading: boolean; +``` + +Defined in: [async-debouncer.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L51) + +Whether to execute on the leading edge of the timeout. +Defaults to false. + +*** + +### onError()? + +```ts +optional onError: (error, debouncer) => void; +``` + +Defined in: [async-debouncer.ts:57](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L57) + +Optional error handler for when the debounced function throws. +If provided, the handler will be called with the error and debouncer instance. +This can be used alongside throwOnError - the handler will be called before any error is thrown. + +#### Parameters + +##### error + +`unknown` + +##### debouncer + +[`AsyncDebouncer`](../../classes/asyncdebouncer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSettled()? + +```ts +optional onSettled: (debouncer) => void; +``` + +Defined in: [async-debouncer.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L61) + +Optional callback to call when the debounced function is executed + +#### Parameters + +##### debouncer + +[`AsyncDebouncer`](../../classes/asyncdebouncer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSuccess()? + +```ts +optional onSuccess: (result, debouncer) => void; +``` + +Defined in: [async-debouncer.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L65) + +Optional callback to call when the debounced function is executed + +#### Parameters + +##### result + +`ReturnType`\<`TFn`\> + +##### debouncer + +[`AsyncDebouncer`](../../classes/asyncdebouncer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### throwOnError? + +```ts +optional throwOnError: boolean; +``` + +Defined in: [async-debouncer.ts:71](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L71) + +Whether to throw errors when they occur. +Defaults to true if no onError handler is provided, false if an onError handler is provided. +Can be explicitly set to override these defaults. + +*** + +### trailing? + +```ts +optional trailing: boolean; +``` + +Defined in: [async-debouncer.ts:76](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L76) + +Whether to execute on the trailing edge of the timeout. +Defaults to true. + +*** + +### wait + +```ts +wait: number | (debouncer) => number; +``` + +Defined in: [async-debouncer.ts:82](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L82) + +Delay in milliseconds to wait after the last call before executing. +Can be a number or a function that returns a number. +Defaults to 0ms diff --git a/docs/reference/interfaces/asyncdebouncerstate.md b/docs/reference/interfaces/asyncdebouncerstate.md new file mode 100644 index 000000000..a7a5a8cf4 --- /dev/null +++ b/docs/reference/interfaces/asyncdebouncerstate.md @@ -0,0 +1,104 @@ +--- +id: AsyncDebouncerState +title: AsyncDebouncerState +--- + + + +# Interface: AsyncDebouncerState\ + +Defined in: [async-debouncer.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L5) + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### canLeadingExecute + +```ts +canLeadingExecute: boolean; +``` + +Defined in: [async-debouncer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L6) + +*** + +### errorCount + +```ts +errorCount: number; +``` + +Defined in: [async-debouncer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L7) + +*** + +### isExecuting + +```ts +isExecuting: boolean; +``` + +Defined in: [async-debouncer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L8) + +*** + +### isPending + +```ts +isPending: boolean; +``` + +Defined in: [async-debouncer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L9) + +*** + +### lastArgs + +```ts +lastArgs: undefined | Parameters; +``` + +Defined in: [async-debouncer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L10) + +*** + +### lastResult + +```ts +lastResult: undefined | ReturnType; +``` + +Defined in: [async-debouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L11) + +*** + +### settleCount + +```ts +settleCount: number; +``` + +Defined in: [async-debouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L12) + +*** + +### status + +```ts +status: "idle" | "pending" | "executing" | "settled"; +``` + +Defined in: [async-debouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L13) + +*** + +### successCount + +```ts +successCount: number; +``` + +Defined in: [async-debouncer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L14) diff --git a/docs/reference/interfaces/asyncqueueroptions.md b/docs/reference/interfaces/asyncqueueroptions.md new file mode 100644 index 000000000..167717227 --- /dev/null +++ b/docs/reference/interfaces/asyncqueueroptions.md @@ -0,0 +1,364 @@ +--- +id: AsyncQueuerOptions +title: AsyncQueuerOptions +--- + + + +# Interface: AsyncQueuerOptions\ + +Defined in: [async-queuer.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L46) + +## Type Parameters + +• **TValue** + +## Properties + +### addItemsTo? + +```ts +optional addItemsTo: QueuePosition; +``` + +Defined in: [async-queuer.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L51) + +Default position to add items to the queuer + +#### Default + +```ts +'back' +``` + +*** + +### concurrency? + +```ts +optional concurrency: number | (queuer) => number; +``` + +Defined in: [async-queuer.ts:57](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L57) + +Maximum number of concurrent tasks to process. +Can be a number or a function that returns a number. + +#### Default + +```ts +1 +``` + +*** + +### expirationDuration? + +```ts +optional expirationDuration: number; +``` + +Defined in: [async-queuer.ts:62](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L62) + +Maximum time in milliseconds that an item can stay in the queue +If not provided, items will never expire + +*** + +### getIsExpired()? + +```ts +optional getIsExpired: (item, addedAt) => boolean; +``` + +Defined in: [async-queuer.ts:67](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L67) + +Function to determine if an item has expired +If provided, this overrides the expirationDuration behavior + +#### Parameters + +##### item + +`TValue` + +##### addedAt + +`number` + +#### Returns + +`boolean` + +*** + +### getItemsFrom? + +```ts +optional getItemsFrom: QueuePosition; +``` + +Defined in: [async-queuer.ts:72](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L72) + +Default position to get items from during processing + +#### Default + +```ts +'front' +``` + +*** + +### getPriority()? + +```ts +optional getPriority: (item) => number; +``` + +Defined in: [async-queuer.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L78) + +Function to determine priority of items in the queuer +Higher priority items will be processed first +If not provided, will use static priority values attached to tasks + +#### Parameters + +##### item + +`TValue` + +#### Returns + +`number` + +*** + +### initialItems? + +```ts +optional initialItems: TValue[]; +``` + +Defined in: [async-queuer.ts:82](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L82) + +Initial items to populate the queuer with + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [async-queuer.ts:86](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L86) + +Initial state for the async queuer + +*** + +### maxSize? + +```ts +optional maxSize: number; +``` + +Defined in: [async-queuer.ts:90](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L90) + +Maximum number of items allowed in the queuer + +*** + +### onError()? + +```ts +optional onError: (error, queuer) => void; +``` + +Defined in: [async-queuer.ts:96](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L96) + +Optional error handler for when a task throws. +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. + +#### Parameters + +##### error + +`unknown` + +##### queuer + +[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onExpire()? + +```ts +optional onExpire: (item, queuer) => void; +``` + +Defined in: [async-queuer.ts:100](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L100) + +Callback fired whenever an item expires in the queuer + +#### Parameters + +##### item + +`TValue` + +##### queuer + +[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onItemsChange()? + +```ts +optional onItemsChange: (queuer) => void; +``` + +Defined in: [async-queuer.ts:104](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L104) + +Callback fired whenever an item is added or removed from the queuer + +#### Parameters + +##### queuer + +[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onReject()? + +```ts +optional onReject: (item, queuer) => void; +``` + +Defined in: [async-queuer.ts:108](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L108) + +Callback fired whenever an item is rejected from being added to the queuer + +#### Parameters + +##### item + +`TValue` + +##### queuer + +[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onSettled()? + +```ts +optional onSettled: (queuer) => void; +``` + +Defined in: [async-queuer.ts:112](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L112) + +Optional callback to call when a task is settled + +#### Parameters + +##### queuer + +[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onSuccess()? + +```ts +optional onSuccess: (result, queuer) => void; +``` + +Defined in: [async-queuer.ts:116](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L116) + +Optional callback to call when a task succeeds + +#### Parameters + +##### result + +`TValue` + +##### queuer + +[`AsyncQueuer`](../../classes/asyncqueuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### started? + +```ts +optional started: boolean; +``` + +Defined in: [async-queuer.ts:120](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L120) + +Whether the queuer should start processing tasks immediately or not. + +*** + +### throwOnError? + +```ts +optional throwOnError: boolean; +``` + +Defined in: [async-queuer.ts:126](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L126) + +Whether to throw errors when they occur. +Defaults to true if no onError handler is provided, false if an onError handler is provided. +Can be explicitly set to override these defaults. + +*** + +### wait? + +```ts +optional wait: number | (queuer) => number; +``` + +Defined in: [async-queuer.ts:132](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L132) + +Time in milliseconds to wait between processing items. +Can be a number or a function that returns a number. + +#### Default + +```ts +0 +``` diff --git a/docs/reference/interfaces/asyncqueuerstate.md b/docs/reference/interfaces/asyncqueuerstate.md new file mode 100644 index 000000000..90ec3db47 --- /dev/null +++ b/docs/reference/interfaces/asyncqueuerstate.md @@ -0,0 +1,174 @@ +--- +id: AsyncQueuerState +title: AsyncQueuerState +--- + + + +# Interface: AsyncQueuerState\ + +Defined in: [async-queuer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L6) + +## Type Parameters + +• **TValue** + +## Properties + +### activeItems + +```ts +activeItems: TValue[]; +``` + +Defined in: [async-queuer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L7) + +*** + +### errorCount + +```ts +errorCount: number; +``` + +Defined in: [async-queuer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L8) + +*** + +### expirationCount + +```ts +expirationCount: number; +``` + +Defined in: [async-queuer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L9) + +*** + +### isEmpty + +```ts +isEmpty: boolean; +``` + +Defined in: [async-queuer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L10) + +*** + +### isFull + +```ts +isFull: boolean; +``` + +Defined in: [async-queuer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L11) + +*** + +### isIdle + +```ts +isIdle: boolean; +``` + +Defined in: [async-queuer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L12) + +*** + +### isRunning + +```ts +isRunning: boolean; +``` + +Defined in: [async-queuer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L13) + +*** + +### items + +```ts +items: TValue[]; +``` + +Defined in: [async-queuer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L15) + +*** + +### itemTimestamps + +```ts +itemTimestamps: number[]; +``` + +Defined in: [async-queuer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L14) + +*** + +### lastResult + +```ts +lastResult: any; +``` + +Defined in: [async-queuer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L16) + +*** + +### pendingTick + +```ts +pendingTick: boolean; +``` + +Defined in: [async-queuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L17) + +*** + +### rejectionCount + +```ts +rejectionCount: number; +``` + +Defined in: [async-queuer.ts:18](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L18) + +*** + +### settledCount + +```ts +settledCount: number; +``` + +Defined in: [async-queuer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L19) + +*** + +### size + +```ts +size: number; +``` + +Defined in: [async-queuer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L20) + +*** + +### status + +```ts +status: "idle" | "running" | "stopped"; +``` + +Defined in: [async-queuer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L21) + +*** + +### successCount + +```ts +successCount: number; +``` + +Defined in: [async-queuer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L22) diff --git a/docs/reference/interfaces/asyncratelimiteroptions.md b/docs/reference/interfaces/asyncratelimiteroptions.md new file mode 100644 index 000000000..e5625e775 --- /dev/null +++ b/docs/reference/interfaces/asyncratelimiteroptions.md @@ -0,0 +1,195 @@ +--- +id: AsyncRateLimiterOptions +title: AsyncRateLimiterOptions +--- + + + +# Interface: AsyncRateLimiterOptions\ + +Defined in: [async-rate-limiter.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L32) + +Options for configuring an async rate-limited function + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### enabled? + +```ts +optional enabled: boolean | (rateLimiter) => boolean; +``` + +Defined in: [async-rate-limiter.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L38) + +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. +Defaults to true. + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [async-rate-limiter.ts:42](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L42) + +Initial state for the rate limiter + +*** + +### limit + +```ts +limit: number | (rateLimiter) => number; +``` + +Defined in: [async-rate-limiter.ts:47](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L47) + +Maximum number of executions allowed within the time window. +Can be a number or a function that returns a number. + +*** + +### onError()? + +```ts +optional onError: (error, rateLimiter) => void; +``` + +Defined in: [async-rate-limiter.ts:53](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L53) + +Optional error handler for when the rate-limited function throws. +If provided, the handler will be called with the error and rate limiter instance. +This can be used alongside throwOnError - the handler will be called before any error is thrown. + +#### Parameters + +##### error + +`unknown` + +##### rateLimiter + +[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onReject()? + +```ts +optional onReject: (rateLimiter) => void; +``` + +Defined in: [async-rate-limiter.ts:57](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L57) + +Optional callback function that is called when an execution is rejected due to rate limiting + +#### Parameters + +##### rateLimiter + +[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSettled()? + +```ts +optional onSettled: (rateLimiter) => void; +``` + +Defined in: [async-rate-limiter.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L61) + +Optional function to call when the rate-limited function is executed + +#### Parameters + +##### rateLimiter + +[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSuccess()? + +```ts +optional onSuccess: (result, rateLimiter) => void; +``` + +Defined in: [async-rate-limiter.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L65) + +Optional function to call when the rate-limited function is executed + +#### Parameters + +##### result + +`ReturnType`\<`TFn`\> + +##### rateLimiter + +[`AsyncRateLimiter`](../../classes/asyncratelimiter.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### throwOnError? + +```ts +optional throwOnError: boolean; +``` + +Defined in: [async-rate-limiter.ts:74](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L74) + +Whether to throw errors when they occur. +Defaults to true if no onError handler is provided, false if an onError handler is provided. +Can be explicitly set to override these defaults. + +*** + +### window + +```ts +window: number | (rateLimiter) => number; +``` + +Defined in: [async-rate-limiter.ts:79](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L79) + +Time window in milliseconds within which the limit applies. +Can be a number or a function that returns a number. + +*** + +### windowType? + +```ts +optional windowType: "fixed" | "sliding"; +``` + +Defined in: [async-rate-limiter.ts:86](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L86) + +Type of window to use for rate limiting +- 'fixed': Uses a fixed window that resets after the window period +- 'sliding': Uses a sliding window that allows executions as old ones expire +Defaults to 'fixed' diff --git a/docs/reference/interfaces/asyncratelimiterstate.md b/docs/reference/interfaces/asyncratelimiterstate.md new file mode 100644 index 000000000..26309a5be --- /dev/null +++ b/docs/reference/interfaces/asyncratelimiterstate.md @@ -0,0 +1,84 @@ +--- +id: AsyncRateLimiterState +title: AsyncRateLimiterState +--- + + + +# Interface: AsyncRateLimiterState\ + +Defined in: [async-rate-limiter.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L5) + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### errorCount + +```ts +errorCount: number; +``` + +Defined in: [async-rate-limiter.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L6) + +*** + +### executionTimes + +```ts +executionTimes: number[]; +``` + +Defined in: [async-rate-limiter.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L7) + +*** + +### isExecuting + +```ts +isExecuting: boolean; +``` + +Defined in: [async-rate-limiter.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L8) + +*** + +### lastResult + +```ts +lastResult: undefined | ReturnType; +``` + +Defined in: [async-rate-limiter.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L9) + +*** + +### rejectionCount + +```ts +rejectionCount: number; +``` + +Defined in: [async-rate-limiter.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L10) + +*** + +### settleCount + +```ts +settleCount: number; +``` + +Defined in: [async-rate-limiter.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L11) + +*** + +### successCount + +```ts +successCount: number; +``` + +Defined in: [async-rate-limiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-rate-limiter.ts#L12) diff --git a/docs/reference/interfaces/asyncthrottleroptions.md b/docs/reference/interfaces/asyncthrottleroptions.md new file mode 100644 index 000000000..1bb8d3561 --- /dev/null +++ b/docs/reference/interfaces/asyncthrottleroptions.md @@ -0,0 +1,172 @@ +--- +id: AsyncThrottlerOptions +title: AsyncThrottlerOptions +--- + + + +# Interface: AsyncThrottlerOptions\ + +Defined in: [async-throttler.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L38) + +Options for configuring an async throttled function + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### enabled? + +```ts +optional enabled: boolean | (throttler) => boolean; +``` + +Defined in: [async-throttler.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L44) + +Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions. +Can be a boolean or a function that returns a boolean. +Defaults to true. + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [async-throttler.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L48) + +Initial state for the async throttler + +*** + +### leading? + +```ts +optional leading: boolean; +``` + +Defined in: [async-throttler.ts:53](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L53) + +Whether to execute the function immediately when called +Defaults to true + +*** + +### onError()? + +```ts +optional onError: (error, asyncThrottler) => void; +``` + +Defined in: [async-throttler.ts:59](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L59) + +Optional error handler for when the throttled function throws. +If provided, the handler will be called with the error and throttler instance. +This can be used alongside throwOnError - the handler will be called before any error is thrown. + +#### Parameters + +##### error + +`unknown` + +##### asyncThrottler + +[`AsyncThrottler`](../../classes/asyncthrottler.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSettled()? + +```ts +optional onSettled: (asyncThrottler) => void; +``` + +Defined in: [async-throttler.ts:63](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L63) + +Optional function to call when the throttled function is executed + +#### Parameters + +##### asyncThrottler + +[`AsyncThrottler`](../../classes/asyncthrottler.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onSuccess()? + +```ts +optional onSuccess: (result, asyncThrottler) => void; +``` + +Defined in: [async-throttler.ts:67](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L67) + +Optional function to call when the throttled function is executed + +#### Parameters + +##### result + +`ReturnType`\<`TFn`\> + +##### asyncThrottler + +[`AsyncThrottler`](../../classes/asyncthrottler.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### throwOnError? + +```ts +optional throwOnError: boolean; +``` + +Defined in: [async-throttler.ts:76](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L76) + +Whether to throw errors when they occur. +Defaults to true if no onError handler is provided, false if an onError handler is provided. +Can be explicitly set to override these defaults. + +*** + +### trailing? + +```ts +optional trailing: boolean; +``` + +Defined in: [async-throttler.ts:81](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L81) + +Whether to execute the function on the trailing edge of the wait period +Defaults to true + +*** + +### wait + +```ts +wait: number | (throttler) => number; +``` + +Defined in: [async-throttler.ts:87](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L87) + +Time window in milliseconds during which the function can only be executed once. +Can be a number or a function that returns a number. +Defaults to 0ms diff --git a/docs/reference/interfaces/asyncthrottlerstate.md b/docs/reference/interfaces/asyncthrottlerstate.md new file mode 100644 index 000000000..f7d761a5e --- /dev/null +++ b/docs/reference/interfaces/asyncthrottlerstate.md @@ -0,0 +1,114 @@ +--- +id: AsyncThrottlerState +title: AsyncThrottlerState +--- + + + +# Interface: AsyncThrottlerState\ + +Defined in: [async-throttler.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L5) + +## Type Parameters + +• **TFn** *extends* [`AnyAsyncFunction`](../../type-aliases/anyasyncfunction.md) + +## Properties + +### errorCount + +```ts +errorCount: number; +``` + +Defined in: [async-throttler.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L6) + +*** + +### isExecuting + +```ts +isExecuting: boolean; +``` + +Defined in: [async-throttler.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L7) + +*** + +### isPending + +```ts +isPending: boolean; +``` + +Defined in: [async-throttler.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L8) + +*** + +### lastArgs + +```ts +lastArgs: undefined | Parameters; +``` + +Defined in: [async-throttler.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L9) + +*** + +### lastExecutionTime + +```ts +lastExecutionTime: number; +``` + +Defined in: [async-throttler.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L10) + +*** + +### lastResult + +```ts +lastResult: undefined | ReturnType; +``` + +Defined in: [async-throttler.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L11) + +*** + +### nextExecutionTime + +```ts +nextExecutionTime: number; +``` + +Defined in: [async-throttler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L12) + +*** + +### settleCount + +```ts +settleCount: number; +``` + +Defined in: [async-throttler.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L13) + +*** + +### status + +```ts +status: "idle" | "pending" | "executing" | "settled"; +``` + +Defined in: [async-throttler.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L14) + +*** + +### successCount + +```ts +successCount: number; +``` + +Defined in: [async-throttler.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L15) diff --git a/docs/reference/interfaces/batcheroptions.md b/docs/reference/interfaces/batcheroptions.md new file mode 100644 index 000000000..53cff38a2 --- /dev/null +++ b/docs/reference/interfaces/batcheroptions.md @@ -0,0 +1,155 @@ +--- +id: BatcherOptions +title: BatcherOptions +--- + + + +# Interface: BatcherOptions\ + +Defined in: [batcher.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L31) + +Options for configuring a Batcher instance + +## Type Parameters + +• **TValue** + +## Properties + +### getShouldExecute()? + +```ts +optional getShouldExecute: (items, batcher) => boolean; +``` + +Defined in: [batcher.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L36) + +Custom function to determine if a batch should be processed +Return true to process the batch immediately + +#### Parameters + +##### items + +`TValue`[] + +##### batcher + +[`Batcher`](../../classes/batcher.md)\<`TValue`\> + +#### Returns + +`boolean` + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [batcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L40) + +Initial state for the batcher + +*** + +### maxSize? + +```ts +optional maxSize: number; +``` + +Defined in: [batcher.ts:45](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L45) + +Maximum number of items in a batch + +#### Default + +```ts +Infinity +``` + +*** + +### onExecute()? + +```ts +optional onExecute: (batcher) => void; +``` + +Defined in: [batcher.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L49) + +Callback fired after a batch is processed + +#### Parameters + +##### batcher + +[`Batcher`](../../classes/batcher.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onItemsChange()? + +```ts +optional onItemsChange: (batcher) => void; +``` + +Defined in: [batcher.ts:53](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L53) + +Callback fired after items are added to the batcher + +#### Parameters + +##### batcher + +[`Batcher`](../../classes/batcher.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### started? + +```ts +optional started: boolean; +``` + +Defined in: [batcher.ts:58](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L58) + +Whether the batcher should start processing immediately + +#### Default + +```ts +true +``` + +*** + +### wait? + +```ts +optional wait: number; +``` + +Defined in: [batcher.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L65) + +Maximum time in milliseconds to wait before processing a batch. +If the wait duration has elapsed, the batch will be processed. +If not provided, the batch will not be triggered by a timeout. + +#### Default + +```ts +Infinity +``` diff --git a/docs/reference/interfaces/batcherstate.md b/docs/reference/interfaces/batcherstate.md new file mode 100644 index 000000000..507dfd76d --- /dev/null +++ b/docs/reference/interfaces/batcherstate.md @@ -0,0 +1,94 @@ +--- +id: BatcherState +title: BatcherState +--- + + + +# Interface: BatcherState\ + +Defined in: [batcher.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L4) + +## Type Parameters + +• **TValue** + +## Properties + +### executionCount + +```ts +executionCount: number; +``` + +Defined in: [batcher.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L5) + +*** + +### isEmpty + +```ts +isEmpty: boolean; +``` + +Defined in: [batcher.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L6) + +*** + +### isPending + +```ts +isPending: boolean; +``` + +Defined in: [batcher.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L7) + +*** + +### isRunning + +```ts +isRunning: boolean; +``` + +Defined in: [batcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L8) + +*** + +### items + +```ts +items: TValue[]; +``` + +Defined in: [batcher.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L10) + +*** + +### size + +```ts +size: number; +``` + +Defined in: [batcher.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L11) + +*** + +### status + +```ts +status: "idle" | "pending"; +``` + +Defined in: [batcher.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L12) + +*** + +### totalItemsProcessed + +```ts +totalItemsProcessed: number; +``` + +Defined in: [batcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L9) diff --git a/docs/reference/interfaces/debounceroptions.md b/docs/reference/interfaces/debounceroptions.md new file mode 100644 index 000000000..33aca1817 --- /dev/null +++ b/docs/reference/interfaces/debounceroptions.md @@ -0,0 +1,105 @@ +--- +id: DebouncerOptions +title: DebouncerOptions +--- + + + +# Interface: DebouncerOptions\ + +Defined in: [debouncer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L28) + +Options for configuring a debounced function + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Properties + +### enabled? + +```ts +optional enabled: boolean | (debouncer) => boolean; +``` + +Defined in: [debouncer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L34) + +Whether the debouncer is enabled. When disabled, maybeExecute will not trigger any executions. +Can be a boolean or a function that returns a boolean. +Defaults to true. + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [debouncer.ts:38](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L38) + +Initial state for the debouncer + +*** + +### leading? + +```ts +optional leading: boolean; +``` + +Defined in: [debouncer.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L44) + +Whether to execute on the leading edge of the timeout. +The first call will execute immediately and the rest will wait the delay. +Defaults to false. + +*** + +### onExecute()? + +```ts +optional onExecute: (debouncer) => void; +``` + +Defined in: [debouncer.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L48) + +Callback function that is called after the function is executed + +#### Parameters + +##### debouncer + +[`Debouncer`](../../classes/debouncer.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### trailing? + +```ts +optional trailing: boolean; +``` + +Defined in: [debouncer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L53) + +Whether to execute on the trailing edge of the timeout. +Defaults to true. + +*** + +### wait + +```ts +wait: number | (debouncer) => number; +``` + +Defined in: [debouncer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L59) + +Delay in milliseconds before executing the function. +Can be a number or a function that returns a number. +Defaults to 0ms diff --git a/docs/reference/interfaces/debouncerstate.md b/docs/reference/interfaces/debouncerstate.md new file mode 100644 index 000000000..14dd8760e --- /dev/null +++ b/docs/reference/interfaces/debouncerstate.md @@ -0,0 +1,64 @@ +--- +id: DebouncerState +title: DebouncerState +--- + + + +# Interface: DebouncerState\ + +Defined in: [debouncer.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L5) + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Properties + +### canLeadingExecute + +```ts +canLeadingExecute: boolean; +``` + +Defined in: [debouncer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L6) + +*** + +### executionCount + +```ts +executionCount: number; +``` + +Defined in: [debouncer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L7) + +*** + +### isPending + +```ts +isPending: boolean; +``` + +Defined in: [debouncer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L8) + +*** + +### lastArgs + +```ts +lastArgs: undefined | Parameters; +``` + +Defined in: [debouncer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L9) + +*** + +### status + +```ts +status: "idle" | "pending"; +``` + +Defined in: [debouncer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L10) diff --git a/docs/reference/interfaces/persistedstorage.md b/docs/reference/interfaces/persistedstorage.md deleted file mode 100644 index b95aba3e3..000000000 --- a/docs/reference/interfaces/persistedstorage.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -id: PersistedStorage -title: PersistedStorage ---- - - - -# Interface: PersistedStorage\ - -Defined in: [storage-persister.ts:4](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L4) - -## Type Parameters - -• **TState** - -## Properties - -### buster? - -```ts -optional buster: string; -``` - -Defined in: [storage-persister.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L5) - -*** - -### state - -```ts -state: undefined | TState; -``` - -Defined in: [storage-persister.ts:6](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L6) - -*** - -### timestamp - -```ts -timestamp: number; -``` - -Defined in: [storage-persister.ts:7](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L7) diff --git a/docs/reference/interfaces/queueroptions.md b/docs/reference/interfaces/queueroptions.md new file mode 100644 index 000000000..f1cdc4e1f --- /dev/null +++ b/docs/reference/interfaces/queueroptions.md @@ -0,0 +1,284 @@ +--- +id: QueuerOptions +title: QueuerOptions +--- + + + +# Interface: QueuerOptions\ + +Defined in: [queuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L41) + +Options for configuring a Queuer instance. + +These options control queue behavior, item expiration, callbacks, and more. + +## Type Parameters + +• **TValue** + +## Properties + +### addItemsTo? + +```ts +optional addItemsTo: QueuePosition; +``` + +Defined in: [queuer.ts:46](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L46) + +Default position to add items to the queuer + +#### Default + +```ts +'back' +``` + +*** + +### expirationDuration? + +```ts +optional expirationDuration: number; +``` + +Defined in: [queuer.ts:51](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L51) + +Maximum time in milliseconds that an item can stay in the queue +If not provided, items will never expire + +*** + +### getIsExpired()? + +```ts +optional getIsExpired: (item, addedAt) => boolean; +``` + +Defined in: [queuer.ts:56](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L56) + +Function to determine if an item has expired +If provided, this overrides the expirationDuration behavior + +#### Parameters + +##### item + +`TValue` + +##### addedAt + +`number` + +#### Returns + +`boolean` + +*** + +### getItemsFrom? + +```ts +optional getItemsFrom: QueuePosition; +``` + +Defined in: [queuer.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L61) + +Default position to get items from during processing + +#### Default + +```ts +'front' +``` + +*** + +### getPriority()? + +```ts +optional getPriority: (item) => number; +``` + +Defined in: [queuer.ts:66](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L66) + +Function to determine priority of items in the queuer +Higher priority items will be processed first + +#### Parameters + +##### item + +`TValue` + +#### Returns + +`number` + +*** + +### initialItems? + +```ts +optional initialItems: TValue[]; +``` + +Defined in: [queuer.ts:70](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L70) + +Initial items to populate the queuer with + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [queuer.ts:74](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L74) + +Initial state for the queuer + +*** + +### maxSize? + +```ts +optional maxSize: number; +``` + +Defined in: [queuer.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L78) + +Maximum number of items allowed in the queuer + +*** + +### onExecute()? + +```ts +optional onExecute: (item, queuer) => void; +``` + +Defined in: [queuer.ts:86](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L86) + +Callback fired whenever an item is removed from the queuer + +#### Parameters + +##### item + +`TValue` + +##### queuer + +[`Queuer`](../../classes/queuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onExpire()? + +```ts +optional onExpire: (item, queuer) => void; +``` + +Defined in: [queuer.ts:82](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L82) + +Callback fired whenever an item expires in the queuer + +#### Parameters + +##### item + +`TValue` + +##### queuer + +[`Queuer`](../../classes/queuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onItemsChange()? + +```ts +optional onItemsChange: (queuer) => void; +``` + +Defined in: [queuer.ts:90](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L90) + +Callback fired whenever an item is added or removed from the queuer + +#### Parameters + +##### queuer + +[`Queuer`](../../classes/queuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### onReject()? + +```ts +optional onReject: (item, queuer) => void; +``` + +Defined in: [queuer.ts:94](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L94) + +Callback fired whenever an item is rejected from being added to the queuer + +#### Parameters + +##### item + +`TValue` + +##### queuer + +[`Queuer`](../../classes/queuer.md)\<`TValue`\> + +#### Returns + +`void` + +*** + +### started? + +```ts +optional started: boolean; +``` + +Defined in: [queuer.ts:98](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L98) + +Whether the queuer should start processing tasks immediately + +*** + +### wait? + +```ts +optional wait: number | (queuer) => number; +``` + +Defined in: [queuer.ts:104](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L104) + +Time in milliseconds to wait between processing items. +Can be a number or a function that returns a number. + +#### Default + +```ts +0 +``` diff --git a/docs/reference/interfaces/queuerstate.md b/docs/reference/interfaces/queuerstate.md new file mode 100644 index 000000000..bc2b58fff --- /dev/null +++ b/docs/reference/interfaces/queuerstate.md @@ -0,0 +1,134 @@ +--- +id: QueuerState +title: QueuerState +--- + + + +# Interface: QueuerState\ + +Defined in: [queuer.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L4) + +## Type Parameters + +• **TValue** + +## Properties + +### executionCount + +```ts +executionCount: number; +``` + +Defined in: [queuer.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L5) + +*** + +### expirationCount + +```ts +expirationCount: number; +``` + +Defined in: [queuer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L6) + +*** + +### isEmpty + +```ts +isEmpty: boolean; +``` + +Defined in: [queuer.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L7) + +*** + +### isFull + +```ts +isFull: boolean; +``` + +Defined in: [queuer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L8) + +*** + +### isIdle + +```ts +isIdle: boolean; +``` + +Defined in: [queuer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L9) + +*** + +### isRunning + +```ts +isRunning: boolean; +``` + +Defined in: [queuer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L10) + +*** + +### items + +```ts +items: TValue[]; +``` + +Defined in: [queuer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L12) + +*** + +### itemTimestamps + +```ts +itemTimestamps: number[]; +``` + +Defined in: [queuer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L11) + +*** + +### pendingTick + +```ts +pendingTick: boolean; +``` + +Defined in: [queuer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L13) + +*** + +### rejectionCount + +```ts +rejectionCount: number; +``` + +Defined in: [queuer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L14) + +*** + +### size + +```ts +size: number; +``` + +Defined in: [queuer.ts:15](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L15) + +*** + +### status + +```ts +status: "idle" | "running" | "stopped"; +``` + +Defined in: [queuer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L16) diff --git a/docs/reference/interfaces/ratelimiteroptions.md b/docs/reference/interfaces/ratelimiteroptions.md new file mode 100644 index 000000000..8a06fe77a --- /dev/null +++ b/docs/reference/interfaces/ratelimiteroptions.md @@ -0,0 +1,126 @@ +--- +id: RateLimiterOptions +title: RateLimiterOptions +--- + + + +# Interface: RateLimiterOptions\ + +Defined in: [rate-limiter.ts:22](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L22) + +Options for configuring a rate-limited function + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Properties + +### enabled? + +```ts +optional enabled: boolean | (rateLimiter) => boolean; +``` + +Defined in: [rate-limiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L27) + +Whether the rate limiter is enabled. When disabled, maybeExecute will not trigger any executions. +Defaults to true. + +*** + +### initialState? + +```ts +optional initialState: Partial; +``` + +Defined in: [rate-limiter.ts:31](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L31) + +Initial state for the rate limiter + +*** + +### limit + +```ts +limit: number | (rateLimiter) => number; +``` + +Defined in: [rate-limiter.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L36) + +Maximum number of executions allowed within the time window. +Can be a number or a callback function that receives the rate limiter instance and returns a number. + +*** + +### onExecute()? + +```ts +optional onExecute: (rateLimiter) => void; +``` + +Defined in: [rate-limiter.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L40) + +Callback function that is called after the function is executed + +#### Parameters + +##### rateLimiter + +[`RateLimiter`](../../classes/ratelimiter.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### onReject()? + +```ts +optional onReject: (rateLimiter) => void; +``` + +Defined in: [rate-limiter.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L44) + +Optional callback function that is called when an execution is rejected due to rate limiting + +#### Parameters + +##### rateLimiter + +[`RateLimiter`](../../classes/ratelimiter.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### window + +```ts +window: number | (rateLimiter) => number; +``` + +Defined in: [rate-limiter.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L49) + +Time window in milliseconds within which the limit applies. +Can be a number or a callback function that receives the rate limiter instance and returns a number. + +*** + +### windowType? + +```ts +optional windowType: "fixed" | "sliding"; +``` + +Defined in: [rate-limiter.ts:56](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L56) + +Type of window to use for rate limiting +- 'fixed': Uses a fixed window that resets after the window period +- 'sliding': Uses a sliding window that allows executions as old ones expire +Defaults to 'fixed' diff --git a/docs/reference/interfaces/ratelimiterstate.md b/docs/reference/interfaces/ratelimiterstate.md new file mode 100644 index 000000000..4b9816211 --- /dev/null +++ b/docs/reference/interfaces/ratelimiterstate.md @@ -0,0 +1,40 @@ +--- +id: RateLimiterState +title: RateLimiterState +--- + + + +# Interface: RateLimiterState + +Defined in: [rate-limiter.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L5) + +## Properties + +### executionCount + +```ts +executionCount: number; +``` + +Defined in: [rate-limiter.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L6) + +*** + +### executionTimes + +```ts +executionTimes: number[]; +``` + +Defined in: [rate-limiter.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L7) + +*** + +### rejectionCount + +```ts +rejectionCount: number; +``` + +Defined in: [rate-limiter.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/rate-limiter.ts#L8) diff --git a/docs/reference/interfaces/storagepersisteroptions.md b/docs/reference/interfaces/storagepersisteroptions.md deleted file mode 100644 index 79123bdbc..000000000 --- a/docs/reference/interfaces/storagepersisteroptions.md +++ /dev/null @@ -1,231 +0,0 @@ ---- -id: StoragePersisterOptions -title: StoragePersisterOptions ---- - - - -# Interface: StoragePersisterOptions\ - -Defined in: [storage-persister.ts:16](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L16) - -Configuration options for creating a browser-based state persister. - -The persister can use either localStorage (persists across browser sessions) or -sessionStorage (cleared when browser tab/window closes) to store serialized state. - -## Type Parameters - -• **TState** - -## Properties - -### buster? - -```ts -optional buster: string; -``` - -Defined in: [storage-persister.ts:21](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L21) - -A version string used to invalidate cached state. When changed, any existing -stored state will be considered invalid and cleared. - -*** - -### deserializer()? - -```ts -optional deserializer: (state) => PersistedStorage; -``` - -Defined in: [storage-persister.ts:26](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L26) - -Optional function to customize how state is deserialized after loading from storage. -By default, JSON.parse is used. - -#### Parameters - -##### state - -`string` - -#### Returns - -[`PersistedStorage`](../persistedstorage.md)\<`TState`\> - -*** - -### key - -```ts -key: string; -``` - -Defined in: [storage-persister.ts:30](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L30) - -Unique identifier used as the storage key for persisting state. - -*** - -### maxAge? - -```ts -optional maxAge: number; -``` - -Defined in: [storage-persister.ts:35](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L35) - -Maximum age in milliseconds before stored state is considered expired. -When exceeded, the state will be cleared and treated as if it doesn't exist. - -*** - -### onLoadState()? - -```ts -optional onLoadState: (state) => void; -``` - -Defined in: [storage-persister.ts:39](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L39) - -Optional callback that runs after state is successfully loaded. - -#### Parameters - -##### state - -`undefined` | `TState` - -#### Returns - -`void` - -*** - -### onLoadStateError()? - -```ts -optional onLoadStateError: (error) => void; -``` - -Defined in: [storage-persister.ts:43](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L43) - -Optional callback that runs after state is unable to be loaded. - -#### Parameters - -##### error - -`Error` - -#### Returns - -`void` - -*** - -### onSaveState()? - -```ts -optional onSaveState: (state) => void; -``` - -Defined in: [storage-persister.ts:47](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L47) - -Optional callback that runs after state is successfully saved. - -#### Parameters - -##### state - -`TState` - -#### Returns - -`void` - -*** - -### onSaveStateError()? - -```ts -optional onSaveStateError: (error) => void; -``` - -Defined in: [storage-persister.ts:52](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L52) - -Optional callback that runs after state is unable to be saved. -For example, if the storage is full (localStorage >= 5MB) - -#### Parameters - -##### error - -`Error` - -#### Returns - -`void` - -*** - -### serializer()? - -```ts -optional serializer: (state) => string; -``` - -Defined in: [storage-persister.ts:57](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L57) - -Optional function to customize how state is serialized before saving to storage. -By default, JSON.stringify is used. - -#### Parameters - -##### state - -[`PersistedStorage`](../persistedstorage.md)\<`TState`\> - -#### Returns - -`string` - -*** - -### stateTransform()? - -```ts -optional stateTransform: (state) => Partial; -``` - -Defined in: [storage-persister.ts:65](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L65) - -Optional function to filter which parts of the state are persisted and loaded, or to otherwise transform the state before saving or loading. -When provided, only the filtered state will be saved to storage and returned when loading. -This is useful for excluding sensitive or temporary data from persistence. - -Note: Don't use this to replace the serialization. Use the `serializer` option instead for that. - -#### Parameters - -##### state - -`TState` - -#### Returns - -`Partial`\<`TState`\> - -*** - -### storage - -```ts -storage: Storage; -``` - -Defined in: [storage-persister.ts:70](https://github.com/TanStack/pacer/blob/main/packages/persister/src/storage-persister.ts#L70) - -The browser storage implementation to use for persisting state. -Typically window.localStorage or window.sessionStorage. diff --git a/docs/reference/interfaces/throttleroptions.md b/docs/reference/interfaces/throttleroptions.md new file mode 100644 index 000000000..12892582a --- /dev/null +++ b/docs/reference/interfaces/throttleroptions.md @@ -0,0 +1,104 @@ +--- +id: ThrottlerOptions +title: ThrottlerOptions +--- + + + +# Interface: ThrottlerOptions\ + +Defined in: [throttler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L30) + +Options for configuring a throttled function + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Properties + +### enabled? + +```ts +optional enabled: boolean | (throttler) => boolean; +``` + +Defined in: [throttler.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L36) + +Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions. +Can be a boolean or a function that returns a boolean. +Defaults to true. + +*** + +### initialState? + +```ts +optional initialState: Partial>; +``` + +Defined in: [throttler.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L40) + +Initial state for the throttler + +*** + +### leading? + +```ts +optional leading: boolean; +``` + +Defined in: [throttler.ts:45](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L45) + +Whether to execute on the leading edge of the timeout. +Defaults to true. + +*** + +### onExecute()? + +```ts +optional onExecute: (throttler) => void; +``` + +Defined in: [throttler.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L49) + +Callback function that is called after the function is executed + +#### Parameters + +##### throttler + +[`Throttler`](../../classes/throttler.md)\<`TFn`\> + +#### Returns + +`void` + +*** + +### trailing? + +```ts +optional trailing: boolean; +``` + +Defined in: [throttler.ts:54](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L54) + +Whether to execute on the trailing edge of the timeout. +Defaults to true. + +*** + +### wait + +```ts +wait: number | (throttler) => number; +``` + +Defined in: [throttler.ts:60](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L60) + +Time window in milliseconds during which the function can only be executed once. +Can be a number or a function that returns a number. +Defaults to 0ms diff --git a/docs/reference/interfaces/throttlerstate.md b/docs/reference/interfaces/throttlerstate.md new file mode 100644 index 000000000..eb5660128 --- /dev/null +++ b/docs/reference/interfaces/throttlerstate.md @@ -0,0 +1,74 @@ +--- +id: ThrottlerState +title: ThrottlerState +--- + + + +# Interface: ThrottlerState\ + +Defined in: [throttler.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L5) + +## Type Parameters + +• **TFn** *extends* [`AnyFunction`](../../type-aliases/anyfunction.md) + +## Properties + +### executionCount + +```ts +executionCount: number; +``` + +Defined in: [throttler.ts:6](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L6) + +*** + +### isPending + +```ts +isPending: boolean; +``` + +Defined in: [throttler.ts:10](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L10) + +*** + +### lastArgs + +```ts +lastArgs: undefined | Parameters; +``` + +Defined in: [throttler.ts:7](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L7) + +*** + +### lastExecutionTime + +```ts +lastExecutionTime: number; +``` + +Defined in: [throttler.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L8) + +*** + +### nextExecutionTime + +```ts +nextExecutionTime: number; +``` + +Defined in: [throttler.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L9) + +*** + +### status + +```ts +status: "idle" | "pending"; +``` + +Defined in: [throttler.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L11) diff --git a/docs/reference/type-aliases/anyasyncfunction.md b/docs/reference/type-aliases/anyasyncfunction.md index fa911f5d8..19b6a17d7 100644 --- a/docs/reference/type-aliases/anyasyncfunction.md +++ b/docs/reference/type-aliases/anyasyncfunction.md @@ -11,7 +11,7 @@ title: AnyAsyncFunction type AnyAsyncFunction = (...args) => Promise; ``` -Defined in: [types.ts:10](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L10) +Defined in: [types.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/types.ts#L9) Represents an asynchronous function that can be called with any arguments and returns a promise. diff --git a/docs/reference/type-aliases/anyfunction.md b/docs/reference/type-aliases/anyfunction.md index 586abc705..039ac0e6e 100644 --- a/docs/reference/type-aliases/anyfunction.md +++ b/docs/reference/type-aliases/anyfunction.md @@ -11,7 +11,7 @@ title: AnyFunction type AnyFunction = (...args) => any; ``` -Defined in: [types.ts:5](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L5) +Defined in: [types.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/types.ts#L4) Represents a function that can be called with any arguments and returns any value. diff --git a/docs/reference/type-aliases/optionalkeys.md b/docs/reference/type-aliases/optionalkeys.md index d18e7c887..e09cbfd2f 100644 --- a/docs/reference/type-aliases/optionalkeys.md +++ b/docs/reference/type-aliases/optionalkeys.md @@ -11,7 +11,7 @@ title: OptionalKeys type OptionalKeys = Omit & Partial>; ``` -Defined in: [types.ts:12](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L12) +Defined in: [types.ts:11](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/types.ts#L11) ## Type Parameters diff --git a/docs/reference/type-aliases/queueposition.md b/docs/reference/type-aliases/queueposition.md new file mode 100644 index 000000000..6cc68355f --- /dev/null +++ b/docs/reference/type-aliases/queueposition.md @@ -0,0 +1,19 @@ +--- +id: QueuePosition +title: QueuePosition +--- + + + +# Type Alias: QueuePosition + +```ts +type QueuePosition = "front" | "back"; +``` + +Defined in: [queuer.ts:133](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L133) + +Position type for addItem and getNextItem operations. + +- 'front': Operate on the front of the queue (FIFO) +- 'back': Operate on the back of the queue (LIFO) diff --git a/docs/reference/type-aliases/requiredkeys.md b/docs/reference/type-aliases/requiredkeys.md deleted file mode 100644 index 3940894fb..000000000 --- a/docs/reference/type-aliases/requiredkeys.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: RequiredKeys -title: RequiredKeys ---- - - - -# Type Alias: RequiredKeys\ - -```ts -type RequiredKeys = Required> & Omit; -``` - -Defined in: [types.ts:15](https://github.com/TanStack/pacer/blob/main/packages/persister/src/types.ts#L15) - -## Type Parameters - -• **T** *extends* `object` - -• **K** *extends* keyof `T` diff --git a/examples/react/useAsyncBatcher/src/index.tsx b/examples/react/useAsyncBatcher/src/index.tsx index 2e0d2a0ca..a2b2c519b 100644 --- a/examples/react/useAsyncBatcher/src/index.tsx +++ b/examples/react/useAsyncBatcher/src/index.tsx @@ -82,7 +82,7 @@ function App() { const executeCurrentBatch = async () => { try { - const result = await asyncBatcher.execute() + const result = await asyncBatcher.flush() console.log('Manual execution result:', result) } catch (error) { console.error('Manual execution failed:', error) diff --git a/examples/react/useAsyncThrottler/src/index.tsx b/examples/react/useAsyncThrottler/src/index.tsx index 1e305da6f..5e0a954cc 100644 --- a/examples/react/useAsyncThrottler/src/index.tsx +++ b/examples/react/useAsyncThrottler/src/index.tsx @@ -78,7 +78,7 @@ function App() {
{error &&
Error: {error.message}
}
-

API calls made: {setSearchAsyncThrottler.getSuccessCount()}

+

API calls made: {setSearchAsyncThrottler.state.successCount}

{results.length > 0 && (
    {results.map((item) => ( @@ -86,9 +86,9 @@ function App() { ))}
)} - {setSearchAsyncThrottler.getIsPending() ? ( + {setSearchAsyncThrottler.state.isPending ? (

Pending...

- ) : setSearchAsyncThrottler.getIsExecuting() ? ( + ) : setSearchAsyncThrottler.state.isExecuting ? (

Executing...

) : null}
diff --git a/examples/react/useBatcher/src/index.tsx b/examples/react/useBatcher/src/index.tsx index f2c022c21..c158a977c 100644 --- a/examples/react/useBatcher/src/index.tsx +++ b/examples/react/useBatcher/src/index.tsx @@ -63,7 +63,7 @@ function App1() {
{error() &&
Error: {error()?.message}
}
-

API calls made: {setSearchAsyncThrottler.successCount()}

+

API calls made: {setSearchAsyncThrottler.state().successCount}

{(item) =>
  • {item.title}
  • }
    - {setSearchAsyncThrottler.isPending() ? ( + {setSearchAsyncThrottler.state().isPending ? (

    Pending...

    - ) : setSearchAsyncThrottler.isExecuting() ? ( + ) : setSearchAsyncThrottler.state().isExecuting ? (

    Executing...

    ) : null}
    diff --git a/examples/solid/createBatcher/src/index.tsx b/examples/solid/createBatcher/src/index.tsx index 564d5fbcc..4b58fac99 100644 --- a/examples/solid/createBatcher/src/index.tsx +++ b/examples/solid/createBatcher/src/index.tsx @@ -56,7 +56,7 @@ function App1() { - - - ) -} -``` - -In this example, the rate limiter's state (including execution count, rejection count, and execution times) is automatically persisted to localStorage. This means that even if the user refreshes the page or closes and reopens the browser, the rate limit will be maintained. The `maxAge` option ensures that the state doesn't become stale, and the `buster` option allows you to invalidate old state when you make breaking changes to your application. - -### Storage Persister Callbacks - -The `StoragePersister` utility provides several callback functions that help you customize and debug state persistence: - -- `onSaveState`: Called whenever state is successfully saved to storage. Useful for logging or analytics. -- `onLoadState`: Called when state is loaded from storage. Helpful for debugging state restoration. -- `onSaveStateError`: Called if there's an error saving state. Use this to handle storage errors gracefully. This can be useful for handling storage quota limits (localStorage is limited to 5MB), or other storage errors. -- `onLoadStateError`: Called if there's an error loading state. Useful for fallback behavior when state can't be restored/parsed. -- `serialize`: Custom function to transform state before saving to storage. Useful for complex state objects, custom serialization for Dates, etc. -- `deserialize`: Custom function to transform loaded data back into state. Should match your serialize functionality. - -### Framework Integrations - -Several convienient hooks are provided for persisting state in each JavaScript framework that TanStack Pacer supports. You may find hooks like `useStoragePersister` or `useLocalStorageState` useful. - -## Creating Custom Persisters - -While the built-in `StoragePersister` covers many use cases, you might need to integrate with custom storage solutions or add specific functionality. TanStack Pacer makes this easy by providing base classes that you can extend. - -For synchronous operations, you can extend the `Persister` class: - -```ts -import { Persister } from '@tanstack/pacer' - -class CustomPersister extends Persister { - constructor(key: string) { - super(key) - } - - loadState(): TState | undefined { - // Load state from your storage - const stored = customStorage.getItem(this.key) - return stored ? JSON.parse(stored) : undefined - } - - saveState(state: TState): void { - // Save state to your storage - customStorage.setItem(this.key, JSON.stringify(state)) - } -} -``` - -### Async Persister (Client-Side or Server-Side) - -When working with asynchronous storage systems, the `AsyncPersister` class provides a foundation for building custom async persisters. This is particularly useful in Node.js backend APIs where you need to share rate limiting state across multiple server instances or maintain state in a database. - -Here's an example of implementing a Redis-based persister for distributed rate limiting: - -```ts -import { AsyncPersister } from '@tanstack/pacer' -import { createClient } from 'redis' - -class RedisPersister extends AsyncPersister { - private client: ReturnType - - constructor(key: string, redisUrl: string) { - super(key) - this.client = createClient({ url: redisUrl }) - this.client.connect() - } - - async loadState(): Promise { - const stored = await this.client.get(this.key) - if (!stored) return undefined - - const { state, timestamp, buster } = JSON.parse(stored) - const now = Date.now() - - // Check if state is expired (e.g., 1 hour old) - if (now - timestamp > 1000 * 60 * 60) { - await this.client.del(this.key) - return undefined - } - - return state - } - - async saveState(state: TState): Promise { - await this.client.set( - this.key, - JSON.stringify({ - state, - timestamp: Date.now(), - buster: 'v1' // Version control for state structure - }) - ) - } - - async cleanup(): Promise { - await this.client.quit() - } -} - -// Usage in an Express API -import express from 'express' -import { AsyncRateLimiter } from '@tanstack/pacer' - -const app = express() -const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379' - -// Create a rate limiter for API endpoints -const apiRateLimiter = new AsyncRateLimiter( - async (req, res) => { - // Your API endpoint logic here - res.json({ success: true }) - }, - { - limit: 100, - window: 60 * 1000, // 100 requests per minute - persister: new RedisPersister('api-rate-limit', redisUrl) - } -) - -app.get('/api/endpoint', async (req, res) => { - const success = await apiRateLimiter.maybeExecute(req, res) - if (!success) { - res.status(429).json({ - error: 'Too many requests', - retryAfter: apiRateLimiter.getMsUntilNextWindow() - }) - } -}) -``` - -This implementation demonstrates how to use `AsyncPersister` in a backend context with Redis, which is ideal for distributed rate limiting. The key features include: - -- Automatic state expiration using timestamps -- Version control through the `buster` field -- Proper connection management with cleanup methods -- Integration with Express -- Distributed rate limiting across multiple server instances - -The key advantage of using async persisters in a backend context is that they allow you to share rate limiting state across multiple server instances. This is crucial for maintaining consistent rate limits in a clustered environment, where multiple servers might be handling requests for the same client. - diff --git a/docs/guides/queuing.md b/docs/guides/queuing.md index a91dd6443..342ed6414 100644 --- a/docs/guides/queuing.md +++ b/docs/guides/queuing.md @@ -210,15 +210,16 @@ The Queuer provides several helpful methods for queue management: ```ts // Queue inspection queue.peekNextItem() // View next item without removing it -queue.getSize() // Get current queue size -queue.getIsEmpty() // Check if queue is empty -queue.getIsFull() // Check if queue has reached maxSize -queue.peekAllItems() // Get copy of all queued items +queue.store.state.size // Get current queue size +queue.store.state.isEmpty // Check if queue is empty +queue.store.state.isFull // Check if queue has reached maxSize +queue.peekAllItems() // Get copy of all queued items // Queue manipulation -queue.clear() // Remove all items -queue.reset() // Reset to initial state -queue.getExecutionCount() // Get number of processed items +queue.clear() // Remove all items +queue.reset() // Reset to initial state +queue.store.state.executionCount // Get number of processed items +queue.flush() // Flush all pending items immediately // Event handling (use the onItemsChange option, not a method) // Example: @@ -271,7 +272,7 @@ const queue = new Queuer( ) // Check expiration statistics -console.log(queue.getExpirationCount()) // Number of items that have expired +console.log(queue.store.state.expirationCount) // Number of items that have expired ``` Expiration features are particularly useful for: @@ -302,7 +303,7 @@ queue.addItem(1) // Accepted queue.addItem(2) // Accepted queue.addItem(3) // Rejected, triggers onReject callback -console.log(queue.getRejectionCount()) // 1 +console.log(queue.store.state.rejectionCount) // 1 ``` ### Initial Items @@ -326,7 +327,7 @@ const queue = new Queuer( ### Dynamic Configuration -The Queuer's options can be modified after creation using `setOptions()` and retrieved using `getOptions()`. Additionally, several options support dynamic values through callback functions: +The Queuer's options can be modified after creation using `setOptions()`. Additionally, several options support dynamic values through callback functions: ```ts const queue = new Queuer( @@ -346,9 +347,9 @@ queue.setOptions({ started: true // Start processing }) -// Get current configuration -const options = queue.getOptions() -console.log(options.wait) // 500 +// Access current state +console.log(queue.store.state.size) // Current queue size +console.log(queue.store.state.isRunning) // Whether queue is running ``` ### Dynamic Options @@ -364,7 +365,7 @@ const queue = new Queuer( { // Dynamic wait time based on queue size wait: (queuer) => { - return queuer.getSize() > 10 ? 2000 : 1000 + return queuer.store.state.size > 10 ? 2000 : 1000 } } ) @@ -377,7 +378,7 @@ This allows for sophisticated queue behavior that adapts to runtime conditions. ### Performance Monitoring -The Queuer provides methods to monitor its performance: +The Queuer provides state properties to monitor its performance: ```ts const queue = new Queuer( @@ -392,8 +393,110 @@ queue.addItem(1) queue.addItem(2) queue.addItem(3) -console.log(queue.getExecutionCount()) // Number of items processed -console.log(queue.getRejectionCount()) // Number of items rejected +console.log(queue.store.state.rejectionCount) // Number of items processed +``` + +## State Management + +The `Queuer` class uses TanStack Store for reactive state management, providing real-time access to queue state, processing statistics, and item tracking. + +### Accessing State + +When using the `Queuer` class directly, access state via the `store.state` property: + +```ts +const queue = new Queuer(processFn, { wait: 1000, maxSize: 10 }) + +// Access current state +console.log(queue.store.state.isFull) +``` + +### Framework Adapters + +When using framework adapters like React or Solid, the state is exposed directly as a reactive property: + +```ts +// React example +const queue = useQueuer(processFn, { wait: 1000, maxSize: 10 }) + +// Access state directly (reactive) +console.log(queue.state.executionCount) // Reactive value +console.log(queue.state.size) // Reactive value +``` + +### Initial State + +You can provide initial state values when creating a queuer: + +```ts +const queue = new Queuer(processFn, { + wait: 1000, + maxSize: 10, + initialState: { + executionCount: 5, // Start with 5 items processed + rejectionCount: 2, // Start with 2 rejections + isRunning: false, // Start paused + } +}) +``` + +### Subscribing to State Changes + +The store is reactive and supports subscriptions: + +```ts +const queue = new Queuer(processFn, { wait: 1000, maxSize: 10 }) + +// Subscribe to state changes +const unsubscribe = queue.store.subscribe((state) => { + console.log('Queue size:', state.size) + console.log('Items processed:', state.executionCount) + console.log('Is running:', state.isRunning) + console.log('Is empty:', state.isEmpty) +}) + +// Unsubscribe when done +unsubscribe() +``` + +### Available State Properties + +The `QueuerState` includes: + +- `executionCount`: Number of items processed +- `rejectionCount`: Number of items rejected due to maxSize +- `expirationCount`: Number of items expired +- `size`: Current number of items in the queue +- `isEmpty`: Whether the queue has no items +- `isFull`: Whether the queue has reached maxSize +- `isIdle`: Whether the queue is not processing any items +- `isRunning`: Whether the queue is active and processing items +- `status`: Current processing status ('idle' | 'running' | 'stopped') +- `items`: Array of items currently in the queue +- `itemTimestamps`: Array of timestamps when items were added +- `pendingTick`: Whether the queue has a pending timeout for processing + +### Flushing Queue Items + +The queuer supports flushing items to process them immediately: + +```ts +const queue = new Queuer(processFn, { wait: 5000 }) + +queue.addItem('item1') +queue.addItem('item2') +console.log(queue.store.state.size) // 2 + +// Flush all items immediately instead of waiting +queue.flush() +console.log(queue.store.state.size) // 0 (items were processed) + +// Or flush a specific number of items +queue.addItem('item3') +queue.addItem('item4') +queue.addItem('item5') +queue.flush(2) // Process only 2 items +console.log(queue.store.state.size) // 1 (one item remaining) ``` ### Asynchronous Queuing diff --git a/docs/guides/rate-limiting.md b/docs/guides/rate-limiting.md index e6673189b..3e7dd41c9 100644 --- a/docs/guides/rate-limiting.md +++ b/docs/guides/rate-limiting.md @@ -112,7 +112,7 @@ const limiter = new RateLimiter( limit: 5, window: 60 * 1000, onExecute: (rateLimiter) => { - console.log('Function executed', rateLimiter.getExecutionCount()) + console.log('Function executed', rateLimiter.store.state.executionCount) }, onReject: (rateLimiter) => { console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`) @@ -120,10 +120,10 @@ const limiter = new RateLimiter( } ) -// Get information about current state +// Access current state via TanStack Store console.log(limiter.getRemainingInWindow()) // Number of calls remaining in current window -console.log(limiter.getExecutionCount()) // Total number of successful executions -console.log(limiter.getRejectionCount()) // Total number of rejected executions +console.log(limiter.store.state.executionCount) // Total number of successful executions +console.log(limiter.store.state.rejectionCount) // Total number of rejected executions // Attempt to execute (returns boolean indicating success) limiter.maybeExecute('user-1') @@ -158,7 +158,7 @@ const limiter = new RateLimiter(fn, { limit: 5, window: 1000, enabled: (limiter) => { - return limiter.getExecutionCount() < 100 // Disable after 100 executions + return limiter.store.state.executionCount < 100 // Disable after 100 executions } }) ``` @@ -173,15 +173,15 @@ Several options in the RateLimiter support dynamic values through callback funct const limiter = new RateLimiter(fn, { // Dynamic limit based on execution count limit: (limiter) => { - return Math.max(1, 10 - limiter.getExecutionCount()) // Decrease limit with each execution + return Math.max(1, 10 - limiter.store.state.executionCount) // Decrease limit with each execution }, // Dynamic window based on execution count window: (limiter) => { - return limiter.getExecutionCount() * 1000 // Increase window with each execution + return limiter.store.state.executionCount * 1000 // Increase window with each execution }, // Dynamic enabled state based on execution count enabled: (limiter) => { - return limiter.getExecutionCount() < 100 // Disable after 100 executions + return limiter.store.state.executionCount < 100 // Disable after 100 executions } }) ``` @@ -203,7 +203,7 @@ const limiter = new RateLimiter(fn, { window: 1000, onExecute: (rateLimiter) => { // Called after each successful execution - console.log('Function executed', rateLimiter.getExecutionCount()) + console.log('Function executed', rateLimiter.store.state.executionCount) }, onReject: (rateLimiter) => { // Called when an execution is rejected @@ -214,6 +214,90 @@ const limiter = new RateLimiter(fn, { The `onExecute` callback is called after each successful execution of the rate-limited function, while the `onReject` callback is called when an execution is rejected due to rate limiting. These callbacks are useful for tracking executions, updating UI state, or providing feedback to users. +## State Management + +The `RateLimiter` class uses TanStack Store for reactive state management, providing real-time access to execution counts and rejection statistics. + +### Accessing State + +When using the `RateLimiter` class directly, access state via the `store.state` property: + +```ts +const limiter = new RateLimiter(fn, { limit: 5, window: 1000 }) + +// Access current state +console.log(limiter.store.state.rejectionCount) +``` + +### Framework Adapters + +When using framework adapters like React or Solid, the state is exposed directly as a reactive property: + +```ts +// React example +const limiter = useRateLimiter(fn, { limit: 5, window: 1000 }) + +// Access state directly (reactive) +console.log(limiter.state.executionCount) // Reactive value +console.log(limiter.state.rejectionCount) // Reactive value +``` + +### Initial State + +You can provide initial state values when creating a rate limiter: + +```ts +const limiter = new RateLimiter(fn, { + limit: 5, + window: 1000, + initialState: { + executionCount: 2, // Start with 2 executions + rejectionCount: 1, // Start with 1 rejection + executionTimes: [Date.now() - 500], // Start with one execution timestamp + } +}) +``` + +### Subscribing to State Changes + +The store is reactive and supports subscriptions: + +```ts +const limiter = new RateLimiter(fn, { limit: 5, window: 1000 }) + +// Subscribe to state changes +const unsubscribe = limiter.store.subscribe((state) => { + console.log('Execution count:', state.executionCount) + console.log('Rejection count:', state.rejectionCount) + console.log('Execution times:', state.executionTimes) +}) + +// Unsubscribe when done +unsubscribe() +``` + +### Available State Properties + +The `RateLimiterState` includes: + +- `executionCount`: Number of successful function executions +- `rejectionCount`: Number of rejected executions due to rate limiting +- `executionTimes`: Array of timestamps when executions occurred (used for rate limiting calculations) + +### Helper Methods + +The rate limiter provides helper methods that compute values based on the current state: + +```ts +const limiter = new RateLimiter(fn, { limit: 5, window: 1000 }) + +// These methods use the current state to compute values +console.log(limiter.getRemainingInWindow()) // Number of calls remaining in current window +console.log(limiter.getMsUntilNextWindow()) // Milliseconds until next window +``` + +These methods are computed values that use the current state and don't need to be accessed through the store. + --- For asynchronous rate limiting (e.g., API calls, async operations), see the [Async Rate Limiting Guide](../async-rate-limiting.md). diff --git a/docs/guides/throttling.md b/docs/guides/throttling.md index 506926551..1d9ea2648 100644 --- a/docs/guides/throttling.md +++ b/docs/guides/throttling.md @@ -75,12 +75,17 @@ const updateThrottler = new Throttler( { wait: 200 } ) -// Get information about execution state -console.log(updateThrottler.getExecutionCount()) // Number of successful executions -console.log(updateThrottler.getLastExecutionTime()) // Timestamp of last execution +// Access current state via TanStack Store +console.log(updateThrottler.store.state.executionCount) // Number of successful executions +console.log(updateThrottler.store.state.lastExecutionTime) // Timestamp of last execution +console.log(updateThrottler.store.state.isPending) // Whether execution is pending +console.log(updateThrottler.store.state.status) // Current execution status // Cancel any pending execution updateThrottler.cancel() + +// Flush pending execution immediately +updateThrottler.flush() ``` ### Leading and Trailing Executions @@ -120,7 +125,7 @@ The `enabled` option can also be a function that returns a boolean, allowing for const throttler = new Throttler(fn, { wait: 200, enabled: (throttler) => { - return throttler.getExecutionCount() < 50 // Disable after 50 executions + return throttler.store.state.executionCount < 50 // Disable after 50 executions } }) ``` @@ -135,11 +140,11 @@ Several options in the Throttler support dynamic values through callback functio const throttler = new Throttler(fn, { // Dynamic wait time based on execution count wait: (throttler) => { - return throttler.getExecutionCount() * 100 // Increase wait time with each execution + return throttler.store.state.executionCount * 100 // Increase wait time with each execution }, // Dynamic enabled state based on execution count enabled: (throttler) => { - return throttler.getExecutionCount() < 50 // Disable after 50 executions + return throttler.store.state.executionCount < 50 // Disable after 50 executions } }) ``` @@ -159,13 +164,99 @@ const throttler = new Throttler(fn, { wait: 200, onExecute: (throttler) => { // Called after each successful execution - console.log('Function executed', throttler.getExecutionCount()) + console.log('Function executed', throttler.store.state.executionCount) } }) ``` The `onExecute` callback is called after each successful execution of the throttled function, making it useful for tracking executions, updating UI state, or performing cleanup operations. +## State Management + +The `Throttler` class uses TanStack Store for reactive state management, providing real-time access to execution state and timing information. + +### Accessing State + +When using the `Throttler` class directly, access state via the `store.state` property: + +```ts +const throttler = new Throttler(fn, { wait: 200 }) + +// Access current state +console.log(throttler.store.state.isPending) +``` + +### Framework Adapters + +When using framework adapters like React or Solid, the state is exposed directly as a reactive property: + +```ts +// React example +const throttler = useThrottler(fn, { wait: 200 }) + +// Access state directly (reactive) +console.log(throttler.state.executionCount) // Reactive value +console.log(throttler.state.isPending) // Reactive value +``` + +### Initial State + +You can provide initial state values when creating a throttler: + +```ts +const throttler = new Throttler(fn, { + wait: 200, + initialState: { + executionCount: 10, // Start with 10 executions + lastExecutionTime: Date.now() - 1000, // Set last execution to 1 second ago + } +}) +``` + +### Subscribing to State Changes + +The store is reactive and supports subscriptions: + +```ts +const throttler = new Throttler(fn, { wait: 200 }) + +// Subscribe to state changes +const unsubscribe = throttler.store.subscribe((state) => { + console.log('Execution count:', state.executionCount) + console.log('Last execution time:', state.lastExecutionTime) + console.log('Is pending:', state.isPending) +}) + +// Unsubscribe when done +unsubscribe() +``` + +### Available State Properties + +The `ThrottlerState` includes: + +- `executionCount`: Number of completed function executions +- `lastExecutionTime`: Timestamp of the last function execution (in milliseconds) +- `nextExecutionTime`: Timestamp when the next execution can occur (in milliseconds) +- `isPending`: Whether the throttler is waiting for timeout to trigger execution +- `status`: Current execution status ('idle' | 'pending') +- `lastArgs`: Arguments from the most recent call to `maybeExecute` + +### Flushing Pending Executions + +The throttler supports flushing pending executions to trigger them immediately: + +```ts +const throttler = new Throttler(fn, { wait: 1000 }) + +throttler.maybeExecute('some-arg') +console.log(throttler.store.state.isPending) // true + +// Flush immediately instead of waiting +throttler.flush() +console.log(throttler.store.state.isPending) // false +``` + --- For asynchronous throttling (e.g., API calls, async operations), see the [Async Throttling Guide](../async-throttling.md). \ No newline at end of file From 4d027b86f5c675564d99f4379e490c5e74962b36 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 10 Jul 2025 00:26:27 -0500 Subject: [PATCH 42/51] progress on persister --- examples/react/useStorageState/.eslintrc.cjs | 18 + examples/react/useStorageState/.gitignore | 27 ++ examples/react/useStorageState/README.md | 20 ++ examples/react/useStorageState/index.html | 16 + examples/react/useStorageState/package.json | 34 ++ .../useStorageState/public/emblem-light.svg | 13 + examples/react/useStorageState/src/index.tsx | 326 ++++++++++++++++++ examples/react/useStorageState/tsconfig.json | 23 ++ examples/react/useStorageState/vite.config.ts | 13 + packages/persister/src/async-persister.ts | 19 +- packages/persister/src/persister.ts | 7 +- packages/persister/src/storage-persister.ts | 119 +++++-- .../src/debouncer/useDebouncedCallback.ts | 2 +- .../storage-persister/useStoragePersister.ts | 11 +- .../src/storage-persister/useStorageState.ts | 67 ++-- pnpm-lock.yaml | 25 ++ 16 files changed, 659 insertions(+), 81 deletions(-) create mode 100644 examples/react/useStorageState/.eslintrc.cjs create mode 100644 examples/react/useStorageState/.gitignore create mode 100644 examples/react/useStorageState/README.md create mode 100644 examples/react/useStorageState/index.html create mode 100644 examples/react/useStorageState/package.json create mode 100644 examples/react/useStorageState/public/emblem-light.svg create mode 100644 examples/react/useStorageState/src/index.tsx create mode 100644 examples/react/useStorageState/tsconfig.json create mode 100644 examples/react/useStorageState/vite.config.ts diff --git a/examples/react/useStorageState/.eslintrc.cjs b/examples/react/useStorageState/.eslintrc.cjs new file mode 100644 index 000000000..274d731f0 --- /dev/null +++ b/examples/react/useStorageState/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + '@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/examples/react/useStorageState/.gitignore b/examples/react/useStorageState/.gitignore new file mode 100644 index 000000000..e81ff5233 --- /dev/null +++ b/examples/react/useStorageState/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# TypeScript +*.tsbuildinfo \ No newline at end of file diff --git a/examples/react/useStorageState/README.md b/examples/react/useStorageState/README.md new file mode 100644 index 000000000..abfb29f36 --- /dev/null +++ b/examples/react/useStorageState/README.md @@ -0,0 +1,20 @@ +# useStorageState Examples + +This example demonstrates the TanStack Persister storage state hooks: + +- **App1**: `useStorageState` - Generic storage state hook with localStorage +- **App2**: `useLocalStorageState` - Convenience hook for localStorage +- **App3**: `useSessionStorageState` - Convenience hook for sessionStorage + +## Features Demonstrated + +- State persistence across browser refreshes +- Cross-tab synchronization (localStorage) +- Session-only storage (sessionStorage) +- Complex state management with objects and arrays +- Form state persistence + +## To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/useStorageState/index.html b/examples/react/useStorageState/index.html new file mode 100644 index 000000000..8f1db37d8 --- /dev/null +++ b/examples/react/useStorageState/index.html @@ -0,0 +1,16 @@ + + + + + + + + + TanStack Persister useStorageState Example + + + +
    + + + diff --git a/examples/react/useStorageState/package.json b/examples/react/useStorageState/package.json new file mode 100644 index 000000000..15c97cbcd --- /dev/null +++ b/examples/react/useStorageState/package.json @@ -0,0 +1,34 @@ +{ + "name": "@tanstack/pacer-example-react-use-storage-state", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3005", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-persister": "^0.0.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.2" + }, + "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/useStorageState/public/emblem-light.svg b/examples/react/useStorageState/public/emblem-light.svg new file mode 100644 index 000000000..a58e69ad5 --- /dev/null +++ b/examples/react/useStorageState/public/emblem-light.svg @@ -0,0 +1,13 @@ + + + + emblem-light + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/react/useStorageState/src/index.tsx b/examples/react/useStorageState/src/index.tsx new file mode 100644 index 000000000..e5d5f3a7a --- /dev/null +++ b/examples/react/useStorageState/src/index.tsx @@ -0,0 +1,326 @@ +import { useState } from 'react' +import ReactDOM from 'react-dom/client' +import { + useLocalStorageState, + useSessionStorageState, +} from '@tanstack/react-persister/storage-persister' + +// App2: useLocalStorageState hook +function App2() { + const [todos, setTodos] = useLocalStorageState( + 'todos-list', + [] as Array + ) + const [newTodo, setNewTodo] = useState('') + + const addTodo = () => { + if (newTodo.trim()) { + setTodos((prev) => [...prev, newTodo.trim()]) + setNewTodo('') + } + } + + const removeTodo = (index: number) => { + setTodos((prev) => prev.filter((_, i) => i !== index)) + } + + const clearAll = () => { + setTodos([]) + } + + return ( +
    +

    TanStack Persister useLocalStorageState Example

    +

    This example uses the useLocalStorageState hook for a todo list

    + +
    +
    + setNewTodo(e.target.value)} + placeholder="Add a new todo" + style={{ flex: 1, padding: '8px' }} + onKeyPress={(e) => e.key === 'Enter' && addTodo()} + /> + +
    + + {todos.length > 0 && ( + + )} +
    + +
    +

    Todo List ({todos.length} items):

    + {todos.length === 0 ? ( +

    + No todos yet. Add one above! +

    + ) : ( +
      + {todos.map((todo, index) => ( +
    • + {todo} + +
    • + ))} +
    + )} +
    + +
    +

    • Todos persist in localStorage

    +

    • Data survives browser refreshes

    +

    • Shared across all tabs in the same domain

    +
    +
    + ) +} + +// App3: useSessionStorageState hook +function App3() { + const [cart, setCart] = useSessionStorageState( + 'shopping-cart', + [] as Array<{ id: number; name: string; quantity: number; price: number }>, + ) + const [formData, setFormData] = useSessionStorageState('checkout-form', { + email: '', + address: '', + city: '', + zipCode: '', + }) + + const products = [ + { id: 1, name: 'Widget A', price: 19.99 }, + { id: 2, name: 'Widget B', price: 29.99 }, + { id: 3, name: 'Widget C', price: 39.99 }, + ] + + const addToCart = (product: (typeof products)[0]) => { + setCart((prev) => { + const existing = prev.find((item) => item.id === product.id) + if (existing) { + return prev.map((item) => + item.id === product.id + ? { ...item, quantity: item.quantity + 1 } + : item, + ) + } + return [...prev, { ...product, quantity: 1 }] + }) + } + + const removeFromCart = (id: number) => { + setCart((prev) => prev.filter((item) => item.id !== id)) + } + + const updateFormData = (field: string, value: string) => { + setFormData((prev) => ({ ...prev, [field]: value })) + } + + const getTotalPrice = () => { + return cart + .reduce((sum, item) => sum + item.price * item.quantity, 0) + .toFixed(2) + } + + return ( +
    +

    TanStack Persister useSessionStorageState Example

    +

    + This example uses the useSessionStorageState hook for a shopping cart +

    + +
    +
    +

    Products:

    + {products.map((product) => ( +
    + + {product.name} - ${product.price} + + +
    + ))} +
    + +
    +

    Shopping Cart ({cart.length} items):

    + {cart.length === 0 ? ( +

    Cart is empty

    + ) : ( + <> + {cart.map((item) => ( +
    + + {item.name} x{item.quantity} + + ${(item.price * item.quantity).toFixed(2)} + +
    + ))} +
    + Total: ${getTotalPrice()} +
    + + )} +
    +
    + +
    +

    Checkout Form:

    +
    + updateFormData('email', e.target.value)} + style={{ padding: '8px' }} + /> + updateFormData('address', e.target.value)} + style={{ padding: '8px' }} + /> + updateFormData('city', e.target.value)} + style={{ padding: '8px' }} + /> + updateFormData('zipCode', e.target.value)} + style={{ padding: '8px' }} + /> +
    +
    + +
    +

    • Data persists in sessionStorage (only this tab)

    +

    • Cart and form data cleared when tab is closed

    +

    • Perfect for temporary shopping sessions

    +
    +
    + ) +} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render( +
    + +
    + +
    , +) diff --git a/examples/react/useStorageState/tsconfig.json b/examples/react/useStorageState/tsconfig.json new file mode 100644 index 000000000..6e9088d67 --- /dev/null +++ b/examples/react/useStorageState/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/useStorageState/vite.config.ts b/examples/react/useStorageState/vite.config.ts new file mode 100644 index 000000000..4e1943662 --- /dev/null +++ b/examples/react/useStorageState/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/packages/persister/src/async-persister.ts b/packages/persister/src/async-persister.ts index f7c1336e7..14410afd5 100644 --- a/packages/persister/src/async-persister.ts +++ b/packages/persister/src/async-persister.ts @@ -1,9 +1,20 @@ /** - * Interface for an async persister that can save/load state for a given type + * Abstract class that defines the contract for an asynchronous state persister. + * An async persister is responsible for saving and loading state to a storage medium using asynchronous operations. + * + * This is useful for scenarios where persistence involves asynchronous APIs, such as IndexedDB, remote storage, or custom async backends. + * + * Implementations should provide methods to load, save, and clear persisted state for a given key. + * + * See also: `Persister` for synchronous persister implementations. */ -export abstract class AsyncPersister { +export abstract class AsyncPersister< + TState, + TSelected extends Partial = TState, +> { constructor(public readonly key: string) {} - abstract loadState: () => Promise - abstract saveState: (state: TState) => Promise + abstract loadState: () => TSelected | undefined + abstract saveState: (state: TState) => void + abstract clearState: (useDefaultState?: boolean) => void } diff --git a/packages/persister/src/persister.ts b/packages/persister/src/persister.ts index cb210a4e0..1250ab345 100644 --- a/packages/persister/src/persister.ts +++ b/packages/persister/src/persister.ts @@ -24,10 +24,13 @@ * } * ``` */ -export abstract class Persister { +export abstract class Persister< + TState, + TSelected extends Partial = TState, +> { constructor(public readonly key: string) {} - abstract loadState: () => TState | undefined + abstract loadState: () => TSelected | undefined abstract saveState: (state: TState) => void abstract clearState: (useDefaultState?: boolean) => void } diff --git a/packages/persister/src/storage-persister.ts b/packages/persister/src/storage-persister.ts index 37b58f814..27376e674 100644 --- a/packages/persister/src/storage-persister.ts +++ b/packages/persister/src/storage-persister.ts @@ -1,9 +1,12 @@ import { Persister } from './persister' import type { RequiredKeys } from './types' -export interface PersistedStorage { +export interface PersistedStorage< + TState, + TSelected extends Partial = TState, +> { buster?: string - state: TState | undefined + state: TSelected | undefined timestamp: number } @@ -13,7 +16,10 @@ export interface PersistedStorage { * The persister can use either localStorage (persists across browser sessions) or * sessionStorage (cleared when browser tab/window closes) to store serialized state. */ -export interface StoragePersisterOptions { +export interface StoragePersisterOptions< + TState, + TSelected extends Partial = TState, +> { /** * A version string used to invalidate cached state. When changed, any existing * stored state will be considered invalid and cleared. @@ -29,7 +35,7 @@ export interface StoragePersisterOptions { * * Optionally, consider using SuperJSON for better deserialization of complex objects. See https://github.com/flightcontrolhq/superjson */ - deserializer?: (state: string) => PersistedStorage + deserializer?: (state: string) => PersistedStorage /** * Unique identifier used as the storage key for persisting state. */ @@ -42,42 +48,54 @@ export interface StoragePersisterOptions { /** * Optional callback that runs after state is successfully loaded. */ - onLoadState?: (state: TState | undefined) => void + onLoadState?: ( + state: TSelected | undefined, + storagePersister: StoragePersister, + ) => void /** * Optional callback that runs after state is unable to be loaded. */ - onLoadStateError?: (error: Error) => void + onLoadStateError?: ( + error: Error, + storagePersister: StoragePersister, + ) => void /** * Optional callback that runs after state is successfully saved. */ - onSaveState?: (state: TState) => void + onSaveState?: ( + state: TSelected, + storagePersister: StoragePersister, + ) => void /** * Optional callback that runs after state is unable to be saved. * For example, if the storage is full (localStorage >= 5MB) */ - onSaveStateError?: (error: Error) => void + onSaveStateError?: ( + error: Error, + storagePersister: StoragePersister, + ) => void /** * Optional function to customize how state is serialized before saving to storage. * By default, JSON.stringify is used. * * Optionally, consider using SuperJSON for better serialization of complex objects. See https://github.com/flightcontrolhq/superjson */ - serializer?: (state: PersistedStorage) => string + serializer?: (state: PersistedStorage) => string /** - * Optional function to filter which parts of the state are persisted and loaded, or to otherwise transform the state before saving or loading. + * Optional function to filter which parts of the state are persisted and loaded. * When provided, only the filtered state will be saved to storage and returned when loading. * This is useful for excluding sensitive or temporary data from persistence. * * Note: Don't use this to replace the serialization. Use the `serializer` option instead for that. */ - stateTransform?: (state: TState) => Partial + select?: (state: TState) => TSelected /** * The browser storage implementation to use for persisting state. * Typically window.localStorage or window.sessionStorage. * * Defaults to window.localStorage. */ - storage?: Storage + storage?: Storage | null } type DefaultOptions = RequiredKeys< @@ -88,7 +106,7 @@ type DefaultOptions = RequiredKeys< const defaultOptions: DefaultOptions = { deserializer: JSON.parse, serializer: JSON.stringify, - storage: window.localStorage, + storage: typeof window !== 'undefined' ? window.localStorage : null, } /** @@ -122,10 +140,13 @@ const defaultOptions: DefaultOptions = { * }) * ``` */ -export class StoragePersister extends Persister { - options: StoragePersisterOptions & DefaultOptions +export class StoragePersister< + TState, + TSelected extends Partial = TState, +> extends Persister { + options: StoragePersisterOptions & DefaultOptions - constructor(initialOptions: StoragePersisterOptions) { + constructor(initialOptions: StoragePersisterOptions) { super(initialOptions.key) this.options = { ...defaultOptions, @@ -136,19 +157,22 @@ export class StoragePersister extends Persister { /** * Updates the persister options */ - setOptions = (newOptions: Partial>): void => { + setOptions = ( + newOptions: Partial>, + ): void => { this.options = { ...this.options, ...newOptions } } /** * Saves the state to storage */ - saveState = (state: TState): void => { + saveState = (state: TState | TSelected): void => { try { - const stateToSave = this.options.stateTransform - ? this.options.stateTransform(state) + const stateToSave = this.options.select + ? this.options.select(state) : state - this.options.storage.setItem( + + this.options.storage?.setItem( this.key, this.options.serializer({ buster: this.options.buster, @@ -156,47 +180,78 @@ export class StoragePersister extends Persister { timestamp: Date.now(), }), ) - this.options.onSaveState?.(state) + this.options.onSaveState?.(state, this) } catch (error) { console.error(error) - this.options.onSaveStateError?.(error as Error) + this.options.onSaveStateError?.(error as Error, this) } } /** * Loads the state from storage */ - loadState = (): TState | undefined => { - const stored = this.options.storage.getItem(this.key) + loadState = (): TSelected | undefined => { + const stored = this.options.storage?.getItem(this.key) if (!stored) { return undefined } try { const parsed = this.options.deserializer(stored) - const isValid = + + const isValidVersion = !this.options.buster || parsed.buster === this.options.buster + const isNotExpired = !this.options.maxAge || !parsed.timestamp || Date.now() - parsed.timestamp <= this.options.maxAge - if (!isValid || !isNotExpired) { + if (!isValidVersion || !isNotExpired) { // clear the item from storage - this.options.storage.removeItem(this.key) + this.options.storage?.removeItem(this.key) return undefined } - const state = parsed.state as TState - this.options.onLoadState?.(state) + const state = parsed.state + this.options.onLoadState?.(state, this) return state } catch (error) { console.error(error) - this.options.onLoadStateError?.(error as Error) + this.options.onLoadStateError?.(error as Error, this) return undefined } } + /** + * Handles the storage event and ignores events triggered by the current tab. + * + * The storage event is only fired in other tabs/windows, not the one that made the change. + * However, some browsers or environments may fire it in the same tab, so we check if the event's storageArea + * matches the persister's storage and ignore if not. + */ + private handleStorageChange = (e: StorageEvent) => { + // Ignore events not from the same storage area or not matching our key + if (e.storageArea !== this.options.storage) { + return + } + // Ignore events triggered by the current tab (storage event is not fired in the same tab in most browsers) + // But this check is defensive in case of non-standard environments + if (e.key === this.key && e.newValue) { + this.loadState() + } + } + + subscribeToStorage = (): void => { + typeof window !== 'undefined' && + window.addEventListener('storage', this.handleStorageChange) + } + + unsubscribeFromStorage = (): void => { + typeof window !== 'undefined' && + window.removeEventListener('storage', this.handleStorageChange) + } + /** * Clears the state from storage or sets the default state if provided and specified to be used */ @@ -204,7 +259,7 @@ export class StoragePersister extends Persister { if (useDefaultState) { this.saveState(this.options.defaultState) } else { - this.options.storage.removeItem(this.key) + this.options.storage?.removeItem(this.key) } } } diff --git a/packages/react-pacer/src/debouncer/useDebouncedCallback.ts b/packages/react-pacer/src/debouncer/useDebouncedCallback.ts index cc6e87448..a7d4095d1 100644 --- a/packages/react-pacer/src/debouncer/useDebouncedCallback.ts +++ b/packages/react-pacer/src/debouncer/useDebouncedCallback.ts @@ -48,7 +48,7 @@ export function useDebouncedCallback< >( fn: TFn, options: DebouncerOptions, - selector?: (state: DebouncerState) => TSelected, + selector: (state: DebouncerState) => TSelected = () => ({}) as TSelected, ): (...args: Parameters) => void { const debouncedFn = useDebouncer(fn, options, selector).maybeExecute return useCallback((...args) => debouncedFn(...args), [debouncedFn]) diff --git a/packages/react-persister/src/storage-persister/useStoragePersister.ts b/packages/react-persister/src/storage-persister/useStoragePersister.ts index 37e0f2a5f..c4aecf534 100644 --- a/packages/react-persister/src/storage-persister/useStoragePersister.ts +++ b/packages/react-persister/src/storage-persister/useStoragePersister.ts @@ -2,10 +2,13 @@ import { useState } from 'react' import { StoragePersister } from '@tanstack/persister/storage-persister' import type { StoragePersisterOptions } from '@tanstack/persister/storage-persister' -export function useStoragePersister( - options: StoragePersisterOptions, -) { - const [persister] = useState(() => new StoragePersister(options)) +export function useStoragePersister< + TState, + TSelected extends Partial = TState, +>( + options: StoragePersisterOptions, +): StoragePersister { + const [persister] = useState(() => new StoragePersister(options)) persister.setOptions(options) diff --git a/packages/react-persister/src/storage-persister/useStorageState.ts b/packages/react-persister/src/storage-persister/useStorageState.ts index 16c12a4b2..3128a8aa2 100644 --- a/packages/react-persister/src/storage-persister/useStorageState.ts +++ b/packages/react-persister/src/storage-persister/useStorageState.ts @@ -2,35 +2,26 @@ import { useEffect, useState } from 'react' import { useStoragePersister } from './useStoragePersister' import type { StoragePersisterOptions } from '@tanstack/persister/storage-persister' -function useStorageState( - initialValue: TValue, - options: StoragePersisterOptions, +function useStorageState = TState>( + initialValue: TState, + options: StoragePersisterOptions, ) { - const persister = useStoragePersister(options) + const persister = useStoragePersister(options) - const [state, setState] = useState(() => { - return persister.loadState() ?? initialValue - }) + const [state, setState] = useState(initialValue) useEffect(() => { - persister.saveState(state) + // eslint-disable-next-line @eslint-react/hooks-extra/no-direct-set-state-in-use-effect + setState(persister.loadState() ?? initialValue) + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) - const handleStorageChange = (e: StorageEvent) => { - if (e.key === options.key && e.newValue) { - try { - const parsed = (options.deserializer ?? JSON.parse)(e.newValue) - if (parsed.state) { - setState(parsed.state) - } - } catch (e) { - console.error('Failed to parse storage event', e) - } - } - } - - window.addEventListener('storage', handleStorageChange) - return () => window.removeEventListener('storage', handleStorageChange) - }, [state, persister, options.deserializer, options.key]) + useEffect(() => { + persister.saveState(state) + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state]) return [state, setState] as const } @@ -43,18 +34,18 @@ function useStorageState( * const [value, setValue] = useLocalStorageState('my-key', 'initial value') * ``` */ -export function useLocalStorageState( +export function useLocalStorageState< + TValue, + TSelected extends Partial = TValue, +>( key: string, initialValue: TValue, - options?: { - buster?: string - maxAge?: number - }, + options?: Omit, 'key' | 'storage'>, ) { return useStorageState(initialValue, { - key, - storage: localStorage, ...options, + key, + storage: typeof window !== 'undefined' ? localStorage : null, }) } @@ -66,17 +57,17 @@ export function useLocalStorageState( * const [value, setValue] = useSessionStorageState('my-key', 'initial value') * ``` */ -export function useSessionStorageState( +export function useSessionStorageState< + TValue, + TSelected extends Partial = TValue, +>( key: string, initialValue: TValue, - options?: { - buster?: string - maxAge?: number - }, + options?: Omit, 'key' | 'storage'>, ) { return useStorageState(initialValue, { - key, - storage: sessionStorage, ...options, + key, + storage: typeof window !== 'undefined' ? sessionStorage : null, }) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24418c7da..e38547a7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -830,6 +830,31 @@ importers: specifier: ^7.0.2 version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + examples/react/useStorageState: + dependencies: + '@tanstack/react-persister': + specifier: ^0.0.0 + version: link:../../../packages/react-persister + react: + specifier: ^19.1.0 + version: 19.1.0 + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + devDependencies: + '@types/react': + specifier: ^19.1.8 + version: 19.1.8 + '@types/react-dom': + specifier: ^19.1.6 + version: 19.1.6(@types/react@19.1.8) + '@vitejs/plugin-react': + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite: + specifier: ^7.0.2 + version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + examples/react/useThrottledCallback: dependencies: '@tanstack/react-pacer': From a172562f4a039d0bb0d59c27d446df08ce158664 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 05:27:32 +0000 Subject: [PATCH 43/51] ci: apply automated fixes --- .../react/reference/functions/usedebouncedcallback.md | 4 ++-- examples/react/useStorageState/src/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/framework/react/reference/functions/usedebouncedcallback.md b/docs/framework/react/reference/functions/usedebouncedcallback.md index cf67f61fc..ba66d0c6f 100644 --- a/docs/framework/react/reference/functions/usedebouncedcallback.md +++ b/docs/framework/react/reference/functions/usedebouncedcallback.md @@ -11,7 +11,7 @@ title: useDebouncedCallback function useDebouncedCallback( fn, options, - selector?): (...args) => void + selector): (...args) => void ``` Defined in: [react-pacer/src/debouncer/useDebouncedCallback.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncedCallback.ts#L45) @@ -51,7 +51,7 @@ Consider using the `useDebouncer` hook instead. `DebouncerOptions`\<`TFn`\> -### selector? +### selector (`state`) => `TSelected` diff --git a/examples/react/useStorageState/src/index.tsx b/examples/react/useStorageState/src/index.tsx index e5d5f3a7a..58db663b4 100644 --- a/examples/react/useStorageState/src/index.tsx +++ b/examples/react/useStorageState/src/index.tsx @@ -9,7 +9,7 @@ import { function App2() { const [todos, setTodos] = useLocalStorageState( 'todos-list', - [] as Array + [] as Array, ) const [newTodo, setNewTodo] = useState('') From 1d5ada46006ce9dca41956afc8c2f97f4b051432 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 10 Jul 2025 00:27:54 -0500 Subject: [PATCH 44/51] update persister tests --- packages/persister/tests/persister.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/persister/tests/persister.test.ts b/packages/persister/tests/persister.test.ts index 8ebef9f1b..c11f9c444 100644 --- a/packages/persister/tests/persister.test.ts +++ b/packages/persister/tests/persister.test.ts @@ -48,7 +48,7 @@ describe('createStoragePersister', () => { onSaveStateError, }) persister.saveState({ count: 1 }) - expect(onSaveStateError).toHaveBeenCalledWith(error) + expect(onSaveStateError).toHaveBeenCalledWith(error, persister) }) it('should call onSaveState callback when state is saved', () => { @@ -60,7 +60,7 @@ describe('createStoragePersister', () => { }) const state = { count: 1 } persister.saveState(state) - expect(onSaveState).toHaveBeenCalledWith(state) + expect(onSaveState).toHaveBeenCalledWith(state, persister) }) it('should call onLoadState callback when state is loaded', () => { @@ -75,7 +75,7 @@ describe('createStoragePersister', () => { JSON.stringify({ state, timestamp: Date.now() }), ) persister.loadState() - expect(onLoadState).toHaveBeenCalledWith(state) + expect(onLoadState).toHaveBeenCalledWith(state, persister) }) it('should respect buster string when loading state', () => { From dc7a17e5bab2c02878c35606be9be5e2e9055ab6 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 10 Jul 2025 00:38:15 -0500 Subject: [PATCH 45/51] add async callback variants to react adapter --- .../react-pacer/src/async-debouncer/index.ts | 1 + .../useAsyncDebouncedCallback.ts | 60 +++++++++++++++ .../src/async-rate-limiter/index.ts | 1 + .../useAsyncRateLimitedCallback.ts | 75 +++++++++++++++++++ .../react-pacer/src/async-throttler/index.ts | 1 + .../useAsyncThrottledCallback.ts | 58 ++++++++++++++ packages/react-pacer/src/index.ts | 3 + .../rate-limiter/useRateLimitedCallback.ts | 2 +- .../src/throttler/useThrottledCallback.ts | 2 +- 9 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts create mode 100644 packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts create mode 100644 packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts diff --git a/packages/react-pacer/src/async-debouncer/index.ts b/packages/react-pacer/src/async-debouncer/index.ts index e61e0b246..ac389c847 100644 --- a/packages/react-pacer/src/async-debouncer/index.ts +++ b/packages/react-pacer/src/async-debouncer/index.ts @@ -2,3 +2,4 @@ export * from '@tanstack/pacer/async-debouncer' export * from './useAsyncDebouncer' +export * from './useAsyncDebouncedCallback' diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts new file mode 100644 index 000000000..88bee2da7 --- /dev/null +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts @@ -0,0 +1,60 @@ +import { useCallback } from 'react' +import { useAsyncDebouncer } from './useAsyncDebouncer' +import type { + AsyncDebouncerOptions, + AsyncDebouncerState, +} from '@tanstack/pacer/async-debouncer' +import type { AnyAsyncFunction } from '@tanstack/pacer/types' + +/** + * A React hook that creates a debounced version of an async callback function. + * This hook is a convenient wrapper around the `useAsyncDebouncer` hook, + * providing a stable, debounced async function reference for use in React components. + * + * The debounced async function will only execute after the specified wait time has elapsed + * since its last invocation. If called again before the wait time expires, the timer + * resets and starts waiting again. The returned function always returns a promise + * that resolves or rejects with the result of the original async function. + * + * This hook provides a simpler API compared to `useAsyncDebouncer`, making it ideal for basic + * async debouncing needs. However, it does not expose the underlying AsyncDebouncer instance. + * + * For advanced usage requiring features like: + * - Manual cancellation + * - Access to execution/error state + * - Custom useCallback dependencies + * + * Consider using the `useAsyncDebouncer` hook instead. + * + * @example + * ```tsx + * // Debounce an async search handler + * const handleSearch = useAsyncDebouncedCallback(async (query: string) => { + * const results = await fetchSearchResults(query); + * return results; + * }, { + * wait: 500 // Wait 500ms between executions + * }); + * + * // Use in an input + * handleSearch(e.target.value)} + * /> + * ``` + */ +export function useAsyncDebouncedCallback< + TFn extends AnyAsyncFunction, + TSelected = AsyncDebouncerState, +>( + fn: TFn, + options: AsyncDebouncerOptions, + selector: (state: AsyncDebouncerState) => TSelected = () => + ({}) as TSelected, +): (...args: Parameters) => Promise> { + const asyncDebouncedFn = useAsyncDebouncer(fn, options, selector).maybeExecute + return useCallback( + (...args) => asyncDebouncedFn(...args) as Promise>, + [asyncDebouncedFn], + ) +} diff --git a/packages/react-pacer/src/async-rate-limiter/index.ts b/packages/react-pacer/src/async-rate-limiter/index.ts index f10072171..14a0cc7e7 100644 --- a/packages/react-pacer/src/async-rate-limiter/index.ts +++ b/packages/react-pacer/src/async-rate-limiter/index.ts @@ -2,3 +2,4 @@ export * from '@tanstack/pacer/async-rate-limiter' export * from './useAsyncRateLimiter' +export * from './useAsyncRateLimitedCallback' diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts new file mode 100644 index 000000000..37f158b72 --- /dev/null +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts @@ -0,0 +1,75 @@ +import { useCallback } from 'react' +import { useAsyncRateLimiter } from './useAsyncRateLimiter' +import type { AnyAsyncFunction } from '@tanstack/pacer/types' +import type { + AsyncRateLimiterOptions, + AsyncRateLimiterState, +} from '@tanstack/pacer/async-rate-limiter' + +/** + * A React hook that creates a rate-limited version of an async callback function. + * This hook is a convenient wrapper around the `useAsyncRateLimiter` hook, + * providing a stable, async rate-limited function reference for use in React components. + * + * Async rate limiting is a "hard limit" approach for async functions: it allows all calls + * until the limit is reached, then blocks (rejects) subsequent calls until the window resets. + * Unlike throttling or debouncing, it does not attempt to space out or collapse calls. + * This can lead to bursts of rapid executions followed by periods where all calls are blocked. + * + * The async rate limiter supports two types of windows: + * - '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. + * + * For smoother execution patterns, consider: + * - useAsyncThrottledCallback: When you want consistent spacing between executions (e.g. UI updates) + * - useAsyncDebouncedCallback: When you want to collapse rapid calls into a single execution (e.g. search input) + * + * Async rate limiting should primarily be used when you need to enforce strict limits + * on async operations, like API rate limits or other scenarios requiring hard caps + * on execution frequency. + * + * This hook provides a simpler API compared to `useAsyncRateLimiter`, making it ideal for basic + * async rate limiting needs. However, it does not expose the underlying AsyncRateLimiter instance. + * + * For advanced usage requiring features like: + * - Manual cancellation + * - Access to execution counts + * - Custom useCallback dependencies + * + * Consider using the `useAsyncRateLimiter` hook instead. + * + * @example + * ```tsx + * // Rate limit async API calls to maximum 5 calls per minute with a sliding window + * const makeApiCall = useAsyncRateLimitedCallback( + * async (data: ApiData) => { + * return fetch('/api/endpoint', { method: 'POST', body: JSON.stringify(data) }); + * }, + * { + * limit: 5, + * window: 60000, // 1 minute + * windowType: 'sliding', + * onReject: () => { + * console.warn('API rate limit reached. Please wait before trying again.'); + * } + * } + * ); + * ``` + */ +export function useAsyncRateLimitedCallback< + TFn extends AnyAsyncFunction, + TSelected = AsyncRateLimiterState, +>( + fn: TFn, + options: AsyncRateLimiterOptions, + selector: (state: AsyncRateLimiterState) => TSelected = () => + ({}) as TSelected, +): (...args: Parameters) => Promise> { + const asyncRateLimitedFn = useAsyncRateLimiter(fn, options, selector).maybeExecute + return useCallback( + (...args) => asyncRateLimitedFn(...args) as Promise>, + [asyncRateLimitedFn], + ) +} diff --git a/packages/react-pacer/src/async-throttler/index.ts b/packages/react-pacer/src/async-throttler/index.ts index 284251dac..f557de642 100644 --- a/packages/react-pacer/src/async-throttler/index.ts +++ b/packages/react-pacer/src/async-throttler/index.ts @@ -2,3 +2,4 @@ export * from '@tanstack/pacer/async-throttler' export * from './useAsyncThrottler' +export * from './useAsyncThrottledCallback' diff --git a/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts b/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts new file mode 100644 index 000000000..2cac8c541 --- /dev/null +++ b/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts @@ -0,0 +1,58 @@ +import { useCallback } from 'react' +import { useAsyncThrottler } from './useAsyncThrottler' +import type { + AsyncThrottlerOptions, + AsyncThrottlerState, +} from '@tanstack/pacer/async-throttler' +import type { AnyAsyncFunction } from '@tanstack/pacer/types' + +/** + * A React hook that creates a throttled version of an async callback function. + * This hook is a convenient wrapper around the `useAsyncThrottler` hook, + * providing a stable, throttled async function reference for use in React components. + * + * The throttled async function will execute at most once within the specified wait time period, + * regardless of how many times it is called. If called multiple times during the wait period, + * only the first invocation will execute, and subsequent calls will be ignored until + * the wait period has elapsed. The returned function always returns a promise + * that resolves or rejects with the result of the original async function. + * + * This hook provides a simpler API compared to `useAsyncThrottler`, making it ideal for basic + * async throttling needs. However, it does not expose the underlying AsyncThrottler instance. + * + * For advanced usage requiring features like: + * - Manual cancellation + * - Access to execution/error state + * - Custom useCallback dependencies + * + * Consider using the `useAsyncThrottler` hook instead. + * + * @example + * ```tsx + * // Throttle an async API call + * const handleApiCall = useAsyncThrottledCallback(async (data) => { + * const result = await sendDataToServer(data); + * return result; + * }, { + * wait: 200 // Execute at most once every 200ms + * }); + * + * // Use in an event handler + * + * ``` + */ +export function useAsyncThrottledCallback< + TFn extends AnyAsyncFunction, + TSelected = AsyncThrottlerState, +>( + fn: TFn, + options: AsyncThrottlerOptions, + selector: (state: AsyncThrottlerState) => TSelected = () => + ({}) as TSelected, +): (...args: Parameters) => Promise> { + const asyncThrottledFn = useAsyncThrottler(fn, options, selector).maybeExecute + return useCallback( + (...args) => asyncThrottledFn(...args) as Promise>, + [asyncThrottledFn], + ) +} diff --git a/packages/react-pacer/src/index.ts b/packages/react-pacer/src/index.ts index aabb18883..790ad0da5 100644 --- a/packages/react-pacer/src/index.ts +++ b/packages/react-pacer/src/index.ts @@ -10,6 +10,7 @@ export * from './async-batcher/useAsyncBatcher' // async-debouncer export * from './async-debouncer/useAsyncDebouncer' +export * from './async-debouncer/useAsyncDebouncedCallback' // async-queuer export * from './async-queuer/useAsyncQueuer' @@ -17,9 +18,11 @@ export * from './async-queuer/useAsyncQueuedState' // async-rate-limiter export * from './async-rate-limiter/useAsyncRateLimiter' +export * from './async-rate-limiter/useAsyncRateLimitedCallback' // async-throttler export * from './async-throttler/useAsyncThrottler' +export * from './async-throttler/useAsyncThrottledCallback' // batcher export * from './batcher/useBatcher' diff --git a/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts b/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts index 56531282f..d09b234be 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts @@ -65,7 +65,7 @@ export function useRateLimitedCallback< >( fn: TFn, options: RateLimiterOptions, - selector?: (state: RateLimiterState) => TSelected, + selector: (state: RateLimiterState) => TSelected = () => ({}) as TSelected, ): (...args: Parameters) => boolean { const rateLimitedFn = useRateLimiter(fn, options, selector).maybeExecute return useCallback((...args) => rateLimitedFn(...args), [rateLimitedFn]) diff --git a/packages/react-pacer/src/throttler/useThrottledCallback.ts b/packages/react-pacer/src/throttler/useThrottledCallback.ts index e75f76f29..474bf2128 100644 --- a/packages/react-pacer/src/throttler/useThrottledCallback.ts +++ b/packages/react-pacer/src/throttler/useThrottledCallback.ts @@ -49,7 +49,7 @@ export function useThrottledCallback< >( fn: TFn, options: ThrottlerOptions, - selector?: (state: ThrottlerState) => TSelected, + selector: (state: ThrottlerState) => TSelected = () => ({}) as TSelected, ): (...args: Parameters) => void { const throttledFn = useThrottler(fn, options, selector).maybeExecute return useCallback((...args) => throttledFn(...args), [throttledFn]) From 86232748082d7b17059fa9a39513677a620869f2 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 05:39:10 +0000 Subject: [PATCH 46/51] ci: apply automated fixes --- .../functions/useasyncdebouncedcallback.md | 88 +++++++++++++++ .../functions/useasyncratelimitedcallback.md | 103 ++++++++++++++++++ .../functions/useasyncthrottledcallback.md | 86 +++++++++++++++ .../functions/useratelimitedcallback.md | 4 +- .../functions/usethrottledcallback.md | 4 +- docs/framework/react/reference/index.md | 3 + .../useAsyncRateLimitedCallback.ts | 6 +- 7 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 docs/framework/react/reference/functions/useasyncdebouncedcallback.md create mode 100644 docs/framework/react/reference/functions/useasyncratelimitedcallback.md create mode 100644 docs/framework/react/reference/functions/useasyncthrottledcallback.md diff --git a/docs/framework/react/reference/functions/useasyncdebouncedcallback.md b/docs/framework/react/reference/functions/useasyncdebouncedcallback.md new file mode 100644 index 000000000..1e1c764b5 --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncdebouncedcallback.md @@ -0,0 +1,88 @@ +--- +id: useAsyncDebouncedCallback +title: useAsyncDebouncedCallback +--- + + + +# Function: useAsyncDebouncedCallback() + +```ts +function useAsyncDebouncedCallback( + fn, + options, +selector): (...args) => Promise> +``` + +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts#L46) + +A React hook that creates a debounced version of an async callback function. +This hook is a convenient wrapper around the `useAsyncDebouncer` hook, +providing a stable, debounced async function reference for use in React components. + +The debounced async function will only execute after the specified wait time has elapsed +since its last invocation. If called again before the wait time expires, the timer +resets and starts waiting again. The returned function always returns a promise +that resolves or rejects with the result of the original async function. + +This hook provides a simpler API compared to `useAsyncDebouncer`, making it ideal for basic +async debouncing needs. However, it does not expose the underlying AsyncDebouncer instance. + +For advanced usage requiring features like: +- Manual cancellation +- Access to execution/error state +- Custom useCallback dependencies + +Consider using the `useAsyncDebouncer` hook instead. + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncDebouncerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`AsyncDebouncerOptions`\<`TFn`\> + +### selector + +(`state`) => `TSelected` + +## Returns + +`Function` + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`Promise`\<`ReturnType`\<`TFn`\>\> + +## Example + +```tsx +// Debounce an async search handler +const handleSearch = useAsyncDebouncedCallback(async (query: string) => { + const results = await fetchSearchResults(query); + return results; +}, { + wait: 500 // Wait 500ms between executions +}); + +// Use in an input + handleSearch(e.target.value)} +/> +``` diff --git a/docs/framework/react/reference/functions/useasyncratelimitedcallback.md b/docs/framework/react/reference/functions/useasyncratelimitedcallback.md new file mode 100644 index 000000000..be04007fa --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncratelimitedcallback.md @@ -0,0 +1,103 @@ +--- +id: useAsyncRateLimitedCallback +title: useAsyncRateLimitedCallback +--- + + + +# Function: useAsyncRateLimitedCallback() + +```ts +function useAsyncRateLimitedCallback( + fn, + options, +selector): (...args) => Promise> +``` + +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts:61](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts#L61) + +A React hook that creates a rate-limited version of an async callback function. +This hook is a convenient wrapper around the `useAsyncRateLimiter` hook, +providing a stable, async rate-limited function reference for use in React components. + +Async rate limiting is a "hard limit" approach for async functions: it allows all calls +until the limit is reached, then blocks (rejects) subsequent calls until the window resets. +Unlike throttling or debouncing, it does not attempt to space out or collapse calls. +This can lead to bursts of rapid executions followed by periods where all calls are blocked. + +The async rate limiter supports two types of windows: +- '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. + +For smoother execution patterns, consider: +- useAsyncThrottledCallback: When you want consistent spacing between executions (e.g. UI updates) +- useAsyncDebouncedCallback: When you want to collapse rapid calls into a single execution (e.g. search input) + +Async rate limiting should primarily be used when you need to enforce strict limits +on async operations, like API rate limits or other scenarios requiring hard caps +on execution frequency. + +This hook provides a simpler API compared to `useAsyncRateLimiter`, making it ideal for basic +async rate limiting needs. However, it does not expose the underlying AsyncRateLimiter instance. + +For advanced usage requiring features like: +- Manual cancellation +- Access to execution counts +- Custom useCallback dependencies + +Consider using the `useAsyncRateLimiter` hook instead. + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncRateLimiterState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`AsyncRateLimiterOptions`\<`TFn`\> + +### selector + +(`state`) => `TSelected` + +## Returns + +`Function` + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`Promise`\<`ReturnType`\<`TFn`\>\> + +## Example + +```tsx +// Rate limit async API calls to maximum 5 calls per minute with a sliding window +const makeApiCall = useAsyncRateLimitedCallback( + async (data: ApiData) => { + return fetch('/api/endpoint', { method: 'POST', body: JSON.stringify(data) }); + }, + { + limit: 5, + window: 60000, // 1 minute + windowType: 'sliding', + onReject: () => { + console.warn('API rate limit reached. Please wait before trying again.'); + } + } +); +``` diff --git a/docs/framework/react/reference/functions/useasyncthrottledcallback.md b/docs/framework/react/reference/functions/useasyncthrottledcallback.md new file mode 100644 index 000000000..6a87ba995 --- /dev/null +++ b/docs/framework/react/reference/functions/useasyncthrottledcallback.md @@ -0,0 +1,86 @@ +--- +id: useAsyncThrottledCallback +title: useAsyncThrottledCallback +--- + + + +# Function: useAsyncThrottledCallback() + +```ts +function useAsyncThrottledCallback( + fn, + options, +selector): (...args) => Promise> +``` + +Defined in: [react-pacer/src/async-throttler/useAsyncThrottledCallback.ts:44](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts#L44) + +A React hook that creates a throttled version of an async callback function. +This hook is a convenient wrapper around the `useAsyncThrottler` hook, +providing a stable, throttled async function reference for use in React components. + +The throttled async function will execute at most once within the specified wait time period, +regardless of how many times it is called. If called multiple times during the wait period, +only the first invocation will execute, and subsequent calls will be ignored until +the wait period has elapsed. The returned function always returns a promise +that resolves or rejects with the result of the original async function. + +This hook provides a simpler API compared to `useAsyncThrottler`, making it ideal for basic +async throttling needs. However, it does not expose the underlying AsyncThrottler instance. + +For advanced usage requiring features like: +- Manual cancellation +- Access to execution/error state +- Custom useCallback dependencies + +Consider using the `useAsyncThrottler` hook instead. + +## Type Parameters + +• **TFn** *extends* `AnyAsyncFunction` + +• **TSelected** = `AsyncThrottlerState`\<`TFn`\> + +## Parameters + +### fn + +`TFn` + +### options + +`AsyncThrottlerOptions`\<`TFn`\> + +### selector + +(`state`) => `TSelected` + +## Returns + +`Function` + +### Parameters + +#### args + +...`Parameters`\<`TFn`\> + +### Returns + +`Promise`\<`ReturnType`\<`TFn`\>\> + +## Example + +```tsx +// Throttle an async API call +const handleApiCall = useAsyncThrottledCallback(async (data) => { + const result = await sendDataToServer(data); + return result; +}, { + wait: 200 // Execute at most once every 200ms +}); + +// Use in an event handler + +``` diff --git a/docs/framework/react/reference/functions/useratelimitedcallback.md b/docs/framework/react/reference/functions/useratelimitedcallback.md index e5c1ad5b8..370f0485d 100644 --- a/docs/framework/react/reference/functions/useratelimitedcallback.md +++ b/docs/framework/react/reference/functions/useratelimitedcallback.md @@ -11,7 +11,7 @@ title: useRateLimitedCallback function useRateLimitedCallback( fn, options, - selector?): (...args) => boolean + selector): (...args) => boolean ``` Defined in: [react-pacer/src/rate-limiter/useRateLimitedCallback.ts:62](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts#L62) @@ -66,7 +66,7 @@ Consider using the `useRateLimiter` hook instead. `RateLimiterOptions`\<`TFn`\> -### selector? +### selector (`state`) => `TSelected` diff --git a/docs/framework/react/reference/functions/usethrottledcallback.md b/docs/framework/react/reference/functions/usethrottledcallback.md index 93b1ca88b..8a20e094d 100644 --- a/docs/framework/react/reference/functions/usethrottledcallback.md +++ b/docs/framework/react/reference/functions/usethrottledcallback.md @@ -11,7 +11,7 @@ title: useThrottledCallback function useThrottledCallback( fn, options, - selector?): (...args) => void + selector): (...args) => void ``` Defined in: [react-pacer/src/throttler/useThrottledCallback.ts:46](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottledCallback.ts#L46) @@ -52,7 +52,7 @@ Consider using the `useThrottler` hook instead. `ThrottlerOptions`\<`TFn`\> -### selector? +### selector (`state`) => `TSelected` diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md index 208197fe8..cf797a6d7 100644 --- a/docs/framework/react/reference/index.md +++ b/docs/framework/react/reference/index.md @@ -23,10 +23,13 @@ title: "@tanstack/react-pacer" ## Functions - [useAsyncBatcher](../functions/useasyncbatcher.md) +- [useAsyncDebouncedCallback](../functions/useasyncdebouncedcallback.md) - [useAsyncDebouncer](../functions/useasyncdebouncer.md) - [useAsyncQueuedState](../functions/useasyncqueuedstate.md) - [useAsyncQueuer](../functions/useasyncqueuer.md) +- [useAsyncRateLimitedCallback](../functions/useasyncratelimitedcallback.md) - [useAsyncRateLimiter](../functions/useasyncratelimiter.md) +- [useAsyncThrottledCallback](../functions/useasyncthrottledcallback.md) - [useAsyncThrottler](../functions/useasyncthrottler.md) - [useBatcher](../functions/usebatcher.md) - [useDebouncedCallback](../functions/usedebouncedcallback.md) diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts index 37f158b72..a60191e91 100644 --- a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts @@ -67,7 +67,11 @@ export function useAsyncRateLimitedCallback< selector: (state: AsyncRateLimiterState) => TSelected = () => ({}) as TSelected, ): (...args: Parameters) => Promise> { - const asyncRateLimitedFn = useAsyncRateLimiter(fn, options, selector).maybeExecute + const asyncRateLimitedFn = useAsyncRateLimiter( + fn, + options, + selector, + ).maybeExecute return useCallback( (...args) => asyncRateLimitedFn(...args) as Promise>, [asyncRateLimitedFn], From eb8e27391d11914e68b408da8e43c09508fc9965 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 10 Jul 2025 08:50:27 -0500 Subject: [PATCH 47/51] update docs config --- docs/config.json | 228 ++++++++++++++---- docs/overview.md | 9 +- .../useAsyncDebouncedCallback.ts | 2 +- .../useAsyncRateLimitedCallback.ts | 2 +- .../useAsyncThrottledCallback.ts | 2 +- .../src/debouncer/useDebouncedCallback.ts | 5 +- .../rate-limiter/useRateLimitedCallback.ts | 5 +- .../src/throttler/useThrottledCallback.ts | 5 +- 8 files changed, 186 insertions(+), 72 deletions(-) diff --git a/docs/config.json b/docs/config.json index c1d7192bc..480682f7b 100644 --- a/docs/config.json +++ b/docs/config.json @@ -123,17 +123,29 @@ "label": "Debouncer API Reference", "children": [ { - "label": "Debouncer Options", + "label": "DebouncerOptions", "to": "reference/interfaces/debounceroptions" }, { - "label": "AsyncDebouncer Options", + "label": "DebouncerState", + "to": "reference/interfaces/debouncerstate" + }, + { + "label": "AsyncDebouncerOptions", "to": "reference/interfaces/asyncdebounceroptions" }, + { + "label": "AsyncDebouncerState", + "to": "reference/interfaces/asyncdebouncerstate" + }, { "label": "debounce", "to": "reference/functions/debounce" }, + { + "label": "asyncDebounce", + "to": "reference/functions/asyncdebounce" + }, { "label": "Debouncer", "to": "reference/classes/debouncer" @@ -147,6 +159,14 @@ { "label": "react", "children": [ + { + "label": "ReactDebouncer", + "to": "framework/react/reference/interfaces/reactdebouncer" + }, + { + "label": "ReactAsyncDebouncer", + "to": "framework/react/reference/interfaces/reactasyncdebouncer" + }, { "label": "useDebouncer", "to": "framework/react/reference/functions/usedebouncer" @@ -166,6 +186,10 @@ { "label": "useAsyncDebouncer", "to": "framework/react/reference/functions/useasyncdebouncer" + }, + { + "label": "useAsyncDebouncedCallback", + "to": "framework/react/reference/functions/useasyncdebouncedcallback" } ] }, @@ -176,12 +200,16 @@ "label": "SolidDebouncer", "to": "framework/solid/reference/interfaces/soliddebouncer" }, + { + "label": "SolidAsyncDebouncer", + "to": "framework/solid/reference/interfaces/solidasyncdebouncer" + }, { "label": "createDebouncer", "to": "framework/solid/reference/functions/createdebouncer" }, { - "label": "createDebouncedState", + "label": "createDebouncedSignal", "to": "framework/solid/reference/functions/createdebouncedsignal" }, { @@ -202,17 +230,29 @@ "label": "Throttler API Reference", "children": [ { - "label": "Throttler Options", + "label": "ThrottlerOptions", "to": "reference/interfaces/throttleroptions" }, { - "label": "AsyncThrottler Options", + "label": "ThrottlerState", + "to": "reference/interfaces/throttlerstate" + }, + { + "label": "AsyncThrottlerOptions", "to": "reference/interfaces/asyncthrottleroptions" }, + { + "label": "AsyncThrottlerState", + "to": "reference/interfaces/asyncthrottlerstate" + }, { "label": "throttle", "to": "reference/functions/throttle" }, + { + "label": "asyncThrottle", + "to": "reference/functions/asyncthrottle" + }, { "label": "Throttler", "to": "reference/classes/throttler" @@ -226,6 +266,14 @@ { "label": "react", "children": [ + { + "label": "ReactThrottler", + "to": "framework/react/reference/interfaces/reactthrottler" + }, + { + "label": "ReactAsyncThrottler", + "to": "framework/react/reference/interfaces/reactasyncthrottler" + }, { "label": "useThrottler", "to": "framework/react/reference/functions/usethrottler" @@ -245,6 +293,10 @@ { "label": "useAsyncThrottler", "to": "framework/react/reference/functions/useasyncthrottler" + }, + { + "label": "useAsyncThrottledCallback", + "to": "framework/react/reference/functions/useasyncthrottledcallback" } ] }, @@ -255,6 +307,10 @@ "label": "SolidThrottler", "to": "framework/solid/reference/interfaces/solidthrottler" }, + { + "label": "SolidAsyncThrottler", + "to": "framework/solid/reference/interfaces/solidasyncthrottler" + }, { "label": "createThrottler", "to": "framework/solid/reference/functions/createthrottler" @@ -284,10 +340,18 @@ "label": "RateLimiterOptions", "to": "reference/interfaces/ratelimiteroptions" }, + { + "label": "RateLimiterState", + "to": "reference/interfaces/ratelimiterstate" + }, { "label": "AsyncRateLimiterOptions", "to": "reference/interfaces/asyncratelimiteroptions" }, + { + "label": "AsyncRateLimiterState", + "to": "reference/interfaces/asyncratelimiterstate" + }, { "label": "rateLimit", "to": "reference/functions/ratelimit" @@ -309,10 +373,22 @@ { "label": "react", "children": [ + { + "label": "ReactRateLimiter", + "to": "framework/react/reference/interfaces/reactratelimiter" + }, + { + "label": "ReactAsyncRateLimiter", + "to": "framework/react/reference/interfaces/reactasyncratelimiter" + }, { "label": "useRateLimiter", "to": "framework/react/reference/functions/useratelimiter" }, + { + "label": "useRateLimitedCallback", + "to": "framework/react/reference/functions/useratelimitedcallback" + }, { "label": "useRateLimitedState", "to": "framework/react/reference/functions/useratelimitedstate" @@ -324,6 +400,10 @@ { "label": "useAsyncRateLimiter", "to": "framework/react/reference/functions/useasyncratelimiter" + }, + { + "label": "useAsyncRateLimitedCallback", + "to": "framework/react/reference/functions/useasyncratelimitedcallback" } ] }, @@ -334,12 +414,16 @@ "label": "SolidRateLimiter", "to": "framework/solid/reference/interfaces/solidratelimiter" }, + { + "label": "SolidAsyncRateLimiter", + "to": "framework/solid/reference/interfaces/solidasyncratelimiter" + }, { "label": "createRateLimiter", "to": "framework/solid/reference/functions/createratelimiter" }, { - "label": "createRateLimitedState", + "label": "createRateLimitedSignal", "to": "framework/solid/reference/functions/createratelimitedsignal" }, { @@ -364,16 +448,16 @@ "to": "reference/interfaces/queueroptions" }, { - "label": "AsyncQueuerOptions", - "to": "reference/interfaces/asyncqueueroptions" + "label": "QueuerState", + "to": "reference/interfaces/queuerstate" }, { - "label": "Queuer", - "to": "reference/classes/queuer" + "label": "AsyncQueuerOptions", + "to": "reference/interfaces/asyncqueueroptions" }, { - "label": "AsyncQueuer", - "to": "reference/classes/asyncqueuer" + "label": "AsyncQueuerState", + "to": "reference/interfaces/asyncqueuerstate" }, { "label": "queue", @@ -382,23 +466,47 @@ { "label": "asyncQueue", "to": "reference/functions/asyncqueue" + }, + { + "label": "Queuer", + "to": "reference/classes/queuer" + }, + { + "label": "AsyncQueuer", + "to": "reference/classes/asyncqueuer" } ], "frameworks": [ { "label": "react", "children": [ + { + "label": "ReactQueuer", + "to": "framework/react/reference/interfaces/reactqueuer" + }, + { + "label": "ReactAsyncQueuer", + "to": "framework/react/reference/interfaces/reactasyncqueuer" + }, { "label": "useQueuer", "to": "framework/react/reference/functions/usequeuer" }, { "label": "useQueuedState", - "to": "framework/react/reference/functions/usequeuerstate" + "to": "framework/react/reference/functions/usequeuedstate" + }, + { + "label": "useQueuedValue", + "to": "framework/react/reference/functions/usequeuedvalue" }, { "label": "useAsyncQueuer", "to": "framework/react/reference/functions/useasyncqueuer" + }, + { + "label": "useAsyncQueuedState", + "to": "framework/react/reference/functions/useasyncqueuedstate" } ] }, @@ -409,6 +517,10 @@ "label": "SolidQueuer", "to": "framework/solid/reference/interfaces/solidqueuer" }, + { + "label": "SolidAsyncQueuer", + "to": "framework/solid/reference/interfaces/solidasyncqueuer" + }, { "label": "createQueuer", "to": "framework/solid/reference/functions/createqueuer" @@ -430,18 +542,54 @@ "label": "BatcherOptions", "to": "reference/interfaces/batcheroptions" }, + { + "label": "BatcherState", + "to": "reference/interfaces/batcherstate" + }, + { + "label": "AsyncBatcherOptions", + "to": "reference/interfaces/asyncbatcheroptions" + }, + { + "label": "AsyncBatcherState", + "to": "reference/interfaces/asyncbatcherstate" + }, + { + "label": "batch", + "to": "reference/functions/batch" + }, + { + "label": "asyncBatch", + "to": "reference/functions/asyncbatch" + }, { "label": "Batcher", "to": "reference/classes/batcher" + }, + { + "label": "AsyncBatcher", + "to": "reference/classes/asyncbatcher" } ], "frameworks": [ { "label": "react", "children": [ + { + "label": "ReactBatcher", + "to": "framework/react/reference/interfaces/reactbatcher" + }, + { + "label": "ReactAsyncBatcher", + "to": "framework/react/reference/interfaces/reactasyncbatcher" + }, { "label": "useBatcher", "to": "framework/react/reference/functions/usebatcher" + }, + { + "label": "useAsyncBatcher", + "to": "framework/react/reference/functions/useasyncbatcher" } ] }, @@ -453,50 +601,16 @@ "to": "framework/solid/reference/interfaces/solidbatcher" }, { - "label": "createBatcher", - "to": "framework/solid/reference/functions/createbatcher" - } - ] - } - ] - }, - { - "collapsible": true, - "defaultCollapsed": true, - "label": "Persister API Reference", - "children": [ - { - "label": "Persister", - "to": "reference/classes/persister" - }, - { - "label": "PersistedStorage", - "to": "reference/interfaces/persistedstorage" - }, - { - "label": "StoragePersisterOptions", - "to": "reference/interfaces/storagepersisteroptions" - }, - { - "label": "StoragePersister", - "to": "reference/classes/storagepersister" - } - ], - "frameworks": [ - { - "label": "react", - "children": [ - { - "label": "useStoragePersister", - "to": "framework/react/reference/functions/usestoragepersister" + "label": "SolidAsyncBatcher", + "to": "framework/solid/reference/interfaces/solidasyncbatcher" }, { - "label": "useLocalStorageState", - "to": "framework/react/reference/functions/uselocalstoragestate" + "label": "createBatcher", + "to": "framework/solid/reference/functions/createbatcher" }, { - "label": "useSessionStorageState", - "to": "framework/react/reference/functions/usesessionstoragestate" + "label": "createAsyncBatcher", + "to": "framework/solid/reference/functions/createasyncbatcher" } ] } @@ -772,6 +886,10 @@ { "label": "useBatcher", "to": "framework/react/examples/useBatcher" + }, + { + "label": "useAsyncBatcher", + "to": "framework/react/examples/useAsyncBatcher" } ] }, @@ -785,6 +903,10 @@ { "label": "createBatcher", "to": "framework/solid/examples/createBatcher" + }, + { + "label": "createAsyncBatcher", + "to": "framework/solid/examples/createAsyncBatcher" } ] } diff --git a/docs/overview.md b/docs/overview.md index 42ce8925c..aef9ea0e1 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -17,7 +17,7 @@ Many of the ideas (and code) for TanStack Pacer are not new. In fact, many of th ## Features > [!NOTE] -> TanStack Pacer is currently a client-side only library +> TanStack Pacer is currently mostly a client-side only library, but it is being designed to be able to potentially be used on the server-side as well. - **Debouncing** - Delay execution until after a period of inactivity for when you only care about the last execution in a sequence. @@ -43,9 +43,10 @@ Many of the ideas (and code) for TanStack Pacer are not new. In fact, many of th - **Async or Sync Variations** - Choose between synchronous and asynchronous versions of each utility - Optional error, success, and settled handling for async variations -- **Comparison Utilities** - - Perform deep equality checks between values - - Create custom comparison logic for specific needs +- **State Management** + - Uses TanStack Store under the hood for state management with fine-grained reactivity + - Easily integrate with your own state management library of choice + - Persist state to local or session storage for some utilities like rate limiting and queuing - **Convenient Hooks** - Reduce boilerplate code with pre-built hooks like `useDebouncedCallback`, `useThrottledValue`, and `useQueuedState`, and more. - Multiple layers of abstraction to choose from depending on your use case. diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts index 88bee2da7..b9d6690d2 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncedCallback.ts @@ -45,7 +45,7 @@ import type { AnyAsyncFunction } from '@tanstack/pacer/types' */ export function useAsyncDebouncedCallback< TFn extends AnyAsyncFunction, - TSelected = AsyncDebouncerState, + TSelected = {}, >( fn: TFn, options: AsyncDebouncerOptions, diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts index a60191e91..94947b05a 100644 --- a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimitedCallback.ts @@ -60,7 +60,7 @@ import type { */ export function useAsyncRateLimitedCallback< TFn extends AnyAsyncFunction, - TSelected = AsyncRateLimiterState, + TSelected = {}, >( fn: TFn, options: AsyncRateLimiterOptions, diff --git a/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts b/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts index 2cac8c541..3d7021f82 100644 --- a/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts +++ b/packages/react-pacer/src/async-throttler/useAsyncThrottledCallback.ts @@ -43,7 +43,7 @@ import type { AnyAsyncFunction } from '@tanstack/pacer/types' */ export function useAsyncThrottledCallback< TFn extends AnyAsyncFunction, - TSelected = AsyncThrottlerState, + TSelected = {}, >( fn: TFn, options: AsyncThrottlerOptions, diff --git a/packages/react-pacer/src/debouncer/useDebouncedCallback.ts b/packages/react-pacer/src/debouncer/useDebouncedCallback.ts index a7d4095d1..673d6ef86 100644 --- a/packages/react-pacer/src/debouncer/useDebouncedCallback.ts +++ b/packages/react-pacer/src/debouncer/useDebouncedCallback.ts @@ -42,10 +42,7 @@ import type { AnyFunction } from '@tanstack/pacer/types' * /> * ``` */ -export function useDebouncedCallback< - TFn extends AnyFunction, - TSelected = DebouncerState, ->( +export function useDebouncedCallback( fn: TFn, options: DebouncerOptions, selector: (state: DebouncerState) => TSelected = () => ({}) as TSelected, diff --git a/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts b/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts index d09b234be..63f9a99ba 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimitedCallback.ts @@ -59,10 +59,7 @@ import type { * ); * ``` */ -export function useRateLimitedCallback< - TFn extends AnyFunction, - TSelected = RateLimiterState, ->( +export function useRateLimitedCallback( fn: TFn, options: RateLimiterOptions, selector: (state: RateLimiterState) => TSelected = () => ({}) as TSelected, diff --git a/packages/react-pacer/src/throttler/useThrottledCallback.ts b/packages/react-pacer/src/throttler/useThrottledCallback.ts index 474bf2128..de6175612 100644 --- a/packages/react-pacer/src/throttler/useThrottledCallback.ts +++ b/packages/react-pacer/src/throttler/useThrottledCallback.ts @@ -43,10 +43,7 @@ import type { AnyFunction } from '@tanstack/pacer/types' * }, [handleResize]); * ``` */ -export function useThrottledCallback< - TFn extends AnyFunction, - TSelected = ThrottlerState, ->( +export function useThrottledCallback( fn: TFn, options: ThrottlerOptions, selector: (state: ThrottlerState) => TSelected = () => ({}) as TSelected, From 6c22f470e8504649d3055aae4e9d936c512e0302 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 10 Jul 2025 17:55:50 -0500 Subject: [PATCH 48/51] fix some edge cases and render bugs with flushing and batching --- .../functions/useasyncdebouncedcallback.md | 2 +- .../functions/useasyncratelimitedcallback.md | 2 +- .../functions/useasyncthrottledcallback.md | 2 +- .../functions/usedebouncedcallback.md | 2 +- .../functions/useratelimitedcallback.md | 2 +- .../functions/usethrottledcallback.md | 2 +- docs/reference/classes/asyncbatcher.md | 26 +++++++------- docs/reference/classes/asyncdebouncer.md | 8 ++--- docs/reference/classes/asyncthrottler.md | 8 ++--- docs/reference/classes/batcher.md | 24 ++++++------- docs/reference/classes/debouncer.md | 8 ++--- docs/reference/classes/throttler.md | 8 ++--- docs/reference/functions/asyncbatch.md | 2 +- docs/reference/functions/asyncdebounce.md | 2 +- docs/reference/functions/asyncqueue.md | 2 +- docs/reference/functions/asyncthrottle.md | 2 +- docs/reference/functions/batch.md | 2 +- docs/reference/functions/debounce.md | 2 +- docs/reference/functions/queue.md | 2 +- docs/reference/functions/throttle.md | 2 +- .../interfaces/asyncbatcheroptions.md | 26 +++++++------- .../reference/interfaces/asyncbatcherstate.md | 30 ++++++++-------- .../interfaces/asyncdebouncerstate.md | 2 +- .../interfaces/asyncthrottlerstate.md | 2 +- docs/reference/interfaces/batcheroptions.md | 18 +++++----- docs/reference/interfaces/batcherstate.md | 18 +++++----- docs/reference/interfaces/debouncerstate.md | 2 +- docs/reference/interfaces/throttlerstate.md | 2 +- examples/react/useAsyncBatcher/src/index.tsx | 16 ++++----- .../react/useAsyncDebouncer/src/index.tsx | 9 +++-- .../react/useAsyncQueuedState/src/index.tsx | 3 ++ examples/react/useAsyncQueuer/src/index.tsx | 22 +++++++----- .../react/useAsyncRateLimiter/src/index.tsx | 3 ++ .../react/useAsyncThrottler/src/index.tsx | 8 ++++- examples/react/useBatcher/src/index.tsx | 15 ++++---- .../react/useDebouncedState/src/index.tsx | 9 +++++ .../react/useDebouncedValue/src/index.tsx | 3 ++ examples/react/useDebouncer/src/index.tsx | 35 +++++++++++++++---- examples/react/useQueuedState/src/index.tsx | 6 ++++ examples/react/useQueuedValue/src/index.tsx | 6 ++++ examples/react/useQueuer/src/index.tsx | 17 +++++++-- .../react/useRateLimitedState/src/index.tsx | 9 +++++ .../react/useRateLimitedValue/src/index.tsx | 3 ++ examples/react/useRateLimiter/src/index.tsx | 9 +++++ .../react/useThrottledState/src/index.tsx | 9 +++++ .../react/useThrottledValue/src/index.tsx | 3 ++ examples/react/useThrottler/src/index.tsx | 21 +++++++++++ packages/pacer/src/async-batcher.ts | 19 +++++----- packages/pacer/src/async-debouncer.ts | 18 +++++----- packages/pacer/src/async-queuer.ts | 1 + packages/pacer/src/async-throttler.ts | 23 ++++++------ packages/pacer/src/batcher.ts | 21 ++++++----- packages/pacer/src/debouncer.ts | 11 +++--- packages/pacer/src/queuer.ts | 1 + packages/pacer/src/throttler.ts | 8 +++-- 55 files changed, 332 insertions(+), 186 deletions(-) diff --git a/docs/framework/react/reference/functions/useasyncdebouncedcallback.md b/docs/framework/react/reference/functions/useasyncdebouncedcallback.md index 1e1c764b5..4d088995a 100644 --- a/docs/framework/react/reference/functions/useasyncdebouncedcallback.md +++ b/docs/framework/react/reference/functions/useasyncdebouncedcallback.md @@ -39,7 +39,7 @@ Consider using the `useAsyncDebouncer` hook instead. • **TFn** *extends* `AnyAsyncFunction` -• **TSelected** = `AsyncDebouncerState`\<`TFn`\> +• **TSelected** = \{\} ## Parameters diff --git a/docs/framework/react/reference/functions/useasyncratelimitedcallback.md b/docs/framework/react/reference/functions/useasyncratelimitedcallback.md index be04007fa..bb44b1031 100644 --- a/docs/framework/react/reference/functions/useasyncratelimitedcallback.md +++ b/docs/framework/react/reference/functions/useasyncratelimitedcallback.md @@ -53,7 +53,7 @@ Consider using the `useAsyncRateLimiter` hook instead. • **TFn** *extends* `AnyAsyncFunction` -• **TSelected** = `AsyncRateLimiterState`\<`TFn`\> +• **TSelected** = \{\} ## Parameters diff --git a/docs/framework/react/reference/functions/useasyncthrottledcallback.md b/docs/framework/react/reference/functions/useasyncthrottledcallback.md index 6a87ba995..f3b908fb2 100644 --- a/docs/framework/react/reference/functions/useasyncthrottledcallback.md +++ b/docs/framework/react/reference/functions/useasyncthrottledcallback.md @@ -40,7 +40,7 @@ Consider using the `useAsyncThrottler` hook instead. • **TFn** *extends* `AnyAsyncFunction` -• **TSelected** = `AsyncThrottlerState`\<`TFn`\> +• **TSelected** = \{\} ## Parameters diff --git a/docs/framework/react/reference/functions/usedebouncedcallback.md b/docs/framework/react/reference/functions/usedebouncedcallback.md index ba66d0c6f..c87338365 100644 --- a/docs/framework/react/reference/functions/usedebouncedcallback.md +++ b/docs/framework/react/reference/functions/usedebouncedcallback.md @@ -39,7 +39,7 @@ Consider using the `useDebouncer` hook instead. • **TFn** *extends* `AnyFunction` -• **TSelected** = `DebouncerState`\<`TFn`\> +• **TSelected** = \{\} ## Parameters diff --git a/docs/framework/react/reference/functions/useratelimitedcallback.md b/docs/framework/react/reference/functions/useratelimitedcallback.md index 370f0485d..405669bce 100644 --- a/docs/framework/react/reference/functions/useratelimitedcallback.md +++ b/docs/framework/react/reference/functions/useratelimitedcallback.md @@ -54,7 +54,7 @@ Consider using the `useRateLimiter` hook instead. • **TFn** *extends* `AnyFunction` -• **TSelected** = `RateLimiterState` +• **TSelected** = \{\} ## Parameters diff --git a/docs/framework/react/reference/functions/usethrottledcallback.md b/docs/framework/react/reference/functions/usethrottledcallback.md index 8a20e094d..17871e5ca 100644 --- a/docs/framework/react/reference/functions/usethrottledcallback.md +++ b/docs/framework/react/reference/functions/usethrottledcallback.md @@ -40,7 +40,7 @@ Consider using the `useThrottler` hook instead. • **TFn** *extends* `AnyFunction` -• **TSelected** = `ThrottlerState`\<`TFn`\> +• **TSelected** = \{\} ## Parameters diff --git a/docs/reference/classes/asyncbatcher.md b/docs/reference/classes/asyncbatcher.md index 89d1fb7ae..0f6ef9d81 100644 --- a/docs/reference/classes/asyncbatcher.md +++ b/docs/reference/classes/asyncbatcher.md @@ -7,7 +7,7 @@ title: AsyncBatcher # Class: AsyncBatcher\ -Defined in: [async-batcher.ts:228](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L228) +Defined in: [async-batcher.ts:229](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L229) A class that collects items and processes them in batches asynchronously. @@ -82,7 +82,7 @@ batcher.addItem(2); new AsyncBatcher(fn, initialOptions): AsyncBatcher ``` -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:236](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L236) #### Parameters @@ -106,7 +106,7 @@ Defined in: [async-batcher.ts:235](https://github.com/TanStack/pacer/blob/main/p options: AsyncBatcherOptionsWithOptionalCallbacks; ``` -Defined in: [async-batcher.ts:232](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L232) +Defined in: [async-batcher.ts:233](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L233) *** @@ -116,7 +116,7 @@ Defined in: [async-batcher.ts:232](https://github.com/TanStack/pacer/blob/main/p readonly store: Store>>; ``` -Defined in: [async-batcher.ts:229](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L229) +Defined in: [async-batcher.ts:230](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L230) ## Methods @@ -126,7 +126,7 @@ Defined in: [async-batcher.ts:229](https://github.com/TanStack/pacer/blob/main/p addItem(item): void ``` -Defined in: [async-batcher.ts:282](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L282) +Defined in: [async-batcher.ts:287](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L287) Adds an item to the async batcher If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed @@ -149,7 +149,7 @@ If the batch size is reached, timeout occurs, or shouldProcess returns true, the clear(): 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:407](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L407) Removes all items from the async batcher @@ -165,7 +165,7 @@ Removes all items from the async batcher flush(): Promise ``` -Defined in: [async-batcher.ts:361](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L361) +Defined in: [async-batcher.ts:363](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L363) Processes the current batch of items immediately @@ -181,7 +181,7 @@ Processes the current batch of items immediately peekAllItems(): TValue[] ``` -Defined in: [async-batcher.ts:387](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L387) +Defined in: [async-batcher.ts:389](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L389) Returns a copy of all items in the async batcher @@ -197,7 +197,7 @@ Returns a copy of all items in the async batcher peekFailedItems(): TValue[] ``` -Defined in: [async-batcher.ts:391](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L391) +Defined in: [async-batcher.ts:393](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L393) #### Returns @@ -211,7 +211,7 @@ Defined in: [async-batcher.ts:391](https://github.com/TanStack/pacer/blob/main/p reset(): void ``` -Defined in: [async-batcher.ts:412](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L412) +Defined in: [async-batcher.ts:414](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L414) Resets the async batcher state to its default values @@ -227,7 +227,7 @@ Resets the async batcher state to its default values setOptions(newOptions): void ``` -Defined in: [async-batcher.ts:250](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L250) +Defined in: [async-batcher.ts:251](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L251) Updates the async batcher options @@ -249,7 +249,7 @@ Updates the async batcher options start(): void ``` -Defined in: [async-batcher.ts:377](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L377) +Defined in: [async-batcher.ts:379](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L379) Starts the async batcher and processes any pending items @@ -265,7 +265,7 @@ Starts the async batcher and processes any pending items stop(): void ``` -Defined in: [async-batcher.ts:369](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L369) +Defined in: [async-batcher.ts:371](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L371) Stops the async batcher from processing batches diff --git a/docs/reference/classes/asyncdebouncer.md b/docs/reference/classes/asyncdebouncer.md index 14a6f9c4c..7cf399806 100644 --- a/docs/reference/classes/asyncdebouncer.md +++ b/docs/reference/classes/asyncdebouncer.md @@ -110,7 +110,7 @@ Defined in: [async-debouncer.ts:170](https://github.com/TanStack/pacer/blob/main cancel(): void ``` -Defined in: [async-debouncer.ts:361](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L361) +Defined in: [async-debouncer.ts:363](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L363) Cancels any pending execution or aborts any execution in progress @@ -126,7 +126,7 @@ Cancels any pending execution or aborts any execution in progress flush(): void ``` -Defined in: [async-debouncer.ts:323](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L323) +Defined in: [async-debouncer.ts:325](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L325) Processes the current pending execution immediately @@ -142,7 +142,7 @@ Processes the current pending execution immediately maybeExecute(...args): Promise> ``` -Defined in: [async-debouncer.ts:252](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L252) +Defined in: [async-debouncer.ts:254](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L254) Attempts to execute the debounced function. If a call is already in progress, it will be queued. @@ -178,7 +178,7 @@ The error from the debounced function if no onError handler is configured reset(): void ``` -Defined in: [async-debouncer.ts:370](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L370) +Defined in: [async-debouncer.ts:372](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L372) Resets the debouncer state to its default values diff --git a/docs/reference/classes/asyncthrottler.md b/docs/reference/classes/asyncthrottler.md index 67295f3b0..a72ace0e9 100644 --- a/docs/reference/classes/asyncthrottler.md +++ b/docs/reference/classes/asyncthrottler.md @@ -113,7 +113,7 @@ Defined in: [async-throttler.ts:181](https://github.com/TanStack/pacer/blob/main cancel(): void ``` -Defined in: [async-throttler.ts:400](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L400) +Defined in: [async-throttler.ts:401](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L401) Cancels any pending execution or aborts any execution in progress @@ -129,7 +129,7 @@ Cancels any pending execution or aborts any execution in progress flush(): void ``` -Defined in: [async-throttler.ts:355](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L355) +Defined in: [async-throttler.ts:356](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L356) Processes the current pending execution immediately @@ -145,7 +145,7 @@ Processes the current pending execution immediately maybeExecute(...args): Promise> ``` -Defined in: [async-throttler.ts:271](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L271) +Defined in: [async-throttler.ts:273](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L273) Attempts to execute the throttled function. The execution behavior depends on the throttler options: @@ -187,7 +187,7 @@ await throttled.maybeExecute('c', 'd'); reset(): void ``` -Defined in: [async-throttler.ts:408](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L408) +Defined in: [async-throttler.ts:409](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L409) Resets the debouncer state to its default values diff --git a/docs/reference/classes/batcher.md b/docs/reference/classes/batcher.md index f62d9b1bc..edbf23e0e 100644 --- a/docs/reference/classes/batcher.md +++ b/docs/reference/classes/batcher.md @@ -7,7 +7,7 @@ title: Batcher # Class: Batcher\ -Defined in: [batcher.ts:142](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L142) +Defined in: [batcher.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L143) A class that collects items and processes them in batches. @@ -59,7 +59,7 @@ batcher.addItem(2); new Batcher(fn, initialOptions): Batcher ``` -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) #### Parameters @@ -83,7 +83,7 @@ Defined in: [batcher.ts:149](https://github.com/TanStack/pacer/blob/main/package options: BatcherOptionsWithOptionalCallbacks; ``` -Defined in: [batcher.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L146) +Defined in: [batcher.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L147) *** @@ -93,7 +93,7 @@ Defined in: [batcher.ts:146](https://github.com/TanStack/pacer/blob/main/package readonly store: Store>>; ``` -Defined in: [batcher.ts:143](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L143) +Defined in: [batcher.ts:144](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L144) ## Methods @@ -103,7 +103,7 @@ Defined in: [batcher.ts:143](https://github.com/TanStack/pacer/blob/main/package addItem(item): void ``` -Defined in: [batcher.ts:189](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L189) +Defined in: [batcher.ts:194](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L194) Adds an item to the batcher If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed @@ -126,7 +126,7 @@ If the batch size is reached, timeout occurs, or shouldProcess returns true, the clear(): void ``` -Defined in: [batcher.ts:280](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L280) +Defined in: [batcher.ts:282](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L282) Removes all items from the batcher @@ -142,7 +142,7 @@ Removes all items from the batcher flush(): void ``` -Defined in: [batcher.ts:240](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L240) +Defined in: [batcher.ts:242](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L242) Processes the current batch of items immediately @@ -158,7 +158,7 @@ Processes the current batch of items immediately peekAllItems(): TValue[] ``` -Defined in: [batcher.ts:266](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L266) +Defined in: [batcher.ts:268](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L268) Returns a copy of all items in the batcher @@ -174,7 +174,7 @@ Returns a copy of all items in the batcher reset(): void ``` -Defined in: [batcher.ts:287](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L287) +Defined in: [batcher.ts:289](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L289) Resets the batcher state to its default values @@ -190,7 +190,7 @@ Resets the batcher state to its default values setOptions(newOptions): void ``` -Defined in: [batcher.ts:163](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L163) +Defined in: [batcher.ts:164](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L164) Updates the batcher options @@ -212,7 +212,7 @@ Updates the batcher options start(): void ``` -Defined in: [batcher.ts:256](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L256) +Defined in: [batcher.ts:258](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L258) Starts the batcher and processes any pending items @@ -228,7 +228,7 @@ Starts the batcher and processes any pending items stop(): void ``` -Defined in: [batcher.ts:248](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L248) +Defined in: [batcher.ts:250](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L250) Stops the batcher from processing batches diff --git a/docs/reference/classes/debouncer.md b/docs/reference/classes/debouncer.md index 4c6c44919..ba5eb24a6 100644 --- a/docs/reference/classes/debouncer.md +++ b/docs/reference/classes/debouncer.md @@ -96,7 +96,7 @@ Defined in: [debouncer.ts:119](https://github.com/TanStack/pacer/blob/main/packa cancel(): void ``` -Defined in: [debouncer.ts:239](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L239) +Defined in: [debouncer.ts:242](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L242) Cancels any pending execution @@ -112,7 +112,7 @@ Cancels any pending execution flush(): void ``` -Defined in: [debouncer.ts:222](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L222) +Defined in: [debouncer.ts:225](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L225) Processes the current pending execution immediately @@ -128,7 +128,7 @@ Processes the current pending execution immediately maybeExecute(...args): void ``` -Defined in: [debouncer.ts:180](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L180) +Defined in: [debouncer.ts:184](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L184) Attempts to execute the debounced function If a call is already in progress, it will be queued @@ -151,7 +151,7 @@ If a call is already in progress, it will be queued reset(): void ``` -Defined in: [debouncer.ts:250](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L250) +Defined in: [debouncer.ts:253](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L253) Resets the debouncer state to its default values diff --git a/docs/reference/classes/throttler.md b/docs/reference/classes/throttler.md index 62b6d6dc6..9c9b63873 100644 --- a/docs/reference/classes/throttler.md +++ b/docs/reference/classes/throttler.md @@ -100,7 +100,7 @@ Defined in: [throttler.ts:127](https://github.com/TanStack/pacer/blob/main/packa cancel(): void ``` -Defined in: [throttler.ts:272](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L272) +Defined in: [throttler.ts:276](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L276) Cancels any pending trailing execution and clears internal state. @@ -122,7 +122,7 @@ Has no effect if there is no pending execution. flush(): void ``` -Defined in: [throttler.ts:250](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L250) +Defined in: [throttler.ts:254](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L254) Processes the current pending execution immediately @@ -138,7 +138,7 @@ Processes the current pending execution immediately maybeExecute(...args): void ``` -Defined in: [throttler.ts:200](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L200) +Defined in: [throttler.ts:204](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L204) Attempts to execute the throttled function. The execution behavior depends on the throttler options: @@ -180,7 +180,7 @@ throttled.maybeExecute('c', 'd'); reset(): void ``` -Defined in: [throttler.ts:283](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L283) +Defined in: [throttler.ts:287](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L287) Resets the throttler state to its default values diff --git a/docs/reference/functions/asyncbatch.md b/docs/reference/functions/asyncbatch.md index 8016239b5..e39ca42be 100644 --- a/docs/reference/functions/asyncbatch.md +++ b/docs/reference/functions/asyncbatch.md @@ -11,7 +11,7 @@ title: asyncBatch function asyncBatch(fn, options): (item) => void ``` -Defined in: [async-batcher.ts:466](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L466) +Defined in: [async-batcher.ts:469](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L469) Creates an async batcher that processes items in batches diff --git a/docs/reference/functions/asyncdebounce.md b/docs/reference/functions/asyncdebounce.md index 0e987a070..72e5b0d8f 100644 --- a/docs/reference/functions/asyncdebounce.md +++ b/docs/reference/functions/asyncdebounce.md @@ -11,7 +11,7 @@ title: asyncDebounce function asyncDebounce(fn, initialOptions): (...args) => Promise> ``` -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:421](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L421) 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. diff --git a/docs/reference/functions/asyncqueue.md b/docs/reference/functions/asyncqueue.md index e392c54e9..d4499c271 100644 --- a/docs/reference/functions/asyncqueue.md +++ b/docs/reference/functions/asyncqueue.md @@ -11,7 +11,7 @@ title: asyncQueue function asyncQueue(fn, initialOptions): (item, position, runOnItemsChange) => boolean ``` -Defined in: [async-queuer.ts:732](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L732) +Defined in: [async-queuer.ts:733](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-queuer.ts#L733) Creates a new AsyncQueuer instance and returns a bound addItem function for adding tasks. The queuer is started automatically and ready to process items. diff --git a/docs/reference/functions/asyncthrottle.md b/docs/reference/functions/asyncthrottle.md index 78387bbc6..f43059681 100644 --- a/docs/reference/functions/asyncthrottle.md +++ b/docs/reference/functions/asyncthrottle.md @@ -11,7 +11,7 @@ title: asyncThrottle function asyncThrottle(fn, initialOptions): (...args) => Promise> ``` -Defined in: [async-throttler.ts:456](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L456) +Defined in: [async-throttler.ts:457](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L457) 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. diff --git a/docs/reference/functions/batch.md b/docs/reference/functions/batch.md index 9d3286eb1..d10aa9c94 100644 --- a/docs/reference/functions/batch.md +++ b/docs/reference/functions/batch.md @@ -11,7 +11,7 @@ title: batch function batch(fn, options): (item) => void ``` -Defined in: [batcher.ts:310](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L310) +Defined in: [batcher.ts:313](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L313) Creates a batcher that processes items in batches diff --git a/docs/reference/functions/debounce.md b/docs/reference/functions/debounce.md index c19563fbc..ecb39132d 100644 --- a/docs/reference/functions/debounce.md +++ b/docs/reference/functions/debounce.md @@ -11,7 +11,7 @@ title: debounce function debounce(fn, initialOptions): (...args) => void ``` -Defined in: [debouncer.ts:283](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L283) +Defined in: [debouncer.ts:286](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L286) 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. diff --git a/docs/reference/functions/queue.md b/docs/reference/functions/queue.md index 1a075857f..937f9bce7 100644 --- a/docs/reference/functions/queue.md +++ b/docs/reference/functions/queue.md @@ -11,7 +11,7 @@ title: queue function queue(fn, initialOptions): (item, position, runOnItemsChange) => boolean ``` -Defined in: [queuer.ts:670](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L670) +Defined in: [queuer.ts:671](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/queuer.ts#L671) Creates a queue that processes items immediately upon addition. Items are processed sequentially in FIFO order by default. diff --git a/docs/reference/functions/throttle.md b/docs/reference/functions/throttle.md index 34e7540d1..cf75eaf7c 100644 --- a/docs/reference/functions/throttle.md +++ b/docs/reference/functions/throttle.md @@ -11,7 +11,7 @@ title: throttle function throttle(fn, initialOptions): (...args) => void ``` -Defined in: [throttler.ts:322](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L322) +Defined in: [throttler.ts:326](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L326) Creates a throttled function that limits how often the provided function can execute. diff --git a/docs/reference/interfaces/asyncbatcheroptions.md b/docs/reference/interfaces/asyncbatcheroptions.md index c084f1697..3ead4e04b 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:85](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L85) +Defined in: [async-batcher.ts:86](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L86) Options for configuring an AsyncBatcher instance @@ -23,7 +23,7 @@ Options for configuring an AsyncBatcher instance optional getShouldExecute: (items, batcher) => boolean; ``` -Defined in: [async-batcher.ts:90](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L90) +Defined in: [async-batcher.ts:91](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L91) Custom function to determine if a batch should be processed Return true to process the batch immediately @@ -50,7 +50,7 @@ Return true to process the batch immediately optional initialState: Partial>; ``` -Defined in: [async-batcher.ts:97](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L97) +Defined in: [async-batcher.ts:98](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L98) Initial state for the async batcher @@ -62,7 +62,7 @@ Initial state for the async batcher optional maxSize: number; ``` -Defined in: [async-batcher.ts:102](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L102) +Defined in: [async-batcher.ts:103](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L103) Maximum number of items in a batch @@ -80,7 +80,7 @@ Infinity optional onError: (error, failedItems, batcher) => void; ``` -Defined in: [async-batcher.ts:108](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L108) +Defined in: [async-batcher.ts:109](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L109) Optional error handler for when the batch function throws. If provided, the handler will be called with the error and batcher instance. @@ -112,7 +112,7 @@ This can be used alongside throwOnError - the handler will be called before any optional onExecute: (batcher) => void; ``` -Defined in: [async-batcher.ts:116](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L116) +Defined in: [async-batcher.ts:117](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L117) Callback fired after a batch is processed @@ -134,7 +134,7 @@ Callback fired after a batch is processed optional onItemsChange: (batcher) => void; ``` -Defined in: [async-batcher.ts:120](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L120) +Defined in: [async-batcher.ts:121](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L121) Callback fired after items are added to the batcher @@ -156,7 +156,7 @@ Callback fired after items are added to the batcher optional onSettled: (batcher) => void; ``` -Defined in: [async-batcher.ts:124](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L124) +Defined in: [async-batcher.ts:125](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L125) Optional callback to call when a batch is settled (completed or failed) @@ -178,7 +178,7 @@ Optional callback to call when a batch is settled (completed or failed) optional onSuccess: (result, batcher) => void; ``` -Defined in: [async-batcher.ts:128](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L128) +Defined in: [async-batcher.ts:129](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L129) Optional callback to call when a batch succeeds @@ -204,7 +204,7 @@ Optional callback to call when a batch succeeds optional started: boolean; ``` -Defined in: [async-batcher.ts:133](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L133) +Defined in: [async-batcher.ts:134](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L134) Whether the batcher should start processing immediately @@ -222,7 +222,7 @@ true optional throwOnError: boolean; ``` -Defined in: [async-batcher.ts:139](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L139) +Defined in: [async-batcher.ts:140](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L140) Whether to throw errors when they occur. Defaults to true if no onError handler is provided, false if an onError handler is provided. @@ -233,10 +233,10 @@ Can be explicitly set to override these defaults. ### wait? ```ts -optional wait: number; +optional wait: number | (asyncBatcher) => number; ``` -Defined in: [async-batcher.ts:146](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L146) +Defined in: [async-batcher.ts:147](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L147) 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 b3fe10888..cd81a0452 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:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L4) +Defined in: [async-batcher.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L5) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [async-batcher.ts:4](https://github.com/TanStack/pacer/blob/main/pac errorCount: number; ``` -Defined in: [async-batcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L8) +Defined in: [async-batcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L9) Number of batch executions that have resulted in errors @@ -33,7 +33,7 @@ Number of batch executions that have resulted in errors failedItems: TValue[]; ``` -Defined in: [async-batcher.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L12) +Defined in: [async-batcher.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L13) Array of items that failed during batch processing @@ -45,7 +45,7 @@ Array of items that failed during batch processing isEmpty: boolean; ``` -Defined in: [async-batcher.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L16) +Defined in: [async-batcher.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L17) Whether the batcher has no items to process (items array is empty) @@ -57,7 +57,7 @@ Whether the batcher has no items to process (items array is empty) isExecuting: boolean; ``` -Defined in: [async-batcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L20) +Defined in: [async-batcher.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L21) Whether a batch is currently being processed asynchronously @@ -69,7 +69,7 @@ Whether a batch is currently being processed asynchronously isPending: boolean; ``` -Defined in: [async-batcher.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L24) +Defined in: [async-batcher.ts:25](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L25) Whether the batcher is waiting for the timeout to trigger batch processing @@ -81,7 +81,7 @@ Whether the batcher is waiting for the timeout to trigger batch processing isRunning: boolean; ``` -Defined in: [async-batcher.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L28) +Defined in: [async-batcher.ts:29](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L29) Whether the batcher is active and will process items automatically @@ -93,7 +93,7 @@ Whether the batcher is active and will process items automatically items: TValue[]; ``` -Defined in: [async-batcher.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L32) +Defined in: [async-batcher.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L33) Array of items currently queued for batch processing @@ -105,7 +105,7 @@ Array of items currently queued for batch processing lastResult: any; ``` -Defined in: [async-batcher.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L36) +Defined in: [async-batcher.ts:37](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L37) The result from the most recent batch execution @@ -117,7 +117,7 @@ The result from the most recent batch execution settleCount: number; ``` -Defined in: [async-batcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L40) +Defined in: [async-batcher.ts:41](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L41) Number of batch executions that have completed (either successfully or with errors) @@ -129,7 +129,7 @@ Number of batch executions that have completed (either successfully or with erro size: number; ``` -Defined in: [async-batcher.ts:44](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L44) +Defined in: [async-batcher.ts:45](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L45) Number of items currently in the batch queue @@ -141,7 +141,7 @@ Number of items currently in the batch queue status: "idle" | "pending" | "executing" | "populated"; ``` -Defined in: [async-batcher.ts:48](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L48) +Defined in: [async-batcher.ts:49](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L49) 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 @@ -153,7 +153,7 @@ Current processing status - 'idle' when not processing, 'pending' when waiting f successCount: number; ``` -Defined in: [async-batcher.ts:52](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L52) +Defined in: [async-batcher.ts:53](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L53) Number of batch executions that have completed successfully @@ -165,7 +165,7 @@ Number of batch executions that have completed successfully totalItemsFailed: number; ``` -Defined in: [async-batcher.ts:60](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L60) +Defined in: [async-batcher.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L61) Total number of items that have failed processing across all batches @@ -177,6 +177,6 @@ Total number of items that have failed processing across all batches totalItemsProcessed: number; ``` -Defined in: [async-batcher.ts:56](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L56) +Defined in: [async-batcher.ts:57](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-batcher.ts#L57) Total number of items that have been processed across all batches diff --git a/docs/reference/interfaces/asyncdebouncerstate.md b/docs/reference/interfaces/asyncdebouncerstate.md index 96a8f6525..a106a2ff4 100644 --- a/docs/reference/interfaces/asyncdebouncerstate.md +++ b/docs/reference/interfaces/asyncdebouncerstate.md @@ -102,7 +102,7 @@ Number of function executions that have completed (either successfully or with e ### status ```ts -status: "idle" | "pending" | "executing" | "settled"; +status: "disabled" | "idle" | "pending" | "executing" | "settled"; ``` Defined in: [async-debouncer.ts:37](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-debouncer.ts#L37) diff --git a/docs/reference/interfaces/asyncthrottlerstate.md b/docs/reference/interfaces/asyncthrottlerstate.md index 2ecf8534a..abb70ea27 100644 --- a/docs/reference/interfaces/asyncthrottlerstate.md +++ b/docs/reference/interfaces/asyncthrottlerstate.md @@ -114,7 +114,7 @@ Number of function executions that have completed (either successfully or with e ### status ```ts -status: "idle" | "pending" | "executing" | "settled"; +status: "disabled" | "idle" | "pending" | "executing" | "settled"; ``` Defined in: [async-throttler.ts:41](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/async-throttler.ts#L41) diff --git a/docs/reference/interfaces/batcheroptions.md b/docs/reference/interfaces/batcheroptions.md index 7043cb2a4..1c75f0319 100644 --- a/docs/reference/interfaces/batcheroptions.md +++ b/docs/reference/interfaces/batcheroptions.md @@ -7,7 +7,7 @@ title: BatcherOptions # Interface: BatcherOptions\ -Defined in: [batcher.ts:55](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L55) +Defined in: [batcher.ts:56](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L56) Options for configuring a Batcher instance @@ -23,7 +23,7 @@ Options for configuring a Batcher instance optional getShouldExecute: (items, batcher) => boolean; ``` -Defined in: [batcher.ts:60](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L60) +Defined in: [batcher.ts:61](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L61) Custom function to determine if a batch should be processed Return true to process the batch immediately @@ -50,7 +50,7 @@ Return true to process the batch immediately optional initialState: Partial>; ``` -Defined in: [batcher.ts:64](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L64) +Defined in: [batcher.ts:65](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L65) Initial state for the batcher @@ -62,7 +62,7 @@ Initial state for the batcher optional maxSize: number; ``` -Defined in: [batcher.ts:69](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L69) +Defined in: [batcher.ts:70](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L70) Maximum number of items in a batch @@ -80,7 +80,7 @@ Infinity optional onExecute: (batcher) => void; ``` -Defined in: [batcher.ts:73](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L73) +Defined in: [batcher.ts:74](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L74) Callback fired after a batch is processed @@ -102,7 +102,7 @@ Callback fired after a batch is processed optional onItemsChange: (batcher) => void; ``` -Defined in: [batcher.ts:77](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L77) +Defined in: [batcher.ts:78](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L78) Callback fired after items are added to the batcher @@ -124,7 +124,7 @@ Callback fired after items are added to the batcher optional started: boolean; ``` -Defined in: [batcher.ts:82](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L82) +Defined in: [batcher.ts:83](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L83) Whether the batcher should start processing immediately @@ -139,10 +139,10 @@ true ### wait? ```ts -optional wait: number; +optional wait: number | (batcher) => number; ``` -Defined in: [batcher.ts:89](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L89) +Defined in: [batcher.ts:90](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L90) 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/batcherstate.md b/docs/reference/interfaces/batcherstate.md index 5130e1ca8..841ccdffc 100644 --- a/docs/reference/interfaces/batcherstate.md +++ b/docs/reference/interfaces/batcherstate.md @@ -7,7 +7,7 @@ title: BatcherState # Interface: BatcherState\ -Defined in: [batcher.ts:4](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L4) +Defined in: [batcher.ts:5](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L5) ## Type Parameters @@ -21,7 +21,7 @@ Defined in: [batcher.ts:4](https://github.com/TanStack/pacer/blob/main/packages/ executionCount: number; ``` -Defined in: [batcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L8) +Defined in: [batcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L9) Number of batch executions that have been completed @@ -33,7 +33,7 @@ Number of batch executions that have been completed isEmpty: boolean; ``` -Defined in: [batcher.ts:12](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L12) +Defined in: [batcher.ts:13](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L13) Whether the batcher has no items to process (items array is empty) @@ -45,7 +45,7 @@ Whether the batcher has no items to process (items array is empty) isPending: boolean; ``` -Defined in: [batcher.ts:16](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L16) +Defined in: [batcher.ts:17](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L17) Whether the batcher is waiting for the timeout to trigger batch processing @@ -57,7 +57,7 @@ Whether the batcher is waiting for the timeout to trigger batch processing isRunning: boolean; ``` -Defined in: [batcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L20) +Defined in: [batcher.ts:21](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L21) Whether the batcher is active and will process items automatically @@ -69,7 +69,7 @@ Whether the batcher is active and will process items automatically items: TValue[]; ``` -Defined in: [batcher.ts:28](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L28) +Defined in: [batcher.ts:29](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L29) Array of items currently queued for batch processing @@ -81,7 +81,7 @@ Array of items currently queued for batch processing size: number; ``` -Defined in: [batcher.ts:32](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L32) +Defined in: [batcher.ts:33](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L33) Number of items currently in the batch queue @@ -93,7 +93,7 @@ Number of items currently in the batch queue status: "idle" | "pending"; ``` -Defined in: [batcher.ts:36](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L36) +Defined in: [batcher.ts:37](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L37) Current processing status - 'idle' when not processing, 'pending' when waiting for timeout @@ -105,6 +105,6 @@ Current processing status - 'idle' when not processing, 'pending' when waiting f totalItemsProcessed: number; ``` -Defined in: [batcher.ts:24](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L24) +Defined in: [batcher.ts:25](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/batcher.ts#L25) Total number of items that have been processed across all batches diff --git a/docs/reference/interfaces/debouncerstate.md b/docs/reference/interfaces/debouncerstate.md index 1c191f347..728ee596b 100644 --- a/docs/reference/interfaces/debouncerstate.md +++ b/docs/reference/interfaces/debouncerstate.md @@ -66,7 +66,7 @@ The arguments from the most recent call to maybeExecute ### status ```ts -status: "idle" | "pending"; +status: "disabled" | "idle" | "pending"; ``` Defined in: [debouncer.ts:25](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/debouncer.ts#L25) diff --git a/docs/reference/interfaces/throttlerstate.md b/docs/reference/interfaces/throttlerstate.md index 75069f728..4f1488cd7 100644 --- a/docs/reference/interfaces/throttlerstate.md +++ b/docs/reference/interfaces/throttlerstate.md @@ -78,7 +78,7 @@ Timestamp when the next execution can occur in milliseconds ### status ```ts -status: "idle" | "pending"; +status: "disabled" | "idle" | "pending"; ``` Defined in: [throttler.ts:29](https://github.com/TanStack/pacer/blob/main/packages/pacer/src/throttler.ts#L29) diff --git a/examples/react/useAsyncBatcher/src/index.tsx b/examples/react/useAsyncBatcher/src/index.tsx index a2b2c519b..ff45ceff7 100644 --- a/examples/react/useAsyncBatcher/src/index.tsx +++ b/examples/react/useAsyncBatcher/src/index.tsx @@ -11,8 +11,6 @@ type Item = { } function App() { - // Use your state management library of choice - const [batchItems, setBatchItems] = useState>([]) const [processedBatches, setProcessedBatches] = useState< Array<{ items: Array; result: string; timestamp: number }> >([]) @@ -43,14 +41,11 @@ function App() { } const asyncBatcher = useAsyncBatcher(processBatch, { - maxSize: 5, // Process in batches of 3 (if reached before wait time) - wait: 2000, // Wait up to 2 seconds before processing a batch + maxSize: 5, // Process in batches of 5 (if reached before wait time) + wait: 4000, // Wait up to 4 seconds before processing a batch getShouldExecute: (items) => items.some((item) => item.value.includes('urgent')), // Process immediately if any item is marked urgent throwOnError: false, // Don't throw errors, handle them via onError - onItemsChange: (batcher) => { - setBatchItems(batcher.peekAllItems()) - }, onSuccess: (result, batcher) => { console.log('Batch succeeded:', result) console.log('Total successful batches:', batcher.store.state.successCount) @@ -110,10 +105,10 @@ function App() {

    Current Batch Items

    - {batchItems.length === 0 ? ( + {asyncBatcher.peekAllItems().length === 0 ? ( No items in current batch ) : ( - batchItems.map((item, index) => ( + asyncBatcher.peekAllItems().map((item, index) => (
    {index + 1}: {item.value} (added at{' '} {new Date(item.timestamp).toLocaleTimeString()}) @@ -219,6 +214,9 @@ function App() {
  • Errors are handled gracefully and don't stop the batcher
  • +
    +        {JSON.stringify(asyncBatcher.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useAsyncDebouncer/src/index.tsx b/examples/react/useAsyncDebouncer/src/index.tsx index 66e241aa3..8dffab9f1 100644 --- a/examples/react/useAsyncDebouncer/src/index.tsx +++ b/examples/react/useAsyncDebouncer/src/index.tsx @@ -80,6 +80,9 @@ function App() { autoComplete="new-password" />
    +
    + +

    API calls made: {asyncDebouncer.state.successCount}

    {results.length > 0 && ( @@ -91,7 +94,9 @@ function App() { )} {asyncDebouncer.state.isPending &&

    Pending...

    } {asyncDebouncer.state.isExecuting &&

    Executing...

    } -
    {JSON.stringify({ state: asyncDebouncer.state }, null, 2)}
    +
    +          {JSON.stringify(asyncDebouncer.state, null, 2)}
    +        
    ) @@ -104,7 +109,7 @@ root.render() // demo unmounting and cancellation document.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { + if (e.shiftKey && e.key === 'Enter') { mounted = !mounted root.render(mounted ? : null) } diff --git a/examples/react/useAsyncQueuedState/src/index.tsx b/examples/react/useAsyncQueuedState/src/index.tsx index 157c6426f..8ab7a937f 100644 --- a/examples/react/useAsyncQueuedState/src/index.tsx +++ b/examples/react/useAsyncQueuedState/src/index.tsx @@ -120,6 +120,9 @@ function App() { Stop Processing +
    +        {JSON.stringify(asyncQueuer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useAsyncQueuer/src/index.tsx b/examples/react/useAsyncQueuer/src/index.tsx index 0d5642488..472cd4686 100644 --- a/examples/react/useAsyncQueuer/src/index.tsx +++ b/examples/react/useAsyncQueuer/src/index.tsx @@ -7,8 +7,6 @@ const fakeWaitTime = 500 type Item = number function App() { - // Use your state management library of choice - const [queueItems, setQueueItems] = useState>([]) const [concurrency, setConcurrency] = useState(2) // The function to process each item (now a number) @@ -25,9 +23,6 @@ function App() { concurrency, // Process 2 items concurrently started: false, wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency - onItemsChange: (asyncQueuer) => { - setQueueItems(asyncQueuer.peekAllItems()) - }, onReject: (item, asyncQueuer) => { console.log( 'Queue is full, rejecting item', @@ -75,7 +70,7 @@ function App() {
    Queue Items: - {queueItems.map((item, index) => ( + {asyncQueuer.peekAllItems().map((item, index) => (
    {index}: {item}
    @@ -92,8 +87,8 @@ function App() { > -
    + +
    +
    +        {JSON.stringify(asyncQueuer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index b9ab5b093..95de48539 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -140,6 +140,9 @@ function App() {
    Enabled:{setCountDebouncer.getEnabled().toString()}
    Is Pending:{setCountDebouncer.getState().isPending.toString()}{setCountDebouncer.state.isPending.toString()}
    Execution Count:{setCountDebouncer.getExecutionCount()}{setCountDebouncer.state.executionCount}
    @@ -93,17 +89,13 @@ function App2() { - - - - - + - + - + - + - + - + @@ -235,7 +259,7 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - queuer.executionCount()) / + ((instantExecutionCount() - queuer.state().executionCount) / instantExecutionCount()) * 100, )} @@ -244,7 +268,7 @@ function App3() { - +
    Enabled:{setSearchDebouncer.getOptions().enabled.toString()}
    Is Pending:{setSearchDebouncer.getState().isPending.toString()}{setSearchDebouncer.state.isPending.toString()}
    Execution Count:{setSearchDebouncer.getExecutionCount()}{setSearchDebouncer.state.executionCount}
    @@ -174,13 +166,9 @@ function App3() { - - - - - + @@ -188,12 +176,12 @@ function App3() { - + @@ -203,7 +191,7 @@ function App3() { ? '0' : Math.round( ((instantExecutionCount - - setValueDebouncer.getExecutionCount()) / + setValueDebouncer.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/react/useQueuedState/package.json b/examples/react/useQueuedState/package.json index 2cf89b277..13b16c1c3 100644 --- a/examples/react/useQueuedState/package.json +++ b/examples/react/useQueuedState/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuedValue/package.json b/examples/react/useQueuedValue/package.json index 9a87422ba..6959f217b 100644 --- a/examples/react/useQueuedValue/package.json +++ b/examples/react/useQueuedValue/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuer/package.json b/examples/react/useQueuer/package.json index 09b3f04e0..68088fb41 100644 --- a/examples/react/useQueuer/package.json +++ b/examples/react/useQueuer/package.json @@ -15,10 +15,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedCallback/package.json b/examples/react/useRateLimitedCallback/package.json index 0f36ede69..2db553c3d 100644 --- a/examples/react/useRateLimitedCallback/package.json +++ b/examples/react/useRateLimitedCallback/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedState/package.json b/examples/react/useRateLimitedState/package.json index bb280c916..8aaaa97b1 100644 --- a/examples/react/useRateLimitedState/package.json +++ b/examples/react/useRateLimitedState/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedValue/package.json b/examples/react/useRateLimitedValue/package.json index 2b69a638a..15230aabd 100644 --- a/examples/react/useRateLimitedValue/package.json +++ b/examples/react/useRateLimitedValue/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimiter/package.json b/examples/react/useRateLimiter/package.json index d71f914b5..c613142cf 100644 --- a/examples/react/useRateLimiter/package.json +++ b/examples/react/useRateLimiter/package.json @@ -15,10 +15,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledCallback/package.json b/examples/react/useThrottledCallback/package.json index 8cfbcdae4..88f97be33 100644 --- a/examples/react/useThrottledCallback/package.json +++ b/examples/react/useThrottledCallback/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledState/package.json b/examples/react/useThrottledState/package.json index f811b8eb7..8c7cb510b 100644 --- a/examples/react/useThrottledState/package.json +++ b/examples/react/useThrottledState/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledValue/package.json b/examples/react/useThrottledValue/package.json index 8156fe918..c10a3a8d3 100644 --- a/examples/react/useThrottledValue/package.json +++ b/examples/react/useThrottledValue/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottler/package.json b/examples/react/useThrottler/package.json index 715222793..7f018bfae 100644 --- a/examples/react/useThrottler/package.json +++ b/examples/react/useThrottler/package.json @@ -14,10 +14,10 @@ "react-dom": "^19.1.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", - "vite": "^6.3.5" + "@vitejs/plugin-react": "^4.6.0", + "vite": "^7.0.1" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncDebounce/package.json b/examples/solid/asyncDebounce/package.json index 5d4d0c198..b66087e69 100644 --- a/examples/solid/asyncDebounce/package.json +++ b/examples/solid/asyncDebounce/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncRateLimit/package.json b/examples/solid/asyncRateLimit/package.json index d11242703..80ca0d46e 100644 --- a/examples/solid/asyncRateLimit/package.json +++ b/examples/solid/asyncRateLimit/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncThrottle/package.json b/examples/solid/asyncThrottle/package.json index 375ad2954..539d7747b 100644 --- a/examples/solid/asyncThrottle/package.json +++ b/examples/solid/asyncThrottle/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/batch/package.json b/examples/solid/batch/package.json index 6a5c2e529..8fc3acf38 100644 --- a/examples/solid/batch/package.json +++ b/examples/solid/batch/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncDebouncer/package.json b/examples/solid/createAsyncDebouncer/package.json index 6404a9dc4..1a50ed472 100644 --- a/examples/solid/createAsyncDebouncer/package.json +++ b/examples/solid/createAsyncDebouncer/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncQueuer/package.json b/examples/solid/createAsyncQueuer/package.json index e5e48c5be..d7d6d9b0a 100644 --- a/examples/solid/createAsyncQueuer/package.json +++ b/examples/solid/createAsyncQueuer/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncRateLimiter/package.json b/examples/solid/createAsyncRateLimiter/package.json index a3895678c..04cebfcc5 100644 --- a/examples/solid/createAsyncRateLimiter/package.json +++ b/examples/solid/createAsyncRateLimiter/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createAsyncThrottler/package.json b/examples/solid/createAsyncThrottler/package.json index 2228519f2..07348989c 100644 --- a/examples/solid/createAsyncThrottler/package.json +++ b/examples/solid/createAsyncThrottler/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createBatcher/package.json b/examples/solid/createBatcher/package.json index 6691f6904..e173a2cec 100644 --- a/examples/solid/createBatcher/package.json +++ b/examples/solid/createBatcher/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createDebouncedSignal/package.json b/examples/solid/createDebouncedSignal/package.json index 87681c04b..92b268bce 100644 --- a/examples/solid/createDebouncedSignal/package.json +++ b/examples/solid/createDebouncedSignal/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createDebouncedValue/package.json b/examples/solid/createDebouncedValue/package.json index 4d936ca93..cb92de3d6 100644 --- a/examples/solid/createDebouncedValue/package.json +++ b/examples/solid/createDebouncedValue/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createDebouncer/package.json b/examples/solid/createDebouncer/package.json index b59e69a1c..dba1f51ca 100644 --- a/examples/solid/createDebouncer/package.json +++ b/examples/solid/createDebouncer/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createQueuer/package.json b/examples/solid/createQueuer/package.json index 36468948d..bdbb69ffb 100644 --- a/examples/solid/createQueuer/package.json +++ b/examples/solid/createQueuer/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createRateLimitedSignal/package.json b/examples/solid/createRateLimitedSignal/package.json index 2b402365b..1dda56a82 100644 --- a/examples/solid/createRateLimitedSignal/package.json +++ b/examples/solid/createRateLimitedSignal/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createRateLimitedValue/package.json b/examples/solid/createRateLimitedValue/package.json index 1674ac30d..a3604c308 100644 --- a/examples/solid/createRateLimitedValue/package.json +++ b/examples/solid/createRateLimitedValue/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createRateLimiter/package.json b/examples/solid/createRateLimiter/package.json index 5cc1c9b7b..05a5dda93 100644 --- a/examples/solid/createRateLimiter/package.json +++ b/examples/solid/createRateLimiter/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createThrottledSignal/package.json b/examples/solid/createThrottledSignal/package.json index d8443c410..b6bcf6aa2 100644 --- a/examples/solid/createThrottledSignal/package.json +++ b/examples/solid/createThrottledSignal/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createThrottledValue/package.json b/examples/solid/createThrottledValue/package.json index 16ba557a5..93cc55c24 100644 --- a/examples/solid/createThrottledValue/package.json +++ b/examples/solid/createThrottledValue/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/createThrottler/package.json b/examples/solid/createThrottler/package.json index 4130a93f4..1b470919b 100644 --- a/examples/solid/createThrottler/package.json +++ b/examples/solid/createThrottler/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/debounce/package.json b/examples/solid/debounce/package.json index 9b72bd0cb..cca67fef5 100644 --- a/examples/solid/debounce/package.json +++ b/examples/solid/debounce/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/queue/package.json b/examples/solid/queue/package.json index a8110d57e..3a0ee1d56 100644 --- a/examples/solid/queue/package.json +++ b/examples/solid/queue/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/rateLimit/package.json b/examples/solid/rateLimit/package.json index 3f51db1ea..e10bbd29b 100644 --- a/examples/solid/rateLimit/package.json +++ b/examples/solid/rateLimit/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/examples/solid/throttle/package.json b/examples/solid/throttle/package.json index 4600b6369..02830b259 100644 --- a/examples/solid/throttle/package.json +++ b/examples/solid/throttle/package.json @@ -13,8 +13,8 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^6.3.5", - "vite-plugin-solid": "^2.11.6" + "vite": "^7.0.1", + "vite-plugin-solid": "^2.11.7" }, "browserslist": { "production": [ diff --git a/package.json b/package.json index f41f5a6f8..e575aedad 100644 --- a/package.json +++ b/package.json @@ -52,29 +52,29 @@ } ], "devDependencies": { - "@changesets/cli": "^2.29.4", - "@faker-js/faker": "^9.8.0", + "@changesets/cli": "^2.29.5", + "@faker-js/faker": "^9.9.0", "@size-limit/preset-small-lib": "^11.2.0", "@svitejs/changesets-changelog-github-compact": "^1.2.0", - "@tanstack/config": "0.18.2", + "@tanstack/config": "0.19.0", "@testing-library/jest-dom": "^6.6.3", - "@types/node": "^22.15.30", - "eslint": "^9.28.0", + "@types/node": "^24.0.10", + "eslint": "^9.30.1", "eslint-plugin-unused-imports": "^4.1.4", "fast-glob": "^3.3.3", "jsdom": "^26.1.0", - "knip": "^5.60.2", + "knip": "^5.61.3", "markdown-link-extractor": "^4.0.2", - "nx": "^21.1.3", + "nx": "^21.2.2", "premove": "^4.0.0", - "prettier": "^3.5.3", + "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "publint": "^0.3.12", - "sherif": "^1.5.0", + "sherif": "^1.6.1", "size-limit": "^11.2.0", "typescript": "5.8.3", - "vite": "^6.3.5", - "vitest": "^3.2.2" + "vite": "^7.0.1", + "vitest": "^3.2.4" }, "overrides": { "@tanstack/pacer": "workspace:*", diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 1ab3ee109..df9447d6a 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -225,6 +225,7 @@ export class AsyncDebouncer { maybeExecute = async ( ...args: Parameters ): Promise | undefined> => { + if (!this.#getEnabled()) return undefined this.#cancelPendingExecution() this.#setState({ lastArgs: args }) diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index c45e7a942..b626986ce 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -1,3 +1,4 @@ +import { Store } from '@tanstack/store' import { parseFunctionOrValue } from './utils' import type { AnyFunction } from './types' @@ -6,6 +7,19 @@ export interface DebouncerState { executionCount: number isPending: boolean lastArgs: Parameters | undefined + status: 'idle' | 'pending' +} + +function getDefaultDebouncerState< + TFn extends AnyFunction, +>(): DebouncerState { + return structuredClone({ + canLeadingExecute: true, + executionCount: 0, + isPending: false, + lastArgs: undefined, + status: 'idle', + }) } /** @@ -92,13 +106,10 @@ const defaultOptions: Omit< * ``` */ export class Debouncer { + readonly store: Store> = new Store( + getDefaultDebouncerState(), + ) #options: DebouncerOptions - #state: DebouncerState = { - canLeadingExecute: true, - executionCount: 0, - isPending: false, - lastArgs: undefined, - } #timeoutId: NodeJS.Timeout | undefined constructor( @@ -109,10 +120,7 @@ export class Debouncer { ...defaultOptions, ...initialOptions, } - this.#state = { - ...this.#state, - ...this.#options.initialState, - } + this.#setState(this.#options.initialState ?? {}) } /** @@ -122,38 +130,36 @@ export class Debouncer { this.#options = { ...this.#options, ...newOptions } // End the pending state if the debouncer is disabled - if (!this.getEnabled()) { + if (!this.#getEnabled()) { this.#setState({ isPending: false }) } } - /** - * Returns the current debouncer options - */ - getOptions = (): Required> => { - return this.#options as Required> - } - - getState = (): DebouncerState => { - return { ...this.#state } - } - #setState = (newState: Partial>): void => { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + const { isPending } = combinedState + return { + ...combinedState, + status: isPending ? 'pending' : 'idle', + } + }) } /** * Returns the current enabled state of the debouncer */ - getEnabled = (): boolean => { + #getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } /** * Returns the current wait time in milliseconds */ - getWait = (): number => { + #getWait = (): number => { return parseFunctionOrValue(this.#options.wait, this) } @@ -162,10 +168,11 @@ export class Debouncer { * If a call is already in progress, it will be queued */ maybeExecute = (...args: Parameters): void => { + if (!this.#getEnabled()) return undefined let _didLeadingExecute = false // Handle leading execution - if (this.#options.leading && this.#state.canLeadingExecute) { + if (this.#options.leading && this.store.state.canLeadingExecute) { this.#setState({ canLeadingExecute: false }) _didLeadingExecute = true this.#execute(...args) @@ -185,15 +192,15 @@ export class Debouncer { if (this.#options.trailing && !_didLeadingExecute) { this.#execute(...args) } - }, this.getWait()) + }, this.#getWait()) } #execute = (...args: Parameters): void => { - if (!this.getEnabled()) return undefined + if (!this.#getEnabled()) return undefined this.fn(...args) // EXECUTE! this.#setState({ isPending: false, - executionCount: this.#state.executionCount + 1, + executionCount: this.store.state.executionCount + 1, lastArgs: args, }) this.#options.onExecute?.(this) @@ -213,17 +220,10 @@ export class Debouncer { } /** - * Returns the number of times the function has been executed - */ - getExecutionCount = (): number => { - return this.#state.executionCount - } - - /** - * Returns `true` if debouncing + * Resets the debouncer state to its default values */ - getIsPending = (): boolean => { - return this.getEnabled() && this.#state.isPending + reset = (): void => { + this.#setState(getDefaultDebouncerState()) } } diff --git a/packages/pacer/tests/debouncer.test.ts b/packages/pacer/tests/debouncer.test.ts index c6b362eb5..fc0be8012 100644 --- a/packages/pacer/tests/debouncer.test.ts +++ b/packages/pacer/tests/debouncer.test.ts @@ -308,15 +308,15 @@ describe('Debouncer', () => { const mockFn = vi.fn() const debouncer = new Debouncer(mockFn, { wait: 1000 }) - expect(debouncer.getExecutionCount()).toBe(0) + expect(debouncer.store.state.executionCount).toBe(0) debouncer.maybeExecute('test') vi.advanceTimersByTime(1000) - expect(debouncer.getExecutionCount()).toBe(1) + expect(debouncer.store.state.executionCount).toBe(1) debouncer.maybeExecute('test') vi.advanceTimersByTime(1000) - expect(debouncer.getExecutionCount()).toBe(2) + expect(debouncer.store.state.executionCount).toBe(2) }) it('should track execution count with leading and trailing', () => { @@ -326,14 +326,14 @@ describe('Debouncer', () => { leading: true, }) - expect(debouncer.getExecutionCount()).toBe(0) + expect(debouncer.store.state.executionCount).toBe(0) debouncer.maybeExecute('test') debouncer.maybeExecute('test2') - expect(debouncer.getExecutionCount()).toBe(1) // Leading execution + expect(debouncer.store.state.executionCount).toBe(1) // Leading execution vi.advanceTimersByTime(1000) - expect(debouncer.getExecutionCount()).toBe(2) // Trailing execution + expect(debouncer.store.state.executionCount).toBe(2) // Trailing execution }) it('should not increment count when execution is cancelled', () => { @@ -344,7 +344,7 @@ describe('Debouncer', () => { debouncer.cancel() vi.advanceTimersByTime(1000) - expect(debouncer.getExecutionCount()).toBe(0) + expect(debouncer.store.state.executionCount).toBe(0) }) }) @@ -358,7 +358,7 @@ describe('Debouncer', () => { }) debouncer.maybeExecute('test') - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Call again before wait expires vi.advanceTimersByTime(500) @@ -366,10 +366,10 @@ describe('Debouncer', () => { // Time is almost up vi.advanceTimersByTime(900) - expect(debouncer.getState().isPending).toBe(true) // Still pending + expect(debouncer.store.state.isPending).toBe(true) // Still pending vi.advanceTimersByTime(100) - expect(debouncer.getState().isPending).toBe(false) // Now it's done + expect(debouncer.store.state.isPending).toBe(false) // Now it's done }) it('should never be pending when trailing is false', () => { @@ -381,7 +381,7 @@ describe('Debouncer', () => { }) debouncer.maybeExecute('test1') - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) // Call again before wait expires vi.advanceTimersByTime(500) @@ -389,10 +389,10 @@ describe('Debouncer', () => { // Time is almost up vi.advanceTimersByTime(900) - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) vi.advanceTimersByTime(100) - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) }) it('should not be pending when leading and trailing are both false', () => { @@ -404,10 +404,10 @@ describe('Debouncer', () => { }) debouncer.maybeExecute('test') - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) vi.advanceTimersByTime(1000) - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) }) it('should not be pending when disabled', () => { @@ -415,10 +415,10 @@ describe('Debouncer', () => { const debouncer = new Debouncer(mockFn, { wait: 1000, enabled: false }) debouncer.maybeExecute('test') - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) vi.advanceTimersByTime(1000) - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) }) it('should update pending when enabling/disabling', () => { @@ -426,15 +426,15 @@ describe('Debouncer', () => { const debouncer = new Debouncer(mockFn, { wait: 1000 }) debouncer.maybeExecute('test') - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) // Disable while there is a pending execution debouncer.setOptions({ enabled: false }) - expect(debouncer.getState().isPending).toBe(false) // Should be false now + expect(debouncer.store.state.isPending).toBe(false) // Should be false now // Re-enable debouncer.setOptions({ enabled: true }) - expect(debouncer.getState().isPending).toBe(false) // Should still be false + expect(debouncer.store.state.isPending).toBe(false) // Should still be false }) it('should set pending to false when canceled', () => { @@ -442,10 +442,10 @@ describe('Debouncer', () => { const debouncer = new Debouncer(mockFn, { wait: 1000 }) debouncer.maybeExecute('test') - expect(debouncer.getState().isPending).toBe(true) + expect(debouncer.store.state.isPending).toBe(true) debouncer.cancel() - expect(debouncer.getState().isPending).toBe(false) + expect(debouncer.store.state.isPending).toBe(false) }) }) diff --git a/packages/react-pacer/package.json b/packages/react-pacer/package.json index 0167c2843..ee7cf810d 100644 --- a/packages/react-pacer/package.json +++ b/packages/react-pacer/package.json @@ -165,9 +165,9 @@ "@tanstack/react-store": "^0.7.1" }, "devDependencies": { - "@eslint-react/eslint-plugin": "^1.51.2", - "@types/react": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", + "@eslint-react/eslint-plugin": "^1.52.2", + "@types/react": "^19.1.8", + "@vitejs/plugin-react": "^4.6.0", "eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-hooks": "^5.2.0", "react": "^19.1.0" diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index 71cfcd273..a324c7e83 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -84,6 +84,8 @@ export function useAsyncDebouncer< const state = useStore(asyncDebouncer.store, selector) + asyncDebouncer.setOptions(options) + useEffect(() => { return () => { asyncDebouncer.cancel() diff --git a/packages/react-pacer/src/debouncer/useDebouncedCallback.ts b/packages/react-pacer/src/debouncer/useDebouncedCallback.ts index 01047a989..cc6e87448 100644 --- a/packages/react-pacer/src/debouncer/useDebouncedCallback.ts +++ b/packages/react-pacer/src/debouncer/useDebouncedCallback.ts @@ -1,6 +1,9 @@ import { useCallback } from 'react' import { useDebouncer } from './useDebouncer' -import type { DebouncerOptions } from '@tanstack/pacer/debouncer' +import type { + DebouncerOptions, + DebouncerState, +} from '@tanstack/pacer/debouncer' import type { AnyFunction } from '@tanstack/pacer/types' /** @@ -39,13 +42,14 @@ import type { AnyFunction } from '@tanstack/pacer/types' * /> * ``` */ -export function useDebouncedCallback( +export function useDebouncedCallback< + TFn extends AnyFunction, + TSelected = DebouncerState, +>( fn: TFn, options: DebouncerOptions, -) { - const debouncedFn = useDebouncer(fn, options).maybeExecute - return useCallback( - (...args: Parameters) => debouncedFn(...args), - [debouncedFn], - ) + selector?: (state: DebouncerState) => TSelected, +): (...args: Parameters) => void { + const debouncedFn = useDebouncer(fn, options, selector).maybeExecute + return useCallback((...args) => debouncedFn(...args), [debouncedFn]) } diff --git a/packages/react-pacer/src/debouncer/useDebouncedState.ts b/packages/react-pacer/src/debouncer/useDebouncedState.ts index 1d3d85418..dcae588e6 100644 --- a/packages/react-pacer/src/debouncer/useDebouncedState.ts +++ b/packages/react-pacer/src/debouncer/useDebouncedState.ts @@ -1,6 +1,10 @@ import { useState } from 'react' import { useDebouncer } from './useDebouncer' -import type { Debouncer, DebouncerOptions } from '@tanstack/pacer/debouncer' +import type { ReactDebouncer } from './useDebouncer' +import type { + DebouncerOptions, + DebouncerState, +} from '@tanstack/pacer/debouncer' /** * A React hook that creates a debounced state value, combining React's useState with debouncing functionality. @@ -35,15 +39,21 @@ import type { Debouncer, DebouncerOptions } from '@tanstack/pacer/debouncer' * const isPending = debouncer.getState().isPending; * ``` */ -export function useDebouncedState( +export function useDebouncedState< + TValue, + TSelected = DebouncerState>>, +>( value: TValue, options: DebouncerOptions>>, + selector?: ( + state: DebouncerState>>, + ) => TSelected, ): [ TValue, React.Dispatch>, - Debouncer>>, + ReactDebouncer>, TSelected>, ] { - const [debouncedValue, setDebouncedValue] = useState(value) - const debouncer = useDebouncer(setDebouncedValue, options) + const [debouncedValue, setDebouncedValue] = useState(value) + const debouncer = useDebouncer(setDebouncedValue, options, selector) return [debouncedValue, debouncer.maybeExecute, debouncer] } diff --git a/packages/react-pacer/src/debouncer/useDebouncedValue.ts b/packages/react-pacer/src/debouncer/useDebouncedValue.ts index 8c37cface..5504b46d8 100644 --- a/packages/react-pacer/src/debouncer/useDebouncedValue.ts +++ b/packages/react-pacer/src/debouncer/useDebouncedValue.ts @@ -1,6 +1,10 @@ import { useEffect } from 'react' import { useDebouncedState } from './useDebouncedState' -import type { Debouncer, DebouncerOptions } from '@tanstack/pacer/debouncer' +import type { ReactDebouncer } from './useDebouncer' +import type { + DebouncerOptions, + DebouncerState, +} from '@tanstack/pacer/debouncer' /** * A React hook that creates a debounced value that updates only after a specified delay. @@ -38,13 +42,23 @@ import type { Debouncer, DebouncerOptions } from '@tanstack/pacer/debouncer' * }; * ``` */ -export function useDebouncedValue( +export function useDebouncedValue< + TValue, + TSelected = DebouncerState>>, +>( value: TValue, options: DebouncerOptions>>, -): [TValue, Debouncer>>] { + selector?: ( + state: DebouncerState>>, + ) => TSelected, +): [ + TValue, + ReactDebouncer>, TSelected>, +] { const [debouncedValue, setDebouncedValue, debouncer] = useDebouncedState( value, options, + selector, ) useEffect(() => { diff --git a/packages/react-pacer/src/debouncer/useDebouncer.ts b/packages/react-pacer/src/debouncer/useDebouncer.ts index 7eb2d69ce..4b84b23e5 100644 --- a/packages/react-pacer/src/debouncer/useDebouncer.ts +++ b/packages/react-pacer/src/debouncer/useDebouncer.ts @@ -1,8 +1,24 @@ -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Debouncer } from '@tanstack/pacer/debouncer' -import type { DebouncerOptions } from '@tanstack/pacer/debouncer' +import { useStore } from '@tanstack/react-store' +import type { + DebouncerOptions, + DebouncerState, +} from '@tanstack/pacer/debouncer' import type { AnyFunction } from '@tanstack/pacer/types' +export interface ReactDebouncer< + TFn extends AnyFunction, + TSelected = DebouncerState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the debouncer state changes + * + * Use this instead of `debouncer.store.state` + */ + state: TSelected +} + /** * A React hook that creates and manages a Debouncer instance. * @@ -38,11 +54,17 @@ import type { AnyFunction } from '@tanstack/pacer/types' * const isPending = searchdebouncer.getState().isPending; * ``` */ -export function useDebouncer( +export function useDebouncer< + TFn extends AnyFunction, + TSelected = DebouncerState, +>( fn: TFn, options: DebouncerOptions, -): Debouncer { - const [debouncer] = useState(() => new Debouncer(fn, options)) + selector?: (state: DebouncerState) => TSelected, +): ReactDebouncer { + const [debouncer] = useState(() => new Debouncer(fn, options)) + + const state = useStore(debouncer.store, selector) debouncer.setOptions(options) @@ -52,5 +74,12 @@ export function useDebouncer( } }, [debouncer]) - return debouncer + return useMemo( + () => + ({ + ...debouncer, + state, + }) as unknown as ReactDebouncer, // omit `store` in favor of `state` + [debouncer, state], + ) } diff --git a/packages/react-persister/package.json b/packages/react-persister/package.json index c1f2f5609..649a809aa 100644 --- a/packages/react-persister/package.json +++ b/packages/react-persister/package.json @@ -105,9 +105,9 @@ "@tanstack/persister": "workspace:*" }, "devDependencies": { - "@eslint-react/eslint-plugin": "^1.51.2", - "@types/react": "^19.1.6", - "@vitejs/plugin-react": "^4.5.1", + "@eslint-react/eslint-plugin": "^1.52.2", + "@types/react": "^19.1.8", + "@vitejs/plugin-react": "^4.6.0", "eslint-plugin-react-compiler": "19.1.0-rc.2", "eslint-plugin-react-hooks": "^5.2.0", "react": "^19.1.0" diff --git a/packages/solid-pacer/package.json b/packages/solid-pacer/package.json index de59dcaa5..2fb80e0b7 100644 --- a/packages/solid-pacer/package.json +++ b/packages/solid-pacer/package.json @@ -166,7 +166,7 @@ }, "devDependencies": { "solid-js": "^1.9.7", - "vite-plugin-solid": "^2.11.6" + "vite-plugin-solid": "^2.11.7" }, "peerDependencies": { "solid-js": ">=1.9.5" diff --git a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts index e12a5349f..b01f05e4d 100644 --- a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts +++ b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts @@ -1,5 +1,6 @@ import { AsyncDebouncer } from '@tanstack/pacer/async-debouncer' import { useStore } from '@tanstack/solid-store' +import { createEffect, onCleanup } from 'solid-js' import type { Accessor } from 'solid-js' import type { AsyncDebouncerOptions, @@ -84,6 +85,12 @@ export function createAsyncDebouncer< const state = useStore(asyncDebouncer.store, selector) + createEffect(() => { + onCleanup(() => { + asyncDebouncer.cancel() + }) + }) + return { ...asyncDebouncer, state, diff --git a/packages/solid-pacer/src/debouncer/createDebouncedSignal.ts b/packages/solid-pacer/src/debouncer/createDebouncedSignal.ts index 7783ac897..6f72122f3 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncedSignal.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncedSignal.ts @@ -2,7 +2,10 @@ import { createSignal } from 'solid-js' import { createDebouncer } from './createDebouncer' import type { SolidDebouncer } from './createDebouncer' import type { Accessor, Setter } from 'solid-js' -import type { DebouncerOptions } from '@tanstack/pacer/debouncer' +import type { + DebouncerOptions, + DebouncerState, +} from '@tanstack/pacer/debouncer' /** * A Solid hook that creates a debounced state value, combining Solid's createSignal with debouncing functionality. @@ -43,13 +46,21 @@ import type { DebouncerOptions } from '@tanstack/pacer/debouncer' * }); * ``` */ -export function createDebouncedSignal( +export function createDebouncedSignal< + TValue, + TSelected = DebouncerState>, +>( value: TValue, initialOptions: DebouncerOptions>, -): [Accessor, Setter, SolidDebouncer>] { + selector?: (state: DebouncerState>) => TSelected, +): [ + Accessor, + Setter, + SolidDebouncer, TSelected>, +] { const [debouncedValue, setDebouncedValue] = createSignal(value) - const debouncer = createDebouncer(setDebouncedValue, initialOptions) + const debouncer = createDebouncer(setDebouncedValue, initialOptions, selector) return [debouncedValue, debouncer.maybeExecute as Setter, debouncer] } diff --git a/packages/solid-pacer/src/debouncer/createDebouncedValue.ts b/packages/solid-pacer/src/debouncer/createDebouncedValue.ts index e610d6a26..6151c405a 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncedValue.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncedValue.ts @@ -2,7 +2,10 @@ import { createEffect } from 'solid-js' import { createDebouncedSignal } from './createDebouncedSignal' import type { SolidDebouncer } from './createDebouncer' import type { Accessor, Setter } from 'solid-js' -import type { DebouncerOptions } from '@tanstack/pacer/debouncer' +import type { + DebouncerOptions, + DebouncerState, +} from '@tanstack/pacer/debouncer' /** * A Solid hook that creates a debounced value that updates only after a specified delay. @@ -38,13 +41,18 @@ import type { DebouncerOptions } from '@tanstack/pacer/debouncer' * debouncer.cancel(); // Cancel any pending updates * ``` */ -export function createDebouncedValue( +export function createDebouncedValue< + TValue, + TSelected = DebouncerState>, +>( value: Accessor, initialOptions: DebouncerOptions>, -): [Accessor, SolidDebouncer>] { + selector?: (state: DebouncerState>) => TSelected, +): [Accessor, SolidDebouncer, TSelected>] { const [debouncedValue, setDebouncedValue, debouncer] = createDebouncedSignal( value(), initialOptions, + selector, ) createEffect(() => { diff --git a/packages/solid-pacer/src/debouncer/createDebouncer.ts b/packages/solid-pacer/src/debouncer/createDebouncer.ts index e388d7299..e07352be8 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncer.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncer.ts @@ -1,19 +1,23 @@ import { Debouncer } from '@tanstack/pacer/debouncer' import { createEffect, onCleanup } from 'solid-js' -import { createStore } from 'solid-js/store' +import { useStore } from '@tanstack/solid-store' +import type { Accessor } from 'solid-js' import type { AnyFunction } from '@tanstack/pacer/types' import type { DebouncerOptions, DebouncerState, } from '@tanstack/pacer/debouncer' -import type { Store } from 'solid-js/store' -/** - * An extension of the Debouncer class that adds Solid signals to access the internal state of the debouncer - */ -export interface SolidDebouncer - extends Omit, 'getState'> { - store: Store> +export interface SolidDebouncer< + TFn extends AnyFunction, + TSelected = DebouncerState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated when the debouncer state changes + * + * Use this instead of `debouncer.store.state` + */ + state: Accessor } /** @@ -52,39 +56,26 @@ export interface SolidDebouncer * debouncer.setOptions({ wait: 1000 }); * ``` */ -export function createDebouncer( +export function createDebouncer< + TFn extends AnyFunction, + TSelected = DebouncerState, +>( fn: TFn, initialOptions: DebouncerOptions, -): SolidDebouncer { - const debouncer = new Debouncer(fn, initialOptions) - const [store, setStore] = createStore>( - debouncer.getState(), - ) - - function setOptions(newOptions: Partial>) { - debouncer.setOptions({ - ...newOptions, - onStateChange: (state, debouncer) => { - setStore(state) - - const onStateChange = - newOptions.onStateChange ?? initialOptions.onStateChange - onStateChange?.(state, debouncer) - }, - }) - } + selector?: (state: DebouncerState) => TSelected, +): SolidDebouncer { + const asyncDebouncer = new Debouncer(fn, initialOptions) - setOptions(initialOptions) + const state = useStore(asyncDebouncer.store, selector) createEffect(() => { onCleanup(() => { - debouncer.cancel() + asyncDebouncer.cancel() }) }) return { - ...debouncer, - store, - setOptions, - } as SolidDebouncer + ...asyncDebouncer, + state, + } as unknown as SolidDebouncer // omit `store` in favor of `state` } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d00c2f74..c0315f416 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: devDependencies: '@changesets/cli': - specifier: ^2.29.4 - version: 2.29.4 + specifier: ^2.29.5 + version: 2.29.5 '@faker-js/faker': - specifier: ^9.8.0 - version: 9.8.0 + specifier: ^9.9.0 + version: 9.9.0 '@size-limit/preset-small-lib': specifier: ^11.2.0 version: 11.2.0(size-limit@11.2.0) @@ -21,20 +21,20 @@ importers: specifier: ^1.2.0 version: 1.2.0 '@tanstack/config': - specifier: 0.18.2 - version: 0.18.2(@types/node@22.15.30)(@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2))(rollup@4.35.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: 0.19.0 + version: 0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 '@types/node': - specifier: ^22.15.30 - version: 22.15.30 + specifier: ^24.0.10 + version: 24.0.10 eslint: - specifier: ^9.28.0 - version: 9.28.0(jiti@2.4.2) + specifier: ^9.30.1 + version: 9.30.1(jiti@2.4.2) eslint-plugin-unused-imports: specifier: ^4.1.4 - version: 4.1.4(@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)) + version: 4.1.4(@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2)) fast-glob: specifier: ^3.3.3 version: 3.3.3 @@ -42,29 +42,29 @@ importers: specifier: ^26.1.0 version: 26.1.0 knip: - specifier: ^5.60.2 - version: 5.60.2(@types/node@22.15.30)(typescript@5.8.3) + specifier: ^5.61.3 + version: 5.61.3(@types/node@24.0.10)(typescript@5.8.3) markdown-link-extractor: specifier: ^4.0.2 version: 4.0.2 nx: - specifier: ^21.1.3 - version: 21.1.3 + specifier: ^21.2.2 + version: 21.2.2 premove: specifier: ^4.0.0 version: 4.0.0 prettier: - specifier: ^3.5.3 - version: 3.5.3 + specifier: ^3.6.2 + version: 3.6.2 prettier-plugin-svelte: specifier: ^3.4.0 - version: 3.4.0(prettier@3.5.3)(svelte@5.22.6) + version: 3.4.0(prettier@3.6.2)(svelte@5.22.6) publint: specifier: ^0.3.12 version: 0.3.12 sherif: - specifier: ^1.5.0 - version: 1.5.0 + specifier: ^1.6.1 + version: 1.6.1 size-limit: specifier: ^11.2.0 version: 11.2.0 @@ -72,11 +72,11 @@ importers: specifier: 5.8.3 version: 5.8.3 vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vitest: - specifier: ^3.2.2 - version: 3.2.2(@types/node@22.15.30)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncDebounce: dependencies: @@ -91,17 +91,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncRateLimit: dependencies: @@ -116,17 +116,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncThrottle: dependencies: @@ -141,17 +141,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/batch: dependencies: @@ -166,17 +166,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/debounce: dependencies: @@ -191,17 +191,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/queue: dependencies: @@ -216,17 +216,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/rateLimit: dependencies: @@ -241,17 +241,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-debounced-prefetch: dependencies: @@ -259,11 +259,11 @@ importers: specifier: ^0.8.0 version: link:../../../packages/react-pacer '@tanstack/react-query': - specifier: ^5.80.6 - version: 5.80.6(react@19.1.0) + specifier: ^5.81.5 + version: 5.81.5(react@19.1.0) '@tanstack/react-query-devtools': - specifier: ^5.80.6 - version: 5.80.6(@tanstack/react-query@5.80.6(react@19.1.0))(react@19.1.0) + specifier: ^5.81.5 + version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -272,17 +272,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-queued-prefetch: dependencies: @@ -290,11 +290,11 @@ importers: specifier: ^0.8.0 version: link:../../../packages/react-pacer '@tanstack/react-query': - specifier: ^5.80.6 - version: 5.80.6(react@19.1.0) + specifier: ^5.81.5 + version: 5.81.5(react@19.1.0) '@tanstack/react-query-devtools': - specifier: ^5.80.6 - version: 5.80.6(@tanstack/react-query@5.80.6(react@19.1.0))(react@19.1.0) + specifier: ^5.81.5 + version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -303,17 +303,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-throttled-prefetch: dependencies: @@ -321,11 +321,11 @@ importers: specifier: ^0.8.0 version: link:../../../packages/react-pacer '@tanstack/react-query': - specifier: ^5.80.6 - version: 5.80.6(react@19.1.0) + specifier: ^5.81.5 + version: 5.81.5(react@19.1.0) '@tanstack/react-query-devtools': - specifier: ^5.80.6 - version: 5.80.6(@tanstack/react-query@5.80.6(react@19.1.0))(react@19.1.0) + specifier: ^5.81.5 + version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -334,17 +334,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/throttle: dependencies: @@ -359,17 +359,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncDebouncer: dependencies: @@ -384,17 +384,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncQueuedState: dependencies: @@ -409,17 +409,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncQueuer: dependencies: @@ -434,17 +434,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncRateLimiter: dependencies: @@ -462,17 +462,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncThrottler: dependencies: @@ -487,17 +487,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useBatcher: dependencies: @@ -512,17 +512,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedCallback: dependencies: @@ -537,17 +537,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedState: dependencies: @@ -562,17 +562,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedValue: dependencies: @@ -587,17 +587,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncer: dependencies: @@ -612,17 +612,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuedState: dependencies: @@ -637,17 +637,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuedValue: dependencies: @@ -662,17 +662,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuer: dependencies: @@ -690,17 +690,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedCallback: dependencies: @@ -715,17 +715,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedState: dependencies: @@ -740,17 +740,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedValue: dependencies: @@ -765,17 +765,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimiter: dependencies: @@ -793,17 +793,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledCallback: dependencies: @@ -818,17 +818,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledState: dependencies: @@ -843,17 +843,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledValue: dependencies: @@ -868,17 +868,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottler: dependencies: @@ -893,17 +893,17 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.6 - version: 19.1.6(@types/react@19.1.6) + version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/solid/asyncDebounce: dependencies: @@ -915,11 +915,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/asyncRateLimit: dependencies: @@ -931,11 +931,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/asyncThrottle: dependencies: @@ -947,11 +947,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/batch: dependencies: @@ -963,11 +963,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncDebouncer: dependencies: @@ -979,11 +979,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncQueuer: dependencies: @@ -995,11 +995,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncRateLimiter: dependencies: @@ -1011,11 +1011,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncThrottler: dependencies: @@ -1027,11 +1027,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createBatcher: dependencies: @@ -1043,11 +1043,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncedSignal: dependencies: @@ -1059,11 +1059,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncedValue: dependencies: @@ -1075,11 +1075,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncer: dependencies: @@ -1091,11 +1091,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createQueuer: dependencies: @@ -1107,11 +1107,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimitedSignal: dependencies: @@ -1123,11 +1123,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimitedValue: dependencies: @@ -1139,11 +1139,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimiter: dependencies: @@ -1155,11 +1155,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottledSignal: dependencies: @@ -1171,11 +1171,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottledValue: dependencies: @@ -1187,11 +1187,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottler: dependencies: @@ -1203,11 +1203,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/debounce: dependencies: @@ -1219,11 +1219,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/queue: dependencies: @@ -1235,11 +1235,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/rateLimit: dependencies: @@ -1251,11 +1251,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/throttle: dependencies: @@ -1267,11 +1267,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.1 + version: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) packages/pacer: dependencies: @@ -1294,20 +1294,20 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@eslint-react/eslint-plugin': - specifier: ^1.51.2 - version: 1.51.2(eslint@9.28.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + specifier: ^1.52.2 + version: 1.52.2(eslint@9.30.1(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) eslint-plugin-react-compiler: specifier: 19.1.0-rc.2 - version: 19.1.0-rc.2(eslint@9.28.0(jiti@2.4.2)) + version: 19.1.0-rc.2(eslint@9.30.1(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: ^5.2.0 - version: 5.2.0(eslint@9.28.0(jiti@2.4.2)) + version: 5.2.0(eslint@9.30.1(jiti@2.4.2)) react: specifier: ^19.1.0 version: 19.1.0 @@ -1322,20 +1322,20 @@ importers: version: 19.1.0(react@19.1.0) devDependencies: '@eslint-react/eslint-plugin': - specifier: ^1.51.2 - version: 1.51.2(eslint@9.28.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + specifier: ^1.52.2 + version: 1.52.2(eslint@9.30.1(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@vitejs/plugin-react': - specifier: ^4.5.1 - version: 4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^4.6.0 + version: 4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) eslint-plugin-react-compiler: specifier: 19.1.0-rc.2 - version: 19.1.0-rc.2(eslint@9.28.0(jiti@2.4.2)) + version: 19.1.0-rc.2(eslint@9.30.1(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: ^5.2.0 - version: 5.2.0(eslint@9.28.0(jiti@2.4.2)) + version: 5.2.0(eslint@9.30.1(jiti@2.4.2)) react: specifier: ^19.1.0 version: 19.1.0 @@ -1353,8 +1353,8 @@ importers: specifier: ^1.9.7 version: 1.9.7 vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + specifier: ^2.11.7 + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) packages: @@ -1372,16 +1372,24 @@ packages: 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'} + '@babel/compat-data@7.26.8': resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + 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.26.9': - resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} engines: {node: '>=6.9.0'} '@babel/generator@7.26.9': @@ -1392,6 +1400,10 @@ packages: 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/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} @@ -1400,12 +1412,20 @@ packages: 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'} + '@babel/helper-create-class-features-plugin@7.27.0': resolution: {integrity: sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.25.9': resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} @@ -1418,12 +1438,22 @@ packages: 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.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.25.9': resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} @@ -1432,6 +1462,10 @@ packages: resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + '@babel/helper-replace-supers@7.26.5': resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==} engines: {node: '>=6.9.0'} @@ -1446,22 +1480,34 @@ packages: 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/helpers@7.26.9': - resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} + '@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.27.6': + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.26.9': resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} engines: {node: '>=6.0.0'} @@ -1472,6 +1518,11 @@ packages: 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/plugin-proposal-private-methods@7.18.6': resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} engines: {node: '>=6.9.0'} @@ -1485,14 +1536,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1509,6 +1560,10 @@ packages: 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'} @@ -1517,6 +1572,10 @@ packages: 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/types@7.26.9': resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} engines: {node: '>=6.9.0'} @@ -1525,17 +1584,21 @@ packages: 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'} + '@changesets/apply-release-plan@7.0.12': resolution: {integrity: sha512-EaET7As5CeuhTzvXTQCRZeBUcisoYPDDcXvgTE/2jmmypKp0RC7LxKj/yzqeh/1qFTZI7oDGFcL1PHRuQuketQ==} - '@changesets/assemble-release-plan@6.0.8': - resolution: {integrity: sha512-y8+8LvZCkKJdbUlpXFuqcavpzJR80PN0OIfn8HZdwK7Sh6MgLXm4hKY5vu6/NDoKp8lAlM4ERZCqRMLxP4m+MQ==} + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} '@changesets/changelog-git@0.2.1': resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} - '@changesets/cli@2.29.4': - resolution: {integrity: sha512-VW30x9oiFp/un/80+5jLeWgEU6Btj8IqOgI+X/zAYu4usVOWXjPIK5jSSlt5jsCU7/6Z7AxEkarxBxGUqkAmNg==} + '@changesets/cli@2.29.5': + resolution: {integrity: sha512-0j0cPq3fgxt2dPdFsg4XvO+6L66RC0pZybT9F4dG5TBrLA3jA/1pNkdTXH9IBBVHkgsKrNKenI3n1mPyPlIydg==} hasBin: true '@changesets/config@3.1.1': @@ -1550,8 +1613,8 @@ packages: '@changesets/get-github-info@0.6.0': resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} - '@changesets/get-release-plan@4.0.12': - resolution: {integrity: sha512-KukdEgaafnyGryUwpHG2kZ7xJquOmWWWk5mmoeQaSvZTWH1DC5D/Sw6ClgGFYtQnOMSQhgoEbDxAbpIIayKH1g==} + '@changesets/get-release-plan@4.0.13': + resolution: {integrity: sha512-DWG1pus72FcNeXkM12tx+xtExyH/c9I1z+2aXlObH3i9YA7+WZEVaiHzHl03thpvAgWTRaH64MpfHxozfF7Dvg==} '@changesets/get-version-range-type@0.4.0': resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} @@ -1778,12 +1841,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1794,20 +1851,20 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-react/ast@1.51.2': - resolution: {integrity: sha512-ro3AjpT+PDl0CVYjbqKFIPZtsaco6+9vdvstKusTqzqG6cpq2Arzm1rZ86RFitgo4TUOJH1IWUB1TWm3z2q2Vw==} + '@eslint-react/ast@1.52.2': + resolution: {integrity: sha512-L0Tbbzx5l7JHgkQ1TqPWQuZ4+PsXDcgtt3056FOYqstUrDRG+5ylm7h3gEWu98I3FDdgLS8q9dOzz0PGgwZCTA==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} - '@eslint-react/core@1.51.2': - resolution: {integrity: sha512-qo8pvQc8DMJgiL0+M+7g9Z7KYFvTnA+Ph3yJlD7915UnITafgw570xeSfI5pDtJ7TN+3UJBRzclQelYC89v2+w==} + '@eslint-react/core@1.52.2': + resolution: {integrity: sha512-FpxKZJHlf3zXETNL+WQP/SoYuVQNheWm1iDgW68RyHygD8mzk9CnVLDgjMrfmh2n0eaOqnWCL/IC2YzD6VpYOQ==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} - '@eslint-react/eff@1.51.2': - resolution: {integrity: sha512-1cv83iz29cHYpeogwSwJQbZ4/3/0N9nd/856Wq2Opx783pvyrou8+43sOhytc4HL458tubj203I3wNEyyhhNnQ==} + '@eslint-react/eff@1.52.2': + resolution: {integrity: sha512-YBPE2J1+PfXrR9Ct+9rQsw8uRU06zHopI508cfj0usaIBf3hz18V2GoRTVhsjniP0QbvKQdHzyPmmS/B6uyMZQ==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} - '@eslint-react/eslint-plugin@1.51.2': - resolution: {integrity: sha512-wXSDrOvMi2DX8dTiXDDSyGkTm77xn/I3qLRYyn1gX/7GV6GPq+wzHF2aL4n8bRRdYmUBCD2ztLp9VaniKNT5YQ==} + '@eslint-react/eslint-plugin@1.52.2': + resolution: {integrity: sha512-e93chCIWTM6DiYpcuEpc7qDUP7bF7swG7Giq0J6S38czLJvtw9YeMaC9y1BL5rlFbmAcCybDm9QcRI55h/EuMw==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -1816,24 +1873,24 @@ packages: typescript: optional: true - '@eslint-react/kit@1.51.2': - resolution: {integrity: sha512-+uip93wD1Qp6nvMdzv8JXkZw/dxMoJ95x4fGKNvTCBei4XaLjrdIueaUno4hgsSyzNptzOMxGnEyplSLo3ukuQ==} + '@eslint-react/kit@1.52.2': + resolution: {integrity: sha512-k0cSgFnPlDPI1xyRzHjEWIapLG0zCy7mx1HBLg5wuKf/zzSh3iNFId53xMebR05vM2k9YH63gsvTwRkGx/77Zw==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} - '@eslint-react/shared@1.51.2': - resolution: {integrity: sha512-+C1a0/+KMmTgbnTPsW3fMv0D24E6xPuFCwvyeg8MA2Xz046UpLz9u4ds5/T6ebGqM4c3EfN6iIxNV5RHz8P2Zw==} + '@eslint-react/shared@1.52.2': + resolution: {integrity: sha512-YHysVcCfmBoxt2+6Ao4HdLPUYNSem70gy+0yzOQvlQFSsGhh+uifQ68SSa/2uJBWfNUm9xQlyDsr2raeO4BlgA==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} - '@eslint-react/var@1.51.2': - resolution: {integrity: sha512-vHm2jsQ96aARePi1vbv3yTHSs8G3tZ6zeKsG30ntT2WzuRXAlG3VpuiANGVSkhSaaxMXm2tBUWl82GYYSzs7Vg==} + '@eslint-react/var@1.52.2': + resolution: {integrity: sha512-/7IYMPsmO0tIYqkqAVnkqB4eXeVBvgBL/a9hcGCO2eUSzslYzQHSzNPhIoPLD9HXng+0CWlT+KupOFIqP9a26A==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} - '@eslint/config-array@0.20.0': - resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.2.1': - resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.14.0': @@ -1844,14 +1901,14 @@ packages: resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.27.0': - resolution: {integrity: sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.28.0': resolution: {integrity: sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.30.1': + resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1860,8 +1917,8 @@ packages: resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@faker-js/faker@9.8.0': - resolution: {integrity: sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==} + '@faker-js/faker@9.9.0': + resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} '@gerrit0/mini-shiki@1.27.2': @@ -1891,6 +1948,9 @@ packages: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -1909,6 +1969,9 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@kwsites/file-exists@1.1.1': resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} @@ -1952,53 +2015,53 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@nx/nx-darwin-arm64@21.1.3': - resolution: {integrity: sha512-gbBKQrw9ecjXHVs7Kwaht5Dip//NBCgmnkf3GGoA40ad3zyvHDe+MBWMxueRToUVW/mDPh8b5lvLbmFApiY6sQ==} + '@nx/nx-darwin-arm64@21.2.2': + resolution: {integrity: sha512-qDF1SHW9UYzFQBRA3MGLYDPCU/j1ACasAdjv5kMXXBtmg+1WC3mZ/KO84wXJE7j9ImXOPKm9dmiW63LfXteXZw==} cpu: [arm64] os: [darwin] - '@nx/nx-darwin-x64@21.1.3': - resolution: {integrity: sha512-yGDWqxwNty1BJcuvZlwGGravAhg8eIRMEIp2omfIxeyfZEVA4b7egwMCqczwU2Li/StNjTtzrUe1HPWgcCVAuQ==} + '@nx/nx-darwin-x64@21.2.2': + resolution: {integrity: sha512-gdxOcfGonAD+eM5oKKd+2rcrGWmJOfON5HJpLkDfgOO/vyb6FUQub3xUu/JB2RAJ4r6iW/8JZxzheFDIiHDEug==} cpu: [x64] os: [darwin] - '@nx/nx-freebsd-x64@21.1.3': - resolution: {integrity: sha512-vpZPfSQgNIQ0vmnQA26DlJKZog20ISdS14ir234mvCaJJFdlgWGcpyEOSCU3Vg+32Z/VsSx7kIkBwRhfEZ73Ag==} + '@nx/nx-freebsd-x64@21.2.2': + resolution: {integrity: sha512-uO+k4AXGchOlzsoE3uljBKYlI84hv15R2CcLfXjbwrIw+0YZOIeZ/pDYNZMpOy1HePTuCVUxaYQCEBO7N2PI3w==} cpu: [x64] os: [freebsd] - '@nx/nx-linux-arm-gnueabihf@21.1.3': - resolution: {integrity: sha512-R2GzEyHvyree2m7w+e/MOZjUY/l99HbW4E/jJl5BBXRGEAnGTIx9fOxSDiOW5QK6U0oZb2YO2b565t+IC+7rBQ==} + '@nx/nx-linux-arm-gnueabihf@21.2.2': + resolution: {integrity: sha512-7ZaZKJNqQvvXs66GYdvY7kJoZ3wFnaIamjdlFYtH+5oQdCTqRTHb9HsB0/q6pf5nEDCEW/FJkXszKgCfViDZLA==} cpu: [arm] os: [linux] - '@nx/nx-linux-arm64-gnu@21.1.3': - resolution: {integrity: sha512-TlFT0G5gO6ujdkT7KUmvS2bwurvpV3olQwchqW1rQwuZ1eEQ1GVDuyzg49UG7lgESYruFn2HRhBf4V+iaD8WIw==} + '@nx/nx-linux-arm64-gnu@21.2.2': + resolution: {integrity: sha512-M1YuraXtzYTm/HXDAUWN7e009lWFTvpFF1Z38f7IuB07u76ARw1Fb/BcjVYHwt65QR70AcM7MQ5Fpq7PThHPkw==} cpu: [arm64] os: [linux] - '@nx/nx-linux-arm64-musl@21.1.3': - resolution: {integrity: sha512-YkdzrZ7p2Y0YpteRyT9lPKhfuz2t5rNFQ87x9WHK2/cFD6H6M42Fg2JldCPIVj2chN9liH+s5ougW5oPQpZyKw==} + '@nx/nx-linux-arm64-musl@21.2.2': + resolution: {integrity: sha512-raXkg8uijQFOgfKadUzwkFetyFb5pQbY0u6aLz0o9Eq5ml82B8ODrHwZdj2YLVNx2bB2Y0nq6R6HeYQRB94xIQ==} cpu: [arm64] os: [linux] - '@nx/nx-linux-x64-gnu@21.1.3': - resolution: {integrity: sha512-nnHxhakNCr4jR1y13g0yS/UOmn5aXkJ+ZA1R6jFQxIwLv3Ocy05i0ZvU7rPOtflluDberxEop8xzoiuEZXDa/w==} + '@nx/nx-linux-x64-gnu@21.2.2': + resolution: {integrity: sha512-je6D2kG8jCB72QVrYRXs4xRrU2g2zQREqODt+s1zI2lWlMDJcBwxDxGtlxXM3mDyeUGCh2s9nlkrA0GCTin1LQ==} cpu: [x64] os: [linux] - '@nx/nx-linux-x64-musl@21.1.3': - resolution: {integrity: sha512-poPt/LnFbq54CA3PZ1af8wcdQ4VsWRuA9w1Q1/G1BhCfDUAVIOZ0mhH1NzFpPwCxgVZ1TbNCZWhV2qjVRwQtlw==} + '@nx/nx-linux-x64-musl@21.2.2': + resolution: {integrity: sha512-ZDCNM0iBACq5Wgb1+JY20jMMRmxQKIDAoCrkxMciSAjh5s/1fGOboqWmKoztwW5g9QPJs/GdOojWbesu4B42eg==} cpu: [x64] os: [linux] - '@nx/nx-win32-arm64-msvc@21.1.3': - resolution: {integrity: sha512-gBSVMRkXRqxTKgj/dabAD1EaptROy64fEtlU1llPz/RtcJcVhIlDczBF/y2WSD6A72cSv6zF/F1n3NrekNSfBA==} + '@nx/nx-win32-arm64-msvc@21.2.2': + resolution: {integrity: sha512-jQRWpp2i5yAYD0FcZWZu6HMVxPWGEEa1DAf9wn7gHsORCehYH91GeOeVmaXcsPEg56uN+QhJhpIRIcDE5Ob4kw==} cpu: [arm64] os: [win32] - '@nx/nx-win32-x64-msvc@21.1.3': - resolution: {integrity: sha512-k3/1b2dLQjnWzrg2UqHDLCoaqEBx2SRgujjYCACRJ12vmYH2gTyFX2UPXikVbbpaTJNeXv8eaCzyCKhuvPK1sQ==} + '@nx/nx-win32-x64-msvc@21.2.2': + resolution: {integrity: sha512-qBrVdqYVRV1KQFyRtQbtic/R5ByH9F0kZJoQM3hSmcHgbg2s2+v9ivnaik4L6iX8FbAoCjYYm+J8L42yuOgCJA==} cpu: [x64] os: [win32] @@ -2071,8 +2134,8 @@ packages: resolution: {integrity: sha512-S+9ANAvUmjutrshV4jZjaiG8XQyuJIZ8a4utWmN/vW1sgQ9IfBnPndwkmQYw53QmouOIytT874u65HEmu6H5jw==} engines: {node: '>=18'} - '@rolldown/pluginutils@1.0.0-beta.9': - resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} + '@rolldown/pluginutils@1.0.0-beta.19': + resolution: {integrity: sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==} '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} @@ -2083,98 +2146,103 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.35.0': - resolution: {integrity: sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==} + '@rollup/rollup-android-arm-eabi@4.44.1': + resolution: {integrity: sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.35.0': - resolution: {integrity: sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==} + '@rollup/rollup-android-arm64@4.44.1': + resolution: {integrity: sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.35.0': - resolution: {integrity: sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==} + '@rollup/rollup-darwin-arm64@4.44.1': + resolution: {integrity: sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.35.0': - resolution: {integrity: sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==} + '@rollup/rollup-darwin-x64@4.44.1': + resolution: {integrity: sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.35.0': - resolution: {integrity: sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==} + '@rollup/rollup-freebsd-arm64@4.44.1': + resolution: {integrity: sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.35.0': - resolution: {integrity: sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==} + '@rollup/rollup-freebsd-x64@4.44.1': + resolution: {integrity: sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.35.0': - resolution: {integrity: sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==} + '@rollup/rollup-linux-arm-gnueabihf@4.44.1': + resolution: {integrity: sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.35.0': - resolution: {integrity: sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==} + '@rollup/rollup-linux-arm-musleabihf@4.44.1': + resolution: {integrity: sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.35.0': - resolution: {integrity: sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==} + '@rollup/rollup-linux-arm64-gnu@4.44.1': + resolution: {integrity: sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.35.0': - resolution: {integrity: sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==} + '@rollup/rollup-linux-arm64-musl@4.44.1': + resolution: {integrity: sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.35.0': - resolution: {integrity: sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==} + '@rollup/rollup-linux-loongarch64-gnu@4.44.1': + resolution: {integrity: sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': - resolution: {integrity: sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==} + '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': + resolution: {integrity: sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.35.0': - resolution: {integrity: sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==} + '@rollup/rollup-linux-riscv64-gnu@4.44.1': + resolution: {integrity: sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.35.0': - resolution: {integrity: sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==} + '@rollup/rollup-linux-riscv64-musl@4.44.1': + resolution: {integrity: sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.44.1': + resolution: {integrity: sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.35.0': - resolution: {integrity: sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==} + '@rollup/rollup-linux-x64-gnu@4.44.1': + resolution: {integrity: sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.35.0': - resolution: {integrity: sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==} + '@rollup/rollup-linux-x64-musl@4.44.1': + resolution: {integrity: sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.35.0': - resolution: {integrity: sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==} + '@rollup/rollup-win32-arm64-msvc@4.44.1': + resolution: {integrity: sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.35.0': - resolution: {integrity: sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==} + '@rollup/rollup-win32-ia32-msvc@4.44.1': + resolution: {integrity: sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.35.0': - resolution: {integrity: sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==} + '@rollup/rollup-win32-x64-msvc@4.44.1': + resolution: {integrity: sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==} cpu: [x64] os: [win32] @@ -2244,32 +2312,32 @@ packages: resolution: {integrity: sha512-08eKiDAjj4zLug1taXSIJ0kGL5cawjVCyJkBb6EWSg5fEPX6L+Wtr0CH2If4j5KYylz85iaZiFlUItvgJvll5g==} engines: {node: ^14.13.1 || ^16.0.0 || >=18} - '@tanstack/config@0.18.2': - resolution: {integrity: sha512-QuEq2Aky5pEJcDA7eZrnp4f2Fyq3X8iXPdcXmbPpanFXoBJYZCWVH9npkRuaNYNIuL2suFeaGzjwW4HhyWNq9g==} + '@tanstack/config@0.19.0': + resolution: {integrity: sha512-Ir9RF+J6BrNvPBe5t7Mh32Cj1GxCWE8sNrP0ItqhYRud7iwiKN2MNQWlMLDSkiigSh/r831n/Dfwk84qBLqjjw==} engines: {node: '>=18'} '@tanstack/eslint-config@0.2.0': resolution: {integrity: sha512-KUJUDvYFaqxekc8IwgokZ7+yJUoR7LPFu788VSfCxtsbqP/wZyppuoaToC/74LIFjBKIHJQN+YdvBFedD2fqJg==} engines: {node: '>=18'} - '@tanstack/publish-config@0.1.1': - resolution: {integrity: sha512-gw46t9d8fkf4ICU+EVcQdVWWRRux3K3+DM/vO3xp3KrU4/BlQOZgP9BFEWY7znU5pqD5rJ3ozrTbv9ezw78Qyw==} + '@tanstack/publish-config@0.2.0': + resolution: {integrity: sha512-RC0yRBFJvGuR58tKQUIkMXVEiATXgESIc+3/NTqoCC7D2YOF4fZGmHGYIanFEPQH7EGfQ5+Bwi+H6BOtKnymtw==} engines: {node: '>=18'} - '@tanstack/query-core@5.80.6': - resolution: {integrity: sha512-nl7YxT/TAU+VTf+e2zTkObGTyY8YZBMnbgeA1ee66lIVqzKlYursAII6z5t0e6rXgwUMJSV4dshBTNacNpZHbQ==} + '@tanstack/query-core@5.81.5': + resolution: {integrity: sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q==} - '@tanstack/query-devtools@5.80.0': - resolution: {integrity: sha512-D6gH4asyjaoXrCOt5vG5Og/YSj0D/TxwNQgtLJIgWbhbWCC/emu2E92EFoVHh4ppVWg1qT2gKHvKyQBEFZhCuA==} + '@tanstack/query-devtools@5.81.2': + resolution: {integrity: sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==} - '@tanstack/react-query-devtools@5.80.6': - resolution: {integrity: sha512-y7Es0OJ4RYQxrPYsuuQP0jxjgJ40a03UbEPmJ6vwf/ERVMRoRIMkpjtvPxf1D+n9nwPfWmGdD0jW8Wxd+TxeEw==} + '@tanstack/react-query-devtools@5.81.5': + resolution: {integrity: sha512-lCGMu4RX0uGnlrlLeSckBfnW/UV+KMlTBVqa97cwK7Z2ED5JKnZRSjNXwoma6sQBTJrcULvzgx2K6jEPvNUpDw==} peerDependencies: - '@tanstack/react-query': ^5.80.6 + '@tanstack/react-query': ^5.81.5 react: ^18 || ^19 - '@tanstack/react-query@5.80.6': - resolution: {integrity: sha512-izX+5CnkpON3NQGcEm3/d7LfFQNo9ZpFtX2QsINgCYK9LT2VCIdi8D3bMaMSNhrAJCznRoAkFic76uvLroALBw==} + '@tanstack/react-query@5.81.5': + resolution: {integrity: sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==} peerDependencies: react: ^18 || ^19 @@ -2329,6 +2397,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2338,16 +2409,16 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@22.15.30': - resolution: {integrity: sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==} + '@types/node@24.0.10': + resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==} '@types/react-dom@19.1.6': resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: '@types/react': ^19.0.0 - '@types/react@19.1.6': - resolution: {integrity: sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==} + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -2373,20 +2444,32 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.31.1': - resolution: {integrity: sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==} + '@typescript-eslint/project-service@8.35.1': + resolution: {integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/scope-manager@8.33.1': resolution: {integrity: sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.35.1': + resolution: {integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.33.1': resolution: {integrity: sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/tsconfig-utils@8.35.1': + resolution: {integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/type-utils@8.33.1': resolution: {integrity: sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2394,19 +2477,20 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.31.1': - resolution: {integrity: sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==} + '@typescript-eslint/type-utils@8.35.1': + resolution: {integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/types@8.33.1': resolution: {integrity: sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.31.1': - resolution: {integrity: sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==} + '@typescript-eslint/types@8.35.1': + resolution: {integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/typescript-estree@8.33.1': resolution: {integrity: sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==} @@ -2414,11 +2498,10 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.31.1': - resolution: {integrity: sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==} + '@typescript-eslint/typescript-estree@8.35.1': + resolution: {integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/utils@8.33.1': @@ -2428,14 +2511,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.31.1': - resolution: {integrity: sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==} + '@typescript-eslint/utils@8.35.1': + resolution: {integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/visitor-keys@8.33.1': resolution: {integrity: sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.35.1': + resolution: {integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@unrs/resolver-binding-darwin-arm64@1.7.11': resolution: {integrity: sha512-i3/wlWjQJXMh1uiGtiv7k1EYvrrS3L1hdwmWJJiz1D8jWy726YFYPIxQWbEIVPVAgrfRR0XNlLrTQwq17cuCGw==} cpu: [arm64] @@ -2521,17 +2611,17 @@ packages: cpu: [x64] os: [win32] - '@vitejs/plugin-react@4.5.1': - resolution: {integrity: sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==} + '@vitejs/plugin-react@4.6.0': + resolution: {integrity: sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 - '@vitest/expect@3.2.2': - resolution: {integrity: sha512-ipHw0z669vEMjzz3xQE8nJX1s0rQIb7oEl4jjl35qWTwm/KIHERIg/p/zORrjAaZKXfsv7IybcNGHwhOOAPMwQ==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/mocker@3.2.2': - resolution: {integrity: sha512-jKojcaRyIYpDEf+s7/dD3LJt53c0dPfp5zCPXz9H/kcGrSlovU/t1yEaNzM9oFME3dcd4ULwRI/x0Po1Zf+LTw==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 @@ -2541,20 +2631,20 @@ packages: vite: optional: true - '@vitest/pretty-format@3.2.2': - resolution: {integrity: sha512-FY4o4U1UDhO9KMd2Wee5vumwcaHw7Vg4V7yR4Oq6uK34nhEJOmdRYrk3ClburPRUA09lXD/oXWZ8y/Sdma0aUQ==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/runner@3.2.2': - resolution: {integrity: sha512-GYcHcaS3ejGRZYed2GAkvsjBeXIEerDKdX3orQrBJqLRiea4NSS9qvn9Nxmuy1IwIB+EjFOaxXnX79l8HFaBwg==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/snapshot@3.2.2': - resolution: {integrity: sha512-aMEI2XFlR1aNECbBs5C5IZopfi5Lb8QJZGGpzS8ZUHML5La5wCbrbhLOVSME68qwpT05ROEEOAZPRXFpxZV2wA==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/spy@3.2.2': - resolution: {integrity: sha512-6Utxlx3o7pcTxvp0u8kUiXtRFScMrUg28KjB3R2hon7w4YqOFAEA9QwzPVVS1QNL3smo4xRNOpNZClRVfpMcYg==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/utils@3.2.2': - resolution: {integrity: sha512-qJYMllrWpF/OYfWHP32T31QCaLa3BAzT/n/8mNGhPdVcjY+JYazQFO1nsJvXU12Kp1xMpNY4AGuljPTNjQve6A==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@volar/language-core@2.4.12': resolution: {integrity: sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==} @@ -2610,6 +2700,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -3087,8 +3182,8 @@ packages: peerDependencies: eslint: '>=7' - eslint-plugin-react-debug@1.51.2: - resolution: {integrity: sha512-5PxCAjrFXl/rE+W7FF9JeNDtRlqBhBDw/YjOnj0bCG0vu9tCtqf7XJVsaAWsx18KisiQrBV7GsJvm/7gmQFTng==} + eslint-plugin-react-debug@1.52.2: + resolution: {integrity: sha512-9aJoZbC7VPhZ9ByKEg0R1ReDaltLGb9oLMwXL+oxoP4MFYQOL2BKNca+yfe74YZbSCOYidV1nsmCdTEQxh3nhg==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3097,8 +3192,8 @@ packages: typescript: optional: true - eslint-plugin-react-dom@1.51.2: - resolution: {integrity: sha512-4nm3HFMZCkuZjeHyPnjboq07aInTGtRf7HWBA/zgJOkS2+jo4bRt7V4GyFFFj24Zq4mcM+p0ZeWxbGK0E4Uu4A==} + eslint-plugin-react-dom@1.52.2: + resolution: {integrity: sha512-HDwQTwGfJTFAa4x0Bf9NH/TVHULEFjI0/vBNhkZt7JAHFb7v+SrhlXGUIIKfQTPHHJIAQZm8v3yzc5g/NlCokA==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3107,8 +3202,8 @@ packages: typescript: optional: true - eslint-plugin-react-hooks-extra@1.51.2: - resolution: {integrity: sha512-wo3KFmVDRx+4KkiC2iqsU544JG1vy4CIWstHcpdZ7fT3gEuRGiGFNlWOAQ0wH/RMvPwMXstc127FPS8R3sUL9A==} + eslint-plugin-react-hooks-extra@1.52.2: + resolution: {integrity: sha512-95vjCeNMGNZGFoBSwrvaAKfCDvHXXbrdiaizlCmD57AYTHALI9CzvEapQP9qjETNzuf5Uta0/kmRI5Ln4v2y6A==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3123,8 +3218,8 @@ 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.51.2: - resolution: {integrity: sha512-oFoxppmuYwj+D6NpxRmVyUjySnuqfsZltz1y+S0BYROA/XDWAdpLkqfVbsJQSHSsytgqk5wpHtsB2FD6Y0jelA==} + eslint-plugin-react-naming-convention@1.52.2: + resolution: {integrity: sha512-Nww0JUC5aq1Wj0ezuPylBfC4w+j3t3pvg0vR0b+OXjMVAttLQJURgXmAzpURJ1dQOrROLtEQGL4lLTeIAEJ3uQ==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3133,8 +3228,8 @@ packages: typescript: optional: true - eslint-plugin-react-web-api@1.51.2: - resolution: {integrity: sha512-nSJUX3vYTcXGW9VI3ingkQzDb8qC51Y/VGMKysCNFUq3ReXdMGtbW4j+UqsaTYbZmzai3BwI0eE4aAjBXEBivA==} + eslint-plugin-react-web-api@1.52.2: + resolution: {integrity: sha512-EAwSufPNZHWievnCGBRnpE9BcH351dZWTdnuLnDBOmoP5VJnfvaaxgupuFeGSYwM+emzA+0h8qZa/uwjG57TOw==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3143,8 +3238,8 @@ packages: typescript: optional: true - eslint-plugin-react-x@1.51.2: - resolution: {integrity: sha512-PW/93ckImql0/nDEzxFWpjucZe1FLNWs0sMT+T/HPT7WCs78IlAQ3y6Iz/18yonnl0sTOmDFqN06O7Py1+crGQ==} + eslint-plugin-react-x@1.52.2: + resolution: {integrity: sha512-Pxpf3YxCUcNgzJVT6blAJ2KvLX32pUxtXndaCZoTdiytFw/H9OZKq4Qczxx/Lpo9Ri5rm4FbIZL3BfL/HGmzBw==} engines: {bun: '>=1.0.15', node: '>=18.18.0'} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3169,6 +3264,10 @@ packages: resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3177,8 +3276,12 @@ packages: resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.28.0: - resolution: {integrity: sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.30.1: + resolution: {integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -3194,6 +3297,10 @@ packages: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -3262,8 +3369,8 @@ packages: picomatch: optional: true - fdir@6.4.4: - resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -3357,9 +3464,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-tsconfig@4.10.0: - resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} - get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} @@ -3583,6 +3687,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -3638,8 +3745,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - knip@5.60.2: - resolution: {integrity: sha512-TsYqEsoL3802RmhGL5MN7RLI6/03kocMYx/4BpMmwo3dSwEJxmzV7HqRxMVZr6c1llbd25+MqjgA86bv1IwsPA==} + knip@5.61.3: + resolution: {integrity: sha512-8iSz8i8ufIjuUwUKzEwye7ROAW0RzCze7T770bUiz0PKL+SSwbs4RS32fjMztLwcOzSsNPlXdUAeqmkdzXxJ1Q==} engines: {node: '>=18.18.0'} hasBin: true peerDependencies: @@ -3695,6 +3802,9 @@ packages: loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3792,8 +3902,8 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - nanoid@3.3.9: - resolution: {integrity: sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -3838,8 +3948,8 @@ packages: nwsapi@2.2.18: resolution: {integrity: sha512-p1TRH/edngVEHVbwqWnxUViEmq5znDvyB+Sik5cmuLpGOIfDf/39zLiq3swPF8Vakqn+gvNiOQAZu8djYlQILA==} - nx@21.1.3: - resolution: {integrity: sha512-GZ7+Bve4xOVIk/hb9nN16fVqVq5PNNyFom1SCQbEGhGkyABJF8kA4JImCKhZpZyg1CtZeUrkPHK4xNO+rw9G5w==} + nx@21.2.2: + resolution: {integrity: sha512-SP+gojzJhvUfGPw94myECAvF+a7KDQe8c1HUr2HOPR20oSukpdhZM2B1Ki4FGUUuzOcCILhNT2QHLo82+FGLng==} hasBin: true peerDependencies: '@swc-node/register': ^1.8.0 @@ -3969,8 +4079,8 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -3993,8 +4103,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true @@ -4100,8 +4210,8 @@ packages: peerDependencies: rollup: 2.x || 3.x || 4.x - rollup@4.35.0: - resolution: {integrity: sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==} + rollup@4.44.1: + resolution: {integrity: sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4137,11 +4247,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -4165,38 +4270,38 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - sherif-darwin-arm64@1.5.0: - resolution: {integrity: sha512-4BYYCEXVJ8+xqfnntxnyGcwgByjrHSeKOitPYdBPMB3aauptC4GUMeVqXRXf5JNbx3lRKg8bZdoq0QPnbsr6BQ==} + sherif-darwin-arm64@1.6.1: + resolution: {integrity: sha512-J15oBJcrnCAZ0rQE8WbMShYw3204A18akCH6C/uZrILTwX/vZyJIqi7lAt5L00LzsadA3HcyQqVjLNNCvuihoQ==} cpu: [arm64] os: [darwin] - sherif-darwin-x64@1.5.0: - resolution: {integrity: sha512-Y8wkqXzpZt2D5n0KnEXKK1QosoPofDpx4IDzxjyVjGvhR4YBoKcyU93tY9mEyYXMlzQGlyezGHpZkFeP0bioDA==} + sherif-darwin-x64@1.6.1: + resolution: {integrity: sha512-oLA/GtvUasi+qCl35LczOhQ4g/xY2mxE5/eiTYQGT3Ow7FKLscnkE6v5l28bgkFeR/uke0AgZ/CgHhozAf0ulg==} cpu: [x64] os: [darwin] - sherif-linux-arm64@1.5.0: - resolution: {integrity: sha512-l4JBoYCD+mJkwxNuQhLXloySXqSNOdTwJ9euBw8GbRjW+iXK2LxFOya1SlZy8CXZngTOBpNNoO9jH4E9NgI0XA==} + sherif-linux-arm64@1.6.1: + resolution: {integrity: sha512-OoltlucT7v9BZdkYZRbs1QU0DYMCQ5qgpMqQdMW1Rq3w3amr7+oEiV9NHntD83udOo8xRxKq0uPXfNYu+VptJw==} cpu: [arm64] os: [linux] - sherif-linux-x64@1.5.0: - resolution: {integrity: sha512-RaSRnYe58Y6yLTeE8Hw5xmEDn7ted1F4kT1FFSR+ola36R5lnCeWV+xZqjKWU7JE8fVSqApJ4EL0mwtwGZT98Q==} + sherif-linux-x64@1.6.1: + resolution: {integrity: sha512-qyDyYqpi3ABGkRuCnjnxN3OMT8DxMiiLzhS9p9xC05Y9nr5hjkxvqP4DdJ4e5opm4E7vzRAS7VQoZ6m7h6tsgQ==} cpu: [x64] os: [linux] - sherif-windows-arm64@1.5.0: - resolution: {integrity: sha512-xcOnhsCTcg0fg1/SYhFN4AErk1k8QfW4sRK8ziaEt/SDtUGIYmt/+3CsWCBIGoMSaURS8upcU2JtofI3P82qHQ==} + sherif-windows-arm64@1.6.1: + resolution: {integrity: sha512-wAbCiqP//lo7bZUlHmZUV3/sGjnJxo6QB5/fqhz5/GUeWh4CTyvlSacJKZxLnXnzpiUSeFnWutquWnHkRov5Ug==} cpu: [arm64] os: [win32] - sherif-windows-x64@1.5.0: - resolution: {integrity: sha512-kWFctc7kEgYq9IoTFWVGQdZv725eMOwhkF3JpkuApMLF/K+pDb1JKHUbmU52nHdMMKpT3SDVqrHyCaUOKmxU5w==} + sherif-windows-x64@1.6.1: + resolution: {integrity: sha512-2r0qMxZGCMO2aq8Hlq7npxtAsUFVDsEFtUM/6dFo1npa/jHe2mbU7ii/Ymy0bloSa/qw/azrSfRV6GLU7Gjtxg==} cpu: [x64] os: [win32] - sherif@1.5.0: - resolution: {integrity: sha512-sCn0w5b3APLRJauj4UDaK5TV5fyuc1ndDGv2cgt0lnb7zVUAekFm6D2duHVg4sf8/ZlDGFcLRc9Y/DxSRXdOAA==} + sherif@1.6.1: + resolution: {integrity: sha512-ZnwyTnmXoUOPClkOA37JWIyFxCoozMGHmhk/p7XbTREI554XXCnBAn3BMX8UsqkhSzQ9eNQsq4U+jnImEIppsQ==} hasBin: true siginfo@2.0.0: @@ -4209,8 +4314,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-git@3.27.0: - resolution: {integrity: sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==} + simple-git@3.28.0: + resolution: {integrity: sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==} size-limit@11.2.0: resolution: {integrity: sha512-2kpQq2DD/pRpx3Tal/qRW1SYwcIeQ0iq8li5CJHQgOC+FtPn2BVmuDtzUCgNnpCrbgtfEHqh+iWzxK+Tq6C+RQ==} @@ -4295,6 +4400,9 @@ packages: resolution: {integrity: sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==} engines: {node: '>=14.16'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4343,16 +4451,12 @@ packages: resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.13: - resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} - tinypool@1.1.0: - resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@2.0.0: @@ -4397,12 +4501,6 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-api-utils@2.0.1: - resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -4484,8 +4582,8 @@ packages: ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} undici@6.21.3: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} @@ -4522,8 +4620,8 @@ packages: validate-html-nesting@1.2.2: resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} - vite-node@3.2.2: - resolution: {integrity: sha512-Xj/jovjZvDXOq2FgLXu8NsY4uHUMWtzVmMC2LkCu9HWdr9Qu1Is5sanX3Z4jOFKdohfaWDnEJWp9pRP0vVpAcA==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -4542,8 +4640,8 @@ packages: peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 - vite-plugin-solid@2.11.6: - resolution: {integrity: sha512-Sl5CTqJTGyEeOsmdH6BOgalIZlwH3t4/y0RQuFLMGnvWMBvxb4+lq7x3BSiAw6etf0QexfNJW7HSOO/Qf7pigg==} + vite-plugin-solid@2.11.7: + resolution: {integrity: sha512-5TgK1RnE449g0Ryxb9BXqem89RSy7fE8XGVCo+Gw84IHgPuPVP7nYNP6WBVAaY/0xw+OqfdQee+kusL0y3XYNg==} peerDependencies: '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* solid-js: ^1.7.2 @@ -4560,19 +4658,19 @@ packages: vite: optional: true - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite@7.0.1: + resolution: {integrity: sha512-BiKOQoW5HGR30E6JDeNsati6HnSPMVEKbkIWbCiol+xKeu3g5owrjy7kbk/QEMuzCV87dSUTvycYKmlcfGKq3Q==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': ^20.19.0 || >=22.12.0 jiti: '>=1.21.0' - less: '*' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -4608,16 +4706,16 @@ packages: vite: optional: true - vitest@3.2.2: - resolution: {integrity: sha512-fyNn/Rp016Bt5qvY0OQvIUCwW2vnaEBLxP42PmKbNIoasSYjML+8xyeADOPvBe+Xfl/ubIw4og7Lt9jflRsCNw==} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.2 - '@vitest/ui': 3.2.2 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -4760,6 +4858,9 @@ packages: zod@3.25.56: resolution: {integrity: sha512-rd6eEF3BTNvQnR2e2wwolfTmUTnp70aUTqr0oaGbHifzC3BKJsoV+Gat8vxUMR1hwOKBs6El+qWehrHbCpW6SQ==} + zod@3.25.72: + resolution: {integrity: sha512-Cl+fe4dNL4XumOBNBsr0lHfA80PQiZXHI4xEMTEr8gt6aGz92t3lBA32e71j9+JeF/VAYvdfBnuwJs+BMx/BrA==} + snapshots: '@adobe/css-tools@4.4.2': {} @@ -4783,8 +4884,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.26.8': {} + '@babel/compat-data@7.28.0': {} + '@babel/core@7.26.10': dependencies: '@ampproject/remapping': 2.3.0 @@ -4805,20 +4914,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@7.26.9': + '@babel/core@7.28.0': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.9 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) - '@babel/helpers': 7.26.9 - '@babel/parser': 7.26.9 - '@babel/template': 7.26.9 - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.1 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -4841,6 +4950,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.25.9': dependencies: '@babel/types': 7.27.0 @@ -4853,6 +4970,14 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4866,6 +4991,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.25.9': dependencies: '@babel/traverse': 7.27.0 @@ -4884,6 +5011,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-imports@7.27.1': + dependencies: + '@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 @@ -4893,12 +5027,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.26.9 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.0 transitivePeerDependencies: - supports-color @@ -4908,6 +5042,8 @@ snapshots: '@babel/helper-plugin-utils@7.26.5': {} + '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4926,20 +5062,26 @@ snapshots: '@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/helpers@7.26.9': - dependencies: - '@babel/template': 7.26.9 - '@babel/types': 7.26.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.27.6': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 + '@babel/parser@7.26.9': dependencies: '@babel/types': 7.26.9 @@ -4948,6 +5090,10 @@ snapshots: dependencies: '@babel/types': 7.27.0 + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.0 + '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4956,20 +5102,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.9)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.26.9 + '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 '@babel/runtime@7.26.10': dependencies: @@ -4987,6 +5133,12 @@ snapshots: '@babel/parser': 7.27.0 '@babel/types': 7.27.0 + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@babel/traverse@7.26.9': dependencies: '@babel/code-frame': 7.26.2 @@ -5011,6 +5163,18 @@ snapshots: 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.1 + transitivePeerDependencies: + - supports-color + '@babel/types@7.26.9': dependencies: '@babel/helper-string-parser': 7.25.9 @@ -5021,6 +5185,11 @@ snapshots: '@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 + '@changesets/apply-release-plan@7.0.12': dependencies: '@changesets/config': 3.1.1 @@ -5035,30 +5204,30 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.7.1 + semver: 7.7.2 - '@changesets/assemble-release-plan@6.0.8': + '@changesets/assemble-release-plan@6.0.9': dependencies: '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.3 '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 - semver: 7.7.1 + semver: 7.7.2 '@changesets/changelog-git@0.2.1': dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.29.4': + '@changesets/cli@2.29.5': dependencies: '@changesets/apply-release-plan': 7.0.12 - '@changesets/assemble-release-plan': 6.0.8 + '@changesets/assemble-release-plan': 6.0.9 '@changesets/changelog-git': 0.2.1 '@changesets/config': 3.1.1 '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.3 - '@changesets/get-release-plan': 4.0.12 + '@changesets/get-release-plan': 4.0.13 '@changesets/git': 3.0.4 '@changesets/logger': 0.1.1 '@changesets/pre': 2.0.2 @@ -5077,7 +5246,7 @@ snapshots: package-manager-detector: 0.2.11 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.7.1 + semver: 7.7.2 spawndamnit: 3.0.1 term-size: 2.2.1 @@ -5100,7 +5269,7 @@ snapshots: '@changesets/types': 6.1.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 - semver: 7.7.1 + semver: 7.7.2 '@changesets/get-github-info@0.6.0': dependencies: @@ -5109,9 +5278,9 @@ snapshots: transitivePeerDependencies: - encoding - '@changesets/get-release-plan@4.0.12': + '@changesets/get-release-plan@4.0.13': dependencies: - '@changesets/assemble-release-plan': 6.0.8 + '@changesets/assemble-release-plan': 6.0.9 '@changesets/config': 3.1.1 '@changesets/pre': 2.0.2 '@changesets/read': 0.6.5 @@ -5289,24 +5458,19 @@ snapshots: '@esbuild/win32-x64@0.25.0': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.28.0(jiti@2.4.2))': - dependencies: - eslint: 9.28.0(jiti@2.4.2) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/eslint-utils@4.7.0(eslint@9.28.0(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': dependencies: - eslint: 9.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint-react/ast@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@eslint-react/ast@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-react/eff': 1.51.2 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) string-ts: 2.2.1 ts-pattern: 5.7.1 transitivePeerDependencies: @@ -5314,17 +5478,17 @@ snapshots: - supports-color - typescript - '@eslint-react/core@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': - dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/var': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/core@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/var': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) birecord: 0.1.1 ts-pattern: 5.7.1 transitivePeerDependencies: @@ -5332,60 +5496,60 @@ snapshots: - supports-color - typescript - '@eslint-react/eff@1.51.2': {} - - '@eslint-react/eslint-plugin@1.51.2(eslint@9.28.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)': - dependencies: - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) - eslint-plugin-react-debug: 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-react-dom: 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-react-hooks-extra: 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-react-naming-convention: 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-react-web-api: 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-react-x: 1.51.2(eslint@9.28.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + '@eslint-react/eff@1.52.2': {} + + '@eslint-react/eslint-plugin@1.52.2(eslint@9.30.1(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)': + dependencies: + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + eslint-plugin-react-debug: 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-react-dom: 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-react-hooks-extra: 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-react-naming-convention: 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-react-web-api: 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-react-x: 1.52.2(eslint@9.30.1(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: - supports-color - ts-api-utils - '@eslint-react/kit@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@eslint-react/kit@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-react/eff': 1.51.2 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) ts-pattern: 5.7.1 - zod: 3.25.56 + zod: 3.25.72 transitivePeerDependencies: - eslint - supports-color - typescript - '@eslint-react/shared@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@eslint-react/shared@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) ts-pattern: 5.7.1 - zod: 3.25.56 + zod: 3.25.72 transitivePeerDependencies: - eslint - supports-color - typescript - '@eslint-react/var@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@eslint-react/var@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) string-ts: 2.2.1 ts-pattern: 5.7.1 transitivePeerDependencies: @@ -5393,15 +5557,15 @@ snapshots: - supports-color - typescript - '@eslint/config-array@0.20.0': + '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.0 + debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.2.1': {} + '@eslint/config-helpers@0.3.0': {} '@eslint/core@0.14.0': dependencies: @@ -5410,8 +5574,8 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 + debug: 4.4.1 + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 @@ -5421,10 +5585,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.27.0': {} - '@eslint/js@9.28.0': {} + '@eslint/js@9.30.1': {} + '@eslint/object-schema@2.1.6': {} '@eslint/plugin-kit@0.3.1': @@ -5432,7 +5596,7 @@ snapshots: '@eslint/core': 0.14.0 levn: 0.4.1 - '@faker-js/faker@9.8.0': {} + '@faker-js/faker@9.9.0': {} '@gerrit0/mini-shiki@1.27.2': dependencies: @@ -5457,6 +5621,11 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.8 + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -5474,9 +5643,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping@0.3.29': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -5498,23 +5672,23 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@microsoft/api-extractor-model@7.29.6(@types/node@22.15.30)': + '@microsoft/api-extractor-model@7.29.6(@types/node@24.0.10)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.7.0(@types/node@22.15.30) + '@rushstack/node-core-library': 5.7.0(@types/node@24.0.10) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.47.7(@types/node@22.15.30)': + '@microsoft/api-extractor@7.47.7(@types/node@24.0.10)': dependencies: - '@microsoft/api-extractor-model': 7.29.6(@types/node@22.15.30) + '@microsoft/api-extractor-model': 7.29.6(@types/node@24.0.10) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.7.0(@types/node@22.15.30) + '@rushstack/node-core-library': 5.7.0(@types/node@24.0.10) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.14.0(@types/node@22.15.30) - '@rushstack/ts-command-line': 4.22.6(@types/node@22.15.30) + '@rushstack/terminal': 0.14.0(@types/node@24.0.10) + '@rushstack/ts-command-line': 4.22.6(@types/node@24.0.10) lodash: 4.17.21 minimatch: 3.0.8 resolve: 1.22.10 @@ -5558,34 +5732,34 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@nx/nx-darwin-arm64@21.1.3': + '@nx/nx-darwin-arm64@21.2.2': optional: true - '@nx/nx-darwin-x64@21.1.3': + '@nx/nx-darwin-x64@21.2.2': optional: true - '@nx/nx-freebsd-x64@21.1.3': + '@nx/nx-freebsd-x64@21.2.2': optional: true - '@nx/nx-linux-arm-gnueabihf@21.1.3': + '@nx/nx-linux-arm-gnueabihf@21.2.2': optional: true - '@nx/nx-linux-arm64-gnu@21.1.3': + '@nx/nx-linux-arm64-gnu@21.2.2': optional: true - '@nx/nx-linux-arm64-musl@21.1.3': + '@nx/nx-linux-arm64-musl@21.2.2': optional: true - '@nx/nx-linux-x64-gnu@21.1.3': + '@nx/nx-linux-x64-gnu@21.2.2': optional: true - '@nx/nx-linux-x64-musl@21.1.3': + '@nx/nx-linux-x64-musl@21.2.2': optional: true - '@nx/nx-win32-arm64-msvc@21.1.3': + '@nx/nx-win32-arm64-msvc@21.2.2': optional: true - '@nx/nx-win32-x64-msvc@21.1.3': + '@nx/nx-win32-x64-msvc@21.2.2': optional: true '@oxc-resolver/binding-darwin-arm64@11.1.0': @@ -5631,74 +5805,77 @@ snapshots: '@publint/pack@0.1.2': {} - '@rolldown/pluginutils@1.0.0-beta.9': {} + '@rolldown/pluginutils@1.0.0-beta.19': {} - '@rollup/pluginutils@5.1.4(rollup@4.35.0)': + '@rollup/pluginutils@5.1.4(rollup@4.44.1)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.35.0 + rollup: 4.44.1 - '@rollup/rollup-android-arm-eabi@4.35.0': + '@rollup/rollup-android-arm-eabi@4.44.1': optional: true - '@rollup/rollup-android-arm64@4.35.0': + '@rollup/rollup-android-arm64@4.44.1': optional: true - '@rollup/rollup-darwin-arm64@4.35.0': + '@rollup/rollup-darwin-arm64@4.44.1': optional: true - '@rollup/rollup-darwin-x64@4.35.0': + '@rollup/rollup-darwin-x64@4.44.1': optional: true - '@rollup/rollup-freebsd-arm64@4.35.0': + '@rollup/rollup-freebsd-arm64@4.44.1': optional: true - '@rollup/rollup-freebsd-x64@4.35.0': + '@rollup/rollup-freebsd-x64@4.44.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.35.0': + '@rollup/rollup-linux-arm-gnueabihf@4.44.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.35.0': + '@rollup/rollup-linux-arm-musleabihf@4.44.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.35.0': + '@rollup/rollup-linux-arm64-gnu@4.44.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.35.0': + '@rollup/rollup-linux-arm64-musl@4.44.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.35.0': + '@rollup/rollup-linux-loongarch64-gnu@4.44.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.35.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.35.0': + '@rollup/rollup-linux-riscv64-gnu@4.44.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.35.0': + '@rollup/rollup-linux-riscv64-musl@4.44.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.35.0': + '@rollup/rollup-linux-s390x-gnu@4.44.1': optional: true - '@rollup/rollup-linux-x64-musl@4.35.0': + '@rollup/rollup-linux-x64-gnu@4.44.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.35.0': + '@rollup/rollup-linux-x64-musl@4.44.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.35.0': + '@rollup/rollup-win32-arm64-msvc@4.44.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.35.0': + '@rollup/rollup-win32-ia32-msvc@4.44.1': optional: true - '@rushstack/node-core-library@5.7.0(@types/node@22.15.30)': + '@rollup/rollup-win32-x64-msvc@4.44.1': + optional: true + + '@rushstack/node-core-library@5.7.0(@types/node@24.0.10)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -5709,23 +5886,23 @@ snapshots: resolve: 1.22.10 semver: 7.5.4 optionalDependencies: - '@types/node': 22.15.30 + '@types/node': 24.0.10 '@rushstack/rig-package@0.5.3': dependencies: resolve: 1.22.10 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.14.0(@types/node@22.15.30)': + '@rushstack/terminal@0.14.0(@types/node@24.0.10)': dependencies: - '@rushstack/node-core-library': 5.7.0(@types/node@22.15.30) + '@rushstack/node-core-library': 5.7.0(@types/node@24.0.10) supports-color: 8.1.1 optionalDependencies: - '@types/node': 22.15.30 + '@types/node': 24.0.10 - '@rushstack/ts-command-line@4.22.6(@types/node@22.15.30)': + '@rushstack/ts-command-line@4.22.6(@types/node@24.0.10)': dependencies: - '@rushstack/terminal': 0.14.0(@types/node@22.15.30) + '@rushstack/terminal': 0.14.0(@types/node@24.0.10) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -5762,10 +5939,10 @@ snapshots: '@size-limit/file': 11.2.0(size-limit@11.2.0) size-limit: 11.2.0 - '@stylistic/eslint-plugin@4.4.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@stylistic/eslint-plugin@4.4.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) eslint-visitor-keys: 4.2.0 espree: 10.3.0 estraverse: 5.3.0 @@ -5785,12 +5962,12 @@ snapshots: transitivePeerDependencies: - encoding - '@tanstack/config@0.18.2(@types/node@22.15.30)(@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2))(rollup@4.35.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@tanstack/config@0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - '@tanstack/eslint-config': 0.2.0(@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@tanstack/publish-config': 0.1.1 + '@tanstack/eslint-config': 0.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@tanstack/publish-config': 0.2.0 '@tanstack/typedoc-config': 0.2.0(typescript@5.8.3) - '@tanstack/vite-config': 0.2.0(@types/node@22.15.30)(rollup@4.35.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + '@tanstack/vite-config': 0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) transitivePeerDependencies: - '@types/node' - '@typescript-eslint/utils' @@ -5801,15 +5978,15 @@ snapshots: - typescript - vite - '@tanstack/eslint-config@0.2.0(@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@tanstack/eslint-config@0.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint/js': 9.27.0 - '@stylistic/eslint-plugin': 4.4.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-import-x: 4.15.1(@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2)) - eslint-plugin-n: 17.19.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@eslint/js': 9.28.0 + '@stylistic/eslint-plugin': 4.4.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-import-x: 4.15.1(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-n: 17.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) globals: 16.2.0 - typescript-eslint: 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - vue-eslint-parser: 10.1.3(eslint@9.28.0(jiti@2.4.2)) + typescript-eslint: 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + vue-eslint-parser: 10.1.3(eslint@9.30.1(jiti@2.4.2)) transitivePeerDependencies: - '@typescript-eslint/utils' - eslint @@ -5817,28 +5994,28 @@ snapshots: - supports-color - typescript - '@tanstack/publish-config@0.1.1': + '@tanstack/publish-config@0.2.0': dependencies: '@commitlint/parse': 19.8.1 jsonfile: 6.1.0 semver: 7.7.2 - simple-git: 3.27.0 + simple-git: 3.28.0 transitivePeerDependencies: - supports-color - '@tanstack/query-core@5.80.6': {} + '@tanstack/query-core@5.81.5': {} - '@tanstack/query-devtools@5.80.0': {} + '@tanstack/query-devtools@5.81.2': {} - '@tanstack/react-query-devtools@5.80.6(@tanstack/react-query@5.80.6(react@19.1.0))(react@19.1.0)': + '@tanstack/react-query-devtools@5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0)': dependencies: - '@tanstack/query-devtools': 5.80.0 - '@tanstack/react-query': 5.80.6(react@19.1.0) + '@tanstack/query-devtools': 5.81.2 + '@tanstack/react-query': 5.81.5(react@19.1.0) react: 19.1.0 - '@tanstack/react-query@5.80.6(react@19.1.0)': + '@tanstack/react-query@5.81.5(react@19.1.0)': dependencies: - '@tanstack/query-core': 5.80.6 + '@tanstack/query-core': 5.81.5 react: 19.1.0 '@tanstack/react-store@0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': @@ -5863,12 +6040,12 @@ snapshots: transitivePeerDependencies: - typescript - '@tanstack/vite-config@0.2.0(@types/node@22.15.30)(rollup@4.35.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@tanstack/vite-config@0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - rollup-plugin-preserve-directives: 0.4.0(rollup@4.35.0) - vite-plugin-dts: 4.2.3(@types/node@22.15.30)(rollup@4.35.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - vite-plugin-externalize-deps: 0.9.0(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - vite-tsconfig-paths: 5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + rollup-plugin-preserve-directives: 0.4.0(rollup@4.44.1) + vite-plugin-dts: 4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-plugin-externalize-deps: 0.9.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-tsconfig-paths: 5.1.4(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) transitivePeerDependencies: - '@types/node' - rollup @@ -5894,24 +6071,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.27.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.27.0 '@types/chai@5.2.2': dependencies: @@ -5919,12 +6096,14 @@ snapshots: '@types/conventional-commits-parser@5.0.1': dependencies: - '@types/node': 22.15.30 + '@types/node': 24.0.10 '@types/deep-eql@4.0.2': {} '@types/estree@1.0.6': {} + '@types/estree@1.0.8': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -5933,29 +6112,29 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@22.15.30': + '@types/node@24.0.10': dependencies: - undici-types: 6.21.0 + undici-types: 7.8.0 - '@types/react-dom@19.1.6(@types/react@19.1.6)': + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: - '@types/react': 19.1.6 + '@types/react': 19.1.8 - '@types/react@19.1.6': + '@types/react@19.1.8': dependencies: csstype: 3.1.3 '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.33.1 - eslint: 9.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -5964,14 +6143,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/parser@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.33.1 '@typescript-eslint/types': 8.33.1 '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.33.1 - debug: 4.4.0 - eslint: 9.28.0(jiti@2.4.2) + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -5980,102 +6159,128 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.33.1(typescript@5.8.3) '@typescript-eslint/types': 8.33.1 - debug: 4.4.0 + debug: 4.4.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.31.1': + '@typescript-eslint/project-service@8.35.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.31.1 - '@typescript-eslint/visitor-keys': 8.31.1 + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + debug: 4.4.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color '@typescript-eslint/scope-manager@8.33.1': dependencies: '@typescript-eslint/types': 8.33.1 '@typescript-eslint/visitor-keys': 8.33.1 + '@typescript-eslint/scope-manager@8.35.1': + dependencies: + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 + '@typescript-eslint/tsconfig-utils@8.33.1(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - debug: 4.4.0 - eslint: 9.28.0(jiti@2.4.2) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.31.1': {} - - '@typescript-eslint/types@8.33.1': {} - - '@typescript-eslint/typescript-estree@8.31.1(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.31.1 - '@typescript-eslint/visitor-keys': 8.31.1 - debug: 4.4.0 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.0.1(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color + '@typescript-eslint/types@8.33.1': {} + + '@typescript-eslint/types@8.35.1': {} + '@typescript-eslint/typescript-estree@8.33.1(typescript@5.8.3)': dependencies: '@typescript-eslint/project-service': 8.33.1(typescript@5.8.3) '@typescript-eslint/tsconfig-utils': 8.33.1(typescript@5.8.3) '@typescript-eslint/types': 8.33.1 '@typescript-eslint/visitor-keys': 8.33.1 - debug: 4.4.0 + debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 + semver: 7.7.2 ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.31.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.31.1 - '@typescript-eslint/types': 8.31.1 - '@typescript-eslint/typescript-estree': 8.31.1(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + '@typescript-eslint/project-service': 8.35.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/utils@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@typescript-eslint/scope-manager': 8.33.1 '@typescript-eslint/types': 8.33.1 '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.31.1': + '@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.31.1 - eslint-visitor-keys: 4.2.0 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color '@typescript-eslint/visitor-keys@8.33.1': dependencies: '@typescript-eslint/types': 8.33.1 eslint-visitor-keys: 4.2.0 + '@typescript-eslint/visitor-keys@8.35.1': + dependencies: + '@typescript-eslint/types': 8.35.1 + eslint-visitor-keys: 4.2.1 + '@unrs/resolver-binding-darwin-arm64@1.7.11': optional: true @@ -6129,57 +6334,58 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.7.11': optional: true - '@vitejs/plugin-react@4.5.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@vitejs/plugin-react@4.6.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - '@babel/core': 7.26.10 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) - '@rolldown/pluginutils': 1.0.0-beta.9 + '@babel/core': 7.28.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) + '@rolldown/pluginutils': 1.0.0-beta.19 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color - '@vitest/expect@3.2.2': + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 - '@vitest/spy': 3.2.2 - '@vitest/utils': 3.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@vitest/mocker@3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: - '@vitest/spy': 3.2.2 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - '@vitest/pretty-format@3.2.2': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@3.2.2': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 3.2.2 + '@vitest/utils': 3.2.4 pathe: 2.0.3 + strip-literal: 3.0.0 - '@vitest/snapshot@3.2.2': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 3.2.2 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 - '@vitest/spy@3.2.2': + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 - '@vitest/utils@3.2.2': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 3.2.2 - loupe: 3.1.3 + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 tinyrainbow: 2.0.0 '@volar/language-core@2.4.12': @@ -6247,8 +6453,14 @@ snapshots: dependencies: acorn: 8.14.1 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn@8.14.1: {} + acorn@8.15.0: {} + agent-base@7.1.3: {} ajv-draft-04@1.0.0(ajv@8.13.0): @@ -6316,20 +6528,20 @@ snapshots: axobject-query@4.1.0: {} - babel-plugin-jsx-dom-expressions@0.39.7(@babel/core@7.26.9): + babel-plugin-jsx-dom-expressions@0.39.7(@babel/core@7.26.10): dependencies: - '@babel/core': 7.26.9 + '@babel/core': 7.26.10 '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) '@babel/types': 7.27.0 html-entities: 2.3.3 parse5: 7.2.1 validate-html-nesting: 1.2.2 - babel-preset-solid@1.9.5(@babel/core@7.26.9): + babel-preset-solid@1.9.5(@babel/core@7.26.10): dependencies: - '@babel/core': 7.26.9 - babel-plugin-jsx-dom-expressions: 0.39.7(@babel/core@7.26.9) + '@babel/core': 7.26.10 + babel-plugin-jsx-dom-expressions: 0.39.7(@babel/core@7.26.10) balanced-match@1.0.2: {} @@ -6679,10 +6891,10 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-compat-utils@0.5.1(eslint@9.28.0(jiti@2.4.2)): + eslint-compat-utils@0.5.1(eslint@9.30.1(jiti@2.4.2)): dependencies: - eslint: 9.28.0(jiti@2.4.2) - semver: 7.7.1 + eslint: 9.30.1(jiti@2.4.2) + semver: 7.7.2 eslint-import-context@0.1.8(unrs-resolver@1.7.11): dependencies: @@ -6700,19 +6912,19 @@ snapshots: - supports-color optional: true - eslint-plugin-es-x@7.8.0(eslint@9.28.0(jiti@2.4.2)): + eslint-plugin-es-x@7.8.0(eslint@9.30.1(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 - eslint: 9.28.0(jiti@2.4.2) - eslint-compat-utils: 0.5.1(eslint@9.28.0(jiti@2.4.2)) + eslint: 9.30.1(jiti@2.4.2) + eslint-compat-utils: 0.5.1(eslint@9.30.1(jiti@2.4.2)) - eslint-plugin-import-x@4.15.1(@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.28.0(jiti@2.4.2)): + eslint-plugin-import-x@4.15.1(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2)): dependencies: '@typescript-eslint/types': 8.33.1 comment-parser: 1.4.1 debug: 4.4.1 - eslint: 9.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) eslint-import-context: 0.1.8(unrs-resolver@1.7.11) is-glob: 4.0.3 minimatch: 9.0.5 @@ -6720,53 +6932,53 @@ snapshots: stable-hash-x: 0.1.1 unrs-resolver: 1.7.11 optionalDependencies: - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-n@17.19.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): + eslint-plugin-n@17.19.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) - '@typescript-eslint/utils': 8.31.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) enhanced-resolve: 5.18.1 - eslint: 9.28.0(jiti@2.4.2) - eslint-plugin-es-x: 7.8.0(eslint@9.28.0(jiti@2.4.2)) - get-tsconfig: 4.10.0 + eslint: 9.30.1(jiti@2.4.2) + eslint-plugin-es-x: 7.8.0(eslint@9.30.1(jiti@2.4.2)) + get-tsconfig: 4.10.1 globals: 15.15.0 ignore: 5.3.2 minimatch: 9.0.5 - semver: 7.7.1 + semver: 7.7.2 ts-declaration-location: 1.0.7(typescript@5.8.3) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-react-compiler@19.1.0-rc.2(eslint@9.28.0(jiti@2.4.2)): + eslint-plugin-react-compiler@19.1.0-rc.2(eslint@9.30.1(jiti@2.4.2)): dependencies: '@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.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) hermes-parser: 0.25.1 zod: 3.24.2 zod-validation-error: 3.4.0(zod@3.24.2) transitivePeerDependencies: - supports-color - eslint-plugin-react-debug@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/core': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/var': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + eslint-plugin-react-debug@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/core': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/var': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -6774,19 +6986,19 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-dom@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/core': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/var': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-react-dom@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/core': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/var': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) compare-versions: 6.1.1 - eslint: 9.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -6794,19 +7006,19 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks-extra@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/core': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/var': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + eslint-plugin-react-hooks-extra@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/core': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/var': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -6814,23 +7026,23 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks@5.2.0(eslint@9.28.0(jiti@2.4.2)): + eslint-plugin-react-hooks@5.2.0(eslint@9.30.1(jiti@2.4.2)): dependencies: - eslint: 9.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) - eslint-plugin-react-naming-convention@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): + eslint-plugin-react-naming-convention@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/core': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/var': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/core': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/var': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -6838,18 +7050,18 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-web-api@1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/core': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/var': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + eslint-plugin-react-web-api@1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/core': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/var': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -6857,21 +7069,21 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-x@1.51.2(eslint@9.28.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3): - dependencies: - '@eslint-react/ast': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/core': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/eff': 1.51.2 - '@eslint-react/kit': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/shared': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@eslint-react/var': 1.51.2(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.33.1 - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-react-x@1.52.2(eslint@9.30.1(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3): + dependencies: + '@eslint-react/ast': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/core': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/eff': 1.52.2 + '@eslint-react/kit': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/shared': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@eslint-react/var': 1.52.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) compare-versions: 6.1.1 - eslint: 9.28.0(jiti@2.4.2) - is-immutable-type: 5.0.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + is-immutable-type: 5.0.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) string-ts: 2.2.1 ts-pattern: 5.7.1 optionalDependencies: @@ -6880,30 +7092,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2)): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2)): dependencies: - eslint: 9.28.0(jiti@2.4.2) + eslint: 9.30.1(jiti@2.4.2) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) eslint-scope@8.3.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@4.2.0: {} - eslint@9.28.0(jiti@2.4.2): + eslint-visitor-keys@4.2.1: {} + + eslint@9.30.1(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.28.0(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.20.0 - '@eslint/config-helpers': 0.2.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 '@eslint/core': 0.14.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.28.0 + '@eslint/js': 9.30.1 '@eslint/plugin-kit': 0.3.1 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 @@ -6913,11 +7132,11 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -6945,6 +7164,12 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.14.1) eslint-visitor-keys: 4.2.0 + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} esquery@1.6.0: @@ -7005,7 +7230,7 @@ snapshots: optionalDependencies: picomatch: 4.0.2 - fdir@6.4.4(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -7098,10 +7323,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-tsconfig@4.10.0: - dependencies: - resolve-pkg-maps: 1.0.0 - get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -7233,11 +7454,11 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-immutable-type@5.0.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): + is-immutable-type@5.0.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/type-utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) - ts-api-utils: 2.0.1(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) ts-declaration-location: 1.0.7(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -7290,6 +7511,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@3.14.1: dependencies: argparse: 1.0.10 @@ -7356,10 +7579,10 @@ snapshots: dependencies: json-buffer: 3.0.1 - knip@5.60.2(@types/node@22.15.30)(typescript@5.8.3): + knip@5.61.3(@types/node@24.0.10)(typescript@5.8.3): dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 22.15.30 + '@types/node': 24.0.10 fast-glob: 3.3.3 formatly: 0.2.4 jiti: 2.4.2 @@ -7371,8 +7594,8 @@ snapshots: smol-toml: 1.3.4 strip-json-comments: 5.0.2 typescript: 5.8.3 - zod: 3.24.2 - zod-validation-error: 3.4.0(zod@3.24.2) + zod: 3.25.56 + zod-validation-error: 3.4.0(zod@3.25.56) kolorist@1.8.0: {} @@ -7417,6 +7640,8 @@ snapshots: loupe@3.1.3: {} + loupe@3.1.4: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -7507,7 +7732,7 @@ snapshots: muggle-string@0.4.1: {} - nanoid@3.3.9: {} + nanoid@3.3.11: {} nanoid@5.1.3: {} @@ -7537,7 +7762,7 @@ snapshots: nwsapi@2.2.18: {} - nx@21.1.3: + nx@21.2.2: dependencies: '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 @@ -7564,7 +7789,7 @@ snapshots: open: 8.4.2 ora: 5.3.0 resolve.exports: 2.0.3 - semver: 7.7.1 + semver: 7.7.2 string-width: 4.2.3 tar-stream: 2.2.0 tmp: 0.2.3 @@ -7575,16 +7800,16 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nx/nx-darwin-arm64': 21.1.3 - '@nx/nx-darwin-x64': 21.1.3 - '@nx/nx-freebsd-x64': 21.1.3 - '@nx/nx-linux-arm-gnueabihf': 21.1.3 - '@nx/nx-linux-arm64-gnu': 21.1.3 - '@nx/nx-linux-arm64-musl': 21.1.3 - '@nx/nx-linux-x64-gnu': 21.1.3 - '@nx/nx-linux-x64-musl': 21.1.3 - '@nx/nx-win32-arm64-msvc': 21.1.3 - '@nx/nx-win32-x64-msvc': 21.1.3 + '@nx/nx-darwin-arm64': 21.2.2 + '@nx/nx-darwin-x64': 21.2.2 + '@nx/nx-freebsd-x64': 21.2.2 + '@nx/nx-linux-arm-gnueabihf': 21.2.2 + '@nx/nx-linux-arm64-gnu': 21.2.2 + '@nx/nx-linux-arm64-musl': 21.2.2 + '@nx/nx-linux-x64-gnu': 21.2.2 + '@nx/nx-linux-x64-musl': 21.2.2 + '@nx/nx-win32-arm64-msvc': 21.2.2 + '@nx/nx-win32-x64-msvc': 21.2.2 transitivePeerDependencies: - debug @@ -7717,9 +7942,9 @@ snapshots: mlly: 1.7.4 pathe: 2.0.3 - postcss@8.5.3: + postcss@8.5.6: dependencies: - nanoid: 3.3.9 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -7727,14 +7952,14 @@ snapshots: premove@4.0.0: {} - prettier-plugin-svelte@3.4.0(prettier@3.5.3)(svelte@5.22.6): + prettier-plugin-svelte@3.4.0(prettier@3.6.2)(svelte@5.22.6): dependencies: - prettier: 3.5.3 + prettier: 3.6.2 svelte: 5.22.6 prettier@2.8.8: {} - prettier@3.5.3: {} + prettier@3.6.2: {} pretty-format@29.7.0: dependencies: @@ -7817,35 +8042,36 @@ snapshots: reusify@1.1.0: {} - rollup-plugin-preserve-directives@0.4.0(rollup@4.35.0): + rollup-plugin-preserve-directives@0.4.0(rollup@4.44.1): dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.35.0) + '@rollup/pluginutils': 5.1.4(rollup@4.44.1) magic-string: 0.30.17 - rollup: 4.35.0 + rollup: 4.44.1 - rollup@4.35.0: + rollup@4.44.1: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.35.0 - '@rollup/rollup-android-arm64': 4.35.0 - '@rollup/rollup-darwin-arm64': 4.35.0 - '@rollup/rollup-darwin-x64': 4.35.0 - '@rollup/rollup-freebsd-arm64': 4.35.0 - '@rollup/rollup-freebsd-x64': 4.35.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.35.0 - '@rollup/rollup-linux-arm-musleabihf': 4.35.0 - '@rollup/rollup-linux-arm64-gnu': 4.35.0 - '@rollup/rollup-linux-arm64-musl': 4.35.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.35.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.35.0 - '@rollup/rollup-linux-riscv64-gnu': 4.35.0 - '@rollup/rollup-linux-s390x-gnu': 4.35.0 - '@rollup/rollup-linux-x64-gnu': 4.35.0 - '@rollup/rollup-linux-x64-musl': 4.35.0 - '@rollup/rollup-win32-arm64-msvc': 4.35.0 - '@rollup/rollup-win32-ia32-msvc': 4.35.0 - '@rollup/rollup-win32-x64-msvc': 4.35.0 + '@rollup/rollup-android-arm-eabi': 4.44.1 + '@rollup/rollup-android-arm64': 4.44.1 + '@rollup/rollup-darwin-arm64': 4.44.1 + '@rollup/rollup-darwin-x64': 4.44.1 + '@rollup/rollup-freebsd-arm64': 4.44.1 + '@rollup/rollup-freebsd-x64': 4.44.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.1 + '@rollup/rollup-linux-arm-musleabihf': 4.44.1 + '@rollup/rollup-linux-arm64-gnu': 4.44.1 + '@rollup/rollup-linux-arm64-musl': 4.44.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.1 + '@rollup/rollup-linux-riscv64-gnu': 4.44.1 + '@rollup/rollup-linux-riscv64-musl': 4.44.1 + '@rollup/rollup-linux-s390x-gnu': 4.44.1 + '@rollup/rollup-linux-x64-gnu': 4.44.1 + '@rollup/rollup-linux-x64-musl': 4.44.1 + '@rollup/rollup-win32-arm64-msvc': 4.44.1 + '@rollup/rollup-win32-ia32-msvc': 4.44.1 + '@rollup/rollup-win32-x64-msvc': 4.44.1 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -7874,8 +8100,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.7.1: {} - semver@7.7.2: {} seroval-plugins@1.3.2(seroval@1.3.2): @@ -7890,32 +8114,32 @@ snapshots: shebang-regex@3.0.0: {} - sherif-darwin-arm64@1.5.0: + sherif-darwin-arm64@1.6.1: optional: true - sherif-darwin-x64@1.5.0: + sherif-darwin-x64@1.6.1: optional: true - sherif-linux-arm64@1.5.0: + sherif-linux-arm64@1.6.1: optional: true - sherif-linux-x64@1.5.0: + sherif-linux-x64@1.6.1: optional: true - sherif-windows-arm64@1.5.0: + sherif-windows-arm64@1.6.1: optional: true - sherif-windows-x64@1.5.0: + sherif-windows-x64@1.6.1: optional: true - sherif@1.5.0: + sherif@1.6.1: optionalDependencies: - sherif-darwin-arm64: 1.5.0 - sherif-darwin-x64: 1.5.0 - sherif-linux-arm64: 1.5.0 - sherif-linux-x64: 1.5.0 - sherif-windows-arm64: 1.5.0 - sherif-windows-x64: 1.5.0 + sherif-darwin-arm64: 1.6.1 + sherif-darwin-x64: 1.6.1 + sherif-linux-arm64: 1.6.1 + sherif-linux-x64: 1.6.1 + sherif-windows-arm64: 1.6.1 + sherif-windows-x64: 1.6.1 siginfo@2.0.0: {} @@ -7923,11 +8147,11 @@ snapshots: signal-exit@4.1.0: {} - simple-git@3.27.0: + simple-git@3.28.0: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color @@ -8007,6 +8231,10 @@ snapshots: strip-json-comments@5.0.2: {} + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8061,17 +8289,12 @@ snapshots: fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 - tinyglobby@0.2.13: - dependencies: - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - tinyglobby@0.2.14: dependencies: - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 - tinypool@1.1.0: {} + tinypool@1.1.1: {} tinyrainbow@2.0.0: {} @@ -8105,10 +8328,6 @@ snapshots: tree-kill@1.2.2: {} - ts-api-utils@2.0.1(typescript@5.8.3): - dependencies: - typescript: 5.8.3 - ts-api-utils@2.1.0(typescript@5.8.3): dependencies: typescript: 5.8.3 @@ -8162,12 +8381,12 @@ snapshots: typescript: 5.8.3 yaml: 2.7.0 - typescript-eslint@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): + typescript-eslint@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.28.0(jiti@2.4.2) + '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -8180,7 +8399,7 @@ snapshots: ufo@1.5.4: {} - undici-types@6.21.0: {} + undici-types@7.8.0: {} undici@6.21.3: {} @@ -8228,13 +8447,13 @@ snapshots: validate-html-nesting@1.2.2: {} - vite-node@3.2.2(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): + vite-node@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - jiti @@ -8249,84 +8468,84 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.2.3(@types/node@22.15.30)(rollup@4.35.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-dts@4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: - '@microsoft/api-extractor': 7.47.7(@types/node@22.15.30) - '@rollup/pluginutils': 5.1.4(rollup@4.35.0) + '@microsoft/api-extractor': 7.47.7(@types/node@24.0.10) + '@rollup/pluginutils': 5.1.4(rollup@4.44.1) '@volar/typescript': 2.4.12 '@vue/language-core': 2.1.6(typescript@5.8.3) compare-versions: 6.1.1 - debug: 4.4.0 + debug: 4.4.1 kolorist: 1.8.0 local-pkg: 0.5.1 magic-string: 0.30.17 typescript: 5.8.3 optionalDependencies: - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-externalize-deps@0.9.0(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-externalize-deps@0.9.0(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vite-plugin-solid@2.11.6(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-solid@2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: - '@babel/core': 7.26.9 + '@babel/core': 7.26.10 '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.5(@babel/core@7.26.9) + babel-preset-solid: 1.9.5(@babel/core@7.26.10) merge-anything: 5.1.7 solid-js: 1.9.7 solid-refresh: 0.6.3(solid-js@1.9.7) - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vitefu: 1.0.6(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) optionalDependencies: '@testing-library/jest-dom': 6.6.3 transitivePeerDependencies: - supports-color - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: - debug: 4.4.0 + debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.8.3) optionalDependencies: - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color - typescript - vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): + vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: esbuild: 0.25.0 - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 - postcss: 8.5.3 - rollup: 4.35.0 - tinyglobby: 0.2.13 + postcss: 8.5.6 + rollup: 4.44.1 + tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 22.15.30 + '@types/node': 24.0.10 fsevents: 2.3.3 jiti: 2.4.2 tsx: 4.19.3 yaml: 2.7.0 - vitefu@1.0.6(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vitefu@1.0.6(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): optionalDependencies: - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vitest@3.2.2(@types/node@22.15.30)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0): + vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 - '@vitest/expect': 3.2.2 - '@vitest/mocker': 3.2.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - '@vitest/pretty-format': 3.2.2 - '@vitest/runner': 3.2.2 - '@vitest/snapshot': 3.2.2 - '@vitest/spy': 3.2.2 - '@vitest/utils': 3.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 debug: 4.4.1 expect-type: 1.2.1 @@ -8337,13 +8556,13 @@ snapshots: tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 - tinypool: 1.1.0 + tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vite-node: 3.2.2(@types/node@22.15.30)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.1(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite-node: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.15.30 + '@types/node': 24.0.10 jsdom: 26.1.0 transitivePeerDependencies: - jiti @@ -8361,16 +8580,16 @@ snapshots: vscode-uri@3.1.0: {} - vue-eslint-parser@10.1.3(eslint@9.28.0(jiti@2.4.2)): + vue-eslint-parser@10.1.3(eslint@9.30.1(jiti@2.4.2)): dependencies: - debug: 4.4.0 - eslint: 9.28.0(jiti@2.4.2) + debug: 4.4.1 + eslint: 9.30.1(jiti@2.4.2) eslint-scope: 8.3.0 eslint-visitor-keys: 4.2.0 espree: 10.3.0 esquery: 1.6.0 lodash: 4.17.21 - semver: 7.7.1 + semver: 7.7.2 transitivePeerDependencies: - supports-color @@ -8457,6 +8676,12 @@ snapshots: dependencies: zod: 3.24.2 + zod-validation-error@3.4.0(zod@3.25.56): + dependencies: + zod: 3.25.56 + zod@3.24.2: {} zod@3.25.56: {} + + zod@3.25.72: {} From e6433732015946f6a80be1d6d32f5926a144d557 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 04:28:01 +0000 Subject: [PATCH 24/51] ci: apply automated fixes --- .../functions/createasyncdebouncer.md | 2 +- .../functions/createdebouncedsignal.md | 15 +++++++++++--- .../functions/createdebouncedvalue.md | 15 +++++++++++--- .../reference/functions/createdebouncer.md | 15 +++++++++++--- .../interfaces/solidasyncdebouncer.md | 4 ++-- .../reference/interfaces/soliddebouncer.md | 20 +++++++++++-------- 6 files changed, 51 insertions(+), 20 deletions(-) diff --git a/docs/framework/solid/reference/functions/createasyncdebouncer.md b/docs/framework/solid/reference/functions/createasyncdebouncer.md index 2d661e504..4bf8a2972 100644 --- a/docs/framework/solid/reference/functions/createasyncdebouncer.md +++ b/docs/framework/solid/reference/functions/createasyncdebouncer.md @@ -14,7 +14,7 @@ function createAsyncDebouncer( selector?): SolidAsyncDebouncer ``` -Defined in: [async-debouncer/createAsyncDebouncer.ts:75](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L75) +Defined in: [async-debouncer/createAsyncDebouncer.ts:76](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L76) A low-level Solid hook that creates an `AsyncDebouncer` instance to delay execution of an async function. diff --git a/docs/framework/solid/reference/functions/createdebouncedsignal.md b/docs/framework/solid/reference/functions/createdebouncedsignal.md index 3f090f46b..5e9b55c27 100644 --- a/docs/framework/solid/reference/functions/createdebouncedsignal.md +++ b/docs/framework/solid/reference/functions/createdebouncedsignal.md @@ -8,10 +8,13 @@ title: createDebouncedSignal # Function: createDebouncedSignal() ```ts -function createDebouncedSignal(value, initialOptions): [Accessor, Setter, SolidDebouncer>] +function createDebouncedSignal( + value, + initialOptions, + selector?): [Accessor, Setter, SolidDebouncer, TSelected>] ``` -Defined in: [debouncer/createDebouncedSignal.ts:46](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncedSignal.ts#L46) +Defined in: [debouncer/createDebouncedSignal.ts:49](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncedSignal.ts#L49) A Solid hook that creates a debounced state value, combining Solid's createSignal with debouncing functionality. This hook provides both the current debounced value and methods to update it. @@ -30,6 +33,8 @@ The hook returns a tuple containing: • **TValue** +• **TSelected** = `DebouncerState`\<`Setter`\<`TValue`\>\> + ## Parameters ### value @@ -40,9 +45,13 @@ The hook returns a tuple containing: `DebouncerOptions`\<`Setter`\<`TValue`\>\> +### selector? + +(`state`) => `TSelected` + ## Returns -\[`Accessor`\<`TValue`\>, `Setter`\<`TValue`\>, [`SolidDebouncer`](../../interfaces/soliddebouncer.md)\<`Setter`\<`TValue`\>\>\] +\[`Accessor`\<`TValue`\>, `Setter`\<`TValue`\>, [`SolidDebouncer`](../../interfaces/soliddebouncer.md)\<`Setter`\<`TValue`\>, `TSelected`\>\] ## Example diff --git a/docs/framework/solid/reference/functions/createdebouncedvalue.md b/docs/framework/solid/reference/functions/createdebouncedvalue.md index 3316e6caa..910863176 100644 --- a/docs/framework/solid/reference/functions/createdebouncedvalue.md +++ b/docs/framework/solid/reference/functions/createdebouncedvalue.md @@ -8,10 +8,13 @@ title: createDebouncedValue # Function: createDebouncedValue() ```ts -function createDebouncedValue(value, initialOptions): [Accessor, SolidDebouncer>] +function createDebouncedValue( + value, + initialOptions, + selector?): [Accessor, SolidDebouncer, TSelected>] ``` -Defined in: [debouncer/createDebouncedValue.ts:41](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncedValue.ts#L41) +Defined in: [debouncer/createDebouncedValue.ts:44](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncedValue.ts#L44) A Solid hook that creates a debounced value that updates only after a specified delay. Unlike createDebouncedSignal, this hook automatically tracks changes to the input value @@ -33,6 +36,8 @@ The hook returns a tuple containing: • **TValue** +• **TSelected** = `DebouncerState`\<`Setter`\<`TValue`\>\> + ## Parameters ### value @@ -43,9 +48,13 @@ The hook returns a tuple containing: `DebouncerOptions`\<`Setter`\<`TValue`\>\> +### selector? + +(`state`) => `TSelected` + ## Returns -\[`Accessor`\<`TValue`\>, [`SolidDebouncer`](../../interfaces/soliddebouncer.md)\<`Setter`\<`TValue`\>\>\] +\[`Accessor`\<`TValue`\>, [`SolidDebouncer`](../../interfaces/soliddebouncer.md)\<`Setter`\<`TValue`\>, `TSelected`\>\] ## Example diff --git a/docs/framework/solid/reference/functions/createdebouncer.md b/docs/framework/solid/reference/functions/createdebouncer.md index e45056c76..9404d6adc 100644 --- a/docs/framework/solid/reference/functions/createdebouncer.md +++ b/docs/framework/solid/reference/functions/createdebouncer.md @@ -8,10 +8,13 @@ title: createDebouncer # Function: createDebouncer() ```ts -function createDebouncer(fn, initialOptions): SolidDebouncer +function createDebouncer( + fn, + initialOptions, +selector?): SolidDebouncer ``` -Defined in: [debouncer/createDebouncer.ts:55](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L55) +Defined in: [debouncer/createDebouncer.ts:59](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L59) A Solid hook that creates and manages a Debouncer instance. @@ -31,6 +34,8 @@ timer resets and starts waiting again. • **TFn** *extends* `AnyFunction` +• **TSelected** = `DebouncerState`\<`TFn`\> + ## Parameters ### fn @@ -41,9 +46,13 @@ timer resets and starts waiting again. `DebouncerOptions`\<`TFn`\> +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidDebouncer`](../../interfaces/soliddebouncer.md)\<`TFn`\> +[`SolidDebouncer`](../../interfaces/soliddebouncer.md)\<`TFn`, `TSelected`\> ## Example diff --git a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md index f188e80af..1fe93f678 100644 --- a/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md +++ b/docs/framework/solid/reference/interfaces/solidasyncdebouncer.md @@ -7,7 +7,7 @@ title: SolidAsyncDebouncer # Interface: SolidAsyncDebouncer\ -Defined in: [async-debouncer/createAsyncDebouncer.ts:10](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L10) +Defined in: [async-debouncer/createAsyncDebouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L11) ## Extends @@ -27,7 +27,7 @@ Defined in: [async-debouncer/createAsyncDebouncer.ts:10](https://github.com/TanS state: Accessor; ``` -Defined in: [async-debouncer/createAsyncDebouncer.ts:19](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L19) +Defined in: [async-debouncer/createAsyncDebouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L20) Reactive state that will be updated when the debouncer state changes diff --git a/docs/framework/solid/reference/interfaces/soliddebouncer.md b/docs/framework/solid/reference/interfaces/soliddebouncer.md index ca489cb3c..61c5b5a0d 100644 --- a/docs/framework/solid/reference/interfaces/soliddebouncer.md +++ b/docs/framework/solid/reference/interfaces/soliddebouncer.md @@ -5,26 +5,30 @@ title: SolidDebouncer -# Interface: SolidDebouncer\ +# Interface: SolidDebouncer\ -Defined in: [debouncer/createDebouncer.ts:14](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L14) - -An extension of the Debouncer class that adds Solid signals to access the internal state of the debouncer +Defined in: [debouncer/createDebouncer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L11) ## Extends -- `Omit`\<`Debouncer`\<`TFn`\>, `"getState"`\> +- `Omit`\<`Debouncer`\<`TFn`\>, `"store"`\> ## Type Parameters • **TFn** *extends* `AnyFunction` +• **TSelected** = `DebouncerState`\<`TFn`\> + ## Properties -### store +### state ```ts -store: DebouncerState; +state: Accessor; ``` -Defined in: [debouncer/createDebouncer.ts:16](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L16) +Defined in: [debouncer/createDebouncer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L20) + +Reactive state that will be updated when the debouncer state changes + +Use this instead of `debouncer.store.state` From 9812d2d908c71e1caf536e9bc361e151edf8ed69 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 6 Jul 2025 12:02:54 -0500 Subject: [PATCH 25/51] update throttler utils to tanstack store --- .../react/useThrottledState/src/index.tsx | 10 +- .../react/useThrottledValue/src/index.tsx | 6 +- examples/react/useThrottler/src/index.tsx | 10 +- .../solid/createThrottledSignal/src/index.tsx | 13 +- .../solid/createThrottledValue/src/index.tsx | 9 +- examples/solid/createThrottler/src/index.tsx | 11 +- packages/pacer/src/async-debouncer.ts | 4 +- packages/pacer/src/debouncer.ts | 11 +- packages/pacer/src/throttler.ts | 124 +++++++----------- packages/pacer/tests/debouncer.test.ts | 18 --- packages/pacer/tests/throttler.test.ts | 12 +- .../src/throttler/useThrottledCallback.ts | 17 ++- .../src/throttler/useThrottledState.ts | 18 ++- .../src/throttler/useThrottledValue.ts | 20 ++- .../react-pacer/src/throttler/useThrottler.ts | 46 +++++-- .../src/debouncer/createDebouncer.ts | 2 +- .../src/throttler/createThrottledSignal.ts | 19 ++- .../src/throttler/createThrottledValue.ts | 14 +- .../src/throttler/createThrottler.ts | 57 ++++---- 19 files changed, 220 insertions(+), 201 deletions(-) diff --git a/examples/react/useThrottledState/src/index.tsx b/examples/react/useThrottledState/src/index.tsx index b1b0de0df..c28acd411 100644 --- a/examples/react/useThrottledState/src/index.tsx +++ b/examples/react/useThrottledState/src/index.tsx @@ -31,7 +31,7 @@ function App1() { - + @@ -85,7 +85,7 @@ function App2() { - + @@ -159,15 +159,15 @@ function App3() { - + - + - + @@ -83,7 +83,7 @@ function App2() { - + @@ -161,16 +161,16 @@ function App3() { - + - + @@ -84,7 +84,7 @@ function App2() { - + @@ -159,11 +159,13 @@ function App3() { - + - + @@ -171,7 +173,8 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - throttler.executionCount()) / + ((instantExecutionCount() - + throttler.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/examples/solid/createThrottledValue/src/index.tsx b/examples/solid/createThrottledValue/src/index.tsx index a10bf258d..56c8cb3c5 100644 --- a/examples/solid/createThrottledValue/src/index.tsx +++ b/examples/solid/createThrottledValue/src/index.tsx @@ -133,11 +133,13 @@ function App3() { - + - + @@ -145,7 +147,8 @@ function App3() { {instantExecutionCount() === 0 ? '0' : Math.round( - ((instantExecutionCount() - throttler.executionCount()) / + ((instantExecutionCount() - + throttler.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/examples/solid/createThrottler/src/index.tsx b/examples/solid/createThrottler/src/index.tsx index f57d0a41e..0e5170f3d 100644 --- a/examples/solid/createThrottler/src/index.tsx +++ b/examples/solid/createThrottler/src/index.tsx @@ -29,7 +29,7 @@ function App1() { - + @@ -82,7 +82,7 @@ function App2() { - + @@ -155,12 +155,13 @@ function App3() { - + @@ -170,7 +171,7 @@ function App3() { ? '0' : Math.round( ((instantExecutionCount() - - setValueThrottler.store.executionCount) / + setValueThrottler.state().executionCount) / instantExecutionCount()) * 100, )} diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index df9447d6a..15a6c4984 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -168,9 +168,9 @@ export class AsyncDebouncer { setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } - // End the pending state if the debouncer is disabled + // Cancel pending execution if the debouncer is disabled if (!this.#getEnabled()) { - this.#setState({ isPending: false }) + this.cancel() } } diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index b626986ce..2209f11c0 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -46,13 +46,6 @@ export interface DebouncerOptions { * Callback function that is called after the function is executed */ onExecute?: (debouncer: Debouncer) => void - /** - * Callback function that is called when the state of the debouncer is updated - */ - onStateChange?: ( - state: DebouncerState, - debouncer: Debouncer, - ) => void /** * Whether to execute on the trailing edge of the timeout. * Defaults to true. @@ -129,9 +122,9 @@ export class Debouncer { setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } - // End the pending state if the debouncer is disabled + // Cancel pending execution if the debouncer is disabled if (!this.#getEnabled()) { - this.#setState({ isPending: false }) + this.cancel() } } diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index 8a55a6a1a..7f76ce009 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -1,3 +1,4 @@ +import { Store } from '@tanstack/store' import { parseFunctionOrValue } from './utils' import type { AnyFunction } from './types' @@ -7,6 +8,20 @@ export interface ThrottlerState { lastExecutionTime: number nextExecutionTime: number isPending: boolean + status: 'idle' | 'pending' +} + +function getDefaultThrottlerState< + TFn extends AnyFunction, +>(): ThrottlerState { + return structuredClone({ + executionCount: 0, + isPending: false, + lastArgs: undefined, + lastExecutionTime: 0, + nextExecutionTime: 0, + status: 'idle', + }) } /** @@ -32,13 +47,6 @@ export interface ThrottlerOptions { * Callback function that is called after the function is executed */ onExecute?: (throttler: Throttler) => void - /** - * Callback function that is called when the state of the throttler is updated - */ - onStateChange?: ( - state: ThrottlerState, - throttler: Throttler, - ) => void /** * Whether to execute on the trailing edge of the timeout. * Defaults to true. @@ -96,14 +104,10 @@ const defaultOptions: Omit< * ``` */ export class Throttler { + readonly store: Store> = new Store( + getDefaultThrottlerState(), + ) #options: ThrottlerOptions - #state: ThrottlerState = { - isPending: false, - executionCount: 0, - lastArgs: undefined, - lastExecutionTime: 0, - nextExecutionTime: 0, - } #timeoutId: NodeJS.Timeout | undefined constructor( @@ -114,10 +118,7 @@ export class Throttler { ...defaultOptions, ...initialOptions, } - this.#state = { - ...this.#state, - ...this.#options.initialState, - } + this.#setState(this.#options.initialState ?? {}) } /** @@ -126,39 +127,31 @@ export class Throttler { setOptions = (newOptions: Partial>): void => { this.#options = { ...this.#options, ...newOptions } - // End the pending state if the throttler is disabled - if (!this.getEnabled()) { + // Cancel pending execution if the throttler is disabled + if (!this.#getEnabled()) { this.cancel() } } - /** - * Returns the current throttler options - */ - getOptions = (): Required> => { - return this.#options as Required> - } - - getState = (): ThrottlerState => { - return { ...this.#state } - } - #setState = (newState: Partial>): void => { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } + const { isPending } = combinedState + return { + ...combinedState, + status: isPending ? 'pending' : 'idle', + } + }) } - /** - * Returns the current enabled state of the throttler - */ - getEnabled = (): boolean => { + #getEnabled = (): boolean => { return !!parseFunctionOrValue(this.#options.enabled, this) } - /** - * Returns the current wait time in milliseconds - */ - getWait = (): number => { + #getWait = (): number => { return parseFunctionOrValue(this.#options.wait, this) } @@ -186,8 +179,8 @@ export class Throttler { */ maybeExecute = (...args: Parameters): void => { const now = Date.now() - const timeSinceLastExecution = now - this.#state.lastExecutionTime - const wait = this.getWait() + const timeSinceLastExecution = now - this.store.state.lastExecutionTime + const wait = this.#getWait() // Handle leading execution if (this.#options.leading && timeSinceLastExecution >= wait) { @@ -199,13 +192,16 @@ export class Throttler { }) // Set up trailing execution if not already scheduled if (!this.#timeoutId && this.#options.trailing) { - const _timeSinceLastExecution = this.#state.lastExecutionTime - ? now - this.#state.lastExecutionTime + // prevent large number if lastExecutionTime is undefined + const _timeSinceLastExecution = this.store.state.lastExecutionTime + ? now - this.store.state.lastExecutionTime : 0 const timeoutDuration = wait - _timeSinceLastExecution + this.#setState({ isPending: true }) this.#timeoutId = setTimeout(() => { - if (this.#state.lastArgs !== undefined) { - this.#execute(...this.#state.lastArgs) + const { lastArgs } = this.store.state + if (lastArgs !== undefined) { + this.#execute(...lastArgs) } }, timeoutDuration) } @@ -213,14 +209,13 @@ export class Throttler { } #execute = (...args: Parameters): void => { - if (!this.getEnabled()) return - this.#setState({ isPending: true }) + if (!this.#getEnabled()) return this.fn(...args) // EXECUTE! const lastExecutionTime = Date.now() - const nextExecutionTime = lastExecutionTime + this.getWait() + const nextExecutionTime = lastExecutionTime + this.#getWait() this.#timeoutId = undefined this.#setState({ - executionCount: this.#state.executionCount + 1, + executionCount: this.store.state.executionCount + 1, lastExecutionTime, nextExecutionTime, isPending: false, @@ -250,31 +245,10 @@ export class Throttler { } /** - * Returns the last execution time - */ - getLastExecutionTime = (): number => { - return this.#state.lastExecutionTime - } - - /** - * Returns the next execution time - */ - getNextExecutionTime = (): number => { - return this.#state.nextExecutionTime - } - - /** - * Returns the number of times the function has been executed - */ - getExecutionCount = (): number => { - return this.#state.executionCount - } - - /** - * Returns `true` if there is a pending execution + * Resets the throttler state to its default values */ - getIsPending = (): boolean => { - return this.#state.isPending + reset = (): void => { + this.#setState(getDefaultThrottlerState()) } } diff --git a/packages/pacer/tests/debouncer.test.ts b/packages/pacer/tests/debouncer.test.ts index fc0be8012..5f6cccb05 100644 --- a/packages/pacer/tests/debouncer.test.ts +++ b/packages/pacer/tests/debouncer.test.ts @@ -632,24 +632,6 @@ describe('Debouncer', () => { vi.advanceTimersByTime(1000) expect(mockFn).toBeCalledTimes(1) }) - - it('should handle rapid enable/disable cycles', () => { - const mockFn = vi.fn() - const debouncer = new Debouncer(mockFn, { wait: 1000 }) - - // Start execution - debouncer.maybeExecute() - - // Rapidly enable/disable - debouncer.setOptions({ enabled: false }) - debouncer.setOptions({ enabled: true }) - debouncer.setOptions({ enabled: false }) - debouncer.setOptions({ enabled: true }) - - // Should execute if last state was enabled - vi.advanceTimersByTime(1000) - expect(mockFn).toBeCalledTimes(1) - }) }) }) diff --git a/packages/pacer/tests/throttler.test.ts b/packages/pacer/tests/throttler.test.ts index 6d266bac0..d0b06aa95 100644 --- a/packages/pacer/tests/throttler.test.ts +++ b/packages/pacer/tests/throttler.test.ts @@ -89,13 +89,13 @@ describe('Throttler', () => { const throttler = new Throttler(mockFn, { wait: 100 }) throttler.maybeExecute() - expect(throttler.getExecutionCount()).toBe(1) + expect(throttler.store.state.executionCount).toBe(1) throttler.maybeExecute() - expect(throttler.getExecutionCount()).toBe(1) + expect(throttler.store.state.executionCount).toBe(1) vi.advanceTimersByTime(100) - expect(throttler.getExecutionCount()).toBe(2) + expect(throttler.store.state.executionCount).toBe(2) }) it('should cancel pending trailing execution', () => { @@ -146,17 +146,17 @@ describe('Throttler', () => { const throttler = new Throttler(mockFn, { wait: 100 }) // Initial state - expect(throttler.getLastExecutionTime()).toBe(0) + expect(throttler.store.state.lastExecutionTime).toBe(0) // First execution throttler.maybeExecute('first') - const firstExecutionTime = throttler.getLastExecutionTime() + const firstExecutionTime = throttler.store.state.lastExecutionTime expect(firstExecutionTime).toBeGreaterThan(0) // Wait and execute again vi.advanceTimersByTime(100) throttler.maybeExecute('second') - const secondExecutionTime = throttler.getLastExecutionTime() + const secondExecutionTime = throttler.store.state.lastExecutionTime expect(secondExecutionTime).toBeGreaterThan(firstExecutionTime) }) diff --git a/packages/react-pacer/src/throttler/useThrottledCallback.ts b/packages/react-pacer/src/throttler/useThrottledCallback.ts index d0395ea76..e75f76f29 100644 --- a/packages/react-pacer/src/throttler/useThrottledCallback.ts +++ b/packages/react-pacer/src/throttler/useThrottledCallback.ts @@ -1,6 +1,9 @@ import { useCallback } from 'react' import { useThrottler } from './useThrottler' -import type { ThrottlerOptions } from '@tanstack/pacer/throttler' +import type { + ThrottlerOptions, + ThrottlerState, +} from '@tanstack/pacer/throttler' import type { AnyFunction } from '@tanstack/pacer/types' /** @@ -42,8 +45,12 @@ import type { AnyFunction } from '@tanstack/pacer/types' */ export function useThrottledCallback< TFn extends AnyFunction, - TArgs extends Parameters, ->(fn: TFn, options: ThrottlerOptions) { - const throttledFn = useThrottler(fn, options).maybeExecute - return useCallback((...args: TArgs) => throttledFn(...args), [throttledFn]) + TSelected = ThrottlerState, +>( + fn: TFn, + options: ThrottlerOptions, + selector?: (state: ThrottlerState) => TSelected, +): (...args: Parameters) => void { + const throttledFn = useThrottler(fn, options, selector).maybeExecute + return useCallback((...args) => throttledFn(...args), [throttledFn]) } diff --git a/packages/react-pacer/src/throttler/useThrottledState.ts b/packages/react-pacer/src/throttler/useThrottledState.ts index 78d0e6b39..ad9c6f716 100644 --- a/packages/react-pacer/src/throttler/useThrottledState.ts +++ b/packages/react-pacer/src/throttler/useThrottledState.ts @@ -1,6 +1,10 @@ import { useState } from 'react' import { useThrottler } from './useThrottler' -import type { Throttler, ThrottlerOptions } from '@tanstack/pacer/throttler' +import type { ReactThrottler } from './useThrottler' +import type { + ThrottlerOptions, + ThrottlerState, +} from '@tanstack/pacer/throttler' /** * A React hook that creates a throttled state value that updates at most once within a specified time window. @@ -37,15 +41,21 @@ import type { Throttler, ThrottlerOptions } from '@tanstack/pacer/throttler' * ``` */ -export function useThrottledState( +export function useThrottledState< + TValue, + TSelected = ThrottlerState>>, +>( value: TValue, options: ThrottlerOptions>>, + selector?: ( + state: ThrottlerState>>, + ) => TSelected, ): [ TValue, React.Dispatch>, - Throttler>>, + ReactThrottler>, TSelected>, ] { const [throttledValue, setThrottledValue] = useState(value) - const throttler = useThrottler(setThrottledValue, options) + const throttler = useThrottler(setThrottledValue, options, selector) return [throttledValue, throttler.maybeExecute, throttler] } diff --git a/packages/react-pacer/src/throttler/useThrottledValue.ts b/packages/react-pacer/src/throttler/useThrottledValue.ts index 30f6be8ab..72694b6cf 100644 --- a/packages/react-pacer/src/throttler/useThrottledValue.ts +++ b/packages/react-pacer/src/throttler/useThrottledValue.ts @@ -1,6 +1,10 @@ import { useEffect } from 'react' import { useThrottledState } from './useThrottledState' -import type { Throttler, ThrottlerOptions } from '@tanstack/pacer/throttler' +import type { ReactThrottler } from './useThrottler' +import type { + ThrottlerOptions, + ThrottlerState, +} from '@tanstack/pacer/throttler' /** * A high-level React hook that creates a throttled version of a value that updates at most once within a specified time window. @@ -29,13 +33,23 @@ import type { Throttler, ThrottlerOptions } from '@tanstack/pacer/throttler' * }); * ``` */ -export function useThrottledValue( +export function useThrottledValue< + TValue, + TSelected = ThrottlerState>>, +>( value: TValue, options: ThrottlerOptions>>, -): [TValue, Throttler>>] { + selector?: ( + state: ThrottlerState>>, + ) => TSelected, +): [ + TValue, + ReactThrottler>, TSelected>, +] { const [throttledValue, setThrottledValue, throttler] = useThrottledState( value, options, + selector, ) useEffect(() => { diff --git a/packages/react-pacer/src/throttler/useThrottler.ts b/packages/react-pacer/src/throttler/useThrottler.ts index a634e6429..b9717f6de 100644 --- a/packages/react-pacer/src/throttler/useThrottler.ts +++ b/packages/react-pacer/src/throttler/useThrottler.ts @@ -1,7 +1,23 @@ -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { Throttler } from '@tanstack/pacer/throttler' +import { useStore } from '@tanstack/react-store' import type { AnyFunction } from '@tanstack/pacer/types' -import type { ThrottlerOptions } from '@tanstack/pacer/throttler' +import type { + ThrottlerOptions, + ThrottlerState, +} from '@tanstack/pacer/throttler' + +export interface ReactThrottler< + TFn extends AnyFunction, + TSelected = ThrottlerState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the throttler state changes + * + * Use this instead of `throttler.store.state` + */ + state: TSelected +} /** * A low-level React hook that creates a `Throttler` instance that limits how often the provided function can execute. @@ -20,13 +36,6 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler' * const [value, setValue] = useState(0); * const throttler = useThrottler(setValue, { wait: 1000 }); * - * // With Redux - * const dispatch = useDispatch(); - * const throttler = useThrottler( - * (value) => dispatch(updateAction(value)), - * { wait: 1000 } - * ); - * * // With any state manager * const throttler = useThrottler( * (value) => stateManager.setState(value), @@ -38,12 +47,18 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler' * ); * ``` */ -export function useThrottler( +export function useThrottler< + TFn extends AnyFunction, + TSelected = ThrottlerState, +>( fn: TFn, options: ThrottlerOptions, -): Throttler { + selector?: (state: ThrottlerState) => TSelected, +): ReactThrottler { const [throttler] = useState(() => new Throttler(fn, options)) + const state = useStore(throttler.store, selector) + throttler.setOptions(options) useEffect(() => { @@ -52,5 +67,12 @@ export function useThrottler( } }, [throttler]) - return throttler + return useMemo( + () => + ({ + ...throttler, + state, + }) as ReactThrottler, // omit `store` in favor of `state` + [throttler, state], + ) } diff --git a/packages/solid-pacer/src/debouncer/createDebouncer.ts b/packages/solid-pacer/src/debouncer/createDebouncer.ts index e07352be8..5d59614eb 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncer.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncer.ts @@ -77,5 +77,5 @@ export function createDebouncer< return { ...asyncDebouncer, state, - } as unknown as SolidDebouncer // omit `store` in favor of `state` + } as SolidDebouncer // omit `store` in favor of `state` } diff --git a/packages/solid-pacer/src/throttler/createThrottledSignal.ts b/packages/solid-pacer/src/throttler/createThrottledSignal.ts index f77916ee8..3dd95c805 100644 --- a/packages/solid-pacer/src/throttler/createThrottledSignal.ts +++ b/packages/solid-pacer/src/throttler/createThrottledSignal.ts @@ -2,7 +2,10 @@ import { createSignal } from 'solid-js' import { createThrottler } from './createThrottler' import type { SolidThrottler } from './createThrottler' import type { Accessor, Setter } from 'solid-js' -import type { ThrottlerOptions } from '@tanstack/pacer/throttler' +import type { + ThrottlerOptions, + ThrottlerState, +} from '@tanstack/pacer/throttler' /** * A Solid hook that creates a throttled state value that updates at most once within a specified time window. @@ -38,11 +41,19 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler' * console.log('Next execution:', throttler.nextExecutionTime()); * ``` */ -export function createThrottledSignal( +export function createThrottledSignal< + TValue, + TSelected = ThrottlerState>, +>( value: TValue, initialOptions: ThrottlerOptions>, -): [Accessor, Setter, SolidThrottler>] { + selector?: (state: ThrottlerState>) => TSelected, +): [ + Accessor, + Setter, + SolidThrottler, TSelected>, +] { const [throttledValue, setThrottledValue] = createSignal(value) - const throttler = createThrottler(setThrottledValue, initialOptions) + const throttler = createThrottler(setThrottledValue, initialOptions, selector) return [throttledValue, throttler.maybeExecute as Setter, throttler] } diff --git a/packages/solid-pacer/src/throttler/createThrottledValue.ts b/packages/solid-pacer/src/throttler/createThrottledValue.ts index eb17e01cd..6138f459d 100644 --- a/packages/solid-pacer/src/throttler/createThrottledValue.ts +++ b/packages/solid-pacer/src/throttler/createThrottledValue.ts @@ -2,7 +2,10 @@ import { createEffect } from 'solid-js' import { createThrottledSignal } from './createThrottledSignal' import type { SolidThrottler } from './createThrottler' import type { Accessor, Setter } from 'solid-js' -import type { ThrottlerOptions } from '@tanstack/pacer/throttler' +import type { + ThrottlerOptions, + ThrottlerState, +} from '@tanstack/pacer/throttler' /** * A high-level Solid hook that creates a throttled version of a value that updates at most once within a specified time window. @@ -32,13 +35,18 @@ import type { ThrottlerOptions } from '@tanstack/pacer/throttler' * throttler.cancel(); // Cancel any pending updates * ``` */ -export function createThrottledValue( +export function createThrottledValue< + TValue, + TSelected = ThrottlerState>, +>( value: Accessor, initialOptions: ThrottlerOptions>, -): [Accessor, SolidThrottler>] { + selector?: (state: ThrottlerState>) => TSelected, +): [Accessor, SolidThrottler, TSelected>] { const [throttledValue, setThrottledValue, throttler] = createThrottledSignal( value(), initialOptions, + selector, ) createEffect(() => { diff --git a/packages/solid-pacer/src/throttler/createThrottler.ts b/packages/solid-pacer/src/throttler/createThrottler.ts index 5ac30e194..d30826d8a 100644 --- a/packages/solid-pacer/src/throttler/createThrottler.ts +++ b/packages/solid-pacer/src/throttler/createThrottler.ts @@ -1,19 +1,23 @@ import { Throttler } from '@tanstack/pacer/throttler' import { createEffect, onCleanup } from 'solid-js' -import { createStore } from 'solid-js/store' -import type { Store } from 'solid-js/store' +import { useStore } from '@tanstack/solid-store' +import type { Accessor } from 'solid-js' import type { AnyFunction } from '@tanstack/pacer/types' import type { ThrottlerOptions, ThrottlerState, } from '@tanstack/pacer/throttler' -/** - * An extension of the Throttler class that adds Solid signals to access the internal state of the throttler - */ -export interface SolidThrottler - extends Omit, 'getState'> { - store: Store> +export interface SolidThrottler< + TFn extends AnyFunction, + TSelected = ThrottlerState, +> extends Omit, 'store'> { + /** + * Reactive state that will be updated when the throttler state changes + * + * Use this instead of `throttler.store.state` + */ + state: Accessor } /** @@ -50,39 +54,26 @@ export interface SolidThrottler * console.log(throttler.nextExecutionTime()); // timestamp of next allowed execution * ``` */ -export function createThrottler( +export function createThrottler< + TFn extends AnyFunction, + TSelected = ThrottlerState, +>( fn: TFn, initialOptions: ThrottlerOptions, -): SolidThrottler { - const throttler = new Throttler(fn, initialOptions) - const [store, setStore] = createStore>( - throttler.getState(), - ) - - function setOptions(newOptions: Partial>) { - throttler.setOptions({ - ...newOptions, - onStateChange: (state, throttler) => { - setStore(state) - - const onStateChange = - newOptions.onStateChange ?? initialOptions.onStateChange - onStateChange?.(state, throttler) - }, - }) - } + selector?: (state: ThrottlerState) => TSelected, +): SolidThrottler { + const asyncThrottler = new Throttler(fn, initialOptions) - setOptions(initialOptions) + const state = useStore(asyncThrottler.store, selector) createEffect(() => { onCleanup(() => { - throttler.cancel() + asyncThrottler.cancel() }) }) return { - ...throttler, - store, - setOptions, - } as SolidThrottler + ...asyncThrottler, + state, + } as SolidThrottler // omit `store` in favor of `state` } From 5f3732e9296e2b05d13d9cda535d36c89a3d50bc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 6 Jul 2025 17:03:52 +0000 Subject: [PATCH 26/51] ci: apply automated fixes --- .../functions/createthrottledsignal.md | 15 +++++++++++--- .../functions/createthrottledvalue.md | 15 +++++++++++--- .../reference/functions/createthrottler.md | 15 +++++++++++--- .../reference/interfaces/solidthrottler.md | 20 +++++++++++-------- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/docs/framework/solid/reference/functions/createthrottledsignal.md b/docs/framework/solid/reference/functions/createthrottledsignal.md index ce685ac54..57b6c46d5 100644 --- a/docs/framework/solid/reference/functions/createthrottledsignal.md +++ b/docs/framework/solid/reference/functions/createthrottledsignal.md @@ -8,10 +8,13 @@ title: createThrottledSignal # Function: createThrottledSignal() ```ts -function createThrottledSignal(value, initialOptions): [Accessor, Setter, SolidThrottler>] +function createThrottledSignal( + value, + initialOptions, + selector?): [Accessor, Setter, SolidThrottler, TSelected>] ``` -Defined in: [throttler/createThrottledSignal.ts:41](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottledSignal.ts#L41) +Defined in: [throttler/createThrottledSignal.ts:44](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottledSignal.ts#L44) A Solid hook that creates a throttled state value that updates at most once within a specified time window. This hook combines Solid's createSignal with throttling functionality to provide controlled state updates. @@ -31,6 +34,8 @@ consider using the lower-level createThrottler hook instead. • **TValue** +• **TSelected** = `ThrottlerState`\<`Setter`\<`TValue`\>\> + ## Parameters ### value @@ -41,9 +46,13 @@ consider using the lower-level createThrottler hook instead. `ThrottlerOptions`\<`Setter`\<`TValue`\>\> +### selector? + +(`state`) => `TSelected` + ## Returns -\[`Accessor`\<`TValue`\>, `Setter`\<`TValue`\>, [`SolidThrottler`](../../interfaces/solidthrottler.md)\<`Setter`\<`TValue`\>\>\] +\[`Accessor`\<`TValue`\>, `Setter`\<`TValue`\>, [`SolidThrottler`](../../interfaces/solidthrottler.md)\<`Setter`\<`TValue`\>, `TSelected`\>\] ## Example diff --git a/docs/framework/solid/reference/functions/createthrottledvalue.md b/docs/framework/solid/reference/functions/createthrottledvalue.md index 622ce2f9e..ae7bc4926 100644 --- a/docs/framework/solid/reference/functions/createthrottledvalue.md +++ b/docs/framework/solid/reference/functions/createthrottledvalue.md @@ -8,10 +8,13 @@ title: createThrottledValue # Function: createThrottledValue() ```ts -function createThrottledValue(value, initialOptions): [Accessor, SolidThrottler>] +function createThrottledValue( + value, + initialOptions, + selector?): [Accessor, SolidThrottler, TSelected>] ``` -Defined in: [throttler/createThrottledValue.ts:35](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottledValue.ts#L35) +Defined in: [throttler/createThrottledValue.ts:38](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottledValue.ts#L38) A high-level Solid hook that creates a throttled version of a value that updates at most once within a specified time window. This hook uses Solid's createSignal internally to manage the throttled state. @@ -32,6 +35,8 @@ consider using the lower-level createThrottler hook instead. • **TValue** +• **TSelected** = `ThrottlerState`\<`Setter`\<`TValue`\>\> + ## Parameters ### value @@ -42,9 +47,13 @@ consider using the lower-level createThrottler hook instead. `ThrottlerOptions`\<`Setter`\<`TValue`\>\> +### selector? + +(`state`) => `TSelected` + ## Returns -\[`Accessor`\<`TValue`\>, [`SolidThrottler`](../../interfaces/solidthrottler.md)\<`Setter`\<`TValue`\>\>\] +\[`Accessor`\<`TValue`\>, [`SolidThrottler`](../../interfaces/solidthrottler.md)\<`Setter`\<`TValue`\>, `TSelected`\>\] ## Example diff --git a/docs/framework/solid/reference/functions/createthrottler.md b/docs/framework/solid/reference/functions/createthrottler.md index 03c60f6cf..0e81f1177 100644 --- a/docs/framework/solid/reference/functions/createthrottler.md +++ b/docs/framework/solid/reference/functions/createthrottler.md @@ -8,10 +8,13 @@ title: createThrottler # Function: createThrottler() ```ts -function createThrottler(fn, initialOptions): SolidThrottler +function createThrottler( + fn, + initialOptions, +selector?): SolidThrottler ``` -Defined in: [throttler/createThrottler.ts:53](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L53) +Defined in: [throttler/createThrottler.ts:57](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L57) A low-level Solid hook that creates a `Throttler` instance that limits how often the provided function can execute. @@ -27,6 +30,8 @@ expensive operations or UI updates. • **TFn** *extends* `AnyFunction` +• **TSelected** = `ThrottlerState`\<`TFn`\> + ## Parameters ### fn @@ -37,9 +42,13 @@ expensive operations or UI updates. `ThrottlerOptions`\<`TFn`\> +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidThrottler`](../../interfaces/solidthrottler.md)\<`TFn`\> +[`SolidThrottler`](../../interfaces/solidthrottler.md)\<`TFn`, `TSelected`\> ## Example diff --git a/docs/framework/solid/reference/interfaces/solidthrottler.md b/docs/framework/solid/reference/interfaces/solidthrottler.md index ba75a1d98..5138a7d45 100644 --- a/docs/framework/solid/reference/interfaces/solidthrottler.md +++ b/docs/framework/solid/reference/interfaces/solidthrottler.md @@ -5,26 +5,30 @@ title: SolidThrottler -# Interface: SolidThrottler\ +# Interface: SolidThrottler\ -Defined in: [throttler/createThrottler.ts:14](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L14) - -An extension of the Throttler class that adds Solid signals to access the internal state of the throttler +Defined in: [throttler/createThrottler.ts:11](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L11) ## Extends -- `Omit`\<`Throttler`\<`TFn`\>, `"getState"`\> +- `Omit`\<`Throttler`\<`TFn`\>, `"store"`\> ## Type Parameters • **TFn** *extends* `AnyFunction` +• **TSelected** = `ThrottlerState`\<`TFn`\> + ## Properties -### store +### state ```ts -store: ThrottlerState; +state: Accessor; ``` -Defined in: [throttler/createThrottler.ts:16](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L16) +Defined in: [throttler/createThrottler.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L20) + +Reactive state that will be updated when the throttler state changes + +Use this instead of `throttler.store.state` From f55ab2d3724f1a10e1bc5096e424999708b1db30 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 6 Jul 2025 15:51:56 -0500 Subject: [PATCH 27/51] rewrite queuer utils with tanstack store --- examples/react/queue/src/index.tsx | 6 +- examples/react/useQueuedState/src/index.tsx | 46 +- examples/react/useQueuedValue/src/index.tsx | 47 +- examples/react/useQueuer/src/index.tsx | 70 +-- examples/solid/createQueuer/src/index.tsx | 102 ++-- examples/solid/queue/src/index.tsx | 6 +- packages/pacer/src/async-queuer.ts | 29 +- packages/pacer/src/queuer.ts | 463 +++++++++--------- packages/pacer/tests/queuer.test.ts | 81 +-- .../react-pacer/src/queuer/useQueuedState.ts | 30 +- .../react-pacer/src/queuer/useQueuedValue.ts | 19 +- packages/react-pacer/src/queuer/useQueuer.ts | 31 +- .../src/async-queuer/createAsyncQueuer.ts | 2 +- .../solid-pacer/src/queuer/createQueuer.ts | 129 +---- .../rate-limiter/createRateLimitedSignal.ts | 2 +- 15 files changed, 484 insertions(+), 579 deletions(-) diff --git a/examples/react/queue/src/index.tsx b/examples/react/queue/src/index.tsx index e47361cc2..17f63a083 100644 --- a/examples/react/queue/src/index.tsx +++ b/examples/react/queue/src/index.tsx @@ -17,7 +17,7 @@ function App1() { wait: 1000, onItemsChange: (queue) => { setQueueItems(queue.peekAllItems()) - setProcessedCount(queue.getExecutionCount()) + setProcessedCount(queue.store.state.executionCount) }, }), [], @@ -74,7 +74,7 @@ function App2() { wait: 500, onItemsChange: (queue) => { setQueueItems(queue.peekAllItems()) - setProcessedCount(queue.getExecutionCount()) + setProcessedCount(queue.store.state.executionCount) }, }), [], @@ -139,7 +139,7 @@ function App3() { wait: 100, onItemsChange: (queue) => { setQueueItems(queue.peekAllItems()) - setProcessedCount(queue.getExecutionCount()) + setProcessedCount(queue.store.state.executionCount) }, }), [], diff --git a/examples/react/useQueuedState/src/index.tsx b/examples/react/useQueuedState/src/index.tsx index 6ffdf6b5e..d0306da97 100644 --- a/examples/react/useQueuedState/src/index.tsx +++ b/examples/react/useQueuedState/src/index.tsx @@ -18,14 +18,14 @@ function App1() { return (

    TanStack Pacer useQueuedState Example 1

    -
    Queue Size: {queuer.getSize()}
    +
    Queue Size: {queuer.state.size}
    Queue Max Size: {25}
    -
    Queue Full: {queuer.getIsFull() ? 'Yes' : 'No'}
    +
    Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
    Queue Peek: {queuer.peekNextItem()}
    -
    Queue Empty: {queuer.getIsEmpty() ? 'Yes' : 'No'}
    -
    Queue Idle: {queuer.getIsIdle() ? 'Yes' : 'No'}
    -
    Queuer Status: {queuer.getIsRunning() ? 'Running' : 'Stopped'}
    -
    Items Processed: {queuer.getExecutionCount()}
    +
    Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
    +
    Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
    +
    Queuer Status: {queuer.state.status}
    +
    Items Processed: {queuer.state.executionCount}
    Queue Items: {queueItems.join(', ')}
    Add Number - - - -
    @@ -131,23 +137,23 @@ function App2() {
    - + - + - + - + - + @@ -155,11 +161,11 @@ function App2() { - + - + @@ -167,7 +173,7 @@ function App2() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - queuer.getExecutionCount()) / + ((instantExecutionCount - queuer.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/react/useQueuedValue/src/index.tsx b/examples/react/useQueuedValue/src/index.tsx index 80a49eae5..21b8843aa 100644 --- a/examples/react/useQueuedValue/src/index.tsx +++ b/examples/react/useQueuedValue/src/index.tsx @@ -8,7 +8,6 @@ function App1() { // Queuer that processes a single value with delays const [value, queuer] = useQueuedValue(instantSearchValue, { maxSize: 25, - started: false, wait: 500, // wait 500ms between processing value changes }) @@ -17,13 +16,13 @@ function App1() {

    TanStack Pacer useQueuedValue Example 1

    Current Value: {value}

    -
    Queue Size: {queuer.getSize()}
    -
    Queue Full: {queuer.getIsFull() ? 'Yes' : 'No'}
    +
    Queue Size: {queuer.state.size}
    +
    Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
    Queue Peek: {queuer.peekNextItem()}
    -
    Queue Empty: {queuer.getIsEmpty() ? 'Yes' : 'No'}
    -
    Queue Idle: {queuer.getIsIdle() ? 'Yes' : 'No'}
    -
    Queuer Status: {queuer.getIsRunning() ? 'Running' : 'Stopped'}
    -
    Items Processed: {queuer.getExecutionCount()}
    +
    Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
    +
    Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
    +
    Queuer Status: {queuer.state.status}
    +
    Items Processed: {queuer.state.executionCount}
    Queue Items: {queuer.peekAllItems().join(', ')}
    - - - -
    @@ -120,23 +125,23 @@ function App2() { - + - + - + - + - + @@ -144,11 +149,11 @@ function App2() { - + - + @@ -156,7 +161,7 @@ function App2() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - queuer.getExecutionCount()) / + ((instantExecutionCount - queuer.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/react/useQueuer/src/index.tsx b/examples/react/useQueuer/src/index.tsx index 20ac8f517..afbc2dab6 100644 --- a/examples/react/useQueuer/src/index.tsx +++ b/examples/react/useQueuer/src/index.tsx @@ -1,13 +1,10 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import ReactDOM from 'react-dom/client' import { useQueuer } from '@tanstack/react-pacer/queuer' import { useStoragePersister } from '@tanstack/react-persister' import type { QueuerState } from '@tanstack/react-pacer/queuer' function App1() { - // Use your state management library of choice - const [queueItems, setQueueItems] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - // optional session storage persister to retain state on page refresh const queuerPersister = useStoragePersister>({ key: 'my-queuer', @@ -22,29 +19,29 @@ function App1() { } const queuer = useQueuer(processItem, { + initialItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + initialState: queuerPersister.loadState(), maxSize: 25, - initialItems: queueItems, started: false, wait: 1000, // wait 1 second between processing items - wait is optional! - onItemsChange: (queue) => { - setQueueItems(queue.peekAllItems()) - }, - initialState: queuerPersister.loadState(), - onStateChange: (state) => queuerPersister.saveState(state), }) + useEffect(() => { + queuerPersister.saveState(queuer.state) + }, [queuer.state]) + return (

    TanStack Pacer useQueuer Example 1 (with persister)

    -
    Queue Size: {queuer.getSize()}
    +
    Queue Size: {queuer.state.size}
    Queue Max Size: {25}
    -
    Queue Full: {queuer.getIsFull() ? 'Yes' : 'No'}
    +
    Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
    Queue Peek: {queuer.peekNextItem()}
    -
    Queue Empty: {queuer.getIsEmpty() ? 'Yes' : 'No'}
    -
    Queue Idle: {queuer.getIsIdle() ? 'Yes' : 'No'}
    -
    Queuer Status: {queuer.getIsRunning() ? 'Running' : 'Stopped'}
    -
    Items Processed: {queuer.getExecutionCount()}
    -
    Queue Items: {queueItems.join(', ')}
    +
    Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
    +
    Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
    +
    Queuer Status: {queuer.state.status}
    +
    Items Processed: {queuer.state.executionCount}
    +
    Queue Items: {queuer.state.items.join(', ')}
    - - - -
    +
    {JSON.stringify(queuer.state, null, 2)}
    ) } @@ -148,23 +152,23 @@ function App2() {
    - + - + - + - + - + @@ -172,11 +176,11 @@ function App2() { - + - + @@ -184,7 +188,7 @@ function App2() { {instantExecutionCount === 0 ? '0' : Math.round( - ((instantExecutionCount - queuer.getExecutionCount()) / + ((instantExecutionCount - queuer.state.executionCount) / instantExecutionCount) * 100, )} diff --git a/examples/solid/createQueuer/src/index.tsx b/examples/solid/createQueuer/src/index.tsx index 9043dd94d..f03a19f9b 100644 --- a/examples/solid/createQueuer/src/index.tsx +++ b/examples/solid/createQueuer/src/index.tsx @@ -18,15 +18,15 @@ function App1() { return (

    TanStack Pacer createQueuer Example 1

    -
    Queue Size: {queuer.size()}
    +
    Queue Size: {queuer.state().size}
    Queue Max Size: {25}
    -
    Queue Full: {queuer.isFull() ? 'Yes' : 'No'}
    -
    Queue Peek: {queuer.nextItem()}
    -
    Queue Empty: {queuer.isEmpty() ? 'Yes' : 'No'}
    -
    Queue Idle: {queuer.isIdle() ? 'Yes' : 'No'}
    -
    Queuer Status: {queuer.isRunning() ? 'Running' : 'Stopped'}
    -
    Items Processed: {queuer.executionCount()}
    -
    Queue Items: {queuer.allItems().join(', ')}
    +
    Queue Full: {queuer.state().isFull ? 'Yes' : 'No'}
    +
    Queue Peek: {queuer.peekNextItem()}
    +
    Queue Empty: {queuer.state().isEmpty ? 'Yes' : 'No'}
    +
    Queue Idle: {queuer.state().isIdle ? 'Yes' : 'No'}
    +
    Queuer Status: {queuer.state().status}
    +
    Items Processed: {queuer.state().executionCount}
    +
    Queue Items: {queuer.state().items.join(', ')}
    - - - -
    @@ -115,15 +127,15 @@ function App2() {
    - + - + - +
    Enabled:{setValueDebouncer.getEnabled().toString()}
    Is Pending:{setValueDebouncer.getState().isPending.toString()}{setValueDebouncer.state.isPending.toString()}
    Instant Executions:
    Debounced Executions:{setValueDebouncer.getExecutionCount()}{setValueDebouncer.state.executionCount}
    Saved Executions: - {instantExecutionCount - setValueDebouncer.getExecutionCount()} + {instantExecutionCount - setValueDebouncer.state.executionCount}
    Execution Count:{throttler.getExecutionCount()}{throttler.state.executionCount}
    Instant Count:
    Execution Count:{throttler.getExecutionCount()}{throttler.state.executionCount}
    Instant Search:
    Throttled Execution Count:{throttler.getExecutionCount()}{throttler.state.executionCount}
    Saved Executions: - {instantExecutionCount - throttler.getExecutionCount()} ( + {instantExecutionCount - throttler.state.executionCount} ( {instantExecutionCount > 0 ? ( - ((instantExecutionCount - throttler.getExecutionCount()) / + ((instantExecutionCount - throttler.state.executionCount) / instantExecutionCount) * 100 ).toFixed(2) diff --git a/examples/react/useThrottledValue/src/index.tsx b/examples/react/useThrottledValue/src/index.tsx index 77552fbcb..4758eb365 100644 --- a/examples/react/useThrottledValue/src/index.tsx +++ b/examples/react/useThrottledValue/src/index.tsx @@ -134,15 +134,15 @@ function App3() {
    Throttled Execution Count:{throttler.getExecutionCount()}{throttler.state.executionCount}
    Saved Executions: - {instantExecutionCount - throttler.getExecutionCount()} ( + {instantExecutionCount - throttler.state.executionCount} ( {instantExecutionCount > 0 ? ( - ((instantExecutionCount - throttler.getExecutionCount()) / + ((instantExecutionCount - throttler.state.executionCount) / instantExecutionCount) * 100 ).toFixed(2) diff --git a/examples/react/useThrottler/src/index.tsx b/examples/react/useThrottler/src/index.tsx index b101ffa81..069d73ad5 100644 --- a/examples/react/useThrottler/src/index.tsx +++ b/examples/react/useThrottler/src/index.tsx @@ -31,7 +31,7 @@ function App1() {
    Execution Count:{setCountThrottler.getExecutionCount()}{setCountThrottler.state.executionCount}
    Instant Count:
    Execution Count:{setSearchThrottler.getExecutionCount()}{setSearchThrottler.state.executionCount}
    Instant Search:
    Throttled Execution Count:{setValueThrottler.getExecutionCount()}{setValueThrottler.state.executionCount}
    Saved Executions: - {instantExecutionCount - setValueThrottler.getExecutionCount()} ( + {instantExecutionCount - setValueThrottler.state.executionCount} ( {instantExecutionCount > 0 ? ( ((instantExecutionCount - - setValueThrottler.getExecutionCount()) / + setValueThrottler.state.executionCount) / instantExecutionCount) * 100 ).toFixed(2) diff --git a/examples/solid/createThrottledSignal/src/index.tsx b/examples/solid/createThrottledSignal/src/index.tsx index 03d1b85ab..65401d4f8 100644 --- a/examples/solid/createThrottledSignal/src/index.tsx +++ b/examples/solid/createThrottledSignal/src/index.tsx @@ -31,7 +31,7 @@ function App1() {
    Execution Count:{throttler.executionCount()}{throttler.state().executionCount}
    Instant Count:
    Execution Count:{throttler.executionCount()}{throttler.state().executionCount}
    Instant Search:
    Throttled Executions:{throttler.executionCount()}{throttler.state().executionCount}
    Saved Executions:{instantExecutionCount() - throttler.executionCount()} + {instantExecutionCount() - throttler.state().executionCount} +
    % Reduction:
    Throttled Executions:{throttler.executionCount()}{throttler.state().executionCount}
    Saved Executions:{instantExecutionCount() - throttler.executionCount()} + {instantExecutionCount() - throttler.state().executionCount} +
    % Reduction:
    Execution Count:{setCountThrottler.store.executionCount}{setCountThrottler.state().executionCount}
    Instant Count:
    Execution Count:{setSearchThrottler.store.executionCount}{setSearchThrottler.state().executionCount}
    Instant Search:
    Throttled Executions:{setValueThrottler.store.executionCount}{setValueThrottler.state().executionCount}
    Saved Executions: - {instantExecutionCount() - setValueThrottler.store.executionCount} + {instantExecutionCount() - + setValueThrottler.state().executionCount}
    Queue Size:{queuer.getSize()}{queuer.state.size}
    Queue Full:{queuer.getIsFull() ? 'Yes' : 'No'}{queuer.state.isFull ? 'Yes' : 'No'}
    Queue Empty:{queuer.getIsEmpty() ? 'Yes' : 'No'}{queuer.state.isEmpty ? 'Yes' : 'No'}
    Queue Idle:{queuer.getIsIdle() ? 'Yes' : 'No'}{queuer.state.isIdle ? 'Yes' : 'No'}
    Queuer Status:{queuer.getIsRunning() ? 'Running' : 'Stopped'}{queuer.state.status}
    Instant Executions:
    Items Processed:{queuer.getExecutionCount()}{queuer.state.executionCount}
    Saved Executions:{instantExecutionCount - queuer.getExecutionCount()}{instantExecutionCount - queuer.state.executionCount}
    % Reduction:
    Queue Size:{queuer.getSize()}{queuer.state.size}
    Queue Full:{queuer.getIsFull() ? 'Yes' : 'No'}{queuer.state.isFull ? 'Yes' : 'No'}
    Queue Empty:{queuer.getIsEmpty() ? 'Yes' : 'No'}{queuer.state.isEmpty ? 'Yes' : 'No'}
    Queue Idle:{queuer.getIsIdle() ? 'Yes' : 'No'}{queuer.state.isIdle ? 'Yes' : 'No'}
    Queuer Status:{queuer.getIsRunning() ? 'Running' : 'Stopped'}{queuer.state.status}
    Instant Executions:
    Items Processed:{queuer.getExecutionCount()}{queuer.state.executionCount}
    Saved Executions:{instantExecutionCount - queuer.getExecutionCount()}{instantExecutionCount - queuer.state.executionCount}
    % Reduction:
    Queue Size:{queuer.getSize()}{queuer.state.size}
    Queue Full:{queuer.getIsFull() ? 'Yes' : 'No'}{queuer.state.isFull ? 'Yes' : 'No'}
    Queue Empty:{queuer.getIsEmpty() ? 'Yes' : 'No'}{queuer.state.isEmpty ? 'Yes' : 'No'}
    Queue Idle:{queuer.getIsIdle() ? 'Yes' : 'No'}{queuer.state.isIdle ? 'Yes' : 'No'}
    Queuer Status:{queuer.getIsRunning() ? 'Running' : 'Stopped'}{queuer.state.isRunning ? 'Running' : 'Stopped'}
    Instant Executions:
    Items Processed:{queuer.getExecutionCount()}{queuer.state.executionCount}
    Saved Executions:{instantExecutionCount - queuer.getExecutionCount()}{instantExecutionCount - queuer.state.executionCount}
    % Reduction:
    Queue Size:{queuer.size()}{queuer.state().size}
    Items Processed:{queuer.executionCount()}{queuer.state().executionCount}
    Queue Items:{queuer.allItems().join(', ')}{queuer.state().items.join(', ')}
    @@ -136,16 +148,28 @@ function App2() { margin: '16px 0', }} > - - - - @@ -215,19 +239,19 @@ function App3() {
    Queue Size:{queuer.size()}{queuer.state().size}
    Queue Full:{queuer.isFull() ? 'Yes' : 'No'}{queuer.state().isFull ? 'Yes' : 'No'}
    Queue Empty:{queuer.isEmpty() ? 'Yes' : 'No'}{queuer.state().isEmpty ? 'Yes' : 'No'}
    Items Processed:{queuer.executionCount()}{queuer.state().executionCount}
    % Saved:
    Queue Items:{queuer.allItems().join(', ')}{queuer.state().items.join(', ')}
    @@ -257,16 +281,28 @@ function App3() { margin: '16px 0', }} > - - - - diff --git a/examples/solid/queue/src/index.tsx b/examples/solid/queue/src/index.tsx index c0191c662..1a8655a72 100644 --- a/examples/solid/queue/src/index.tsx +++ b/examples/solid/queue/src/index.tsx @@ -16,7 +16,7 @@ function App1() { wait: 1000, onItemsChange: (queue) => { setQueueItems(queue.peekAllItems()) - setProcessedCount(queue.getExecutionCount()) + setProcessedCount(queue.store.state.executionCount) }, }) @@ -70,7 +70,7 @@ function App2() { wait: 500, onItemsChange: (queue) => { setQueueItems(queue.peekAllItems()) - setProcessedCount(queue.getExecutionCount()) + setProcessedCount(queue.store.state.executionCount) }, }) @@ -132,7 +132,7 @@ function App3() { wait: 100, onItemsChange: (queue) => { setQueueItems(queue.peekAllItems()) - setProcessedCount(queue.getExecutionCount()) + setProcessedCount(queue.store.state.executionCount) }, }) diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index 60619948c..ea8d34fba 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -205,10 +205,11 @@ export class AsyncQueuer { AsyncQueuerState >(getDefaultAsyncQueuerState()) #options: AsyncQueuerOptions + #timeoutIds: Set = new Set() constructor( - private fn: (value: TValue) => Promise, - initialOptions: AsyncQueuerOptions, + private fn: (item: TValue) => Promise, + initialOptions: AsyncQueuerOptions = {}, ) { this.#options = { ...defaultOptions, @@ -293,6 +294,7 @@ export class AsyncQueuer { this.#setState({ pendingTick: false }) return } + this.#setState({ pendingTick: true }) // Check for expired items this.#checkExpiredItems() @@ -317,7 +319,8 @@ export class AsyncQueuer { const wait = this.#getWait() if (wait > 0) { - setTimeout(() => this.#tick(), wait) + const timeoutId = setTimeout(() => this.#tick(), wait) + this.#timeoutIds.add(timeoutId) return } @@ -339,23 +342,23 @@ export class AsyncQueuer { * ``` */ addItem = ( - item: TValue & { priority?: number }, + item: TValue, position: QueuePosition = this.#options.addItemsTo ?? 'back', runOnItemsChange: boolean = true, - ): void => { + ): boolean => { if (this.store.state.isFull) { this.#setState({ rejectionCount: this.store.state.rejectionCount + 1, }) this.#options.onReject?.(item, this) - return + return false } // Get priority either from the function or from getPriority option const priority = this.#options.getPriority !== defaultOptions.getPriority ? this.#options.getPriority!(item) - : item.priority + : (item as any).priority const items = this.store.state.items const itemTimestamps = this.store.state.itemTimestamps @@ -399,9 +402,10 @@ export class AsyncQueuer { } if (this.store.state.isRunning && !this.store.state.pendingTick) { - this.#setState({ pendingTick: true }) this.#tick() } + + return true } /** @@ -419,8 +423,7 @@ export class AsyncQueuer { getNextItem = ( position: QueuePosition = this.#options.getItemsFrom ?? 'front', ): TValue | undefined => { - const items = this.store.state.items - const itemTimestamps = this.store.state.itemTimestamps + const { items, itemTimestamps } = this.store.state let item: TValue | undefined if (position === 'front') { @@ -497,8 +500,9 @@ export class AsyncQueuer { if ( (this.#options.expirationDuration ?? Infinity) === Infinity && this.#options.getIsExpired === defaultOptions.getIsExpired - ) + ) { return + } const now = Date.now() const expiredIndices: Array = [] @@ -589,7 +593,6 @@ export class AsyncQueuer { start = (): void => { this.#setState({ isRunning: true }) if (!this.store.state.pendingTick && !this.store.state.isEmpty) { - this.#setState({ pendingTick: true }) this.#tick() } } @@ -598,6 +601,8 @@ export class AsyncQueuer { * Stops processing items in the queue. Does not clear the queue. */ stop = (): void => { + this.#timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId)) + this.#timeoutIds.clear() this.#setState({ isRunning: false, pendingTick: false }) } diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index faa7ff6f3..d67e17ebf 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -1,12 +1,36 @@ +import { Store } from '@tanstack/store' import { parseFunctionOrValue } from './utils' export interface QueuerState { executionCount: number expirationCount: number - items: Array + isEmpty: boolean + isFull: boolean + isIdle: boolean + isRunning: boolean itemTimestamps: Array + items: Array + pendingTick: boolean rejectionCount: number - running: boolean + size: number + status: 'idle' | 'running' | 'stopped' +} + +function getDefaultQueuerState(): QueuerState { + return { + executionCount: 0, + expirationCount: 0, + isEmpty: true, + isFull: false, + isIdle: true, + isRunning: true, + itemTimestamps: [], + items: [], + pendingTick: false, + rejectionCount: 0, + size: 0, + status: 'idle', + } } /** @@ -60,10 +84,6 @@ export interface QueuerOptions { * Callback fired whenever an item is removed from the queuer */ onExecute?: (item: TValue, queuer: Queuer) => void - /** - * Callback fired whenever the queuer's running state changes - */ - onIsRunningChange?: (queuer: Queuer) => void /** * Callback fired whenever an item is added or removed from the queuer */ @@ -72,10 +92,6 @@ export interface QueuerOptions { * Callback fired whenever an item is rejected from being added to the queuer */ onReject?: (item: TValue, queuer: Queuer) => void - /** - * Callback function that is called when the state of the queuer is updated - */ - onStateChange?: (state: QueuerState, queuer: Queuer) => void /** * Whether the queuer should start processing tasks immediately */ @@ -128,7 +144,7 @@ export type QueuePosition = 'front' | 'back' * - Callbacks for queue state changes, execution, rejection, and expiration * * Running behavior: - * - `start()`: Begins automatically processing items in the queue (defaults to running) + * - `start()`: Begins automatically processing items in the queue (defaults to isRunning) * - `stop()`: Pauses processing but maintains queue state * - `wait`: Configurable delay between processing items * - `onItemsChange`/`onExecute`: Callbacks for monitoring queue state @@ -160,7 +176,7 @@ export type QueuePosition = 'front' | 'back' * State Management: * - Use `initialState` to provide initial state values when creating the queuer * - Use `onStateChange` callback to react to state changes and implement custom persistence - * - The state includes execution count, expiration count, rejection count, and running status + * - The state includes execution count, expiration count, rejection count, and isRunning status * - State can be retrieved using `getState()` method * * Example usage: @@ -185,34 +201,29 @@ export type QueuePosition = 'front' | 'back' * ``` */ export class Queuer { + readonly store: Store> = new Store( + getDefaultQueuerState(), + ) #options: QueuerOptions - #state: QueuerState = { - executionCount: 0, - expirationCount: 0, - items: [], - itemTimestamps: [], - rejectionCount: 0, - running: true, - } - #onItemsChanges: Array<(item: TValue) => void> = [] - #pendingTick = false #timeoutId: NodeJS.Timeout | null = null constructor( private fn: (item: TValue) => void, initialOptions: QueuerOptions = {}, ) { - this.#options = { ...defaultOptions, ...initialOptions } - this.#state = { - ...this.#state, - ...this.#options.initialState, - running: - this.#options.initialState?.running ?? this.#options.started ?? true, + this.#options = { + ...defaultOptions, + ...initialOptions, } + const isInitiallyRunning = + this.#options.initialState?.isRunning ?? this.#options.started ?? true + this.#setState({ + ...this.#options.initialState, + isRunning: isInitiallyRunning, + }) if (this.#options.initialState?.items) { - this.#options.onItemsChange?.(this) - if (this.#state.running) { + if (this.store.state.isRunning) { this.#tick() } } else { @@ -222,10 +233,6 @@ export class Queuer { this.addItem(item, this.#options.addItemsTo ?? 'back', isLast) } } - - if (this.#options.started) { - this.start() - } } /** @@ -235,27 +242,38 @@ export class Queuer { this.#options = { ...this.#options, ...newOptions } } - /** - * Returns the current queuer options, including defaults and any overrides. - */ - getOptions = (): Required> => { - return this.#options as Required> - } + #setState = (newState: Partial>): void => { + this.store.setState((state) => { + const combinedState = { + ...state, + ...newState, + } - getState = (): QueuerState => { - return { ...this.#state } - } + const { items, isRunning } = combinedState - #setState = (newState: Partial>): void => { - this.#state = { ...this.#state, ...newState } - this.#options.onStateChange?.(this.#state, this) + const size = items.length + const isFull = size >= (this.#options.maxSize ?? Infinity) + const isEmpty = size === 0 + const isIdle = isRunning && isEmpty + + const status = isIdle ? 'idle' : isRunning ? 'running' : 'stopped' + + return { + ...combinedState, + isEmpty, + isFull, + isIdle, + size, + status, + } + }) } /** * Returns the current wait time (in milliseconds) between processing items. * If a function is provided, it is called with the queuer instance. */ - getWait = (): number => { + #getWait = (): number => { return parseFunctionOrValue(this.#options.wait ?? 0, this) } @@ -263,22 +281,23 @@ export class Queuer { * Processes items in the queue up to the wait interval. Internal use only. */ #tick = () => { - if (!this.#state.running) { - this.#pendingTick = false + if (!this.store.state.isRunning) { + this.#setState({ pendingTick: false }) return } + this.#setState({ pendingTick: true }) + // Check for expired items this.#checkExpiredItems() - while (!this.getIsEmpty()) { + while (!this.store.state.isEmpty) { const nextItem = this.execute(this.#options.getItemsFrom ?? 'front') if (nextItem === undefined) { break } - this.#onItemsChanges.forEach((cb) => cb(nextItem)) - const wait = this.getWait() + const wait = this.#getWait() if (wait > 0) { // Use setTimeout to wait before processing next item this.#timeoutId = setTimeout(() => this.#tick(), wait) @@ -287,116 +306,7 @@ export class Queuer { this.#tick() } - this.#pendingTick = false - } - - /** - * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. - * Internal use only. - */ - #checkExpiredItems = (): void => { - if ( - (this.#options.expirationDuration ?? Infinity) === Infinity && - this.#options.getIsExpired === defaultOptions.getIsExpired - ) - return - - const now = Date.now() - const expiredIndices: Array = [] - - // Find indices of expired items - for (let i = 0; i < this.#state.items.length; i++) { - const timestamp = this.#state.itemTimestamps[i] - if (timestamp === undefined) continue - - const item = this.#state.items[i] - if (item === undefined) continue - - const isExpired = - this.#options.getIsExpired !== defaultOptions.getIsExpired - ? this.#options.getIsExpired!(item, timestamp) - : now - timestamp > (this.#options.expirationDuration ?? Infinity) - - if (isExpired) { - expiredIndices.push(i) - } - } - - // Remove expired items from back to front to maintain indices - for (let i = expiredIndices.length - 1; i >= 0; i--) { - const index = expiredIndices[i] - if (index === undefined) continue - - const expiredItem = this.#state.items[index] - if (expiredItem === undefined) continue - - this.#state.items.splice(index, 1) - this.#state.itemTimestamps.splice(index, 1) - this.#setState({ - expirationCount: this.#state.expirationCount + 1, - }) - this.#options.onExpire?.(expiredItem, this) - } - - if (expiredIndices.length > 0) { - this.#options.onItemsChange?.(this) - } - } - - /** - * Stops processing items in the queue. Does not clear the queue. - */ - stop = () => { - if (this.#timeoutId) { - clearTimeout(this.#timeoutId) - this.#timeoutId = null - } - this.#setState({ running: false }) - this.#pendingTick = false - this.#options.onIsRunningChange?.(this) - } - - /** - * Starts processing items in the queue. If already running, does nothing. - */ - start = () => { - this.#setState({ running: true }) - if (!this.#pendingTick && !this.getIsEmpty()) { - this.#pendingTick = true - this.#tick() - } - this.#options.onIsRunningChange?.(this) - } - - /** - * Removes all pending items from the queue. Does not affect items being processed. - */ - clear = (): void => { - this.#setState({ - items: [], - itemTimestamps: [], - }) - this.#options.onItemsChange?.(this) - } - - /** - * Resets the queuer to its initial state. Optionally repopulates with initial items. - * Does not affect callbacks or options. - */ - reset = (withInitialItems?: boolean): void => { - this.clear() - this.#setState({ - executionCount: 0, - expirationCount: 0, - rejectionCount: 0, - running: this.#options.started ?? true, - }) - if (withInitialItems) { - this.#setState({ - items: [...(this.#options.initialItems ?? [])], - itemTimestamps: this.#state.items.map(() => Date.now()), - }) - } + this.#setState({ pendingTick: false }) } /** @@ -414,52 +324,68 @@ export class Queuer { addItem = ( item: TValue, position: QueuePosition = this.#options.addItemsTo ?? 'back', - runOnUpdate: boolean = true, + runOnItemsChange: boolean = true, ): boolean => { - if (this.getIsFull()) { + if (this.store.state.isFull) { this.#setState({ - rejectionCount: this.#state.rejectionCount + 1, + rejectionCount: this.store.state.rejectionCount + 1, }) this.#options.onReject?.(item, this) return false } - if (this.#options.getPriority !== defaultOptions.getPriority) { - // If custom priority function is provided, insert based on priority - const priority = this.#options.getPriority!(item) - const insertIndex = this.#state.items.findIndex( - (existing) => this.#options.getPriority!(existing) < priority, - ) + // Get priority either from the function or from getPriority option + const priority = + this.#options.getPriority !== defaultOptions.getPriority + ? this.#options.getPriority!(item) + : (item as any).priority + + const items = this.store.state.items + const itemTimestamps = this.store.state.itemTimestamps + + if (priority !== undefined) { + // Insert based on priority - higher priority items go to front + const insertIndex = items.findIndex((existing) => { + const existingPriority: number = + this.#options.getPriority !== defaultOptions.getPriority + ? this.#options.getPriority!(existing) + : (existing as any).priority + return existingPriority < priority + }) if (insertIndex === -1) { - this.#state.items.push(item) - this.#state.itemTimestamps.push(Date.now()) + items.push(item) + itemTimestamps.push(Date.now()) } else { - this.#state.items.splice(insertIndex, 0, item) - this.#state.itemTimestamps.splice(insertIndex, 0, Date.now()) + items.splice(insertIndex, 0, item) + itemTimestamps.splice(insertIndex, 0, Date.now()) } } else { - // Default FIFO/LIFO behavior if (position === 'front') { - this.#state.items.unshift(item) - this.#state.itemTimestamps.unshift(Date.now()) + // Default FIFO/LIFO behavior + items.unshift(item) + itemTimestamps.unshift(Date.now()) } else { - this.#state.items.push(item) - this.#state.itemTimestamps.push(Date.now()) + // LIFO + items.push(item) + itemTimestamps.push(Date.now()) } } - if (this.#state.running && !this.#pendingTick) { - this.#pendingTick = true - this.#tick() - } - if (runOnUpdate) { - this.#options.onItemsChange?.(this) - } this.#setState({ - items: this.#state.items, - itemTimestamps: this.#state.itemTimestamps, + items, + itemTimestamps, }) + + if (runOnItemsChange) { + this.#options.onItemsChange?.(this) + } + + if (this.store.state.isRunning && !this.store.state.pendingTick) { + this.#setState({ pendingTick: true }) + this.#tick() + } + return true } @@ -478,14 +404,25 @@ export class Queuer { getNextItem = ( position: QueuePosition = this.#options.getItemsFrom ?? 'front', ): TValue | undefined => { + const { items, itemTimestamps } = this.store.state let item: TValue | undefined if (position === 'front') { - item = this.#state.items.shift() - this.#state.itemTimestamps.shift() + item = items[0] + if (item !== undefined) { + this.#setState({ + items: items.slice(1), + itemTimestamps: itemTimestamps.slice(1), + }) + } } else { - item = this.#state.items.pop() - this.#state.itemTimestamps.pop() + item = items[items.length - 1] + if (item !== undefined) { + this.#setState({ + items: items.slice(0, -1), + itemTimestamps: itemTimestamps.slice(0, -1), + }) + } } if (item !== undefined) { @@ -510,13 +447,71 @@ export class Queuer { if (item !== undefined) { this.fn(item) this.#setState({ - executionCount: this.#state.executionCount + 1, + executionCount: this.store.state.executionCount + 1, }) this.#options.onExecute?.(item, this) } return item } + /** + * Checks for expired items in the queue and removes them. Calls onExpire for each expired item. + * Internal use only. + */ + #checkExpiredItems = (): void => { + if ( + (this.#options.expirationDuration ?? Infinity) === Infinity && + this.#options.getIsExpired === defaultOptions.getIsExpired + ) { + return + } + + const now = Date.now() + const expiredIndices: Array = [] + + // Find indices of expired items + for (let i = 0; i < this.store.state.items.length; i++) { + const timestamp = this.store.state.itemTimestamps[i] + if (timestamp === undefined) continue + + const item = this.store.state.items[i] + if (item === undefined) continue + + const isExpired = + this.#options.getIsExpired !== defaultOptions.getIsExpired + ? this.#options.getIsExpired!(item, timestamp) + : now - timestamp > (this.#options.expirationDuration ?? Infinity) + + if (isExpired) { + expiredIndices.push(i) + } + } + + // Remove expired items from back to front to maintain indices + for (let i = expiredIndices.length - 1; i >= 0; i--) { + const index = expiredIndices[i] + if (index === undefined) continue + + const expiredItem = this.store.state.items[index] + if (expiredItem === undefined) continue + + const newItems = [...this.store.state.items] + const newTimestamps = [...this.store.state.itemTimestamps] + newItems.splice(index, 1) + newTimestamps.splice(index, 1) + this.#setState({ + items: newItems, + itemTimestamps: newTimestamps, + expirationCount: this.store.state.expirationCount + 1, + }) + this.#options.onExpire?.(expiredItem, this) + } + + if (expiredIndices.length > 0) { + this.#options.onItemsChange?.(this) + } + } + /** * Returns the next item in the queue without removing it. * @@ -526,76 +521,54 @@ export class Queuer { * queuer.peekNextItem('back'); // back * ``` */ - peekNextItem = ( - position: QueuePosition = this.#options.getItemsFrom ?? 'front', - ): TValue | undefined => { + peekNextItem = (position: QueuePosition = 'front'): TValue | undefined => { if (position === 'front') { - return this.#state.items[0] + return this.store.state.items[0] } - return this.#state.items[this.#state.items.length - 1] - } - - /** - * Returns true if the queue is empty (no pending items). - */ - getIsEmpty = (): boolean => { - return this.#state.items.length === 0 - } - - /** - * Returns true if the queue is full (reached maxSize). - */ - getIsFull = (): boolean => { - return this.#state.items.length >= (this.#options.maxSize ?? Infinity) - } - - /** - * Returns the number of pending items in the queue. - */ - getSize = (): number => { - return this.#state.items.length + return this.store.state.items[this.store.state.size - 1] } /** * Returns a copy of all items in the queue. */ peekAllItems = (): Array => { - return [...this.#state.items] - } - - /** - * Returns the number of items that have been processed and removed from the queue. - */ - getExecutionCount = (): number => { - return this.#state.executionCount + return [...this.store.state.items] } /** - * Returns the number of items that have been rejected from being added to the queue. + * Starts processing items in the queue. If already isRunning, does nothing. */ - getRejectionCount = (): number => { - return this.#state.rejectionCount + start = () => { + this.#setState({ isRunning: true }) + if (!this.store.state.pendingTick && !this.store.state.isEmpty) { + this.#tick() + } } /** - * Returns the number of items that have expired and been removed from the queue. + * Stops processing items in the queue. Does not clear the queue. */ - getExpirationCount = (): number => { - return this.#state.expirationCount + stop = () => { + if (this.#timeoutId) { + clearTimeout(this.#timeoutId) + this.#timeoutId = null + } + this.#setState({ isRunning: false, pendingTick: false }) } /** - * Returns true if the queuer is currently running (processing items). + * Removes all pending items from the queue. Does not affect items being processed. */ - getIsRunning = () => { - return this.#state.running + clear = (): void => { + this.#setState({ items: [], itemTimestamps: [] }) + this.#options.onItemsChange?.(this) } /** - * Returns true if the queuer is running but has no items to process. + * Resets the queuer state to its default values */ - getIsIdle = () => { - return this.#state.running && this.getIsEmpty() + reset = (): void => { + this.#setState(getDefaultQueuerState()) } } @@ -604,13 +577,13 @@ export class Queuer { * 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 running and will process items as they are added. + * `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. * * State Management: * - Use `initialState` to provide initial state values when creating the queuer * - Use `onStateChange` callback to react to state changes and implement custom persistence - * - The state includes execution count, expiration count, rejection count, and running status + * - The state includes execution count, expiration count, rejection count, and isRunning status * * Example usage: * ```ts @@ -632,8 +605,8 @@ export class Queuer { */ export function queue( fn: (item: TValue) => void, - options: QueuerOptions, + initialOptions: QueuerOptions, ) { - const queuer = new Queuer(fn, options) - return queuer.addItem.bind(queuer) + const queuer = new Queuer(fn, initialOptions) + return queuer.addItem } diff --git a/packages/pacer/tests/queuer.test.ts b/packages/pacer/tests/queuer.test.ts index 099ff82da..02902986e 100644 --- a/packages/pacer/tests/queuer.test.ts +++ b/packages/pacer/tests/queuer.test.ts @@ -5,14 +5,14 @@ describe('Queuer', () => { it('should create an empty queuer', () => { const fn = vi.fn() const queuer = new Queuer(fn, { started: false }) - expect(queuer.getIsEmpty()).toBe(true) - expect(queuer.getSize()).toBe(0) + expect(queuer.store.state.isEmpty).toBe(true) + expect(queuer.store.state.size).toBe(0) }) it('should start with started: true by default', () => { const fn = vi.fn() const queuer = new Queuer(fn, {}) - expect(queuer.getIsRunning()).toBe(true) + expect(queuer.store.state.isRunning).toBe(true) }) it('should respect maxSize option', () => { @@ -21,7 +21,7 @@ describe('Queuer', () => { expect(queuer.addItem(1)).toBe(true) expect(queuer.addItem(2)).toBe(true) expect(queuer.addItem(3)).toBe(false) - expect(queuer.getSize()).toBe(2) + expect(queuer.store.state.size).toBe(2) }) describe('addItem', () => { @@ -29,7 +29,7 @@ describe('Queuer', () => { const fn = vi.fn() const queuer = new Queuer(fn, { started: false }) expect(queuer.addItem(1)).toBe(true) - expect(queuer.getSize()).toBe(1) + expect(queuer.store.state.size).toBe(1) expect(queuer.peekNextItem()).toBe(1) }) }) @@ -63,7 +63,7 @@ describe('Queuer', () => { queuer.addItem(2) expect(queuer.peekNextItem()).toBe(1) - expect(queuer.getSize()).toBe(2) + expect(queuer.store.state.size).toBe(2) }) it('should return undefined when queuer is empty', () => { @@ -77,14 +77,14 @@ describe('Queuer', () => { it('should return true when queuer is empty', () => { const fn = vi.fn() const queuer = new Queuer(fn, { started: false }) - expect(queuer.getIsEmpty()).toBe(true) + expect(queuer.store.state.isEmpty).toBe(true) }) it('should return false when queuer has items', () => { const fn = vi.fn() const queuer = new Queuer(fn, { started: false }) queuer.addItem(1) - expect(queuer.getIsEmpty()).toBe(false) + expect(queuer.store.state.isEmpty).toBe(false) }) }) @@ -94,14 +94,14 @@ describe('Queuer', () => { const queuer = new Queuer(fn, { maxSize: 2, started: false }) queuer.addItem(1) queuer.addItem(2) - expect(queuer.getIsFull()).toBe(true) + expect(queuer.store.state.isFull).toBe(true) }) it('should return false when queuer is not full', () => { const fn = vi.fn() const queuer = new Queuer(fn, { maxSize: 2, started: false }) queuer.addItem(1) - expect(queuer.getIsFull()).toBe(false) + expect(queuer.store.state.isFull).toBe(false) }) }) @@ -113,8 +113,8 @@ describe('Queuer', () => { queuer.addItem(2) queuer.clear() - expect(queuer.getIsEmpty()).toBe(true) - expect(queuer.getSize()).toBe(0) + expect(queuer.store.state.isEmpty).toBe(true) + expect(queuer.store.state.size).toBe(0) expect(queuer.peekNextItem()).toBeUndefined() }) }) @@ -127,7 +127,7 @@ describe('Queuer', () => { initialItems: [1, 2, 3], started: false, }) - expect(queuer.getSize()).toBe(3) + expect(queuer.store.state.size).toBe(3) expect(queuer.peekAllItems()).toEqual([1, 2, 3]) }) @@ -153,7 +153,7 @@ describe('Queuer', () => { it('should handle empty initialItems array', () => { const fn = vi.fn() const queuer = new Queuer(fn, { initialItems: [], started: false }) - expect(queuer.getIsEmpty()).toBe(true) + expect(queuer.store.state.isEmpty).toBe(true) }) }) @@ -305,7 +305,7 @@ describe('Queuer', () => { expect(queuer.peekNextItem('front')).toBe(2) expect(queuer.peekNextItem('back')).toBe(3) - expect(queuer.getSize()).toBe(3) + expect(queuer.store.state.size).toBe(3) // Remove from both ends expect(queuer.execute('front')).toBe(2) @@ -313,41 +313,11 @@ describe('Queuer', () => { expect(queuer.execute('front')).toBe(1) }) - describe('setOptions', () => { - it('should update queuer options', () => { - const fn = vi.fn() - const queuer = new Queuer(fn, { wait: 100 }) - queuer.setOptions({ wait: 200 }) - expect(queuer.getWait()).toBe(200) - }) - }) - - describe('getOptions', () => { - it('should return current queuer options', () => { - const fn = vi.fn() - const options = { wait: 123, maxSize: 5 } - const queuer = new Queuer(fn, options) - const result = queuer.getOptions() - expect(result.wait).toBe(123) - expect(result.maxSize).toBe(5) - }) - }) - - describe('getWait', () => { - it('should return the current wait time', () => { - const fn = vi.fn() - const queuer = new Queuer(fn, { wait: 42 }) - expect(queuer.getWait()).toBe(42) - }) - }) - describe('reset', () => { it('should reset the queuer to its initial state', () => { const fn = vi.fn() const queuer = new Queuer(fn, { initialItems: [1, 2], started: false }) queuer.addItem(3) - queuer.reset(true) - expect(queuer.peekAllItems()).toEqual([1, 2]) queuer.reset() expect(queuer.peekAllItems()).toEqual([]) }) @@ -357,9 +327,9 @@ describe('Queuer', () => { it('should start the queuer', () => { const fn = vi.fn() const queuer = new Queuer(fn, { started: false }) - expect(queuer.getIsRunning()).toBe(false) + expect(queuer.store.state.isRunning).toBe(false) queuer.start() - expect(queuer.getIsRunning()).toBe(true) + expect(queuer.store.state.isRunning).toBe(true) }) }) @@ -367,9 +337,9 @@ describe('Queuer', () => { it('should stop the queuer', () => { const fn = vi.fn() const queuer = new Queuer(fn, { started: true }) - expect(queuer.getIsRunning()).toBe(true) + expect(queuer.store.state.isRunning).toBe(true) queuer.stop() - expect(queuer.getIsRunning()).toBe(false) + expect(queuer.store.state.isRunning).toBe(false) }) }) @@ -381,7 +351,7 @@ describe('Queuer', () => { queuer.addItem(2) queuer.execute() queuer.execute() - expect(queuer.getExecutionCount()).toBe(2) + expect(queuer.store.state.executionCount).toBe(2) }) }) @@ -391,7 +361,7 @@ describe('Queuer', () => { const queuer = new Queuer(fn, { maxSize: 1, started: false }) queuer.addItem(1) queuer.addItem(2) - expect(queuer.getRejectionCount()).toBe(1) + expect(queuer.store.state.rejectionCount).toBe(1) }) }) @@ -412,14 +382,5 @@ describe('Queuer', () => { queuer.execute() expect(onExecute).toHaveBeenCalledWith(1, queuer) }) - it('should call onIsRunningChange when running state changes', () => { - const onIsRunningChange = vi.fn() - const fn = vi.fn() - const queuer = new Queuer(fn, { onIsRunningChange, started: false }) - queuer.start() - queuer.stop() - expect(onIsRunningChange).toHaveBeenCalledTimes(2) - expect(onIsRunningChange).toHaveBeenCalledWith(queuer) - }) }) }) diff --git a/packages/react-pacer/src/queuer/useQueuedState.ts b/packages/react-pacer/src/queuer/useQueuedState.ts index 66e23b5ea..bfdb4b335 100644 --- a/packages/react-pacer/src/queuer/useQueuedState.ts +++ b/packages/react-pacer/src/queuer/useQueuedState.ts @@ -1,6 +1,6 @@ -import { useState } from 'react' import { useQueuer } from './useQueuer' -import type { Queuer, QueuerOptions } from '@tanstack/pacer/queuer' +import type { ReactQueuer } from './useQueuer' +import type { Queuer, QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' /** * A React hook that creates a queuer with managed state, combining React's useState with queuing functionality. @@ -51,25 +51,15 @@ import type { Queuer, QueuerOptions } from '@tanstack/pacer/queuer' * }; * ``` */ -export function useQueuedState( +export function useQueuedState< + TValue, + TSelected extends Pick, 'items'> = QueuerState, +>( fn: (item: TValue) => void, options: QueuerOptions = {}, -): [Array, Queuer['addItem'], Queuer] { - const [allItems, setAllItems] = useState>( - options.initialItems || [], - ) + selector?: (state: QueuerState) => TSelected, +): [Array, Queuer['addItem'], ReactQueuer] { + const queue = useQueuer(fn, options, selector) - const queue = useQueuer(fn, { - ...options, - onItemsChange: (queue) => { - setAllItems(queue.peekAllItems()) - options.onItemsChange?.(queue) - }, - onIsRunningChange: (queue) => { - setAllItems((prev) => [...prev]) // rerender - options.onIsRunningChange?.(queue) - }, - }) - - return [allItems, queue.addItem, queue] + return [queue.state.items, queue.addItem, queue] } diff --git a/packages/react-pacer/src/queuer/useQueuedValue.ts b/packages/react-pacer/src/queuer/useQueuedValue.ts index 2dae097a1..d4c08dcc2 100644 --- a/packages/react-pacer/src/queuer/useQueuedValue.ts +++ b/packages/react-pacer/src/queuer/useQueuedValue.ts @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react' import { useQueuedState } from './useQueuedState' -import type { Queuer, QueuerOptions } from '@tanstack/pacer/queuer' +import type { ReactQueuer } from './useQueuer' +import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' /** * A React hook that creates a queued value that processes state changes in order with an optional delay. @@ -37,20 +38,22 @@ import type { Queuer, QueuerOptions } from '@tanstack/pacer/queuer' * }; * ``` */ -export function useQueuedValue( +export function useQueuedValue< + TValue, + TSelected extends Pick, 'items'> = QueuerState, +>( initialValue: TValue, options: QueuerOptions = {}, -): [TValue, Queuer] { + selector?: (state: QueuerState) => TSelected, +): [TValue, ReactQueuer] { const [value, setValue] = useState(initialValue) - const [, addItem, queuer] = useQueuedState( + const [, addItem, queuer] = useQueuedState( (item) => { setValue(item) }, - { - started: true, - ...options, - }, + options, + selector, ) useEffect(() => { diff --git a/packages/react-pacer/src/queuer/useQueuer.ts b/packages/react-pacer/src/queuer/useQueuer.ts index f8f06f015..ce5c0696e 100644 --- a/packages/react-pacer/src/queuer/useQueuer.ts +++ b/packages/react-pacer/src/queuer/useQueuer.ts @@ -1,6 +1,17 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { Queuer } from '@tanstack/pacer/queuer' -import type { QueuerOptions } from '@tanstack/pacer/queuer' +import { useStore } from '@tanstack/react-store' +import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' + +export interface ReactQueuer> + extends Omit, 'store'> { + /** + * Reactive state that will be updated and re-rendered when the queuer state changes + * + * Use this instead of `queuer.store.state` + */ + state: TSelected +} /** * A React hook that creates and manages a Queuer instance. @@ -40,13 +51,23 @@ import type { QueuerOptions } from '@tanstack/pacer/queuer' * queue.start(); // Resume processing * ``` */ -export function useQueuer( +export function useQueuer>( fn: (item: TValue) => void, options: QueuerOptions = {}, -): Queuer { + selector?: (state: QueuerState) => TSelected, +): ReactQueuer { const [queuer] = useState(() => new Queuer(fn, options)) + const state = useStore(queuer.store, selector) + queuer.setOptions(options) - return queuer + return useMemo( + () => + ({ + ...queuer, + state, + }) as unknown as ReactQueuer, // omit `store` in favor of `state` + [queuer, state], + ) } diff --git a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts index 45794b745..8aef21cfd 100644 --- a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts +++ b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts @@ -80,5 +80,5 @@ export function createAsyncQueuer>( return { ...asyncQueuer, state, - } as unknown as SolidAsyncQueuer + } as unknown as SolidAsyncQueuer // omit `store` in favor of `state` } diff --git a/packages/solid-pacer/src/queuer/createQueuer.ts b/packages/solid-pacer/src/queuer/createQueuer.ts index 1daf425d5..9c8b31c0a 100644 --- a/packages/solid-pacer/src/queuer/createQueuer.ts +++ b/packages/solid-pacer/src/queuer/createQueuer.ts @@ -1,56 +1,16 @@ import { Queuer } from '@tanstack/pacer/queuer' -import { createSignal } from 'solid-js' +import { useStore } from '@tanstack/solid-store' import type { Accessor } from 'solid-js' -import type { QueuerOptions } from '@tanstack/pacer/queuer' +import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' -export interface SolidQueuer - extends Omit< - Queuer, - | 'getExecutionCount' - | 'getIsEmpty' - | 'getIsFull' - | 'getIsIdle' - | 'getIsRunning' - | 'getSize' - | 'peekAllItems' - | 'peekNextItem' - > { +export interface SolidQueuer> + extends Omit, 'store'> { /** - * Signal version of `peekAllItems` + * Reactive state that will be updated when the queuer state changes + * + * Use this instead of `queuer.store.state` */ - allItems: Accessor> - /** - * Signal version of `getExecutionCount` - */ - executionCount: Accessor - /** - * Signal version of `getIsEmpty` - */ - isEmpty: Accessor - /** - * Signal version of `getIsFull` - */ - isFull: Accessor - /** - * Signal version of `getIsIdle` - */ - isIdle: Accessor - /** - * Signal version of `getIsRunning` - */ - isRunning: Accessor - /** - * Signal version of `peekNextItem` - */ - nextItem: Accessor - /** - * Signal version of `getRejectionCount` - */ - rejectionCount: Accessor - /** - * Signal version of `getSize` - */ - size: Accessor + state: Accessor } /** @@ -104,76 +64,17 @@ export interface SolidQueuer * console.log('Next item:', queue.nextItem()); * ``` */ -export function createQueuer( +export function createQueuer>( fn: (item: TValue) => void, initialOptions: QueuerOptions = {}, -): SolidQueuer { - const queuer = new Queuer(fn, initialOptions) - - const [allItems, setAllItems] = createSignal>( - queuer.peekAllItems(), - ) - const [executionCount, setExecutionCount] = createSignal( - queuer.getExecutionCount(), - ) - const [rejectionCount, setRejectionCount] = createSignal( - queuer.getRejectionCount(), - ) - const [isEmpty, setIsEmpty] = createSignal(queuer.getIsEmpty()) - const [isFull, setIsFull] = createSignal(queuer.getIsFull()) - const [isIdle, setIsIdle] = createSignal(queuer.getIsIdle()) - const [isRunning, setIsRunning] = createSignal(queuer.getIsRunning()) - const [nextItem, setNextItem] = createSignal( - queuer.peekNextItem(), - ) - const [size, setSize] = createSignal(queuer.getSize()) - - function setOptions(newOptions: Partial>) { - queuer.setOptions({ - ...newOptions, - onItemsChange: (queuer) => { - setAllItems(queuer.peekAllItems()) - setExecutionCount(queuer.getExecutionCount()) - setIsEmpty(queuer.getIsEmpty()) - setIsFull(queuer.getIsFull()) - setIsIdle(queuer.getIsIdle()) - setNextItem(() => queuer.peekNextItem()) - setSize(queuer.getSize()) - - const onItemsChange = - newOptions.onItemsChange ?? initialOptions.onItemsChange - onItemsChange?.(queuer) - }, - onIsRunningChange: (queuer) => { - setIsRunning(queuer.getIsRunning()) - setIsIdle(queuer.getIsIdle()) - - const onIsRunningChange = - newOptions.onIsRunningChange ?? initialOptions.onIsRunningChange - onIsRunningChange?.(queuer) - }, - onReject: (item, queuer) => { - setRejectionCount(queuer.getRejectionCount()) - - const onReject = newOptions.onReject ?? initialOptions.onReject - onReject?.(item, queuer) - }, - }) - } + selector?: (state: QueuerState) => TSelected, +): SolidQueuer { + const queuer = new Queuer(fn, initialOptions) - setOptions(initialOptions) + const state = useStore(queuer.store, selector) return { ...queuer, - allItems, - executionCount, - isEmpty, - isFull, - isIdle, - isRunning, - nextItem, - rejectionCount, - size, - setOptions, - } as SolidQueuer + state, + } as unknown as SolidQueuer // omit `store` in favor of `state` } diff --git a/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts b/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts index f39956652..22d9103b9 100644 --- a/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts +++ b/packages/solid-pacer/src/rate-limiter/createRateLimitedSignal.ts @@ -84,7 +84,7 @@ export function createRateLimitedSignal( return [ rateLimitedValue, - rateLimiter.maybeExecute.bind(rateLimiter) as Setter, + rateLimiter.maybeExecute as Setter, rateLimiter, ] } From 8c6e25b3e6fe47c0f72cf4f5a78fc89291009d1c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 6 Jul 2025 20:53:03 +0000 Subject: [PATCH 28/51] ci: apply automated fixes --- .../solid/reference/functions/createqueuer.md | 15 ++- .../solid/reference/interfaces/solidqueuer.md | 118 ++---------------- 2 files changed, 21 insertions(+), 112 deletions(-) diff --git a/docs/framework/solid/reference/functions/createqueuer.md b/docs/framework/solid/reference/functions/createqueuer.md index 2f8e23191..7c3ea7645 100644 --- a/docs/framework/solid/reference/functions/createqueuer.md +++ b/docs/framework/solid/reference/functions/createqueuer.md @@ -8,10 +8,13 @@ title: createQueuer # Function: createQueuer() ```ts -function createQueuer(fn, initialOptions): SolidQueuer +function createQueuer( + fn, + initialOptions, +selector?): SolidQueuer ``` -Defined in: [queuer/createQueuer.ts:107](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L107) +Defined in: [queuer/createQueuer.ts:67](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L67) Creates a Solid-compatible Queuer instance for managing a synchronous queue of items, exposing Solid signals for all stateful properties. @@ -67,6 +70,8 @@ console.log('Next item:', queue.nextItem()); • **TValue** +• **TSelected** = `QueuerState`\<`TValue`\> + ## Parameters ### fn @@ -77,6 +82,10 @@ console.log('Next item:', queue.nextItem()); `QueuerOptions`\<`TValue`\> = `{}` +### selector? + +(`state`) => `TSelected` + ## Returns -[`SolidQueuer`](../../interfaces/solidqueuer.md)\<`TValue`\> +[`SolidQueuer`](../../interfaces/solidqueuer.md)\<`TValue`, `TSelected`\> diff --git a/docs/framework/solid/reference/interfaces/solidqueuer.md b/docs/framework/solid/reference/interfaces/solidqueuer.md index 170cf61e9..fd365a917 100644 --- a/docs/framework/solid/reference/interfaces/solidqueuer.md +++ b/docs/framework/solid/reference/interfaces/solidqueuer.md @@ -5,130 +5,30 @@ title: SolidQueuer -# Interface: SolidQueuer\ +# Interface: SolidQueuer\ Defined in: [queuer/createQueuer.ts:6](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L6) ## Extends -- `Omit`\<`Queuer`\<`TValue`\>, - \| `"getExecutionCount"` - \| `"getIsEmpty"` - \| `"getIsFull"` - \| `"getIsIdle"` - \| `"getIsRunning"` - \| `"getSize"` - \| `"peekAllItems"` - \| `"peekNextItem"`\> +- `Omit`\<`Queuer`\<`TValue`\>, `"store"`\> ## Type Parameters • **TValue** -## Properties - -### allItems - -```ts -allItems: Accessor; -``` - -Defined in: [queuer/createQueuer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L21) - -Signal version of `peekAllItems` - -*** - -### executionCount - -```ts -executionCount: Accessor; -``` - -Defined in: [queuer/createQueuer.ts:25](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L25) - -Signal version of `getExecutionCount` - -*** - -### isEmpty - -```ts -isEmpty: Accessor; -``` - -Defined in: [queuer/createQueuer.ts:29](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L29) - -Signal version of `getIsEmpty` - -*** - -### isFull - -```ts -isFull: Accessor; -``` - -Defined in: [queuer/createQueuer.ts:33](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L33) - -Signal version of `getIsFull` +• **TSelected** = `QueuerState`\<`TValue`\> -*** - -### isIdle - -```ts -isIdle: Accessor; -``` - -Defined in: [queuer/createQueuer.ts:37](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L37) - -Signal version of `getIsIdle` - -*** - -### isRunning - -```ts -isRunning: Accessor; -``` - -Defined in: [queuer/createQueuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L41) - -Signal version of `getIsRunning` - -*** - -### nextItem - -```ts -nextItem: Accessor; -``` - -Defined in: [queuer/createQueuer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L45) - -Signal version of `peekNextItem` - -*** +## Properties -### rejectionCount +### state ```ts -rejectionCount: Accessor; +state: Accessor; ``` -Defined in: [queuer/createQueuer.ts:49](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L49) - -Signal version of `getRejectionCount` - -*** - -### size - -```ts -size: Accessor; -``` +Defined in: [queuer/createQueuer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L13) -Defined in: [queuer/createQueuer.ts:53](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L53) +Reactive state that will be updated when the queuer state changes -Signal version of `getSize` +Use this instead of `queuer.store.state` From 954c8b2ac082fffc9d805581760d9e78fbc716e3 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 6 Jul 2025 15:58:13 -0500 Subject: [PATCH 29/51] fix solid debouncer examples --- examples/solid/createDebouncedSignal/src/index.tsx | 13 ++++++++----- examples/solid/createDebouncedValue/src/index.tsx | 9 ++++++--- examples/solid/createDebouncer/src/index.tsx | 10 +++++----- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/examples/solid/createDebouncedSignal/src/index.tsx b/examples/solid/createDebouncedSignal/src/index.tsx index 2dd444bef..71180828b 100644 --- a/examples/solid/createDebouncedSignal/src/index.tsx +++ b/examples/solid/createDebouncedSignal/src/index.tsx @@ -32,7 +32,7 @@ function App1() {
    Execution Count:{debouncer.executionCount()}{debouncer.state().executionCount}
    @@ -89,7 +89,7 @@ function App2() {
    Execution Count:{debouncer.executionCount()}{debouncer.state().executionCount}
    @@ -169,11 +169,13 @@ function App3() {
    Debounced Executions:{debouncer.executionCount()}{debouncer.state().executionCount}
    Saved Executions:{instantExecutionCount() - debouncer.executionCount()} + {instantExecutionCount() - debouncer.state().executionCount} +
    % Reduction:
    Debounced Executions:{debouncer.executionCount()}{debouncer.state().executionCount}
    Saved Executions:{instantExecutionCount() - debouncer.executionCount()} + {instantExecutionCount() - debouncer.state().executionCount} +
    % Reduction:
    Execution Count:{setCountDebouncer.store.executionCount}{setCountDebouncer.state().executionCount}
    @@ -88,7 +88,7 @@ function App2() {
    Execution Count:{setSearchDebouncer.store.executionCount}{setSearchDebouncer.state().executionCount}
    @@ -166,12 +166,12 @@ function App3() {
    Debounced Executions:{setValueDebouncer.store.executionCount}{setValueDebouncer.state().executionCount}
    Saved Executions: - {instantExecutionCount() - setValueDebouncer.store.executionCount} + {instantExecutionCount() - setValueDebouncer.state().executionCount}
    Saved Executions: - {instantExecutionCount() - setValueDebouncer.state().executionCount} + {instantExecutionCount() - + setValueDebouncer.state().executionCount}
    API calls made:{setSearchAsyncRateLimiter.getSuccessCount()}{setSearchAsyncRateLimiter.state.successCount}
    Rejected calls:{setSearchAsyncRateLimiter.getRejectionCount()}{setSearchAsyncRateLimiter.state.rejectionCount}
    Is executing: - {setSearchAsyncRateLimiter.getIsExecuting() ? 'Yes' : 'No'} + {setSearchAsyncRateLimiter.state.isExecuting ? 'Yes' : 'No'}
    +
    +        {JSON.stringify(setSearchAsyncRateLimiter.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useAsyncThrottler/src/index.tsx b/examples/react/useAsyncThrottler/src/index.tsx index 5e0a954cc..9a9608c76 100644 --- a/examples/react/useAsyncThrottler/src/index.tsx +++ b/examples/react/useAsyncThrottler/src/index.tsx @@ -76,6 +76,9 @@ function App() { autoComplete="new-password" /> +
    + +
    {error &&
    Error: {error.message}
    }

    API calls made: {setSearchAsyncThrottler.state.successCount}

    @@ -91,6 +94,9 @@ function App() { ) : setSearchAsyncThrottler.state.isExecuting ? (

    Executing...

    ) : null} +
    +          {JSON.stringify(setSearchAsyncThrottler.state, null, 2)}
    +        
    ) @@ -103,7 +109,7 @@ root.render() // demo unmounting and cancellation document.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { + if (e.shiftKey && e.key === 'Enter') { mounted = !mounted root.render(mounted ? : null) } diff --git a/examples/react/useBatcher/src/index.tsx b/examples/react/useBatcher/src/index.tsx index c158a977c..f9a26f521 100644 --- a/examples/react/useBatcher/src/index.tsx +++ b/examples/react/useBatcher/src/index.tsx @@ -4,7 +4,6 @@ import { useBatcher } from '@tanstack/react-pacer/batcher' function App1() { // Use your state management library of choice - const [batchItems, setBatchItems] = useState>([]) const [processedBatches, setProcessedBatches] = useState< Array> >([]) @@ -20,9 +19,6 @@ function App1() { maxSize: 5, // Process in batches of 5 (if comes before wait time) wait: 3000, // wait up to 3 seconds before processing a batch (if time elapses before maxSize is reached) getShouldExecute: (items, _batcher) => items.includes(42), // or pass in a custom function to determine if the batch should be processed - onItemsChange: (batcher) => { - setBatchItems(batcher.peekAllItems()) - }, }) return ( @@ -30,7 +26,7 @@ function App1() {

    TanStack Pacer useBatcher Example 1

    Batch Size: {batcher.state.size}
    Batch Max Size: {3}
    -
    Batch Items: {batchItems.join(', ')}
    +
    Batch Items: {batcher.peekAllItems().join(', ')}
    Batches Processed: {batcher.state.executionCount}
    Items Processed: {batcher.state.totalItemsProcessed}
    @@ -52,8 +48,8 @@ function App1() { >
    +
    +        {JSON.stringify(batcher.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useDebouncedState/src/index.tsx b/examples/react/useDebouncedState/src/index.tsx index 7c672bdb5..fd1ea2c30 100644 --- a/examples/react/useDebouncedState/src/index.tsx +++ b/examples/react/useDebouncedState/src/index.tsx @@ -56,6 +56,9 @@ function App1() {
    +
    +        {JSON.stringify(debouncer.state, null, 2)}
    +      
    ) } @@ -116,6 +119,9 @@ function App2() {
    +
    +        {JSON.stringify(debouncer.state, null, 2)}
    +      
    ) } @@ -206,6 +212,9 @@ function App3() {

    Debounced to 250ms wait time

    +
    +        {JSON.stringify(debouncer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useDebouncedValue/src/index.tsx b/examples/react/useDebouncedValue/src/index.tsx index 808dbe019..d46d5b57d 100644 --- a/examples/react/useDebouncedValue/src/index.tsx +++ b/examples/react/useDebouncedValue/src/index.tsx @@ -163,6 +163,9 @@ function App3() {

    Debounced to 250ms wait time

    +
    +        {JSON.stringify(debouncer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useDebouncer/src/index.tsx b/examples/react/useDebouncer/src/index.tsx index ad4f48872..c0adb7984 100644 --- a/examples/react/useDebouncer/src/index.tsx +++ b/examples/react/useDebouncer/src/index.tsx @@ -8,9 +8,9 @@ function App1() { const [debouncedCount, setDebouncedCount] = useState(0) // Lower-level useDebouncer hook - requires you to manage your own state - const setCountDebouncer = useDebouncer(setDebouncedCount, { - wait: 500, - // enabled: () => instantCount > 2, // optional, defaults to true + const debouncer = useDebouncer(setDebouncedCount, { + wait: 800, + enabled: () => instantCount > 2, // optional, defaults to true // leading: true, // optional, defaults to false }) @@ -18,7 +18,7 @@ function App1() { // this pattern helps avoid common bugs with stale closures and state setInstantCount((c) => { const newInstantCount = c + 1 // common new value for both - setCountDebouncer.maybeExecute(newInstantCount) // debounced state update + debouncer.maybeExecute(newInstantCount) // debounced state update return newInstantCount // instant state update }) } @@ -29,12 +29,12 @@ function App1() { - - + + - +
    Is Pending:{setCountDebouncer.state.isPending.toString()}Status:{debouncer.state.status}
    Execution Count:{setCountDebouncer.state.executionCount}{debouncer.state.executionCount}
    @@ -53,7 +53,16 @@ function App1() {
    +
    +
    +        {JSON.stringify(debouncer.state, null, 2)}
    +      
    ) } @@ -112,6 +121,12 @@ function App2() {
    +
    + +
    +
    +        {JSON.stringify(setSearchDebouncer.state, null, 2)}
    +      
    ) } @@ -203,6 +218,12 @@ function App3() {

    Debounced to 250ms wait time

    +
    + +
    +
    +        {JSON.stringify(setValueDebouncer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useQueuedState/src/index.tsx b/examples/react/useQueuedState/src/index.tsx index d0306da97..f4dcae537 100644 --- a/examples/react/useQueuedState/src/index.tsx +++ b/examples/react/useQueuedState/src/index.tsx @@ -74,6 +74,9 @@ function App1() { Stop Processing
    +
    +        {JSON.stringify(queuer.state, null, 2)}
    +      
    ) } @@ -185,6 +188,9 @@ function App2() {

    Queued with 100ms wait time

    +
    +        {JSON.stringify(queuer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useQueuedValue/src/index.tsx b/examples/react/useQueuedValue/src/index.tsx index 21b8843aa..c395ce4a3 100644 --- a/examples/react/useQueuedValue/src/index.tsx +++ b/examples/react/useQueuedValue/src/index.tsx @@ -70,6 +70,9 @@ function App1() { Stop Processing +
    +        {JSON.stringify(queuer.state, null, 2)}
    +      
    ) } @@ -173,6 +176,9 @@ function App2() {

    Queued with 100ms wait time

    +
    +        {JSON.stringify(queuer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useQueuer/src/index.tsx b/examples/react/useQueuer/src/index.tsx index afbc2dab6..d07a9b664 100644 --- a/examples/react/useQueuer/src/index.tsx +++ b/examples/react/useQueuer/src/index.tsx @@ -21,8 +21,8 @@ function App1() { const queuer = useQueuer(processItem, { initialItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], initialState: queuerPersister.loadState(), - maxSize: 25, - started: false, + maxSize: 25, // optional, defaults to Infinity + started: false, // optional, defaults to true wait: 1000, // wait 1 second between processing items - wait is optional! }) @@ -89,8 +89,13 @@ function App1() { > Stop Processing + -
    {JSON.stringify(queuer.state, null, 2)}
    +
    +        {JSON.stringify(queuer.state, null, 2)}
    +      
    ) } @@ -200,6 +205,12 @@ function App2() {

    Queued with 100ms wait time

    +
    + +
    +
    +        {JSON.stringify(queuer.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useRateLimitedState/src/index.tsx b/examples/react/useRateLimitedState/src/index.tsx index 371990bfb..0d364cdd2 100644 --- a/examples/react/useRateLimitedState/src/index.tsx +++ b/examples/react/useRateLimitedState/src/index.tsx @@ -59,6 +59,9 @@ function App1() { +
    +        {JSON.stringify(rateLimiter.state, null, 2)}
    +      
    ) } @@ -126,6 +129,9 @@ function App2() { +
    +        {JSON.stringify(rateLimiter.state, null, 2)}
    +      
    ) } @@ -231,6 +237,9 @@ function App3() {

    Rate limited to 20 updates per 2 seconds

    +
    +        {JSON.stringify(rateLimiter.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useRateLimitedValue/src/index.tsx b/examples/react/useRateLimitedValue/src/index.tsx index 3dd0d8a58..e9c58cf4a 100644 --- a/examples/react/useRateLimitedValue/src/index.tsx +++ b/examples/react/useRateLimitedValue/src/index.tsx @@ -189,6 +189,9 @@ function App3() {

    Rate limited to 20 updates per 2 seconds

    +
    +        {JSON.stringify(rateLimiter.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index 6039c2093..bac215f2f 100644 --- a/examples/react/useRateLimiter/src/index.tsx +++ b/examples/react/useRateLimiter/src/index.tsx @@ -84,6 +84,9 @@ function App1() { +
    +        {JSON.stringify(rateLimiter.state, null, 2)}
    +      
    ) } @@ -159,6 +162,9 @@ function App2() {
    +
    +        {JSON.stringify(rateLimiter.state, null, 2)}
    +      
    ) } @@ -262,6 +268,9 @@ function App3() {

    Rate limited to 20 updates per 2 seconds

    +
    +        {JSON.stringify(rateLimiter.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useThrottledState/src/index.tsx b/examples/react/useThrottledState/src/index.tsx index c28acd411..2151aa029 100644 --- a/examples/react/useThrottledState/src/index.tsx +++ b/examples/react/useThrottledState/src/index.tsx @@ -46,6 +46,9 @@ function App1() {
    +
    +        {JSON.stringify(throttler.state, null, 2)}
    +      
    ) } @@ -97,6 +100,9 @@ function App2() { +
    +        {JSON.stringify(throttler.state, null, 2)}
    +      
    ) } @@ -180,6 +186,9 @@ function App3() {

    Throttled to 1 update per 250ms

    +
    +        {JSON.stringify(throttler.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useThrottledValue/src/index.tsx b/examples/react/useThrottledValue/src/index.tsx index 4758eb365..bb3bc95da 100644 --- a/examples/react/useThrottledValue/src/index.tsx +++ b/examples/react/useThrottledValue/src/index.tsx @@ -155,6 +155,9 @@ function App3() {

    Throttled to 1 update per 250ms

    +
    +        {JSON.stringify(throttler.state, null, 2)}
    +      
    ) } diff --git a/examples/react/useThrottler/src/index.tsx b/examples/react/useThrottler/src/index.tsx index 069d73ad5..a5c14caff 100644 --- a/examples/react/useThrottler/src/index.tsx +++ b/examples/react/useThrottler/src/index.tsx @@ -45,7 +45,16 @@ function App1() {
    +
    +
    +        {JSON.stringify(setCountThrottler.state, null, 2)}
    +      
    ) } @@ -95,6 +104,12 @@ function App2() { +
    + +
    +
    +        {JSON.stringify(setSearchThrottler.state, null, 2)}
    +      
    ) } @@ -183,6 +198,12 @@ function App3() {

    Throttled to 1 update per 250ms (trailing edge)

    +
    + +
    +
    +        {JSON.stringify(setValueThrottler.state, null, 2)}
    +      
    ) } diff --git a/packages/pacer/src/async-batcher.ts b/packages/pacer/src/async-batcher.ts index 7de478ef3..f70b77534 100644 --- a/packages/pacer/src/async-batcher.ts +++ b/packages/pacer/src/async-batcher.ts @@ -1,4 +1,5 @@ import { Store } from '@tanstack/store' +import { parseFunctionOrValue } from './utils' import type { OptionalKeys } from './types' export interface AsyncBatcherState { @@ -143,7 +144,7 @@ export interface AsyncBatcherOptions { * If not provided, the batch will not be triggered by a timeout. * @default Infinity */ - wait?: number + wait?: number | ((asyncBatcher: AsyncBatcher) => number) } type AsyncBatcherOptionsWithOptionalCallbacks = OptionalKeys< @@ -275,6 +276,10 @@ export class AsyncBatcher { }) } + #getWait = (): number => { + return parseFunctionOrValue(this.options.wait, this) + } + /** * Adds an item to the async batcher * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed @@ -292,12 +297,9 @@ export class AsyncBatcher { if (shouldProcess) { this.#execute() - } else if ( - this.store.state.isRunning && - !this.#timeoutId && - this.options.wait !== Infinity - ) { - this.#timeoutId = setTimeout(() => this.#execute(), this.options.wait) + } else if (this.store.state.isRunning && this.options.wait !== Infinity) { + this.#clearTimeout() // clear any pending timeout to replace it with a new one + this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait()) } } @@ -377,7 +379,7 @@ export class AsyncBatcher { start = (): void => { this.#setState({ isRunning: true }) if (this.store.state.items.length > 0 && !this.#timeoutId) { - this.#timeoutId = setTimeout(() => this.#execute(), this.options.wait) + this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait()) } } @@ -411,6 +413,7 @@ export class AsyncBatcher { */ reset = (): void => { this.#setState(getDefaultAsyncBatcherState()) + this.options.onItemsChange?.(this) } } diff --git a/packages/pacer/src/async-debouncer.ts b/packages/pacer/src/async-debouncer.ts index 2fc1479cd..c7ab604f2 100644 --- a/packages/pacer/src/async-debouncer.ts +++ b/packages/pacer/src/async-debouncer.ts @@ -34,7 +34,7 @@ export interface AsyncDebouncerState { /** * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed */ - status: 'idle' | 'pending' | 'executing' | 'settled' + status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled' /** * Number of function executions that have completed successfully */ @@ -210,13 +210,15 @@ export class AsyncDebouncer { const { isPending, isExecuting, settleCount } = combinedState return { ...combinedState, - status: isPending - ? 'pending' - : isExecuting - ? 'executing' - : settleCount > 0 - ? 'settled' - : 'idle', + status: !this.#getEnabled() + ? 'disabled' + : isPending + ? 'pending' + : isExecuting + ? 'executing' + : settleCount > 0 + ? 'settled' + : 'idle', } }) } diff --git a/packages/pacer/src/async-queuer.ts b/packages/pacer/src/async-queuer.ts index 971cd7828..b9cf97f73 100644 --- a/packages/pacer/src/async-queuer.ts +++ b/packages/pacer/src/async-queuer.ts @@ -693,6 +693,7 @@ export class AsyncQueuer { */ reset = (): void => { this.#setState(getDefaultAsyncQueuerState()) + this.options.onItemsChange?.(this) } } diff --git a/packages/pacer/src/async-throttler.ts b/packages/pacer/src/async-throttler.ts index ca4a33988..a387e73b4 100644 --- a/packages/pacer/src/async-throttler.ts +++ b/packages/pacer/src/async-throttler.ts @@ -38,7 +38,7 @@ export interface AsyncThrottlerState { /** * Current execution status - 'idle' when not active, 'pending' when waiting, 'executing' when running, 'settled' when completed */ - status: 'idle' | 'pending' | 'executing' | 'settled' + status: 'disabled' | 'idle' | 'pending' | 'executing' | 'settled' /** * Number of function executions that have completed successfully */ @@ -221,13 +221,15 @@ export class AsyncThrottler { const { isPending, isExecuting, settleCount } = combinedState return { ...combinedState, - status: isPending - ? 'pending' - : isExecuting - ? 'executing' - : settleCount > 0 - ? 'settled' - : 'idle', + status: !this.#getEnabled() + ? 'disabled' + : isPending + ? 'pending' + : isExecuting + ? 'executing' + : settleCount > 0 + ? 'settled' + : 'idle', } }) } @@ -275,6 +277,8 @@ export class AsyncThrottler { 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.#setState({ lastArgs: args }) this.#resolvePreviousPromiseInternal() @@ -283,9 +287,6 @@ export class AsyncThrottler { await this.#execute(...args) return this.store.state.lastResult } else { - // Store the most recent arguments for potential trailing execution - this.#setState({ lastArgs: args }) - return new Promise((resolve) => { this.#resolvePreviousPromise = resolve // Clear any existing timeout to ensure we use the latest arguments diff --git a/packages/pacer/src/batcher.ts b/packages/pacer/src/batcher.ts index 2d92ec9d6..2a79c9c35 100644 --- a/packages/pacer/src/batcher.ts +++ b/packages/pacer/src/batcher.ts @@ -1,4 +1,5 @@ import { Store } from '@tanstack/store' +import { parseFunctionOrValue } from './utils' import type { OptionalKeys } from './types' export interface BatcherState { @@ -86,7 +87,7 @@ export interface BatcherOptions { * If not provided, the batch will not be triggered by a timeout. * @default Infinity */ - wait?: number + wait?: number | ((batcher: Batcher) => number) } type BatcherOptionsWithOptionalCallbacks = OptionalKeys< @@ -182,6 +183,10 @@ export class Batcher { }) } + #getWait = (): number => { + return parseFunctionOrValue(this.options.wait, this) + } + /** * Adds an item to the batcher * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed @@ -199,12 +204,9 @@ export class Batcher { if (shouldProcess) { this.#execute() - } else if ( - this.store.state.isRunning && - !this.#timeoutId && - this.options.wait !== Infinity - ) { - this.#timeoutId = setTimeout(() => this.#execute(), this.options.wait) + } else if (this.store.state.isRunning && this.options.wait !== Infinity) { + this.#clearTimeout() // clear any pending timeout to replace it with a new one + this.#timeoutId = setTimeout(() => this.#execute(), this.#getWait()) } } @@ -255,8 +257,8 @@ export class Batcher { */ start = (): void => { this.#setState({ isRunning: true }) - if (this.store.state.items.length > 0 && !this.#timeoutId) { - this.#timeoutId = setTimeout(() => this.#execute(), this.options.wait) + if (this.store.state.items.length > 0) { + this.#execute() } } @@ -286,6 +288,7 @@ export class Batcher { */ reset = (): void => { this.#setState(getDefaultBatcherState()) + this.options.onItemsChange?.(this) } } diff --git a/packages/pacer/src/debouncer.ts b/packages/pacer/src/debouncer.ts index 9d4712201..6ad7c6b80 100644 --- a/packages/pacer/src/debouncer.ts +++ b/packages/pacer/src/debouncer.ts @@ -22,7 +22,7 @@ export interface DebouncerState { /** * Current execution status - 'idle' when not active, 'pending' when waiting for timeout */ - status: 'idle' | 'pending' + status: 'disabled' | 'idle' | 'pending' } function getDefaultDebouncerState< @@ -154,7 +154,11 @@ export class Debouncer { const { isPending } = combinedState return { ...combinedState, - status: isPending ? 'pending' : 'idle', + status: !this.#getEnabled() + ? 'disabled' + : isPending + ? 'pending' + : 'idle', } }) } @@ -190,7 +194,7 @@ export class Debouncer { // Start pending state to indicate that the debouncer is waiting for the trailing edge if (this.options.trailing) { - this.#setState({ isPending: true }) + this.#setState({ isPending: true, lastArgs: args }) } // Clear any existing timeout @@ -211,7 +215,6 @@ export class Debouncer { this.#setState({ isPending: false, executionCount: this.store.state.executionCount + 1, - lastArgs: args, }) this.options.onExecute?.(this) } diff --git a/packages/pacer/src/queuer.ts b/packages/pacer/src/queuer.ts index 4856379cf..445a9420a 100644 --- a/packages/pacer/src/queuer.ts +++ b/packages/pacer/src/queuer.ts @@ -627,6 +627,7 @@ export class Queuer { */ reset = (): void => { this.#setState(getDefaultQueuerState()) + this.options.onItemsChange?.(this) } } diff --git a/packages/pacer/src/throttler.ts b/packages/pacer/src/throttler.ts index b765256ac..2cd3d233e 100644 --- a/packages/pacer/src/throttler.ts +++ b/packages/pacer/src/throttler.ts @@ -26,7 +26,7 @@ export interface ThrottlerState { /** * Current execution status - 'idle' when not active, 'pending' when waiting for timeout */ - status: 'idle' | 'pending' + status: 'disabled' | 'idle' | 'pending' } function getDefaultThrottlerState< @@ -162,7 +162,11 @@ export class Throttler { const { isPending } = combinedState return { ...combinedState, - status: isPending ? 'pending' : 'idle', + status: !this.#getEnabled() + ? 'disabled' + : isPending + ? 'pending' + : 'idle', } }) } From 8710998b748df1dccef6c124c1b769397fd31db4 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 10 Jul 2025 18:24:08 -0500 Subject: [PATCH 49/51] package upgrades and update changesets --- .changeset/quick-teams-happen.md | 11 ++++++++--- examples/react/asyncDebounce/package.json | 2 +- examples/react/asyncRateLimit/package.json | 2 +- examples/react/asyncThrottle/package.json | 2 +- examples/react/batch/package.json | 2 +- examples/react/debounce/package.json | 2 +- examples/react/queue/package.json | 2 +- examples/react/rateLimit/package.json | 2 +- .../react/react-query-debounced-prefetch/package.json | 6 +++--- .../react/react-query-queued-prefetch/package.json | 6 +++--- .../react/react-query-throttled-prefetch/package.json | 6 +++--- examples/react/throttle/package.json | 2 +- examples/react/useAsyncBatcher/package.json | 2 +- examples/react/useAsyncDebouncer/package.json | 2 +- examples/react/useAsyncQueuedState/package.json | 2 +- examples/react/useAsyncQueuer/package.json | 2 +- examples/react/useAsyncRateLimiter/package.json | 2 +- examples/react/useAsyncThrottler/package.json | 2 +- examples/react/useBatcher/package.json | 2 +- examples/react/useDebouncedCallback/package.json | 2 +- examples/react/useDebouncedState/package.json | 2 +- examples/react/useDebouncedValue/package.json | 2 +- examples/react/useDebouncer/package.json | 2 +- examples/react/useQueuedState/package.json | 2 +- examples/react/useQueuedValue/package.json | 2 +- examples/react/useQueuer/package.json | 2 +- examples/react/useRateLimitedCallback/package.json | 2 +- examples/react/useRateLimitedState/package.json | 2 +- examples/react/useRateLimitedValue/package.json | 2 +- examples/react/useRateLimiter/package.json | 2 +- examples/react/useStorageState/package.json | 2 +- examples/react/useThrottledCallback/package.json | 2 +- examples/react/useThrottledState/package.json | 2 +- examples/react/useThrottledValue/package.json | 2 +- examples/react/useThrottler/package.json | 2 +- examples/solid/asyncDebounce/package.json | 2 +- examples/solid/asyncRateLimit/package.json | 2 +- examples/solid/asyncThrottle/package.json | 2 +- examples/solid/batch/package.json | 2 +- examples/solid/createAsyncBatcher/package.json | 2 +- examples/solid/createAsyncDebouncer/package.json | 2 +- examples/solid/createAsyncQueuer/package.json | 2 +- examples/solid/createAsyncRateLimiter/package.json | 2 +- examples/solid/createAsyncThrottler/package.json | 2 +- examples/solid/createBatcher/package.json | 2 +- examples/solid/createDebouncedSignal/package.json | 2 +- examples/solid/createDebouncedValue/package.json | 2 +- examples/solid/createDebouncer/package.json | 2 +- examples/solid/createQueuer/package.json | 2 +- examples/solid/createRateLimitedSignal/package.json | 2 +- examples/solid/createRateLimitedValue/package.json | 2 +- examples/solid/createRateLimiter/package.json | 2 +- examples/solid/createThrottledSignal/package.json | 2 +- examples/solid/createThrottledValue/package.json | 2 +- examples/solid/createThrottler/package.json | 2 +- examples/solid/debounce/package.json | 2 +- examples/solid/queue/package.json | 2 +- examples/solid/rateLimit/package.json | 2 +- examples/solid/throttle/package.json | 2 +- package.json | 4 ++-- 60 files changed, 74 insertions(+), 69 deletions(-) diff --git a/.changeset/quick-teams-happen.md b/.changeset/quick-teams-happen.md index 5e6d0656d..799f7f0a8 100644 --- a/.changeset/quick-teams-happen.md +++ b/.changeset/quick-teams-happen.md @@ -4,6 +4,11 @@ '@tanstack/pacer': minor --- -- Rewrote TanStack Pacer to use TanStack Store for state management -- Removed most "get" methods that can now be read directly from the state (e.g. `debouncer.getExecutionCount()` -> `debouncer.store.state.executionCount` or `debouncer.state.executionCount` in framework adapters) -- Added `flush` methods to all utils to trigger pending executions to execute immediately. +- breaking: Removed most "get" methods that can now be read directly from the state (e.g. `debouncer.getExecutionCount()` -> `debouncer.store.state.executionCount` or `debouncer.state.executionCount` in framework adapters) +- breaking: Removed `getOptions` and other option resolver methods such as `getEnabled` and `getWait` +- feat: Rewrote TanStack Pacer to use TanStack Store for state management +- feat: Added `flush` methods to all utils to trigger pending executions to execute immediately. +- feat: Added an `initialState` option to all utils to set the initial state for persistence features +- feat: Added status state to all utils except rate-limiters for pending, excution, etc. states. +- feat: Added new AsyncBatcher utility +- fix: Multiple bug fixes \ No newline at end of file diff --git a/examples/react/asyncDebounce/package.json b/examples/react/asyncDebounce/package.json index 74bc5ec9b..5764c73e8 100644 --- a/examples/react/asyncDebounce/package.json +++ b/examples/react/asyncDebounce/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/asyncRateLimit/package.json b/examples/react/asyncRateLimit/package.json index 91dc02bbf..9746da0cf 100644 --- a/examples/react/asyncRateLimit/package.json +++ b/examples/react/asyncRateLimit/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/asyncThrottle/package.json b/examples/react/asyncThrottle/package.json index 3462e5d40..8ce62f4a5 100644 --- a/examples/react/asyncThrottle/package.json +++ b/examples/react/asyncThrottle/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/batch/package.json b/examples/react/batch/package.json index 87c4b4a53..b342e2902 100644 --- a/examples/react/batch/package.json +++ b/examples/react/batch/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/debounce/package.json b/examples/react/debounce/package.json index 9ac7a3c28..038ede402 100644 --- a/examples/react/debounce/package.json +++ b/examples/react/debounce/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/queue/package.json b/examples/react/queue/package.json index 06c1d1757..965bdcecb 100644 --- a/examples/react/queue/package.json +++ b/examples/react/queue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/rateLimit/package.json b/examples/react/rateLimit/package.json index 5403d254a..1d0dce7df 100644 --- a/examples/react/rateLimit/package.json +++ b/examples/react/rateLimit/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-debounced-prefetch/package.json b/examples/react/react-query-debounced-prefetch/package.json index ffc12361a..a3bd779ca 100644 --- a/examples/react/react-query-debounced-prefetch/package.json +++ b/examples/react/react-query-debounced-prefetch/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", - "@tanstack/react-query": "^5.81.5", - "@tanstack/react-query-devtools": "^5.81.5", + "@tanstack/react-query": "^5.82.0", + "@tanstack/react-query-devtools": "^5.82.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, @@ -19,7 +19,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-queued-prefetch/package.json b/examples/react/react-query-queued-prefetch/package.json index c96e2a0f9..1c7df8814 100644 --- a/examples/react/react-query-queued-prefetch/package.json +++ b/examples/react/react-query-queued-prefetch/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", - "@tanstack/react-query": "^5.81.5", - "@tanstack/react-query-devtools": "^5.81.5", + "@tanstack/react-query": "^5.82.0", + "@tanstack/react-query-devtools": "^5.82.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, @@ -19,7 +19,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/react-query-throttled-prefetch/package.json b/examples/react/react-query-throttled-prefetch/package.json index 8f5730917..e00d56dae 100644 --- a/examples/react/react-query-throttled-prefetch/package.json +++ b/examples/react/react-query-throttled-prefetch/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-pacer": "^0.8.0", - "@tanstack/react-query": "^5.81.5", - "@tanstack/react-query-devtools": "^5.81.5", + "@tanstack/react-query": "^5.82.0", + "@tanstack/react-query-devtools": "^5.82.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, @@ -19,7 +19,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/throttle/package.json b/examples/react/throttle/package.json index f0792e05a..3a0db1561 100644 --- a/examples/react/throttle/package.json +++ b/examples/react/throttle/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncBatcher/package.json b/examples/react/useAsyncBatcher/package.json index 92cbd787d..306d4a2d9 100644 --- a/examples/react/useAsyncBatcher/package.json +++ b/examples/react/useAsyncBatcher/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncDebouncer/package.json b/examples/react/useAsyncDebouncer/package.json index 0009f52f7..7e04ebb5d 100644 --- a/examples/react/useAsyncDebouncer/package.json +++ b/examples/react/useAsyncDebouncer/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncQueuedState/package.json b/examples/react/useAsyncQueuedState/package.json index 038201339..7c22ada6a 100644 --- a/examples/react/useAsyncQueuedState/package.json +++ b/examples/react/useAsyncQueuedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncQueuer/package.json b/examples/react/useAsyncQueuer/package.json index b2890640f..5c69f7244 100644 --- a/examples/react/useAsyncQueuer/package.json +++ b/examples/react/useAsyncQueuer/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncRateLimiter/package.json b/examples/react/useAsyncRateLimiter/package.json index 24d37136f..521fa15bf 100644 --- a/examples/react/useAsyncRateLimiter/package.json +++ b/examples/react/useAsyncRateLimiter/package.json @@ -18,7 +18,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useAsyncThrottler/package.json b/examples/react/useAsyncThrottler/package.json index 2df7617d2..206d039ba 100644 --- a/examples/react/useAsyncThrottler/package.json +++ b/examples/react/useAsyncThrottler/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useBatcher/package.json b/examples/react/useBatcher/package.json index 4e76a059b..88709b58d 100644 --- a/examples/react/useBatcher/package.json +++ b/examples/react/useBatcher/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedCallback/package.json b/examples/react/useDebouncedCallback/package.json index 4eb41c630..080077707 100644 --- a/examples/react/useDebouncedCallback/package.json +++ b/examples/react/useDebouncedCallback/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedState/package.json b/examples/react/useDebouncedState/package.json index 90148778b..ed2d0abf8 100644 --- a/examples/react/useDebouncedState/package.json +++ b/examples/react/useDebouncedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncedValue/package.json b/examples/react/useDebouncedValue/package.json index a1a6d4936..2fc0c983f 100644 --- a/examples/react/useDebouncedValue/package.json +++ b/examples/react/useDebouncedValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useDebouncer/package.json b/examples/react/useDebouncer/package.json index b567f0228..cbf5907cd 100644 --- a/examples/react/useDebouncer/package.json +++ b/examples/react/useDebouncer/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuedState/package.json b/examples/react/useQueuedState/package.json index 08ea7e5fd..6b84926af 100644 --- a/examples/react/useQueuedState/package.json +++ b/examples/react/useQueuedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuedValue/package.json b/examples/react/useQueuedValue/package.json index f7408d6a1..26c0b6fa8 100644 --- a/examples/react/useQueuedValue/package.json +++ b/examples/react/useQueuedValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useQueuer/package.json b/examples/react/useQueuer/package.json index 7ce27e6d7..4323f9d4e 100644 --- a/examples/react/useQueuer/package.json +++ b/examples/react/useQueuer/package.json @@ -18,7 +18,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedCallback/package.json b/examples/react/useRateLimitedCallback/package.json index af710c599..3fec901b9 100644 --- a/examples/react/useRateLimitedCallback/package.json +++ b/examples/react/useRateLimitedCallback/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedState/package.json b/examples/react/useRateLimitedState/package.json index 4cdcd2d58..168356e2c 100644 --- a/examples/react/useRateLimitedState/package.json +++ b/examples/react/useRateLimitedState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimitedValue/package.json b/examples/react/useRateLimitedValue/package.json index 0b3189587..53aca172d 100644 --- a/examples/react/useRateLimitedValue/package.json +++ b/examples/react/useRateLimitedValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useRateLimiter/package.json b/examples/react/useRateLimiter/package.json index eb095928f..4ac54abc1 100644 --- a/examples/react/useRateLimiter/package.json +++ b/examples/react/useRateLimiter/package.json @@ -18,7 +18,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useStorageState/package.json b/examples/react/useStorageState/package.json index 15c97cbcd..db0ea6a40 100644 --- a/examples/react/useStorageState/package.json +++ b/examples/react/useStorageState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledCallback/package.json b/examples/react/useThrottledCallback/package.json index e6c312202..546608715 100644 --- a/examples/react/useThrottledCallback/package.json +++ b/examples/react/useThrottledCallback/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledState/package.json b/examples/react/useThrottledState/package.json index 2f4cba569..0e9c313e8 100644 --- a/examples/react/useThrottledState/package.json +++ b/examples/react/useThrottledState/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottledValue/package.json b/examples/react/useThrottledValue/package.json index d2b077b2e..2a86840c3 100644 --- a/examples/react/useThrottledValue/package.json +++ b/examples/react/useThrottledValue/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/react/useThrottler/package.json b/examples/react/useThrottler/package.json index 82c7e4de4..324e6e761 100644 --- a/examples/react/useThrottler/package.json +++ b/examples/react/useThrottler/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", - "vite": "^7.0.2" + "vite": "^7.0.4" }, "browserslist": { "production": [ diff --git a/examples/solid/asyncDebounce/package.json b/examples/solid/asyncDebounce/package.json index 06962ec3b..e0d208bce 100644 --- a/examples/solid/asyncDebounce/package.json +++ b/examples/solid/asyncDebounce/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/asyncRateLimit/package.json b/examples/solid/asyncRateLimit/package.json index ec67f709d..771d57f53 100644 --- a/examples/solid/asyncRateLimit/package.json +++ b/examples/solid/asyncRateLimit/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/asyncThrottle/package.json b/examples/solid/asyncThrottle/package.json index 1cd3d765a..f9229527c 100644 --- a/examples/solid/asyncThrottle/package.json +++ b/examples/solid/asyncThrottle/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/batch/package.json b/examples/solid/batch/package.json index 8a621345d..2e5e94a4d 100644 --- a/examples/solid/batch/package.json +++ b/examples/solid/batch/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncBatcher/package.json b/examples/solid/createAsyncBatcher/package.json index 095a0aec6..8b8ce041f 100644 --- a/examples/solid/createAsyncBatcher/package.json +++ b/examples/solid/createAsyncBatcher/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncDebouncer/package.json b/examples/solid/createAsyncDebouncer/package.json index 453c0ffdb..a0267f372 100644 --- a/examples/solid/createAsyncDebouncer/package.json +++ b/examples/solid/createAsyncDebouncer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncQueuer/package.json b/examples/solid/createAsyncQueuer/package.json index f206752e7..f20886fb8 100644 --- a/examples/solid/createAsyncQueuer/package.json +++ b/examples/solid/createAsyncQueuer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncRateLimiter/package.json b/examples/solid/createAsyncRateLimiter/package.json index c9b634f89..28f598fb5 100644 --- a/examples/solid/createAsyncRateLimiter/package.json +++ b/examples/solid/createAsyncRateLimiter/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createAsyncThrottler/package.json b/examples/solid/createAsyncThrottler/package.json index 4848bfb67..b8fb4ea51 100644 --- a/examples/solid/createAsyncThrottler/package.json +++ b/examples/solid/createAsyncThrottler/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createBatcher/package.json b/examples/solid/createBatcher/package.json index c4caad188..d721aa9f3 100644 --- a/examples/solid/createBatcher/package.json +++ b/examples/solid/createBatcher/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createDebouncedSignal/package.json b/examples/solid/createDebouncedSignal/package.json index 5fe689355..38a859b69 100644 --- a/examples/solid/createDebouncedSignal/package.json +++ b/examples/solid/createDebouncedSignal/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createDebouncedValue/package.json b/examples/solid/createDebouncedValue/package.json index 63aa61a5e..d7c3f85bf 100644 --- a/examples/solid/createDebouncedValue/package.json +++ b/examples/solid/createDebouncedValue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createDebouncer/package.json b/examples/solid/createDebouncer/package.json index 5aa1de1ad..8b2d52b74 100644 --- a/examples/solid/createDebouncer/package.json +++ b/examples/solid/createDebouncer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createQueuer/package.json b/examples/solid/createQueuer/package.json index 03238147e..f1cec31fc 100644 --- a/examples/solid/createQueuer/package.json +++ b/examples/solid/createQueuer/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createRateLimitedSignal/package.json b/examples/solid/createRateLimitedSignal/package.json index 926c55bf1..2e09beed6 100644 --- a/examples/solid/createRateLimitedSignal/package.json +++ b/examples/solid/createRateLimitedSignal/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createRateLimitedValue/package.json b/examples/solid/createRateLimitedValue/package.json index d7cdae0a0..70261a75f 100644 --- a/examples/solid/createRateLimitedValue/package.json +++ b/examples/solid/createRateLimitedValue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createRateLimiter/package.json b/examples/solid/createRateLimiter/package.json index c9bb95bef..4100be27f 100644 --- a/examples/solid/createRateLimiter/package.json +++ b/examples/solid/createRateLimiter/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createThrottledSignal/package.json b/examples/solid/createThrottledSignal/package.json index 063807e77..bf8131fef 100644 --- a/examples/solid/createThrottledSignal/package.json +++ b/examples/solid/createThrottledSignal/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createThrottledValue/package.json b/examples/solid/createThrottledValue/package.json index 11b6ff61a..ae870a4d8 100644 --- a/examples/solid/createThrottledValue/package.json +++ b/examples/solid/createThrottledValue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/createThrottler/package.json b/examples/solid/createThrottler/package.json index 9e2b3d0cb..74f442fca 100644 --- a/examples/solid/createThrottler/package.json +++ b/examples/solid/createThrottler/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/debounce/package.json b/examples/solid/debounce/package.json index a2bfca341..1ff7cde36 100644 --- a/examples/solid/debounce/package.json +++ b/examples/solid/debounce/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/queue/package.json b/examples/solid/queue/package.json index 7e77f9133..35a85aafd 100644 --- a/examples/solid/queue/package.json +++ b/examples/solid/queue/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/rateLimit/package.json b/examples/solid/rateLimit/package.json index 2d7b11d68..346195812 100644 --- a/examples/solid/rateLimit/package.json +++ b/examples/solid/rateLimit/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/examples/solid/throttle/package.json b/examples/solid/throttle/package.json index d3ec057fe..40c14f022 100644 --- a/examples/solid/throttle/package.json +++ b/examples/solid/throttle/package.json @@ -13,7 +13,7 @@ "solid-js": "^1.9.7" }, "devDependencies": { - "vite": "^7.0.2", + "vite": "^7.0.4", "vite-plugin-solid": "^2.11.7" }, "browserslist": { diff --git a/package.json b/package.json index e43e95ab7..c26339dbb 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@svitejs/changesets-changelog-github-compact": "^1.2.0", "@tanstack/config": "0.19.0", "@testing-library/jest-dom": "^6.6.3", - "@types/node": "^24.0.10", + "@types/node": "^24.0.13", "eslint": "^9.30.1", "eslint-plugin-unused-imports": "^4.1.4", "fast-glob": "^3.3.3", @@ -73,7 +73,7 @@ "sherif": "^1.6.1", "size-limit": "^11.2.0", "typescript": "5.8.3", - "vite": "^7.0.2", + "vite": "^7.0.4", "vitest": "^3.2.4" }, "overrides": { From 6b0b27de76c06e369ab478e527220f0efe45b3f4 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Thu, 10 Jul 2025 18:24:27 -0500 Subject: [PATCH 50/51] update lock file --- pnpm-lock.yaml | 522 ++++++++++++++++++++++++------------------------- 1 file changed, 261 insertions(+), 261 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e38547a7a..51aec220e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,13 +22,13 @@ importers: version: 1.2.0 '@tanstack/config': specifier: 0.19.0 - version: 0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 0.19.0(@types/node@24.0.13)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 '@types/node': - specifier: ^24.0.10 - version: 24.0.10 + specifier: ^24.0.13 + version: 24.0.13 eslint: specifier: ^9.30.1 version: 9.30.1(jiti@2.4.2) @@ -43,7 +43,7 @@ importers: version: 26.1.0 knip: specifier: ^5.61.3 - version: 5.61.3(@types/node@24.0.10)(typescript@5.8.3) + version: 5.61.3(@types/node@24.0.13)(typescript@5.8.3) markdown-link-extractor: specifier: ^4.0.2 version: 4.0.2 @@ -72,11 +72,11 @@ importers: specifier: 5.8.3 version: 5.8.3 vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0) + version: 3.2.4(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncDebounce: dependencies: @@ -98,10 +98,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncRateLimit: dependencies: @@ -123,10 +123,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/asyncThrottle: dependencies: @@ -148,10 +148,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/batch: dependencies: @@ -173,10 +173,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/debounce: dependencies: @@ -198,10 +198,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/queue: dependencies: @@ -223,10 +223,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/rateLimit: dependencies: @@ -248,10 +248,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-debounced-prefetch: dependencies: @@ -259,11 +259,11 @@ importers: specifier: ^0.8.0 version: link:../../../packages/react-pacer '@tanstack/react-query': - specifier: ^5.81.5 - version: 5.81.5(react@19.1.0) + specifier: ^5.82.0 + version: 5.82.0(react@19.1.0) '@tanstack/react-query-devtools': - specifier: ^5.81.5 - version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) + specifier: ^5.82.0 + version: 5.82.0(@tanstack/react-query@5.82.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -279,10 +279,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-queued-prefetch: dependencies: @@ -290,11 +290,11 @@ importers: specifier: ^0.8.0 version: link:../../../packages/react-pacer '@tanstack/react-query': - specifier: ^5.81.5 - version: 5.81.5(react@19.1.0) + specifier: ^5.82.0 + version: 5.82.0(react@19.1.0) '@tanstack/react-query-devtools': - specifier: ^5.81.5 - version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) + specifier: ^5.82.0 + version: 5.82.0(@tanstack/react-query@5.82.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -310,10 +310,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/react-query-throttled-prefetch: dependencies: @@ -321,11 +321,11 @@ importers: specifier: ^0.8.0 version: link:../../../packages/react-pacer '@tanstack/react-query': - specifier: ^5.81.5 - version: 5.81.5(react@19.1.0) + specifier: ^5.82.0 + version: 5.82.0(react@19.1.0) '@tanstack/react-query-devtools': - specifier: ^5.81.5 - version: 5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0) + specifier: ^5.82.0 + version: 5.82.0(@tanstack/react-query@5.82.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -341,10 +341,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/throttle: dependencies: @@ -366,10 +366,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncBatcher: dependencies: @@ -391,10 +391,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncDebouncer: dependencies: @@ -416,10 +416,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncQueuedState: dependencies: @@ -441,10 +441,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncQueuer: dependencies: @@ -466,10 +466,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncRateLimiter: dependencies: @@ -494,10 +494,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useAsyncThrottler: dependencies: @@ -519,10 +519,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useBatcher: dependencies: @@ -544,10 +544,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedCallback: dependencies: @@ -569,10 +569,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedState: dependencies: @@ -594,10 +594,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncedValue: dependencies: @@ -619,10 +619,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useDebouncer: dependencies: @@ -644,10 +644,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuedState: dependencies: @@ -669,10 +669,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuedValue: dependencies: @@ -694,10 +694,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useQueuer: dependencies: @@ -722,10 +722,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedCallback: dependencies: @@ -747,10 +747,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedState: dependencies: @@ -772,10 +772,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimitedValue: dependencies: @@ -797,10 +797,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useRateLimiter: dependencies: @@ -825,10 +825,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useStorageState: dependencies: @@ -850,10 +850,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledCallback: dependencies: @@ -875,10 +875,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledState: dependencies: @@ -900,10 +900,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottledValue: dependencies: @@ -925,10 +925,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/react/useThrottler: dependencies: @@ -950,10 +950,10 @@ importers: version: 19.1.6(@types/react@19.1.8) '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) examples/solid/asyncDebounce: dependencies: @@ -965,11 +965,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/asyncRateLimit: dependencies: @@ -981,11 +981,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/asyncThrottle: dependencies: @@ -997,11 +997,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/batch: dependencies: @@ -1013,11 +1013,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncBatcher: dependencies: @@ -1029,11 +1029,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncDebouncer: dependencies: @@ -1045,11 +1045,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncQueuer: dependencies: @@ -1061,11 +1061,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncRateLimiter: dependencies: @@ -1077,11 +1077,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createAsyncThrottler: dependencies: @@ -1093,11 +1093,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createBatcher: dependencies: @@ -1109,11 +1109,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncedSignal: dependencies: @@ -1125,11 +1125,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncedValue: dependencies: @@ -1141,11 +1141,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createDebouncer: dependencies: @@ -1157,11 +1157,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createQueuer: dependencies: @@ -1173,11 +1173,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimitedSignal: dependencies: @@ -1189,11 +1189,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimitedValue: dependencies: @@ -1205,11 +1205,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createRateLimiter: dependencies: @@ -1221,11 +1221,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottledSignal: dependencies: @@ -1237,11 +1237,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottledValue: dependencies: @@ -1253,11 +1253,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/createThrottler: dependencies: @@ -1269,11 +1269,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/debounce: dependencies: @@ -1285,11 +1285,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/queue: dependencies: @@ -1301,11 +1301,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/rateLimit: dependencies: @@ -1317,11 +1317,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) examples/solid/throttle: dependencies: @@ -1333,11 +1333,11 @@ importers: version: 1.9.7 devDependencies: vite: - specifier: ^7.0.2 - version: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + specifier: ^7.0.4 + version: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) packages/pacer: dependencies: @@ -1367,7 +1367,7 @@ importers: version: 19.1.8 '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) eslint-plugin-react-compiler: specifier: 19.1.0-rc.2 version: 19.1.0-rc.2(eslint@9.30.1(jiti@2.4.2)) @@ -1395,7 +1395,7 @@ importers: version: 19.1.8 '@vitejs/plugin-react': specifier: ^4.6.0 - version: 4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) eslint-plugin-react-compiler: specifier: 19.1.0-rc.2 version: 19.1.0-rc.2(eslint@9.30.1(jiti@2.4.2)) @@ -1420,7 +1420,7 @@ importers: version: 1.9.7 vite-plugin-solid: specifier: ^2.11.7 - version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + version: 2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) packages: @@ -2390,20 +2390,20 @@ packages: resolution: {integrity: sha512-RC0yRBFJvGuR58tKQUIkMXVEiATXgESIc+3/NTqoCC7D2YOF4fZGmHGYIanFEPQH7EGfQ5+Bwi+H6BOtKnymtw==} engines: {node: '>=18'} - '@tanstack/query-core@5.81.5': - resolution: {integrity: sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q==} + '@tanstack/query-core@5.82.0': + resolution: {integrity: sha512-JrjoVuaajBQtnoWSg8iaPHaT4mW73lK2t+exxHNOSMqy0+13eKLqJgTKXKImLejQIfdAHQ6Un0njEhOvUtOd5w==} '@tanstack/query-devtools@5.81.2': resolution: {integrity: sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==} - '@tanstack/react-query-devtools@5.81.5': - resolution: {integrity: sha512-lCGMu4RX0uGnlrlLeSckBfnW/UV+KMlTBVqa97cwK7Z2ED5JKnZRSjNXwoma6sQBTJrcULvzgx2K6jEPvNUpDw==} + '@tanstack/react-query-devtools@5.82.0': + resolution: {integrity: sha512-MC05Zq3zr/59jhgF7dL6JSGPg1krbasDSizmRxjNcvxgh/sUTwRFD9CGN10YYX7LB6jq0ZpFtCjSVGdLiFrKAA==} peerDependencies: - '@tanstack/react-query': ^5.81.5 + '@tanstack/react-query': ^5.82.0 react: ^18 || ^19 - '@tanstack/react-query@5.81.5': - resolution: {integrity: sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==} + '@tanstack/react-query@5.82.0': + resolution: {integrity: sha512-mnk8/ofKEthFeMdhV1dV8YXRf+9HqvXAcciXkoo755d/ocfWq7N/Y9jGOzS3h7ZW9dDGwSIhs3/HANWUBsyqYg==} peerDependencies: react: ^18 || ^19 @@ -2475,8 +2475,8 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@24.0.10': - resolution: {integrity: sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==} + '@types/node@24.0.13': + resolution: {integrity: sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==} '@types/react-dom@19.1.6': resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} @@ -4724,8 +4724,8 @@ packages: vite: optional: true - vite@7.0.2: - resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==} + vite@7.0.4: + resolution: {integrity: sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -5738,23 +5738,23 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@microsoft/api-extractor-model@7.29.6(@types/node@24.0.10)': + '@microsoft/api-extractor-model@7.29.6(@types/node@24.0.13)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.7.0(@types/node@24.0.10) + '@rushstack/node-core-library': 5.7.0(@types/node@24.0.13) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.47.7(@types/node@24.0.10)': + '@microsoft/api-extractor@7.47.7(@types/node@24.0.13)': dependencies: - '@microsoft/api-extractor-model': 7.29.6(@types/node@24.0.10) + '@microsoft/api-extractor-model': 7.29.6(@types/node@24.0.13) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.7.0(@types/node@24.0.10) + '@rushstack/node-core-library': 5.7.0(@types/node@24.0.13) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.14.0(@types/node@24.0.10) - '@rushstack/ts-command-line': 4.22.6(@types/node@24.0.10) + '@rushstack/terminal': 0.14.0(@types/node@24.0.13) + '@rushstack/ts-command-line': 4.22.6(@types/node@24.0.13) lodash: 4.17.21 minimatch: 3.0.8 resolve: 1.22.10 @@ -5941,7 +5941,7 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.44.1': optional: true - '@rushstack/node-core-library@5.7.0(@types/node@24.0.10)': + '@rushstack/node-core-library@5.7.0(@types/node@24.0.13)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -5952,23 +5952,23 @@ snapshots: resolve: 1.22.10 semver: 7.5.4 optionalDependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 '@rushstack/rig-package@0.5.3': dependencies: resolve: 1.22.10 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.14.0(@types/node@24.0.10)': + '@rushstack/terminal@0.14.0(@types/node@24.0.13)': dependencies: - '@rushstack/node-core-library': 5.7.0(@types/node@24.0.10) + '@rushstack/node-core-library': 5.7.0(@types/node@24.0.13) supports-color: 8.1.1 optionalDependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 - '@rushstack/ts-command-line@4.22.6(@types/node@24.0.10)': + '@rushstack/ts-command-line@4.22.6(@types/node@24.0.13)': dependencies: - '@rushstack/terminal': 0.14.0(@types/node@24.0.10) + '@rushstack/terminal': 0.14.0(@types/node@24.0.13) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -6028,12 +6028,12 @@ snapshots: transitivePeerDependencies: - encoding - '@tanstack/config@0.19.0(@types/node@24.0.10)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@tanstack/config@0.19.0(@types/node@24.0.13)(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@tanstack/eslint-config': 0.2.0(@typescript-eslint/utils@8.35.1(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) '@tanstack/publish-config': 0.2.0 '@tanstack/typedoc-config': 0.2.0(typescript@5.8.3) - '@tanstack/vite-config': 0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + '@tanstack/vite-config': 0.2.0(@types/node@24.0.13)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) transitivePeerDependencies: - '@types/node' - '@typescript-eslint/utils' @@ -6069,19 +6069,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/query-core@5.81.5': {} + '@tanstack/query-core@5.82.0': {} '@tanstack/query-devtools@5.81.2': {} - '@tanstack/react-query-devtools@5.81.5(@tanstack/react-query@5.81.5(react@19.1.0))(react@19.1.0)': + '@tanstack/react-query-devtools@5.82.0(@tanstack/react-query@5.82.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/query-devtools': 5.81.2 - '@tanstack/react-query': 5.81.5(react@19.1.0) + '@tanstack/react-query': 5.82.0(react@19.1.0) react: 19.1.0 - '@tanstack/react-query@5.81.5(react@19.1.0)': + '@tanstack/react-query@5.82.0(react@19.1.0)': dependencies: - '@tanstack/query-core': 5.81.5 + '@tanstack/query-core': 5.82.0 react: 19.1.0 '@tanstack/react-store@0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': @@ -6106,12 +6106,12 @@ snapshots: transitivePeerDependencies: - typescript - '@tanstack/vite-config@0.2.0(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@tanstack/vite-config@0.2.0(@types/node@24.0.13)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: rollup-plugin-preserve-directives: 0.4.0(rollup@4.44.1) - vite-plugin-dts: 4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - vite-plugin-externalize-deps: 0.9.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) - vite-tsconfig-paths: 5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-plugin-dts: 4.2.3(@types/node@24.0.13)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-plugin-externalize-deps: 0.9.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite-tsconfig-paths: 5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) transitivePeerDependencies: - '@types/node' - rollup @@ -6162,7 +6162,7 @@ snapshots: '@types/conventional-commits-parser@5.0.1': dependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 '@types/deep-eql@4.0.2': {} @@ -6178,7 +6178,7 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@24.0.10': + '@types/node@24.0.13': dependencies: undici-types: 7.8.0 @@ -6400,7 +6400,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.7.11': optional: true - '@vitejs/plugin-react@4.6.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@vitejs/plugin-react@4.6.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@babel/core': 7.28.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) @@ -6408,7 +6408,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.19 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -6420,13 +6420,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': + '@vitest/mocker@3.2.4(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -7645,10 +7645,10 @@ snapshots: dependencies: json-buffer: 3.0.1 - knip@5.61.3(@types/node@24.0.10)(typescript@5.8.3): + knip@5.61.3(@types/node@24.0.13)(typescript@5.8.3): dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 24.0.10 + '@types/node': 24.0.13 fast-glob: 3.3.3 formatly: 0.2.4 jiti: 2.4.2 @@ -8513,13 +8513,13 @@ snapshots: validate-html-nesting@1.2.2: {} - vite-node@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): + vite-node@3.2.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - jiti @@ -8534,9 +8534,9 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.2.3(@types/node@24.0.10)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-dts@4.2.3(@types/node@24.0.13)(rollup@4.44.1)(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: - '@microsoft/api-extractor': 7.47.7(@types/node@24.0.10) + '@microsoft/api-extractor': 7.47.7(@types/node@24.0.13) '@rollup/pluginutils': 5.1.4(rollup@4.44.1) '@volar/typescript': 2.4.12 '@vue/language-core': 2.1.6(typescript@5.8.3) @@ -8547,17 +8547,17 @@ snapshots: magic-string: 0.30.17 typescript: 5.8.3 optionalDependencies: - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-externalize-deps@0.9.0(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-externalize-deps@0.9.0(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vite-plugin-solid@2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-plugin-solid@2.11.7(@testing-library/jest-dom@6.6.3)(solid-js@1.9.7)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: '@babel/core': 7.26.10 '@types/babel__core': 7.20.5 @@ -8565,25 +8565,25 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.7 solid-refresh: 0.6.3(solid-js@1.9.7) - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vitefu: 1.0.6(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vitefu: 1.0.6(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) optionalDependencies: '@testing-library/jest-dom': 6.6.3 transitivePeerDependencies: - supports-color - vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vite-tsconfig-paths@5.1.4(typescript@5.8.3)(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): dependencies: debug: 4.4.1 globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.8.3) optionalDependencies: - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) transitivePeerDependencies: - supports-color - typescript - vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): + vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0): dependencies: esbuild: 0.25.0 fdir: 6.4.6(picomatch@4.0.2) @@ -8592,21 +8592,21 @@ snapshots: rollup: 4.44.1 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 fsevents: 2.3.3 jiti: 2.4.2 tsx: 4.19.3 yaml: 2.7.0 - vitefu@1.0.6(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): + vitefu@1.0.6(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)): optionalDependencies: - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vitest@3.2.4(@types/node@24.0.10)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0): + vitest@3.2.4(@types/node@24.0.13)(jiti@2.4.2)(jsdom@26.1.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) + '@vitest/mocker': 3.2.4(vite@7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -8624,11 +8624,11 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.2(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) - vite-node: 3.2.4(@types/node@24.0.10)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite: 7.0.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) + vite-node: 3.2.4(@types/node@24.0.13)(jiti@2.4.2)(tsx@4.19.3)(yaml@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.0.10 + '@types/node': 24.0.13 jsdom: 26.1.0 transitivePeerDependencies: - jiti From 20d429c8f70904cff3fa9a29014baf62f9a104ee Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 23:25:23 +0000 Subject: [PATCH 51/51] ci: apply automated fixes --- .changeset/quick-teams-happen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-teams-happen.md b/.changeset/quick-teams-happen.md index 799f7f0a8..27e26d82b 100644 --- a/.changeset/quick-teams-happen.md +++ b/.changeset/quick-teams-happen.md @@ -11,4 +11,4 @@ - feat: Added an `initialState` option to all utils to set the initial state for persistence features - feat: Added status state to all utils except rate-limiters for pending, excution, etc. states. - feat: Added new AsyncBatcher utility -- fix: Multiple bug fixes \ No newline at end of file +- fix: Multiple bug fixes