Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/petite-wombats-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@codebelt/classy-store": minor
---

refactor: rename `useStore` to `useClassyStore`
14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ packages/classy-store/src/
├── collections/collections.ts # ReactiveMap and ReactiveSet (array-backed Map/Set emulation)
├── collections/index.ts # Collections barrel: reactiveMap, reactiveSet, ReactiveMap, ReactiveSet
├── frameworks/
│ ├── react/react.ts # Layer 3 (React): useStore(), useLocalStore() via useSyncExternalStore
│ ├── vue/vue.ts # Vue: useStore() → ShallowRef<Snapshot<T>> (onUnmounted cleanup)
│ ├── react/react.ts # Layer 3 (React): useClassyStore(), useLocalStore() via useSyncExternalStore
│ ├── vue/vue.ts # Vue: useClassyStore() → ShallowRef<Snapshot<T>> (onUnmounted cleanup)
│ ├── svelte/svelte.ts # Svelte: toSvelteStore() → ClassyReadable<Snapshot<T>>
│ ├── solid/solid.ts # Solid: useStore() → () => Snapshot<T> signal (onCleanup)
│ ├── solid/solid.ts # Solid: useClassyStore() → () => Snapshot<T> signal (onCleanup)
│ └── angular/angular.ts # Angular: injectStore() → Signal<Snapshot<T>> (DestroyRef)
└── utils/
├── index.ts # Utils barrel: persist, devtools, subscribeKey, withHistory
Expand Down Expand Up @@ -101,18 +101,18 @@ Enforced by Biome 2.4.0 (`biome.json` at repo root):
- **Computed memoization:** two layers — the write proxy caches getter results keyed on dependency versions/values; the snapshot layer adds cross-snapshot caching using structural sharing reference equality.
- **Structural sharing:** unchanged sub-trees reuse the previous frozen snapshot reference. This makes `Object.is` comparisons in selectors efficient without `shallowEqual`.
- **Version numbers:** monotonically increasing integers stored per proxy node. Child mutations propagate version bumps up to the root. The snapshot cache is keyed on version — a cache hit is O(1).
- **React auto-tracking:** `useStore(store)` (no selector) wraps the snapshot in a `proxy-compare` tracking proxy. Only properties the component actually reads trigger re-renders. `proxy-compare` is the library's only production dependency.
- **React auto-tracking:** `useClassyStore(store)` (no selector) wraps the snapshot in a `proxy-compare` tracking proxy. Only properties the component actually reads trigger re-renders. `proxy-compare` is the library's only production dependency.

## Package Export Entry Points

| Import path | Contents |
|---|---|
| `@codebelt/classy-store` | `createClassyStore`, `snapshot`, `subscribe`, `getVersion`, `shallowEqual`, `Snapshot` type |
| `@codebelt/classy-store/collections` | `reactiveMap`, `reactiveSet`, `ReactiveMap` type, `ReactiveSet` type |
| `@codebelt/classy-store/react` | `useStore`, `useLocalStore` |
| `@codebelt/classy-store/vue` | `useStore` (ShallowRef) |
| `@codebelt/classy-store/react` | `useClassyStore`, `useLocalStore` |
| `@codebelt/classy-store/vue` | `useClassyStore` (ShallowRef) |
| `@codebelt/classy-store/svelte` | `toSvelteStore` (ClassyReadable) |
| `@codebelt/classy-store/solid` | `useStore` (signal getter) |
| `@codebelt/classy-store/solid` | `useClassyStore` (signal getter) |
| `@codebelt/classy-store/angular` | `injectStore` (Signal) |
| `@codebelt/classy-store/utils` | `persist`, `devtools`, `subscribeKey`, `withHistory` |

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const cartStore = createClassyStore(new CartStore());

// 3. Use in any framework — React shown here
function CartTotal() {
const total = useStore(cartStore, (state) => state.total);
const total = useClassyStore(cartStore, (state) => state.total);
return <span>${total.toFixed(2)}</span>;
}

Expand Down Expand Up @@ -88,7 +88,7 @@ This library wouldn't exist without the ideas pioneered by these projects. Each

**[Valtio](https://github.com/pmndrs/valtio)** — Daishi Kato's proxy-based masterpiece gave us the core architectural pattern: a mutable write proxy for ergonomic mutations paired with immutable snapshots for React integration. Valtio's structural sharing approach — where unchanged sub-trees keep the same frozen reference across snapshots — is what makes `Object.is` selectors efficient without custom equality. We also adopted its `proxy-compare` library for automatic property tracking in selectorless mode.

**[Zustand](https://github.com/pmndrs/zustand)** — Also by Daishi Kato, Zustand set the standard for minimal, hook-first state management. Its selector pattern (`useStore(store, s => s.count)`) with `Object.is` equality is what we use in selector mode. Zustand proved that you don't need Providers, context wrappers, or HOCs — just a hook and a store. Its focus on tiny bundle size pushed us to keep things lean.
**[Zustand](https://github.com/pmndrs/zustand)** — Also by Daishi Kato, Zustand set the standard for minimal, hook-first state management. Its selector pattern (`useClassyStore(store, s => s.count)`) with `Object.is` equality is what we use in selector mode. Zustand proved that you don't need Providers, context wrappers, or HOCs — just a hook and a store. Its focus on tiny bundle size pushed us to keep things lean.

**[proxy-compare](https://github.com/dai-shi/proxy-compare)** — The ~1KB utility (also by Dai-shi) that powers our auto-tracked mode. It wraps frozen snapshot objects in a tracking proxy, recording which properties a component reads, then efficiently diffs only those properties between snapshots. This eliminates the need for manual selectors in most cases.

Expand Down
12 changes: 6 additions & 6 deletions examples/nextjs/src/components/dashboard/DashboardOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import {shallowEqual} from '@codebelt/classy-store';
import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import {plannerStore} from '@/stores/planner-store';
import {recipeStore} from '@/stores/recipe-store';
Expand Down Expand Up @@ -62,7 +62,7 @@ const Icons = {
};

export function DashboardOverview() {
const recipeStats = useStore(
const recipeStats = useClassyStore(
recipeStore,
(state) => ({
count: state.recipeCount,
Expand All @@ -71,7 +71,7 @@ export function DashboardOverview() {
shallowEqual,
);

const shoppingStats = useStore(
const shoppingStats = useClassyStore(
shoppingStore,
(state) => ({
total: state.totalItems,
Expand All @@ -80,7 +80,7 @@ export function DashboardOverview() {
shallowEqual,
);

const plannerStats = useStore(
const plannerStats = useClassyStore(
plannerStore,
(state) => ({
meals: state.totalMealsPlanned,
Expand Down Expand Up @@ -122,9 +122,9 @@ export function DashboardOverview() {
<h2 className="text-xl font-semibold tracking-tight">Overview</h2>
<ApiInfo
minimal={true}
apis={['useStore', 'selector', 'shallowEqual']}
apis={['useClassyStore', 'selector', 'shallowEqual']}
description="Reads from 3 stores using selectors with shallowEqual."
code={`const stats = useStore(recipeStore, (state) => ({
code={`const stats = useClassyStore(recipeStore, (state) => ({
count: state.recipeCount,
avgTime: state.averageTotalTime,
}), shallowEqual);`}
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/editor/EditorPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import type {RecipeEditorStore} from '@/stores/recipe-editor-store';

export function EditorPreview({store}: {store: RecipeEditorStore}) {
const snap = useStore(store);
const snap = useClassyStore(store);

return (
<div className="bg-card border border-border rounded-lg p-4 space-y-4 h-fit sticky top-4">
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/editor/HistoryControls.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import type {HistoryHandle} from '@codebelt/classy-store/utils';
import {ApiInfo} from '@/components/shared/ApiInfo';

Expand All @@ -12,7 +12,7 @@ export function HistoryControls({
store: object;
}) {
// Subscribe to store changes so we re-render when canUndo/canRedo changes.
useStore(store);
useClassyStore(store);

return (
<div className="bg-card border border-border rounded-lg p-4 space-y-3">
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/editor/IngredientEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import type {RecipeEditorStore} from '@/stores/recipe-editor-store';

export function IngredientEditor({store}: {store: RecipeEditorStore}) {
const snap = useStore(store);
const snap = useClassyStore(store);

return (
<div className="bg-card border border-border rounded-lg p-4 space-y-3">
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/editor/InstructionEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import type {RecipeEditorStore} from '@/stores/recipe-editor-store';

export function InstructionEditor({store}: {store: RecipeEditorStore}) {
const snap = useStore(store);
const snap = useClassyStore(store);

return (
<div className="bg-card border border-border rounded-lg p-4 space-y-3">
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/editor/RecipeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import {useLocalStore, useStore} from '@codebelt/classy-store/react';
import {useClassyStore, useLocalStore} from '@codebelt/classy-store/react';
import type {HistoryHandle} from '@codebelt/classy-store/utils';
import {withHistory} from '@codebelt/classy-store/utils';
import {useRef} from 'react';
Expand All @@ -17,7 +17,7 @@ export function RecipeEditor() {
if (!historyRef.current) {
historyRef.current = withHistory(store, {limit: 30});
}
const snap = useStore(store);
const snap = useClassyStore(store);

return (
<div className="space-y-4">
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/features/FeatureMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const sections: FeatureSection[] = [
title: 'React Bindings',
rows: [
{
api: 'useStore() auto-tracked',
api: 'useClassyStore() auto-tracked',
description:
'Proxy-based tracking \u2014 only re-renders when accessed properties change',
links: [
Expand All @@ -76,7 +76,7 @@ const sections: FeatureSection[] = [
],
},
{
api: 'useStore() with selector',
api: 'useClassyStore() with selector',
description:
'Selector extracts derived values; combined with shallowEqual for efficiency',
links: [
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/nav/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import Link from 'next/link';
import {usePathname} from 'next/navigation';
import {shoppingStore} from '@/stores/shopping-store';
Expand All @@ -17,7 +17,7 @@ const links = [

export function NavBar() {
const pathname = usePathname();
const shoppingSnap = useStore(shoppingStore);
const shoppingSnap = useClassyStore(shoppingStore);

return (
<nav className="sticky top-0 z-50 w-full border-b border-white/10 bg-black/50 backdrop-blur-xl supports-[backdrop-filter]:bg-black/20">
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/nav/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {useEffect, useState} from 'react';
import {settingsStore} from '@/stores/settings-store';

Expand All @@ -17,7 +17,7 @@ const next: Record<string, 'light' | 'dark' | 'system'> = {
};

export function ThemeToggle() {
const snap = useStore(settingsStore, (state) => state.theme);
const snap = useClassyStore(settingsStore, (state) => state.theme);
const [mounted, setMounted] = useState(false);

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/planner/MealSlotPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import type {DayName, DayPlan} from '@/stores/planner-store';
import {plannerStore} from '@/stores/planner-store';
import type {Recipe} from '@/stores/recipe-store';
Expand All @@ -15,7 +15,7 @@ export function MealSlotPicker({
slot: keyof DayPlan;
currentId: string | null;
}) {
const recipeSnap = useStore(recipeStore);
const recipeSnap = useClassyStore(recipeStore);
const recipes = (
recipeSnap.recipes._entries as ReadonlyArray<readonly [string, Recipe]>
).map(([, r]) => r);
Expand Down
6 changes: 3 additions & 3 deletions examples/nextjs/src/components/planner/PlannerGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {Fragment} from 'react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import type {DayName, DayPlan} from '@/stores/planner-store';
Expand All @@ -10,14 +10,14 @@ import {MealSlotPicker} from './MealSlotPicker';
const SLOTS: (keyof DayPlan)[] = ['breakfast', 'lunch', 'dinner'];

export function PlannerGrid() {
const snap = useStore(plannerStore);
const snap = useClassyStore(plannerStore);

return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<h2 className="font-semibold">Weekly Plan</h2>
<ApiInfo
apis={['useStore (auto-tracked)', 'deep nested objects']}
apis={['useClassyStore (auto-tracked)', 'deep nested objects']}
description="Reads and mutates deeply nested days[day][slot] objects. Cross-store data via MealSlotPicker."
code={`const meal = snap.days[day][slot];
plannerStore.setMeal(day, slot, id, title);`}
Expand Down
6 changes: 3 additions & 3 deletions examples/nextjs/src/components/planner/PlannerStats.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import {plannerStore} from '@/stores/planner-store';

export function PlannerStats() {
const snap = useStore(plannerStore);
const snap = useClassyStore(plannerStore);

return (
<div className="space-y-2">
<ApiInfo
apis={['useStore', 'computed getters']}
apis={['useClassyStore', 'computed getters']}
description="Displays computed stats: totalMealsPlanned, completionPercentage."
code={`get totalMealsPlanned() { ... }
get completionPercentage() { ... }`}
Expand Down
6 changes: 3 additions & 3 deletions examples/nextjs/src/components/recipes/RecipeCard.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
'use client';

import {shallowEqual} from '@codebelt/classy-store';
import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import type {Recipe} from '@/stores/recipe-store';
import {recipeStore} from '@/stores/recipe-store';

export function RecipeCard({recipeId}: {recipeId: string}) {
const recipe = useStore(
const recipe = useClassyStore(
recipeStore,
(state) => {
// Access _entries directly — snapshot freezes ReactiveMap into a plain
Expand Down Expand Up @@ -38,7 +38,7 @@ export function RecipeCard({recipeId}: {recipeId: string}) {
<ApiInfo
minimal={true}
alignment="left"
apis={['useStore', 'selector', 'shallowEqual']}
apis={['useClassyStore', 'selector', 'shallowEqual']}
description="Selector extracts a single recipe by ID; shallowEqual prevents re-renders when unrelated recipes change."
/>
</div>
Expand Down
8 changes: 4 additions & 4 deletions examples/nextjs/src/components/recipes/RecipeFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import {recipeStore} from '@/stores/recipe-store';

export function RecipeFilter() {
const snap = useStore(recipeStore);
const snap = useClassyStore(recipeStore);

return (
<div className="glass-card p-6 space-y-4 animate-slide-up-delay-1">
<div className="flex items-center justify-between">
<h2 className="font-semibold text-lg tracking-tight">Filter Recipes</h2>
<ApiInfo
alignment="left"
apis={['useStore (auto-tracked)', 'deep nested mutation']}
apis={['useClassyStore (auto-tracked)', 'deep nested mutation']}
description="Auto-tracked mode — only re-renders when accessed properties change. Mutates nested store.filter.searchTerm directly."
code={`const snap = useStore(recipeStore);
code={`const snap = useClassyStore(recipeStore);
// deep nested mutation:
recipeStore.filter.maxPrepTime = Number(value) || 0;`}
/>
Expand Down
10 changes: 5 additions & 5 deletions examples/nextjs/src/components/recipes/RecipeList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use client';

import {useStore} from '@codebelt/classy-store/react';
import {useClassyStore} from '@codebelt/classy-store/react';
import {ApiInfo} from '@/components/shared/ApiInfo';
import {recipeStore} from '@/stores/recipe-store';
import {RecipeCard} from './RecipeCard';

export function RecipeList() {
const snap = useStore(recipeStore);
const snap = useClassyStore(recipeStore);

return (
<div className="space-y-6">
Expand All @@ -32,9 +32,9 @@ export function RecipeList() {

<ApiInfo
alignment="left"
apis={['useStore (auto-tracked)', 'computed getters']}
description="Auto-tracked useStore reads computed getters like filteredRecipes and recipeCount."
code={`const snap = useStore(recipeStore);
apis={['useClassyStore (auto-tracked)', 'computed getters']}
description="Auto-tracked useClassyStore reads computed getters like filteredRecipes and recipeCount."
code={`const snap = useClassyStore(recipeStore);
snap.filteredRecipes // computed getter
snap.recipeCount // computed getter`}
/>
Expand Down
Loading
Loading