diff --git a/CHANGELOG.md b/CHANGELOG.md index c74b7ea..a52ba31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Removed + +- Removed deprecated provider, resolved-store, and lifecycle aliases. + ## [0.4.2] - 2026-07-25 ### Fixed diff --git a/README.md b/README.md index 5ee4565..56ee77b 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,21 @@ If you want Zustand Redux DevTools, apply `devtools(...)` in the store creator i - Full TypeScript inference with Zustand middleware support - React 19 helpers for transitions, optimistic updates, and action state adapters +## Store Access Matrix + +Each factory uses the same value/plain/API naming pattern: + +| Store source | Shallow-first value | Plain value | Store API | +| ------------ | ------------------- | ----------------------- | --------------------- | +| Global | `useStore` | `useStorePlain` | `useStoreApi` | +| Provider | `useContextStore` | `useContextStorePlain` | `useContextStoreApi` | +| Resolved | `useResolvedValue` | `useResolvedStorePlain` | `useResolvedStoreApi` | + +The provider factory also exposes `useContextStoreOptional` for integrations that +need to detect whether a matching provider is present. The toolkit exposes its +shared provider bindings through `provider`; `getProvider()` returns that same +object when function-based access is useful. + ## Installation ```bash @@ -121,11 +136,17 @@ interface WizardStore { next: () => void; } -const { Provider: WizardProvider, useContextStore, useContextStoreApi } = - createStoreProvider((set) => ({ +const { + Provider: WizardProvider, + useContextStore, + useContextStoreApi, +} = createStoreProvider( + (set) => ({ step: 1, next: () => set((state) => ({ step: state.step + 1 })), - }), "Wizard"); + }), + "Wizard" +); function WizardStep() { const step = useContextStore((state) => state.step); @@ -162,10 +183,13 @@ interface CartStore { addItem: (item: string) => void; } -const cartToolkit = createStoreToolkit((set) => ({ - items: [], - addItem: (item) => set((state) => ({ items: [...state.items, item] })), -}), { name: "Cart" }); +const cartToolkit = createStoreToolkit( + (set) => ({ + items: [], + addItem: (item) => set((state) => ({ items: [...state.items, item] })), + }), + { name: "Cart" } +); export const { useResolvedValue: useCart } = cartToolkit; export const { Provider: CartProvider } = cartToolkit.provider; @@ -183,7 +207,6 @@ Returns: - `useStoreApi` - `provider` - `getProvider()` -- `createProvider()` deprecated alias - `useResolvedStoreApi()` - `useResolvedValue()` - `useResolvedStorePlain()` @@ -269,9 +292,12 @@ const { useStoreApi } = createShallowStore(() => ({ theme: "light", })); -const { useContextStoreOptional } = createStoreProvider(() => ({ - theme: "light", -}), "Preferences"); +const { useContextStoreOptional } = createStoreProvider( + () => ({ + theme: "light", + }), + "Preferences" +); const { useResolvedValue } = createResolvedStoreHooks(useStoreApi, useContextStoreOptional); @@ -284,10 +310,28 @@ function ThemeLabel() { It returns the same resolved hook family used by `createStoreToolkit`: -- `useResolvedStoreApi()` and its deprecated `useResolvedStore()` alias -- `useResolvedValue()` and its deprecated `useResolvedStoreWithSelector()` alias +- `useResolvedStoreApi()` +- `useResolvedValue()` - `useResolvedStorePlain()` +## Upgrading After Deprecated Alias Removal + +The deprecated compatibility names have been removed. Replace them with their +canonical equivalents: + +| Removed name | Replacement | +| -------------------------------- | --------------------------------------------- | +| `toolkit.createProvider()` | `toolkit.provider` or `toolkit.getProvider()` | +| `useContext()` | `useContextStoreApi()` | +| `useOptionalContext()` | `useContextStoreOptional()` | +| `useResolvedStore()` | `useResolvedStoreApi()` | +| `useResolvedStoreWithSelector()` | `useResolvedValue()` | +| `onStoreCreate` | `onStoreReady` | + +`onStoreReady` runs after the provider commits and at most once for each provider +store instance. Use `onStoreInit` when state must be initialized synchronously +during store creation. + ## Provider Lifecycle `createStoreProvider` supports two lifecycle stages: @@ -317,11 +361,6 @@ const { Provider } = createStoreProvider((set) => ({ ; ``` -Deprecated alias: - -- `onStoreCreate` maps to the post-commit `onStoreReady` behavior and is ignored - when `onStoreReady` is also provided - ## Middleware Support Zustand middleware belongs in the store creator. @@ -371,10 +410,7 @@ interface FilterStore { setQuery: (query: string) => void; } -const { useStoreApi } = createShallowStore< - FilterStore, - [["zustand/subscribeWithSelector", never]] ->( +const { useStoreApi } = createShallowStore( subscribeWithSelector((set) => ({ query: "", setQuery: (query) => set({ query }), @@ -391,51 +427,6 @@ const unsubscribe = useStoreApi.subscribe( unsubscribe(); ``` -## Migration - -Deprecated names still exist for compatibility, but new code should prefer the current API. - -### Provider access - -```tsx -const { Provider } = toolkit.provider; -``` - -Old aliases: - -```tsx -const { Provider } = toolkit.getProvider(); -const { Provider: LegacyProvider } = toolkit.createProvider(); -``` - -### Raw provider hooks - -```tsx -const store = useContextStoreApi(); -const maybeStore = useContextStoreOptional(); -``` - -Old aliases: - -```tsx -const store = useContext(); -const maybeStore = useOptionalContext(); -``` - -### Resolved hooks - -```tsx -const value = useResolvedValue((state) => state.value); -const store = useResolvedStoreApi(); -``` - -Old aliases: - -```tsx -const value = useResolvedStoreWithSelector((state) => state.value); -const store = useResolvedStore(); -``` - ## React 19 Helpers ```tsx @@ -459,10 +450,10 @@ const [status, submit, isPending] = useActionStateAdapter(async (payload: FormDa return "saved"; }, "idle"); -const [optimisticTodos, addOptimisticTodo] = useOptimisticReducer( - todos, - (current, nextTodo) => [...current, nextTodo] -); +const [optimisticTodos, addOptimisticTodo] = useOptimisticReducer(todos, (current, nextTodo) => [ + ...current, + nextTodo, +]); ``` `createTransitionAction` supports synchronous and asynchronous actions. In React 19, diff --git a/src/core/__tests__/createStoreToolkit.test.tsx b/src/core/__tests__/createStoreToolkit.test.tsx index ed092f3..f7f01e5 100644 --- a/src/core/__tests__/createStoreToolkit.test.tsx +++ b/src/core/__tests__/createStoreToolkit.test.tsx @@ -10,33 +10,20 @@ interface TestStore { increment: () => void; } -interface LegacyToolkitApi { - createProvider: () => { Provider: (props: { children: ReactNode }) => ReactNode }; - useResolvedStore: () => unknown; - useResolvedStoreWithSelector: ( - selector: (state: TestStore) => T, - equalityFn?: (a: T, b: T) => boolean - ) => T; -} - describe("createStoreToolkit", () => { it("should create complete toolkit", () => { const toolkit = createStoreToolkit((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); - const legacyToolkit: LegacyToolkitApi = toolkit; expect(toolkit.useStore).toBeDefined(); expect(toolkit.useStorePlain).toBeDefined(); expect(toolkit.useStoreApi).toBeDefined(); expect(toolkit.provider).toBeDefined(); expect(toolkit.getProvider).toBeDefined(); - expect(legacyToolkit.createProvider).toBeDefined(); expect(toolkit.useResolvedStoreApi).toBeDefined(); - expect(legacyToolkit.useResolvedStore).toBeDefined(); expect(toolkit.useResolvedValue).toBeDefined(); - expect(legacyToolkit.useResolvedStoreWithSelector).toBeDefined(); expect(toolkit.useResolvedStorePlain).toBeDefined(); }); @@ -45,10 +32,7 @@ describe("createStoreToolkit", () => { count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); - const legacyToolkit: LegacyToolkitApi = toolkit; - expect(toolkit.provider).toBe(toolkit.getProvider()); - expect(toolkit.getProvider()).toBe(legacyToolkit.createProvider()); }); it("should work with global store", () => { @@ -83,16 +67,13 @@ describe("createStoreToolkit", () => { count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); - const legacyToolkit: LegacyToolkitApi = toolkit; - const { Provider } = toolkit.provider; const wrapper = ({ children }: { children: ReactNode }) => {children}; - const { result } = renderHook( - () => legacyToolkit.useResolvedStoreWithSelector((state) => state.count), - { wrapper } - ); + const { result } = renderHook(() => toolkit.useResolvedValue((state) => state.count), { + wrapper, + }); expect(result.current).toBe(0); }); @@ -102,20 +83,18 @@ describe("createStoreToolkit", () => { count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); - const legacyToolkit: LegacyToolkitApi = toolkit; - const { Provider } = toolkit.provider; // Global store const { result: globalResult } = renderHook(() => - legacyToolkit.useResolvedStoreWithSelector((state) => state.count) + toolkit.useResolvedValue((state) => state.count) ); // Provider store const wrapper = ({ children }: { children: ReactNode }) => {children}; const { result: providerResult } = renderHook( - () => legacyToolkit.useResolvedStoreWithSelector((state) => state.count), + () => toolkit.useResolvedValue((state) => state.count), { wrapper } ); @@ -138,10 +117,8 @@ describe("createStoreToolkit", () => { count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); - const legacyToolkit: LegacyToolkitApi = toolkit; - const { result } = renderHook(() => - legacyToolkit.useResolvedStoreWithSelector( + toolkit.useResolvedValue( (state) => state.count, () => true ) @@ -161,14 +138,11 @@ describe("createStoreToolkit", () => { count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); - const legacyToolkit: LegacyToolkitApi = toolkit; - const { result } = renderHook(() => toolkit.useResolvedStorePlain((state) => state.count)); const { result: apiResult } = renderHook(() => toolkit.useResolvedStoreApi()); expect(result.current).toBe(0); expect(apiResult.current).toBe(toolkit.useStoreApi); - expect(toolkit.useResolvedValue).toBe(legacyToolkit.useResolvedStoreWithSelector); }); it("should expose full resolved state without selector outside provider", () => { diff --git a/src/core/createShallowStore.ts b/src/core/createShallowStore.ts index ca5f4f3..82f7b83 100644 --- a/src/core/createShallowStore.ts +++ b/src/core/createShallowStore.ts @@ -1,11 +1,7 @@ import { createStore } from "zustand"; import { useStoreSelection, useStoreSelectionPlain } from "../hooks"; -import type { - MutatorsStateCreator, - ShallowStoreBindings, - StoreApiWithMutators, - StoreMutatorTuple, -} from "../types"; +import type { MutatorsStateCreator, StoreApiWithMutators, StoreMutatorTuple } from "../types"; +import type { ShallowStoreBindings } from "./createShallowStore.types"; /** * Creates a Zustand store with shallow-first selector semantics. diff --git a/src/core/createShallowStore.types.ts b/src/core/createShallowStore.types.ts new file mode 100644 index 0000000..e5666a1 --- /dev/null +++ b/src/core/createShallowStore.types.ts @@ -0,0 +1,9 @@ +import type { StorePlainHook, StoreValueHook } from "../hooks"; +import type { StoreApiWithMutators, StoreMutatorTuple } from "../types"; + +/** Store bindings with shallow comparison built in. */ +export interface ShallowStoreBindings = []> { + useStore: StoreValueHook; + useStorePlain: StorePlainHook; + useStoreApi: StoreApiWithMutators; +} diff --git a/src/core/createStoreToolkit.ts b/src/core/createStoreToolkit.ts index 8d8602d..2b9d4a4 100644 --- a/src/core/createStoreToolkit.ts +++ b/src/core/createStoreToolkit.ts @@ -1,12 +1,9 @@ import { createResolvedStoreHooks } from "../hooks"; import { createStoreProvider } from "../providers"; import { createShallowStore } from "./createShallowStore"; -import type { - MutatorsStateCreator, - StoreMutatorTuple, - StoreProviderResult, - StoreToolkit, -} from "../types"; +import type { StoreProviderResult } from "../providers"; +import type { MutatorsStateCreator, StoreMutatorTuple } from "../types"; +import type { StoreToolkit } from "./createStoreToolkit.types"; /** * Creates a complete Zustand store toolkit with global store, provider, and resolution hooks @@ -44,19 +41,11 @@ export function createStoreToolkit { - return getProvider(); - } - // Use the shared provider's hooks for resolution const { useContextStoreOptional } = provider; // Create resolution hooks - const { useResolvedStoreApi, useResolvedValue, useResolvedStorePlain } = createResolvedStoreHooks( - useStoreApi, - useContextStoreOptional - ); + const resolvedBindings = createResolvedStoreHooks(useStoreApi, useContextStoreOptional); return { useStore, @@ -64,11 +53,6 @@ export function createStoreToolkit = []> + extends ShallowStoreBindings, ResolvedStoreBindings { + provider: StoreProviderResult; + getProvider: () => StoreProviderResult; +} diff --git a/src/core/index.ts b/src/core/index.ts index 9852d78..7fe782e 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,2 +1,4 @@ export { createShallowStore } from "./createShallowStore"; export { createStoreToolkit } from "./createStoreToolkit"; +export type { ShallowStoreBindings } from "./createShallowStore.types"; +export type { StoreToolkit } from "./createStoreToolkit.types"; diff --git a/src/hooks/createResolvedStoreHooks.ts b/src/hooks/createResolvedStoreHooks.ts index 950e7d8..65bc86f 100644 --- a/src/hooks/createResolvedStoreHooks.ts +++ b/src/hooks/createResolvedStoreHooks.ts @@ -1,5 +1,6 @@ import { useStoreSelection, useStoreSelectionPlain } from "./storeSelection"; import type { StoreApiWithMutators, StoreMutatorTuple } from "../types"; +import type { ResolvedStoreBindings } from "./createResolvedStoreHooks.types"; /** * Creates hooks that resolve between context store and global store @@ -16,22 +17,7 @@ import type { StoreApiWithMutators, StoreMutatorTuple } from "../types"; export function createResolvedStoreHooks = []>( globalStoreApi: StoreApiWithMutators, useContextStore: () => StoreApiWithMutators | null -): { - useResolvedStoreApi: () => StoreApiWithMutators; - useResolvedStore: () => StoreApiWithMutators; - useResolvedValue: { - (): TState; - (selector: (state: TState) => T, equalityFn?: (a: T, b: T) => boolean): T; - }; - useResolvedStoreWithSelector: { - (): TState; - (selector: (state: TState) => T, equalityFn?: (a: T, b: T) => boolean): T; - }; - useResolvedStorePlain: { - (): TState; - (selector: (state: TState) => T): T; - }; -} { +): ResolvedStoreBindings { function useResolvedStoreApi(): StoreApiWithMutators { const contextStore = useContextStore(); return contextStore ?? globalStoreApi; @@ -59,9 +45,7 @@ export function createResolvedStoreHooks = []> { + useResolvedStoreApi: () => StoreApiWithMutators; + useResolvedValue: StoreValueHook; + useResolvedStorePlain: StorePlainHook; +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 3746885..a64b2bf 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,4 +1,5 @@ export { createResolvedStoreHooks } from "./createResolvedStoreHooks"; +export type { ResolvedStoreBindings } from "./createResolvedStoreHooks.types"; export { useSelectorWithEquality } from "./useSelectorWithEquality"; export type { UseSelectorWithEqualityEqualityFn, @@ -7,3 +8,4 @@ export type { UseSelectorWithEqualitySelector, } from "./useSelectorWithEquality"; export { useStoreSelection, useStoreSelectionPlain } from "./storeSelection"; +export type { StorePlainHook, StoreValueHook } from "./storeSelection"; diff --git a/src/hooks/storeSelection/index.ts b/src/hooks/storeSelection/index.ts index 54ffb10..c44cb00 100644 --- a/src/hooks/storeSelection/index.ts +++ b/src/hooks/storeSelection/index.ts @@ -1 +1,2 @@ export { useStoreSelection, useStoreSelectionPlain } from "./useStoreSelection"; +export type { StorePlainHook, StoreValueHook } from "./storeSelection.types"; diff --git a/src/hooks/storeSelection/storeSelection.types.ts b/src/hooks/storeSelection/storeSelection.types.ts index 2fcc2af..b1398eb 100644 --- a/src/hooks/storeSelection/storeSelection.types.ts +++ b/src/hooks/storeSelection/storeSelection.types.ts @@ -4,3 +4,13 @@ export type SelectionStore = Pick< StoreApi, "getState" | "getInitialState" | "subscribe" >; + +export interface StoreValueHook { + (): TState; + (selector: (state: TState) => T, equalityFn?: (left: T, right: T) => boolean): T; +} + +export interface StorePlainHook { + (): TState; + (selector: (state: TState) => T): T; +} diff --git a/src/index.ts b/src/index.ts index 79eee7e..74a73c3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,22 @@ // Core utilities export { createShallowStore, createStoreToolkit } from "./core"; +export type { ShallowStoreBindings, StoreToolkit } from "./core"; // Provider utilities export { createStoreProvider } from "./providers"; +export type { StoreProviderConfig, StoreProviderProps, StoreProviderResult } from "./providers"; // Hook utilities export { createResolvedStoreHooks } from "./hooks"; +export type { ResolvedStoreBindings, StorePlainHook, StoreValueHook } from "./hooks"; // React 19 utilities export { createTransitionAction, useActionStateAdapter, useOptimisticReducer } from "./react19"; -// Types +// Shared store types export type { MutatorsStateCreator, - ShallowStoreBindings, SimpleStateCreator, StoreApiWithMutators, StoreMutatorTuple, - StorePlainHook, - StoreProviderConfig, - StoreProviderProps, - StoreProviderResult, - StoreToolkit, - StoreValueHook, } from "./types"; diff --git a/src/providers/__tests__/createStoreProvider.test.tsx b/src/providers/__tests__/createStoreProvider.test.tsx index 4b954f5..129eeb0 100644 --- a/src/providers/__tests__/createStoreProvider.test.tsx +++ b/src/providers/__tests__/createStoreProvider.test.tsx @@ -9,10 +9,6 @@ interface TestStore { increment: () => void; } -interface LegacyProviderProps { - onStoreCreate?: () => void; -} - describe("createStoreProvider", () => { it("should create provider and context hooks", () => { const { @@ -226,31 +222,6 @@ describe("createStoreProvider", () => { }); }); - it("should keep deprecated onStoreCreate as onStoreReady alias", async () => { - let calls = 0; - - const { Provider, useContextStore } = createStoreProvider((set) => ({ - value: 0, - increment: () => set((state) => ({ value: state.value + 1 })), - })); - - const legacyProps: LegacyProviderProps = { - onStoreCreate: () => { - calls += 1; - }, - }; - - const wrapper = ({ children }: PropsWithChildren) => ( - {children} - ); - - renderHook(() => useContextStore((state) => state.value), { wrapper }); - - await waitFor(() => { - expect(calls).toBe(1); - }); - }); - it("should support custom equality for context selector hook", () => { const { Provider, useContextStoreApi, useContextStore } = createStoreProvider( (set) => ({ diff --git a/src/providers/createStoreProvider.tsx b/src/providers/createStoreProvider.tsx index 2a26496..26e11e5 100644 --- a/src/providers/createStoreProvider.tsx +++ b/src/providers/createStoreProvider.tsx @@ -1,20 +1,8 @@ import { type ReactNode, createContext, useContext, useEffect, useRef, useState } from "react"; import { createStore } from "zustand"; import { useStoreSelection, useStoreSelectionPlain } from "../hooks"; -import type { - MutatorsStateCreator, - StoreApiWithMutators, - StoreMutatorTuple, - StoreProviderProps, - StoreProviderResult, -} from "../types"; - -type ProviderRuntimeProps> = Omit< - StoreProviderProps, - "onStoreCreate" -> & { - onStoreCreate?: (store: StoreApiWithMutators) => void; -}; +import type { MutatorsStateCreator, StoreApiWithMutators, StoreMutatorTuple } from "../types"; +import type { StoreProviderProps, StoreProviderResult } from "./createStoreProvider.types"; /** * Creates a React Context provider for isolated Zustand store instances. @@ -168,8 +156,7 @@ export function createStoreProvider): ReactNode { + }: StoreProviderProps): ReactNode { const isReadyRef = useRef(false); const [store] = useState>(() => { const newStore = createStore(storeCreator); @@ -178,13 +165,11 @@ export function createStoreProvider { - const readyCallback = onStoreReady ?? onStoreCreate; - - if (!isReadyRef.current && readyCallback) { - readyCallback(store); + if (!isReadyRef.current && onStoreReady) { + onStoreReady(store); isReadyRef.current = true; } - }, [onStoreCreate, onStoreReady, store]); + }, [onStoreReady, store]); return {children}; } @@ -234,11 +219,9 @@ export function createStoreProvider = [], +> { + /** Pure synchronous initialization invoked when the store instance is created. */ + onStoreInit?: (store: StoreApiWithMutators) => void; + /** Post-commit callback invoked at most once for each provider store instance. */ + onStoreReady?: (store: StoreApiWithMutators) => void; +} + +/** Props for a generated provider. */ +export interface StoreProviderProps< + TState = unknown, + TMutators extends Array = [], +> extends StoreProviderConfig { + children: ReactNode; +} + +/** Bindings returned by the store provider factory. */ +export interface StoreProviderResult = []> { + Provider: (props: StoreProviderProps) => ReactNode; + useContextStoreApi: () => StoreApiWithMutators; + useContextStore: StoreValueHook; + useContextStorePlain: StorePlainHook; + useIsInsideProvider: () => boolean; + useContextStoreOptional: () => StoreApiWithMutators | null; +} diff --git a/src/providers/index.ts b/src/providers/index.ts index 8c6d6dd..c8ac84c 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1 +1,6 @@ export { createStoreProvider } from "./createStoreProvider"; +export type { + StoreProviderConfig, + StoreProviderProps, + StoreProviderResult, +} from "./createStoreProvider.types"; diff --git a/src/types/index.ts b/src/types/index.ts index b0f1d6b..1e5895f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,165 +1,6 @@ -import type { ReactNode } from "react"; -import type { Mutate, StateCreator, StoreApi, StoreMutatorIdentifier } from "zustand"; - -export type StoreMutatorTuple = [StoreMutatorIdentifier, unknown]; -export type StoreApiWithMutators = []> = Mutate< - StoreApi, - TMutators ->; - -export interface StoreValueHook { - (): TState; - (selector: (state: TState) => T, equalityFn?: (a: T, b: T) => boolean): T; -} - -export interface StorePlainHook { - (): TState; - (selector: (state: TState) => T): T; -} - -/** - * Store bindings with shallow comparison built-in - */ -export interface ShallowStoreBindings = []> { - /** - * Hook to access store state with automatic shallow comparison - * Can be used with or without selector - */ - useStore: StoreValueHook; - /** - * Hook to access store state with plain Zustand selector semantics - */ - useStorePlain: StorePlainHook; - /** - * Direct access to store API for advanced usage - */ - useStoreApi: StoreApiWithMutators; -} - -/** - * Configuration for store provider - */ -export interface StoreProviderConfig< - TState = unknown, - TMutators extends Array = [], -> { - /** - * Pure synchronous initialization hook invoked when the store instance is created. - * This callback must stay idempotent and side-effect free. - */ - onStoreInit?: (store: StoreApiWithMutators) => void; - /** - * Post-commit lifecycle hook for side effects that need a ready store instance. - * Called at most once per provider store instance. May be provided after the - * initial render if no ready callback has already run. - */ - onStoreReady?: (store: StoreApiWithMutators) => void; - /** - * @deprecated Use `onStoreReady` for post-commit side effects. - * Ignored when `onStoreReady` is also provided. - */ - onStoreCreate?: (store: StoreApiWithMutators) => void; -} - -/** - * Props for generated provider component - */ -export interface StoreProviderProps< - TState = unknown, - TMutators extends Array = [], -> extends StoreProviderConfig { - children: ReactNode; -} - -/** - * Result of createStoreProvider factory - */ -export interface StoreProviderResult = []> { - /** - * Provider component to wrap your app/subtree - */ - Provider: (props: StoreProviderProps) => ReactNode; - /** - * Hook to access store from context (throws if outside provider) - */ - useContextStoreApi: () => StoreApiWithMutators; - /** - * @deprecated Use `useContextStoreApi`. - */ - useContext: () => StoreApiWithMutators; - /** - * Hook to access store with selector from context - */ - useContextStore: StoreValueHook; - /** - * Hook to access store with plain Zustand selector semantics from context - */ - useContextStorePlain: StorePlainHook; - /** - * Check if component is inside provider - */ - useIsInsideProvider: () => boolean; - /** - * Hook to access store from context (returns null if outside provider) - */ - useContextStoreOptional: () => StoreApiWithMutators | null; - /** - * @deprecated Use `useContextStoreOptional`. - */ - useOptionalContext: () => StoreApiWithMutators | null; -} - -/** - * Combined result with both global and provider capabilities - */ -export interface StoreToolkit< - TState, - TMutators extends Array = [], -> extends ShallowStoreBindings { - /** - * Shared provider toolkit for isolated store instances. - */ - provider: StoreProviderResult; - /** - * Get the shared provider toolkit. - * Multiple calls return the same provider/context hooks. - */ - getProvider: () => StoreProviderResult; - /** - * @deprecated Use `provider` or `getProvider()`. - */ - createProvider: () => StoreProviderResult; - /** - * Hook that resolves to context store if inside provider, otherwise global store - */ - useResolvedStoreApi: () => StoreApiWithMutators; - /** - * @deprecated Use `useResolvedStoreApi`. - */ - useResolvedStore: () => StoreApiWithMutators; - /** - * Hook that resolves store and applies selector with shallow comparison - */ - useResolvedValue: StoreValueHook; - /** - * @deprecated Use `useResolvedValue`. - */ - useResolvedStoreWithSelector: StoreValueHook; - /** - * Hook that resolves store and applies plain Zustand selector semantics - */ - useResolvedStorePlain: StorePlainHook; -} - -/** - * Type alias for StateCreator without mutators for simpler typing - */ -export type SimpleStateCreator = StateCreator; - -/** - * Type alias for StateCreator with mutators support - */ -export type MutatorsStateCreator< - TState, - TMutators extends Array = [], -> = StateCreator; +export type { + MutatorsStateCreator, + SimpleStateCreator, + StoreApiWithMutators, + StoreMutatorTuple, +} from "./store"; diff --git a/src/types/store.ts b/src/types/store.ts new file mode 100644 index 0000000..dec158b --- /dev/null +++ b/src/types/store.ts @@ -0,0 +1,17 @@ +import type { Mutate, StateCreator, StoreApi, StoreMutatorIdentifier } from "zustand"; + +export type StoreMutatorTuple = [StoreMutatorIdentifier, unknown]; + +export type StoreApiWithMutators = []> = Mutate< + StoreApi, + TMutators +>; + +/** Type alias for StateCreator without mutators. */ +export type SimpleStateCreator = StateCreator; + +/** Type alias for StateCreator with mutators support. */ +export type MutatorsStateCreator< + TState, + TMutators extends Array = [], +> = StateCreator; diff --git a/src/types/typecheck/middlewareCompatibility.ts b/src/types/typecheck/middlewareCompatibility.ts deleted file mode 100644 index 9a243be..0000000 --- a/src/types/typecheck/middlewareCompatibility.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { createJSONStorage, devtools, persist, subscribeWithSelector } from "zustand/middleware"; -import { immer } from "zustand/middleware/immer"; -import { createShallowStore, createStoreToolkit } from "../../core"; -import { createStoreProvider } from "../../providers"; - -interface CounterState { - count: number; - increment: () => void; -} - -type Assert = T; -type HasKey = K extends keyof T ? true : false; -type IsCallable = T extends (...args: Array) => unknown ? true : false; - -const storage = createJSONStorage(() => ({ - getItem: () => null, - setItem: () => undefined, - removeItem: () => undefined, -})); - -const _devtoolsStore = createShallowStore( - devtools( - (set) => ({ - count: 0, - increment: () => { - set((state) => ({ count: state.count + 1 })); - }, - }), - { name: "DevtoolsStore" } - ) -); - -type DevtoolsCleanupAssertion = Assert< - IsCallable ->; - -const _persistStore = createShallowStore( - persist( - (set) => ({ - count: 0, - increment: () => { - set((state) => ({ count: state.count + 1 })); - }, - }), - { name: "PersistStore", storage } - ) -); - -type PersistApiAssertion = Assert>; - -const _subscribeStore = createShallowStore< - CounterState, - [["zustand/subscribeWithSelector", never]] ->( - subscribeWithSelector((set) => ({ - count: 0, - increment: () => { - set((state) => ({ count: state.count + 1 })); - }, - })) -); - -const _unsubscribe = _subscribeStore.useStoreApi.subscribe( - (state) => state.count, - (selected, previous) => { - const nextCount: number = selected; - const prevCount: number = previous; - - void nextCount; - void prevCount; - }, - { fireImmediately: true } -); - -void _unsubscribe; - -const _immerStore = createShallowStore( - immer((set) => ({ - count: 0, - increment: () => { - set({ count: 1 }); - }, - })) -); - -const _immerUpdater: Parameters[0] = ( - draft: CounterState -) => { - draft.count += 1; -}; - -void _immerUpdater; - -type CombinedMutators = [["zustand/persist", CounterState], ["zustand/devtools", never]]; - -const combinedCreator = persist( - devtools( - (set) => ({ - count: 0, - increment: () => { - set((state) => ({ count: state.count + 1 })); - }, - }), - { name: "CombinedStore" } - ), - { name: "CombinedStore", storage } -); - -const _provider = createStoreProvider(combinedCreator); -const _toolkit = createStoreToolkit(combinedCreator); - -type ProviderPersistAssertion = Assert< - HasKey, "persist"> ->; -type ProviderDevtoolsAssertion = Assert< - HasKey, "devtools"> ->; -type ToolkitPersistAssertion = Assert>; -type ToolkitDevtoolsAssertion = Assert>; -type ResolvedPersistAssertion = Assert< - HasKey, "persist"> ->; - -export type MiddlewareCompatibilityAssertions = [ - DevtoolsCleanupAssertion, - PersistApiAssertion, - ProviderPersistAssertion, - ProviderDevtoolsAssertion, - ToolkitPersistAssertion, - ToolkitDevtoolsAssertion, - ResolvedPersistAssertion, -];