react-narrow 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 James Karlsson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,153 @@
1
- # Temporary Holding Version
1
+ # react-narrow
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `useState` for nested data. Mutate it like a plain object, keep it as plain immutable React state, and hand any component just the slice it needs.
4
+
5
+ No global store, no selectors, no actions. State is plain data that lives in your component tree and flows through props and context, so React and the React Compiler treat it like any other state. Writes go through [Mutative](https://github.com/unadlib/mutative), which turns your mutations into immutable updates.
6
+
7
+ ```tsx
8
+ // Before: rebuild every level by hand
9
+ setState((s) => ({
10
+ ...s,
11
+ projects: s.projects.map((p, i) =>
12
+ i === pi ? { ...p, tasks: p.tasks.map((t, j) => (j === ti ? { ...t, done: true } : t)) } : p,
13
+ ),
14
+ }))
15
+
16
+ // After
17
+ setState((s) => { s.projects[pi].tasks[ti].done = true })
18
+ ```
19
+
20
+ > **Warning:** `react-narrow` is still a 0.x release, so the API may change. Found a bug, or a case it doesn't handle? Please [open an issue](https://github.com/jaaamesey/narrow/issues).
21
+
22
+ ## First, create a store
23
+
24
+ It works like `useState`, except a function you pass to the setter mutates a draft.
25
+
26
+ ```tsx
27
+ import { useNewStore } from 'react-narrow'
28
+
29
+ function TodoApp() {
30
+ const [state, setState] = useNewStore({ todos: [] as Todo[], settings: { showDone: true } })
31
+
32
+ const add = (title: string) =>
33
+ setState((s) => {
34
+ s.todos.push({ id: crypto.randomUUID(), title, done: false })
35
+ })
36
+ // ...
37
+ }
38
+ ```
39
+
40
+ To replace the state instead, pass a value, or a function with `'swap'`:
41
+
42
+ ```ts
43
+ setState({ todos: [], settings: { showDone: true } })
44
+ setState((s) => ({ ...s, todos: [] }), 'swap')
45
+ ```
46
+
47
+ Returning a new object from a mutating function is a type error, so a forgotten `'swap'` doesn't slip through.
48
+
49
+ ## Then narrow it
50
+
51
+ Narrowing gives you one part of the state and a setter for just that part. Writes still land in the original store.
52
+
53
+ ```tsx
54
+ const [settings, setSettings] = useNarrowedStore(state, (s) => s.settings, setState)
55
+
56
+ setSettings((s) => { s.showDone = false }) // updates state.settings.showDone
57
+ ```
58
+
59
+ `(s) => s.settings` is the narrow function: it picks the part of the state to narrow to. A narrowed store works like any other, so you can pass it down and narrow it again.
60
+
61
+ You can use `NarrowList` to pass just a single item in a store down to a component. This helper doesn't just isolate reads/writes to that item. Here (assuming you've enabled the React Compiler, or are manually memoizing), it also ensures that a `TodoRow` only re-renders when its own `todo` changes.
62
+
63
+ ```tsx
64
+ const [todos, setTodos] = useNarrowedStore(state, (s) => s.todos, setState)
65
+
66
+ <NarrowList items={todos} setStore={setTodos} getKey={(t) => t.id}>
67
+ {(todo, setTodo) => <TodoRow todo={todo} setTodo={setTodo} />}
68
+ </NarrowList>
69
+
70
+ function TodoRow({ todo, setTodo }: { todo: Todo; setTodo: SetStore<Todo> }) {
71
+ return <input type="checkbox" checked={todo.done} onChange={() => setTodo((t) => { t.done = !t.done })} />
72
+ }
73
+ ```
74
+
75
+ `NarrowList` also takes a record or a `Map`, for normalized state. Those are keyed already, so there's no `getKey`. `children` gets each entry's key as a third argument.
76
+
77
+ ## Share it through context
78
+
79
+ ```tsx
80
+ const appStore = createContextStore<AppState>()
81
+
82
+ <appStore.Provider initial={…}>…</appStore.Provider> // the Provider owns a new store
83
+ <appStore.Provider store={state} setStore={setState}>…</appStore.Provider> // or share one from useNewStore
84
+
85
+ const state = appStore.useVal() // re-renders when the state changes
86
+ const setState = appStore.useSet() // doesn't
87
+ ```
88
+
89
+ Narrowed contexts only re-render their consumers when their slice changes:
90
+
91
+ ```tsx
92
+ const settingsStore = createNarrowedStoreContext(appStore, (s) => s.settings)
93
+
94
+ <settingsStore.Provider>…</settingsStore.Provider> // settingsStore.useVal() / useSet() inside
95
+ ```
96
+
97
+ ## Why react-narrow?
98
+
99
+ ### What it's particularly useful for
100
+ - Relatively deep, global-ish state that's frequently written to
101
+ - Document editors: slide decks, whiteboards, diagrams, page builders
102
+ - Builders: forms, surveys, workflows, product configurators
103
+ - Large editable tables and lists: spreadsheets, Kanban boards, outliners
104
+ - Timeline and layout editors: video, scheduling, floor plans, game levels
105
+
106
+ ### Where it's less useful
107
+ - Data fetching (seriously, use react-query or something for that)
108
+ - Anywhere outside of React (like `useState`, you can only pass down data and callbacks from the component tree)
109
+ - Wherever extremely fine-grained rendering is needed (use a signals-based state library for that, or avoid React entirely)
110
+ - Codebases that don't have the React Compiler enabled (a lot of the library's ergonomics lean on it)
111
+
112
+ ### Why use this over useState and context?
113
+
114
+ - A deep update is one line, not a spread at every level, where missing one level silently drops data
115
+ - A child gets its slice and a setter scoped to it. It can make any edit to that slice, can't touch anything else, and doesn't need to know where the slice lives
116
+ - Value and setter come in separate contexts, so components that only write never re-render. Put `[state, setState]` in one context and every consumer re-renders on every change
117
+
118
+ ### Why use this over useImmer?
119
+
120
+ - `useImmer` makes the top-level update easy, but a child still needs the whole state or a callback for every kind of edit (`onToggle`, `onRename`, …). Here it gets `[todo, setTodo]` and makes the edit itself
121
+ - Context stores are built in, so you don't hand-roll a provider and a memoized value. Narrowed contexts re-render only for their slice
122
+ - `NarrowList` gives every item a stable setter, so unchanged rows skip re-rendering (with the React Compiler, or `useCallback`). With `useImmer`, you'd thread ids and memoized callbacks through by hand to get the same result
123
+
124
+ ### Why use this over Zustand, Jotai or Valtio?
125
+
126
+ - It's React state: it still lives in your component tree, behaves exactly as React would expect (making it safe with concurrent and compiler features), and doesn't need snapshots to be taken
127
+ - Updates are ordinary `useState` updates, so they batch and work with transitions. Zustand and Valtio read through `useSyncExternalStore`, so an update inside `startTransition` still blocks
128
+ - Components take a slice and a setter as props, so they work with any store and test with plain values, instead of being wired to one global store by path
129
+
130
+
131
+ ## Narrowing to an item picked at runtime
132
+
133
+ Give the narrow function a second parameter, and pass it to the Provider:
134
+
135
+ ```tsx
136
+ const todoStore = createNarrowedStoreContext(appStore, (s, id: string) => s.todos.find((t) => t.id === id))
137
+
138
+ <todoStore.Provider narrowArg={selectedId}>…</todoStore.Provider>
139
+ ```
140
+
141
+ ## Edge cases
142
+
143
+ - Setter functions run as React state updaters, so like `useState` updaters they must be pure. Strict Mode calls them twice.
144
+ - Index keys (`getKey={(_, i) => i}`) need the full array as `items`. With a filtered list, the indexes point at the wrong items.
145
+ - Reading narrowed data that no longer exists throws. Writing to it does nothing, so late async callbacks are safe.
146
+ - Following Mutative, plain objects, arrays, Maps and Sets can be mutated. Dates and class instances are replaced, not mutated.
147
+
148
+ ## Examples
149
+
150
+ - [`examples/slides`](examples/slides): a slide deck editor. Dragging a shape re-renders only that shape and its slide's thumbnail.
151
+ - [`examples/todomvc`](examples/todomvc): TodoMVC.
152
+
153
+ Both use the React Compiler and no `memo`. Run one with `npm install && npm run dev -w examples/slides`.
@@ -0,0 +1,3 @@
1
+ export type { SetStore, Narrower } from './store.js';
2
+ export { useNewStore, useNarrowedStore, NarrowList, createContextStore, createNarrowedStoreContext, } from './react.js';
3
+ export type { ContextStore, StoreContext } from './react.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { useNewStore, useNarrowedStore, NarrowList, createContextStore, createNarrowedStoreContext, } from './react.js';
@@ -0,0 +1,65 @@
1
+ import { type Key, type ReactNode } from 'react';
2
+ import type { Draft } from 'mutative';
3
+ import { type Narrower, type SetStore } from './store.js';
4
+ /** Like `useState`, but a function passed to the setter mutates a draft. */
5
+ export declare function useNewStore<T extends object>(initial: T | (() => T)): [T, SetStore<T>];
6
+ /** Narrows a store to the part `narrow` picks, returning that part and a setter that writes back into the store. */
7
+ export declare function useNarrowedStore<T extends object, N extends object>(store: T, narrow: Narrower<T, N>, setStore: SetStore<T>): [N, SetStore<N>];
8
+ type RenderItem<T extends object, K> = (item: T, setItem: SetStore<T>, key: K) => ReactNode;
9
+ interface NarrowListComponent {
10
+ <T extends object>(props: {
11
+ items: T[];
12
+ setStore: SetStore<T[]>;
13
+ getKey: (item: T, index: number) => Key;
14
+ children: RenderItem<T, Key>;
15
+ }): ReactNode;
16
+ <K extends string, T extends object>(props: {
17
+ items: Record<K, T>;
18
+ setStore: SetStore<Record<K, T>>;
19
+ getKey?: undefined;
20
+ children: RenderItem<T, K>;
21
+ }): ReactNode;
22
+ <K extends Key, T extends object>(props: {
23
+ items: Map<K, T>;
24
+ setStore: SetStore<Map<K, T>>;
25
+ getKey?: undefined;
26
+ children: RenderItem<T, K>;
27
+ }): ReactNode;
28
+ }
29
+ /** Renders every item of an array, record or Map with its own narrowed store; only changed items re-render. */
30
+ export declare const NarrowList: NarrowListComponent;
31
+ /** A store shared through context: `useVal()` re-renders on changes, `useSet()` never does. */
32
+ export interface ContextStore<T extends object> {
33
+ useVal(): T;
34
+ useSet(): SetStore<T>;
35
+ }
36
+ type ProviderProps<T extends object> = {
37
+ store: T;
38
+ setStore: SetStore<T>;
39
+ initial?: never;
40
+ children?: ReactNode;
41
+ } | {
42
+ initial: T | (() => T);
43
+ store?: never;
44
+ setStore?: never;
45
+ children?: ReactNode;
46
+ };
47
+ export interface StoreContext<T extends object> extends ContextStore<T> {
48
+ Provider(props: ProviderProps<T>): ReactNode;
49
+ }
50
+ /** Creates a context store. `fallback` is a read-only value used when no Provider is above. */
51
+ export declare function createContextStore<T extends object>(fallback?: T): StoreContext<T>;
52
+ /** Creates a context store narrowed from `parent`, whose consumers only re-render when their part changes. */
53
+ export declare function createNarrowedStoreContext<T extends object, N extends object, A = void>(parent: ContextStore<T>, narrow: ((state: T, arg: A) => N | undefined) | {
54
+ get: (state: T, arg: A) => N | undefined;
55
+ swap: (draft: Draft<T>, value: N, arg: A) => void;
56
+ }, fallback?: N): ContextStore<N> & {
57
+ Provider(props: {
58
+ children?: ReactNode;
59
+ } & ([A] extends [void] ? {
60
+ narrowArg?: undefined;
61
+ } : {
62
+ narrowArg: A;
63
+ })): ReactNode;
64
+ };
65
+ export {};
package/dist/react.js ADDED
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, memo, useContext, useMemo, useState, } from 'react';
3
+ import { applyUpdate, getterOf, narrowSetter } from './store.js';
4
+ /** Like `useState`, but a function passed to the setter mutates a draft. */
5
+ export function useNewStore(initial) {
6
+ const [state, setState] = useState(initial);
7
+ const [set] = useState(() => ((next, mode) => setState((prev) => applyUpdate(prev, next, mode))));
8
+ return [state, set];
9
+ }
10
+ /** Narrows a store to the part `narrow` picks, returning that part and a setter that writes back into the store. */
11
+ export function useNarrowedStore(store, narrow, setStore) {
12
+ const set = useMemo(() => narrowSetter(setStore, narrow), [setStore, narrow]);
13
+ const value = getterOf(narrow)(store);
14
+ if (value == null)
15
+ throw new Error('react-narrow: the narrowed node is missing (narrow returned null/undefined)');
16
+ return [value, set];
17
+ }
18
+ /** Renders every item of an array, record or Map with its own narrowed store; only changed items re-render. */
19
+ export const NarrowList = memo(function NarrowList({ items, setStore, getKey, children }) {
20
+ const kind = Array.isArray(items) ? 'array' : items instanceof Map ? 'map' : 'record';
21
+ const entries = Array.isArray(items)
22
+ ? items.map((item, i) => [getKey(item, i), item])
23
+ : items instanceof Map
24
+ ? [...items]
25
+ : Object.entries(items);
26
+ return entries.map(([key, item]) => (_jsx(NarrowListItem, { itemKey: key, item: item, kind: kind, setStore: setStore, getKey: getKey, render: children }, key)));
27
+ });
28
+ const narrowToItem = (kind, key, getKey) => kind === 'array'
29
+ ? (items) => items.find((x, i) => getKey(x, i) === key)
30
+ : kind === 'map'
31
+ ? { get: (m) => m.get(key), swap: (d, v) => void d.set(key, v) }
32
+ : (r) => r[String(key)];
33
+ const NarrowListItem = memo(function NarrowListItem(props) {
34
+ const { itemKey, item, kind, setStore, getKey, render } = props;
35
+ const setItem = useMemo(() => narrowSetter(setStore, narrowToItem(kind, itemKey, getKey)), [setStore, kind, getKey, itemKey]);
36
+ return render(item, setItem, itemKey);
37
+ });
38
+ const NONE = Symbol('no provider');
39
+ const readOnlyDefault = (() => {
40
+ throw new Error('react-narrow: no store in context; the default value is read-only');
41
+ });
42
+ // Separate value and setter contexts, so setter-only consumers don't re-render.
43
+ function makeContext(fallback) {
44
+ const ValueCtx = createContext(NONE);
45
+ const SetCtx = createContext(NONE);
46
+ const missing = () => new Error('react-narrow: no store found in context and no default was given');
47
+ const def = {
48
+ useVal() {
49
+ const value = useContext(ValueCtx);
50
+ if (value !== NONE)
51
+ return value;
52
+ if (fallback !== undefined)
53
+ return fallback;
54
+ throw missing();
55
+ },
56
+ useSet() {
57
+ const set = useContext(SetCtx);
58
+ if (set !== NONE)
59
+ return set;
60
+ if (fallback !== undefined)
61
+ return readOnlyDefault;
62
+ throw missing();
63
+ },
64
+ };
65
+ const Provide = (p) => (_jsx(ValueCtx, { value: p.value, children: _jsx(SetCtx, { value: p.set, children: p.children }) }));
66
+ return { def, Provide };
67
+ }
68
+ /** Creates a context store. `fallback` is a read-only value used when no Provider is above. */
69
+ export function createContextStore(fallback) {
70
+ const { def, Provide } = makeContext(fallback);
71
+ function Owned(p) {
72
+ const [value, set] = useNewStore(p.initial);
73
+ return _jsx(Provide, { value: value, set: set, children: p.children });
74
+ }
75
+ function Provider({ store, setStore, initial, children }) {
76
+ if (initial !== undefined)
77
+ return _jsx(Owned, { initial: initial, children: children });
78
+ return _jsx(Provide, { value: store, set: setStore, children: children });
79
+ }
80
+ return { ...def, Provider };
81
+ }
82
+ /** Creates a context store narrowed from `parent`, whose consumers only re-render when their part changes. */
83
+ export function createNarrowedStoreContext(parent, narrow, fallback) {
84
+ const { def, Provide } = makeContext(fallback);
85
+ function Provider(props) {
86
+ const arg = props.narrowArg;
87
+ const narrowed = useMemo(() => typeof narrow === 'function'
88
+ ? (s) => narrow(s, arg)
89
+ : { get: (s) => narrow.get(s, arg), swap: (d, v) => narrow.swap(d, v, arg) }, [arg]);
90
+ const [value, set] = useNarrowedStore(parent.useVal(), narrowed, parent.useSet());
91
+ return _jsx(Provide, { value: value, set: set, children: props.children });
92
+ }
93
+ return { ...def, Provider };
94
+ }
@@ -0,0 +1,18 @@
1
+ import { type Draft } from 'mutative';
2
+ type Primitive = string | number | boolean | bigint | symbol | null | undefined;
3
+ type MutateResult<R> = [R] extends [void | Primitive] ? R : 'recipe returned an object: it mutates by default. Pass "swap" as the second argument to replace the state';
4
+ /** Mutates a draft when given a function, replaces the state when given a value or a function with `'swap'`. */
5
+ export interface SetStore<T extends object> {
6
+ (next: T): void;
7
+ <R>(recipe: (draft: Draft<T>) => MutateResult<R>, mode?: 'mutate'): void;
8
+ (recipe: (current: T) => T, mode: 'swap'): void;
9
+ }
10
+ export declare function applyUpdate<T>(prev: T, next: unknown, mode?: 'mutate' | 'swap'): T;
11
+ /** A narrow function, or `{ get, swap }` when the function can't be traced. */
12
+ export type Narrower<T, N> = ((state: T) => N | undefined) | {
13
+ get: (state: T) => N | undefined;
14
+ swap: (draft: Draft<T>, value: N) => void;
15
+ };
16
+ export declare const getterOf: <T, N>(narrow: Narrower<T, N>) => (state: T) => N | undefined;
17
+ export declare function narrowSetter<T extends object, N extends object>(parentSet: SetStore<T>, narrow: Narrower<T, N>): SetStore<N>;
18
+ export {};
package/dist/store.js ADDED
@@ -0,0 +1,84 @@
1
+ import { create } from 'mutative';
2
+ const modeOf = (next, mode) => mode ?? (typeof next === 'function' ? 'mutate' : 'swap');
3
+ export function applyUpdate(prev, next, mode) {
4
+ if (modeOf(next, mode) === 'swap')
5
+ return (typeof next === 'function' ? next(prev) : next);
6
+ return create(prev, (draft) => void next(draft));
7
+ }
8
+ export const getterOf = (narrow) => (typeof narrow === 'function' ? narrow : narrow.get);
9
+ const PATH = Symbol('path');
10
+ const untraceable = (cause) => new Error('react-narrow: could not trace narrowFn to a node inside the state (it returned a copy/computed value, ' +
11
+ 'or used an identity comparison or Map lookup). Pass { get, swap } instead.', { cause });
12
+ // The path of keys narrowFn reads to reach the node it returns, or null if it returns nothing.
13
+ function pathOf(state, narrowFn) {
14
+ // Proxy a stand-in, not `v`: proxy invariants forbid wrapping frozen properties.
15
+ const track = (v, path) => {
16
+ if (v === null || typeof v !== 'object')
17
+ return v;
18
+ const isArray = Array.isArray(v);
19
+ return new Proxy(isArray ? [] : {}, {
20
+ get: (_, k) => {
21
+ if (k === PATH)
22
+ return path;
23
+ // A write to a getter would be lost, so don't trace through one.
24
+ if (Reflect.getOwnPropertyDescriptor(v, k)?.get)
25
+ return Reflect.get(v, k);
26
+ return track(Reflect.get(v, k), [...path, k]);
27
+ },
28
+ has: (_, k) => k in v,
29
+ ownKeys: () => Reflect.ownKeys(v),
30
+ getPrototypeOf: () => Object.getPrototypeOf(v),
31
+ getOwnPropertyDescriptor: (_, k) => {
32
+ // The stand-in array's own `length` is non-configurable.
33
+ if (isArray && k === 'length')
34
+ return { value: v.length, writable: true, enumerable: false, configurable: false };
35
+ const d = Reflect.getOwnPropertyDescriptor(v, k);
36
+ return d && { ...d, configurable: true };
37
+ },
38
+ });
39
+ };
40
+ // On failure, rerun narrowFn on the real state: if it fails there too, that error is narrowFn's own bug.
41
+ let node;
42
+ try {
43
+ node = narrowFn(track(state, []));
44
+ }
45
+ catch (cause) {
46
+ narrowFn(state);
47
+ throw untraceable(cause);
48
+ }
49
+ if (node == null) {
50
+ if (narrowFn(state) == null)
51
+ return null;
52
+ throw untraceable();
53
+ }
54
+ const path = node[PATH];
55
+ if (!path)
56
+ throw untraceable();
57
+ return path;
58
+ }
59
+ const getAt = (v, path) => path.reduce((o, k) => o[k], v);
60
+ export function narrowSetter(parentSet, narrow) {
61
+ const set = (next, mode) => parentSet((current) => {
62
+ if (typeof narrow !== 'function') {
63
+ const node = narrow.get(current);
64
+ // Deliberately silent: late async writes to deleted items are normal (React dropped its similar warning).
65
+ if (node == null)
66
+ return current;
67
+ const value = applyUpdate(node, next, mode);
68
+ return value === node ? current : create(current, (d) => void narrow.swap(d, value));
69
+ }
70
+ const path = pathOf(current, narrow);
71
+ if (!path)
72
+ return current; // see above
73
+ if (modeOf(next, mode) === 'mutate')
74
+ return create(current, (d) => void next(getAt(d, path)));
75
+ const node = getAt(current, path);
76
+ const value = typeof next === 'function' ? next(node) : next;
77
+ if (value === node)
78
+ return current;
79
+ if (!path.length)
80
+ return value;
81
+ return create(current, (d) => void (getAt(d, path.slice(0, -1))[path.at(-1)] = value));
82
+ }, 'swap');
83
+ return set;
84
+ }
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "react-narrow",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/jaaamesey/narrow.git"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "sideEffects": false,
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "test": "vitest run",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
27
+ },
28
+ "peerDependencies": {
29
+ "react": ">=19"
30
+ },
31
+ "dependencies": {
32
+ "mutative": "^2.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@babel/core": "^8.0.7",
36
+ "@rolldown/plugin-babel": "^0.2.4",
37
+ "@testing-library/dom": "^10.4.2",
38
+ "@testing-library/react": "^16.3.3",
39
+ "@types/react": "^19.3.0",
40
+ "@types/react-dom": "^19.3.0",
41
+ "@vitejs/plugin-react": "^6.1.2",
42
+ "babel-plugin-react-compiler": "^1.0.0",
43
+ "jsdom": "^29.1.1",
44
+ "react": "^19.3.1",
45
+ "react-dom": "^19.3.1",
46
+ "typescript": "^7.0.2",
47
+ "vitest": "^5.0.5"
48
+ },
49
+ "workspaces": [
50
+ "examples/*"
51
+ ]
52
+ }