diff --git a/.changeset/petite-actors-lie.md b/.changeset/petite-actors-lie.md new file mode 100644 index 00000000..effc666e --- /dev/null +++ b/.changeset/petite-actors-lie.md @@ -0,0 +1,11 @@ +--- +'@tanstack/angular-store': minor +'@tanstack/preact-store': minor +'@tanstack/svelte-store': minor +'@tanstack/react-store': minor +'@tanstack/solid-store': minor +'@tanstack/vue-store': minor +'@tanstack/store': minor +--- + +chore: update deps and change build process to tsdown diff --git a/.gitignore b/.gitignore index cc376bd6..182bead0 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ dist .env.test.local .env.production.local .next +.angular/* +.cache/* npm-debug.log* yarn-debug.log* @@ -41,3 +43,16 @@ stats.html vite.config.js.timestamp-* vite.config.ts.timestamp-* + +examples/angular/atoms/.angular/cache/* +examples/angular/atoms/.angular/cache/21.2.7/atoms/angular-compiler.db +examples/angular/atoms/.angular/cache/21.2.7/atoms/angular-compiler.db-lock +examples/angular/store-actions/.angular/cache/21.2.7/store-actions/.tsbuildinfo +examples/angular/store-actions/.angular/cache/21.2.7/store-actions/angular-compiler.db +examples/angular/store-actions/.angular/cache/21.2.7/store-actions/angular-compiler.db-lock +examples/angular/store-context/.angular/cache/21.2.7/store-context/.tsbuildinfo +examples/angular/store-context/.angular/cache/21.2.7/store-context/angular-compiler.db +examples/angular/store-context/.angular/cache/21.2.7/store-context/angular-compiler.db-lock +examples/angular/stores/.angular/cache/21.2.7/stores/.tsbuildinfo +examples/angular/stores/.angular/cache/21.2.7/stores/angular-compiler.db +examples/angular/stores/.angular/cache/21.2.7/stores/angular-compiler.db-lock diff --git a/docs/config.json b/docs/config.json index b083a847..e84654ae 100644 --- a/docs/config.json +++ b/docs/config.json @@ -83,88 +83,171 @@ "label": "API Reference", "children": [ { - "label": "JavaScript Reference", + "label": "Core API Reference", "to": "reference/index" + } + ], + "frameworks": [ + { + "label": "react", + "children": [ + { + "label": "React Hooks", + "to": "framework/react/reference/index" + } + ] }, { - "label": "Classes / Store", + "label": "vue", + "children": [ + { + "label": "Vue Hooks", + "to": "framework/vue/reference/index" + } + ] + }, + { + "label": "solid", + "children": [ + { + "label": "Solid Hooks", + "to": "framework/solid/reference/index" + } + ] + }, + { + "label": "angular", + "children": [ + { + "label": "Angular Inject API", + "to": "framework/angular/reference/index" + } + ] + }, + { + "label": "svelte", + "children": [ + { + "label": "Svelte Hooks", + "to": "framework/svelte/reference/index" + } + ] + }, + { + "label": "preact", + "children": [ + { + "label": "Preact Hooks", + "to": "framework/preact/reference/index" + } + ] + } + ] + }, + { + "collapsible": true, + "defaultCollapsed": true, + "label": "Store API Reference", + "children": [ + { + "label": "Store", "to": "reference/classes/Store" }, { - "label": "Interfaces / Atom", - "to": "reference/interfaces/atom" + "label": "ReadonlyStore", + "to": "reference/classes/ReadonlyStore" + }, + { + "label": "Atom", + "to": "reference/interfaces/Atom" + }, + { + "label": "AtomOptions", + "to": "reference/interfaces/AtomOptions" + }, + { + "label": "BaseAtom", + "to": "reference/interfaces/BaseAtom" + }, + { + "label": "InternalBaseAtom", + "to": "reference/interfaces/InternalBaseAtom" + }, + { + "label": "InternalReadonlyAtom", + "to": "reference/interfaces/InternalReadonlyAtom" }, { - "label": "Interfaces / AtomOptions", - "to": "reference/interfaces/atomoptions" + "label": "InteropSubscribable", + "to": "reference/interfaces/InteropSubscribable" }, { - "label": "Interfaces / BaseAtom", - "to": "reference/interfaces/baseatom" + "label": "Readable", + "to": "reference/interfaces/Readable" }, { - "label": "Interfaces / InternalBaseAtom", - "to": "reference/interfaces/internalbaseatom" + "label": "ReadonlyAtom", + "to": "reference/interfaces/ReadonlyAtom" }, { - "label": "Interfaces / InternalReadonlyAtom", - "to": "reference/interfaces/internalreadonlyatom" + "label": "StoreActionsApi", + "to": "reference/interfaces/StoreActionsApi" }, { - "label": "Interfaces / InteropSubscribable", - "to": "reference/interfaces/interopsubscribable" + "label": "Subscribable", + "to": "reference/interfaces/Subscribable" }, { - "label": "Interfaces / Readable", - "to": "reference/interfaces/readable" + "label": "Subscription", + "to": "reference/interfaces/Subscription" }, { - "label": "Interfaces / ReadonlyAtom", - "to": "reference/interfaces/readonlyatom" + "label": "AnyAtom", + "to": "reference/type-aliases/AnyAtom" }, { - "label": "Interfaces / Subscribable", - "to": "reference/interfaces/subscribable" + "label": "Observer", + "to": "reference/type-aliases/Observer" }, { - "label": "Interfaces / Subscription", - "to": "reference/interfaces/subscription" + "label": "Selection", + "to": "reference/type-aliases/Selection" }, { - "label": "Type Aliases / AnyAtom", - "to": "reference/type-aliases/anyatom" + "label": "StoreAction", + "to": "reference/type-aliases/StoreAction" }, { - "label": "Type Aliases / Observer", - "to": "reference/type-aliases/observer" + "label": "StoreActionMap", + "to": "reference/type-aliases/StoreActionMap" }, { - "label": "Type Aliases / Selection", - "to": "reference/type-aliases/selection" + "label": "StoreActionsFactory", + "to": "reference/type-aliases/StoreActionsFactory" }, { - "label": "Functions / batch", + "label": "batch", "to": "reference/functions/batch" }, { - "label": "Functions / createAsyncAtom", - "to": "reference/functions/createasyncatom" + "label": "createAsyncAtom", + "to": "reference/functions/createAsyncAtom" }, { - "label": "Functions / createAtom", - "to": "reference/functions/createatom" + "label": "createAtom", + "to": "reference/functions/createAtom" }, { - "label": "Functions / createStore", - "to": "reference/functions/createstore" + "label": "createStore", + "to": "reference/functions/createStore" }, { - "label": "Functions / flush", + "label": "flush", "to": "reference/functions/flush" }, { - "label": "Functions / toObserver", - "to": "reference/functions/toobserver" + "label": "toObserver", + "to": "reference/functions/toObserver" } ], "frameworks": [ @@ -172,16 +255,48 @@ "label": "react", "children": [ { - "label": "React Reference", - "to": "framework/react/reference/index" + "label": "createStoreContext", + "to": "framework/react/reference/functions/createStoreContext" + }, + { + "label": "useCreateAtom", + "to": "framework/react/reference/functions/useCreateAtom" + }, + { + "label": "useCreateStore", + "to": "framework/react/reference/functions/useCreateStore" + }, + { + "label": "useSelector", + "to": "framework/react/reference/functions/useSelector" + }, + { + "label": "useValue", + "to": "framework/react/reference/functions/useValue" }, { - "label": "Functions / useStore", + "label": "useSetValue", + "to": "framework/react/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/react/reference/functions/useAtom" + }, + { + "label": "useStoreActions", + "to": "framework/react/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/react/reference/functions/useStore" }, { - "label": "Functions / shallow", + "label": "shallow", "to": "framework/react/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/react/reference/interfaces/UseSelectorOptions" } ] }, @@ -189,16 +304,36 @@ "label": "vue", "children": [ { - "label": "Vue Reference", - "to": "framework/vue/reference/index" + "label": "useSelector", + "to": "framework/vue/reference/functions/useSelector" + }, + { + "label": "useValue", + "to": "framework/vue/reference/functions/useValue" + }, + { + "label": "useSetValue", + "to": "framework/vue/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/vue/reference/functions/useAtom" }, { - "label": "Functions / useStore", + "label": "useStoreActions", + "to": "framework/vue/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/vue/reference/functions/useStore" }, { - "label": "Functions / shallow", + "label": "shallow", "to": "framework/vue/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/vue/reference/interfaces/UseSelectorOptions" } ] }, @@ -206,12 +341,36 @@ "label": "solid", "children": [ { - "label": "Solid Reference", - "to": "framework/solid/reference/index" + "label": "useSelector", + "to": "framework/solid/reference/functions/useSelector" + }, + { + "label": "useValue", + "to": "framework/solid/reference/functions/useValue" + }, + { + "label": "useSetValue", + "to": "framework/solid/reference/functions/useSetValue" }, { - "label": "Functions / useStore", + "label": "useAtom", + "to": "framework/solid/reference/functions/useAtom" + }, + { + "label": "useStoreActions", + "to": "framework/solid/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/solid/reference/functions/useStore" + }, + { + "label": "shallow", + "to": "framework/solid/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/solid/reference/interfaces/UseSelectorOptions" } ] }, @@ -219,12 +378,36 @@ "label": "angular", "children": [ { - "label": "Angular Reference", - "to": "framework/angular/reference/index" + "label": "injectSelector", + "to": "framework/angular/reference/functions/injectSelector" + }, + { + "label": "injectValue", + "to": "framework/angular/reference/functions/injectValue" + }, + { + "label": "injectSetValue", + "to": "framework/angular/reference/functions/injectSetValue" }, { - "label": "Functions / injectStore", + "label": "injectAtom", + "to": "framework/angular/reference/functions/injectAtom" + }, + { + "label": "injectStoreActions", + "to": "framework/angular/reference/functions/injectStoreActions" + }, + { + "label": "injectStore (deprecated)", "to": "framework/angular/reference/functions/injectStore" + }, + { + "label": "shallow", + "to": "framework/angular/reference/functions/shallow" + }, + { + "label": "InjectSelectorOptions", + "to": "framework/angular/reference/interfaces/InjectSelectorOptions" } ] }, @@ -232,16 +415,36 @@ "label": "svelte", "children": [ { - "label": "Svelte Reference", - "to": "framework/svelte/reference/index" + "label": "useSelector", + "to": "framework/svelte/reference/functions/useSelector" }, { - "label": "Functions / useStore", + "label": "useValue", + "to": "framework/svelte/reference/functions/useValue" + }, + { + "label": "useSetValue", + "to": "framework/svelte/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/svelte/reference/functions/useAtom" + }, + { + "label": "useStoreActions", + "to": "framework/svelte/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/svelte/reference/functions/useStore" }, { - "label": "Functions / shallow", + "label": "shallow", "to": "framework/svelte/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/svelte/reference/interfaces/UseSelectorOptions" } ] }, @@ -249,16 +452,48 @@ "label": "preact", "children": [ { - "label": "Preact Reference", - "to": "framework/preact/reference/index" + "label": "createStoreContext", + "to": "framework/preact/reference/functions/createStoreContext" + }, + { + "label": "useCreateAtom", + "to": "framework/preact/reference/functions/useCreateAtom" + }, + { + "label": "useCreateStore", + "to": "framework/preact/reference/functions/useCreateStore" }, { - "label": "Functions / useStore", + "label": "useSelector", + "to": "framework/preact/reference/functions/useSelector" + }, + { + "label": "useValue", + "to": "framework/preact/reference/functions/useValue" + }, + { + "label": "useSetValue", + "to": "framework/preact/reference/functions/useSetValue" + }, + { + "label": "useAtom", + "to": "framework/preact/reference/functions/useAtom" + }, + { + "label": "useStoreActions", + "to": "framework/preact/reference/functions/useStoreActions" + }, + { + "label": "useStore (deprecated)", "to": "framework/preact/reference/functions/useStore" }, { - "label": "Functions / shallow", + "label": "shallow", "to": "framework/preact/reference/functions/shallow" + }, + { + "label": "UseSelectorOptions", + "to": "framework/preact/reference/interfaces/UseSelectorOptions" } ] } @@ -274,6 +509,22 @@ { "label": "Simple", "to": "framework/react/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/react/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/react/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/react/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/react/examples/store-context" } ] }, @@ -283,6 +534,22 @@ { "label": "Simple", "to": "framework/vue/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/vue/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/vue/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/vue/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/vue/examples/store-context" } ] }, @@ -292,6 +559,22 @@ { "label": "Simple", "to": "framework/angular/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/angular/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/angular/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/angular/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/angular/examples/store-context" } ] }, @@ -301,6 +584,22 @@ { "label": "Simple", "to": "framework/svelte/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/svelte/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/svelte/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/svelte/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/svelte/examples/store-context" } ] }, @@ -310,6 +609,22 @@ { "label": "Simple", "to": "framework/solid/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/solid/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/solid/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/solid/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/solid/examples/store-context" } ] }, @@ -319,6 +634,22 @@ { "label": "Simple", "to": "framework/preact/examples/simple" + }, + { + "label": "Atoms", + "to": "framework/preact/examples/atoms" + }, + { + "label": "Stores", + "to": "framework/preact/examples/stores" + }, + { + "label": "Store Actions", + "to": "framework/preact/examples/store-actions" + }, + { + "label": "Store Context", + "to": "framework/preact/examples/store-context" } ] } diff --git a/docs/framework/angular/quick-start.md b/docs/framework/angular/quick-start.md index b4b48695..89e4847c 100644 --- a/docs/framework/angular/quick-start.md +++ b/docs/framework/angular/quick-start.md @@ -52,7 +52,7 @@ export function updateState(animal: 'dogs' | 'cats') { **display.component.ts** ```angular-ts -import { injectStore } from '@tanstack/angular-store'; +import { injectSelector } from '@tanstack/angular-store'; import { store } from './store'; @Component({ @@ -65,7 +65,7 @@ import { store } from './store'; }) export class Display { animal = input.required(); - count = injectStore(store, (state) => state[this.animal()]); + count = injectSelector(store, (state) => state[this.animal()]); } ``` @@ -86,3 +86,5 @@ export class Increment { updateState = updateState; } ``` + +`injectStore` remains available as a deprecated alias to `injectSelector`. diff --git a/docs/framework/angular/reference/functions/createStoreContext.md b/docs/framework/angular/reference/functions/createStoreContext.md new file mode 100644 index 00000000..05ac169b --- /dev/null +++ b/docs/framework/angular/reference/functions/createStoreContext.md @@ -0,0 +1,94 @@ +--- +id: createStoreContext +title: createStoreContext +--- + +# Function: createStoreContext() + +```ts +function createStoreContext(): object; +``` + +Defined in: [packages/angular-store/src/createStoreContext.ts:48](https://github.com/TanStack/store/blob/main/packages/angular-store/src/createStoreContext.ts#L48) + +Creates a typed Angular dependency-injection context for sharing a bundle of +atoms and stores with a component subtree. + +The returned `provideStoreContext` function accepts a factory that creates the +context value. Using a factory (rather than a static value) ensures each +component instance — and each SSR request — receives its own state, avoiding +cross-request pollution. + +Consumers call `injectStoreContext()` inside an injection context (typically a +constructor or field initializer) to retrieve the contextual atoms and stores, +then compose them with existing hooks like [injectSelector](injectSelector.md), +[injectValue](injectValue.md), and [injectAtom](injectAtom.md). + +## Type Parameters + +### TValue + +`TValue` *extends* `object` + +## Returns + +`object` + +### injectStoreContext() + +```ts +injectStoreContext: () => TValue; +``` + +#### Returns + +`TValue` + +### provideStoreContext() + +```ts +provideStoreContext: (factory) => Provider; +``` + +#### Parameters + +##### factory + +() => `TValue` + +#### Returns + +`Provider` + +## Example + +```ts +const { provideStoreContext, injectStoreContext } = createStoreContext<{ + countAtom: Atom + totalsStore: Store<{ count: number }> +}>() + +// Parent component provides the context +@Component({ + providers: [ + provideStoreContext(() => ({ + countAtom: createAtom(0), + totalsStore: new Store({ count: 0 }), + })), + ], + template: ``, +}) +class ParentComponent {} + +// Child component consumes the context +@Component({ template: `{{ count() }}` }) +class ChildComponent { + private ctx = injectStoreContext() + count = injectValue(this.ctx.countAtom) +} +``` + +## Throws + +When `injectStoreContext()` is called without a matching + `provideStoreContext()` in a parent component's providers. diff --git a/docs/framework/angular/reference/functions/injectAtom.md b/docs/framework/angular/reference/functions/injectAtom.md new file mode 100644 index 00000000..8d9bd6ef --- /dev/null +++ b/docs/framework/angular/reference/functions/injectAtom.md @@ -0,0 +1,48 @@ +--- +id: injectAtom +title: injectAtom +--- + +# Function: injectAtom() + +```ts +function injectAtom(atom, options?): WritableAtomSignal; +``` + +Defined in: [packages/angular-store/src/injectAtom.ts:44](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L44) + +Returns a [WritableAtomSignal](../interfaces/WritableAtomSignal.md) that reads the current atom value when +called and exposes a `.set` method for updates. + +Use this when a component needs to both read and update the same writable +atom. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### atom + +`Atom`\<`TValue`\> + +### options? + +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TValue`\> + +## Returns + +[`WritableAtomSignal`](../interfaces/WritableAtomSignal.md)\<`TValue`\> + +## Example + +```ts +readonly count = injectAtom(countAtom) + +increment() { + this.count.set((prev) => prev + 1) +} +``` diff --git a/docs/framework/angular/reference/functions/injectSelector.md b/docs/framework/angular/reference/functions/injectSelector.md new file mode 100644 index 00000000..a92e9bc2 --- /dev/null +++ b/docs/framework/angular/reference/functions/injectSelector.md @@ -0,0 +1,58 @@ +--- +id: injectSelector +title: injectSelector +--- + +# Function: injectSelector() + +```ts +function injectSelector( + source, + selector, +options?): Signal; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:93](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L93) + +Selects a slice of state from an atom or store and returns it as an Angular +signal. + +This is the primary Angular read hook for TanStack Store. + +## Type Parameters + +### TState + +`TState` + +### TSelected + +`TSelected` = `NoInfer`\<`TState`\> + +## Parameters + +### source + +[`SelectionSource`](../type-aliases/SelectionSource.md)\<`TState`\> + +### selector + +(`state`) => `TSelected` + +### options? + +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TSelected`\> + +## Returns + +`Signal`\<`TSelected`\> + +## Examples + +```ts +readonly count = injectSelector(counterStore, (state) => state.count) +``` + +```ts +readonly doubled = injectSelector(countAtom, (value) => value * 2) +``` diff --git a/docs/framework/angular/reference/functions/injectStore-1.md b/docs/framework/angular/reference/functions/injectStore-1.md new file mode 100644 index 00000000..6e89cddc --- /dev/null +++ b/docs/framework/angular/reference/functions/injectStore-1.md @@ -0,0 +1,55 @@ +--- +id: injectStore +title: injectStore +--- + +# ~~Function: injectStore()~~ + +```ts +function injectStore( + store, + selector?, +options?): Signal; +``` + +Defined in: [packages/angular-store/src/injectStore.ts:20](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectStore.ts#L20) + +Deprecated alias for [injectSelector](injectSelector.md). + +## Type Parameters + +### TState + +`TState` + +### TSelected + +`TSelected` = `NoInfer`\<`TState`\> + +## Parameters + +### store + +[`SelectionSource`](../type-aliases/SelectionSource.md)\<`TState`\> + +### selector? + +(`state`) => `TSelected` + +### options? + +`CompatibilityInjectStoreOptions`\<`TSelected`\> + +## Returns + +`Signal`\<`TSelected`\> + +## Example + +```ts +readonly count = injectStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `injectSelector` instead. diff --git a/docs/framework/angular/reference/functions/injectStore.md b/docs/framework/angular/reference/functions/injectStore.md index 0219a908..b88a398b 100644 --- a/docs/framework/angular/reference/functions/injectStore.md +++ b/docs/framework/angular/reference/functions/injectStore.md @@ -1,84 +1,65 @@ --- -id: injectStore -title: injectStore +id: _injectStore +title: _injectStore --- -# Function: injectStore() - -## Call Signature +# Function: \_injectStore() ```ts -function injectStore( +function _injectStore( store, - selector?, -options?): Signal; + selector, + options?): [Signal, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [index.ts:16](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L16) - -### Type Parameters +Defined in: [packages/angular-store/src/\_injectStore.ts:24](https://github.com/TanStack/store/blob/main/packages/angular-store/src/_injectStore.ts#L24) -#### TState - -`TState` - -#### TSelected - -`TSelected` = `NoInfer`\<`TState`\> - -### Parameters - -#### store - -`Atom`\<`TState`\> - -#### selector? - -(`state`) => `TSelected` +Experimental combined read+write injection function for stores, mirroring +injectAtom's pattern. -#### options? +Returns `[signal, actions]` when the store has an actions factory, or +`[signal, setState]` for plain stores. -`CreateSignalOptions`\<`TSelected`\> & `object` +## Type Parameters -### Returns +### TState -`Signal`\<`TSelected`\> - -## Call Signature +`TState` -```ts -function injectStore( - store, - selector?, -options?): Signal; -``` +### TActions -Defined in: [index.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/index.ts#L21) +`TActions` *extends* `StoreActionMap` -### Type Parameters +### TSelected -#### TState +`TSelected` = `NoInfer`\<`TState`\> -`TState` +## Parameters -#### TSelected +### store -`TSelected` = `NoInfer`\<`TState`\> +`Store`\<`TState`, `TActions`\> -### Parameters +### selector -#### store +(`state`) => `TSelected` -`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> +### options? -#### selector? +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TSelected`\> -(`state`) => `TSelected` +## Returns -#### options? +\[`Signal`\<`TSelected`\>, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] -`CreateSignalOptions`\<`TSelected`\> & `object` +## Example -### Returns +```ts +// Store with actions +readonly result = _injectStore(petStore, (s) => s.cats) +// result[0] is Signal, result[1] is actions -`Signal`\<`TSelected`\> +// Store without actions +readonly result = _injectStore(plainStore, (s) => s) +// result[0] is Signal, result[1] is setState +``` diff --git a/docs/framework/angular/reference/functions/injectValue.md b/docs/framework/angular/reference/functions/injectValue.md new file mode 100644 index 00000000..834dc44e --- /dev/null +++ b/docs/framework/angular/reference/functions/injectValue.md @@ -0,0 +1,46 @@ +--- +id: injectValue +title: injectValue +--- + +# Function: injectValue() + +```ts +function injectValue(source, options?): Signal; +``` + +Defined in: [packages/angular-store/src/injectValue.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectValue.ts#L21) + +Returns the current value signal for an atom or store. + +This is the whole-value counterpart to [injectSelector](injectSelector.md). + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### source + +`Atom`\<`TValue`\> | `ReadonlyAtom`\<`TValue`\> | `Store`\<`TValue`, `any`\> | `ReadonlyStore`\<`TValue`\> + +### options? + +[`InjectSelectorOptions`](../interfaces/InjectSelectorOptions.md)\<`TValue`\> + +## Returns + +`Signal`\<`TValue`\> + +## Examples + +```ts +readonly count = injectValue(countAtom) +``` + +```ts +readonly state = injectValue(counterStore) +``` diff --git a/docs/framework/angular/reference/index.md b/docs/framework/angular/reference/index.md index b77cf408..a836ecd2 100644 --- a/docs/framework/angular/reference/index.md +++ b/docs/framework/angular/reference/index.md @@ -5,6 +5,20 @@ title: "@tanstack/angular-store" # @tanstack/angular-store +## Interfaces + +- [InjectSelectorOptions](interfaces/InjectSelectorOptions.md) +- [WritableAtomSignal](interfaces/WritableAtomSignal.md) + +## Type Aliases + +- [SelectionSource](type-aliases/SelectionSource.md) + ## Functions -- [injectStore](functions/injectStore.md) +- [\_injectStore](functions/injectStore.md) +- [createStoreContext](functions/createStoreContext.md) +- [injectAtom](functions/injectAtom.md) +- [injectSelector](functions/injectSelector.md) +- [~~injectStore~~](functions/injectStore-1.md) +- [injectValue](functions/injectValue.md) diff --git a/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md b/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md new file mode 100644 index 00000000..993b6db4 --- /dev/null +++ b/docs/framework/angular/reference/interfaces/InjectSelectorOptions.md @@ -0,0 +1,70 @@ +--- +id: InjectSelectorOptions +title: InjectSelectorOptions +--- + +# Interface: InjectSelectorOptions\ + +Defined in: [packages/angular-store/src/injectSelector.ts:11](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L11) + +## Extends + +- `Omit`\<`CreateSignalOptions`\<`TSelected`\>, `"equal"`\> + +## Type Parameters + +### TSelected + +`TSelected` + +## Properties + +### compare()? + +```ts +optional compare: (a, b) => boolean; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:15](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L15) + +#### Parameters + +##### a + +`TSelected` + +##### b + +`TSelected` + +#### Returns + +`boolean` + +*** + +### debugName? + +```ts +optional debugName: string; +``` + +Defined in: node\_modules/.pnpm/@angular+core@21.2.8\_@angular+compiler@21.2.8\_rxjs@7.8.2\_zone.js@0.16.1/node\_modules/@angular/core/types/\_chrome\_dev\_tools\_performance-chunk.d.ts:54 + +A debug name for the signal. Used in Angular DevTools to identify the signal. + +#### Inherited from + +```ts +Omit.debugName +``` + +*** + +### injector? + +```ts +optional injector: Injector; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:16](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L16) diff --git a/docs/framework/angular/reference/interfaces/WritableAtomSignal.md b/docs/framework/angular/reference/interfaces/WritableAtomSignal.md new file mode 100644 index 00000000..ce3ea69a --- /dev/null +++ b/docs/framework/angular/reference/interfaces/WritableAtomSignal.md @@ -0,0 +1,54 @@ +--- +id: WritableAtomSignal +title: WritableAtomSignal +--- + +# Interface: WritableAtomSignal()\ + +Defined in: [packages/angular-store/src/injectAtom.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L21) + +A callable signal that reads the current atom value when invoked and +exposes a `.set` method matching the atom's native setter contract. + +This is the Angular-idiomatic return type for [injectAtom](../functions/injectAtom.md). It can +be used as a class property and called directly in templates. + +## Example + +```ts +readonly count = injectAtom(countAtom) + +// read in template: {{ count() }} +// write in class: this.count.set(5) +// this.count.set(prev => prev + 1) +``` + +## Type Parameters + +### T + +`T` + +```ts +WritableAtomSignal(): T; +``` + +Defined in: [packages/angular-store/src/injectAtom.ts:23](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L23) + +Read the current value. + +## Returns + +`T` + +## Properties + +### set + +```ts +set: (fn) => void & (value) => void; +``` + +Defined in: [packages/angular-store/src/injectAtom.ts:25](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectAtom.ts#L25) + +Set the atom value (accepts a direct value or an updater function). diff --git a/docs/framework/angular/reference/type-aliases/SelectionSource.md b/docs/framework/angular/reference/type-aliases/SelectionSource.md new file mode 100644 index 00000000..03444a60 --- /dev/null +++ b/docs/framework/angular/reference/type-aliases/SelectionSource.md @@ -0,0 +1,62 @@ +--- +id: SelectionSource +title: SelectionSource +--- + +# Type Alias: SelectionSource\ + +```ts +type SelectionSource = object; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:19](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L19) + +## Type Parameters + +### T + +`T` + +## Properties + +### get() + +```ts +get: () => T; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:20](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L20) + +#### Returns + +`T` + +*** + +### subscribe() + +```ts +subscribe: (listener) => object; +``` + +Defined in: [packages/angular-store/src/injectSelector.ts:21](https://github.com/TanStack/store/blob/main/packages/angular-store/src/injectSelector.ts#L21) + +#### Parameters + +##### listener + +(`value`) => `void` + +#### Returns + +`object` + +##### unsubscribe() + +```ts +unsubscribe: () => void; +``` + +###### Returns + +`void` diff --git a/docs/framework/preact/quick-start.md b/docs/framework/preact/quick-start.md index 841fcab7..a09c6ea0 100644 --- a/docs/framework/preact/quick-start.md +++ b/docs/framework/preact/quick-start.md @@ -7,7 +7,7 @@ The basic preact app example to get started with the TanStack preact-store. ```tsx import { render } from "preact"; -import { createStore, useStore } from "@tanstack/preact-store"; +import { createStore, useSelector } from "@tanstack/preact-store"; // You can instantiate the store outside of Preact components too! export const store = createStore({ @@ -18,7 +18,7 @@ export const store = createStore({ // This will only re-render when `state[animal]` changes. If an unrelated store property changes, it won't re-render const Display = ({ animal }) => { - const count = useStore(store, (state) => state[animal]); + const count = useSelector(store, (state) => state[animal]); return
{`${animal}: ${count}`}
; }; @@ -53,3 +53,4 @@ function App() { render(, document.getElementById("root")); ``` +`useStore` remains available as a deprecated alias to `useSelector`. diff --git a/docs/framework/preact/reference/functions/createStoreContext.md b/docs/framework/preact/reference/functions/createStoreContext.md new file mode 100644 index 00000000..bef5f673 --- /dev/null +++ b/docs/framework/preact/reference/functions/createStoreContext.md @@ -0,0 +1,95 @@ +--- +id: createStoreContext +title: createStoreContext +--- + +# Function: createStoreContext() + +```ts +function createStoreContext(): object; +``` + +Defined in: [preact-store/src/createStoreContext.tsx:44](https://github.com/TanStack/store/blob/main/packages/preact-store/src/createStoreContext.tsx#L44) + +Creates a typed Preact context for sharing a bundle of atoms and stores with +a subtree. + +The returned `StoreProvider` only transports the provided object through +Preact context. Consumers destructure the contextual atoms and stores, then +compose them with the existing hooks like [useSelector](useSelector.md), +[useValue](useValue.md), useSetValue, and [useAtom](useAtom.md). + +The object shape is preserved exactly, so keyed atoms and stores remain fully +typed when read back with `useStoreContext()`. + +## Type Parameters + +### TValue + +`TValue` *extends* `object` + +## Returns + +`object` + +### StoreProvider() + +```ts +StoreProvider: (__namedParameters) => Element; +``` + +#### Parameters + +##### \_\_namedParameters + +###### children? + +`ComponentChildren` + +###### value + +`TValue` + +#### Returns + +`Element` + +### useStoreContext() + +```ts +useStoreContext: () => TValue; +``` + +#### Returns + +`TValue` + +## Example + +```tsx +const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: Atom + totalsStore: Store<{ count: number }> +}>() + +function CountButton() { + const { countAtom, totalsStore } = useStoreContext() + const count = useValue(countAtom) + const total = useSelector(totalsStore, (state) => state.count) + + return ( + + ) +} +``` + +## Throws + +When `useStoreContext()` is called outside the matching `StoreProvider`. diff --git a/docs/framework/preact/reference/functions/shallow.md b/docs/framework/preact/reference/functions/shallow.md deleted file mode 100644 index eb6e75c9..00000000 --- a/docs/framework/preact/reference/functions/shallow.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: shallow -title: shallow ---- - -# Function: shallow() - -```ts -function shallow(objA, objB): boolean; -``` - -Defined in: [index.ts:116](https://github.com/TanStack/store/blob/main/packages/preact-store/src/index.ts#L116) - -## Type Parameters - -### T - -`T` - -## Parameters - -### objA - -`T` - -### objB - -`T` - -## Returns - -`boolean` diff --git a/docs/framework/preact/reference/functions/useAtom.md b/docs/framework/preact/reference/functions/useAtom.md new file mode 100644 index 00000000..9a0daab4 --- /dev/null +++ b/docs/framework/preact/reference/functions/useAtom.md @@ -0,0 +1,49 @@ +--- +id: useAtom +title: useAtom +--- + +# Function: useAtom() + +```ts +function useAtom(atom, options?): [TValue, (fn) => void & (value) => void]; +``` + +Defined in: [preact-store/src/useAtom.ts:22](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useAtom.ts#L22) + +Returns the current atom value together with a stable setter. + +Use this when a component needs to both read and update the same writable +atom. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### atom + +`Atom`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +\[`TValue`, (`fn`) => `void` & (`value`) => `void`\] + +## Example + +```tsx +const [count, setCount] = useAtom(countAtom) + +return ( + +) +``` diff --git a/docs/framework/preact/reference/functions/useCreateAtom.md b/docs/framework/preact/reference/functions/useCreateAtom.md new file mode 100644 index 00000000..a92253d0 --- /dev/null +++ b/docs/framework/preact/reference/functions/useCreateAtom.md @@ -0,0 +1,104 @@ +--- +id: useCreateAtom +title: useCreateAtom +--- + +# Function: useCreateAtom() + +## Call Signature + +```ts +function useCreateAtom(getValue, options?): ReadonlyAtom; +``` + +Defined in: [preact-store/src/useCreateAtom.ts:27](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateAtom.ts#L27) + +Creates a stable atom instance for the lifetime of the component. + +Pass an initial value to create a writable atom, or a getter function to +create a readonly derived atom. This mirrors createAtom, but only +creates the atom once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### getValue + +(`prev?`) => `T` + +#### options? + +`AtomOptions`\<`T`\> + +### Returns + +`ReadonlyAtom`\<`T`\> + +### Example + +```tsx +function Counter() { + const countAtom = useCreateAtom(0) + const [count, setCount] = useAtom(countAtom) + + return ( + + ) +} +``` + +## Call Signature + +```ts +function useCreateAtom(initialValue, options?): Atom; +``` + +Defined in: [preact-store/src/useCreateAtom.ts:31](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateAtom.ts#L31) + +Creates a stable atom instance for the lifetime of the component. + +Pass an initial value to create a writable atom, or a getter function to +create a readonly derived atom. This mirrors createAtom, but only +creates the atom once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### initialValue + +`T` + +#### options? + +`AtomOptions`\<`T`\> + +### Returns + +`Atom`\<`T`\> + +### Example + +```tsx +function Counter() { + const countAtom = useCreateAtom(0) + const [count, setCount] = useAtom(countAtom) + + return ( + + ) +} +``` diff --git a/docs/framework/preact/reference/functions/useCreateStore.md b/docs/framework/preact/reference/functions/useCreateStore.md new file mode 100644 index 00000000..56b8f91c --- /dev/null +++ b/docs/framework/preact/reference/functions/useCreateStore.md @@ -0,0 +1,161 @@ +--- +id: useCreateStore +title: useCreateStore +--- + +# Function: useCreateStore() + +## Call Signature + +```ts +function useCreateStore(getValue): ReadonlyStore; +``` + +Defined in: [preact-store/src/useCreateStore.ts:38](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateStore.ts#L38) + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This mirrors createStore, but only +creates the store once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### getValue + +(`prev?`) => `T` + +### Returns + +`ReadonlyStore`\<`T`\> + +### Example + +```tsx +function Counter() { + const counterStore = useCreateStore({ count: 0 }) + const count = useSelector(counterStore, (state) => state.count) + const setState = useSetValue(counterStore) + + return ( + + ) +} +``` + +## Call Signature + +```ts +function useCreateStore(initialValue): Store; +``` + +Defined in: [preact-store/src/useCreateStore.ts:41](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateStore.ts#L41) + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This mirrors createStore, but only +creates the store once per component mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### initialValue + +`T` + +### Returns + +`Store`\<`T`\> + +### Example + +```tsx +function Counter() { + const counterStore = useCreateStore({ count: 0 }) + const count = useSelector(counterStore, (state) => state.count) + const setState = useSetValue(counterStore) + + return ( + + ) +} +``` + +## Call Signature + +```ts +function useCreateStore(initialValue, actions): Store; +``` + +Defined in: [preact-store/src/useCreateStore.ts:42](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useCreateStore.ts#L42) + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This mirrors createStore, but only +creates the store once per component mount. + +### Type Parameters + +#### T + +`T` + +#### TActions + +`TActions` *extends* `StoreActionMap` + +### Parameters + +#### initialValue + +`NonFunction`\<`T`\> + +#### actions + +`StoreActionsFactory`\<`T`, `TActions`\> + +### Returns + +`Store`\<`T`, `TActions`\> + +### Example + +```tsx +function Counter() { + const counterStore = useCreateStore({ count: 0 }) + const count = useSelector(counterStore, (state) => state.count) + const setState = useSetValue(counterStore) + + return ( + + ) +} +``` diff --git a/docs/framework/preact/reference/functions/useSelector.md b/docs/framework/preact/reference/functions/useSelector.md new file mode 100644 index 00000000..7a406e6b --- /dev/null +++ b/docs/framework/preact/reference/functions/useSelector.md @@ -0,0 +1,59 @@ +--- +id: useSelector +title: useSelector +--- + +# Function: useSelector() + +```ts +function useSelector( + source, + selector, + options?): TSelected; +``` + +Defined in: [preact-store/src/useSelector.ts:127](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSelector.ts#L127) + +Selects a slice of state from an atom or store and subscribes the component +to that selection. + +This is the primary Preact read hook for TanStack Store. Use it when a +component only needs part of a source value. + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +`SelectionSource`\<`TSource`\> + +### selector + +(`snapshot`) => `TSelected` + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> + +## Returns + +`TSelected` + +## Examples + +```tsx +const count = useSelector(counterStore, (state) => state.count) +``` + +```tsx +const doubled = useSelector(countAtom, (value) => value * 2) +``` diff --git a/docs/framework/preact/reference/functions/useStore-1.md b/docs/framework/preact/reference/functions/useStore-1.md new file mode 100644 index 00000000..24850d6d --- /dev/null +++ b/docs/framework/preact/reference/functions/useStore-1.md @@ -0,0 +1,61 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, + compare?): TSelected; +``` + +Defined in: [preact-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useStore.ts#L13) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +#### get + +() => `TSource` + +#### subscribe + +(`listener`) => `object` + +### selector + +(`snapshot`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`TSelected` + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/preact/reference/functions/useStore.md b/docs/framework/preact/reference/functions/useStore.md index b9a67a5d..ecfcbe95 100644 --- a/docs/framework/preact/reference/functions/useStore.md +++ b/docs/framework/preact/reference/functions/useStore.md @@ -1,18 +1,24 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# Function: useStore() +# Function: \_useStore() ```ts -function useStore( +function _useStore( store, selector, - options): TSelected; + options?): [TSelected, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [index.ts:100](https://github.com/TanStack/store/blob/main/packages/preact-store/src/index.ts#L100) +Defined in: [preact-store/src/\_useStore.ts:24](https://github.com/TanStack/store/blob/main/packages/preact-store/src/_useStore.ts#L24) + +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters @@ -20,6 +26,10 @@ Defined in: [index.ts:100](https://github.com/TanStack/store/blob/main/packages/ `TState` +### TActions + +`TActions` *extends* `StoreActionMap` + ### TSelected `TSelected` = `NoInfer`\<`TState`\> @@ -28,16 +38,27 @@ Defined in: [index.ts:100](https://github.com/TanStack/store/blob/main/packages/ ### store -`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> +`Store`\<`TState`, `TActions`\> ### selector (`state`) => `TSelected` -### options +### options? -`UseStoreOptions`\<`TSelected`\> = `{}` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`TSelected` +\[`TSelected`, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] + +## Example + +```tsx +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +setState((prev) => prev + 1) +``` diff --git a/docs/framework/preact/reference/functions/useValue.md b/docs/framework/preact/reference/functions/useValue.md new file mode 100644 index 00000000..78d43934 --- /dev/null +++ b/docs/framework/preact/reference/functions/useValue.md @@ -0,0 +1,47 @@ +--- +id: useValue +title: useValue +--- + +# Function: useValue() + +```ts +function useValue(source, options?): TValue; +``` + +Defined in: [preact-store/src/useValue.ts:22](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useValue.ts#L22) + +Subscribes to an atom or store and returns its current value. + +This is the whole-value counterpart to [useSelector](useSelector.md). Use it when the +component needs the entire current value from a source. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### source + +`Atom`\<`TValue`\> | `ReadonlyAtom`\<`TValue`\> | `Store`\<`TValue`, `any`\> | `ReadonlyStore`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +`TValue` + +## Examples + +```tsx +const count = useValue(countAtom) +``` + +```tsx +const state = useValue(counterStore) +``` diff --git a/docs/framework/preact/reference/index.md b/docs/framework/preact/reference/index.md index 1ef55ff2..7534c172 100644 --- a/docs/framework/preact/reference/index.md +++ b/docs/framework/preact/reference/index.md @@ -5,7 +5,17 @@ title: "@tanstack/preact-store" # @tanstack/preact-store +## Interfaces + +- [UseSelectorOptions](interfaces/UseSelectorOptions.md) + ## Functions -- [shallow](functions/shallow.md) -- [useStore](functions/useStore.md) +- [\_useStore](functions/useStore.md) +- [createStoreContext](functions/createStoreContext.md) +- [useAtom](functions/useAtom.md) +- [useCreateAtom](functions/useCreateAtom.md) +- [useCreateStore](functions/useCreateStore.md) +- [useSelector](functions/useSelector.md) +- [~~useStore~~](functions/useStore-1.md) +- [useValue](functions/useValue.md) diff --git a/docs/framework/preact/reference/interfaces/UseSelectorOptions.md b/docs/framework/preact/reference/interfaces/UseSelectorOptions.md new file mode 100644 index 00000000..b3a6fa5e --- /dev/null +++ b/docs/framework/preact/reference/interfaces/UseSelectorOptions.md @@ -0,0 +1,38 @@ +--- +id: UseSelectorOptions +title: UseSelectorOptions +--- + +# Interface: UseSelectorOptions\ + +Defined in: [preact-store/src/useSelector.ts:9](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSelector.ts#L9) + +## Type Parameters + +### TSelected + +`TSelected` + +## Properties + +### compare()? + +```ts +optional compare: (a, b) => boolean; +``` + +Defined in: [preact-store/src/useSelector.ts:10](https://github.com/TanStack/store/blob/main/packages/preact-store/src/useSelector.ts#L10) + +#### Parameters + +##### a + +`TSelected` + +##### b + +`TSelected` + +#### Returns + +`boolean` diff --git a/docs/framework/react/quick-start.md b/docs/framework/react/quick-start.md index bab45973..9162d4ad 100644 --- a/docs/framework/react/quick-start.md +++ b/docs/framework/react/quick-start.md @@ -8,7 +8,7 @@ The basic react app example to get started with the TanStack react-store. ```tsx import React from "react"; import ReactDOM from "react-dom/client"; -import { createStore, useStore } from "@tanstack/react-store"; +import { createStore, useSelector } from "@tanstack/react-store"; // You can instantiate the store outside of React components too! export const store = createStore({ @@ -19,7 +19,7 @@ export const store = createStore({ // This will only re-render when `state[animal]` changes. If an unrelated store property changes, it won't re-render const Display = ({ animal }) => { - const count = useStore(store, (state) => state[animal]); + const count = useSelector(store, (state) => state[animal]); return
{`${animal}: ${count}`}
; }; @@ -55,3 +55,5 @@ const root = ReactDOM.createRoot(document.getElementById("root")); root.render(); ``` + +`useStore` remains available as a deprecated alias to `useSelector`. diff --git a/docs/framework/react/reference/functions/createStoreContext.md b/docs/framework/react/reference/functions/createStoreContext.md new file mode 100644 index 00000000..1515155b --- /dev/null +++ b/docs/framework/react/reference/functions/createStoreContext.md @@ -0,0 +1,86 @@ +--- +id: createStoreContext +title: createStoreContext +--- + +# Function: createStoreContext() + +```ts +function createStoreContext(): object; +``` + +Defined in: [packages/react-store/src/createStoreContext.tsx:40](https://github.com/TanStack/store/blob/main/packages/react-store/src/createStoreContext.tsx#L40) + +Creates a typed React context for sharing a bundle of atoms and stores with a subtree. + +The returned `StoreProvider` only transports the provided object through +React context. Consumers destructure the contextual atoms and stores, then +compose them with the existing hooks like [useSelector](useSelector.md), +[useValue](useValue.md), useSetValue, and [useAtom](useAtom.md). + +The object shape is preserved exactly, so keyed atoms and stores remain fully +typed when read back with `useStoreContext()`. + +## Type Parameters + +### TValue + +`TValue` *extends* `object` + +## Returns + +`object` + +### StoreProvider() + +```ts +StoreProvider: (props) => ReactElement; +``` + +#### Parameters + +##### props + +`object` & `object` + +#### Returns + +`ReactElement` + +### useStoreContext() + +```ts +useStoreContext: () => TValue; +``` + +#### Returns + +`TValue` + +## Example + +```tsx +const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: Atom + totalsStore: Store<{ count: number }> +}>() + +function CountButton() { + const { countAtom, totalsStore } = useStoreContext() + const count = useValue(countAtom) + const total = useSelector(totalsStore, (state) => state.count) + + return ( + + ) +} +``` + +## Throws + +When `useStoreContext()` is called outside the matching `StoreProvider`. diff --git a/docs/framework/react/reference/functions/useAtom.md b/docs/framework/react/reference/functions/useAtom.md new file mode 100644 index 00000000..5cd2eb68 --- /dev/null +++ b/docs/framework/react/reference/functions/useAtom.md @@ -0,0 +1,43 @@ +--- +id: useAtom +title: useAtom +--- + +# Function: useAtom() + +```ts +function useAtom(atom, options?): [TValue, (fn) => void & (value) => void]; +``` + +Defined in: [packages/react-store/src/useAtom.ts:16](https://github.com/TanStack/store/blob/main/packages/react-store/src/useAtom.ts#L16) + +Returns the current atom value together with a stable setter. + +This is the writable-atom convenience hook for components that need to both +read and update the same atom. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### atom + +`Atom`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +\[`TValue`, (`fn`) => `void` & (`value`) => `void`\] + +## Example + +```tsx +const [count, setCount] = useAtom(countAtom) +``` diff --git a/docs/framework/react/reference/functions/useCreateAtom.md b/docs/framework/react/reference/functions/useCreateAtom.md new file mode 100644 index 00000000..1b6cfaab --- /dev/null +++ b/docs/framework/react/reference/functions/useCreateAtom.md @@ -0,0 +1,86 @@ +--- +id: useCreateAtom +title: useCreateAtom +--- + +# Function: useCreateAtom() + +## Call Signature + +```ts +function useCreateAtom(getValue, options?): ReadonlyAtom; +``` + +Defined in: [packages/react-store/src/useCreateAtom.ts:17](https://github.com/TanStack/store/blob/main/packages/react-store/src/useCreateAtom.ts#L17) + +Creates a stable atom instance for the lifetime of the component. + +Pass an initial value to create a writable atom, or a getter function to +create a readonly derived atom. This hook mirrors the overloads from +createAtom, but ensures the atom is only created once per mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### getValue + +(`prev?`) => `T` + +#### options? + +`AtomOptions`\<`T`\> + +### Returns + +`ReadonlyAtom`\<`T`\> + +### Example + +```tsx +const countAtom = useCreateAtom(0) +``` + +## Call Signature + +```ts +function useCreateAtom(initialValue, options?): Atom; +``` + +Defined in: [packages/react-store/src/useCreateAtom.ts:21](https://github.com/TanStack/store/blob/main/packages/react-store/src/useCreateAtom.ts#L21) + +Creates a stable atom instance for the lifetime of the component. + +Pass an initial value to create a writable atom, or a getter function to +create a readonly derived atom. This hook mirrors the overloads from +createAtom, but ensures the atom is only created once per mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### initialValue + +`T` + +#### options? + +`AtomOptions`\<`T`\> + +### Returns + +`Atom`\<`T`\> + +### Example + +```tsx +const countAtom = useCreateAtom(0) +``` diff --git a/docs/framework/react/reference/functions/useCreateStore.md b/docs/framework/react/reference/functions/useCreateStore.md new file mode 100644 index 00000000..f01d05ba --- /dev/null +++ b/docs/framework/react/reference/functions/useCreateStore.md @@ -0,0 +1,122 @@ +--- +id: useCreateStore +title: useCreateStore +--- + +# Function: useCreateStore() + +## Call Signature + +```ts +function useCreateStore(getValue): ReadonlyStore; +``` + +Defined in: [packages/react-store/src/useCreateStore.ts:24](https://github.com/TanStack/store/blob/main/packages/react-store/src/useCreateStore.ts#L24) + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This hook mirrors the overloads from +createStore, but ensures the store is only created once per mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### getValue + +(`prev?`) => `T` + +### Returns + +`ReadonlyStore`\<`T`\> + +### Example + +```tsx +const counterStore = useCreateStore({ count: 0 }) +``` + +## Call Signature + +```ts +function useCreateStore(initialValue): Store; +``` + +Defined in: [packages/react-store/src/useCreateStore.ts:27](https://github.com/TanStack/store/blob/main/packages/react-store/src/useCreateStore.ts#L27) + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This hook mirrors the overloads from +createStore, but ensures the store is only created once per mount. + +### Type Parameters + +#### T + +`T` + +### Parameters + +#### initialValue + +`T` + +### Returns + +`Store`\<`T`\> + +### Example + +```tsx +const counterStore = useCreateStore({ count: 0 }) +``` + +## Call Signature + +```ts +function useCreateStore(initialValue, actions): Store; +``` + +Defined in: [packages/react-store/src/useCreateStore.ts:28](https://github.com/TanStack/store/blob/main/packages/react-store/src/useCreateStore.ts#L28) + +Creates a stable store instance for the lifetime of the component. + +Pass an initial value to create a writable store, or a getter function to +create a readonly derived store. This hook mirrors the overloads from +createStore, but ensures the store is only created once per mount. + +### Type Parameters + +#### T + +`T` + +#### TActions + +`TActions` *extends* `StoreActionMap` + +### Parameters + +#### initialValue + +`NonFunction`\<`T`\> + +#### actions + +`StoreActionsFactory`\<`T`, `TActions`\> + +### Returns + +`Store`\<`T`, `TActions`\> + +### Example + +```tsx +const counterStore = useCreateStore({ count: 0 }) +``` diff --git a/docs/framework/react/reference/functions/useSelector.md b/docs/framework/react/reference/functions/useSelector.md new file mode 100644 index 00000000..e36d806b --- /dev/null +++ b/docs/framework/react/reference/functions/useSelector.md @@ -0,0 +1,60 @@ +--- +id: useSelector +title: useSelector +--- + +# Function: useSelector() + +```ts +function useSelector( + source, + selector, + options?): TSelected; +``` + +Defined in: [packages/react-store/src/useSelector.ts:41](https://github.com/TanStack/store/blob/main/packages/react-store/src/useSelector.ts#L41) + +Selects a slice of state from an atom or store and subscribes the component +to that selection. + +This is the primary React read hook for TanStack Store. It works with any +source that exposes `get()` and `subscribe()`, including atoms, readonly +atoms, stores, and readonly stores. + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +`SelectionSource`\<`TSource`\> + +### selector + +(`snapshot`) => `TSelected` + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> + +## Returns + +`TSelected` + +## Examples + +```tsx +const count = useSelector(counterStore, (state) => state.count) +``` + +```tsx +const count = useSelector(countAtom, (value) => value) +``` diff --git a/docs/framework/react/reference/functions/useStore-1.md b/docs/framework/react/reference/functions/useStore-1.md new file mode 100644 index 00000000..7fab183d --- /dev/null +++ b/docs/framework/react/reference/functions/useStore-1.md @@ -0,0 +1,61 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, + compare?): TSelected; +``` + +Defined in: [packages/react-store/src/useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/react-store/src/useStore.ts#L13) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +#### get + +() => `TSource` + +#### subscribe + +(`listener`) => `object` + +### selector + +(`snapshot`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`TSelected` + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/react/reference/functions/useStore.md b/docs/framework/react/reference/functions/useStore.md index 680155b5..bbc43c6b 100644 --- a/docs/framework/react/reference/functions/useStore.md +++ b/docs/framework/react/reference/functions/useStore.md @@ -1,43 +1,64 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# Function: useStore() +# Function: \_useStore() ```ts -function useStore( - atom, +function _useStore( + store, selector, - compare): T; + options?): [TSelected, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [useStore.ts:13](https://github.com/TanStack/store/blob/main/packages/react-store/src/useStore.ts#L13) +Defined in: [packages/react-store/src/\_useStore.ts:24](https://github.com/TanStack/store/blob/main/packages/react-store/src/_useStore.ts#L24) + +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters -### TAtom +### TState + +`TState` + +### TActions -`TAtom` *extends* `AnyAtom` \| `undefined` +`TActions` *extends* `StoreActionMap` -### T +### TSelected -`T` +`TSelected` = `NoInfer`\<`TState`\> ## Parameters -### atom +### store -`TAtom` +`Store`\<`TState`, `TActions`\> ### selector -(`snapshot`) => `T` +(`state`) => `TSelected` -### compare +### options? -(`a`, `b`) => `boolean` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`T` +\[`TSelected`, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] + +## Example + +```tsx +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +setState((prev) => prev + 1) +``` diff --git a/docs/framework/react/reference/functions/useValue.md b/docs/framework/react/reference/functions/useValue.md new file mode 100644 index 00000000..4da54112 --- /dev/null +++ b/docs/framework/react/reference/functions/useValue.md @@ -0,0 +1,49 @@ +--- +id: useValue +title: useValue +--- + +# Function: useValue() + +```ts +function useValue(source, options?): TValue; +``` + +Defined in: [packages/react-store/src/useValue.ts:23](https://github.com/TanStack/store/blob/main/packages/react-store/src/useValue.ts#L23) + +Subscribes to an atom or store and returns its current value. + +This is the whole-value counterpart to [useSelector](useSelector.md). Use it when a +component needs the entire current value from a writable or readonly atom or +store. Pass `options.compare` to suppress rerenders when successive values +should be treated as equivalent. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### source + +`Atom`\<`TValue`\> | `ReadonlyAtom`\<`TValue`\> | `Store`\<`TValue`, `any`\> | `ReadonlyStore`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +`TValue` + +## Examples + +```tsx +const count = useValue(countAtom) +``` + +```tsx +const state = useValue(appStore) +``` diff --git a/docs/framework/react/reference/index.md b/docs/framework/react/reference/index.md index 11b180f1..0505d839 100644 --- a/docs/framework/react/reference/index.md +++ b/docs/framework/react/reference/index.md @@ -5,7 +5,17 @@ title: "@tanstack/react-store" # @tanstack/react-store +## Interfaces + +- [UseSelectorOptions](interfaces/UseSelectorOptions.md) + ## Functions -- [shallow](functions/shallow.md) -- [useStore](functions/useStore.md) +- [\_useStore](functions/useStore.md) +- [createStoreContext](functions/createStoreContext.md) +- [useAtom](functions/useAtom.md) +- [useCreateAtom](functions/useCreateAtom.md) +- [useCreateStore](functions/useCreateStore.md) +- [useSelector](functions/useSelector.md) +- [~~useStore~~](functions/useStore-1.md) +- [useValue](functions/useValue.md) diff --git a/docs/framework/react/reference/interfaces/UseSelectorOptions.md b/docs/framework/react/reference/interfaces/UseSelectorOptions.md new file mode 100644 index 00000000..efaa91df --- /dev/null +++ b/docs/framework/react/reference/interfaces/UseSelectorOptions.md @@ -0,0 +1,38 @@ +--- +id: UseSelectorOptions +title: UseSelectorOptions +--- + +# Interface: UseSelectorOptions\ + +Defined in: [packages/react-store/src/useSelector.ts:4](https://github.com/TanStack/store/blob/main/packages/react-store/src/useSelector.ts#L4) + +## Type Parameters + +### TSelected + +`TSelected` + +## Properties + +### compare()? + +```ts +optional compare: (a, b) => boolean; +``` + +Defined in: [packages/react-store/src/useSelector.ts:5](https://github.com/TanStack/store/blob/main/packages/react-store/src/useSelector.ts#L5) + +#### Parameters + +##### a + +`TSelected` + +##### b + +`TSelected` + +#### Returns + +`boolean` diff --git a/docs/framework/solid/quick-start.md b/docs/framework/solid/quick-start.md index 67819d76..19f63861 100644 --- a/docs/framework/solid/quick-start.md +++ b/docs/framework/solid/quick-start.md @@ -6,7 +6,7 @@ id: quick-start The basic Solid app example to get started with the TanStack Solid-store. ```jsx -import { createStore, useStore } from '@tanstack/solid-store'; +import { createStore, useSelector } from '@tanstack/solid-store'; // You can instantiate the store outside of Solid components too! export const store = createStore({ @@ -15,7 +15,7 @@ export const store = createStore({ }) export const Display = (props) => { -  const count = useStore(store, (state) => state[props.animals]); +  const count = useSelector(store, (state) => state[props.animals]);   return (           {props.animals}: {count()} @@ -58,3 +58,5 @@ const App = () => { export default App; ``` + +`useStore` remains available as a deprecated alias to `useSelector`. diff --git a/docs/framework/solid/reference/functions/shallow.md b/docs/framework/solid/reference/functions/shallow.md deleted file mode 100644 index d8809f33..00000000 --- a/docs/framework/solid/reference/functions/shallow.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: shallow -title: shallow ---- - -# Function: shallow() - -```ts -function shallow(objA, objB): boolean; -``` - -Defined in: [index.tsx:35](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L35) - -## Type Parameters - -### T - -`T` - -## Parameters - -### objA - -`T` - -### objB - -`T` - -## Returns - -`boolean` diff --git a/docs/framework/solid/reference/functions/useAtom.md b/docs/framework/solid/reference/functions/useAtom.md new file mode 100644 index 00000000..a05c4337 --- /dev/null +++ b/docs/framework/solid/reference/functions/useAtom.md @@ -0,0 +1,49 @@ +--- +id: useAtom +title: useAtom +--- + +# Function: useAtom() + +```ts +function useAtom(atom, options?): [Accessor, (fn) => void & (value) => void]; +``` + +Defined in: [solid-store/src/useAtom.ts:23](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useAtom.ts#L23) + +Returns the current atom accessor together with a setter. + +Use this when a component needs to both read and update the same writable +atom. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### atom + +`Atom`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +\[`Accessor`\<`TValue`\>, (`fn`) => `void` & (`value`) => `void`\] + +## Example + +```tsx +const [count, setCount] = useAtom(countAtom) + +return ( + +) +``` diff --git a/docs/framework/solid/reference/functions/useSelector.md b/docs/framework/solid/reference/functions/useSelector.md new file mode 100644 index 00000000..f2a109fd --- /dev/null +++ b/docs/framework/solid/reference/functions/useSelector.md @@ -0,0 +1,61 @@ +--- +id: useSelector +title: useSelector +--- + +# Function: useSelector() + +```ts +function useSelector( + source, + selector, +options?): Accessor; +``` + +Defined in: [solid-store/src/useSelector.ts:38](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSelector.ts#L38) + +Selects a slice of state from an atom or store and subscribes the component +to that selection. + +This is the primary Solid read hook for TanStack Store. It returns a Solid +accessor so consumers can read the selected value reactively. + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +`SelectionSource`\<`TSource`\> + +### selector + +(`snapshot`) => `TSelected` + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> + +## Returns + +`Accessor`\<`TSelected`\> + +## Examples + +```tsx +const count = useSelector(counterStore, (state) => state.count) + +return

{count()}

+``` + +```tsx +const doubled = useSelector(countAtom, (value) => value * 2) +``` diff --git a/docs/framework/solid/reference/functions/useStore-1.md b/docs/framework/solid/reference/functions/useStore-1.md new file mode 100644 index 00000000..6e73b2fc --- /dev/null +++ b/docs/framework/solid/reference/functions/useStore-1.md @@ -0,0 +1,61 @@ +--- +id: useStore +title: useStore +--- + +# ~~Function: useStore()~~ + +```ts +function useStore( + source, + selector, +compare?): Accessor; +``` + +Defined in: [solid-store/src/useStore.ts:14](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useStore.ts#L14) + +Deprecated alias for [useSelector](useSelector.md). + +## Type Parameters + +### TSource + +`TSource` + +### TSelected + +`TSelected` + +## Parameters + +### source + +#### get + +() => `TSource` + +#### subscribe + +(`listener`) => `object` + +### selector + +(`snapshot`) => `TSelected` + +### compare? + +(`a`, `b`) => `boolean` + +## Returns + +`Accessor`\<`TSelected`\> + +## Example + +```tsx +const count = useStore(counterStore, (state) => state.count) +``` + +## Deprecated + +Use `useSelector` instead. diff --git a/docs/framework/solid/reference/functions/useStore.md b/docs/framework/solid/reference/functions/useStore.md index 6952ba19..046e7a04 100644 --- a/docs/framework/solid/reference/functions/useStore.md +++ b/docs/framework/solid/reference/functions/useStore.md @@ -1,18 +1,24 @@ --- -id: useStore -title: useStore +id: _useStore +title: _useStore --- -# Function: useStore() +# Function: \_useStore() ```ts -function useStore( +function _useStore( store, selector, -options): Accessor; + options?): [Accessor, [TActions] extends [never] ? (updater) => void : TActions]; ``` -Defined in: [index.tsx:12](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L12) +Defined in: [solid-store/src/\_useStore.ts:23](https://github.com/TanStack/store/blob/main/packages/solid-store/src/_useStore.ts#L23) + +Experimental combined read+write hook for stores, mirroring useAtom's tuple +pattern. + +Returns `[selected, actions]` when the store has an actions factory, or +`[selected, setState]` for plain stores. ## Type Parameters @@ -20,6 +26,10 @@ Defined in: [index.tsx:12](https://github.com/TanStack/store/blob/main/packages/ `TState` +### TActions + +`TActions` *extends* `StoreActionMap` + ### TSelected `TSelected` = `NoInfer`\<`TState`\> @@ -28,16 +38,27 @@ Defined in: [index.tsx:12](https://github.com/TanStack/store/blob/main/packages/ ### store -`Atom`\<`TState`\> | `ReadonlyAtom`\<`TState`\> +`Store`\<`TState`, `TActions`\> ### selector (`state`) => `TSelected` -### options +### options? -`UseStoreOptions`\<`TSelected`\> = `{}` +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TSelected`\> ## Returns -`Accessor`\<`TSelected`\> +\[`Accessor`\<`TSelected`\>, \[`TActions`\] *extends* \[`never`\] ? (`updater`) => `void` : `TActions`\] + +## Example + +```tsx +// Store with actions +const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + +// Store without actions +const [count, setState] = _useStore(plainStore, (s) => s) +setState((prev) => prev + 1) +``` diff --git a/docs/framework/solid/reference/functions/useValue.md b/docs/framework/solid/reference/functions/useValue.md new file mode 100644 index 00000000..770bda4d --- /dev/null +++ b/docs/framework/solid/reference/functions/useValue.md @@ -0,0 +1,49 @@ +--- +id: useValue +title: useValue +--- + +# Function: useValue() + +```ts +function useValue(source, options?): Accessor; +``` + +Defined in: [solid-store/src/useValue.ts:24](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useValue.ts#L24) + +Subscribes to an atom or store and returns its current value accessor. + +This is the whole-value counterpart to [useSelector](useSelector.md). Use it when the +component needs the entire current value from a source. + +## Type Parameters + +### TValue + +`TValue` + +## Parameters + +### source + +`Atom`\<`TValue`\> | `ReadonlyAtom`\<`TValue`\> | `Store`\<`TValue`, `any`\> | `ReadonlyStore`\<`TValue`\> + +### options? + +[`UseSelectorOptions`](../interfaces/UseSelectorOptions.md)\<`TValue`\> + +## Returns + +`Accessor`\<`TValue`\> + +## Examples + +```tsx +const count = useValue(countAtom) + +return

{count()}

+``` + +```tsx +const state = useValue(counterStore) +``` diff --git a/docs/framework/solid/reference/index.md b/docs/framework/solid/reference/index.md index 71951743..5e9608c9 100644 --- a/docs/framework/solid/reference/index.md +++ b/docs/framework/solid/reference/index.md @@ -5,7 +5,14 @@ title: "@tanstack/solid-store" # @tanstack/solid-store +## Interfaces + +- [UseSelectorOptions](interfaces/UseSelectorOptions.md) + ## Functions -- [shallow](functions/shallow.md) -- [useStore](functions/useStore.md) +- [\_useStore](functions/useStore.md) +- [useAtom](functions/useAtom.md) +- [useSelector](functions/useSelector.md) +- [~~useStore~~](functions/useStore-1.md) +- [useValue](functions/useValue.md) diff --git a/docs/framework/solid/reference/interfaces/UseSelectorOptions.md b/docs/framework/solid/reference/interfaces/UseSelectorOptions.md new file mode 100644 index 00000000..2d3845b8 --- /dev/null +++ b/docs/framework/solid/reference/interfaces/UseSelectorOptions.md @@ -0,0 +1,38 @@ +--- +id: UseSelectorOptions +title: UseSelectorOptions +--- + +# Interface: UseSelectorOptions\ + +Defined in: [solid-store/src/useSelector.ts:4](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSelector.ts#L4) + +## Type Parameters + +### TSelected + +`TSelected` + +## Properties + +### compare()? + +```ts +optional compare: (a, b) => boolean; +``` + +Defined in: [solid-store/src/useSelector.ts:5](https://github.com/TanStack/store/blob/main/packages/solid-store/src/useSelector.ts#L5) + +#### Parameters + +##### a + +`TSelected` + +##### b + +`TSelected` + +#### Returns + +`boolean` diff --git a/docs/framework/svelte/quick-start.md b/docs/framework/svelte/quick-start.md index 3c126fb2..5fe7d502 100644 --- a/docs/framework/svelte/quick-start.md +++ b/docs/framework/svelte/quick-start.md @@ -45,17 +45,19 @@ export function updateState(animal: 'cats' | 'dogs') { **Display.svelte** ```html
{ animal }: { count.current }
``` +`useStore` remains available as a deprecated alias to `useSelector`. + **Increment.svelte** ```html @@ -58,6 +58,8 @@ const count = useStore(store, (state) => state[props.animal]); ``` +`useStore` remains available as a deprecated alias to `useSelector`. + **Increment.vue** ```vue + + diff --git a/examples/preact/atoms/package.json b/examples/preact/atoms/package.json new file mode 100644 index 00000000..16e05d54 --- /dev/null +++ b/examples/preact/atoms/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/atoms/src/index.tsx b/examples/preact/atoms/src/index.tsx new file mode 100644 index 00000000..bd29194d --- /dev/null +++ b/examples/preact/atoms/src/index.tsx @@ -0,0 +1,62 @@ +import { render } from 'preact' +import { + createAtom, + useAtom, + // useCreateAtom, + useValue, +} from '@tanstack/preact-store' + +// Optionally, you can create atoms outside of Preact components at module scope +const countAtom = createAtom(0) + +function App() { + // or define atoms inside of components with hook variant. You would have to pass atom as props or use store context though. + // const countAtom = useCreateAtom(0) + + return ( +
+

Preact Atom Hooks

+

+ This example creates a module-level atom and reads and updates it with + the Preact hooks. +

+ + + +
+ ) +} + +function AtomValuePanel() { + const count = useValue(countAtom) // useValue re-renders when the value changes. Useful for read-only access to an atom. + + return

Total: {count}

+} + +function AtomButtons() { + return ( +
+ + +
+ ) +} + +function AtomStepper() { + const [count, setCount] = useAtom(countAtom) // read and write access to the atom. Re-renders when the value changes. + + return ( +
+

Editable count: {count}

+ +
+ ) +} + +render(, document.getElementById('app')!) diff --git a/examples/preact/atoms/tsconfig.json b/examples/preact/atoms/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/atoms/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/atoms/vite.config.ts b/examples/preact/atoms/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/atoms/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/preact/simple/README.md b/examples/preact/simple/README.md index a9d90bf0..a29e7193 100644 --- a/examples/preact/simple/README.md +++ b/examples/preact/simple/README.md @@ -1,15 +1,6 @@ -# `create-preact` +# Preact Simple Example -

- -

+To run this example: -

Get started using Preact and Vite!

- -## Getting Started - -- `pnpm dev` - Starts a dev server at http://localhost:5173/ - -- `pnpm build` - Builds for production, emitting to `dist/` - -- `pnpm preview` - Starts a server at http://localhost:4173/ to test production build locally +- `npm install` +- `npm run dev` diff --git a/examples/preact/simple/package.json b/examples/preact/simple/package.json index bf268228..3debbb1c 100644 --- a/examples/preact/simple/package.json +++ b/examples/preact/simple/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3002", + "dev": "vite --port=3050", "build": "vite build", "preview": "vite preview" }, @@ -13,7 +13,7 @@ }, "devDependencies": { "@preact/preset-vite": "^2.10.5", - "@types/node": "^25.5.2", + "@types/node": "^25.6.0", "eslint": "^10.2.0", "eslint-config-preact": "^2.0.0", "typescript": "6.0.2", diff --git a/examples/preact/simple/src/index.tsx b/examples/preact/simple/src/index.tsx index ffc07839..aa927e96 100644 --- a/examples/preact/simple/src/index.tsx +++ b/examples/preact/simple/src/index.tsx @@ -1,21 +1,53 @@ import { render } from 'preact' -import { Store, useStore } from '@tanstack/preact-store' +import { createStore, useSelector } from '@tanstack/preact-store' -export const store = new Store({ - count: 0, +// You can instantiate a Store outside of Preact components too! +export const store = createStore({ + dogs: 0, + cats: 0, }) -function Counter() { - const count = useStore(store, (state) => state.count) +interface DisplayProps { + animal: 'dogs' | 'cats' +} + +// This will only re-render when `state[animal]` changes. If an unrelated store property changes, it won't re-render +function Display({ animal }: DisplayProps) { + const count = useSelector(store, (state) => state[animal]) // formerly, useStore. Now renamed to useSelector. + return
{`${animal}: ${count}`}
+} + +const updateState = (animal: 'dogs' | 'cats') => { + store.setState((state) => { + return { + ...state, + [animal]: state[animal] + 1, + } + }) +} + +interface IncrementProps { + animal: 'dogs' | 'cats' +} + +const Increment = ({ animal }: IncrementProps) => ( + +) + +function App() { return (
-
Count: {count}
- +

How many of your friends like cats or dogs?

+

+ Press one of the buttons to add a counter of how many of your friends + like cats or dogs +

+ + + +
) } -const root = document.body -render(, root) +render(, document.getElementById('app')!) diff --git a/examples/preact/simple/tsconfig.json b/examples/preact/simple/tsconfig.json index b8f664db..bd8198e7 100644 --- a/examples/preact/simple/tsconfig.json +++ b/examples/preact/simple/tsconfig.json @@ -13,5 +13,6 @@ "jsxImportSource": "preact", "skipLibCheck": true }, - "include": ["node_modules/vite/client.d.ts", "**/*"] + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] } diff --git a/examples/preact/store-actions/README.md b/examples/preact/store-actions/README.md new file mode 100644 index 00000000..d019ddb4 --- /dev/null +++ b/examples/preact/store-actions/README.md @@ -0,0 +1,12 @@ +# Preact Store Actions Example + +This example demonstrates: + +- `useSelector` +- `_useStore` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/preact/store-actions/index.html b/examples/preact/store-actions/index.html new file mode 100644 index 00000000..b9bd38f8 --- /dev/null +++ b/examples/preact/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Preact Store Actions Example App + + +
+ + + diff --git a/examples/preact/store-actions/package.json b/examples/preact/store-actions/package.json new file mode 100644 index 00000000..9c6b3bd1 --- /dev/null +++ b/examples/preact/store-actions/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-store-actions", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/store-actions/src/index.tsx b/examples/preact/store-actions/src/index.tsx new file mode 100644 index 00000000..ac8d5eb7 --- /dev/null +++ b/examples/preact/store-actions/src/index.tsx @@ -0,0 +1,84 @@ +import { render } from 'preact' +import { createStore, _useStore, useSelector } from '@tanstack/preact-store' + +// Optionally, you can create stores outside of Preact components at module scope +const petStore = createStore( + { + cats: 0, + dogs: 0, + }, + ({ setState, get }) => + // optionally, define actions for updating your store in specific ways right on the store. + ({ + addCat: () => + setState((prev) => ({ + ...prev, + cats: prev.cats + 1, + })), + addDog: () => + setState((prev) => ({ + ...prev, + dogs: prev.dogs + 1, + })), + log: () => console.log(get()), + }), +) + +function App() { + // or define stores inside of components with hook variant. You would have to pass store as props or use store context though. + // const petStore = useCreateStore(...) + + return ( +
+ +

Preact Store Actions

+

+ This example creates a module-level store with actions. Components read + state with useSelector and call mutations through{' '} + store.actions or the experimental _useStore{' '} + hook. +

+ + + +
+ ) +} + +function CatVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [cats, { addCat }] = _useStore(petStore, (state) => state.cats) + + return ( +
+

Cats: {cats}

+ +
+ ) +} + +function DogVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [dogs, { addDog }] = _useStore(petStore, (state) => state.dogs) + + return ( +
+

Dogs: {dogs}

+ +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +render(, document.getElementById('app')!) diff --git a/examples/preact/store-actions/tsconfig.json b/examples/preact/store-actions/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/store-actions/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/store-actions/vite.config.ts b/examples/preact/store-actions/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/store-actions/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/preact/store-context/README.md b/examples/preact/store-context/README.md new file mode 100644 index 00000000..c90e4891 --- /dev/null +++ b/examples/preact/store-context/README.md @@ -0,0 +1,17 @@ +# Preact Store Context Example + +This example demonstrates: + +- `createStoreContext` +- `useCreateStore` +- `useCreateAtom` +- `useStoreContext` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/preact/store-context/index.html b/examples/preact/store-context/index.html new file mode 100644 index 00000000..099280fe --- /dev/null +++ b/examples/preact/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Preact Store Context Example App + + +
+ + + diff --git a/examples/preact/store-context/package.json b/examples/preact/store-context/package.json new file mode 100644 index 00000000..aefc8aa3 --- /dev/null +++ b/examples/preact/store-context/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-store-context", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/store-context/src/index.tsx b/examples/preact/store-context/src/index.tsx new file mode 100644 index 00000000..44f994c5 --- /dev/null +++ b/examples/preact/store-context/src/index.tsx @@ -0,0 +1,157 @@ +import { render } from 'preact' +import { + useAtom, + useCreateAtom, + createStoreContext, + useCreateStore, + useSelector, + useValue, +} from '@tanstack/preact-store' +import type { Atom, Store } from '@tanstack/preact-store' + +// one drawback of storing stores and atoms in context is you have to define types for the context manually, instead of everything being inferred. + +type CounterStore = { + cats: number + dogs: number +} + +type StoreContextValue = { + votesStore: Store + countAtom: Atom +} + +// create context provider and hook +const { StoreProvider, useStoreContext } = + createStoreContext() + +// top-level app component with provider +function App() { + // create the store + const votesStore = useCreateStore({ + cats: 0, + dogs: 0, + }) + // create the atom + const countAtom = useCreateAtom(0) + + return ( + // provide both the store and atom in a single context object + +
+

Preact Store Context

+

+ This example provides both atoms and stores through a single typed + context object, then consumes them from nested components. +

+ + + + +
+

Nested Atom Components

+ + + +
+
+
+ ) +} + +function CatCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.cats) + + return

Cats: {value}

+} + +function DogCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.dogs) + + return

Dogs: {value}

+} + +function TotalCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // custom selector to calculate total votes from the store state + const total = useSelector(votesStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +function AtomSummary() { + // pull an atom from context + const { countAtom } = useStoreContext() + const count = useValue(countAtom) + + return

Atom count: {count}

+} + +function NestedAtomControls() { + const { countAtom } = useStoreContext() + + return ( +
+ + +
+ ) +} + +function DeepAtomEditor() { + const { countAtom } = useStoreContext() + const [count, setCount] = useAtom(countAtom) + + return ( +
+

Editable atom count: {count}

+ +
+ ) +} + +function StoreButtons() { + const { votesStore } = useStoreContext() + + return ( +
+ + +
+ ) +} + +render(, document.getElementById('app')!) diff --git a/examples/preact/store-context/tsconfig.json b/examples/preact/store-context/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/store-context/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/store-context/vite.config.ts b/examples/preact/store-context/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/store-context/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/preact/stores/README.md b/examples/preact/stores/README.md new file mode 100644 index 00000000..c4e36d67 --- /dev/null +++ b/examples/preact/stores/README.md @@ -0,0 +1,12 @@ +# Preact Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/preact/stores/index.html b/examples/preact/stores/index.html new file mode 100644 index 00000000..0a729bff --- /dev/null +++ b/examples/preact/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Preact Stores Example App + + +
+ + + diff --git a/examples/preact/stores/package.json b/examples/preact/stores/package.json new file mode 100644 index 00000000..efedc217 --- /dev/null +++ b/examples/preact/stores/package.json @@ -0,0 +1,26 @@ +{ + "name": "@tanstack/store-example-preact-stores", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/preact-store": "^0.12.0", + "preact": "^10.29.1" + }, + "devDependencies": { + "@preact/preset-vite": "^2.10.5", + "@types/node": "^25.6.0", + "eslint": "^10.2.0", + "eslint-config-preact": "^2.0.0", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "eslintConfig": { + "extends": "preact" + } +} diff --git a/examples/preact/stores/src/index.tsx b/examples/preact/stores/src/index.tsx new file mode 100644 index 00000000..1e65553b --- /dev/null +++ b/examples/preact/stores/src/index.tsx @@ -0,0 +1,79 @@ +import { render } from 'preact' +import { createStore, useSelector } from '@tanstack/preact-store' + +// Optionally, you can create stores outside of Preact components at module scope +const petStore = createStore({ + cats: 0, + dogs: 0, +}) + +function App() { + // or define stores inside of components with hook variant. You would have to pass store as props or use store context though. + // const petStore = useCreateStore(...) + + return ( +
+

Preact Store Hooks

+

+ This example creates a module-level store. Components read state with + `useSelector` and update it directly with `store.setState`. +

+ + + + +
+ ) +} + +function CatsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.cats) + + return

Cats: {value}

+} + +function DogsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.dogs) + + return

Dogs: {value}

+} + +function StoreButtons() { + return ( +
+ + +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +render(, document.getElementById('app')!) diff --git a/examples/preact/stores/tsconfig.json b/examples/preact/stores/tsconfig.json new file mode 100644 index 00000000..e71f296c --- /dev/null +++ b/examples/preact/stores/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "noEmit": true, + "allowJs": true, + "checkJs": true, + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "skipLibCheck": true + }, + "include": ["node_modules/vite/client.d.ts", "src", "vite.config.ts"], + "exclude": ["dist"] +} diff --git a/examples/preact/stores/vite.config.ts b/examples/preact/stores/vite.config.ts new file mode 100644 index 00000000..b9d4ccf9 --- /dev/null +++ b/examples/preact/stores/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import preact from '@preact/preset-vite' + +export default defineConfig({ + plugins: [preact()], + optimizeDeps: { + exclude: ['@tanstack/preact-store'], + }, +}) diff --git a/examples/react/atoms/README.md b/examples/react/atoms/README.md new file mode 100644 index 00000000..0b6300ef --- /dev/null +++ b/examples/react/atoms/README.md @@ -0,0 +1,13 @@ +# React Atom Hooks Example + +This example demonstrates: + +- `useCreateAtom` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/atoms/index.html b/examples/react/atoms/index.html new file mode 100644 index 00000000..211b6974 --- /dev/null +++ b/examples/react/atoms/index.html @@ -0,0 +1,13 @@ + + + + + + React Atom Hooks Example + + +
+ + + + diff --git a/examples/react/atoms/package.json b/examples/react/atoms/package.json new file mode 100644 index 00000000..ec1d69a2 --- /dev/null +++ b/examples/react/atoms/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-react-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-store": "^0.10.0", + "react": "^19.2.5", + "react-dom": "^19.2.5" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" + } +} diff --git a/examples/react/atoms/src/index.tsx b/examples/react/atoms/src/index.tsx new file mode 100644 index 00000000..e1c83deb --- /dev/null +++ b/examples/react/atoms/src/index.tsx @@ -0,0 +1,63 @@ +import ReactDOM from 'react-dom/client' +import { + createAtom, + useAtom, + // useCreateAtom, + useValue, +} from '@tanstack/react-store' + +// Optionally, you can create atoms outside of React components at module scope +const countAtom = createAtom(0) + +function App() { + // or define atoms inside of components with hook variant. You would have to pass atom as props or use store context though. + // const countAtom = useCreateAtom(0) + + return ( +
+

React Atom Hooks

+

+ This example creates a module-level atom and reads and updates it with + the React hooks. +

+ + + +
+ ) +} + +function AtomValuePanel() { + const count = useValue(countAtom) // useValue re-renders when the value changes. Useful for read-only access to an atom. + + return

Total: {count}

+} + +function AtomButtons() { + return ( +
+ + +
+ ) +} + +function AtomStepper() { + const [count, setCount] = useAtom(countAtom) // read and write access to the atom. Re-renders when the value changes. + + return ( +
+

Editable count: {count}

+ +
+ ) +} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render() diff --git a/examples/react/atoms/tsconfig.json b/examples/react/atoms/tsconfig.json new file mode 100644 index 00000000..15be0f95 --- /dev/null +++ b/examples/react/atoms/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "paths": { + "@tanstack/store": ["../../../packages/store/src/index.ts"], + "@tanstack/react-store": ["../../../packages/react-store/src/index.ts"] + }, + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/react/simple/index.html b/examples/react/simple/index.html index 53fc4b04..fcae0145 100644 --- a/examples/react/simple/index.html +++ b/examples/react/simple/index.html @@ -11,6 +11,7 @@
+ diff --git a/examples/react/simple/package.json b/examples/react/simple/package.json index 02f15808..f8402b87 100644 --- a/examples/react/simple/package.json +++ b/examples/react/simple/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vite --port=3001", + "dev": "vite --port=3050", "build": "vite build", "preview": "vite preview", "test:types": "tsc" @@ -17,7 +17,6 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", - "react-scan": "^0.5.3", "vite": "^8.0.8" }, "browserslist": { diff --git a/examples/react/simple/src/index.tsx b/examples/react/simple/src/index.tsx index b63e48eb..2b75babf 100644 --- a/examples/react/simple/src/index.tsx +++ b/examples/react/simple/src/index.tsx @@ -1,9 +1,8 @@ -import { scan } from 'react-scan' // dev-tools for demo import ReactDOM from 'react-dom/client' -import { Store, useStore } from '@tanstack/react-store' +import { createStore, useSelector } from '@tanstack/react-store' // You can use instantiate a Store outside of React components too! -export const store = new Store({ +export const store = createStore({ dogs: 0, cats: 0, }) @@ -14,7 +13,7 @@ interface DisplayProps { // This will only re-render when `state[animal]` changes. If an unrelated store property changes, it won't re-render const Display = ({ animal }: DisplayProps) => { - const count = useStore(store, (state) => state[animal]) + const count = useSelector(store, (state) => state[animal]) // formerly, useStore. Now renamed to useSelector. return
{`${animal}: ${count}`}
} @@ -53,5 +52,3 @@ function App() { const root = ReactDOM.createRoot(document.getElementById('root')!) root.render() - -scan() diff --git a/examples/react/store-actions/README.md b/examples/react/store-actions/README.md new file mode 100644 index 00000000..b36726b9 --- /dev/null +++ b/examples/react/store-actions/README.md @@ -0,0 +1,12 @@ +# React Store Actions Example + +This example demonstrates: + +- `useSelector` +- `_useStore` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/store-actions/index.html b/examples/react/store-actions/index.html new file mode 100644 index 00000000..dbdef132 --- /dev/null +++ b/examples/react/store-actions/index.html @@ -0,0 +1,13 @@ + + + + + + React Store Actions Example + + +
+ + + + diff --git a/examples/react/store-actions/package.json b/examples/react/store-actions/package.json new file mode 100644 index 00000000..6d44044a --- /dev/null +++ b/examples/react/store-actions/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-react-store-actions", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-store": "^0.10.0", + "react": "^19.2.5", + "react-dom": "^19.2.5" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" + } +} diff --git a/examples/react/store-actions/src/index.tsx b/examples/react/store-actions/src/index.tsx new file mode 100644 index 00000000..a27dc597 --- /dev/null +++ b/examples/react/store-actions/src/index.tsx @@ -0,0 +1,83 @@ +import ReactDOM from 'react-dom/client' +import { createStore, _useStore, useSelector } from '@tanstack/react-store' + +// Optionally, you can create stores outside of React components at module scope +const petStore = createStore( + { + cats: 0, + dogs: 0, + }, + ({ setState, get }) => + // optionally, define actions for updating your store in specific ways right on the store. + ({ + addCat: () => + setState((prev) => ({ + ...prev, + cats: prev.cats + 1, + })), + addDog: () => + setState((prev) => ({ + ...prev, + dogs: prev.dogs + 1, + })), + log: () => console.log(get()), + }), +) + +function App() { + // or define stores inside of components with hook variant. You would have to pass store as props or use store context though. + // const petStore = useCreateStore(...) + + return ( +
+ +

React Store Actions

+

+ This example creates a module-level store with actions. Components read + state with useSelector and call mutations through{' '} + store.actions or the experimental _useStore{' '} + hook. +

+ + + +
+ ) +} + +function CatVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [cats, { addCat }] = _useStore(petStore, (state) => state.cats) + + return ( +
+

Cats: {cats}

+ +
+ ) +} + +function DogVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [dogs, { addDog }] = _useStore(petStore, (state) => state.dogs) + + return ( +
+

Dogs: {dogs}

+ +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render() diff --git a/examples/react/store-actions/tsconfig.json b/examples/react/store-actions/tsconfig.json new file mode 100644 index 00000000..15be0f95 --- /dev/null +++ b/examples/react/store-actions/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "paths": { + "@tanstack/store": ["../../../packages/store/src/index.ts"], + "@tanstack/react-store": ["../../../packages/react-store/src/index.ts"] + }, + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/react/store-context/README.md b/examples/react/store-context/README.md new file mode 100644 index 00000000..f9b63c12 --- /dev/null +++ b/examples/react/store-context/README.md @@ -0,0 +1,17 @@ +# React Store Context Example + +This example demonstrates: + +- `createStoreContext` +- `useCreateStore` +- `useCreateAtom` +- `useStoreContext` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/store-context/index.html b/examples/react/store-context/index.html new file mode 100644 index 00000000..2e583b0b --- /dev/null +++ b/examples/react/store-context/index.html @@ -0,0 +1,13 @@ + + + + + + React Store Context Example + + +
+ + + + diff --git a/examples/react/store-context/package.json b/examples/react/store-context/package.json new file mode 100644 index 00000000..0d54fcba --- /dev/null +++ b/examples/react/store-context/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-react-store-context", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-store": "^0.10.0", + "react": "^19.2.5", + "react-dom": "^19.2.5" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" + } +} diff --git a/examples/react/store-context/src/index.tsx b/examples/react/store-context/src/index.tsx new file mode 100644 index 00000000..2c2add5c --- /dev/null +++ b/examples/react/store-context/src/index.tsx @@ -0,0 +1,158 @@ +import ReactDOM from 'react-dom/client' +import { + useAtom, + useCreateAtom, + createStoreContext, + useCreateStore, + useSelector, + useValue, +} from '@tanstack/react-store' +import type { Atom, Store } from '@tanstack/react-store' + +// one drawback of storing stores and atoms in context is you have to define types for the context manually, instead of everything being inferred. + +type CounterStore = { + cats: number + dogs: number +} + +type StoreContextValue = { + votesStore: Store + countAtom: Atom +} + +// create context provider and hook +const { StoreProvider, useStoreContext } = + createStoreContext() + +// top-level app component with provider +function App() { + // create the store + const votesStore = useCreateStore({ + cats: 0, + dogs: 0, + }) + // create the atom + const countAtom = useCreateAtom(0) + + return ( + // provide both the store and atom in a single context object + +
+

React Store Context

+

+ This example provides both atoms and stores through a single typed + context object, then consumes them from nested components. +

+ + + + +
+

Nested Atom Components

+ + + +
+
+
+ ) +} + +function CatCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.cats) + + return

Cats: {value}

+} + +function DogCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.dogs) + + return

Dogs: {value}

+} + +function TotalCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // custom selector to calculate total votes from the store state + const total = useSelector(votesStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +function AtomSummary() { + // pull an atom from context + const { countAtom } = useStoreContext() + const count = useValue(countAtom) + + return

Atom count: {count}

+} + +function NestedAtomControls() { + const { countAtom } = useStoreContext() + + return ( +
+ + +
+ ) +} + +function DeepAtomEditor() { + const { countAtom } = useStoreContext() + const [count, setCount] = useAtom(countAtom) + + return ( +
+

Editable atom count: {count}

+ +
+ ) +} + +function StoreButtons() { + const { votesStore } = useStoreContext() + + return ( +
+ + +
+ ) +} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render() diff --git a/examples/react/store-context/src/vite-env.d.ts b/examples/react/store-context/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/react/store-context/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/react/store-context/tsconfig.json b/examples/react/store-context/tsconfig.json new file mode 100644 index 00000000..2988a352 --- /dev/null +++ b/examples/react/store-context/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "paths": { + "@tanstack/store": ["../../../packages/store/src/index.ts"], + "@tanstack/react-store": ["../../../packages/react-store/src/index.ts"] + }, + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + } +} diff --git a/examples/react/store-context/vite.config.ts b/examples/react/store-context/vite.config.ts new file mode 100644 index 00000000..9ffcc675 --- /dev/null +++ b/examples/react/store-context/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], +}) diff --git a/examples/react/stores/README.md b/examples/react/stores/README.md new file mode 100644 index 00000000..0be2ea68 --- /dev/null +++ b/examples/react/stores/README.md @@ -0,0 +1,12 @@ +# React Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/react/stores/index.html b/examples/react/stores/index.html new file mode 100644 index 00000000..507a7cfd --- /dev/null +++ b/examples/react/stores/index.html @@ -0,0 +1,13 @@ + + + + + + React Store Hooks Example + + +
+ + + + diff --git a/examples/react/stores/package.json b/examples/react/stores/package.json new file mode 100644 index 00000000..b39e48c7 --- /dev/null +++ b/examples/react/stores/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-react-stores", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "test:types": "tsc" + }, + "dependencies": { + "@tanstack/react-store": "^0.10.0", + "react": "^19.2.5", + "react-dom": "^19.2.5" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" + } +} diff --git a/examples/react/stores/src/index.tsx b/examples/react/stores/src/index.tsx new file mode 100644 index 00000000..178f928f --- /dev/null +++ b/examples/react/stores/src/index.tsx @@ -0,0 +1,80 @@ +import ReactDOM from 'react-dom/client' +import { createStore, useSelector } from '@tanstack/react-store' + +// Optionally, you can create stores outside of React components at module scope +const petStore = createStore({ + cats: 0, + dogs: 0, +}) + +function App() { + // or define stores inside of components with hook variant. You would have to pass store as props or use store context though. + // const petStore = useCreateStore(...) + + return ( +
+

React Store Hooks

+

+ This example creates a module-level store. Components read state with + `useSelector` and update it directly with `store.setState`. +

+ + + + +
+ ) +} + +function CatsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.cats) + + return

Cats: {value}

+} + +function DogsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.dogs) + + return

Dogs: {value}

+} + +function StoreButtons() { + return ( +
+ + +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total}

+} + +const root = ReactDOM.createRoot(document.getElementById('root')!) +root.render() diff --git a/examples/react/stores/tsconfig.json b/examples/react/stores/tsconfig.json new file mode 100644 index 00000000..15be0f95 --- /dev/null +++ b/examples/react/stores/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "paths": { + "@tanstack/store": ["../../../packages/store/src/index.ts"], + "@tanstack/react-store": ["../../../packages/react-store/src/index.ts"] + }, + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/examples/solid/atoms/README.md b/examples/solid/atoms/README.md new file mode 100644 index 00000000..bbe7bfe8 --- /dev/null +++ b/examples/solid/atoms/README.md @@ -0,0 +1,12 @@ +# Solid Atom Hooks Example + +This example demonstrates: + +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/atoms/index.html b/examples/solid/atoms/index.html new file mode 100644 index 00000000..829c3d75 --- /dev/null +++ b/examples/solid/atoms/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Atoms Example App + + +
+ + + diff --git a/examples/solid/atoms/package.json b/examples/solid/atoms/package.json new file mode 100644 index 00000000..0059230f --- /dev/null +++ b/examples/solid/atoms/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/atoms/src/index.tsx b/examples/solid/atoms/src/index.tsx new file mode 100644 index 00000000..54da1f41 --- /dev/null +++ b/examples/solid/atoms/src/index.tsx @@ -0,0 +1,54 @@ +import { render } from 'solid-js/web' +import { createAtom, useAtom, useValue } from '@tanstack/solid-store' + +// Optionally, you can create atoms outside of Solid components at module scope +const countAtom = createAtom(0) + +function App() { + return ( +
+

Solid Atom Hooks

+

+ This example creates a module-level atom and reads and updates it with + the Solid hooks. +

+ + + +
+ ) +} + +function AtomValuePanel() { + const count = useValue(countAtom) // useValue re-renders when the value changes. Useful for read-only access to an atom. + + return

Total: {count()}

+} + +function AtomButtons() { + return ( +
+ + +
+ ) +} + +function AtomStepper() { + const [count, setCount] = useAtom(countAtom) // read and write access to the atom. + + return ( +
+

Editable count: {count()}

+ +
+ ) +} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/atoms/tsconfig.json b/examples/solid/atoms/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/atoms/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/atoms/vite.config.ts b/examples/solid/atoms/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/atoms/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/solid/simple/README.md b/examples/solid/simple/README.md index 99613fc0..f34d525e 100644 --- a/examples/solid/simple/README.md +++ b/examples/solid/simple/README.md @@ -1,28 +1,6 @@ -## Usage +# Solid Simple Example -```bash -$ npm install # or pnpm install or yarn install -``` +To run this example: -### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) - -## Available Scripts - -In the project directory, you can run: - -### `npm run dev` - -Runs the app in the development mode.
-Open [http://localhost:5173](http://localhost:5173) to view it in the browser. - -### `npm run build` - -Builds the app for production to the `dist` folder.
-It correctly bundles Solid in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -## Deployment - -Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html) +- `npm install` +- `npm run dev` diff --git a/examples/solid/simple/package.json b/examples/solid/simple/package.json index 280aea97..6d12e011 100644 --- a/examples/solid/simple/package.json +++ b/examples/solid/simple/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --port=3050", "build": "tsc && vite build", "test:types": "tsc", "preview": "vite preview" diff --git a/examples/solid/simple/src/index.tsx b/examples/solid/simple/src/index.tsx index c99f311b..c6274db5 100644 --- a/examples/solid/simple/src/index.tsx +++ b/examples/solid/simple/src/index.tsx @@ -1,8 +1,8 @@ -import { Store, useStore } from '@tanstack/solid-store' +import { createStore, useSelector } from '@tanstack/solid-store' import { render } from 'solid-js/web' // You can instantiate a Store outside of Solid components too! -export const store = new Store({ +export const store = createStore({ cats: 0, dogs: 0, }) @@ -12,7 +12,7 @@ interface DisplayProps { } export const Display = (props: DisplayProps) => { - const count = useStore(store, (state) => state[props.animals]) + const count = useSelector(store, (state) => state[props.animals]) // formerly, useStore. Now renamed to useSelector. return (
{props.animals}: {count()} diff --git a/examples/solid/store-actions/README.md b/examples/solid/store-actions/README.md new file mode 100644 index 00000000..2b3d74e4 --- /dev/null +++ b/examples/solid/store-actions/README.md @@ -0,0 +1,12 @@ +# Solid Store Actions Example + +This example demonstrates: + +- `useSelector` +- `_useStore` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/store-actions/index.html b/examples/solid/store-actions/index.html new file mode 100644 index 00000000..6d560e19 --- /dev/null +++ b/examples/solid/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Store Actions Example App + + +
+ + + diff --git a/examples/solid/store-actions/package.json b/examples/solid/store-actions/package.json new file mode 100644 index 00000000..454334b3 --- /dev/null +++ b/examples/solid/store-actions/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-store-actions", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/store-actions/src/index.tsx b/examples/solid/store-actions/src/index.tsx new file mode 100644 index 00000000..2474fc7c --- /dev/null +++ b/examples/solid/store-actions/src/index.tsx @@ -0,0 +1,81 @@ +import { render } from 'solid-js/web' +import { createStore, _useStore, useSelector } from '@tanstack/solid-store' + +// Optionally, you can create stores outside of Solid components at module scope +const petStore = createStore( + { + cats: 0, + dogs: 0, + }, + ({ setState, get }) => + // optionally, define actions for updating your store in specific ways right on the store. + ({ + addCat: () => + setState((prev) => ({ + ...prev, + cats: prev.cats + 1, + })), + addDog: () => + setState((prev) => ({ + ...prev, + dogs: prev.dogs + 1, + })), + log: () => console.log(get()), + }), +) + +function App() { + return ( +
+ +

Solid Store Actions

+

+ This example creates a module-level store with actions. Components read + state with useSelector and call mutations through{' '} + store.actions or the experimental _useStore{' '} + hook. +

+ + + +
+ ) +} + +function CatVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [cats, { addCat }] = _useStore(petStore, (state) => state.cats) + + return ( +
+

Cats: {cats()}

+ +
+ ) +} + +function DogVoter() { + // _useStore gives both the selected state and actions in a single tuple + const [dogs, { addDog }] = _useStore(petStore, (state) => state.dogs) + + return ( +
+

Dogs: {dogs()}

+ +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total()}

+} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/store-actions/tsconfig.json b/examples/solid/store-actions/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/store-actions/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/store-actions/vite.config.ts b/examples/solid/store-actions/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/store-actions/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/solid/store-context/README.md b/examples/solid/store-context/README.md new file mode 100644 index 00000000..8ea54a87 --- /dev/null +++ b/examples/solid/store-context/README.md @@ -0,0 +1,14 @@ +# Solid Store Context Example + +This example demonstrates: + +- Solid `createContext` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/store-context/index.html b/examples/solid/store-context/index.html new file mode 100644 index 00000000..8d3656ea --- /dev/null +++ b/examples/solid/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Store Context Example App + + +
+ + + diff --git a/examples/solid/store-context/package.json b/examples/solid/store-context/package.json new file mode 100644 index 00000000..6475f866 --- /dev/null +++ b/examples/solid/store-context/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-store-context", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/store-context/src/index.tsx b/examples/solid/store-context/src/index.tsx new file mode 100644 index 00000000..73a1075c --- /dev/null +++ b/examples/solid/store-context/src/index.tsx @@ -0,0 +1,162 @@ +import { createContext, useContext } from 'solid-js' +import { render } from 'solid-js/web' +import { + createAtom, + createStore, + useAtom, + useSelector, + useValue, +} from '@tanstack/solid-store' +import type { Atom, Store } from '@tanstack/solid-store' + +// one drawback of storing stores and atoms in context is you have to define types for the context manually, instead of everything being inferred. + +type CounterStore = { + cats: number + dogs: number +} + +type StoreContextValue = { + votesStore: Store + countAtom: Atom +} + +const StoreContext = createContext() + +function useStoreContext() { + const value = useContext(StoreContext) + + if (!value) { + throw new Error('Missing StoreProvider for StoreContext') + } + + return value +} + +function App() { + // Solid components only run once per mount, so stores and atoms created here stay stable for this provider instance. + const votesStore = createStore({ + cats: 0, + dogs: 0, + }) + const countAtom = createAtom(0) + + return ( + +
+

Solid Store Context

+

+ This example provides both atoms and stores through a single typed + context object, then consumes them from nested components. +

+ + + + +
+

Nested Atom Components

+ + + +
+
+
+ ) +} + +function CatCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.cats) + + return

Cats: {value()}

+} + +function DogCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // select a value from the store with useSelector + const value = useSelector(votesStore, (state) => state.dogs) + + return

Dogs: {value()}

+} + +function TotalCard() { + // pull a store from context + const { votesStore } = useStoreContext() + // custom selector to calculate total votes from the store state + const total = useSelector(votesStore, (state) => state.cats + state.dogs) + + return

Total votes: {total()}

+} + +function AtomSummary() { + // pull an atom from context + const { countAtom } = useStoreContext() + const count = useValue(countAtom) + + return

Atom count: {count()}

+} + +function NestedAtomControls() { + const { countAtom } = useStoreContext() + + return ( +
+ + +
+ ) +} + +function DeepAtomEditor() { + const { countAtom } = useStoreContext() + const [count, setCount] = useAtom(countAtom) + + return ( +
+

Editable atom count: {count()}

+ +
+ ) +} + +function StoreButtons() { + const { votesStore } = useStoreContext() + + return ( +
+ + +
+ ) +} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/store-context/tsconfig.json b/examples/solid/store-context/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/store-context/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/store-context/vite.config.ts b/examples/solid/store-context/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/store-context/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/solid/stores/README.md b/examples/solid/stores/README.md new file mode 100644 index 00000000..623b48f3 --- /dev/null +++ b/examples/solid/stores/README.md @@ -0,0 +1,12 @@ +# Solid Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/solid/stores/index.html b/examples/solid/stores/index.html new file mode 100644 index 00000000..5eff5b4f --- /dev/null +++ b/examples/solid/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Solid Stores Example App + + +
+ + + diff --git a/examples/solid/stores/package.json b/examples/solid/stores/package.json new file mode 100644 index 00000000..cc97b8d8 --- /dev/null +++ b/examples/solid/stores/package.json @@ -0,0 +1,20 @@ +{ + "name": "@tanstack/store-example-solid-stores", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "tsc && vite build", + "test:types": "tsc", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/solid-store": "^0.10.0", + "solid-js": "^1.9.12" + }, + "devDependencies": { + "typescript": "6.0.2", + "vite": "^8.0.8", + "vite-plugin-solid": "^2.11.12" + } +} diff --git a/examples/solid/stores/src/index.tsx b/examples/solid/stores/src/index.tsx new file mode 100644 index 00000000..80ab43b4 --- /dev/null +++ b/examples/solid/stores/src/index.tsx @@ -0,0 +1,76 @@ +import { render } from 'solid-js/web' +import { createStore, useSelector } from '@tanstack/solid-store' + +// Optionally, you can create stores outside of Solid components at module scope +const petStore = createStore({ + cats: 0, + dogs: 0, +}) + +function App() { + return ( +
+

Solid Store Hooks

+

+ This example creates a module-level store. Components read state with + `useSelector` and update it directly with `store.setState`. +

+ + + + +
+ ) +} + +function CatsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.cats) + + return

Cats: {value()}

+} + +function DogsCard() { + // read state slice (only re-renders when the selected value changes) + const value = useSelector(petStore, (state) => state.dogs) + + return

Dogs: {value()}

+} + +function StoreButtons() { + return ( +
+ + +
+ ) +} + +function TotalCard() { + const total = useSelector(petStore, (state) => state.cats + state.dogs) + + return

Total votes: {total()}

+} + +render(() => , document.getElementById('root')!) diff --git a/examples/solid/stores/tsconfig.json b/examples/solid/stores/tsconfig.json new file mode 100644 index 00000000..5470f3ca --- /dev/null +++ b/examples/solid/stores/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "vite.config.ts"] +} diff --git a/examples/solid/stores/vite.config.ts b/examples/solid/stores/vite.config.ts new file mode 100644 index 00000000..4095d9be --- /dev/null +++ b/examples/solid/stores/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +}) diff --git a/examples/svelte/atoms/README.md b/examples/svelte/atoms/README.md new file mode 100644 index 00000000..8c30f684 --- /dev/null +++ b/examples/svelte/atoms/README.md @@ -0,0 +1,12 @@ +# Svelte Atom Hooks Example + +This example demonstrates: + +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/atoms/index.html b/examples/svelte/atoms/index.html new file mode 100644 index 00000000..7e5b4461 --- /dev/null +++ b/examples/svelte/atoms/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Atoms Example App + + +
+ + + diff --git a/examples/svelte/atoms/package.json b/examples/svelte/atoms/package.json new file mode 100644 index 00000000..1000e895 --- /dev/null +++ b/examples/svelte/atoms/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-atoms", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/atoms/src/App.svelte b/examples/svelte/atoms/src/App.svelte new file mode 100644 index 00000000..bcc00d96 --- /dev/null +++ b/examples/svelte/atoms/src/App.svelte @@ -0,0 +1,30 @@ + + +
+

Svelte Atom Hooks

+

+ This example creates a module-level atom and reads and updates it with the + Svelte hooks. +

+

Total: {count.current}

+
+ + +
+
+

Editable count: {editableCount.current}

+ +
+
diff --git a/examples/svelte/atoms/src/main.ts b/examples/svelte/atoms/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/atoms/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/atoms/src/vite-env.d.ts b/examples/svelte/atoms/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/atoms/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/atoms/svelte.config.js b/examples/svelte/atoms/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/atoms/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/atoms/tsconfig.json b/examples/svelte/atoms/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/atoms/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/atoms/tsconfig.node.json b/examples/svelte/atoms/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/atoms/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/atoms/vite.config.ts b/examples/svelte/atoms/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/atoms/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/simple/README.md b/examples/svelte/simple/README.md index e6cd94fc..b4e8e337 100644 --- a/examples/svelte/simple/README.md +++ b/examples/svelte/simple/README.md @@ -1,47 +1,6 @@ -# Svelte + TS + Vite +# Svelte Simple Example -This template should help get you started developing with Svelte and TypeScript in Vite. +To run this example: -## Recommended IDE Setup - -[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). - -## Need an official Svelte framework? - -Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. - -## Technical considerations - -**Why use this over SvelteKit?** - -- It brings its own routing solution which might not be preferable for some users. -- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. - -This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. - -Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. - -**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** - -Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. - -**Why include `.vscode/extensions.json`?** - -Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. - -**Why enable `allowJs` in the TS template?** - -While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. - -**Why is HMR not preserving my local component state?** - -HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). - -If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. - -```ts -// store.ts -// An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) -``` +- `npm install` +- `npm run dev` diff --git a/examples/svelte/simple/package.json b/examples/svelte/simple/package.json index 81e2bedd..84ee12f4 100644 --- a/examples/svelte/simple/package.json +++ b/examples/svelte/simple/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --port=3050", "build": "vite build", "preview": "vite preview", "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" @@ -12,7 +12,7 @@ "devDependencies": { "@sveltejs/vite-plugin-svelte": "^7.0.0", "@tsconfig/svelte": "^5.0.8", - "svelte": "^5.55.2", + "svelte": "^5.55.3", "svelte-check": "^4.4.6", "tslib": "^2.8.1", "typescript": "6.0.2", diff --git a/examples/svelte/simple/src/Display.svelte b/examples/svelte/simple/src/Display.svelte index b364c925..5d1a48aa 100644 --- a/examples/svelte/simple/src/Display.svelte +++ b/examples/svelte/simple/src/Display.svelte @@ -1,9 +1,9 @@ diff --git a/examples/svelte/simple/src/store.ts b/examples/svelte/simple/src/store.ts index 3889d180..9131ecfd 100644 --- a/examples/svelte/simple/src/store.ts +++ b/examples/svelte/simple/src/store.ts @@ -1,7 +1,7 @@ -import { Store } from '@tanstack/svelte-store' +import { createStore } from '@tanstack/svelte-store' // You can instantiate a Store outside of Svelte files too! -export const store = new Store({ +export const store = createStore({ dogs: 0, cats: 0, }) diff --git a/examples/svelte/store-actions/README.md b/examples/svelte/store-actions/README.md new file mode 100644 index 00000000..9b0ca088 --- /dev/null +++ b/examples/svelte/store-actions/README.md @@ -0,0 +1,12 @@ +# Svelte Store Actions Example + +This example demonstrates: + +- `useSelector` +- `_useStore` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/store-actions/index.html b/examples/svelte/store-actions/index.html new file mode 100644 index 00000000..f9a099cd --- /dev/null +++ b/examples/svelte/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Store Actions Example App + + +
+ + + diff --git a/examples/svelte/store-actions/package.json b/examples/svelte/store-actions/package.json new file mode 100644 index 00000000..9bf1ffbc --- /dev/null +++ b/examples/svelte/store-actions/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-store-actions", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/store-actions/src/App.svelte b/examples/svelte/store-actions/src/App.svelte new file mode 100644 index 00000000..f4759365 --- /dev/null +++ b/examples/svelte/store-actions/src/App.svelte @@ -0,0 +1,51 @@ + + +
+ +

Svelte Store Actions

+

+ This example creates a module-level store with actions. Components read + state with useSelector and call mutations through + store.actions or the experimental _useStore + hook. +

+
+

Cats: {cats.current}

+ +
+
+

Dogs: {dogs.current}

+ +
+

Total votes: {total.current}

+
diff --git a/examples/svelte/store-actions/src/main.ts b/examples/svelte/store-actions/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/store-actions/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/store-actions/src/vite-env.d.ts b/examples/svelte/store-actions/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/store-actions/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/store-actions/svelte.config.js b/examples/svelte/store-actions/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/store-actions/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/store-actions/tsconfig.json b/examples/svelte/store-actions/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/store-actions/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/store-actions/tsconfig.node.json b/examples/svelte/store-actions/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/store-actions/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/store-actions/vite.config.ts b/examples/svelte/store-actions/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/store-actions/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/store-context/README.md b/examples/svelte/store-context/README.md new file mode 100644 index 00000000..e43aec87 --- /dev/null +++ b/examples/svelte/store-context/README.md @@ -0,0 +1,14 @@ +# Svelte Store Context Example + +This example demonstrates: + +- Svelte `setContext`/`getContext` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/store-context/index.html b/examples/svelte/store-context/index.html new file mode 100644 index 00000000..01da3cc3 --- /dev/null +++ b/examples/svelte/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Store Context Example App + + +
+ + + diff --git a/examples/svelte/store-context/package.json b/examples/svelte/store-context/package.json new file mode 100644 index 00000000..e5e50572 --- /dev/null +++ b/examples/svelte/store-context/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-store-context", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/store-context/src/App.svelte b/examples/svelte/store-context/src/App.svelte new file mode 100644 index 00000000..eb3c7a19 --- /dev/null +++ b/examples/svelte/store-context/src/App.svelte @@ -0,0 +1,29 @@ + + +
+

Svelte Store Context

+

+ This example provides both atoms and stores through a single typed context + object, then consumes them from nested components. +

+ + +
diff --git a/examples/svelte/store-context/src/AtomSection.svelte b/examples/svelte/store-context/src/AtomSection.svelte new file mode 100644 index 00000000..847eb952 --- /dev/null +++ b/examples/svelte/store-context/src/AtomSection.svelte @@ -0,0 +1,28 @@ + + +
+

Nested Atom Components

+

Atom count: {count.current}

+
+ + +
+
+

Editable atom count: {editableCount.current}

+ +
+
diff --git a/examples/svelte/store-context/src/StoreSection.svelte b/examples/svelte/store-context/src/StoreSection.svelte new file mode 100644 index 00000000..9e86e01c --- /dev/null +++ b/examples/svelte/store-context/src/StoreSection.svelte @@ -0,0 +1,38 @@ + + +

Cats: {cats.current}

+

Dogs: {dogs.current}

+

Total votes: {total.current}

+
+ + +
diff --git a/examples/svelte/store-context/src/context.ts b/examples/svelte/store-context/src/context.ts new file mode 100644 index 00000000..0d15dab0 --- /dev/null +++ b/examples/svelte/store-context/src/context.ts @@ -0,0 +1,13 @@ +import type { Atom, Store } from '@tanstack/svelte-store' + +export type CounterStore = { + cats: number + dogs: number +} + +export type StoreContextValue = { + votesStore: Store + countAtom: Atom +} + +export const STORE_CONTEXT = Symbol('store-context') diff --git a/examples/svelte/store-context/src/main.ts b/examples/svelte/store-context/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/store-context/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/store-context/src/vite-env.d.ts b/examples/svelte/store-context/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/store-context/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/store-context/svelte.config.js b/examples/svelte/store-context/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/store-context/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/store-context/tsconfig.json b/examples/svelte/store-context/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/store-context/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/store-context/tsconfig.node.json b/examples/svelte/store-context/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/store-context/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/store-context/vite.config.ts b/examples/svelte/store-context/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/store-context/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/svelte/stores/README.md b/examples/svelte/stores/README.md new file mode 100644 index 00000000..5a2eb4b0 --- /dev/null +++ b/examples/svelte/stores/README.md @@ -0,0 +1,12 @@ +# Svelte Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/svelte/stores/index.html b/examples/svelte/stores/index.html new file mode 100644 index 00000000..1e30df07 --- /dev/null +++ b/examples/svelte/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Svelte Stores Example App + + +
+ + + diff --git a/examples/svelte/stores/package.json b/examples/svelte/stores/package.json new file mode 100644 index 00000000..bda27b0f --- /dev/null +++ b/examples/svelte/stores/package.json @@ -0,0 +1,24 @@ +{ + "name": "@tanstack/store-example-svelte-stores", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tsconfig/svelte": "^5.0.8", + "svelte": "^5.55.3", + "svelte-check": "^4.4.6", + "tslib": "^2.8.1", + "typescript": "6.0.2", + "vite": "^8.0.8" + }, + "dependencies": { + "@tanstack/svelte-store": "^0.11.0" + } +} diff --git a/examples/svelte/stores/src/App.svelte b/examples/svelte/stores/src/App.svelte new file mode 100644 index 00000000..2b73a550 --- /dev/null +++ b/examples/svelte/stores/src/App.svelte @@ -0,0 +1,44 @@ + + +
+

Svelte Store Hooks

+

+ This example creates a module-level store. Components read state with + `useSelector` and update it directly with `store.setState`. +

+

Cats: {cats.current}

+

Dogs: {dogs.current}

+

Total votes: {total.current}

+
+ + +
+
diff --git a/examples/svelte/stores/src/main.ts b/examples/svelte/stores/src/main.ts new file mode 100644 index 00000000..928b6c52 --- /dev/null +++ b/examples/svelte/stores/src/main.ts @@ -0,0 +1,8 @@ +import { mount } from 'svelte' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/examples/svelte/stores/src/vite-env.d.ts b/examples/svelte/stores/src/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/examples/svelte/stores/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/svelte/stores/svelte.config.js b/examples/svelte/stores/svelte.config.js new file mode 100644 index 00000000..8abe4369 --- /dev/null +++ b/examples/svelte/stores/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess(), +} diff --git a/examples/svelte/stores/tsconfig.json b/examples/svelte/stores/tsconfig.json new file mode 100644 index 00000000..d9867cfa --- /dev/null +++ b/examples/svelte/stores/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/examples/svelte/stores/tsconfig.node.json b/examples/svelte/stores/tsconfig.node.json new file mode 100644 index 00000000..408b6903 --- /dev/null +++ b/examples/svelte/stores/tsconfig.node.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/svelte/stores/vite.config.ts b/examples/svelte/stores/vite.config.ts new file mode 100644 index 00000000..951a9ba4 --- /dev/null +++ b/examples/svelte/stores/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/examples/vue/atoms/README.md b/examples/vue/atoms/README.md new file mode 100644 index 00000000..522cac99 --- /dev/null +++ b/examples/vue/atoms/README.md @@ -0,0 +1,12 @@ +# Vue Atom Hooks Example + +This example demonstrates: + +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/atoms/index.html b/examples/vue/atoms/index.html new file mode 100644 index 00000000..d6750447 --- /dev/null +++ b/examples/vue/atoms/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Atoms Example App + + +
+ + + diff --git a/examples/vue/atoms/package.json b/examples/vue/atoms/package.json new file mode 100644 index 00000000..02b792ee --- /dev/null +++ b/examples/vue/atoms/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-atoms", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/atoms/src/App.vue b/examples/vue/atoms/src/App.vue new file mode 100644 index 00000000..ffbbfed2 --- /dev/null +++ b/examples/vue/atoms/src/App.vue @@ -0,0 +1,35 @@ + + + diff --git a/examples/vue/atoms/src/main.ts b/examples/vue/atoms/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/atoms/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/atoms/src/shims-vue.d.ts b/examples/vue/atoms/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/atoms/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/atoms/tsconfig.json b/examples/vue/atoms/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/atoms/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/atoms/vite.config.ts b/examples/vue/atoms/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/atoms/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/examples/vue/simple/README.md b/examples/vue/simple/README.md index 28462a4a..4143f8f7 100644 --- a/examples/vue/simple/README.md +++ b/examples/vue/simple/README.md @@ -1,6 +1,6 @@ -# Basic example +# Vue Simple Example To run this example: -- `npm install` or `yarn` or `pnpm i` -- `npm run dev` or `yarn dev` or `pnpm dev` +- `npm install` +- `npm run dev` diff --git a/examples/vue/simple/package.json b/examples/vue/simple/package.json index 58cfdce2..31d8d158 100644 --- a/examples/vue/simple/package.json +++ b/examples/vue/simple/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --port=3050", "build": "vite build", "build:dev": "vite build -m development", "test:types": "vue-tsc", diff --git a/examples/vue/simple/src/Display.vue b/examples/vue/simple/src/Display.vue index ac211d10..9f95603e 100644 --- a/examples/vue/simple/src/Display.vue +++ b/examples/vue/simple/src/Display.vue @@ -1,9 +1,9 @@ diff --git a/examples/vue/simple/src/store.ts b/examples/vue/simple/src/store.ts index 73ac2621..b48c3006 100644 --- a/examples/vue/simple/src/store.ts +++ b/examples/vue/simple/src/store.ts @@ -1,7 +1,7 @@ -import { Store } from '@tanstack/vue-store' +import { createStore } from '@tanstack/vue-store' // You can instantiate a Store outside of Vue components too! -export const store = new Store({ +export const store = createStore({ dogs: 0, cats: 0, }) diff --git a/examples/vue/store-actions/README.md b/examples/vue/store-actions/README.md new file mode 100644 index 00000000..48758d4e --- /dev/null +++ b/examples/vue/store-actions/README.md @@ -0,0 +1,12 @@ +# Vue Store Actions Example + +This example demonstrates: + +- `useSelector` +- `_useStore` +- module-level `Store` actions + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/store-actions/index.html b/examples/vue/store-actions/index.html new file mode 100644 index 00000000..5bc92e3a --- /dev/null +++ b/examples/vue/store-actions/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Store Actions Example App + + +
+ + + diff --git a/examples/vue/store-actions/package.json b/examples/vue/store-actions/package.json new file mode 100644 index 00000000..4448bace --- /dev/null +++ b/examples/vue/store-actions/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-store-actions", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/store-actions/src/App.vue b/examples/vue/store-actions/src/App.vue new file mode 100644 index 00000000..f08b8ac0 --- /dev/null +++ b/examples/vue/store-actions/src/App.vue @@ -0,0 +1,53 @@ + + + diff --git a/examples/vue/store-actions/src/main.ts b/examples/vue/store-actions/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/store-actions/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/store-actions/src/shims-vue.d.ts b/examples/vue/store-actions/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/store-actions/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/store-actions/tsconfig.json b/examples/vue/store-actions/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/store-actions/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/store-actions/vite.config.ts b/examples/vue/store-actions/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/store-actions/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/examples/vue/store-context/README.md b/examples/vue/store-context/README.md new file mode 100644 index 00000000..6d07f72d --- /dev/null +++ b/examples/vue/store-context/README.md @@ -0,0 +1,14 @@ +# Vue Store Context Example + +This example demonstrates: + +- Vue `provide`/`inject` +- `useSelector` +- `useValue` +- `useSetValue` +- `useAtom` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/store-context/index.html b/examples/vue/store-context/index.html new file mode 100644 index 00000000..74eaf634 --- /dev/null +++ b/examples/vue/store-context/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Store Context Example App + + +
+ + + diff --git a/examples/vue/store-context/package.json b/examples/vue/store-context/package.json new file mode 100644 index 00000000..03e1a0f9 --- /dev/null +++ b/examples/vue/store-context/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-store-context", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/store-context/src/App.vue b/examples/vue/store-context/src/App.vue new file mode 100644 index 00000000..793b010c --- /dev/null +++ b/examples/vue/store-context/src/App.vue @@ -0,0 +1,166 @@ + + + diff --git a/examples/vue/store-context/src/main.ts b/examples/vue/store-context/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/store-context/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/store-context/src/shims-vue.d.ts b/examples/vue/store-context/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/store-context/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/store-context/tsconfig.json b/examples/vue/store-context/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/store-context/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/store-context/vite.config.ts b/examples/vue/store-context/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/store-context/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/examples/vue/stores/README.md b/examples/vue/stores/README.md new file mode 100644 index 00000000..66ce8b1f --- /dev/null +++ b/examples/vue/stores/README.md @@ -0,0 +1,12 @@ +# Vue Store Hooks Example + +This example demonstrates: + +- `useSelector` +- `store.setState` +- module-level `Store` + +To run this example: + +- `npm install` +- `npm run dev` diff --git a/examples/vue/stores/index.html b/examples/vue/stores/index.html new file mode 100644 index 00000000..351679fc --- /dev/null +++ b/examples/vue/stores/index.html @@ -0,0 +1,12 @@ + + + + + + TanStack Store Vue Stores Example App + + +
+ + + diff --git a/examples/vue/stores/package.json b/examples/vue/stores/package.json new file mode 100644 index 00000000..1e814dc2 --- /dev/null +++ b/examples/vue/stores/package.json @@ -0,0 +1,22 @@ +{ + "name": "@tanstack/store-example-vue-stores", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3050", + "build": "vite build", + "build:dev": "vite build -m development", + "test:types": "vue-tsc", + "serve": "vite preview" + }, + "dependencies": { + "@tanstack/vue-store": "^0.10.0", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "typescript": "6.0.2", + "vite": "^8.0.8", + "vue-tsc": "^3.2.6" + } +} diff --git a/examples/vue/stores/src/App.vue b/examples/vue/stores/src/App.vue new file mode 100644 index 00000000..4df5917f --- /dev/null +++ b/examples/vue/stores/src/App.vue @@ -0,0 +1,46 @@ + + + diff --git a/examples/vue/stores/src/main.ts b/examples/vue/stores/src/main.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/examples/vue/stores/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/examples/vue/stores/src/shims-vue.d.ts b/examples/vue/stores/src/shims-vue.d.ts new file mode 100644 index 00000000..ac1ded79 --- /dev/null +++ b/examples/vue/stores/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/examples/vue/stores/tsconfig.json b/examples/vue/stores/tsconfig.json new file mode 100644 index 00000000..2dfc1e6b --- /dev/null +++ b/examples/vue/stores/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"] +} diff --git a/examples/vue/stores/vite.config.ts b/examples/vue/stores/vite.config.ts new file mode 100644 index 00000000..c40aa3c3 --- /dev/null +++ b/examples/vue/stores/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) diff --git a/package.json b/package.json index 14b04b95..950291ce 100644 --- a/package.json +++ b/package.json @@ -46,16 +46,16 @@ "@tanstack/typedoc-config": "0.3.3", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", - "@types/node": "^25.5.2", + "@types/node": "^25.6.0", "@vitest/coverage-istanbul": "^4.1.4", "eslint": "^10.2.0", "eslint-plugin-react-hooks": "^7.0.1", "jsdom": "^29.0.2", - "knip": "^6.3.1", + "knip": "^6.4.0", "markdown-link-extractor": "^4.0.3", - "nx": "22.6.4", + "nx": "22.6.5", "premove": "^4.0.0", - "prettier": "^3.8.1", + "prettier": "^3.8.2", "prettier-plugin-svelte": "^3.5.1", "publint": "^0.3.18", "sherif": "^1.11.1", diff --git a/packages/angular-store/package.json b/packages/angular-store/package.json index c9c34019..1911b3cf 100644 --- a/packages/angular-store/package.json +++ b/packages/angular-store/package.json @@ -30,19 +30,13 @@ "build": "tsdown --tsconfig tsconfig.build.json" }, "type": "module", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.cts", "main": "./dist/index.cjs", "module": "./dist/index.js", "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "import": "./dist/index.js", + "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, @@ -55,7 +49,7 @@ "@tanstack/store": "workspace:*" }, "devDependencies": { - "@analogjs/vite-plugin-angular": "^2.4.3", + "@analogjs/vite-plugin-angular": "^2.4.5", "@angular/common": "^21.2.8", "@angular/compiler": "^21.2.8", "@angular/core": "^21.2.8", diff --git a/packages/angular-store/src/_injectStore.ts b/packages/angular-store/src/_injectStore.ts new file mode 100644 index 00000000..2a6f9714 --- /dev/null +++ b/packages/angular-store/src/_injectStore.ts @@ -0,0 +1,41 @@ +import { injectSelector } from './injectSelector' +import type { Signal } from '@angular/core' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { InjectSelectorOptions } from './injectSelector' + +/** + * Experimental combined read+write injection function for stores, mirroring + * injectAtom's pattern. + * + * Returns `[signal, actions]` when the store has an actions factory, or + * `[signal, setState]` for plain stores. + * + * @example + * ```ts + * // Store with actions + * readonly result = _injectStore(petStore, (s) => s.cats) + * // result[0] is Signal, result[1] is actions + * + * // Store without actions + * readonly result = _injectStore(plainStore, (s) => s) + * // result[0] is Signal, result[1] is setState + * ``` + */ +export function _injectStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: InjectSelectorOptions, +): [ + Signal, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = injectSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/angular-store/src/createStoreContext.ts b/packages/angular-store/src/createStoreContext.ts new file mode 100644 index 00000000..e724713c --- /dev/null +++ b/packages/angular-store/src/createStoreContext.ts @@ -0,0 +1,71 @@ +import { InjectionToken, inject } from '@angular/core' +import type { Provider } from '@angular/core' + +/** + * Creates a typed Angular dependency-injection context for sharing a bundle of + * atoms and stores with a component subtree. + * + * The returned `provideStoreContext` function accepts a factory that creates the + * context value. Using a factory (rather than a static value) ensures each + * component instance — and each SSR request — receives its own state, avoiding + * cross-request pollution. + * + * Consumers call `injectStoreContext()` inside an injection context (typically a + * constructor or field initializer) to retrieve the contextual atoms and stores, + * then compose them with existing hooks like {@link injectSelector}, + * {@link injectValue}, and {@link injectAtom}. + * + * @example + * ```ts + * const { provideStoreContext, injectStoreContext } = createStoreContext<{ + * countAtom: Atom + * totalsStore: Store<{ count: number }> + * }>() + * + * // Parent component provides the context + * @Component({ + * providers: [ + * provideStoreContext(() => ({ + * countAtom: createAtom(0), + * totalsStore: new Store({ count: 0 }), + * })), + * ], + * template: ``, + * }) + * class ParentComponent {} + * + * // Child component consumes the context + * @Component({ template: `{{ count() }}` }) + * class ChildComponent { + * private ctx = injectStoreContext() + * count = injectValue(this.ctx.countAtom) + * } + * ``` + * + * @throws When `injectStoreContext()` is called without a matching + * `provideStoreContext()` in a parent component's providers. + */ +export function createStoreContext(): { + provideStoreContext: (factory: () => TValue) => Provider + injectStoreContext: () => TValue +} { + const token = new InjectionToken('StoreContext') + + function provideStoreContext(factory: () => TValue): Provider { + return { provide: token, useFactory: factory } + } + + function injectStoreContext(): TValue { + const value = inject(token, { optional: true }) + + if (value === null) { + throw new Error( + "Missing StoreContext provider. Add provideStoreContext() to a parent component's providers array.", + ) + } + + return value + } + + return { provideStoreContext, injectStoreContext } +} diff --git a/packages/angular-store/src/index.ts b/packages/angular-store/src/index.ts index 746bfed2..0164d1cd 100644 --- a/packages/angular-store/src/index.ts +++ b/packages/angular-store/src/index.ts @@ -1,108 +1,10 @@ -import { - DestroyRef, - Injector, - assertInInjectionContext, - inject, - linkedSignal, - runInInjectionContext, -} from '@angular/core' -import type { Atom, ReadonlyAtom } from '@tanstack/store' -import type { CreateSignalOptions, Signal } from '@angular/core' - -type StoreContext = Record - export * from '@tanstack/store' -export function injectStore>( - store: Atom, - selector?: (state: NoInfer) => TSelected, - options?: CreateSignalOptions & { injector?: Injector }, -): Signal -export function injectStore>( - store: Atom | ReadonlyAtom, - selector?: (state: NoInfer) => TSelected, - options?: CreateSignalOptions & { injector?: Injector }, -): Signal -export function injectStore< - TState extends StoreContext, - TSelected = NoInfer, ->( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => - d as unknown as TSelected, - options: CreateSignalOptions & { injector?: Injector } = { - equal: shallow, - }, -): Signal { - !options.injector && assertInInjectionContext(injectStore) - - if (!options.injector) { - options.injector = inject(Injector) - } - - return runInInjectionContext(options.injector, () => { - const destroyRef = inject(DestroyRef) - const slice = linkedSignal(() => selector(store.get()), options) - - const { unsubscribe } = store.subscribe((s) => { - slice.set(selector(s)) - }) - - destroyRef.onDestroy(() => { - unsubscribe() - }) - - return slice.asReadonly() - }) -} - -function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } +export * from './injectSelector' +export * from './injectValue' - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } +export * from './injectAtom' +export * from './injectStore' // @deprecated in favor of injectSelector +export * from './_injectStore' - for (const key of keysA) { - if ( - !Object.prototype.hasOwnProperty.call(objB, key) || - !Object.is(objA[key as keyof T], objB[key as keyof T]) - ) { - return false - } - } - return true -} +export * from './createStoreContext' diff --git a/packages/angular-store/src/injectAtom.ts b/packages/angular-store/src/injectAtom.ts new file mode 100644 index 00000000..ef28257e --- /dev/null +++ b/packages/angular-store/src/injectAtom.ts @@ -0,0 +1,52 @@ +import { injectValue } from './injectValue' +import type { Atom } from '@tanstack/store' +import type { InjectSelectorOptions } from './injectSelector' + +/** + * A callable signal that reads the current atom value when invoked and + * exposes a `.set` method matching the atom's native setter contract. + * + * This is the Angular-idiomatic return type for {@link injectAtom}. It can + * be used as a class property and called directly in templates. + * + * @example + * ```ts + * readonly count = injectAtom(countAtom) + * + * // read in template: {{ count() }} + * // write in class: this.count.set(5) + * // this.count.set(prev => prev + 1) + * ``` + */ +export interface WritableAtomSignal { + /** Read the current value. */ + (): T + /** Set the atom value (accepts a direct value or an updater function). */ + set: Atom['set'] +} + +/** + * Returns a {@link WritableAtomSignal} that reads the current atom value when + * called and exposes a `.set` method for updates. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * readonly count = injectAtom(countAtom) + * + * increment() { + * this.count.set((prev) => prev + 1) + * } + * ``` + */ +export function injectAtom( + atom: Atom, + options?: InjectSelectorOptions, +): WritableAtomSignal { + const value = injectValue(atom, options) + const atomSignal = (() => value()) as WritableAtomSignal + atomSignal.set = atom.set + return atomSignal +} diff --git a/packages/angular-store/src/injectSelector.ts b/packages/angular-store/src/injectSelector.ts new file mode 100644 index 00000000..d9b0e249 --- /dev/null +++ b/packages/angular-store/src/injectSelector.ts @@ -0,0 +1,100 @@ +import { + DestroyRef, + Injector, + assertInInjectionContext, + inject, + linkedSignal, + runInInjectionContext, +} from '@angular/core' +import type { CreateSignalOptions, Signal } from '@angular/core' + +export interface InjectSelectorOptions extends Omit< + CreateSignalOptions, + 'equal' +> { + compare?: (a: TSelected, b: TSelected) => boolean + injector?: Injector +} + +export type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +function resolveInjector( + fn: (...args: Array) => unknown, + injector?: Injector, +) { + if (!injector) { + assertInInjectionContext(fn) + return inject(Injector) + } + + return injector +} + +function createReadonlySelectionSignal( + source: SelectionSource, + selector: (state: NoInfer) => TSelected, + options?: InjectSelectorOptions, +): Signal { + const injector = resolveInjector( + createReadonlySelectionSignal, + options?.injector, + ) + + return runInInjectionContext(injector, () => { + const destroyRef = inject(DestroyRef) + const compare = options?.compare ?? defaultCompare + const { + injector: _injector, + compare: _compare, + ...signalOptions + } = options ?? {} + const slice = linkedSignal(() => selector(source.get()), { + ...signalOptions, + equal: compare, + }) + + const { unsubscribe } = source.subscribe((state) => { + slice.set(selector(state)) + }) + + destroyRef.onDestroy(() => { + unsubscribe() + }) + + return slice.asReadonly() + }) +} + +/** + * Selects a slice of state from an atom or store and returns it as an Angular + * signal. + * + * This is the primary Angular read hook for TanStack Store. + * + * @example + * ```ts + * readonly count = injectSelector(counterStore, (state) => state.count) + * ``` + * + * @example + * ```ts + * readonly doubled = injectSelector(countAtom, (value) => value * 2) + * ``` + */ +export function injectSelector>( + source: SelectionSource, + selector: (state: NoInfer) => TSelected = (d) => + d as unknown as TSelected, + options?: InjectSelectorOptions, +): Signal { + return createReadonlySelectionSignal(source, selector, options) +} diff --git a/packages/angular-store/src/injectStore.ts b/packages/angular-store/src/injectStore.ts new file mode 100644 index 00000000..360492d1 --- /dev/null +++ b/packages/angular-store/src/injectStore.ts @@ -0,0 +1,38 @@ +import { injectSelector } from './injectSelector' +import type { CreateSignalOptions, Injector, Signal } from '@angular/core' +import type { SelectionSource } from './injectSelector' + +type CompatibilityInjectStoreOptions = + CreateSignalOptions & { + injector?: Injector + } + +/** + * Deprecated alias for {@link injectSelector}. + * + * @example + * ```ts + * readonly count = injectStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `injectSelector` instead. + */ +export function injectStore>( + store: SelectionSource, + selector?: (state: NoInfer) => TSelected, + options?: CompatibilityInjectStoreOptions, +): Signal +export function injectStore>( + store: SelectionSource, + selector: (state: NoInfer) => TSelected = (d) => + d as unknown as TSelected, + options?: CompatibilityInjectStoreOptions, +): Signal { + const { equal, injector, ...signalOptions } = options ?? {} + + return injectSelector(store, selector, { + ...signalOptions, + compare: equal, + injector, + }) +} diff --git a/packages/angular-store/src/injectValue.ts b/packages/angular-store/src/injectValue.ts new file mode 100644 index 00000000..ea20b78c --- /dev/null +++ b/packages/angular-store/src/injectValue.ts @@ -0,0 +1,30 @@ +import { injectSelector } from './injectSelector' +import type { Signal } from '@angular/core' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { InjectSelectorOptions } from './injectSelector' + +/** + * Returns the current value signal for an atom or store. + * + * This is the whole-value counterpart to {@link injectSelector}. + * + * @example + * ```ts + * readonly count = injectValue(countAtom) + * ``` + * + * @example + * ```ts + * readonly state = injectValue(counterStore) + * ``` + */ +export function injectValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: InjectSelectorOptions, +): Signal { + return injectSelector(source, (value) => value, options) +} diff --git a/packages/angular-store/tests/index.test.ts b/packages/angular-store/tests/index.test.ts index 88da8df7..b25558db 100644 --- a/packages/angular-store/tests/index.test.ts +++ b/packages/angular-store/tests/index.test.ts @@ -2,11 +2,124 @@ import { describe, expect, test } from 'vitest' import { Component, effect } from '@angular/core' import { TestBed } from '@angular/core/testing' import { By } from '@angular/platform-browser' -import { createStore } from '@tanstack/store' -import { injectStore } from '../src/index' +import { Store, createAtom, createStore } from '@tanstack/store' +import { + _injectStore, + createStoreContext, + injectAtom, + injectSelector, + injectStore, + injectValue, +} from '../src/index' +import type { Atom } from '@tanstack/store' -describe('injectStore', () => { - test(`allows us to select state using a selector`, () => { +describe('atom hooks', () => { + test('injectValue reads mutable atom state and rerenders when updated', () => { + const atom = createAtom(0) + + @Component({ + template: ` +
+

Value: {{ value() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + value = injectValue(atom) + + update() { + atom.set((prev) => prev + 1) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 0') + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 1') + }) + + test('injectAtom returns a callable signal with a set method', () => { + const atom = createAtom(0) + + @Component({ + template: ` +
+

Value: {{ count() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + count = injectAtom(atom) + + add() { + this.count.set((prev) => prev + 5) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 0') + + fixture.debugElement + .query(By.css('button#add')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 5') + }) + + test('injectAtom set accepts a direct value', () => { + const atom = createAtom(0) + + @Component({ + template: ` +
+

Value: {{ count() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + count = injectAtom(atom) + + constructor() { + this.count.set(42) + } + + reset() { + this.count.set(0) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 42') + + fixture.debugElement + .query(By.css('button#reset')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Value: 0') + }) +}) + +describe('selector hooks', () => { + test('allows us to select state using a selector', () => { const store = createStore({ select: 0, ignored: 1 }) @Component({ @@ -14,14 +127,61 @@ describe('injectStore', () => { standalone: true, }) class MyCmp { - storeVal = injectStore(store, (state) => state.select) + storeVal = injectSelector(store, (state) => state.select) + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Store: 0') + }) + + test('injectValue reads writable and readonly store state', () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + + @Component({ + template: ` +
+

{{ value() }}

+

{{ readonlyValue().value }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + value = injectValue(baseStore) + readonlyValue = injectValue(readonlyStore) + + update() { + baseStore.setState((prev) => prev + 1) + } } const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() - const element = fixture.nativeElement - expect(element.textContent).toContain('Store: 0') + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('1') + expect( + fixture.debugElement.query(By.css('p#readonly')).nativeElement + .textContent, + ).toContain('2') + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('2') + expect( + fixture.debugElement.query(By.css('p#readonly')).nativeElement + .textContent, + ).toContain('4') }) test('only triggers a re-render when selector state is updated', () => { @@ -43,11 +203,11 @@ describe('injectStore', () => { standalone: true, }) class MyCmp { - storeVal = injectStore(store, (state) => state.select) + storeVal = injectSelector(store, (state) => state.select) constructor() { effect(() => { - console.log(this.storeVal()) + this.storeVal() count++ }) } @@ -70,27 +230,159 @@ describe('injectStore', () => { const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() - const element = fixture.nativeElement - const debugElement = fixture.debugElement - - expect(element.textContent).toContain('Store: 0') + expect(fixture.nativeElement.textContent).toContain('Store: 0') expect(count).toEqual(1) - debugElement + fixture.debugElement .query(By.css('button#updateSelect')) .triggerEventHandler('click', null) + fixture.detectChanges() + expect(fixture.nativeElement.textContent).toContain('Store: 10') + expect(count).toEqual(2) + fixture.debugElement + .query(By.css('button#updateIgnored')) + .triggerEventHandler('click', null) fixture.detectChanges() - expect(element.textContent).toContain('Store: 10') + expect(fixture.nativeElement.textContent).toContain('Store: 10') expect(count).toEqual(2) + }) + + test('injectSelector allows specifying a custom equality function', () => { + const store = createStore({ + array: [ + { select: 0, ignore: 1 }, + { select: 0, ignore: 1 }, + ], + }) + let count = 0 + + @Component({ + template: ` +
+

{{ sum() }}

+ + +
+ `, + standalone: true, + }) + class MyCmp { + sum = injectSelector( + store, + (state) => + state.array + .map(({ ignore, ...rest }) => rest) + .reduce((total, item) => total + item.select, 0), + { + compare: (prev, next) => prev === next, + }, + ) + + constructor() { + effect(() => { + this.sum() + count++ + }) + } + + updateSelect() { + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + select: item.select + 5, + })), + })) + } + + updateIgnored() { + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + ignore: item.ignore + 1, + })), + })) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('0') + expect(count).toBe(1) - debugElement + fixture.debugElement .query(By.css('button#updateIgnored')) .triggerEventHandler('click', null) + fixture.detectChanges() + expect(count).toBe(1) + fixture.debugElement + .query(By.css('button#updateSelect')) + .triggerEventHandler('click', null) fixture.detectChanges() - expect(element.textContent).toContain('Store: 10') - expect(count).toEqual(2) + expect(fixture.nativeElement.textContent).toContain('10') + expect(count).toBe(2) + }) + + test('injectSelector works with mounted derived stores', () => { + const store = createStore(0) + const derived = createStore(() => ({ val: store.state * 2 })) + + @Component({ + template: ` +
+

{{ derivedVal() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + derivedVal = injectSelector(derived, (state) => state.val) + + update() { + store.setState((prev) => prev + 1) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#derived')).nativeElement.textContent, + ).toContain('0') + + fixture.debugElement + .query(By.css('button#update')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#derived')).nativeElement.textContent, + ).toContain('2') + }) +}) + +describe('injectStore', () => { + test('is a compatibility alias for injectSelector', () => { + const store = createStore({ select: 0 }) + + @Component({ + template: `

Store: {{ storeVal() }}

`, + standalone: true, + }) + class MyCmp { + storeVal = injectStore(store, (state) => state.select) + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect(fixture.nativeElement.textContent).toContain('Store: 0') }) }) @@ -108,13 +400,7 @@ describe('dataType', () => { standalone: true, }) class MyCmp { - storeVal = injectStore(store, (state) => state.date) - - constructor() { - effect(() => { - console.log(this.storeVal()) - }) - } + storeVal = injectSelector(store, (state) => state.date) updateDate() { store.setState((v) => ({ @@ -127,19 +413,188 @@ describe('dataType', () => { const fixture = TestBed.createComponent(MyCmp) fixture.detectChanges() - const debugElement = fixture.debugElement - expect( - debugElement.query(By.css('p#displayStoreVal')).nativeElement.textContent, + fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement + .textContent, ).toContain(new Date('2025-03-29T21:06:30.401Z')) - debugElement + fixture.debugElement .query(By.css('button#updateDate')) .triggerEventHandler('click', null) - fixture.detectChanges() expect( - debugElement.query(By.css('p#displayStoreVal')).nativeElement.textContent, + fixture.debugElement.query(By.css('p#displayStoreVal')).nativeElement + .textContent, ).toContain(new Date('2025-03-29T21:06:40.401Z')) }) }) + +describe('_injectStore', () => { + test('returns selected state and actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + @Component({ + template: ` +
+

{{ count() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + private result = _injectStore(store, (state) => state.count) + count = this.result[0] + actions = this.result[1] + + inc() { + this.actions.inc() + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('0') + + fixture.debugElement + .query(By.css('button#inc')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('1') + }) + + test('returns selected state and setState for plain stores', () => { + const store = createStore(0) + + @Component({ + template: ` +
+

{{ value() }}

+ +
+ `, + standalone: true, + }) + class MyCmp { + private result = _injectStore(store, (state) => state) + value = this.result[0] + setState = this.result[1] + + inc() { + this.setState((prev) => prev + 1) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('0') + + fixture.debugElement + .query(By.css('button#inc')) + .triggerEventHandler('click', null) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#value')).nativeElement.textContent, + ).toContain('1') + }) +}) + +describe('createStoreContext', () => { + test('provides and injects a typed store context', () => { + const { provideStoreContext, injectStoreContext } = createStoreContext<{ + countAtom: Atom + petStore: Store<{ cats: number; dogs: number }> + }>() + + @Component({ + template: ` +
+

{{ count() }}

+

{{ cats() }}

+ + +
+ `, + standalone: true, + providers: [ + provideStoreContext(() => ({ + countAtom: createAtom(10), + petStore: new Store({ cats: 2, dogs: 3 }), + })), + ], + }) + class MyCmp { + private ctx = injectStoreContext() + count = injectValue(this.ctx.countAtom) + cats = injectSelector(this.ctx.petStore, (s) => s.cats) + + inc() { + this.ctx.countAtom.set((prev) => prev + 1) + } + + addCat() { + this.ctx.petStore.setState((prev) => ({ + ...prev, + cats: prev.cats + 1, + })) + } + } + + const fixture = TestBed.createComponent(MyCmp) + fixture.detectChanges() + + expect( + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('10') + expect( + fixture.debugElement.query(By.css('p#cats')).nativeElement.textContent, + ).toContain('2') + + fixture.debugElement + .query(By.css('button#inc')) + .triggerEventHandler('click', null) + fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#count')).nativeElement.textContent, + ).toContain('11') + + fixture.debugElement + .query(By.css('button#addCat')) + .triggerEventHandler('click', null) + fixture.detectChanges() + expect( + fixture.debugElement.query(By.css('p#cats')).nativeElement.textContent, + ).toContain('3') + }) + + test('throws when injectStoreContext is called without a provider', () => { + const { injectStoreContext } = createStoreContext<{ + countAtom: Atom + }>() + + @Component({ + template: `

{{ count() }}

`, + standalone: true, + }) + class MyCmp { + private ctx = injectStoreContext() + count = injectValue(this.ctx.countAtom) + } + + expect(() => TestBed.createComponent(MyCmp)).toThrow( + /Missing StoreContext provider/, + ) + }) +}) diff --git a/packages/angular-store/tests/test.test-d.ts b/packages/angular-store/tests/test.test-d.ts index 430a75a7..8db1b503 100644 --- a/packages/angular-store/tests/test.test-d.ts +++ b/packages/angular-store/tests/test.test-d.ts @@ -1,16 +1,95 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { injectStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { + _injectStore, + createStoreContext, + injectAtom, + injectSelector, + injectStore, + injectValue, +} from '../src' import type { Signal } from '@angular/core' +import type { Atom, Store } from '@tanstack/store' +import type { WritableAtomSignal } from '../src' -test('injectStore works with derived state', () => { +test('injectSelector works with derived state', () => { const store = createStore(12) const derived = createStore(() => store.state * 2) - const val = injectStore(derived, (state) => { + const val = injectSelector(derived, (state) => { expectTypeOf(state).toEqualTypeOf() return state }) expectTypeOf(val).toEqualTypeOf>() }) + +test('injectValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(injectValue(writableAtom)).toEqualTypeOf>() + expectTypeOf(injectValue(readonlyAtom)).toEqualTypeOf>() + expectTypeOf(injectValue(writableStore)).toEqualTypeOf>() + expectTypeOf(injectValue(readonlyStore)).toEqualTypeOf>() +}) + +test('injectAtom returns a WritableAtomSignal', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const atomSignal = injectAtom(writableAtom) + + expectTypeOf(atomSignal).toEqualTypeOf>() + expectTypeOf(atomSignal()).toEqualTypeOf() + expectTypeOf(atomSignal.set).toEqualTypeOf['set']>() + + // @ts-expect-error readonly atoms cannot be used with injectAtom + injectAtom(readonlyAtom) +}) + +test('injectStore matches injectSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = injectSelector(store, (state) => state) + const compatValue = injectStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf>() + expectTypeOf(compatValue).toEqualTypeOf>() +}) + +test('createStoreContext preserves typed context shape', () => { + const { provideStoreContext, injectStoreContext } = createStoreContext<{ + countAtom: Atom + petStore: Store<{ cats: number }> + }>() + + expectTypeOf(provideStoreContext).toBeFunction() + expectTypeOf(injectStoreContext).toBeFunction() + + const ctx = injectStoreContext() + + expectTypeOf(ctx.countAtom).toEqualTypeOf>() + expectTypeOf(ctx.petStore).toEqualTypeOf>() +}) + +test('_injectStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const [selected, actions] = _injectStore(store, (state) => state.count) + + expectTypeOf(selected).toEqualTypeOf>() + expectTypeOf(actions.inc).toBeFunction() +}) + +test('_injectStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _injectStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf>() + expectTypeOf(setState).toEqualTypeOf['setState']>() +}) diff --git a/packages/preact-store/package.json b/packages/preact-store/package.json index 45d233fe..9f2ed474 100644 --- a/packages/preact-store/package.json +++ b/packages/preact-store/package.json @@ -33,19 +33,13 @@ "build": "tsdown --tsconfig tsconfig.build.json" }, "type": "module", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.cts", "main": "./dist/index.cjs", "module": "./dist/index.js", "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "import": "./dist/index.js", + "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, diff --git a/packages/preact-store/src/_useStore.ts b/packages/preact-store/src/_useStore.ts new file mode 100644 index 00000000..89be003a --- /dev/null +++ b/packages/preact-store/src/_useStore.ts @@ -0,0 +1,43 @@ +import { useMemo } from 'preact/hooks' +import { useSelector } from './useSelector' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' // eslint-disable-line no-duplicate-imports + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```tsx + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +/* eslint-disable react-hooks/rules-of-hooks -- experimental API with underscore prefix */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + TSelected, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = useMemo( + () => (store.actions as StoreActionMap | undefined) ?? store.setState, + [store], + ) + + return [selected, actionsOrSetState] as any +} diff --git a/packages/preact-store/src/createStoreContext.tsx b/packages/preact-store/src/createStoreContext.tsx new file mode 100644 index 00000000..097cfcc7 --- /dev/null +++ b/packages/preact-store/src/createStoreContext.tsx @@ -0,0 +1,72 @@ +// eslint-disable-next-line import/consistent-type-specifier-style +import { type ComponentChildren, createContext } from 'preact' +import { useContext } from 'preact/hooks' + +/** + * Creates a typed Preact context for sharing a bundle of atoms and stores with + * a subtree. + * + * The returned `StoreProvider` only transports the provided object through + * Preact context. Consumers destructure the contextual atoms and stores, then + * compose them with the existing hooks like {@link useSelector}, + * {@link useValue}, {@link useSetValue}, and {@link useAtom}. + * + * The object shape is preserved exactly, so keyed atoms and stores remain fully + * typed when read back with `useStoreContext()`. + * + * @example + * ```tsx + * const { StoreProvider, useStoreContext } = createStoreContext<{ + * countAtom: Atom + * totalsStore: Store<{ count: number }> + * }>() + * + * function CountButton() { + * const { countAtom, totalsStore } = useStoreContext() + * const count = useValue(countAtom) + * const total = useSelector(totalsStore, (state) => state.count) + * + * return ( + * + * ) + * } + * ``` + * + * @throws When `useStoreContext()` is called outside the matching `StoreProvider`. + */ +export function createStoreContext() { + const Context = createContext(null) + Context.displayName = 'StoreContext' + + function StoreProvider({ + children, + value, + }: { + children?: ComponentChildren + value: TValue + }) { + return {children} + } + + function useStoreContext() { + const value = useContext(Context) + + if (value === null) { + throw new Error('Missing StoreProvider for StoreContext') + } + + return value + } + + return { + StoreProvider, + useStoreContext, + } +} diff --git a/packages/preact-store/src/index.ts b/packages/preact-store/src/index.ts index c4572691..4bcfb415 100644 --- a/packages/preact-store/src/index.ts +++ b/packages/preact-store/src/index.ts @@ -1,171 +1,12 @@ -import { useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks' -import type { Atom, ReadonlyAtom } from '@tanstack/store' - export * from '@tanstack/store' -type InternalStore = { - _value: any - _getSnapshot: () => any -} - -type StoreRef = { - _instance: InternalStore -} - -/** - * This is taken from https://github.com/preactjs/preact/blob/main/compat/src/hooks.js#L8-L54 - * which is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 - * on a high level this cuts out the warnings, ... and attempts a smaller implementation. - * This way we don't have to import preact/compat with side effects - */ -function useSyncExternalStore( - subscribe: (onStoreChange: () => void) => () => void, - getSnapshot: () => any, -) { - const value = getSnapshot() - - const [{ _instance }, forceUpdate] = useState({ - _instance: { _value: value, _getSnapshot: getSnapshot }, - }) - - useLayoutEffect(() => { - _instance._value = value - _instance._getSnapshot = getSnapshot - - if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }) - } - }, [subscribe, value, getSnapshot]) - - useEffect(() => { - if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }) - } - - return subscribe(() => { - if (didSnapshotChange(_instance)) { - forceUpdate({ _instance }) - } - }) - }, [subscribe]) - - return value -} - -function didSnapshotChange(inst: { - _getSnapshot: () => any - _value: any -}): boolean { - const latestGetSnapshot = inst._getSnapshot - const prevValue = inst._value - try { - const nextValue = latestGetSnapshot() - return !Object.is(prevValue, nextValue) - // eslint-disable-next-line no-unused-vars - } catch (_error) { - return true - } -} - -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn -} - -function useSyncExternalStoreWithSelector( - subscribe: (onStoreChange: () => void) => () => void, - getSnapshot: () => TSnapshot, - selector: (snapshot: TSnapshot) => TSelected, - isEqual: (a: TSelected, b: TSelected) => boolean, -): TSelected { - const selectedSnapshotRef = useRef() - - const getSelectedSnapshot = () => { - const snapshot = getSnapshot() - const selected = selector(snapshot) - - if ( - selectedSnapshotRef.current === undefined || - !isEqual(selectedSnapshotRef.current, selected) - ) { - selectedSnapshotRef.current = selected - } - - return selectedSnapshotRef.current - } - - return useSyncExternalStore(subscribe, getSelectedSnapshot) -} - -export function useStore>( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, -): TSelected { - const equal = options.equal ?? shallow - const slice = useSyncExternalStoreWithSelector( - (onStoreChange) => store.subscribe(onStoreChange).unsubscribe, - () => store.get(), - selector, - equal, - ) - - return slice -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = getOwnKeys(objA) - if (keysA.length !== getOwnKeys(objB).length) { - return false - } +export * from './createStoreContext' +export * from './useCreateAtom' +export * from './useCreateStore' - for (const key of keysA) { - if ( - !Object.prototype.hasOwnProperty.call(objB, key as string) || - !Object.is(objA[key as keyof T], objB[key as keyof T]) - ) { - return false - } - } - return true -} +export * from './useValue' +export * from './useSelector' -function getOwnKeys(obj: object): Array { - return (Object.keys(obj) as Array).concat( - Object.getOwnPropertySymbols(obj), - ) -} +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector +export * from './_useStore' diff --git a/packages/preact-store/src/useAtom.ts b/packages/preact-store/src/useAtom.ts new file mode 100644 index 00000000..7492d000 --- /dev/null +++ b/packages/preact-store/src/useAtom.ts @@ -0,0 +1,29 @@ +import { useValue } from './useValue' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Returns the current atom value together with a stable setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```tsx + * const [count, setCount] = useAtom(countAtom) + * + * return ( + * + * ) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [TValue, Atom['set']] { + const value = useValue(atom, options) + + return [value, atom.set] +} diff --git a/packages/preact-store/src/useCreateAtom.ts b/packages/preact-store/src/useCreateAtom.ts new file mode 100644 index 00000000..08b3e2be --- /dev/null +++ b/packages/preact-store/src/useCreateAtom.ts @@ -0,0 +1,48 @@ +import { useState } from 'preact/hooks' +import { createAtom } from '@tanstack/store' +// eslint-disable-next-line no-duplicate-imports +import type { Atom, AtomOptions, ReadonlyAtom } from '@tanstack/store' + +/** + * Creates a stable atom instance for the lifetime of the component. + * + * Pass an initial value to create a writable atom, or a getter function to + * create a readonly derived atom. This mirrors {@link createAtom}, but only + * creates the atom once per component mount. + * + * @example + * ```tsx + * function Counter() { + * const countAtom = useCreateAtom(0) + * const [count, setCount] = useAtom(countAtom) + * + * return ( + * + * ) + * } + * ``` + */ +export function useCreateAtom( + getValue: (prev?: NoInfer) => T, + options?: AtomOptions, +): ReadonlyAtom +export function useCreateAtom( + initialValue: T, + options?: AtomOptions, +): Atom +export function useCreateAtom( + valueOrFn: T | ((prev?: T) => T), + options?: AtomOptions, +): Atom | ReadonlyAtom { + const [atom] = useState | ReadonlyAtom>(() => { + if (typeof valueOrFn === 'function') { + return createAtom(valueOrFn as (prev?: NoInfer) => T, options) + } + + return createAtom(valueOrFn, options) + }) + + return atom +} diff --git a/packages/preact-store/src/useCreateStore.ts b/packages/preact-store/src/useCreateStore.ts new file mode 100644 index 00000000..1f518238 --- /dev/null +++ b/packages/preact-store/src/useCreateStore.ts @@ -0,0 +1,65 @@ +import { useState } from 'preact/hooks' +import { createStore } from '@tanstack/store' +// eslint-disable-next-line no-duplicate-imports +import type { + ReadonlyStore, + Store, + StoreActionMap, + StoreActionsFactory, +} from '@tanstack/store' + +type NonFunction = T extends (...args: Array) => any ? never : T + +/** + * Creates a stable store instance for the lifetime of the component. + * + * Pass an initial value to create a writable store, or a getter function to + * create a readonly derived store. This mirrors {@link createStore}, but only + * creates the store once per component mount. + * + * @example + * ```tsx + * function Counter() { + * const counterStore = useCreateStore({ count: 0 }) + * const count = useSelector(counterStore, (state) => state.count) + * const setState = useSetValue(counterStore) + * + * return ( + * + * ) + * } + * ``` + */ +export function useCreateStore( + getValue: (prev?: NoInfer) => T, +): ReadonlyStore +export function useCreateStore(initialValue: T): Store +export function useCreateStore( + initialValue: NonFunction, + actions: StoreActionsFactory, +): Store +export function useCreateStore( + valueOrFn: T | ((prev?: T) => T), + actions?: StoreActionsFactory, +): Store | Store | ReadonlyStore { + const [store] = useState | Store | ReadonlyStore>( + () => { + if (typeof valueOrFn === 'function') { + return createStore(valueOrFn as (prev?: NoInfer) => T) + } + + if (actions) { + return createStore(valueOrFn as NonFunction, actions) + } + + return createStore(valueOrFn) + }, + ) + + return store +} diff --git a/packages/preact-store/src/useSelector.ts b/packages/preact-store/src/useSelector.ts new file mode 100644 index 00000000..fc833e25 --- /dev/null +++ b/packages/preact-store/src/useSelector.ts @@ -0,0 +1,150 @@ +import { + useCallback, + useEffect, + useLayoutEffect, + useRef, + useState, +} from 'preact/hooks' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +type InternalStore = { + _value: any + _getSnapshot: () => any +} + +type StoreRef = { + _instance: InternalStore +} + +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * This is taken from https://github.com/preactjs/preact/blob/main/compat/src/hooks.js#L8-L54 + * which is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 + * on a high level this cuts out the warnings, ... and attempts a smaller implementation. + * This way we don't have to import preact/compat with side effects + */ +function useSyncExternalStore( + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => any, +) { + const value = getSnapshot() + + const [{ _instance }, forceUpdate] = useState({ + _instance: { _value: value, _getSnapshot: getSnapshot }, + }) + + useLayoutEffect(() => { + _instance._value = value + _instance._getSnapshot = getSnapshot + + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }) + } + }, [_instance, subscribe, value, getSnapshot]) + + useEffect(() => { + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }) + } + + return subscribe(() => { + if (didSnapshotChange(_instance)) { + forceUpdate({ _instance }) + } + }) + }, [_instance, subscribe]) + + return value +} + +function didSnapshotChange(inst: InternalStore): boolean { + const latestGetSnapshot = inst._getSnapshot + const prevValue = inst._value + try { + const nextValue = latestGetSnapshot() + return !Object.is(prevValue, nextValue) + // eslint-disable-next-line no-unused-vars + } catch (_error) { + return true + } +} + +function useSyncExternalStoreWithSelector( + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => TSnapshot, + selector: (snapshot: TSnapshot) => TSelected, + compare: (a: TSelected, b: TSelected) => boolean, +): TSelected { + const selectedSnapshotRef = useRef() + + const getSelectedSnapshot = () => { + const snapshot = getSnapshot() + const selected = selector(snapshot) + + if ( + selectedSnapshotRef.current === undefined || + !compare(selectedSnapshotRef.current, selected) + ) { + selectedSnapshotRef.current = selected + } + + return selectedSnapshotRef.current + } + + return useSyncExternalStore(subscribe, getSelectedSnapshot) +} + +/** + * Selects a slice of state from an atom or store and subscribes the component + * to that selection. + * + * This is the primary Preact read hook for TanStack Store. Use it when a + * component only needs part of a source value. + * + * @example + * ```tsx + * const count = useSelector(counterStore, (state) => state.count) + * ``` + * + * @example + * ```tsx + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector( + source: SelectionSource, + selector: (snapshot: TSource) => TSelected, + options?: UseSelectorOptions, +): TSelected { + const compare = options?.compare ?? defaultCompare + + const subscribe = useCallback( + (handleStoreChange: () => void) => { + const { unsubscribe } = source.subscribe(handleStoreChange) + return unsubscribe + }, + [source], + ) + + const getSnapshot = useCallback(() => source.get(), [source]) + + return useSyncExternalStoreWithSelector( + subscribe, + getSnapshot, + selector, + compare, + ) +} diff --git a/packages/preact-store/src/useStore.ts b/packages/preact-store/src/useStore.ts new file mode 100644 index 00000000..b08021a1 --- /dev/null +++ b/packages/preact-store/src/useStore.ts @@ -0,0 +1,22 @@ +import { useSelector } from './useSelector' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```tsx + * const count = useStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = ( + source: { + get: () => TSource + subscribe: (listener: (value: TSource) => void) => { + unsubscribe: () => void + } + }, + selector: (snapshot: TSource) => TSelected, + compare?: (a: TSelected, b: TSelected) => boolean, +) => useSelector(source, selector, { compare }) diff --git a/packages/preact-store/src/useValue.ts b/packages/preact-store/src/useValue.ts new file mode 100644 index 00000000..a0e037e8 --- /dev/null +++ b/packages/preact-store/src/useValue.ts @@ -0,0 +1,31 @@ +import { useSelector } from './useSelector' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +// eslint-disable-next-line no-duplicate-imports +import type { UseSelectorOptions } from './useSelector' + +/** + * Subscribes to an atom or store and returns its current value. + * + * This is the whole-value counterpart to {@link useSelector}. Use it when the + * component needs the entire current value from a source. + * + * @example + * ```tsx + * const count = useValue(countAtom) + * ``` + * + * @example + * ```tsx + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): TValue { + return useSelector(source, (value) => value, options) +} diff --git a/packages/preact-store/tests/index.test.tsx b/packages/preact-store/tests/index.test.tsx index 8012ddf9..2f66e036 100644 --- a/packages/preact-store/tests/index.test.tsx +++ b/packages/preact-store/tests/index.test.tsx @@ -1,20 +1,388 @@ -import { describe, expect, it, test, vi } from 'vitest' -import { render, waitFor } from '@testing-library/preact' +import { act, render, renderHook, waitFor } from '@testing-library/preact' import { userEvent } from '@testing-library/user-event' -import { createStore } from '@tanstack/store' -import { shallow, useStore } from '../src/index' +import { describe, expect, it, test, vi } from 'vitest' +import { createAtom, createStore } from '@tanstack/store' +import { + _useStore, + createStoreContext, + shallow, + useAtom, + useCreateAtom, + useCreateStore, + useSelector, + useStore, + useValue, +} from '../src/index' const user = userEvent.setup() -describe('useStore', () => { - it('allows us to select state using a selector', () => { +describe('atom hooks', () => { + it('useCreateAtom creates a stable atom instance across rerenders', () => { + const { result, rerender } = renderHook(() => useCreateAtom(0)) + const atom = result.current + + act(() => { + atom.set(1) + }) + + rerender() + + expect(result.current).toBe(atom) + expect(result.current.get()).toBe(1) + }) + + it('useValue reads mutable atom state and rerenders when updated', async () => { + const atom = createAtom(0) + + function Comp() { + const value = useValue(atom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('useValue reads readonly atom state', async () => { + const countAtom = createAtom(1) + const doubledAtom = createAtom(() => countAtom.get() * 2) + + function Comp() { + const value = useValue(doubledAtom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 4')).toBeInTheDocument()) + }) + + it('useValue respects custom compare', async () => { + const atom = createAtom({ + select: 0, + ignored: 1, + }) + const renderSpy = vi.fn() + + function Comp() { + const value = useValue(atom, { + compare: (prev, next) => prev.select === next.select, + }) + renderSpy() + + return ( +
+

Renders: {renderSpy.mock.calls.length}

+

Value: {value.select}

+ + +
+ ) + } + + const { getByText } = render() + + expect(getByText('Renders: 1')).toBeInTheDocument() + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update ignored')) + expect(getByText('Renders: 1')).toBeInTheDocument() + + await user.click(getByText('Update select')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + expect(getByText('Renders: 2')).toBeInTheDocument() + }) + + it('useAtom returns the current value and setter', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useAtom(atom)) + + expect(result.current[0]).toBe(0) + + act(() => { + result.current[1]((prev) => prev + 5) + }) + + expect(result.current[0]).toBe(5) + }) +}) + +describe('store contexts', () => { + it('provides bundled writable atoms and stores', async () => { + const countAtom = createAtom(0) + const totalStore = createStore({ count: 0 }) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof countAtom + totalStore: typeof totalStore + }>() + + function Comp() { + const { countAtom: currentAtom, totalStore: currentStore } = + useStoreContext() + const value = useValue(currentAtom) + const total = useSelector(currentStore, (state) => state.count) + + return ( +
+

Value: {value}

+

Total: {total}

+ + +
+ ) + } + + const { getByText } = render( + + + , + ) + + expect(getByText('Value: 0')).toBeInTheDocument() + expect(getByText('Total: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + await user.click(getByText('Update total')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Total: 1')).toBeInTheDocument()) + }) + + it('supports readonly atoms and stores in the same context', async () => { + const baseAtom = createAtom(1) + const readonlyAtom = createAtom(() => baseAtom.get() * 2) + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + const { StoreProvider, useStoreContext } = createStoreContext<{ + readonlyAtom: typeof readonlyAtom + readonlyStore: typeof readonlyStore + }>() + + function Comp() { + const { readonlyAtom: currentAtom, readonlyStore: currentStore } = + useStoreContext() + const atomValue = useValue(currentAtom) + const storeValue = useSelector(currentStore, (state) => state.value) + + return ( +
+

Atom: {atomValue}

+

Store: {storeValue}

+
+ ) + } + + const { getByText } = render( + + + , + ) + + expect(getByText('Atom: 2')).toBeInTheDocument() + expect(getByText('Store: 2')).toBeInTheDocument() + + act(() => { + baseAtom.set((prev) => prev + 1) + baseStore.setState((prev) => prev + 1) + }) + + await waitFor(() => expect(getByText('Atom: 4')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Store: 4')).toBeInTheDocument()) + }) + + it('works with useAtom against contextual atoms', async () => { + const countAtom = createAtom(0) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof countAtom + }>() + + function Comp() { + const { countAtom: atom } = useStoreContext() + const [value, setValue] = useAtom(atom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render( + + + , + ) + + await user.click(getByText('Add 5')) + + await waitFor(() => expect(getByText('Value: 5')).toBeInTheDocument()) + }) + + it('throws a clear error when a store provider is missing', () => { + const { useStoreContext } = createStoreContext<{ countAtom: number }>() + + function Comp() { + useStoreContext() + return null + } + + expect(() => render()).toThrowError( + 'Missing StoreProvider for StoreContext', + ) + }) + + it('nested providers override parent values', async () => { + const outerAtom = createAtom(1) + const innerAtom = createAtom(5) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof outerAtom + }>() + + function Value() { + const { countAtom: atom } = useStoreContext() + const value = useValue(atom) + + return

Value: {value}

+ } + + const { getAllByText } = render( + + + + + + , + ) + + expect(getAllByText(/Value:/).map((node) => node.textContent)).toEqual([ + 'Value: 1', + 'Value: 5', + ]) + + act(() => { + innerAtom.set(7) + }) + + await waitFor(() => + expect(getAllByText(/Value:/).map((node) => node.textContent)).toEqual([ + 'Value: 1', + 'Value: 7', + ]), + ) + }) +}) + +describe('store hooks', () => { + it('useCreateStore creates a stable store instance across rerenders', () => { + const { result, rerender } = renderHook(() => useCreateStore(0)) + const store = result.current + + act(() => { + store.setState((prev) => prev + 1) + }) + + rerender() + + expect(result.current).toBe(store) + expect(result.current.state).toBe(1) + }) + + it('useCreateStore supports actions and keeps them stable', () => { + const { result, rerender } = renderHook(() => + useCreateStore({ count: 0 }, ({ get, setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })), + ) + const store = result.current + const actions = store.actions + + act(() => { + store.actions.inc() + }) + + rerender() + + expect(result.current).toBe(store) + expect(result.current.actions).toBe(actions) + expect(result.current.actions.current()).toBe(1) + }) + + it('useSelector allows us to select state using a selector', () => { const store = createStore({ select: 0, ignored: 1, }) function Comp() { - const storeVal = useStore(store, (state) => state.select) + const storeVal = useSelector(store, (state) => state.select) return

Store: {storeVal}

} @@ -23,8 +391,40 @@ describe('useStore', () => { expect(getByText('Store: 0')).toBeInTheDocument() }) - // This should ideally test the custom uSES hook - it('only triggers a re-render when selector state is updated', async () => { + it('useValue reads writable and readonly store state', async () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + + function Comp() { + const value = useValue(baseStore) + const readonlyValue = useValue(readonlyStore) + + return ( +
+

Value: {value}

+

Readonly: {readonlyValue.value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 1')).toBeInTheDocument() + expect(getByText('Readonly: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) + }) + + it('useSelector only triggers a re-render when selector state is updated', async () => { const store = createStore({ select: 0, ignored: 1, @@ -32,7 +432,7 @@ describe('useStore', () => { const renderSpy = vi.fn() function Comp() { - const storeVal = useStore(store, (state) => state.select) + const storeVal = useSelector(store, (state) => state.select) renderSpy() return ( @@ -78,7 +478,7 @@ describe('useStore', () => { expect(getByText('Number rendered: 2')).toBeInTheDocument() }) - it('allow specifying custom equality function', async () => { + it('useSelector allows specifying a custom equality function', async () => { const store = createStore({ array: [ { select: 0, ignore: 1 }, @@ -92,10 +492,10 @@ describe('useStore', () => { const renderSpy = vi.fn() function Comp() { - const storeVal = useStore( + const storeVal = useSelector( store, (state) => state.array.map(({ ignore, ...rest }) => rest), - { equal: deepEqual }, + { compare: deepEqual }, ) renderSpy() @@ -150,13 +550,12 @@ describe('useStore', () => { expect(getByText('Number rendered: 2')).toBeInTheDocument() }) - it('works with mounted derived stores', async () => { + it('useSelector works with mounted derived stores', async () => { const store = createStore(0) - - const derived = createStore(() => store.state * 2) + const derived = createStore(() => ({ val: store.state * 2 })) function Comp() { - const derivedVal = useStore(derived, (state) => state) + const derivedVal = useSelector(derived, (state) => state.val) return (
@@ -177,6 +576,113 @@ describe('useStore', () => { }) }) +describe('useStore', () => { + it('is a compatibility alias for useSelector', async () => { + const store = createStore(0) + + function Comp() { + const value = useStore(store, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('supports atom sources through the deprecated alias', async () => { + const atom = createAtom(0) + + function Comp() { + const value = useStore(atom, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + +describe('_useStore', () => { + it('returns selected state and actions for stores with actions', async () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + function Comp() { + const [count, { inc }] = _useStore(store, (state) => state.count) + + return ( +
+

Count: {count}

+ +
+ ) + } + + const { getByText } = render() + expect(getByText('Count: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Count: 1')).toBeInTheDocument()) + }) + + it('returns selected state and setState for plain stores', async () => { + const store = createStore(0) + + function Comp() { + const [value, setState] = _useStore(store, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + describe('shallow', () => { test('should return true for shallowly equal objects', () => { const objA = { a: 1, b: 'hello' } diff --git a/packages/preact-store/tests/test.test-d.ts b/packages/preact-store/tests/test.test-d.ts index 3ce5bf4d..95b1c310 100644 --- a/packages/preact-store/tests/test.test-d.ts +++ b/packages/preact-store/tests/test.test-d.ts @@ -1,15 +1,179 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { useStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { + _useStore, + createStoreContext, + useAtom, + useCreateAtom, + useCreateStore, + useSelector, + useStore, + useValue, +} from '../src' +// eslint-disable-next-line no-duplicate-imports +import type { Atom, ReadonlyStore, Store } from '@tanstack/store' -test('useStore works with derived state', () => { - const store = createStore(12) - const derived = createStore(() => store.state * 2) +test('useCreateAtom returns a writable atom for initial values', () => { + const atom = useCreateAtom(12) - const val = useStore(derived, (state) => { - expectTypeOf(state).toEqualTypeOf() - return state + expectTypeOf(atom.get()).toExtend() + expectTypeOf(atom.set).toBeFunction() +}) + +test('useCreateAtom returns a readonly atom for derived values', () => { + const atom = useCreateAtom(() => 12, { + compare: (prev, next) => prev === next, + }) + + expectTypeOf(atom.get()).toExtend() + expectTypeOf(atom).not.toHaveProperty('set') +}) + +test('useValue infers value from mutable and readonly atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toExtend() + expectTypeOf(useValue(readonlyAtom)).toExtend() + expectTypeOf(useValue(writableStore)).toExtend() + expectTypeOf(useValue(readonlyStore)).toExtend() + expectTypeOf( + useValue(writableAtom, { + compare: (prev, next) => prev === next, + }), + ).toExtend() +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + const [valueWithOptions] = useAtom(writableAtom, { + compare: (prev, next) => prev === next, }) - expectTypeOf(val).toEqualTypeOf() + expectTypeOf(value).toExtend() + expectTypeOf(valueWithOptions).toExtend() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useCreateStore returns writable and readonly store types', () => { + const writableStore = useCreateStore(12) + const writableStoreWithActions = useCreateStore( + { count: 0 }, + ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + }), + ) + const readonlyStore = useCreateStore(() => 24) + + expectTypeOf(writableStore.state).toExtend() + expectTypeOf(writableStore.setState).toBeFunction() + expectTypeOf(writableStoreWithActions.state).toMatchObjectType<{ + count: number + }>() + expectTypeOf(writableStoreWithActions.actions.inc).toBeFunction() + expectTypeOf(readonlyStore.state).toExtend() + expectTypeOf(readonlyStore).not.toHaveProperty('setState') + + useCreateStore({ count: 0 }, () => ({ + // @ts-expect-error actions must be functions + asdf: 123, + inc: () => {}, + })) +}) + +test('useSelector infers state and selected types for stores', () => { + const baseStore = createStore(12) + const derivedStore = createStore(() => { + return { val: baseStore.state * 2 } + }) + + const val = useSelector(derivedStore, (state) => { + expectTypeOf(state).toMatchObjectType<{ val: number }>() + return state.val + }) + const valWithOptions = useSelector(derivedStore, (state) => state.val, { + compare: (prev, next) => prev === next, + }) + + expectTypeOf(val).toExtend() + expectTypeOf(valWithOptions).toExtend() +}) + +test('useSelector infers state and selected types for atoms', () => { + const atom = createAtom({ val: 12 }) + + const val = useSelector(atom, (state) => { + expectTypeOf(state).toMatchObjectType<{ val: number }>() + return state.val + }) + + expectTypeOf(val).toExtend() +}) + +test('useStore matches useSelector types for compatibility', () => { + const baseStore = createStore(12) + const derivedStore = createStore(() => { + return { val: baseStore.state * 2 } + }) + + const selectorValue = useSelector(derivedStore, (state) => state.val) + const compatValue = useStore( + derivedStore, + (state) => state.val, + (prev, next) => prev === next, + ) + + expectTypeOf(selectorValue).toExtend() + expectTypeOf(compatValue).toExtend() +}) + +test('createStoreContext preserves keyed atom and store types', () => { + const countAtom = createAtom(12) + const readonlySource = createStore(() => ({ value: 24 })) + const storeFactory = createStoreContext<{ + countAtom: typeof countAtom + readonlyStore: typeof readonlySource + }>() + const contextValue = storeFactory.useStoreContext() + + expectTypeOf(contextValue.countAtom).toExtend>() + expectTypeOf(contextValue.countAtom.set).toBeFunction() + + const [value, setValue] = useAtom(contextValue.countAtom) + expectTypeOf(value).toExtend() + expectTypeOf(setValue).toBeFunction() + + const readonlyStore = contextValue.readonlyStore + expectTypeOf(readonlyStore).toExtend>() + expectTypeOf(readonlyStore).not.toHaveProperty('setState') + + const selected = useSelector(readonlyStore, (state) => state.value) + expectTypeOf(selected).toExtend() +}) + +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const [selected, actions] = _useStore(store, (state) => state.count) + + expectTypeOf(selected).toExtend() + expectTypeOf(actions.inc).toBeFunction() +}) + +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toExtend() + expectTypeOf(setState).toEqualTypeOf['setState']>() }) diff --git a/packages/react-store/package.json b/packages/react-store/package.json index d7e7d1fe..bf83679c 100644 --- a/packages/react-store/package.json +++ b/packages/react-store/package.json @@ -33,19 +33,13 @@ "build": "tsdown --tsconfig tsconfig.build.json" }, "type": "module", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.cts", "main": "./dist/index.cjs", "module": "./dist/index.js", "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "import": "./dist/index.js", + "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, diff --git a/packages/react-store/src/_useStore.ts b/packages/react-store/src/_useStore.ts new file mode 100644 index 00000000..30fdd927 --- /dev/null +++ b/packages/react-store/src/_useStore.ts @@ -0,0 +1,43 @@ +import { useMemo } from 'react' +import { useSelector } from './useSelector' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```tsx + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +/* eslint-disable react-hooks/rules-of-hooks, @eslint-react/rules-of-hooks -- experimental API with underscore prefix */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + TSelected, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = useMemo( + () => (store.actions as StoreActionMap | undefined) ?? store.setState, + [store], + ) + + return [selected, actionsOrSetState] as any +} diff --git a/packages/react-store/src/createStoreContext.tsx b/packages/react-store/src/createStoreContext.tsx new file mode 100644 index 00000000..b2de1f33 --- /dev/null +++ b/packages/react-store/src/createStoreContext.tsx @@ -0,0 +1,78 @@ +import { createContext, useContext } from 'react' +import type { PropsWithChildren, ReactElement } from 'react' + +/** + * Creates a typed React context for sharing a bundle of atoms and stores with a subtree. + * + * The returned `StoreProvider` only transports the provided object through + * React context. Consumers destructure the contextual atoms and stores, then + * compose them with the existing hooks like {@link useSelector}, + * {@link useValue}, {@link useSetValue}, and {@link useAtom}. + * + * The object shape is preserved exactly, so keyed atoms and stores remain fully + * typed when read back with `useStoreContext()`. + * + * @example + * ```tsx + * const { StoreProvider, useStoreContext } = createStoreContext<{ + * countAtom: Atom + * totalsStore: Store<{ count: number }> + * }>() + * + * function CountButton() { + * const { countAtom, totalsStore } = useStoreContext() + * const count = useValue(countAtom) + * const total = useSelector(totalsStore, (state) => state.count) + * + * return ( + * + * ) + * } + * ``` + * + * @throws When `useStoreContext()` is called outside the matching `StoreProvider`. + */ +export function createStoreContext(): { + StoreProvider: ( + props: { + value: TValue + } & PropsWithChildren, + ) => ReactElement + useStoreContext: () => TValue +} { + const Context = createContext(null) + Context.displayName = 'StoreContext' + + // eslint-disable-next-line @eslint-react/component-hook-factories + function StoreProvider({ + children, + value, + }: PropsWithChildren<{ + value: TValue + }>) { + // eslint-disable-next-line @eslint-react/no-context-provider + return {children} + } + + // eslint-disable-next-line @eslint-react/component-hook-factories + function useStoreContext() { + // eslint-disable-next-line @eslint-react/no-use-context + const value = useContext(Context) + + if (value === null) { + throw new Error('Missing StoreProvider for StoreContext') + } + + return value + } + + return { + StoreProvider, + useStoreContext, + } +} diff --git a/packages/react-store/src/index.ts b/packages/react-store/src/index.ts index 68ec6c35..4bcfb415 100644 --- a/packages/react-store/src/index.ts +++ b/packages/react-store/src/index.ts @@ -1,61 +1,12 @@ export * from '@tanstack/store' -export { useStore } from './useStore' +export * from './createStoreContext' +export * from './useCreateAtom' +export * from './useCreateStore' -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } +export * from './useValue' +export * from './useSelector' - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = getOwnKeys(objA) - if (keysA.length !== getOwnKeys(objB).length) { - return false - } - - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} - -function getOwnKeys(obj: object): Array { - return (Object.keys(obj) as Array).concat( - Object.getOwnPropertySymbols(obj), - ) -} +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector +export * from './_useStore' diff --git a/packages/react-store/src/useAtom.ts b/packages/react-store/src/useAtom.ts new file mode 100644 index 00000000..862be2dc --- /dev/null +++ b/packages/react-store/src/useAtom.ts @@ -0,0 +1,23 @@ +import { useValue } from './useValue' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Returns the current atom value together with a stable setter. + * + * This is the writable-atom convenience hook for components that need to both + * read and update the same atom. + * + * @example + * ```tsx + * const [count, setCount] = useAtom(countAtom) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [TValue, Atom['set']] { + const value = useValue(atom, options) + + return [value, atom.set] +} diff --git a/packages/react-store/src/useCreateAtom.ts b/packages/react-store/src/useCreateAtom.ts new file mode 100644 index 00000000..eea0258e --- /dev/null +++ b/packages/react-store/src/useCreateAtom.ts @@ -0,0 +1,38 @@ +import { useState } from 'react' +import { createAtom } from '@tanstack/store' +import type { Atom, AtomOptions, ReadonlyAtom } from '@tanstack/store' + +/** + * Creates a stable atom instance for the lifetime of the component. + * + * Pass an initial value to create a writable atom, or a getter function to + * create a readonly derived atom. This hook mirrors the overloads from + * {@link createAtom}, but ensures the atom is only created once per mount. + * + * @example + * ```tsx + * const countAtom = useCreateAtom(0) + * ``` + */ +export function useCreateAtom( + getValue: (prev?: NoInfer) => T, + options?: AtomOptions, +): ReadonlyAtom +export function useCreateAtom( + initialValue: T, + options?: AtomOptions, +): Atom +export function useCreateAtom( + valueOrFn: T | ((prev?: T) => T), + options?: AtomOptions, +): Atom | ReadonlyAtom { + const [atom] = useState | ReadonlyAtom>(() => { + if (typeof valueOrFn === 'function') { + return createAtom(valueOrFn as (prev?: NoInfer) => T, options) + } + + return createAtom(valueOrFn, options) + }) + + return atom +} diff --git a/packages/react-store/src/useCreateStore.ts b/packages/react-store/src/useCreateStore.ts new file mode 100644 index 00000000..30db1585 --- /dev/null +++ b/packages/react-store/src/useCreateStore.ts @@ -0,0 +1,51 @@ +import { useState } from 'react' +import { createStore } from '@tanstack/store' +import type { + ReadonlyStore, + Store, + StoreActionMap, + StoreActionsFactory, +} from '@tanstack/store' + +type NonFunction = T extends (...args: Array) => any ? never : T + +/** + * Creates a stable store instance for the lifetime of the component. + * + * Pass an initial value to create a writable store, or a getter function to + * create a readonly derived store. This hook mirrors the overloads from + * {@link createStore}, but ensures the store is only created once per mount. + * + * @example + * ```tsx + * const counterStore = useCreateStore({ count: 0 }) + * ``` + */ +export function useCreateStore( + getValue: (prev?: NoInfer) => T, +): ReadonlyStore +export function useCreateStore(initialValue: T): Store +export function useCreateStore( + initialValue: NonFunction, + actions: StoreActionsFactory, +): Store +export function useCreateStore( + valueOrFn: T | ((prev?: T) => T), + actions?: StoreActionsFactory, +): Store | Store | ReadonlyStore { + const [store] = useState | Store | ReadonlyStore>( + () => { + if (typeof valueOrFn === 'function') { + return createStore(valueOrFn as (prev?: NoInfer) => T) + } + + if (actions) { + return createStore(valueOrFn as NonFunction, actions) + } + + return createStore(valueOrFn) + }, + ) + + return store +} diff --git a/packages/react-store/src/useSelector.ts b/packages/react-store/src/useSelector.ts new file mode 100644 index 00000000..7cc67dc3 --- /dev/null +++ b/packages/react-store/src/useSelector.ts @@ -0,0 +1,65 @@ +import { useCallback } from 'react' +import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +type SyncExternalStoreSubscribe = Parameters< + typeof useSyncExternalStoreWithSelector +>[0] + +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and subscribes the component + * to that selection. + * + * This is the primary React read hook for TanStack Store. It works with any + * source that exposes `get()` and `subscribe()`, including atoms, readonly + * atoms, stores, and readonly stores. + * + * @example + * ```tsx + * const count = useSelector(counterStore, (state) => state.count) + * ``` + * + * @example + * ```tsx + * const count = useSelector(countAtom, (value) => value) + * ``` + */ +export function useSelector( + source: SelectionSource, + selector: (snapshot: TSource) => TSelected, + options?: UseSelectorOptions, +): TSelected { + const compare = options?.compare ?? defaultCompare + + const subscribe: SyncExternalStoreSubscribe = useCallback( + (handleStoreChange) => { + const { unsubscribe } = source.subscribe(handleStoreChange) + return unsubscribe + }, + [source], + ) + + const getSnapshot = useCallback(() => source.get(), [source]) + + return useSyncExternalStoreWithSelector( + subscribe, + getSnapshot, + getSnapshot, + selector, + compare, + ) +} diff --git a/packages/react-store/src/useStore.ts b/packages/react-store/src/useStore.ts index 70e8c2e7..b08021a1 100644 --- a/packages/react-store/src/useStore.ts +++ b/packages/react-store/src/useStore.ts @@ -1,44 +1,22 @@ -import { useCallback } from 'react' -import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector' -import type { AnyAtom } from '@tanstack/store' +import { useSelector } from './useSelector' -type SyncExternalStoreSubscribe = Parameters< - typeof useSyncExternalStoreWithSelector ->[0] - -function defaultCompare(a: T, b: T) { - return a === b -} - -export function useStore( - atom: TAtom, - selector: ( - snapshot: TAtom extends { get: () => infer TSnapshot } - ? TSnapshot - : undefined, - ) => T, - compare: (a: T, b: T) => boolean = defaultCompare, -): T { - const subscribe: SyncExternalStoreSubscribe = useCallback( - (handleStoreChange) => { - if (!atom) { - return () => {} - } - const { unsubscribe } = atom.subscribe(handleStoreChange) - return unsubscribe - }, - [atom], - ) - - const boundGetSnapshot = useCallback(() => atom?.get(), [atom]) - - const selectedSnapshot = useSyncExternalStoreWithSelector( - subscribe, - boundGetSnapshot, - boundGetSnapshot, - selector, - compare, - ) - - return selectedSnapshot -} +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```tsx + * const count = useStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = ( + source: { + get: () => TSource + subscribe: (listener: (value: TSource) => void) => { + unsubscribe: () => void + } + }, + selector: (snapshot: TSource) => TSelected, + compare?: (a: TSelected, b: TSelected) => boolean, +) => useSelector(source, selector, { compare }) diff --git a/packages/react-store/src/useValue.ts b/packages/react-store/src/useValue.ts new file mode 100644 index 00000000..37213160 --- /dev/null +++ b/packages/react-store/src/useValue.ts @@ -0,0 +1,32 @@ +import { useSelector } from './useSelector' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Subscribes to an atom or store and returns its current value. + * + * This is the whole-value counterpart to {@link useSelector}. Use it when a + * component needs the entire current value from a writable or readonly atom or + * store. Pass `options.compare` to suppress rerenders when successive values + * should be treated as equivalent. + * + * @example + * ```tsx + * const count = useValue(countAtom) + * ``` + * + * @example + * ```tsx + * const state = useValue(appStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): TValue { + return useSelector(source, (value) => value, options) +} diff --git a/packages/react-store/tests/index.test.tsx b/packages/react-store/tests/index.test.tsx index a7687515..2beb066d 100644 --- a/packages/react-store/tests/index.test.tsx +++ b/packages/react-store/tests/index.test.tsx @@ -1,20 +1,388 @@ -import { describe, expect, it, test, vi } from 'vitest' -import { render, waitFor } from '@testing-library/react' +import { act, render, renderHook, waitFor } from '@testing-library/react' import { userEvent } from '@testing-library/user-event' -import { createStore } from '@tanstack/store' -import { shallow, useStore } from '../src/index' +import { describe, expect, it, test, vi } from 'vitest' +import { createAtom, createStore } from '@tanstack/store' +import { + _useStore, + createStoreContext, + shallow, + useAtom, + useCreateAtom, + useCreateStore, + useSelector, + useStore, + useValue, +} from '../src/index' const user = userEvent.setup() -describe('useStore', () => { - it('allows us to select state using a selector', () => { +describe('atom hooks', () => { + it('useCreateAtom creates a stable atom instance across rerenders', () => { + const { result, rerender } = renderHook(() => useCreateAtom(0)) + const atom = result.current + + act(() => { + atom.set(1) + }) + + rerender() + + expect(result.current).toBe(atom) + expect(result.current.get()).toBe(1) + }) + + it('useValue reads mutable atom state and rerenders when updated', async () => { + const atom = createAtom(0) + + function Comp() { + const value = useValue(atom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('useValue reads readonly atom state', async () => { + const countAtom = createAtom(1) + const doubledAtom = createAtom(() => countAtom.get() * 2) + + function Comp() { + const value = useValue(doubledAtom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 4')).toBeInTheDocument()) + }) + + it('useValue respects custom compare', async () => { + const atom = createAtom({ + select: 0, + ignored: 1, + }) + const renderSpy = vi.fn() + + function Comp() { + const value = useValue(atom, { + compare: (prev, next) => prev.select === next.select, + }) + renderSpy() + + return ( +
+

Renders: {renderSpy.mock.calls.length}

+

Value: {value.select}

+ + +
+ ) + } + + const { getByText } = render() + + expect(getByText('Renders: 1')).toBeInTheDocument() + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update ignored')) + expect(getByText('Renders: 1')).toBeInTheDocument() + + await user.click(getByText('Update select')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + expect(getByText('Renders: 2')).toBeInTheDocument() + }) + + it('useAtom returns the current value and setter', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useAtom(atom)) + + expect(result.current[0]).toBe(0) + + act(() => { + result.current[1]((prev) => prev + 5) + }) + + expect(result.current[0]).toBe(5) + }) +}) + +describe('store contexts', () => { + it('provides bundled writable atoms and stores', async () => { + const countAtom = createAtom(0) + const totalStore = createStore({ count: 0 }) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof countAtom + totalStore: typeof totalStore + }>() + + function Comp() { + const { countAtom: currentAtom, totalStore: currentStore } = + useStoreContext() + const value = useValue(currentAtom) + const total = useSelector(currentStore, (state) => state.count) + + return ( +
+

Value: {value}

+

Total: {total}

+ + +
+ ) + } + + const { getByText } = render( + + + , + ) + + expect(getByText('Value: 0')).toBeInTheDocument() + expect(getByText('Total: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + await user.click(getByText('Update total')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Total: 1')).toBeInTheDocument()) + }) + + it('supports readonly atoms and stores in the same context', async () => { + const baseAtom = createAtom(1) + const readonlyAtom = createAtom(() => baseAtom.get() * 2) + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + const { StoreProvider, useStoreContext } = createStoreContext<{ + readonlyAtom: typeof readonlyAtom + readonlyStore: typeof readonlyStore + }>() + + function Comp() { + const { readonlyAtom: currentAtom, readonlyStore: currentStore } = + useStoreContext() + const atomValue = useValue(currentAtom) + const storeValue = useSelector(currentStore, (state) => state.value) + + return ( +
+

Atom: {atomValue}

+

Store: {storeValue}

+
+ ) + } + + const { getByText } = render( + + + , + ) + + expect(getByText('Atom: 2')).toBeInTheDocument() + expect(getByText('Store: 2')).toBeInTheDocument() + + act(() => { + baseAtom.set((prev) => prev + 1) + baseStore.setState((prev) => prev + 1) + }) + + await waitFor(() => expect(getByText('Atom: 4')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Store: 4')).toBeInTheDocument()) + }) + + it('works with useAtom against contextual atoms', async () => { + const countAtom = createAtom(0) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof countAtom + }>() + + function Comp() { + const { countAtom: atom } = useStoreContext() + const [value, setValue] = useAtom(atom) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render( + + + , + ) + + await user.click(getByText('Add 5')) + + await waitFor(() => expect(getByText('Value: 5')).toBeInTheDocument()) + }) + + it('throws a clear error when a store provider is missing', () => { + const { useStoreContext } = createStoreContext<{ countAtom: number }>() + + function Comp() { + useStoreContext() + return null + } + + expect(() => render()).toThrowError( + 'Missing StoreProvider for StoreContext', + ) + }) + + it('nested providers override parent values', async () => { + const outerAtom = createAtom(1) + const innerAtom = createAtom(5) + const { StoreProvider, useStoreContext } = createStoreContext<{ + countAtom: typeof outerAtom + }>() + + function Value() { + const { countAtom: atom } = useStoreContext() + const value = useValue(atom) + + return

Value: {value}

+ } + + const { getAllByText } = render( + + + + + + , + ) + + expect(getAllByText(/Value:/).map((node) => node.textContent)).toEqual([ + 'Value: 1', + 'Value: 5', + ]) + + act(() => { + innerAtom.set(7) + }) + + await waitFor(() => + expect(getAllByText(/Value:/).map((node) => node.textContent)).toEqual([ + 'Value: 1', + 'Value: 7', + ]), + ) + }) +}) + +describe('store hooks', () => { + it('useCreateStore creates a stable store instance across rerenders', () => { + const { result, rerender } = renderHook(() => useCreateStore(0)) + const store = result.current + + act(() => { + store.setState((prev) => prev + 1) + }) + + rerender() + + expect(result.current).toBe(store) + expect(result.current.state).toBe(1) + }) + + it('useCreateStore supports actions and keeps them stable', () => { + const { result, rerender } = renderHook(() => + useCreateStore({ count: 0 }, ({ get, setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })), + ) + const store = result.current + const actions = store.actions + + act(() => { + store.actions.inc() + }) + + rerender() + + expect(result.current).toBe(store) + expect(result.current.actions).toBe(actions) + expect(result.current.actions.current()).toBe(1) + }) + + it('useSelector allows us to select state using a selector', () => { const store = createStore({ select: 0, ignored: 1, }) function Comp() { - const storeVal = useStore(store, (state) => state.select) + const storeVal = useSelector(store, (state) => state.select) return

Store: {storeVal}

} @@ -23,17 +391,48 @@ describe('useStore', () => { expect(getByText('Store: 0')).toBeInTheDocument() }) - it('only triggers a re-render when selector state is updated', async () => { + it('useValue reads writable and readonly store state', async () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + + function Comp() { + const value = useValue(baseStore) + const readonlyValue = useValue(readonlyStore) + + return ( +
+

Value: {value}

+

Readonly: {readonlyValue.value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 1')).toBeInTheDocument() + expect(getByText('Readonly: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) + }) + + it('useSelector only triggers a re-render when selector state is updated', async () => { const store = createStore({ select: 0, ignored: 1, }) - // Spy must be created outside the component so we get one mock instance whose - // .mock.calls we can read. useState(vi.fn) would store the factory, not a mock. const renderSpy = vi.fn() function Comp() { - const storeVal = useStore(store, (s) => s.select) + const storeVal = useSelector(store, (state) => state.select) renderSpy() return ( @@ -79,7 +478,7 @@ describe('useStore', () => { expect(getByText('Number rendered: 2')).toBeInTheDocument() }) - it('allow specifying custom equality function', async () => { + it('useSelector allows specifying a custom equality function', async () => { const store = createStore({ array: [ { select: 0, ignore: 1 }, @@ -93,10 +492,10 @@ describe('useStore', () => { const renderSpy = vi.fn() function Comp() { - const storeVal = useStore( + const storeVal = useSelector( store, - (s) => s.array.map(({ ignore, ...rest }) => rest), - deepEqual, + (state) => state.array.map(({ ignore, ...rest }) => rest), + { compare: deepEqual }, ) renderSpy() @@ -151,12 +550,12 @@ describe('useStore', () => { expect(getByText('Number rendered: 2')).toBeInTheDocument() }) - it('works with mounted derived stores', async () => { + it('useSelector works with mounted derived stores', async () => { const store = createStore(0) const derived = createStore(() => ({ val: store.state * 2 })) function Comp() { - const derivedVal = useStore(derived, (s) => s.val) + const derivedVal = useSelector(derived, (state) => state.val) return (
@@ -177,6 +576,113 @@ describe('useStore', () => { }) }) +describe('useStore', () => { + it('is a compatibility alias for useSelector', async () => { + const store = createStore(0) + + function Comp() { + const value = useStore(store, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('supports atom sources through the deprecated alias', async () => { + const atom = createAtom(0) + + function Comp() { + const value = useStore(atom, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + +describe('_useStore', () => { + it('returns selected state and actions for stores with actions', async () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + function Comp() { + const [count, { inc }] = _useStore(store, (state) => state.count) + + return ( +
+

Count: {count}

+ +
+ ) + } + + const { getByText } = render() + expect(getByText('Count: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Count: 1')).toBeInTheDocument()) + }) + + it('returns selected state and setState for plain stores', async () => { + const store = createStore(0) + + function Comp() { + const [value, setState] = _useStore(store, (state) => state) + + return ( +
+

Value: {value}

+ +
+ ) + } + + const { getByText } = render() + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + describe('shallow', () => { test('should return true for shallowly equal objects', () => { const objA = { a: 1, b: 'hello' } @@ -259,40 +765,4 @@ describe('shallow', () => { expect(shallow(objA, objB)).toBe(true) }) - - test('should return true for shallowly equal maps', () => { - const objA = new Map([['1', 'hello']]) - const objB = new Map([['1', 'hello']]) - expect(shallow(objA, objB)).toBe(true) - }) - - test('should return false for maps with different values', () => { - const objA = new Map([['1', 'hello']]) - const objB = new Map([['1', 'world']]) - expect(shallow(objA, objB)).toBe(false) - }) - - test('should return true for shallowly equal sets', () => { - const objA = new Set([1]) - const objB = new Set([1]) - expect(shallow(objA, objB)).toBe(true) - }) - - test('should return false for sets with different values', () => { - const objA = new Set([1]) - const objB = new Set([2]) - expect(shallow(objA, objB)).toBe(false) - }) - - test('should return false for dates with different values', () => { - const objA = new Date('2025-04-10T14:48:00') - const objB = new Date('2025-04-10T14:58:00') - expect(shallow(objA, objB)).toBe(false) - }) - - test('should return true for equal dates', () => { - const objA = new Date('2025-02-10') - const objB = new Date('2025-02-10') - expect(shallow(objA, objB)).toBe(true) - }) }) diff --git a/packages/react-store/tests/test.test-d.ts b/packages/react-store/tests/test.test-d.ts index 2afcfca5..55b0d1b6 100644 --- a/packages/react-store/tests/test.test-d.ts +++ b/packages/react-store/tests/test.test-d.ts @@ -1,16 +1,178 @@ import { expectTypeOf, test } from 'vitest' -import { createStore, useStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { + _useStore, + createStoreContext, + useAtom, + useCreateAtom, + useCreateStore, + useSelector, + useStore, + useValue, +} from '../src' +import type { Atom, ReadonlyStore, Store } from '@tanstack/store' -test('useStore works with derived state', () => { - const store = createStore(12) - const derived = createStore(() => { - return { val: store.state * 2 } +test('useCreateAtom returns a writable atom for initial values', () => { + const atom = useCreateAtom(12) + + expectTypeOf(atom.get()).toExtend() + expectTypeOf(atom.set).toBeFunction() +}) + +test('useCreateAtom returns a readonly atom for derived values', () => { + const atom = useCreateAtom(() => 12, { + compare: (prev, next) => prev === next, + }) + + expectTypeOf(atom.get()).toExtend() + expectTypeOf(atom).not.toHaveProperty('set') +}) + +test('useValue infers value from mutable and readonly atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toExtend() + expectTypeOf(useValue(readonlyAtom)).toExtend() + expectTypeOf(useValue(writableStore)).toExtend() + expectTypeOf(useValue(readonlyStore)).toExtend() + expectTypeOf( + useValue(writableAtom, { + compare: (prev, next) => prev === next, + }), + ).toExtend() +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + const [valueWithOptions] = useAtom(writableAtom, { + compare: (prev, next) => prev === next, + }) + + expectTypeOf(value).toExtend() + expectTypeOf(valueWithOptions).toExtend() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useCreateStore returns writable and readonly store types', () => { + const writableStore = useCreateStore(12) + const writableStoreWithActions = useCreateStore( + { count: 0 }, + ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + }), + ) + const readonlyStore = useCreateStore(() => 24) + + expectTypeOf(writableStore.state).toExtend() + expectTypeOf(writableStore.setState).toBeFunction() + expectTypeOf(writableStoreWithActions.state).toMatchObjectType<{ + count: number + }>() + expectTypeOf(writableStoreWithActions.actions.inc).toBeFunction() + expectTypeOf(readonlyStore.state).toExtend() + expectTypeOf(readonlyStore).not.toHaveProperty('setState') + + useCreateStore({ count: 0 }, () => ({ + // @ts-expect-error actions must be functions + asdf: 123, + inc: () => {}, + })) +}) + +test('useSelector infers state and selected types for stores', () => { + const baseStore = createStore(12) + const derivedStore = createStore(() => { + return { val: baseStore.state * 2 } }) - const val = useStore(derived, (state) => { + const val = useSelector(derivedStore, (state) => { expectTypeOf(state).toMatchObjectType<{ val: number }>() return state.val }) + const valWithOptions = useSelector(derivedStore, (state) => state.val, { + compare: (prev, next) => prev === next, + }) expectTypeOf(val).toExtend() + expectTypeOf(valWithOptions).toExtend() +}) + +test('useSelector infers state and selected types for atoms', () => { + const atom = createAtom({ val: 12 }) + + const val = useSelector(atom, (state) => { + expectTypeOf(state).toMatchObjectType<{ val: number }>() + return state.val + }) + + expectTypeOf(val).toExtend() +}) + +test('useStore matches useSelector types for compatibility', () => { + const baseStore = createStore(12) + const derivedStore = createStore(() => { + return { val: baseStore.state * 2 } + }) + + const selectorValue = useSelector(derivedStore, (state) => state.val) + const compatValue = useStore( + derivedStore, + (state) => state.val, + (prev, next) => prev === next, + ) + + expectTypeOf(selectorValue).toExtend() + expectTypeOf(compatValue).toExtend() +}) + +test('createStoreContext preserves keyed atom and store types', () => { + const countAtom = createAtom(12) + const readonlySource = createStore(() => ({ value: 24 })) + const storeFactory = createStoreContext<{ + countAtom: typeof countAtom + readonlyStore: typeof readonlySource + }>() + const contextValue = storeFactory.useStoreContext() + + expectTypeOf(contextValue.countAtom).toExtend>() + expectTypeOf(contextValue.countAtom.set).toBeFunction() + + const [value, setValue] = useAtom(contextValue.countAtom) + expectTypeOf(value).toExtend() + expectTypeOf(setValue).toBeFunction() + + const readonlyStore = contextValue.readonlyStore + expectTypeOf(readonlyStore).toExtend>() + expectTypeOf(readonlyStore).not.toHaveProperty('setState') + + const selected = useSelector(readonlyStore, (state) => state.value) + expectTypeOf(selected).toExtend() +}) + +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const [selected, actions] = _useStore(store, (state) => state.count) + + expectTypeOf(selected).toExtend() + expectTypeOf(actions.inc).toBeFunction() +}) + +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toExtend() + expectTypeOf(setState).toEqualTypeOf['setState']>() }) diff --git a/packages/solid-store/package.json b/packages/solid-store/package.json index 9ea1d3b6..1006e03c 100644 --- a/packages/solid-store/package.json +++ b/packages/solid-store/package.json @@ -33,23 +33,13 @@ "build": "tsdown --tsconfig tsconfig.build.json && tsc -p tsconfig.source.json" }, "type": "module", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.cts", "main": "./dist/index.cjs", "module": "./dist/index.js", "exports": { ".": { - "solid": { - "types": "./dist/source/index.d.ts", - "default": "./dist/source/index.jsx" - }, - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "import": "./dist/index.js", + "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, diff --git a/packages/solid-store/src/_useStore.ts b/packages/solid-store/src/_useStore.ts new file mode 100644 index 00000000..4b2ba8b4 --- /dev/null +++ b/packages/solid-store/src/_useStore.ts @@ -0,0 +1,40 @@ +import { useSelector } from './useSelector' +import type { Accessor } from 'solid-js' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```tsx + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + Accessor, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/solid-store/src/index.tsx b/packages/solid-store/src/index.tsx index 6f8cee03..ea10ac37 100644 --- a/packages/solid-store/src/index.tsx +++ b/packages/solid-store/src/index.tsx @@ -1,84 +1,8 @@ -import { createSignal, onCleanup } from 'solid-js' -import type { Accessor } from 'solid-js' -import type { Atom, ReadonlyAtom } from '@tanstack/store' - export * from '@tanstack/store' -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn -} - -export function useStore>( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, -): Accessor { - const [signal, setSignal] = createSignal(selector(store.get())) - const equal = options.equal ?? shallow - - const unsub = store.subscribe((s) => { - const data = selector(s) - if (equal(signal(), data)) { - return - } - setSignal(() => data) - }).unsubscribe - - onCleanup(() => { - unsub() - }) - - return signal -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } +export * from './useValue' +export * from './useSelector' - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector +export * from './_useStore' diff --git a/packages/solid-store/src/useAtom.ts b/packages/solid-store/src/useAtom.ts new file mode 100644 index 00000000..7703a16c --- /dev/null +++ b/packages/solid-store/src/useAtom.ts @@ -0,0 +1,30 @@ +import { useValue } from './useValue' +import type { Accessor } from 'solid-js' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Returns the current atom accessor together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```tsx + * const [count, setCount] = useAtom(countAtom) + * + * return ( + * + * ) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [Accessor, Atom['set']] { + const value = useValue(atom, options) + + return [value, atom.set] +} diff --git a/packages/solid-store/src/useSelector.ts b/packages/solid-store/src/useSelector.ts new file mode 100644 index 00000000..720c5579 --- /dev/null +++ b/packages/solid-store/src/useSelector.ts @@ -0,0 +1,57 @@ +import { createSignal, onCleanup } from 'solid-js' +import type { Accessor } from 'solid-js' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and subscribes the component + * to that selection. + * + * This is the primary Solid read hook for TanStack Store. It returns a Solid + * accessor so consumers can read the selected value reactively. + * + * @example + * ```tsx + * const count = useSelector(counterStore, (state) => state.count) + * + * return

{count()}

+ * ``` + * + * @example + * ```tsx + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector( + source: SelectionSource, + selector: (snapshot: TSource) => TSelected, + options?: UseSelectorOptions, +): Accessor { + const compare = options?.compare ?? defaultCompare + const [signal, setSignal] = createSignal(selector(source.get()), { + equals: compare, + }) + + const unsubscribe = source.subscribe((snapshot) => { + setSignal(() => selector(snapshot)) + }).unsubscribe + + onCleanup(() => { + unsubscribe() + }) + + return signal +} diff --git a/packages/solid-store/src/useStore.ts b/packages/solid-store/src/useStore.ts new file mode 100644 index 00000000..05149a97 --- /dev/null +++ b/packages/solid-store/src/useStore.ts @@ -0,0 +1,24 @@ +import { useSelector } from './useSelector' +import type { Accessor } from 'solid-js' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```tsx + * const count = useStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = ( + source: { + get: () => TSource + subscribe: (listener: (value: TSource) => void) => { + unsubscribe: () => void + } + }, + selector: (snapshot: TSource) => TSelected = (value) => + value as unknown as TSelected, + compare?: (a: TSelected, b: TSelected) => boolean, +): Accessor => useSelector(source, selector, { compare }) diff --git a/packages/solid-store/src/useValue.ts b/packages/solid-store/src/useValue.ts new file mode 100644 index 00000000..a6ebbd63 --- /dev/null +++ b/packages/solid-store/src/useValue.ts @@ -0,0 +1,33 @@ +import { useSelector } from './useSelector' +import type { Accessor } from 'solid-js' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Subscribes to an atom or store and returns its current value accessor. + * + * This is the whole-value counterpart to {@link useSelector}. Use it when the + * component needs the entire current value from a source. + * + * @example + * ```tsx + * const count = useValue(countAtom) + * + * return

{count()}

+ * ``` + * + * @example + * ```tsx + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): Accessor { + return useSelector(source, (value) => value, options) +} diff --git a/packages/solid-store/tests/index.test.tsx b/packages/solid-store/tests/index.test.tsx index 082521c0..55c11a58 100644 --- a/packages/solid-store/tests/index.test.tsx +++ b/packages/solid-store/tests/index.test.tsx @@ -1,55 +1,309 @@ -import { describe, expect, it } from 'vitest' -import { render, renderHook } from '@solidjs/testing-library' -import { createStore } from '@tanstack/store' -import { useStore } from '../src/index' +import { describe, expect, it, test, vi } from 'vitest' +import { renderHook } from '@solidjs/testing-library' +import { createAtom, createStore } from '@tanstack/store' +import { + _useStore, + shallow, + useAtom, + useSelector, + useStore, + useValue, +} from '../src/index' -describe('useStore', () => { - it.todo('allows us to select state using a selector', () => { +describe('atom hooks', () => { + it('useValue reads mutable atom state and updates when changed', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useValue(atom)) + + expect(result()).toBe(0) + + atom.set((prev) => prev + 1) + + expect(result()).toBe(1) + }) + + it('useAtom returns the current accessor and setter', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useAtom(atom)) + + expect(result[0]()).toBe(0) + + result[1]((prev) => prev + 5) + + expect(result[0]()).toBe(5) + }) +}) + +describe('store hooks', () => { + it('useSelector allows us to select state using a selector', () => { const store = createStore({ select: 0, ignored: 1, }) - function Comp() { - const storeVal = useStore(store, (state) => state.select) + const { result } = renderHook(() => + useSelector(store, (state) => state.select), + ) + + expect(result()).toBe(0) + }) + + it('useValue reads writable and readonly store state', () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + const { result: writableValue } = renderHook(() => useValue(baseStore)) + const { result: readonlyValue } = renderHook(() => useValue(readonlyStore)) + + expect(writableValue()).toBe(1) + expect(readonlyValue().value).toBe(2) - return

Store: {storeVal()}

- } + baseStore.setState((prev) => prev + 1) - const { getByText } = render(() => ) - expect(getByText('Store: 0')).toBeInTheDocument() + expect(writableValue()).toBe(2) + expect(readonlyValue().value).toBe(4) }) - it('allows us to select state using a selector', () => { + it('useSelector only updates the accessor when selected state changes', () => { const store = createStore({ select: 0, ignored: 1, }) + const renderSpy = vi.fn() + + const { result } = renderHook(() => { + const value = useSelector(store, (state) => state.select) + renderSpy() + return value + }) + + expect(result()).toBe(0) + expect(renderSpy).toHaveBeenCalledTimes(1) + + store.setState((prev) => ({ + ...prev, + ignored: prev.ignored + 1, + })) + expect(renderSpy).toHaveBeenCalledTimes(1) + + store.setState((prev) => ({ + ...prev, + select: prev.select + 1, + })) + expect(result()).toBe(1) + expect(renderSpy).toHaveBeenCalledTimes(1) + }) + + it('useSelector respects custom compare', () => { + const store = createStore({ + array: [ + { select: 0, ignore: 1 }, + { select: 0, ignore: 1 }, + ], + }) + const renderSpy = vi.fn() + + const { result } = renderHook(() => { + const value = useSelector( + store, + (state) => state.array.map(({ ignore, ...rest }) => rest), + { + compare: (prev, next) => + JSON.stringify(prev) === JSON.stringify(next), + }, + ) + renderSpy() + return value + }) + + expect(result().map((item) => item.select)).toEqual([0, 0]) + expect(renderSpy).toHaveBeenCalledTimes(1) + + store.setState((prev) => ({ + array: prev.array.map((item) => ({ + ...item, + ignore: item.ignore + 1, + })), + })) + expect(renderSpy).toHaveBeenCalledTimes(1) + store.setState((prev) => ({ + array: prev.array.map((item) => ({ + ...item, + select: item.select + 1, + })), + })) + expect(result().map((item) => item.select)).toEqual([1, 1]) + expect(renderSpy).toHaveBeenCalledTimes(1) + }) + + it('useSelector works with mounted derived stores', () => { + const store = createStore(0) + const derived = createStore(() => ({ val: store.state * 2 })) const { result } = renderHook(() => - useStore(store, (state) => state.select), + useSelector(derived, (state) => state.val), ) expect(result()).toBe(0) + + store.setState((prev) => prev + 1) + + expect(result()).toBe(2) }) +}) - it('updates accessor value when state is updated', () => { +describe('useStore', () => { + it('is a compatibility alias for useSelector', () => { const store = createStore(0) + const { result } = renderHook(() => useStore(store, (state) => state)) - const { result } = renderHook(() => useStore(store)) + expect(result()).toBe(0) store.setState((prev) => prev + 1) expect(result()).toBe(1) }) - it('updates when date changes', () => { - const store = createStore(new Date('2025-03-29T21:06:30.401Z')) + it('supports atom sources through the deprecated alias', () => { + const atom = createAtom(0) + const { result } = renderHook(() => useStore(atom, (state) => state)) + + expect(result()).toBe(0) + + atom.set((prev) => prev + 1) + + expect(result()).toBe(1) + }) +}) + +describe('_useStore', () => { + it('returns selected state and actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const { result } = renderHook(() => + _useStore(store, (state) => state.count), + ) + + expect(result[0]()).toBe(0) + + result[1].inc() + + expect(result[0]()).toBe(1) + }) + + it('returns selected state and setState for plain stores', () => { + const store = createStore(0) + + const { result } = renderHook(() => _useStore(store, (state) => state)) + + expect(result[0]()).toBe(0) + + result[1]((prev) => prev + 1) + + expect(result[0]()).toBe(1) + }) +}) + +describe('shallow', () => { + test('should return true for shallowly equal objects', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 1, b: 'hello' } + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for objects with different values', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 2, b: 'world' } + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for objects with different keys', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: 1, c: 'world' } + // @ts-expect-error + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for objects with different structures', () => { + const objA = { a: 1, b: 'hello' } + const objB = [1, 'hello'] + // @ts-expect-error + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for one object being null', () => { + const objA = { a: 1, b: 'hello' } + const objB = null + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return false for one object being undefined', () => { + const objA = { a: 1, b: 'hello' } + const objB = undefined + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for two null objects', () => { + const objA = null + const objB = null + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for objects with different types', () => { + const objA = { a: 1, b: 'hello' } + const objB = { a: '1', b: 'hello' } + // @ts-expect-error + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for shallow equal objects with symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 1 } + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for shallow different values for symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 2 } + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for shallowly equal maps', () => { + const objA = new Map([['1', 'hello']]) + const objB = new Map([['1', 'hello']]) + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for maps with different values', () => { + const objA = new Map([['1', 'hello']]) + const objB = new Map([['1', 'world']]) + expect(shallow(objA, objB)).toBe(false) + }) + + test('should return true for shallowly equal sets', () => { + const objA = new Set([1]) + const objB = new Set([1]) + expect(shallow(objA, objB)).toBe(true) + }) - const { result } = renderHook(() => useStore(store)) + test('should return false for sets with different values', () => { + const objA = new Set([1]) + const objB = new Set([2]) + expect(shallow(objA, objB)).toBe(false) + }) - store.setState(() => new Date('2025-03-29T21:06:40.401Z')) + test('should return false for dates with different values', () => { + const objA = new Date('2025-04-10T14:48:00') + const objB = new Date('2025-04-10T14:58:00') + expect(shallow(objA, objB)).toBe(false) + }) - expect(result()).toStrictEqual(new Date('2025-03-29T21:06:40.401Z')) + test('should return true for equal dates', () => { + const objA = new Date('2025-02-10') + const objB = new Date('2025-02-10') + expect(shallow(objA, objB)).toBe(true) }) }) diff --git a/packages/solid-store/tests/test.test-d.ts b/packages/solid-store/tests/test.test-d.ts index e3da48b4..1e49ea64 100644 --- a/packages/solid-store/tests/test.test-d.ts +++ b/packages/solid-store/tests/test.test-d.ts @@ -1,16 +1,70 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { useStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { _useStore, useAtom, useSelector, useStore, useValue } from '../src' import type { Accessor } from 'solid-js' +import type { Store } from '@tanstack/store' -test('useStore works with derived state', () => { +test('useSelector works with derived state', () => { const store = createStore(12) const derived = createStore(() => store.state * 2) - const val = useStore(derived, (state) => { + const val = useSelector(derived, (state) => { expectTypeOf(state).toEqualTypeOf() return state }) expectTypeOf(val).toEqualTypeOf>() }) + +test('useValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toEqualTypeOf>() + expectTypeOf(useValue(readonlyAtom)).toEqualTypeOf>() + expectTypeOf(useValue(writableStore)).toEqualTypeOf>() + expectTypeOf(useValue(readonlyStore)).toEqualTypeOf>() +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + + expectTypeOf(value).toEqualTypeOf>() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useStore matches useSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = useSelector(store, (state) => state) + const compatValue = useStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf>() + expectTypeOf(compatValue).toEqualTypeOf>() +}) + +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const [selected, actions] = _useStore(store, (state) => state.count) + + expectTypeOf(selected).toEqualTypeOf>() + expectTypeOf(actions.inc).toBeFunction() +}) + +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf>() + expectTypeOf(setState).toEqualTypeOf['setState']>() +}) diff --git a/packages/store/package.json b/packages/store/package.json index 2112f3ee..f04015c4 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -33,19 +33,13 @@ "build": "tsdown --tsconfig tsconfig.build.json" }, "type": "module", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.cts", "main": "./dist/index.cjs", "module": "./dist/index.js", "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "import": "./dist/index.js", + "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index ff7c5459..71215a5e 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -1,3 +1,4 @@ export * from './types' export * from './atom' export * from './store' +export * from './shallow' diff --git a/packages/store/src/shallow.ts b/packages/store/src/shallow.ts new file mode 100644 index 00000000..75ff1634 --- /dev/null +++ b/packages/store/src/shallow.ts @@ -0,0 +1,57 @@ +export function shallow(objA: T, objB: T) { + if (Object.is(objA, objB)) { + return true + } + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false + } + + if (objA instanceof Map && objB instanceof Map) { + if (objA.size !== objB.size) return false + for (const [k, v] of objA) { + if (!objB.has(k) || !Object.is(v, objB.get(k))) return false + } + return true + } + + if (objA instanceof Set && objB instanceof Set) { + if (objA.size !== objB.size) return false + for (const v of objA) { + if (!objB.has(v)) return false + } + return true + } + + if (objA instanceof Date && objB instanceof Date) { + if (objA.getTime() !== objB.getTime()) return false + return true + } + + const keysA = getOwnKeys(objA) + if (keysA.length !== getOwnKeys(objB).length) { + return false + } + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || + !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) + ) { + return false + } + } + return true +} + +function getOwnKeys(obj: object): Array { + return (Object.keys(obj) as Array).concat( + Object.getOwnPropertySymbols(obj), + ) +} diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts index 66e7ab12..69a91678 100644 --- a/packages/store/src/store.ts +++ b/packages/store/src/store.ts @@ -1,16 +1,44 @@ import { createAtom, toObserver } from './atom' import type { Atom, Observer, Subscription } from './types' -export class Store { +export type StoreAction = (...args: Array) => any + +export type StoreActionMap = Record + +export type StoreActionsFactory = (store: { + setState: Store['setState'] + get: Store['get'] +}) => TActions + +type NonFunction = T extends (...args: Array) => any ? never : T + +export class Store { private atom: Atom + public readonly actions!: TActions constructor(getValue: (prev?: NoInfer) => T) constructor(initialValue: T) - constructor(valueOrFn: T | ((prev?: T) => T)) { + constructor( + initialValue: NonFunction, + actionsFactory: StoreActionsFactory, + ) + constructor( + valueOrFn: T | ((prev?: T) => T), + actionsFactory?: StoreActionsFactory, + ) { // createAtom has overloads that return ReadonlyAtom for functions and Atom for values // Store always needs Atom for setState, so we assert the return type this.atom = createAtom( valueOrFn as T | ((prev?: NoInfer) => T), ) as Atom + + // bind for safe destructuring + this.get = this.get.bind(this) + this.setState = this.setState.bind(this) + this.subscribe = this.subscribe.bind(this) + + if (actionsFactory) { + this.actions = actionsFactory(this) + } } public setState(updater: (prev: T) => T) { this.atom.set(updater) @@ -28,7 +56,10 @@ export class Store { } } -export class ReadonlyStore implements Omit, 'setState'> { +export class ReadonlyStore implements Omit< + Store, + 'setState' | 'actions' +> { private atom: Atom constructor(getValue: (prev?: NoInfer) => T) constructor(initialValue: T) @@ -56,11 +87,19 @@ export function createStore( getValue: (prev?: NoInfer) => T, ): ReadonlyStore export function createStore(initialValue: T): Store -export function createStore( +export function createStore( + initialValue: NonFunction, + actions: StoreActionsFactory, +): Store +export function createStore( valueOrFn: T | ((prev?: T) => T), -): Store | ReadonlyStore { + actions?: StoreActionsFactory, +): Store | Store | ReadonlyStore { if (typeof valueOrFn === 'function') { return new ReadonlyStore(valueOrFn as (prev?: NoInfer) => T) } + if (actions) { + return new Store(valueOrFn as NonFunction, actions) + } return new Store(valueOrFn) } diff --git a/packages/store/tests/store-type-safety.test.ts b/packages/store/tests/store-type-safety.test.ts index c4a967ff..e0585d8e 100644 --- a/packages/store/tests/store-type-safety.test.ts +++ b/packages/store/tests/store-type-safety.test.ts @@ -1,7 +1,9 @@ import { describe, expect, test, vi } from 'vitest' -import { createStore } from '../src' +import { Store, createStore } from '../src' describe('Store.setState Type Safety Improvements', () => { + const typecheckOnly = false as boolean + test('should handle function updater safely', () => { const store = createStore(0) @@ -61,6 +63,58 @@ describe('Store.setState Type Safety Improvements', () => { }) }) + test('should infer action types safely', () => { + const store = createStore({ count: 0 }, ({ get, setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + current: () => get().count, + })) + + store.actions.inc() + expect(store.actions.current()).toBe(1) + + createStore({ count: 0 }, ({ setState }) => ({ + // @ts-expect-error setState only accepts updater functions + bad: () => setState({ count: 1 }), + })) + + if (typecheckOnly) { + createStore({ count: 0 }, () => ({ + // @ts-expect-error actions must be functions + asdf: 123, + inc: () => {}, + })) + + type InvalidCounterActions = { + asdf: number + inc: () => void + } + + // @ts-expect-error action maps may only contain functions + new Store<{ count: number }, InvalidCounterActions>({ count: 0 }, () => ({ + asdf: 123, + inc: () => {}, + })) + } + }) + + test('should reject actions on derived stores', () => { + const count = createStore(1) + const derived = createStore(() => count.state * 2) + + // @ts-expect-error readonly stores do not expose actions + derived.actions + + if (typecheckOnly) { + createStore( + // @ts-expect-error function first arg with actions is not supported + () => ({ count: 0 }), + ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + }), + ) + } + }) + test('should handle edge cases safely', () => { const nullableStore = createStore(null) nullableStore.setState(() => 'not null') @@ -93,4 +147,13 @@ describe('Store.setState Type Safety Improvements', () => { expect(store.state).toBe(iterations) expect(duration).toBeLessThan(100) }) + + test('plain writable stores should not expose usable actions', () => { + const store = createStore(0) + + if (typecheckOnly) { + // @ts-expect-error plain writable stores do not have usable actions + store.actions.toString() + } + }) }) diff --git a/packages/store/tests/store.test.ts b/packages/store/tests/store.test.ts index ab8d47b8..7c088144 100644 --- a/packages/store/tests/store.test.ts +++ b/packages/store/tests/store.test.ts @@ -38,6 +38,47 @@ describe('store', () => { expect(store.state).toEqual(4) }) + test('supports actions on writable stores', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + expect(store.state).toEqual({ count: 0 }) + + store.actions.inc() + + expect(store.state).toEqual({ count: 1 }) + }) + + test('actions can read current state', () => { + const store = createStore({ count: 1 }, ({ get, setState }) => ({ + addIfOdd: () => { + if (get().count % 2 === 1) { + setState((prev) => ({ count: prev.count + 1 })) + } + }, + })) + + store.actions.addIfOdd() + expect(store.state).toEqual({ count: 2 }) + + store.actions.addIfOdd() + expect(store.state).toEqual({ count: 2 }) + }) + + test('actions bag is stable across updates', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const actions = store.actions + + store.actions.inc() + store.setState((prev) => ({ count: prev.count + 1 })) + + expect(store.actions).toBe(actions) + }) + test(`updateFn acts as state transformer`, () => { const store = createStore('1') const derivedStore = createStore(() => Number(store.state)) diff --git a/packages/svelte-store/package.json b/packages/svelte-store/package.json index 1204d316..602bf8f0 100644 --- a/packages/svelte-store/package.json +++ b/packages/svelte-store/package.json @@ -53,7 +53,7 @@ "@sveltejs/vite-plugin-svelte": "^7.0.0", "@testing-library/svelte": "^5.3.1", "eslint-plugin-svelte": "^3.17.0", - "svelte": "^5.55.2", + "svelte": "^5.55.3", "svelte-check": "^4.4.6" }, "peerDependencies": { diff --git a/packages/svelte-store/src/_useStore.ts b/packages/svelte-store/src/_useStore.ts new file mode 100644 index 00000000..da9caba6 --- /dev/null +++ b/packages/svelte-store/src/_useStore.ts @@ -0,0 +1,38 @@ +import { useSelector } from './useSelector.svelte.js' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector.svelte.js' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```ts + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * ``` + */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + { readonly current: TSelected }, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/svelte-store/src/index.svelte.ts b/packages/svelte-store/src/index.svelte.ts index ea4df8ce..9ae7245d 100644 --- a/packages/svelte-store/src/index.svelte.ts +++ b/packages/svelte-store/src/index.svelte.ts @@ -1,86 +1,8 @@ -import type { Atom, ReadonlyAtom } from '@tanstack/store' - export * from '@tanstack/store' -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn -} - -export function useStore>( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, -): { readonly current: TSelected } { - const equal = options.equal ?? shallow - let slice = $state(selector(store.get())) - - $effect(() => { - const unsub = store.subscribe((s) => { - const data = selector(s) - if (equal(slice, data)) { - return - } - slice = data - }).unsubscribe - - return unsub - }) - - return { - get current() { - return slice - }, - } -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } +export * from './useSelector.svelte.js' +export * from './useValue' - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector +export * from './_useStore' diff --git a/packages/svelte-store/src/useAtom.ts b/packages/svelte-store/src/useAtom.ts new file mode 100644 index 00000000..5fcbbeaa --- /dev/null +++ b/packages/svelte-store/src/useAtom.ts @@ -0,0 +1,25 @@ +import { useValue } from './useValue' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector.svelte.js' + +/** + * Returns the current atom holder together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * const [count, setCount] = useAtom(countAtom) + * setCount((prev) => prev + 1) + * console.log(count.current) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [{ readonly current: TValue }, Atom['set']] { + const value = useValue(atom, options) + + return [value, atom.set] +} diff --git a/packages/svelte-store/src/useSelector.svelte.ts b/packages/svelte-store/src/useSelector.svelte.ts new file mode 100644 index 00000000..82a72fb1 --- /dev/null +++ b/packages/svelte-store/src/useSelector.svelte.ts @@ -0,0 +1,57 @@ +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and exposes it through a + * rune-friendly holder object. + * + * Read the selected value from `.current`. + * + * @example + * ```ts + * const count = useSelector(counterStore, (state) => state.count) + * console.log(count.current) + * ``` + * + * @example + * ```ts + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector>( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + selector: (state: NoInfer) => TSelected = (d) => d as any, + options: UseSelectorOptions = {}, +): { readonly current: TSelected } { + const compare = options.compare ?? defaultCompare + let slice = $state(selector(source.get())) + + $effect(() => { + const unsub = source.subscribe((s) => { + const data = selector(s) + if (compare(slice, data)) { + return + } + slice = data + }).unsubscribe + + return unsub + }) + + return { + get current() { + return slice + }, + } +} diff --git a/packages/svelte-store/src/useStore.ts b/packages/svelte-store/src/useStore.ts new file mode 100644 index 00000000..b3a5642b --- /dev/null +++ b/packages/svelte-store/src/useStore.ts @@ -0,0 +1,23 @@ +import { useSelector } from './useSelector.svelte.js' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```ts + * const count = useStore(counterStore, (state) => state.count) + * console.log(count.current) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = >( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + selector: (state: NoInfer) => TSelected = (d) => d as any, + compare?: (a: TSelected, b: TSelected) => boolean, +) => useSelector(source, selector, { compare }) diff --git a/packages/svelte-store/src/useValue.ts b/packages/svelte-store/src/useValue.ts new file mode 100644 index 00000000..95765c4a --- /dev/null +++ b/packages/svelte-store/src/useValue.ts @@ -0,0 +1,29 @@ +import { useSelector } from './useSelector.svelte.js' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector.svelte.js' + +/** + * Subscribes to an atom or store and returns its whole current value through a + * rune-friendly holder object. + * + * @example + * ```ts + * const count = useValue(countAtom) + * console.log(count.current) + * ``` + * + * @example + * ```ts + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): { readonly current: TValue } { + return useSelector(source, (value) => value, options) +} diff --git a/packages/svelte-store/tests/BaseStore.test.svelte b/packages/svelte-store/tests/BaseStore.test.svelte index 529e410f..aef07158 100644 --- a/packages/svelte-store/tests/BaseStore.test.svelte +++ b/packages/svelte-store/tests/BaseStore.test.svelte @@ -1,13 +1,13 @@

Store: {storeVal.current}

diff --git a/packages/svelte-store/tests/Render.test.svelte b/packages/svelte-store/tests/Render.test.svelte index 7700c555..baf6381a 100644 --- a/packages/svelte-store/tests/Render.test.svelte +++ b/packages/svelte-store/tests/Render.test.svelte @@ -1,14 +1,14 @@ + +
+

Value: {value.current}

+

Readonly: {readonlyValue.current.value}

+ +
diff --git a/packages/svelte-store/tests/index.test.ts b/packages/svelte-store/tests/index.test.ts index 24f7a3dd..158fc0ef 100644 --- a/packages/svelte-store/tests/index.test.ts +++ b/packages/svelte-store/tests/index.test.ts @@ -4,10 +4,11 @@ import { userEvent } from '@testing-library/user-event' import { shallow } from '../src/index.svelte.js' import TestBaseStore from './BaseStore.test.svelte' import TestRerender from './Render.test.svelte' +import TestValue from './Value.test.svelte' const user = userEvent.setup() -describe('useStore', () => { +describe('useSelector', () => { it('allows us to select state using a selector', () => { const { getByText } = render(TestBaseStore) expect(getByText('Store: 0')).toBeInTheDocument() @@ -26,6 +27,17 @@ describe('useStore', () => { await user.click(getByText('Update ignored')) expect(getByText('Number rendered: 2')).toBeInTheDocument() }) + + it('useValue reads writable and readonly store state', async () => { + const { getByText } = render(TestValue) + expect(getByText('Value: 1')).toBeInTheDocument() + expect(getByText('Readonly: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) + }) }) describe('shallow', () => { diff --git a/packages/svelte-store/tests/test.test-d.ts b/packages/svelte-store/tests/test.test-d.ts new file mode 100644 index 00000000..d26acb20 --- /dev/null +++ b/packages/svelte-store/tests/test.test-d.ts @@ -0,0 +1,84 @@ +import { expectTypeOf, test } from 'vitest' +import { createAtom, createStore } from '@tanstack/store' +import { + _useStore, + useAtom, + useSelector, + useStore, + useValue, +} from '../src/index.svelte.js' +import type { Store } from '@tanstack/store' + +test('useSelector works with derived state', () => { + const store = createStore(12) + const derived = createStore(() => store.state * 2) + + const val = useSelector(derived, (state) => { + expectTypeOf(state).toEqualTypeOf() + return state + }) + + expectTypeOf(val).toEqualTypeOf<{ readonly current: number }>() +}) + +test('useValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toEqualTypeOf<{ + readonly current: number + }>() + expectTypeOf(useValue(readonlyAtom)).toEqualTypeOf<{ + readonly current: number + }>() + expectTypeOf(useValue(writableStore)).toEqualTypeOf<{ + readonly current: number + }>() + expectTypeOf(useValue(readonlyStore)).toEqualTypeOf<{ + readonly current: number + }>() +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + + expectTypeOf(value).toEqualTypeOf<{ readonly current: number }>() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useStore matches useSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = useSelector(store, (state) => state) + const compatValue = useStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf<{ readonly current: number }>() + expectTypeOf(compatValue).toEqualTypeOf<{ readonly current: number }>() +}) + +test('_useStore returns selected state and second tuple element for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const result = _useStore(store, (state) => state.count) + + expectTypeOf(result[0]).toEqualTypeOf<{ readonly current: number }>() + // The second element should be the actions bag + expectTypeOf(result).toBeArray() +}) + +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf<{ readonly current: number }>() + expectTypeOf(setState).toEqualTypeOf['setState']>() +}) diff --git a/packages/vue-store/package.json b/packages/vue-store/package.json index c808d19c..3fb79960 100644 --- a/packages/vue-store/package.json +++ b/packages/vue-store/package.json @@ -37,19 +37,13 @@ "build": "tsdown --tsconfig tsconfig.build.json" }, "type": "module", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.cts", "main": "./dist/index.cjs", "module": "./dist/index.js", "exports": { ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.cts", - "default": "./dist/index.cjs" - } + "import": "./dist/index.js", + "require": "./dist/index.cjs" }, "./package.json": "./package.json" }, diff --git a/packages/vue-store/src/_useStore.ts b/packages/vue-store/src/_useStore.ts new file mode 100644 index 00000000..f7401fb8 --- /dev/null +++ b/packages/vue-store/src/_useStore.ts @@ -0,0 +1,41 @@ +import { useSelector } from './useSelector' +import type { Ref } from 'vue-demi' +import type { Store, StoreActionMap } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Experimental combined read+write hook for stores, mirroring useAtom's tuple + * pattern. + * + * Returns `[selected, actions]` when the store has an actions factory, or + * `[selected, setState]` for plain stores. + * + * @example + * ```ts + * // Store with actions + * const [cats, { addCat }] = _useStore(petStore, (s) => s.cats) + * console.log(cats.value) + * + * // Store without actions + * const [count, setState] = _useStore(plainStore, (s) => s) + * setState((prev) => prev + 1) + * ``` + */ +export function _useStore< + TState, + TActions extends StoreActionMap, + TSelected = NoInfer, +>( + store: Store, + selector: (state: NoInfer) => TSelected, + options?: UseSelectorOptions, +): [ + Readonly>, + [TActions] extends [never] ? Store['setState'] : TActions, +] { + const selected = useSelector(store, selector, options) + const actionsOrSetState = + (store.actions as StoreActionMap | undefined) ?? store.setState + + return [selected, actionsOrSetState] as any +} diff --git a/packages/vue-store/src/index.ts b/packages/vue-store/src/index.ts index e8c1107c..ea10ac37 100644 --- a/packages/vue-store/src/index.ts +++ b/packages/vue-store/src/index.ts @@ -1,90 +1,8 @@ -import { readonly, ref, toRaw, watch } from 'vue-demi' -import type { Atom, ReadonlyAtom } from '@tanstack/store' -import type { Ref } from 'vue-demi' - export * from '@tanstack/store' -type EqualityFn = (objA: T, objB: T) => boolean -interface UseStoreOptions { - equal?: EqualityFn -} - -export function useStore>( - store: Atom | ReadonlyAtom, - selector: (state: NoInfer) => TSelected = (d) => d as any, - options: UseStoreOptions = {}, -): Readonly> { - const slice = ref(selector(store.get())) as Ref - const equal = options.equal ?? shallow - - watch( - () => store, - (value, _oldValue, onCleanup) => { - const unsub = value.subscribe((s) => { - const data = selector(s) - if (equal(toRaw(slice.value), data)) { - return - } - slice.value = data - }).unsubscribe - - onCleanup(() => { - unsub() - }) - }, - { immediate: true }, - ) - - return readonly(slice) as never -} - -export function shallow(objA: T, objB: T) { - if (Object.is(objA, objB)) { - return true - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false - } - - if (objA instanceof Map && objB instanceof Map) { - if (objA.size !== objB.size) return false - for (const [k, v] of objA) { - if (!objB.has(k) || !Object.is(v, objB.get(k))) return false - } - return true - } - - if (objA instanceof Set && objB instanceof Set) { - if (objA.size !== objB.size) return false - for (const v of objA) { - if (!objB.has(v)) return false - } - return true - } - - if (objA instanceof Date && objB instanceof Date) { - if (objA.getTime() !== objB.getTime()) return false - return true - } - - const keysA = Object.keys(objA) - if (keysA.length !== Object.keys(objB).length) { - return false - } +export * from './useValue' +export * from './useSelector' - for (let i = 0; i < keysA.length; i++) { - if ( - !Object.prototype.hasOwnProperty.call(objB, keysA[i] as string) || - !Object.is(objA[keysA[i] as keyof T], objB[keysA[i] as keyof T]) - ) { - return false - } - } - return true -} +export * from './useAtom' +export * from './useStore' // @deprecated in favor of useSelector +export * from './_useStore' diff --git a/packages/vue-store/src/useAtom.ts b/packages/vue-store/src/useAtom.ts new file mode 100644 index 00000000..673441a3 --- /dev/null +++ b/packages/vue-store/src/useAtom.ts @@ -0,0 +1,27 @@ +import { useValue } from './useValue' +import type { Ref } from 'vue-demi' +import type { Atom } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Returns the current atom ref together with a setter. + * + * Use this when a component needs to both read and update the same writable + * atom. + * + * @example + * ```ts + * const [count, setCount] = useAtom(countAtom) + * + * setCount((prev) => prev + 1) + * console.log(count.value) + * ``` + */ +export function useAtom( + atom: Atom, + options?: UseSelectorOptions, +): [Readonly>, Atom['set']] { + const value = useValue(atom, options) + + return [value, atom.set] +} diff --git a/packages/vue-store/src/useSelector.ts b/packages/vue-store/src/useSelector.ts new file mode 100644 index 00000000..a557a960 --- /dev/null +++ b/packages/vue-store/src/useSelector.ts @@ -0,0 +1,57 @@ +import { onScopeDispose, readonly, shallowRef, toRaw } from 'vue-demi' +import type { Ref } from 'vue-demi' + +export interface UseSelectorOptions { + compare?: (a: TSelected, b: TSelected) => boolean +} + +type SelectionSource = { + get: () => T + subscribe: (listener: (value: T) => void) => { + unsubscribe: () => void + } +} + +function defaultCompare(a: T, b: T) { + return a === b +} + +/** + * Selects a slice of state from an atom or store and subscribes the component + * to that selection. + * + * This is the primary Vue read hook for TanStack Store. It returns a readonly + * ref containing the selected value. + * + * @example + * ```ts + * const count = useSelector(counterStore, (state) => state.count) + * console.log(count.value) + * ``` + * + * @example + * ```ts + * const doubled = useSelector(countAtom, (value) => value * 2) + * ``` + */ +export function useSelector( + source: SelectionSource, + selector: (snapshot: TSource) => TSelected, + options?: UseSelectorOptions, +): Readonly> { + const compare = options?.compare ?? defaultCompare + const slice = shallowRef(selector(source.get())) as Ref + const unsubscribe = source.subscribe((snapshot) => { + const selected = selector(snapshot) + if (compare(toRaw(slice.value), selected)) { + return + } + slice.value = selected + }).unsubscribe + + onScopeDispose(() => { + unsubscribe() + }) + + return readonly(slice) as Readonly> +} diff --git a/packages/vue-store/src/useStore.ts b/packages/vue-store/src/useStore.ts new file mode 100644 index 00000000..2883074c --- /dev/null +++ b/packages/vue-store/src/useStore.ts @@ -0,0 +1,23 @@ +import { useSelector } from './useSelector' +import type { Ref } from 'vue-demi' + +/** + * Deprecated alias for {@link useSelector}. + * + * @example + * ```ts + * const count = useStore(counterStore, (state) => state.count) + * ``` + * + * @deprecated Use `useSelector` instead. + */ +export const useStore = ( + source: { + get: () => TSource + subscribe: (listener: (value: TSource) => void) => { + unsubscribe: () => void + } + }, + selector: (snapshot: TSource) => TSelected, + compare?: (a: TSelected, b: TSelected) => boolean, +): Readonly> => useSelector(source, selector, { compare }) diff --git a/packages/vue-store/src/useValue.ts b/packages/vue-store/src/useValue.ts new file mode 100644 index 00000000..ef24720b --- /dev/null +++ b/packages/vue-store/src/useValue.ts @@ -0,0 +1,32 @@ +import { useSelector } from './useSelector' +import type { Ref } from 'vue-demi' +import type { Atom, ReadonlyAtom, ReadonlyStore, Store } from '@tanstack/store' +import type { UseSelectorOptions } from './useSelector' + +/** + * Subscribes to an atom or store and returns its current value ref. + * + * This is the whole-value counterpart to {@link useSelector}. Use it when the + * component needs the entire current value from a source. + * + * @example + * ```ts + * const count = useValue(countAtom) + * console.log(count.value) + * ``` + * + * @example + * ```ts + * const state = useValue(counterStore) + * ``` + */ +export function useValue( + source: + | Atom + | ReadonlyAtom + | Store + | ReadonlyStore, + options?: UseSelectorOptions, +): Readonly> { + return useSelector(source, (value) => value, options) +} diff --git a/packages/vue-store/tests/index.test.tsx b/packages/vue-store/tests/index.test.tsx index a81df786..c5d36a97 100644 --- a/packages/vue-store/tests/index.test.tsx +++ b/packages/vue-store/tests/index.test.tsx @@ -1,13 +1,76 @@ import { describe, expect, it, test, vi } from 'vitest' import { defineComponent, h } from 'vue-demi' import { render, waitFor } from '@testing-library/vue' -import { createStore } from '@tanstack/store' +import { createAtom, createStore } from '@tanstack/store' import { userEvent } from '@testing-library/user-event' -import { shallow, useStore } from '../src/index' +import { + _useStore, + shallow, + useAtom, + useSelector, + useStore, + useValue, +} from '../src/index' const user = userEvent.setup() -describe('useStore', () => { +describe('atom hooks', () => { + it('useValue reads mutable atom state and rerenders when updated', async () => { + const atom = createAtom(0) + + const Comp = defineComponent(() => { + const value = useValue(atom) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => atom.set((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('useAtom returns the current ref and setter', async () => { + const atom = createAtom(0) + + const Comp = defineComponent(() => { + const [value, setValue] = useAtom(atom) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => setValue((prev) => prev + 5), + }, + 'Add 5', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Add 5')) + + await waitFor(() => expect(getByText('Value: 5')).toBeInTheDocument()) + }) +}) + +describe('store hooks', () => { it('allows us to select state using a selector', () => { const store = createStore({ select: 0, @@ -15,7 +78,7 @@ describe('useStore', () => { }) const Comp = defineComponent(() => { - const storeVal = useStore(store, (state) => state.select) + const storeVal = useSelector(store, (state) => state.select) return () => h('p', `Store: ${storeVal.value}`) }) @@ -24,6 +87,38 @@ describe('useStore', () => { expect(getByText('Store: 0')).toBeInTheDocument() }) + it('useValue reads writable and readonly store state', async () => { + const baseStore = createStore(1) + const readonlyStore = createStore(() => ({ value: baseStore.state * 2 })) + + const Comp = defineComponent(() => { + const value = useValue(baseStore) + const readonlyValue = useValue(readonlyStore) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h('p', `Readonly: ${readonlyValue.value.value}`), + h( + 'button', + { + onClick: () => baseStore.setState((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 1')).toBeInTheDocument() + expect(getByText('Readonly: 2')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 2')).toBeInTheDocument()) + await waitFor(() => expect(getByText('Readonly: 4')).toBeInTheDocument()) + }) + it('only triggers a re-render when selector state is updated', async () => { const store = createStore({ select: 0, @@ -31,8 +126,7 @@ describe('useStore', () => { }) const Comp = defineComponent(() => { - const storeVal = useStore(store, (state) => state.select) - + const storeVal = useSelector(store, (state) => state.select) const fn = vi.fn() return () => { @@ -78,6 +172,207 @@ describe('useStore', () => { await user.click(getByText('Update ignored')) expect(getByText('Number rendered: 2')).toBeInTheDocument() }) + + it('useSelector allows specifying a custom equality function', async () => { + const store = createStore({ + array: [ + { select: 0, ignore: 1 }, + { select: 0, ignore: 1 }, + ], + }) + + const Comp = defineComponent(() => { + const storeVal = useSelector( + store, + (state) => state.array.map(({ ignore, ...rest }) => rest), + { + compare: (prev, next) => + JSON.stringify(prev) === JSON.stringify(next), + }, + ) + const fn = vi.fn() + + return () => { + fn() + const value = storeVal.value + .map((item) => item.select) + .reduce((total, num) => total + num, 0) + + return h('div', [ + h('p', `Number rendered: ${fn.mock.calls.length}`), + h('p', `Store: ${value}`), + h( + 'button', + { + onClick: () => + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + select: item.select + 5, + })), + })), + }, + 'Update select', + ), + h( + 'button', + { + onClick: () => + store.setState((v) => ({ + array: v.array.map((item) => ({ + ...item, + ignore: item.ignore + 2, + })), + })), + }, + 'Update ignored', + ), + ]) + } + }) + + const { getByText } = render(Comp) + expect(getByText('Store: 0')).toBeInTheDocument() + expect(getByText('Number rendered: 1')).toBeInTheDocument() + + await user.click(getByText('Update select')) + + await waitFor(() => expect(getByText('Store: 10')).toBeInTheDocument()) + expect(getByText('Number rendered: 2')).toBeInTheDocument() + + await user.click(getByText('Update ignored')) + expect(getByText('Number rendered: 2')).toBeInTheDocument() + }) + + it('useSelector works with mounted derived stores', async () => { + const store = createStore(0) + const derived = createStore(() => ({ val: store.state * 2 })) + + const Comp = defineComponent(() => { + const derivedVal = useSelector(derived, (state) => state.val) + + return () => + h('div', [ + h('p', `Derived: ${derivedVal.value}`), + h( + 'button', + { + onClick: () => store.setState((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Derived: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Derived: 2')).toBeInTheDocument()) + }) +}) + +describe('useStore', () => { + it('is a compatibility alias for useSelector', async () => { + const store = createStore(0) + + const Comp = defineComponent(() => { + const value = useStore(store, (state) => state) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => store.setState((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) + + it('supports atom sources through the deprecated alias', async () => { + const atom = createAtom(0) + + const Comp = defineComponent(() => { + const value = useStore(atom, (state) => state) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h( + 'button', + { + onClick: () => atom.set((prev) => prev + 1), + }, + 'Update', + ), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Update')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) +}) + +describe('_useStore', () => { + it('returns selected state and actions for stores with actions', async () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const Comp = defineComponent(() => { + const [count, { inc }] = _useStore(store, (state) => state.count) + + return () => + h('div', [ + h('p', `Count: ${count.value}`), + h('button', { onClick: () => inc() }, 'Inc'), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Count: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Count: 1')).toBeInTheDocument()) + }) + + it('returns selected state and setState for plain stores', async () => { + const store = createStore(0) + + const Comp = defineComponent(() => { + const [value, setState] = _useStore(store, (state) => state) + + return () => + h('div', [ + h('p', `Value: ${value.value}`), + h('button', { onClick: () => setState((prev) => prev + 1) }, 'Inc'), + ]) + }) + + const { getByText } = render(Comp) + expect(getByText('Value: 0')).toBeInTheDocument() + + await user.click(getByText('Inc')) + + await waitFor(() => expect(getByText('Value: 1')).toBeInTheDocument()) + }) }) describe('shallow', () => { @@ -132,6 +427,20 @@ describe('shallow', () => { expect(shallow(objA, objB)).toBe(false) }) + test('should return true for shallow equal objects with symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 1 } + expect(shallow(objA, objB)).toBe(true) + }) + + test('should return false for shallow different values for symbol keys', () => { + const sym = Symbol.for('key') + const objA = { [sym]: 1 } + const objB = { [sym]: 2 } + expect(shallow(objA, objB)).toBe(false) + }) + test('should return true for shallowly equal maps', () => { const objA = new Map([['1', 'hello']]) const objB = new Map([['1', 'hello']]) diff --git a/packages/vue-store/tests/test.test-d.ts b/packages/vue-store/tests/test.test-d.ts index 5b0108b3..f224628f 100644 --- a/packages/vue-store/tests/test.test-d.ts +++ b/packages/vue-store/tests/test.test-d.ts @@ -1,16 +1,70 @@ import { expectTypeOf, test } from 'vitest' -import { createStore } from '@tanstack/store' -import { useStore } from '../src' +import { createAtom, createStore } from '@tanstack/store' +import { _useStore, useAtom, useSelector, useStore, useValue } from '../src' import type { Ref } from 'vue-demi' +import type { Store } from '@tanstack/store' -test('useStore works with derived state', () => { +test('useSelector works with derived state', () => { const store = createStore(12) const derived = createStore(() => store.state * 2) - const val = useStore(derived, (state) => { + const val = useSelector(derived, (state) => { expectTypeOf(state).toEqualTypeOf() return state }) expectTypeOf(val).toEqualTypeOf>>() }) + +test('useValue infers value from mutable and readonly sources', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + const writableStore = createStore(12) + const readonlyStore = createStore(() => 24) + + expectTypeOf(useValue(writableAtom)).toEqualTypeOf>>() + expectTypeOf(useValue(readonlyAtom)).toEqualTypeOf>>() + expectTypeOf(useValue(writableStore)).toEqualTypeOf>>() + expectTypeOf(useValue(readonlyStore)).toEqualTypeOf>>() +}) + +test('useAtom only accepts writable atoms', () => { + const writableAtom = createAtom(12) + const readonlyAtom = createAtom(() => 24) + + const [value, setValue] = useAtom(writableAtom) + + expectTypeOf(value).toEqualTypeOf>>() + expectTypeOf(setValue).toBeFunction() + // @ts-expect-error readonly atoms cannot be used with useAtom + useAtom(readonlyAtom) +}) + +test('useStore matches useSelector types for compatibility', () => { + const store = createStore(12) + const selectorValue = useSelector(store, (state) => state) + const compatValue = useStore(store, (state) => state) + + expectTypeOf(selectorValue).toEqualTypeOf>>() + expectTypeOf(compatValue).toEqualTypeOf>>() +}) + +test('_useStore returns actions for stores with actions', () => { + const store = createStore({ count: 0 }, ({ setState }) => ({ + inc: () => setState((prev) => ({ count: prev.count + 1 })), + })) + + const [selected, actions] = _useStore(store, (state) => state.count) + + expectTypeOf(selected).toEqualTypeOf>>() + expectTypeOf(actions.inc).toBeFunction() +}) + +test('_useStore returns setState for plain stores', () => { + const store = createStore(0) + + const [selected, setState] = _useStore(store, (state) => state) + + expectTypeOf(selected).toEqualTypeOf>>() + expectTypeOf(setState).toEqualTypeOf['setState']>() +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87e9a8f6..f8c59b6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: devDependencies: '@changesets/cli': specifier: ^2.30.0 - version: 2.30.0(@types/node@25.5.2) + version: 2.30.0(@types/node@25.6.0) '@eslint-react/eslint-plugin': specifier: ^4.2.3 version: 4.2.3(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) @@ -32,8 +32,8 @@ importers: specifier: ^14.6.1 version: 14.6.1(@testing-library/dom@10.4.1) '@types/node': - specifier: ^25.5.2 - version: 25.5.2 + specifier: ^25.6.0 + version: 25.6.0 '@vitest/coverage-istanbul': specifier: ^4.1.4 version: 4.1.4(vitest@4.1.4) @@ -47,23 +47,23 @@ importers: specifier: ^29.0.2 version: 29.0.2 knip: - specifier: ^6.3.1 - version: 6.3.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + specifier: ^6.4.0 + version: 6.4.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) markdown-link-extractor: specifier: ^4.0.3 version: 4.0.3 nx: - specifier: 22.6.4 - version: 22.6.4 + specifier: 22.6.5 + version: 22.6.5 premove: specifier: ^4.0.0 version: 4.0.0 prettier: - specifier: ^3.8.1 - version: 3.8.1 + specifier: ^3.8.2 + version: 3.8.2 prettier-plugin-svelte: specifier: ^3.5.1 - version: 3.5.1(prettier@3.8.1)(svelte@5.55.2) + version: 3.5.1(prettier@3.8.2)(svelte@5.55.3) publint: specifier: ^0.3.18 version: 0.3.18 @@ -90,10 +90,59 @@ importers: version: typescript@5.8.3 vite: specifier: ^8.0.8 - version: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: ^4.1.4 - version: 4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/angular/atoms: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 examples/angular/simple: dependencies: @@ -133,10 +182,108 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^21.2.7 - version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + + examples/angular/store-actions: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + + examples/angular/store-context: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) '@angular/cli': specifier: ^21.2.7 - version: 21.2.7(@types/node@25.5.2)(chokidar@5.0.0) + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) '@angular/compiler-cli': specifier: ^21.2.8 version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) @@ -144,63 +291,397 @@ importers: specifier: ^6.0.2 version: 6.0.2 - examples/preact/simple: + examples/angular/stores: + dependencies: + '@angular/animations': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': + specifier: ^21.2.8 + version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.8 + version: 21.2.8 + '@angular/core': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': + specifier: ^21.2.8 + version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/router': + specifier: ^21.2.8 + version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@tanstack/angular-store': + specifier: ^0.10.0 + version: link:../../../packages/angular-store + rxjs: + specifier: ^7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ^0.16.1 + version: 0.16.1 + devDependencies: + '@angular-devkit/build-angular': + specifier: ^21.2.7 + version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/cli': + specifier: ^21.2.7 + version: 21.2.7(@types/node@25.6.0)(chokidar@5.0.0) + '@angular/compiler-cli': + specifier: ^21.2.8 + version: 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) + typescript: + specifier: ^6.0.2 + version: 6.0.2 + + examples/preact/atoms: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/preact/simple: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/preact/store-actions: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/preact/store-context: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/preact/stores: + dependencies: + '@tanstack/preact-store': + specifier: ^0.12.0 + version: link:../../../packages/preact-store + preact: + specifier: ^10.29.1 + version: 10.29.1 + devDependencies: + '@preact/preset-vite': + specifier: ^2.10.5 + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@types/node': + specifier: ^25.6.0 + version: 25.6.0 + eslint: + specifier: ^10.2.0 + version: 10.2.0(jiti@2.6.1) + eslint-config-preact: + specifier: ^2.0.0 + version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/react/atoms: + dependencies: + '@tanstack/react-store': + specifier: ^0.10.0 + version: link:../../../packages/react-store + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/react/simple: + dependencies: + '@tanstack/react-store': + specifier: ^0.10.0 + version: link:../../../packages/react-store + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/react/store-actions: + dependencies: + '@tanstack/react-store': + specifier: ^0.10.0 + version: link:../../../packages/react-store + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/react/store-context: + dependencies: + '@tanstack/react-store': + specifier: ^0.10.0 + version: link:../../../packages/react-store + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/react/stores: + dependencies: + '@tanstack/react-store': + specifier: ^0.10.0 + version: link:../../../packages/react-store + react: + specifier: ^19.2.5 + version: 19.2.5 + react-dom: + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) + devDependencies: + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/solid/atoms: + dependencies: + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 + devDependencies: + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/solid/simple: + dependencies: + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 + devDependencies: + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/solid/store-actions: dependencies: - '@tanstack/preact-store': - specifier: ^0.12.0 - version: link:../../../packages/preact-store - preact: - specifier: ^10.29.1 - version: 10.29.1 + '@tanstack/solid-store': + specifier: ^0.10.0 + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 devDependencies: - '@preact/preset-vite': - specifier: ^2.10.5 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) - '@types/node': - specifier: ^25.5.2 - version: 25.5.2 - eslint: - specifier: ^10.2.0 - version: 10.2.0(jiti@2.6.1) - eslint-config-preact: - specifier: ^2.0.0 - version: 2.0.0(eslint@10.2.0(jiti@2.6.1)) typescript: specifier: 6.0.2 version: 6.0.2 vite: specifier: ^8.0.8 - version: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) - examples/react/simple: + examples/solid/store-context: dependencies: - '@tanstack/react-store': + '@tanstack/solid-store': specifier: ^0.10.0 - version: link:../../../packages/react-store - react: - specifier: ^19.2.5 - version: 19.2.5 - react-dom: - specifier: ^19.2.5 - version: 19.2.5(react@19.2.5) + version: link:../../../packages/solid-store + solid-js: + specifier: ^1.9.12 + version: 1.9.12 devDependencies: - '@types/react': - specifier: ^19.2.14 - version: 19.2.14 - '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.14) - '@vitejs/plugin-react': - specifier: ^6.0.1 - version: 6.0.1(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) - react-scan: - specifier: ^0.5.3 - version: 0.5.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1) + typescript: + specifier: 6.0.2 + version: 6.0.2 vite: specifier: ^8.0.8 - version: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-plugin-solid: + specifier: ^2.11.12 + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) - examples/solid/simple: + examples/solid/stores: dependencies: '@tanstack/solid-store': specifier: ^0.10.0 @@ -214,10 +695,38 @@ importers: version: 6.0.2 vite: specifier: ^8.0.8 - version: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-solid: specifier: ^2.11.12 - version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + + examples/svelte/atoms: + dependencies: + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) examples/svelte/simple: dependencies: @@ -227,16 +736,100 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^7.0.0 - version: 7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/svelte/store-actions: + dependencies: + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/svelte/store-context: + dependencies: + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@tsconfig/svelte': + specifier: ^5.0.8 + version: 5.0.8 + svelte: + specifier: ^5.55.3 + version: 5.55.3 + svelte-check: + specifier: ^4.4.6 + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) + tslib: + specifier: ^2.8.1 + version: 2.8.1 + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/svelte/stores: + dependencies: + '@tanstack/svelte-store': + specifier: ^0.11.0 + version: link:../../../packages/svelte-store + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^7.0.0 + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@tsconfig/svelte': specifier: ^5.0.8 version: 5.0.8 svelte: - specifier: ^5.55.2 - version: 5.55.2 + specifier: ^5.55.3 + version: 5.55.3 svelte-check: specifier: ^4.4.6 - version: 4.4.6(picomatch@4.0.4)(svelte@5.55.2)(typescript@6.0.2) + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) tslib: specifier: ^2.8.1 version: 2.8.1 @@ -245,7 +838,29 @@ importers: version: 6.0.2 vite: specifier: ^8.0.8 - version: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + + examples/vue/atoms: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) examples/vue/simple: dependencies: @@ -258,13 +873,79 @@ importers: devDependencies: '@vitejs/plugin-vue': specifier: ^6.0.5 - version: 6.0.5(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) + + examples/vue/store-actions: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) + + examples/vue/store-context: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + typescript: + specifier: 6.0.2 + version: 6.0.2 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vue-tsc: + specifier: ^3.2.6 + version: 3.2.6(typescript@6.0.2) + + examples/vue/stores: + dependencies: + '@tanstack/vue-store': + specifier: ^0.10.0 + version: link:../../../packages/vue-store + vue: + specifier: ^3.5.32 + version: 3.5.32(typescript@6.0.2) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.5 + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) typescript: specifier: 6.0.2 version: 6.0.2 vite: specifier: ^8.0.8 - version: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) vue-tsc: specifier: ^3.2.6 version: 3.2.6(typescript@6.0.2) @@ -276,8 +957,8 @@ importers: version: link:../store devDependencies: '@analogjs/vite-plugin-angular': - specifier: ^2.4.3 - version: 2.4.3(b4bedc2873066b6ee314e16b886d8b9b) + specifier: ^2.4.5 + version: 2.4.5(8a49bd0b443111e2f7e24ed14753c56f) '@angular/common': specifier: ^21.2.8 version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) @@ -305,7 +986,7 @@ importers: devDependencies: '@preact/preset-vite': specifier: ^2.10.5 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@testing-library/preact': specifier: ^3.2.4 version: 3.2.4(preact@10.29.1) @@ -323,7 +1004,7 @@ importers: version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) vitest: specifier: ^4.1.4 - version: 4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) packages/react-store: dependencies: @@ -348,7 +1029,7 @@ importers: version: 1.5.0 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) react: specifier: ^19.2.5 version: 19.2.5 @@ -370,7 +1051,7 @@ importers: version: 1.9.12 vite-plugin-solid: specifier: ^2.11.12 - version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) packages/store: devDependencies: @@ -395,22 +1076,22 @@ importers: devDependencies: '@sveltejs/package': specifier: ^2.5.7 - version: 2.5.7(svelte@5.55.2)(typescript@6.0.2) + version: 2.5.7(svelte@5.55.3)(typescript@6.0.2) '@sveltejs/vite-plugin-svelte': specifier: ^7.0.0 - version: 7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@testing-library/svelte': specifier: ^5.3.1 - version: 5.3.1(svelte@5.55.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + version: 5.3.1(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) eslint-plugin-svelte: specifier: ^3.17.0 - version: 3.17.0(eslint@10.2.0(jiti@2.6.1))(svelte@5.55.2) + version: 3.17.0(eslint@10.2.0(jiti@2.6.1))(svelte@5.55.3) svelte: - specifier: ^5.55.2 - version: 5.55.2 + specifier: ^5.55.3 + version: 5.55.3 svelte-check: specifier: ^4.4.6 - version: 4.4.6(picomatch@4.0.4)(svelte@5.55.2)(typescript@6.0.2) + version: 4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2) packages/vue-store: dependencies: @@ -426,7 +1107,7 @@ importers: version: 8.1.0(@vue/compiler-sfc@3.5.32)(vue@3.5.32(typescript@6.0.2)) '@vitejs/plugin-vue': specifier: ^6.0.5 - version: 6.0.5(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) + version: 6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) '@vue/composition-api': specifier: ^1.7.2 version: 1.7.2(vue@3.5.32(typescript@6.0.2)) @@ -505,8 +1186,8 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@analogjs/vite-plugin-angular@2.4.3': - resolution: {integrity: sha512-ukGEIdT+1VfeH1sezkNk/niA/0/o5+Ew4HQtb2qDOb4rge7LTEFJ6s/Sue0MEryzptq06BVSl+SaNzQU/nJPLQ==} + '@analogjs/vite-plugin-angular@2.4.5': + resolution: {integrity: sha512-nrDV7vqbclBuACykxO5H1TBuG9G1GAT/IW7I8if1oGDHyiNNhjSs/lhxTomJXrgYeJ4SXWBnx+x+8ctb/3tpFg==} peerDependencies: '@angular-devkit/build-angular': ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 '@angular/build': ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 @@ -1389,30 +2070,15 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} - '@emnapi/core@1.9.1': - resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} - '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/runtime@1.9.1': - resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} - '@emnapi/runtime@1.9.2': resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} - '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} @@ -1425,12 +2091,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.27.3': resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} @@ -1443,12 +2103,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.27.3': resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} @@ -1461,12 +2115,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.27.3': resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} @@ -1479,12 +2127,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.27.3': resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} @@ -1497,12 +2139,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.27.3': resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} @@ -1515,12 +2151,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.27.3': resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} @@ -1533,12 +2163,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.27.3': resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} @@ -1551,12 +2175,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.27.3': resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} @@ -1569,12 +2187,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.27.3': resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} @@ -1587,12 +2199,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.27.3': resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} @@ -1605,12 +2211,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.27.3': resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} @@ -1623,12 +2223,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.27.3': resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} @@ -1641,12 +2235,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.27.3': resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} @@ -1659,12 +2247,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.27.3': resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} @@ -1677,12 +2259,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.27.3': resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} @@ -1695,12 +2271,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.27.3': resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} @@ -1713,12 +2283,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.27.3': resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} @@ -1731,12 +2295,6 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.27.3': resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} @@ -1749,12 +2307,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.27.3': resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} @@ -1767,12 +2319,6 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.27.3': resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} @@ -1785,12 +2331,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.27.3': resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} @@ -1803,12 +2343,6 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.27.3': resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} @@ -1821,12 +2355,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.27.3': resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} @@ -1839,12 +2367,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.27.3': resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} @@ -1857,12 +2379,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.27.3': resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} engines: {node: '>=18'} @@ -2343,9 +2859,6 @@ packages: cpu: [x64] os: [win32] - '@ltd/j-toml@1.38.0': - resolution: {integrity: sha512-lYtBcmvHustHQtg4X7TXUu1Xa/tbLC3p2wLvgQI+fWVySguVZJF60Snxijw5EiohumxZbR10kWYFFebh1zotiw==} - '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -2593,57 +3106,57 @@ packages: resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} engines: {node: ^20.17.0 || >=22.9.0} - '@nx/nx-darwin-arm64@22.6.4': - resolution: {integrity: sha512-KuUQ9t8pxIO+Px1kbjA0XDLOU6XoAsijl0ssIMRYN1w5ly+0k/KglWt7qgwDockkaLRHkQ3YSR8I2LJXJE+Vig==} + '@nx/nx-darwin-arm64@22.6.5': + resolution: {integrity: sha512-qT77Omkg5xQuL2+pDbneX2tI+XW5ZeayMylu7UUgK8OhTrAkJLKjpuYRH4xT5XBipxbDtlxmO3aLS3Ib1pKzJQ==} cpu: [arm64] os: [darwin] - '@nx/nx-darwin-x64@22.6.4': - resolution: {integrity: sha512-FB2XL2+ixbRI1fddz4oW+9MhoJASoTD8Ai4q5+B1OUPftgarIPLxaqI8TWba30Bos2AiYDofMJPf9uhBmLDH5Q==} + '@nx/nx-darwin-x64@22.6.5': + resolution: {integrity: sha512-9jICxb7vfJ56y/7Yuh3b/n1QJqWxO9xnXKYEs6SO8xPoW/KomVckILGc1C6RQSs6/3ixVJC7k1Dh1wm5tKPFrg==} cpu: [x64] os: [darwin] - '@nx/nx-freebsd-x64@22.6.4': - resolution: {integrity: sha512-qNsXhlflc77afjcRKCn7bqI8l/HPEjKhQRFs8wfKbAfNw3XEASc0EZtBV/TStLGV6PEZQldVBaId5FBMp8GW6Q==} + '@nx/nx-freebsd-x64@22.6.5': + resolution: {integrity: sha512-6B1wEKpqz5dI3AGMqttAVnA6M3DB/besAtuGyQiymK9ROlta1iuWgCcIYwcCQyhLn2Rx7vqj447KKcgCa8HlVw==} cpu: [x64] os: [freebsd] - '@nx/nx-linux-arm-gnueabihf@22.6.4': - resolution: {integrity: sha512-rjfnii0xGe8SQqsO/DDHeJSjbqp2H5fOEgZlaYXDGOwQeLZ1TQplEdx8hyI/ErAUwVO3YHnzoMtmachBQOlspw==} + '@nx/nx-linux-arm-gnueabihf@22.6.5': + resolution: {integrity: sha512-xV50B8mnDPboct7JkAHftajI02s+8FszA8WTzhore+YGR+lEKHTLpucwGEaQuMlSdLplH7pQix4B4uK5pcMhZw==} cpu: [arm] os: [linux] - '@nx/nx-linux-arm64-gnu@22.6.4': - resolution: {integrity: sha512-x6Zim1STewCXuHBCgoy2TO0586UlwH4RNCobn0mTiPd1jt7nU+fNqo3SpY8RzY1KmBfgcO48BBrfykPE9YWMpg==} + '@nx/nx-linux-arm64-gnu@22.6.5': + resolution: {integrity: sha512-2JkWuMGj+HpW6oPAvU5VdAx1afTnEbiM10Y3YOrl3fipWV4BiP5VDx762QTrfCraP4hl6yqTgvTe7F9xaby+jQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@nx/nx-linux-arm64-musl@22.6.4': - resolution: {integrity: sha512-vYOqdgXIhtHFWdtnonp/jFfmfkyNPTu1JEdXuJpSxwUQdV2dWqS/l3HVPVWHXDrVKofPafK3M72jMvoWoaOQ6g==} + '@nx/nx-linux-arm64-musl@22.6.5': + resolution: {integrity: sha512-Z/zMqFClnEyqDXouJKEPoWVhMQIif5F0YuECWBYjd3ZLwQsXGTItoh+6Wm3XF/nGMA2uLOHyTq/X7iFXQY3RzA==} cpu: [arm64] os: [linux] libc: [musl] - '@nx/nx-linux-x64-gnu@22.6.4': - resolution: {integrity: sha512-UfWUDlOzlvQNVa1mnqOFxzvUwoGfM2o9ruhwYRoFm3XJbVYnjINyQsdcHwwDJItJP04LZzLPxA1+O8sU+Oqg6A==} + '@nx/nx-linux-x64-gnu@22.6.5': + resolution: {integrity: sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==} cpu: [x64] os: [linux] libc: [glibc] - '@nx/nx-linux-x64-musl@22.6.4': - resolution: {integrity: sha512-dwXpcyin4ScD5gH9FdhiNnOqFXclXLFBDTyRCEOlRUbOPayF9YEcH0PPIf9uWmwP3tshhAdr5sg9DLN+r7M3xg==} + '@nx/nx-linux-x64-musl@22.6.5': + resolution: {integrity: sha512-RVOe2qcwhoIx6mxQURPjUfAW5SEOmT2gdhewvdcvX9ICq1hj5B2VarmkhTg0qroO7xiyqOqwq26mCzoV2I3NgQ==} cpu: [x64] os: [linux] libc: [musl] - '@nx/nx-win32-arm64-msvc@22.6.4': - resolution: {integrity: sha512-KqjJbFWhKJaKjET3Ep8hltXPizO0EstF4yfmp3oepWVn11poagc2MT1pf/tnRf6cdD88wd0bmw/83Ng6WUQ3Uw==} + '@nx/nx-win32-arm64-msvc@22.6.5': + resolution: {integrity: sha512-ZqurqI8VuYnsr2Kn4K4t+Gx6j/BZdf6qz/6Tv4A7XQQ6oNYVQgTqoNEFj+CCkVaIe6aIdCWpousFLqs+ZgBqYQ==} cpu: [arm64] os: [win32] - '@nx/nx-win32-x64-msvc@22.6.4': - resolution: {integrity: sha512-CIL9m6uilGGr/eU+41/+aVWUnEcq+j1EDynUX2A4InLTbAN0ylte4Af+72mvipNiqJgDkjKaNzOCQDnp8QBjEQ==} + '@nx/nx-win32-x64-msvc@22.6.5': + resolution: {integrity: sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==} cpu: [x64] os: [win32] @@ -3035,11 +3548,6 @@ packages: '@preact/signals-core@1.14.1': resolution: {integrity: sha512-vxPpfXqrwUe9lpjqfYNjAF/0RF/eFGeLgdJzdmIIZjpOnTmGmAB4BjWone562mJGMRP4frU6iZ6ei3PDsu52Ng==} - '@preact/signals@1.3.4': - resolution: {integrity: sha512-TPMkStdT0QpSc8FpB63aOwXoSiZyIrPsP9Uj347KopdS6olZdAYeeird/5FZv/M1Yc1ge5qstub2o8VDbvkT4g==} - peerDependencies: - preact: 10.x - '@preact/signals@2.9.0': resolution: {integrity: sha512-hYrY0KyUqkDgOl1qba/JGn6y81pXnurn21PMaxfcMwdncdZ3M/oVdmpTvEnsGjh48dIwDVc7bjWHqIsngSjYug==} peerDependencies: @@ -3782,11 +4290,8 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@20.19.39': - resolution: {integrity: sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==} - - '@types/node@25.5.2': - resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} '@types/qs@6.15.0': resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} @@ -4410,9 +4915,6 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -4427,8 +4929,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.12.0: - resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==} + axios@1.15.0: + resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -4516,11 +5018,6 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bippy@0.5.39: - resolution: {integrity: sha512-8hE8rKSl8JWyeaY+JjpnmceWAZPpLEyzOZQpWXM5Rc7861c5WotMJHy2aRZKZrGA8nMpvLNF01t4yQQ+HcZG3w==} - peerDependencies: - react: '>=17.0.1' - birecord@0.1.1: resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} @@ -5044,9 +5541,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} + ejs@5.0.1: + resolution: {integrity: sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==} + engines: {node: '>=0.12.18'} hasBin: true electron-to-chromium@1.5.330: @@ -5164,11 +5661,6 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.27.3: resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} @@ -5487,9 +5979,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - filelist@1.0.6: - resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -6138,11 +6627,6 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jake@10.9.4: - resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} - engines: {node: '>=10'} - hasBin: true - jest-diff@30.3.0: resolution: {integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -6246,16 +6730,12 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - knip@6.3.1: - resolution: {integrity: sha512-22kLJloVcOVOAudCxlFOC0ICAMme7dKsS7pVTEnrmyKGpswb8ieznvAiSKUeFVDJhb01ect6dkDc1Ha1g1sPpg==} + knip@6.4.0: + resolution: {integrity: sha512-SAEeggehgkPdoLZWVEcFKzPw+vNlnrUBDqcX8cOcHGydRInSn5pnn9LN3dDJ8SkDHKXR7xYzNq3HtRJaYmxOHg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -6596,10 +7076,6 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} - minimatch@9.0.9: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} @@ -6805,8 +7281,8 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nx@22.6.4: - resolution: {integrity: sha512-WEaCnLKeO9RhQAOBMfXgYO/Lx5wL4ARCtRGiYCjJtAJIZ5kcVn4uPKL2Xz1xekpF7ef/+YNrUQSrblx47Ms9Rg==} + nx@22.6.5: + resolution: {integrity: sha512-VRKhDAt684dXNSz9MNjE7MekkCfQF41P2PSx5jEWQjDEP1Z4jFZbyeygWs5ZyOroG7/n0MoWAJTe6ftvIcBOAg==} hasBin: true peerDependencies: '@swc-node/register': ^1.11.1 @@ -7175,8 +7651,8 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.2: + resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} engines: {node: '>=14'} hasBin: true @@ -7199,10 +7675,6 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -7213,8 +7685,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -7282,13 +7755,6 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-scan@0.5.3: - resolution: {integrity: sha512-qde9PupmUf0L3MU1H6bjmoukZNbCXdMyTEwP4Gh8RQ4rZPd2GGNBgEKWszwLm96E8k+sGtMpc0B9P0KyFDP6Bw==} - hasBin: true - 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 - react@19.2.5: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} @@ -7711,9 +8177,6 @@ packages: simple-code-frame@1.3.0: resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -7929,8 +8392,8 @@ packages: svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 typescript: ^4.9.4 || ^5.0.0 - svelte@5.55.2: - resolution: {integrity: sha512-z41M/hi0ZPTzrwVKLvB/R1/Oo08gL1uIib8HZ+FncqxxtY9MLb01emg2fqk+WLZ/lNrrtNDFh7BZLDxAHvMgLw==} + svelte@5.55.3: + resolution: {integrity: sha512-dS1N+i3bA1v+c4UDb750MlN5vCO82G6vxh8HeTsPsTdJ1BLsN1zxSyDlIdBBqUjqZ/BxEwM8UrFf98aaoVnZFQ==} engines: {node: '>=18'} symbol-tree@3.2.4: @@ -8199,11 +8662,8 @@ packages: unconfig-core@7.5.0: resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} undici@7.24.4: resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} @@ -8237,10 +8697,6 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - unplugin@2.1.0: - resolution: {integrity: sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==} - engines: {node: '>=18.12.0'} - unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -8547,9 +9003,6 @@ packages: html-webpack-plugin: optional: true - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - webpack@5.105.2: resolution: {integrity: sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==} engines: {node: '>=10.13.0'} @@ -8836,13 +9289,13 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@analogjs/vite-plugin-angular@2.4.3(b4bedc2873066b6ee314e16b886d8b9b)': + '@analogjs/vite-plugin-angular@2.4.5(8a49bd0b443111e2f7e24ed14753c56f)': dependencies: tinyglobby: 0.2.16 ts-morph: 21.0.1 optionalDependencies: - '@angular-devkit/build-angular': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) - '@angular/build': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.8)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular-devkit/build-angular': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/build': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.8)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) '@angular-devkit/architect@0.2102.7(chokidar@5.0.0)': dependencies: @@ -8851,13 +9304,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3)': + '@angular-devkit/build-angular@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) '@angular-devkit/build-webpack': 0.2102.7(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)))(webpack@5.105.2(esbuild@0.27.3)) '@angular-devkit/core': 21.2.7(chokidar@5.0.0) - '@angular/build': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) + '@angular/build': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3) '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -8904,7 +9357,7 @@ snapshots: tree-kill: 1.2.2 tslib: 2.8.1 typescript: 6.0.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) webpack-merge: 6.0.1 @@ -8943,7 +9396,7 @@ snapshots: dependencies: '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) rxjs: 7.8.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) transitivePeerDependencies: - chokidar @@ -8974,7 +9427,7 @@ snapshots: '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) tslib: 2.8.1 - '@angular/build@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3)': + '@angular/build@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) @@ -8983,8 +9436,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 5.1.21(@types/node@25.5.2) - '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@inquirer/confirm': 5.1.21(@types/node@25.6.0) + '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) beasties: 0.4.1 browserslist: 4.28.2 esbuild: 0.27.3 @@ -9005,7 +9458,7 @@ snapshots: tslib: 2.8.1 typescript: 6.0.2 undici: 7.24.4 - vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) watchpack: 2.5.1 optionalDependencies: '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) @@ -9014,7 +9467,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2) postcss: 8.5.6 - vitest: 4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -9030,7 +9483,7 @@ snapshots: - tsx - yaml - '@angular/build@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.8)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3)': + '@angular/build@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(ng-packagr@21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2))(postcss@8.5.8)(terser@5.46.0)(tslib@2.8.1)(tsx@4.21.0)(typescript@6.0.2)(vitest@4.1.4)(yaml@2.8.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) @@ -9039,8 +9492,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 5.1.21(@types/node@25.5.2) - '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@inquirer/confirm': 5.1.21(@types/node@25.6.0) + '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) beasties: 0.4.1 browserslist: 4.28.2 esbuild: 0.27.3 @@ -9061,7 +9514,7 @@ snapshots: tslib: 2.8.1 typescript: 6.0.2 undici: 7.24.4 - vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) watchpack: 2.5.1 optionalDependencies: '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) @@ -9070,7 +9523,7 @@ snapshots: lmdb: 3.5.1 ng-packagr: 21.2.2(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2))(tslib@2.8.1)(typescript@6.0.2) postcss: 8.5.8 - vitest: 4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -9087,13 +9540,13 @@ snapshots: - yaml optional: true - '@angular/cli@21.2.7(@types/node@25.5.2)(chokidar@5.0.0)': + '@angular/cli@21.2.7(@types/node@25.6.0)(chokidar@5.0.0)': dependencies: '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) '@angular-devkit/core': 21.2.7(chokidar@5.0.0) '@angular-devkit/schematics': 21.2.7(chokidar@5.0.0) - '@inquirer/prompts': 7.10.1(@types/node@25.5.2) - '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1(@types/node@25.5.2))(@types/node@25.5.2)(listr2@9.0.5) + '@inquirer/prompts': 7.10.1(@types/node@25.6.0) + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1(@types/node@25.6.0))(@types/node@25.6.0)(listr2@9.0.5) '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) '@schematics/angular': 21.2.7(chokidar@5.0.0) '@yarnpkg/lockfile': 1.1.0 @@ -9954,7 +10407,7 @@ snapshots: dependencies: '@changesets/types': 6.1.0 - '@changesets/cli@2.30.0(@types/node@25.5.2)': + '@changesets/cli@2.30.0(@types/node@25.6.0)': dependencies: '@changesets/apply-release-plan': 7.1.0 '@changesets/assemble-release-plan': 6.0.9 @@ -9970,7 +10423,7 @@ snapshots: '@changesets/should-skip-package': 0.1.2 '@changesets/types': 6.1.0 '@changesets/write': 0.4.0 - '@inquirer/external-editor': 1.0.3(@types/node@25.5.2) + '@inquirer/external-editor': 1.0.3(@types/node@25.6.0) '@manypkg/get-packages': 1.1.3 ansi-colors: 4.1.3 enquirer: 2.4.1 @@ -10101,37 +10554,18 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} - '@emnapi/core@1.9.1': - dependencies: - '@emnapi/wasi-threads': 1.2.0 - tslib: 2.8.1 - '@emnapi/core@1.9.2': dependencies: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.9.1': - dependencies: - tslib: 2.8.1 '@emnapi/runtime@1.9.2': dependencies: tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.0': - dependencies: - tslib: 2.8.1 '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.25.12': - optional: true '@esbuild/aix-ppc64@0.27.3': optional: true @@ -10139,225 +10573,150 @@ snapshots: '@esbuild/aix-ppc64@0.27.4': optional: true - '@esbuild/android-arm64@0.25.12': - optional: true - '@esbuild/android-arm64@0.27.3': optional: true '@esbuild/android-arm64@0.27.4': optional: true - '@esbuild/android-arm@0.25.12': - optional: true - '@esbuild/android-arm@0.27.3': optional: true '@esbuild/android-arm@0.27.4': optional: true - '@esbuild/android-x64@0.25.12': - optional: true - '@esbuild/android-x64@0.27.3': optional: true '@esbuild/android-x64@0.27.4': optional: true - '@esbuild/darwin-arm64@0.25.12': - optional: true - '@esbuild/darwin-arm64@0.27.3': optional: true '@esbuild/darwin-arm64@0.27.4': optional: true - '@esbuild/darwin-x64@0.25.12': - optional: true - '@esbuild/darwin-x64@0.27.3': optional: true '@esbuild/darwin-x64@0.27.4': optional: true - '@esbuild/freebsd-arm64@0.25.12': - optional: true - '@esbuild/freebsd-arm64@0.27.3': optional: true '@esbuild/freebsd-arm64@0.27.4': optional: true - '@esbuild/freebsd-x64@0.25.12': - optional: true - '@esbuild/freebsd-x64@0.27.3': optional: true '@esbuild/freebsd-x64@0.27.4': optional: true - '@esbuild/linux-arm64@0.25.12': - optional: true - '@esbuild/linux-arm64@0.27.3': optional: true '@esbuild/linux-arm64@0.27.4': optional: true - '@esbuild/linux-arm@0.25.12': - optional: true - '@esbuild/linux-arm@0.27.3': optional: true '@esbuild/linux-arm@0.27.4': optional: true - '@esbuild/linux-ia32@0.25.12': - optional: true - '@esbuild/linux-ia32@0.27.3': optional: true '@esbuild/linux-ia32@0.27.4': optional: true - '@esbuild/linux-loong64@0.25.12': - optional: true - '@esbuild/linux-loong64@0.27.3': optional: true '@esbuild/linux-loong64@0.27.4': optional: true - '@esbuild/linux-mips64el@0.25.12': - optional: true - '@esbuild/linux-mips64el@0.27.3': optional: true '@esbuild/linux-mips64el@0.27.4': optional: true - '@esbuild/linux-ppc64@0.25.12': - optional: true - '@esbuild/linux-ppc64@0.27.3': optional: true '@esbuild/linux-ppc64@0.27.4': optional: true - '@esbuild/linux-riscv64@0.25.12': - optional: true - '@esbuild/linux-riscv64@0.27.3': optional: true '@esbuild/linux-riscv64@0.27.4': optional: true - '@esbuild/linux-s390x@0.25.12': - optional: true - '@esbuild/linux-s390x@0.27.3': optional: true '@esbuild/linux-s390x@0.27.4': optional: true - '@esbuild/linux-x64@0.25.12': - optional: true - '@esbuild/linux-x64@0.27.3': optional: true '@esbuild/linux-x64@0.27.4': optional: true - '@esbuild/netbsd-arm64@0.25.12': - optional: true - '@esbuild/netbsd-arm64@0.27.3': optional: true '@esbuild/netbsd-arm64@0.27.4': optional: true - '@esbuild/netbsd-x64@0.25.12': - optional: true - '@esbuild/netbsd-x64@0.27.3': optional: true '@esbuild/netbsd-x64@0.27.4': optional: true - '@esbuild/openbsd-arm64@0.25.12': - optional: true - '@esbuild/openbsd-arm64@0.27.3': optional: true '@esbuild/openbsd-arm64@0.27.4': optional: true - '@esbuild/openbsd-x64@0.25.12': - optional: true - '@esbuild/openbsd-x64@0.27.3': optional: true '@esbuild/openbsd-x64@0.27.4': optional: true - '@esbuild/openharmony-arm64@0.25.12': - optional: true - '@esbuild/openharmony-arm64@0.27.3': optional: true '@esbuild/openharmony-arm64@0.27.4': optional: true - '@esbuild/sunos-x64@0.25.12': - optional: true - '@esbuild/sunos-x64@0.27.3': optional: true '@esbuild/sunos-x64@0.27.4': optional: true - '@esbuild/win32-arm64@0.25.12': - optional: true - '@esbuild/win32-arm64@0.27.3': optional: true '@esbuild/win32-arm64@0.27.4': optional: true - '@esbuild/win32-ia32@0.25.12': - optional: true - '@esbuild/win32-ia32@0.27.3': optional: true '@esbuild/win32-ia32@0.27.4': optional: true - '@esbuild/win32-x64@0.25.12': - optional: true - '@esbuild/win32-x64@0.27.3': optional: true @@ -10513,128 +10872,128 @@ snapshots: '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@25.5.2)': + '@inquirer/checkbox@4.3.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/confirm@5.1.21(@types/node@25.5.2)': + '@inquirer/confirm@5.1.21(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/core@10.3.2(@types/node@25.5.2)': + '@inquirer/core@10.3.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/editor@4.2.23(@types/node@25.5.2)': + '@inquirer/editor@4.2.23(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/external-editor': 1.0.3(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/external-editor': 1.0.3(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/expand@4.0.23(@types/node@25.5.2)': + '@inquirer/expand@4.0.23(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/external-editor@1.0.3(@types/node@25.5.2)': + '@inquirer/external-editor@1.0.3(@types/node@25.6.0)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@25.5.2)': + '@inquirer/input@4.3.1(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/number@3.0.23(@types/node@25.5.2)': + '@inquirer/number@3.0.23(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/password@4.0.23(@types/node@25.5.2)': + '@inquirer/password@4.0.23(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 - - '@inquirer/prompts@7.10.1(@types/node@25.5.2)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.5.2) - '@inquirer/confirm': 5.1.21(@types/node@25.5.2) - '@inquirer/editor': 4.2.23(@types/node@25.5.2) - '@inquirer/expand': 4.0.23(@types/node@25.5.2) - '@inquirer/input': 4.3.1(@types/node@25.5.2) - '@inquirer/number': 3.0.23(@types/node@25.5.2) - '@inquirer/password': 4.0.23(@types/node@25.5.2) - '@inquirer/rawlist': 4.1.11(@types/node@25.5.2) - '@inquirer/search': 3.2.2(@types/node@25.5.2) - '@inquirer/select': 4.4.2(@types/node@25.5.2) + '@types/node': 25.6.0 + + '@inquirer/prompts@7.10.1(@types/node@25.6.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.6.0) + '@inquirer/confirm': 5.1.21(@types/node@25.6.0) + '@inquirer/editor': 4.2.23(@types/node@25.6.0) + '@inquirer/expand': 4.0.23(@types/node@25.6.0) + '@inquirer/input': 4.3.1(@types/node@25.6.0) + '@inquirer/number': 3.0.23(@types/node@25.6.0) + '@inquirer/password': 4.0.23(@types/node@25.6.0) + '@inquirer/rawlist': 4.1.11(@types/node@25.6.0) + '@inquirer/search': 3.2.2(@types/node@25.6.0) + '@inquirer/select': 4.4.2(@types/node@25.6.0) optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/rawlist@4.1.11(@types/node@25.5.2)': + '@inquirer/rawlist@4.1.11(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/search@3.2.2(@types/node@25.5.2)': + '@inquirer/search@3.2.2(@types/node@25.6.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/select@4.4.2(@types/node@25.5.2)': + '@inquirer/select@4.4.2(@types/node@25.6.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.2) + '@inquirer/core': 10.3.2(@types/node@25.6.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/type': 3.0.10(@types/node@25.6.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 - '@inquirer/type@3.0.10(@types/node@25.5.2)': + '@inquirer/type@3.0.10(@types/node@25.6.0)': optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@isaacs/cliui@8.0.2': dependencies: @@ -10812,10 +11171,10 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@25.5.2))(@types/node@25.5.2)(listr2@9.0.5)': + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@25.6.0))(@types/node@25.6.0)(listr2@9.0.5)': dependencies: - '@inquirer/prompts': 7.10.1(@types/node@25.5.2) - '@inquirer/type': 3.0.10(@types/node@25.5.2) + '@inquirer/prompts': 7.10.1(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) listr2: 9.0.5 transitivePeerDependencies: - '@types/node' @@ -10841,8 +11200,6 @@ snapshots: '@lmdb/lmdb-win32-x64@3.5.1': optional: true - '@ltd/j-toml@1.38.0': {} - '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.29.2 @@ -10977,15 +11334,15 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.9.1 - '@emnapi/runtime': 1.9.1 + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 '@tybys/wasm-util': 0.10.1 optional: true '@napi-rs/wasm-runtime@0.2.4': dependencies: - '@emnapi/core': 1.9.1 - '@emnapi/runtime': 1.9.1 + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 '@tybys/wasm-util': 0.9.0 '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': @@ -11006,7 +11363,7 @@ snapshots: dependencies: '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@6.0.2) typescript: 6.0.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: @@ -11084,34 +11441,34 @@ snapshots: transitivePeerDependencies: - supports-color - '@nx/nx-darwin-arm64@22.6.4': + '@nx/nx-darwin-arm64@22.6.5': optional: true - '@nx/nx-darwin-x64@22.6.4': + '@nx/nx-darwin-x64@22.6.5': optional: true - '@nx/nx-freebsd-x64@22.6.4': + '@nx/nx-freebsd-x64@22.6.5': optional: true - '@nx/nx-linux-arm-gnueabihf@22.6.4': + '@nx/nx-linux-arm-gnueabihf@22.6.5': optional: true - '@nx/nx-linux-arm64-gnu@22.6.4': + '@nx/nx-linux-arm64-gnu@22.6.5': optional: true - '@nx/nx-linux-arm64-musl@22.6.4': + '@nx/nx-linux-arm64-musl@22.6.5': optional: true - '@nx/nx-linux-x64-gnu@22.6.4': + '@nx/nx-linux-x64-gnu@22.6.5': optional: true - '@nx/nx-linux-x64-musl@22.6.4': + '@nx/nx-linux-x64-musl@22.6.5': optional: true - '@nx/nx-win32-arm64-msvc@22.6.4': + '@nx/nx-win32-arm64-msvc@22.6.5': optional: true - '@nx/nx-win32-x64-msvc@22.6.4': + '@nx/nx-win32-x64-msvc@22.6.5': optional: true '@one-ini/wasm@0.1.1': {} @@ -11239,7 +11596,7 @@ snapshots: '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: - '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -11410,19 +11767,19 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.60.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vite-prerender-plugin: 0.5.13(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite-prerender-plugin: 0.5.13(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -11431,11 +11788,6 @@ snapshots: '@preact/signals-core@1.14.1': {} - '@preact/signals@1.3.4(preact@10.29.1)': - dependencies: - '@preact/signals-core': 1.14.1 - preact: 10.29.1 - '@preact/signals@2.9.0(preact@10.29.1)': dependencies: '@preact/signals-core': 1.14.1 @@ -11449,7 +11801,7 @@ snapshots: '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@prefresh/babel-plugin': 0.5.3 @@ -11457,7 +11809,7 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.29.1 - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -11805,25 +12157,25 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/package@2.5.7(svelte@5.55.2)(typescript@6.0.2)': + '@sveltejs/package@2.5.7(svelte@5.55.3)(typescript@6.0.2)': dependencies: chokidar: 5.0.0 kleur: 4.1.5 sade: 1.8.1 semver: 7.7.4 - svelte: 5.55.2 - svelte2tsx: 0.7.53(svelte@5.55.2)(typescript@6.0.2) + svelte: 5.55.3 + svelte2tsx: 0.7.53(svelte@5.55.3)(typescript@6.0.2) transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 - svelte: 5.55.2 - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + svelte: 5.55.3 + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@svitejs/changesets-changelog-github-compact@1.2.0': dependencies: @@ -11913,18 +12265,18 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@testing-library/svelte-core@1.0.0(svelte@5.55.2)': + '@testing-library/svelte-core@1.0.0(svelte@5.55.3)': dependencies: - svelte: 5.55.2 + svelte: 5.55.3 - '@testing-library/svelte@5.3.1(svelte@5.55.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)': + '@testing-library/svelte@5.3.1(svelte@5.55.3)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)': dependencies: '@testing-library/dom': 10.4.1 - '@testing-library/svelte-core': 1.0.0(svelte@5.55.2) - svelte: 5.55.2 + '@testing-library/svelte-core': 1.0.0(svelte@5.55.3) + svelte: 5.55.3 optionalDependencies: - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vitest: 4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: @@ -11990,11 +12342,11 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/bonjour@3.5.13': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/chai@5.2.3': dependencies: @@ -12004,11 +12356,11 @@ snapshots: '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.19.8 - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/connect@3.4.38': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/deep-eql@4.0.2': {} @@ -12028,7 +12380,7 @@ snapshots: '@types/express-serve-static-core@4.19.8': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/qs': 6.15.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -12048,7 +12400,7 @@ snapshots: '@types/http-proxy@1.17.17': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/jsesc@2.5.1': {} @@ -12058,13 +12410,9 @@ snapshots: '@types/node@12.20.55': {} - '@types/node@20.19.39': - dependencies: - undici-types: 6.21.0 - - '@types/node@25.5.2': + '@types/node@25.6.0': dependencies: - undici-types: 7.18.2 + undici-types: 7.19.2 '@types/qs@6.15.0': {} @@ -12083,11 +12431,11 @@ snapshots: '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/send@1.2.1': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/serve-index@1.9.4': dependencies: @@ -12096,12 +12444,12 @@ snapshots: '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/send': 0.17.6 '@types/sockjs@0.3.36': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/trusted-types@2.0.7': {} @@ -12111,7 +12459,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: @@ -12326,24 +12674,24 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) optional: true - '@vitejs/plugin-react@6.0.1(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-react@6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - '@vitejs/plugin-vue@6.0.5(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2))': + '@vitejs/plugin-vue@6.0.5(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.32(typescript@6.0.2) '@vitest/coverage-istanbul@4.1.4(vitest@4.1.4)': @@ -12358,7 +12706,7 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 tinyrainbow: 3.1.0 - vitest: 4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -12371,21 +12719,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.4(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - - '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 4.1.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@4.1.4': dependencies: @@ -12813,8 +13153,6 @@ snapshots: async-function@1.0.0: {} - async@3.2.6: {} - asynckit@0.4.0: {} autoprefixer@10.4.27(postcss@8.5.6): @@ -12830,11 +13168,11 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axios@1.12.0: + axios@1.15.0: dependencies: follow-redirects: 1.15.11(debug@4.4.3) form-data: 4.0.5 - proxy-from-env: 1.1.0 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -12844,7 +13182,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 find-up: 5.0.0 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) babel-plugin-jsx-dom-expressions@0.40.6(@babel/core@7.29.0): dependencies: @@ -12932,10 +13270,6 @@ snapshots: binary-extensions@2.3.0: {} - bippy@0.5.39(react@19.2.5): - dependencies: - react: 19.2.5 - birecord@0.1.1: {} birpc@4.0.0: {} @@ -13175,7 +13509,8 @@ snapshots: commander@10.0.1: {} - commander@14.0.3: {} + commander@14.0.3: + optional: true commander@2.20.3: {} @@ -13245,7 +13580,7 @@ snapshots: schema-utils: 4.3.3 serialize-javascript: 7.0.5 tinyglobby: 0.2.16 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) core-js-compat@3.49.0: dependencies: @@ -13284,7 +13619,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) css-select@5.2.2: dependencies: @@ -13494,9 +13829,7 @@ snapshots: ee-first@1.1.1: {} - ejs@3.1.10: - dependencies: - jake: 10.9.4 + ejs@5.0.1: {} electron-to-chromium@1.5.330: {} @@ -13674,35 +14007,6 @@ snapshots: esbuild-wasm@0.27.3: {} - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - esbuild@0.27.3: optionalDependencies: '@esbuild/aix-ppc64': 0.27.3 @@ -13987,7 +14291,7 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-svelte@3.17.0(eslint@10.2.0(jiti@2.6.1))(svelte@5.55.2): + eslint-plugin-svelte@3.17.0(eslint@10.2.0(jiti@2.6.1))(svelte@5.55.3): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) '@jridgewell/sourcemap-codec': 1.5.5 @@ -13999,9 +14303,9 @@ snapshots: postcss-load-config: 3.1.4(postcss@8.5.8) postcss-safe-parser: 7.0.1(postcss@8.5.8) semver: 7.7.4 - svelte-eslint-parser: 1.6.0(svelte@5.55.2) + svelte-eslint-parser: 1.6.0(svelte@5.55.3) optionalDependencies: - svelte: 5.55.2 + svelte: 5.55.3 transitivePeerDependencies: - ts-node @@ -14090,7 +14394,7 @@ snapshots: esrap@2.2.4: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/types': 8.58.1 esrecurse@4.3.0: dependencies: @@ -14242,10 +14546,6 @@ snapshots: dependencies: flat-cache: 4.0.1 - filelist@1.0.6: - dependencies: - minimatch: 5.1.9 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -14894,12 +15194,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jake@10.9.4: - dependencies: - async: 3.2.6 - filelist: 1.0.6 - picocolors: 1.1.1 - jest-diff@30.3.0: dependencies: '@jest/diff-sequences': 30.3.0 @@ -14909,7 +15203,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -15009,11 +15303,9 @@ snapshots: kind-of@6.0.3: {} - kleur@3.0.3: {} - kleur@4.1.5: {} - knip@6.3.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2): + knip@6.4.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2): dependencies: '@nodelib/fs.walk': 1.2.8 fast-glob: 3.3.3 @@ -15047,7 +15339,7 @@ snapshots: dependencies: less: 4.4.2 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) less@4.4.2: dependencies: @@ -15086,7 +15378,7 @@ snapshots: dependencies: webpack-sources: 3.3.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) lightningcss-android-arm64@1.32.0: optional: true @@ -15357,7 +15649,7 @@ snapshots: dependencies: schema-utils: 4.3.3 tapable: 2.3.2 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) minimalistic-assert@1.0.1: {} @@ -15373,10 +15665,6 @@ snapshots: dependencies: brace-expansion: 1.1.13 - minimatch@5.1.9: - dependencies: - brace-expansion: 2.0.3 - minimatch@9.0.9: dependencies: brace-expansion: 2.0.3 @@ -15607,20 +15895,19 @@ snapshots: dependencies: boolbase: 1.0.0 - nx@22.6.4: + nx@22.6.5: dependencies: - '@ltd/j-toml': 1.38.0 '@napi-rs/wasm-runtime': 0.2.4 '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.2 '@zkochan/js-yaml': 0.0.7 - axios: 1.12.0 + axios: 1.15.0 cli-cursor: 3.1.0 cli-spinners: 2.6.1 cliui: 8.0.1 dotenv: 16.4.7 dotenv-expand: 11.0.7 - ejs: 3.1.10 + ejs: 5.0.1 enquirer: 2.3.6 figures: 3.2.0 flat: 5.0.2 @@ -15636,6 +15923,7 @@ snapshots: picocolors: 1.1.1 resolve.exports: 2.0.3 semver: 7.7.4 + smol-toml: 1.6.1 string-width: 4.2.3 tar-stream: 2.2.0 tmp: 0.2.5 @@ -15646,16 +15934,16 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@nx/nx-darwin-arm64': 22.6.4 - '@nx/nx-darwin-x64': 22.6.4 - '@nx/nx-freebsd-x64': 22.6.4 - '@nx/nx-linux-arm-gnueabihf': 22.6.4 - '@nx/nx-linux-arm64-gnu': 22.6.4 - '@nx/nx-linux-arm64-musl': 22.6.4 - '@nx/nx-linux-x64-gnu': 22.6.4 - '@nx/nx-linux-x64-musl': 22.6.4 - '@nx/nx-win32-arm64-msvc': 22.6.4 - '@nx/nx-win32-x64-msvc': 22.6.4 + '@nx/nx-darwin-arm64': 22.6.5 + '@nx/nx-darwin-x64': 22.6.5 + '@nx/nx-freebsd-x64': 22.6.5 + '@nx/nx-linux-arm-gnueabihf': 22.6.5 + '@nx/nx-linux-arm64-gnu': 22.6.5 + '@nx/nx-linux-arm64-musl': 22.6.5 + '@nx/nx-linux-x64-gnu': 22.6.5 + '@nx/nx-linux-x64-musl': 22.6.5 + '@nx/nx-win32-arm64-msvc': 22.6.5 + '@nx/nx-win32-x64-msvc': 22.6.5 transitivePeerDependencies: - debug @@ -16014,7 +16302,7 @@ snapshots: postcss: 8.5.6 semver: 7.7.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) transitivePeerDependencies: - typescript @@ -16076,14 +16364,14 @@ snapshots: premove@4.0.0: {} - prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.55.2): + prettier-plugin-svelte@3.5.1(prettier@3.8.2)(svelte@5.55.3): dependencies: - prettier: 3.8.1 - svelte: 5.55.2 + prettier: 3.8.2 + svelte: 5.55.3 prettier@2.8.8: {} - prettier@3.8.1: {} + prettier@3.8.2: {} pretty-format@27.5.1: dependencies: @@ -16106,11 +16394,6 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -16124,7 +16407,7 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-from-env@1.1.0: {} + proxy-from-env@2.1.0: {} prr@1.0.1: optional: true @@ -16187,29 +16470,6 @@ snapshots: react-is@18.3.1: {} - react-scan@0.5.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1): - dependencies: - '@babel/core': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/types': 7.29.0 - '@preact/signals': 1.3.4(preact@10.29.1) - '@rollup/pluginutils': 5.3.0(rollup@4.60.1) - '@types/node': 20.19.39 - bippy: 0.5.39(react@19.2.5) - commander: 14.0.3 - esbuild: 0.25.12 - estree-walker: 3.0.3 - picocolors: 1.1.1 - preact: 10.29.1 - prompts: 2.4.2 - react: 19.2.5 - react-dom: 19.2.5(react@19.2.5) - optionalDependencies: - unplugin: 2.1.0 - transitivePeerDependencies: - - rollup - - supports-color - react@19.2.5: {} read-yaml-file@1.1.0: @@ -16531,7 +16791,7 @@ snapshots: neo-async: 2.6.2 optionalDependencies: sass: 1.97.3 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) sass@1.97.3: dependencies: @@ -16774,8 +17034,6 @@ snapshots: dependencies: kolorist: 1.8.0 - sisteransi@1.0.5: {} - slash@3.0.0: {} slice-ansi@7.1.2: @@ -16832,7 +17090,7 @@ snapshots: dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) source-map-support@0.5.21: dependencies: @@ -17006,19 +17264,19 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.4.6(picomatch@4.0.4)(svelte@5.55.2)(typescript@6.0.2): + svelte-check@4.4.6(picomatch@4.0.4)(svelte@5.55.3)(typescript@6.0.2): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.4) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.55.2 + svelte: 5.55.3 typescript: 6.0.2 transitivePeerDependencies: - picomatch - svelte-eslint-parser@1.6.0(svelte@5.55.2): + svelte-eslint-parser@1.6.0(svelte@5.55.3): dependencies: eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -17028,16 +17286,16 @@ snapshots: postcss-selector-parser: 7.1.1 semver: 7.7.4 optionalDependencies: - svelte: 5.55.2 + svelte: 5.55.3 - svelte2tsx@0.7.53(svelte@5.55.2)(typescript@6.0.2): + svelte2tsx@0.7.53(svelte@5.55.3)(typescript@6.0.2): dependencies: dedent-js: 1.0.1 scule: 1.3.0 - svelte: 5.55.2 + svelte: 5.55.3 typescript: 6.0.2 - svelte@5.55.2: + svelte@5.55.3: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 @@ -17078,15 +17336,15 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.4.0(esbuild@0.27.4)(webpack@5.105.2(esbuild@0.27.3)): + terser-webpack-plugin@5.4.0(esbuild@0.27.3)(webpack@5.105.2(esbuild@0.27.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.46.0 - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) optionalDependencies: - esbuild: 0.27.4 + esbuild: 0.27.3 terser@5.46.0: dependencies: @@ -17326,9 +17584,7 @@ snapshots: '@quansync/fs': 1.0.0 quansync: 1.0.0 - undici-types@6.21.0: {} - - undici-types@7.18.2: {} + undici-types@7.19.2: {} undici@7.24.4: {} @@ -17349,12 +17605,6 @@ snapshots: unpipe@1.0.0: {} - unplugin@2.1.0: - dependencies: - acorn: 8.16.0 - webpack-virtual-modules: 0.6.2 - optional: true - unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -17410,7 +17660,7 @@ snapshots: vary@1.1.2: {} - vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-solid@2.11.12(@testing-library/jest-dom@6.9.1)(solid-js@1.9.12)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@babel/core': 7.29.0 '@types/babel__core': 7.20.5 @@ -17418,14 +17668,14 @@ snapshots: merge-anything: 5.1.7 solid-js: 1.9.12 solid-refresh: 0.6.3(solid-js@1.9.12) - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: '@testing-library/jest-dom': 6.9.1 transitivePeerDependencies: - supports-color - vite-prerender-plugin@0.5.13(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-prerender-plugin@0.5.13(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -17433,9 +17683,9 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3): + vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -17444,7 +17694,7 @@ snapshots: rollup: 4.60.1 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.2 @@ -17454,7 +17704,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3): + vite@7.3.2(@types/node@25.6.0)(jiti@2.6.1)(less@4.6.4)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -17463,7 +17713,7 @@ snapshots: rollup: 4.60.1 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 fsevents: 2.3.3 jiti: 2.6.1 less: 4.6.4 @@ -17474,7 +17724,7 @@ snapshots: yaml: 2.8.3 optional: true - vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -17482,7 +17732,7 @@ snapshots: rolldown: 1.0.0-rc.15 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 esbuild: 0.27.4 fsevents: 2.3.3 jiti: 2.6.1 @@ -17492,43 +17742,14 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vitefu@1.1.2(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): - optionalDependencies: - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - - vitest@4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): - dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.0.4 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - why-is-node-running: 2.3.0 + vitefu@1.1.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: - '@types/node': 25.5.2 - '@vitest/coverage-istanbul': 4.1.4(vitest@4.1.4) - jsdom: 29.0.2 - transitivePeerDependencies: - - msw + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) - vitest@4.1.4(@types/node@25.5.2)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.4(@types/node@25.6.0)(@vitest/coverage-istanbul@4.1.4)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 @@ -17545,10 +17766,10 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.8(@types/node@25.5.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.6.4)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@vitest/coverage-istanbul': 4.1.4(vitest@4.1.4) jsdom: 29.0.2 transitivePeerDependencies: @@ -17634,7 +17855,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) transitivePeerDependencies: - tslib @@ -17669,7 +17890,7 @@ snapshots: webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) ws: 8.20.0 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.4) + webpack: 5.105.2(esbuild@0.27.3) transitivePeerDependencies: - bufferutil - debug @@ -17688,12 +17909,9 @@ snapshots: webpack-subresource-integrity@5.1.0(webpack@5.105.2(esbuild@0.27.3)): dependencies: typed-assert: 1.0.9 - webpack: 5.105.2(esbuild@0.27.4) - - webpack-virtual-modules@0.6.2: - optional: true + webpack: 5.105.2(esbuild@0.27.3) - webpack@5.105.2(esbuild@0.27.4): + webpack@5.105.2(esbuild@0.27.3): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -17717,7 +17935,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(esbuild@0.27.4)(webpack@5.105.2(esbuild@0.27.3)) + terser-webpack-plugin: 5.4.0(esbuild@0.27.3)(webpack@5.105.2(esbuild@0.27.3)) watchpack: 2.5.1 webpack-sources: 3.3.4 transitivePeerDependencies: