@@ -30,7 +30,7 @@ function CountCard() {
}
function NameCard() {
- const name = useStore(counterStore, (s) => s.name);
+ const name = useStore(counterStore, (store) => store.name);
const renders = useRenderCount();
return (
@@ -48,7 +48,7 @@ function NameCard() {
}
function BatchCard() {
- const count = useStore(batchStore, (s) => s.count);
+ const count = useStore(batchStore, (store) => store.count);
const renders = useRenderCount();
return (
diff --git a/examples/rendering/src/StructuralSharingDemo.tsx b/examples/rendering/src/StructuralSharingDemo.tsx
index 1d282d5..9bd986f 100644
--- a/examples/rendering/src/StructuralSharingDemo.tsx
+++ b/examples/rendering/src/StructuralSharingDemo.tsx
@@ -85,21 +85,33 @@ function SnapshotTree() {
Snapshot Reference Tree
-
s}>
- s.metadata} depth={1} />
- s.content} depth={1}>
+ store}>
+ store.metadata}
+ depth={1}
+ />
+ store.content}
+ depth={1}
+ >
s.content.sections[0]}
+ getValue={(store) => store.content.sections[0]}
depth={2}
/>
s.content.sections[1]}
+ getValue={(store) => store.content.sections[1]}
depth={2}
/>
- s.settings} depth={1} />
+ store.settings}
+ depth={1}
+ />
);
diff --git a/examples/rendering/src/persistStores.ts b/examples/rendering/src/persistStores.ts
index 0f9bdcb..2895f0c 100644
--- a/examples/rendering/src/persistStores.ts
+++ b/examples/rendering/src/persistStores.ts
@@ -1,4 +1,8 @@
-import {reactiveMap, reactiveSet, store} from '@codebelt/classy-store';
+import {
+ createClassyStore,
+ reactiveMap,
+ reactiveSet,
+} from '@codebelt/classy-store';
import {persist} from '@codebelt/classy-store/utils';
// ── Simple Store ────────────────────────────────────────────────────────────
@@ -43,7 +47,7 @@ export class PreferencesStore {
}
}
-export const preferencesStore = store(new PreferencesStore());
+export const preferencesStore = createClassyStore(new PreferencesStore());
export const preferencesHandle = persist(preferencesStore, {
name: 'preferences',
@@ -145,7 +149,7 @@ export class KitchenSinkStore {
}
}
-export const kitchenSinkStore = store(new KitchenSinkStore());
+export const kitchenSinkStore = createClassyStore(new KitchenSinkStore());
export const kitchenSinkHandle = persist(kitchenSinkStore, {
name: 'kitchen-sink',
diff --git a/examples/rendering/src/stores.ts b/examples/rendering/src/stores.ts
index b3ef62b..a83f1c0 100644
--- a/examples/rendering/src/stores.ts
+++ b/examples/rendering/src/stores.ts
@@ -1,4 +1,8 @@
-import {reactiveMap, reactiveSet, store} from '@codebelt/classy-store';
+import {
+ createClassyStore,
+ reactiveMap,
+ reactiveSet,
+} from '@codebelt/classy-store';
export class CounterStore {
count = 0;
@@ -84,7 +88,7 @@ export class DocumentStore {
}
updateSectionBody(id: number, body: string) {
- const section = this.content.sections.find((s) => s.id === id);
+ const section = this.content.sections.find((store) => store.id === id);
if (section) section.body = body;
}
@@ -145,7 +149,7 @@ export class CollectionStore {
}
}
-export const counterStore = store(new CounterStore());
-export const postStore = store(new PostStore());
-export const documentStore = store(new DocumentStore());
-export const collectionStore = store(new CollectionStore());
+export const counterStore = createClassyStore(new CounterStore());
+export const postStore = createClassyStore(new PostStore());
+export const documentStore = createClassyStore(new DocumentStore());
+export const collectionStore = createClassyStore(new CollectionStore());
diff --git a/src/collections/collections.test.ts b/src/collections/collections.test.ts
index 35a6ebd..df9ed57 100644
--- a/src/collections/collections.test.ts
+++ b/src/collections/collections.test.ts
@@ -1,5 +1,5 @@
import {describe, expect, test} from 'bun:test';
-import {store, subscribe} from '../core/core';
+import {createClassyStore, subscribe} from '../core/core';
import {snapshot} from '../snapshot/snapshot';
import {reactiveMap, reactiveSet} from './collections';
@@ -87,7 +87,7 @@ describe('reactiveMap()', () => {
});
test('triggers store subscription on set', async () => {
- const s = store({users: reactiveMap
()});
+ const s = createClassyStore({users: reactiveMap()});
let count = 0;
subscribe(s, () => count++);
@@ -98,7 +98,7 @@ describe('reactiveMap()', () => {
});
test('triggers store subscription on delete', async () => {
- const s = store({
+ const s = createClassyStore({
users: reactiveMap([['id1', 'Alice']] as [string, string][]),
});
let count = 0;
@@ -111,7 +111,7 @@ describe('reactiveMap()', () => {
});
test('triggers store subscription on clear', async () => {
- const s = store({
+ const s = createClassyStore({
users: reactiveMap([
['a', 1],
['b', 2],
@@ -127,7 +127,9 @@ describe('reactiveMap()', () => {
});
test('snapshot captures map data', async () => {
- const s = store({m: reactiveMap([['k', 'v']] as [string, string][])});
+ const s = createClassyStore({
+ m: reactiveMap([['k', 'v']] as [string, string][]),
+ });
const snap = snapshot(s);
expect(snap.m._entries).toEqual([['k', 'v']]);
// Snapshot is frozen
@@ -188,7 +190,7 @@ describe('reactiveSet()', () => {
});
test('triggers store subscription on add', async () => {
- const s = store({tags: reactiveSet()});
+ const s = createClassyStore({tags: reactiveSet()});
let count = 0;
subscribe(s, () => count++);
@@ -199,7 +201,7 @@ describe('reactiveSet()', () => {
});
test('triggers store subscription on delete', async () => {
- const s = store({tags: reactiveSet(['a', 'b'])});
+ const s = createClassyStore({tags: reactiveSet(['a', 'b'])});
let count = 0;
subscribe(s, () => count++);
@@ -210,7 +212,7 @@ describe('reactiveSet()', () => {
});
test('triggers store subscription on clear', async () => {
- const s = store({tags: reactiveSet(['a', 'b'])});
+ const s = createClassyStore({tags: reactiveSet(['a', 'b'])});
let count = 0;
subscribe(s, () => count++);
@@ -221,7 +223,7 @@ describe('reactiveSet()', () => {
});
test('snapshot captures set data', async () => {
- const s = store({t: reactiveSet([1, 2, 3])});
+ const s = createClassyStore({t: reactiveSet([1, 2, 3])});
const snap = snapshot(s);
expect(snap.t._items).toEqual([1, 2, 3]);
expect(Object.isFrozen(snap.t)).toBe(true);
@@ -245,7 +247,7 @@ describe('collections inside class store', () => {
}
test('class methods mutate collections reactively', async () => {
- const s = store(new ProjectStore());
+ const s = createClassyStore(new ProjectStore());
let count = 0;
subscribe(s, () => count++);
diff --git a/src/collections/collections.ts b/src/collections/collections.ts
index 091b083..916e794 100644
--- a/src/collections/collections.ts
+++ b/src/collections/collections.ts
@@ -3,7 +3,7 @@ import {PROXYABLE} from '../utils/internal/internal';
// ── ReactiveMap ───────────────────────────────────────────────────────────────
/**
- * A Map-like class backed by a plain array so `store()` can proxy mutations.
+ * A Map-like class backed by a plain array so `createClassyStore()` can proxy mutations.
*
* Native `Map` uses internal slots that ES6 Proxy can't intercept, so mutations
* like `.set()` would be invisible to the store. `ReactiveMap` solves this by
@@ -11,7 +11,7 @@ import {PROXYABLE} from '../utils/internal/internal';
*
* Usage:
* ```ts
- * const myStore = store({ users: reactiveMap() });
+ * const myStore = createClassyStore({ users: reactiveMap() });
* myStore.users.set('id1', { name: 'Alice' }); // reactive
* ```
*/
@@ -106,7 +106,7 @@ export class ReactiveMap {
// ── ReactiveSet ───────────────────────────────────────────────────────────────
/**
- * A Set-like class backed by a plain array so `store()` can proxy mutations.
+ * A Set-like class backed by a plain array so `createClassyStore()` can proxy mutations.
*
* Native `Set` uses internal slots that ES6 Proxy can't intercept, so mutations
* like `.add()` would be invisible to the store. `ReactiveSet` solves this by
@@ -114,7 +114,7 @@ export class ReactiveMap {
*
* Usage:
* ```ts
- * const myStore = store({ tags: reactiveSet(['urgent']) });
+ * const myStore = createClassyStore({ tags: reactiveSet(['urgent']) });
* myStore.tags.add('bug'); // reactive
* ```
*/
@@ -198,7 +198,7 @@ export class ReactiveSet {
/**
* Creates a reactive Map-like collection backed by a plain array.
- * Wrap the parent object with `store()` for full reactivity.
+ * Wrap the parent object with `createClassyStore()` for full reactivity.
*
* @param initial - Optional iterable of `[key, value]` pairs.
*/
@@ -210,7 +210,7 @@ export function reactiveMap(
/**
* Creates a reactive Set-like collection backed by a plain array.
- * Wrap the parent object with `store()` for full reactivity.
+ * Wrap the parent object with `createClassyStore()` for full reactivity.
*
* @param initial - Optional iterable of values.
*/
diff --git a/src/core/computed.test.tsx b/src/core/computed.test.tsx
index 3c63158..fef74e5 100644
--- a/src/core/computed.test.tsx
+++ b/src/core/computed.test.tsx
@@ -3,7 +3,7 @@ import {act, type ReactNode} from 'react';
import {createRoot} from 'react-dom/client';
import {useStore} from '../react/react';
import {snapshot} from '../snapshot/snapshot';
-import {store} from './core';
+import {createClassyStore} from './core';
/** Helper: flush the queueMicrotask-based batching. */
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
@@ -26,7 +26,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const result1 = s.filtered;
const result2 = s.filtered;
@@ -55,7 +55,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.doubled).toBe(10);
expect(callCount).toBe(1);
@@ -83,7 +83,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.doubled).toBe(10);
expect(callCount).toBe(1);
@@ -114,7 +114,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.filteredCount).toBe(3);
expect(filteredCallCount).toBe(1);
@@ -143,7 +143,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.count).toBe(2);
expect(callCount).toBe(1);
@@ -168,7 +168,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.currentTheme).toBe('dark');
expect(callCount).toBe(1);
@@ -199,7 +199,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.sum).toBe(6);
expect(s.average).toBe(2);
@@ -229,7 +229,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
expect(s.label).toBe('derived:5');
// Snapshot should also use the derived getter
@@ -255,7 +255,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
// Write proxy: both getters work
expect(s.doubled).toBe(10); // base getter
@@ -288,7 +288,7 @@ describe('computed getters — write proxy memoization', () => {
// `doubled` is NOT overridden — inherits from Base
}
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
// Override wins for `label`
expect(s.label).toBe('derived:5');
@@ -323,7 +323,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new LevelC());
+ const s = createClassyStore(new LevelC());
// Write proxy
expect(s.squared).toBe(4);
@@ -351,7 +351,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.isPositive).toBe(false);
@@ -380,7 +380,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.count).toBe(3);
expect(callCount).toBe(1);
@@ -408,7 +408,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
// First access: returns undefined, cached.
expect(s.first).toBeUndefined();
@@ -441,7 +441,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.constant).toBe(42);
expect(callCount).toBe(1);
@@ -466,7 +466,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
// First access throws.
expect(() => s.computed).toThrow('intentional error');
@@ -492,7 +492,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.sum).toBe(3);
// Replace the data object entirely (delete is hard to test on child proxies
@@ -514,7 +514,7 @@ describe('computed getters — write proxy memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.hasA).toBe(true);
expect(callCount).toBe(1);
@@ -548,7 +548,7 @@ describe('computed getters — snapshot memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const snap = snapshot(s);
expect(snap.count).toBe(3);
@@ -568,7 +568,7 @@ describe('computed getters — snapshot memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const snap = snapshot(s);
const result1 = snap.filtered;
@@ -593,7 +593,7 @@ describe('computed getters — snapshot memoization', () => {
}
}
- const s = store(new TodoStore());
+ const s = createClassyStore(new TodoStore());
const snap1 = snapshot(s);
const result1 = snap1.activeTodos;
@@ -622,7 +622,7 @@ describe('computed getters — snapshot memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const snap1 = snapshot(s);
expect(snap1.doubled).toBe(10);
@@ -651,7 +651,7 @@ describe('computed getters — snapshot memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const snap1 = snapshot(s);
expect(snap1.count).toBe(3);
@@ -674,7 +674,7 @@ describe('computed getters — snapshot memoization', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const snap = snapshot(s);
expect(snap.fullName).toBe('John Doe');
@@ -720,7 +720,7 @@ describe('computed getters — useStore integration', () => {
}
}
- const todoStore = store(new TodoStore());
+ const todoStore = createClassyStore(new TodoStore());
const renderCount = mock(() => {});
function ActiveList() {
@@ -759,7 +759,7 @@ describe('computed getters — useStore integration', () => {
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
function Display() {
const snap = useStore(s);
diff --git a/src/core/core.test.ts b/src/core/core.test.ts
index 863a8e3..fd1f0ae 100644
--- a/src/core/core.test.ts
+++ b/src/core/core.test.ts
@@ -1,21 +1,21 @@
import {describe, expect, it, mock} from 'bun:test';
-import {getVersion, store, subscribe} from './core';
+import {createClassyStore, getVersion, subscribe} from './core';
/** Helper: flush the queueMicrotask-based batching. */
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
-describe('store() — core reactivity', () => {
+describe('createClassyStore() — core reactivity', () => {
// ── Primitive mutations ───────────────────────────────────────────────────
describe('primitive mutations', () => {
it('reads initial values through the proxy', () => {
- const s = store({count: 0, name: 'hello'});
+ const s = createClassyStore({count: 0, name: 'hello'});
expect(s.count).toBe(0);
expect(s.name).toBe('hello');
});
it('notifies listeners when a primitive property changes', async () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const listener = mock(() => {});
subscribe(s, listener);
@@ -27,7 +27,7 @@ describe('store() — core reactivity', () => {
});
it('does NOT notify when same value is set (noop)', async () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const listener = mock(() => {});
subscribe(s, listener);
@@ -38,7 +38,7 @@ describe('store() — core reactivity', () => {
});
it('bumps version on mutation', async () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const v1 = getVersion(s);
s.count = 1;
@@ -53,7 +53,7 @@ describe('store() — core reactivity', () => {
describe('batching', () => {
it('batches multiple synchronous mutations into one notification', async () => {
- const s = store({a: 0, b: 0, c: 0});
+ const s = createClassyStore({a: 0, b: 0, c: 0});
const listener = mock(() => {});
subscribe(s, listener);
@@ -69,7 +69,7 @@ describe('store() — core reactivity', () => {
});
it('batches array push (multiple set traps) into one notification', async () => {
- const s = store({items: [] as string[]});
+ const s = createClassyStore({items: [] as string[]});
const listener = mock(() => {});
subscribe(s, listener);
@@ -97,7 +97,7 @@ describe('store() — core reactivity', () => {
}
it('methods mutate through the proxy', async () => {
- const s = store(new Counter());
+ const s = createClassyStore(new Counter());
const listener = mock(() => {});
subscribe(s, listener);
@@ -109,7 +109,7 @@ describe('store() — core reactivity', () => {
});
it('methods with arguments work correctly', async () => {
- const s = store(new Counter());
+ const s = createClassyStore(new Counter());
s.add(10);
await flush();
expect(s.count).toBe(10);
@@ -136,13 +136,13 @@ describe('store() — core reactivity', () => {
}
it('getters return computed values', () => {
- const s = store(new Store());
+ const s = createClassyStore(new Store());
expect(s.doubled).toBe(10);
expect(s.isPositive).toBe(true);
});
it('getters reflect mutations', async () => {
- const s = store(new Store());
+ const s = createClassyStore(new Store());
s.setCount(0);
expect(s.doubled).toBe(0);
expect(s.isPositive).toBe(false);
@@ -153,7 +153,9 @@ describe('store() — core reactivity', () => {
describe('deep nested objects', () => {
it('nested object property mutations trigger root listener', async () => {
- const s = store({user: {name: 'Alice', address: {city: 'NYC'}}});
+ const s = createClassyStore({
+ user: {name: 'Alice', address: {city: 'NYC'}},
+ });
const listener = mock(() => {});
subscribe(s, listener);
@@ -165,7 +167,9 @@ describe('store() — core reactivity', () => {
});
it('deeply nested mutations trigger root listener', async () => {
- const s = store({user: {name: 'Alice', address: {city: 'NYC'}}});
+ const s = createClassyStore({
+ user: {name: 'Alice', address: {city: 'NYC'}},
+ });
const listener = mock(() => {});
subscribe(s, listener);
@@ -177,7 +181,7 @@ describe('store() — core reactivity', () => {
});
it('replacing a nested object triggers listener', async () => {
- const s = store({user: {name: 'Alice'}});
+ const s = createClassyStore({user: {name: 'Alice'}});
const listener = mock(() => {});
subscribe(s, listener);
@@ -193,7 +197,7 @@ describe('store() — core reactivity', () => {
describe('array operations', () => {
it('push triggers listener', async () => {
- const s = store({items: ['a']});
+ const s = createClassyStore({items: ['a']});
const listener = mock(() => {});
subscribe(s, listener);
@@ -205,7 +209,7 @@ describe('store() — core reactivity', () => {
});
it('splice triggers listener', async () => {
- const s = store({items: ['a', 'b', 'c']});
+ const s = createClassyStore({items: ['a', 'b', 'c']});
const listener = mock(() => {});
subscribe(s, listener);
@@ -217,7 +221,7 @@ describe('store() — core reactivity', () => {
});
it('index assignment triggers listener', async () => {
- const s = store({items: ['a', 'b']});
+ const s = createClassyStore({items: ['a', 'b']});
const listener = mock(() => {});
subscribe(s, listener);
@@ -229,7 +233,7 @@ describe('store() — core reactivity', () => {
});
it('array of objects: nested mutation triggers listener', async () => {
- const s = store({items: [{name: 'Alice'}, {name: 'Bob'}]});
+ const s = createClassyStore({items: [{name: 'Alice'}, {name: 'Bob'}]});
const listener = mock(() => {});
subscribe(s, listener);
@@ -245,7 +249,7 @@ describe('store() — core reactivity', () => {
describe('subscribe / unsubscribe', () => {
it('unsubscribe stops notifications', async () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const listener = mock(() => {});
const unsub = subscribe(s, listener);
@@ -260,7 +264,7 @@ describe('store() — core reactivity', () => {
});
it('multiple listeners all fire', async () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const listener1 = mock(() => {});
const listener2 = mock(() => {});
subscribe(s, listener1);
@@ -274,7 +278,7 @@ describe('store() — core reactivity', () => {
});
it('subscribe on a child proxy fires when the child mutates', async () => {
- const s = store({user: {name: 'Alice'}});
+ const s = createClassyStore({user: {name: 'Alice'}});
const listener = mock(() => {});
subscribe(s.user, listener);
@@ -285,7 +289,7 @@ describe('store() — core reactivity', () => {
});
it('subscribe on a child proxy fires when a sibling mutates', async () => {
- const s = store({user: {name: 'Alice'}, count: 0});
+ const s = createClassyStore({user: {name: 'Alice'}, count: 0});
const listener = mock(() => {});
subscribe(s.user, listener);
@@ -297,7 +301,7 @@ describe('store() — core reactivity', () => {
});
it('unsubscribe from child proxy stops notifications', async () => {
- const s = store({user: {name: 'Alice'}});
+ const s = createClassyStore({user: {name: 'Alice'}});
const listener = mock(() => {});
const unsub = subscribe(s.user, listener);
@@ -316,7 +320,7 @@ describe('store() — core reactivity', () => {
describe('deleteProperty', () => {
it('deleting a property triggers listener', async () => {
- const s = store({a: 1, b: 2} as Record);
+ const s = createClassyStore({a: 1, b: 2} as Record);
const listener = mock(() => {});
subscribe(s, listener);
@@ -348,7 +352,7 @@ describe('store() — core reactivity', () => {
}
it('base method mutates state reactively on a derived instance', async () => {
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
const listener = mock(() => {});
subscribe(s, listener);
@@ -360,7 +364,7 @@ describe('store() — core reactivity', () => {
});
it('derived method works alongside inherited method', async () => {
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
const listener = mock(() => {});
subscribe(s, listener);
@@ -383,7 +387,7 @@ describe('store() — core reactivity', () => {
}
}
- const s = store(new Extended());
+ const s = createClassyStore(new Extended());
const listener = mock(() => {});
subscribe(s, listener);
@@ -405,7 +409,7 @@ describe('store() — core reactivity', () => {
}
}
- const s = store(new Overrider());
+ const s = createClassyStore(new Overrider());
const listener = mock(() => {});
subscribe(s, listener);
@@ -442,7 +446,7 @@ describe('store() — core reactivity', () => {
}
}
- const s = store(new LevelC());
+ const s = createClassyStore(new LevelC());
const listener = mock(() => {});
subscribe(s, listener);
diff --git a/src/core/core.ts b/src/core/core.ts
index bb08983..6ef6131 100644
--- a/src/core/core.ts
+++ b/src/core/core.ts
@@ -113,7 +113,7 @@ function evaluateComputed(
/**
* Retrieve the internal bookkeeping for a store proxy.
- * Throws if the object was not created with `store()`.
+ * Throws if the object was not created with `createClassyStore()`.
*/
export function getInternal(proxy: object): StoreInternal {
const internal = internalsMap.get(proxy);
@@ -122,13 +122,6 @@ export function getInternal(proxy: object): StoreInternal {
return internal;
}
-/**
- * Returns `true` if the given object is a store proxy.
- */
-export function isStoreProxy(value: unknown): boolean {
- return typeof value === 'object' && value !== null && internalsMap.has(value);
-}
-
// ── Notification batching ─────────────────────────────────────────────────────
/**
@@ -289,8 +282,13 @@ function createStoreProxy(
*
* @param instance - A class instance (or plain object) to make reactive.
* @returns The same object wrapped in a reactive Proxy.
+ *
+ * @example
+ * ```ts
+ * const myStore = createClassyStore(new MyClass());
+ * ```
*/
-export function store(instance: T): T {
+export function createClassyStore(instance: T): T {
return createStoreProxy(instance, null);
}
@@ -298,7 +296,7 @@ export function store(instance: T): T {
* Subscribe to store changes. The callback fires once per batched mutation
* (coalesced via `queueMicrotask`), not once per individual property write.
*
- * @param proxy - A reactive proxy created by `store()`.
+ * @param proxy - A reactive proxy created by `createClassyStore()`.
* @param callback - Invoked after each batched mutation.
* @returns An unsubscribe function. Call it to stop receiving notifications.
*/
diff --git a/src/index.ts b/src/index.ts
index b9cef8f..31fd8a0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -8,7 +8,7 @@
*/
export type {ReactiveMap, ReactiveSet} from './collections/collections';
export {reactiveMap, reactiveSet} from './collections/collections';
-export {getVersion, store, subscribe} from './core/core';
+export {createClassyStore, getVersion, subscribe} from './core/core';
export {snapshot} from './snapshot/snapshot';
export type {Snapshot} from './types';
export {shallowEqual} from './utils/equality/equality';
diff --git a/src/react/react.behavior.test.tsx b/src/react/react.behavior.test.tsx
index 7359ec8..a38367a 100644
--- a/src/react/react.behavior.test.tsx
+++ b/src/react/react.behavior.test.tsx
@@ -1,7 +1,7 @@
import {afterEach, describe, expect, it, mock} from 'bun:test';
import {act, type ReactNode} from 'react';
import {createRoot, type Root} from 'react-dom/client';
-import {store} from '../core/core';
+import {createClassyStore} from '../core/core';
import {shallowEqual} from '../utils/equality/equality';
import {useStore} from './react';
@@ -82,7 +82,7 @@ describe('Batching — multi-prop methods cause single re-render', () => {
afterEach(teardown);
it('method writing two props (selector mode)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -110,7 +110,7 @@ describe('Batching — multi-prop methods cause single re-render', () => {
});
it('method writing two props (auto-tracked mode)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -137,7 +137,7 @@ describe('Batching — multi-prop methods cause single re-render', () => {
});
it('method writing two props, only one selected', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function CountOnly() {
@@ -166,7 +166,7 @@ describe('Batching — rapid mutations in a loop', () => {
afterEach(teardown);
it('100 increments in for-loop (selector mode)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -189,7 +189,7 @@ describe('Batching — rapid mutations in a loop', () => {
});
it('100 increments in for-loop (auto-tracked mode)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -212,7 +212,7 @@ describe('Batching — rapid mutations in a loop', () => {
});
it('50 array pushes in for-loop (selector on length)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -242,7 +242,7 @@ describe('Set-then-revert — no re-render when value returns to original', () =
afterEach(teardown);
it('primitive revert (selector mode)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -269,7 +269,7 @@ describe('Set-then-revert — no re-render when value returns to original', () =
// Unlike selector mode (which compares the extracted value), auto-tracked mode
// detects that the snapshot *object* is a new reference (version was bumped),
// so `getAutoTrackSnapshot` builds a new tracking proxy → re-render.
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -293,7 +293,7 @@ describe('Set-then-revert — no re-render when value returns to original', () =
});
it('nested object prop revert (selector on user.name)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -318,7 +318,7 @@ describe('Set-then-revert — no re-render when value returns to original', () =
});
it('unrelated prop revert — structural sharing preserves reference', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -351,7 +351,7 @@ describe('Async methods — re-renders split across await boundaries', () => {
it('1 await, mutations before + after (selector)', async () => {
// React batches both microtask notifications within act() into a single
// commit, so we see 1 re-render (not 2) for mutations on both sides of an await.
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -376,7 +376,7 @@ describe('Async methods — re-renders split across await boundaries', () => {
it('2 awaits, mutations between each (selector)', async () => {
// 3 mutations across 3 turns — React batches some of the microtask
// notifications, resulting in 2 re-renders rather than 3.
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -399,7 +399,7 @@ describe('Async methods — re-renders split across await boundaries', () => {
});
it('mutations only before await (selector)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -422,7 +422,7 @@ describe('Async methods — re-renders split across await boundaries', () => {
});
it('mutations only after await (selector)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -445,7 +445,7 @@ describe('Async methods — re-renders split across await boundaries', () => {
});
it('1 await, mutations before + after (auto-tracked)', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -474,7 +474,7 @@ describe('Multiple components sharing the same store', () => {
afterEach(teardown);
it('different selectors, independent re-render', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCountA = mock(() => {});
const renderCountB = mock(() => {});
@@ -510,7 +510,7 @@ describe('Multiple components sharing the same store', () => {
});
it('same selector, both re-render', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCountA = mock(() => {});
const renderCountB = mock(() => {});
@@ -549,7 +549,7 @@ describe('Multiple components sharing the same store', () => {
// In auto-tracked mode, when the store notifies all subscribers, each
// component's getSnapshot produces a new snapshot ref — both components
// re-render, unlike selector mode which compares extracted values.
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCountA = mock(() => {});
const renderCountB = mock(() => {});
@@ -585,7 +585,7 @@ describe('Multiple components sharing the same store', () => {
});
it('batched multi-prop, both affected', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCountA = mock(() => {});
const renderCountB = mock(() => {});
@@ -627,7 +627,7 @@ describe('Unmount safety', () => {
afterEach(teardown);
it('unmount during pending microtask does not crash', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
function Display() {
const count = useStore(s, (snap) => snap.count);
@@ -649,7 +649,7 @@ describe('Unmount safety', () => {
});
it('unmount and remount with same store shows latest state', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
function Display() {
const count = useStore(s, (snap) => snap.count);
@@ -683,7 +683,7 @@ describe('shallowEqual as useStore isEqual — integration', () => {
afterEach(teardown);
it('prevents re-render when shallow values are unchanged', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
@@ -714,7 +714,7 @@ describe('shallowEqual as useStore isEqual — integration', () => {
});
it('re-renders when shallow values differ', async () => {
- const s = store(new TestStore());
+ const s = createClassyStore(new TestStore());
const renderCount = mock(() => {});
function Display() {
diff --git a/src/react/react.test.tsx b/src/react/react.test.tsx
index e966d04..b57ba5a 100644
--- a/src/react/react.test.tsx
+++ b/src/react/react.test.tsx
@@ -1,7 +1,7 @@
import {afterEach, describe, expect, it, mock} from 'bun:test';
import {act, type ReactNode} from 'react';
import {createRoot} from 'react-dom/client';
-import {store} from '../core/core';
+import {createClassyStore} from '../core/core';
import {useStore} from './react';
// ── Test harness ────────────────────────────────────────────────────────────
@@ -37,7 +37,7 @@ describe('useStore — selector mode', () => {
class Counter {
count = 42;
}
- const s = store(new Counter());
+ const s = createClassyStore(new Counter());
function Display() {
const count = useStore(s, (snap) => snap.count);
@@ -56,7 +56,7 @@ describe('useStore — selector mode', () => {
this.count++;
}
}
- const s = store(new Counter());
+ const s = createClassyStore(new Counter());
const renderCount = mock(() => {});
function Display() {
@@ -80,7 +80,7 @@ describe('useStore — selector mode', () => {
});
it('does NOT re-render when unrelated prop changes', async () => {
- const s = store({count: 0, name: 'hello'});
+ const s = createClassyStore({count: 0, name: 'hello'});
const renderCount = mock(() => {});
function CountDisplay() {
@@ -111,7 +111,7 @@ describe('useStore — selector mode', () => {
this.user.name = name;
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const renderCount = mock(() => {});
function UserDisplay() {
@@ -136,7 +136,7 @@ describe('useStore — selector mode', () => {
});
it('handles array selectors', async () => {
- const s = store({items: ['a', 'b']});
+ const s = createClassyStore({items: ['a', 'b']});
const renderCount = mock(() => {});
function List() {
@@ -169,7 +169,7 @@ describe('useStore — selector mode', () => {
this.count = value;
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
function Display() {
const doubled = useStore(s, (snap) => snap.doubled);
@@ -189,7 +189,7 @@ describe('useStore — selector mode', () => {
});
it('supports custom isEqual for selector', async () => {
- const s = store({items: [{id: 1, name: 'a'}]});
+ const s = createClassyStore({items: [{id: 1, name: 'a'}]});
const renderCount = mock(() => {});
// Selector always returns a new array reference, but custom isEqual does shallow comparison.
@@ -224,7 +224,7 @@ describe('useStore — auto-tracked mode', () => {
afterEach(teardown);
it('renders accessed properties', () => {
- const s = store({count: 42, name: 'hello'});
+ const s = createClassyStore({count: 42, name: 'hello'});
function Display() {
const snap = useStore(s);
@@ -237,7 +237,7 @@ describe('useStore — auto-tracked mode', () => {
});
it('re-renders when accessed property changes', async () => {
- const s = store({count: 0, name: 'hello'});
+ const s = createClassyStore({count: 0, name: 'hello'});
const renderCount = mock(() => {});
function Display() {
@@ -261,7 +261,7 @@ describe('useStore — auto-tracked mode', () => {
});
it('does NOT re-render when non-accessed property changes', async () => {
- const s = store({count: 0, name: 'hello'});
+ const s = createClassyStore({count: 0, name: 'hello'});
const renderCount = mock(() => {});
function CountOnly() {
@@ -284,7 +284,10 @@ describe('useStore — auto-tracked mode', () => {
});
it('tracks nested object property access', async () => {
- const s = store({user: {name: 'Alice', age: 30}, theme: 'dark'});
+ const s = createClassyStore({
+ user: {name: 'Alice', age: 30},
+ theme: 'dark',
+ });
const renderCount = mock(() => {});
function UserName() {
@@ -315,7 +318,7 @@ describe('useStore — auto-tracked mode', () => {
});
it('tracks array length and element access', async () => {
- const s = store({items: ['a', 'b'], other: 'x'});
+ const s = createClassyStore({items: ['a', 'b'], other: 'x'});
const renderCount = mock(() => {});
function ItemCount() {
@@ -348,7 +351,7 @@ describe('useStore — auto-tracked mode', () => {
this.count = value;
}
}
- const s = store(new Store());
+ const s = createClassyStore(new Store());
function Display() {
const snap = useStore(s);
diff --git a/src/snapshot/snapshot.test.ts b/src/snapshot/snapshot.test.ts
index 450c3c1..fcbdb54 100644
--- a/src/snapshot/snapshot.test.ts
+++ b/src/snapshot/snapshot.test.ts
@@ -1,5 +1,5 @@
import {describe, expect, it} from 'bun:test';
-import {store} from '../core/core';
+import {createClassyStore} from '../core/core';
import {snapshot} from './snapshot';
/** Helper: flush the queueMicrotask-based batching. */
@@ -10,7 +10,7 @@ describe('snapshot()', () => {
describe('freezing', () => {
it('returns a deeply frozen object', () => {
- const s = store({user: {name: 'Alice'}, items: [1, 2, 3]});
+ const s = createClassyStore({user: {name: 'Alice'}, items: [1, 2, 3]});
const snap = snapshot(s);
expect(Object.isFrozen(snap)).toBe(true);
@@ -19,7 +19,7 @@ describe('snapshot()', () => {
});
it('throws when attempting to mutate a snapshot', () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const snap = snapshot(s);
expect(() => {
@@ -32,7 +32,7 @@ describe('snapshot()', () => {
describe('version cache', () => {
it('returns the same snapshot object when version has not changed', () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const snap1 = snapshot(s);
const snap2 = snapshot(s);
@@ -40,7 +40,7 @@ describe('snapshot()', () => {
});
it('returns a new snapshot after mutation + flush', async () => {
- const s = store({count: 0});
+ const s = createClassyStore({count: 0});
const snap1 = snapshot(s);
s.count = 1;
@@ -57,7 +57,7 @@ describe('snapshot()', () => {
describe('structural sharing', () => {
it('unchanged nested objects retain the same reference across snapshots', async () => {
- const s = store({
+ const s = createClassyStore({
user: {name: 'Alice'},
settings: {theme: 'dark'},
});
@@ -74,7 +74,7 @@ describe('snapshot()', () => {
});
it('unchanged array elements retain the same reference', async () => {
- const s = store({
+ const s = createClassyStore({
items: [
{id: 1, name: 'a'},
{id: 2, name: 'b'},
@@ -112,7 +112,7 @@ describe('snapshot()', () => {
}
it('getters evaluate correctly on the snapshot', () => {
- const s = store(new Store());
+ const s = createClassyStore(new Store());
const snap = snapshot(s);
expect(snap.doubled).toBe(10);
@@ -120,7 +120,7 @@ describe('snapshot()', () => {
});
it('getters reflect mutations in subsequent snapshots', async () => {
- const s = store(new Store());
+ const s = createClassyStore(new Store());
s.setCount(10);
await flush();
@@ -135,7 +135,7 @@ describe('snapshot()', () => {
describe('array snapshots', () => {
it('array push is reflected in new snapshot', async () => {
- const s = store({items: ['a', 'b']});
+ const s = createClassyStore({items: ['a', 'b']});
const snap1 = snapshot(s);
s.items.push('c');
@@ -147,7 +147,7 @@ describe('snapshot()', () => {
});
it('array splice is reflected in new snapshot', async () => {
- const s = store({items: ['a', 'b', 'c']});
+ const s = createClassyStore({items: ['a', 'b', 'c']});
const snap1 = snapshot(s);
s.items.splice(1, 1);
@@ -159,7 +159,7 @@ describe('snapshot()', () => {
});
it('replacing array by reference triggers new snapshot', async () => {
- const s = store({items: [1, 2, 3]});
+ const s = createClassyStore({items: [1, 2, 3]});
const snap1 = snapshot(s);
s.items = [4, 5];
@@ -175,14 +175,14 @@ describe('snapshot()', () => {
describe('edge cases', () => {
it('snapshot of an empty store', () => {
- const s = store({});
+ const s = createClassyStore({});
const snap = snapshot(s);
expect(snap).toEqual({});
expect(Object.isFrozen(snap)).toBe(true);
});
it('snapshot captures null and undefined values', () => {
- const s = store({
+ const s = createClassyStore({
a: null as string | null,
b: undefined as string | undefined,
});
@@ -221,7 +221,7 @@ describe('snapshot()', () => {
}
it('snapshot preserves instanceof for the derived class', () => {
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
const snap = snapshot(s);
expect(snap instanceof Derived).toBe(true);
@@ -229,7 +229,7 @@ describe('snapshot()', () => {
});
it('snapshot includes own properties from all inheritance levels', () => {
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
const snap = snapshot(s);
// Base-level properties
@@ -240,7 +240,7 @@ describe('snapshot()', () => {
});
it('structural sharing works across inheritance levels', async () => {
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
const snap1 = snapshot(s);
// Mutate only derived-level property, leave base-level nested object untouched
@@ -254,7 +254,7 @@ describe('snapshot()', () => {
});
it('getters from multiple inheritance levels evaluate correctly in snapshot', () => {
- const s = store(new Derived());
+ const s = createClassyStore(new Derived());
s.count = 5;
s.extra = 'tag';
diff --git a/src/snapshot/snapshot.ts b/src/snapshot/snapshot.ts
index f1d5fd8..2e4177d 100644
--- a/src/snapshot/snapshot.ts
+++ b/src/snapshot/snapshot.ts
@@ -8,7 +8,7 @@ import {canProxy, findGetterDescriptor} from '../utils/internal/internal';
* Version-stamped snapshot cache for tracked (proxied) sub-trees.
* Key: raw target object → [version, frozen snapshot].
*/
-const snapCache = new WeakMap