Kin Store
Start minimal. Grow structured.
A tiny, framework-agnostic reactive state library for TypeScript.
# Vanilla projects.
npm add @kintools/store-core
# React projects (includes core).
npm add @kintools/store-react
# Optional plugins.
npm add @kintools/store-pluginscreateStore
A value plus get, set, merge, subscribe. Logic can live in plain
functions that call them, no framework required.
import { createStore } from "@kintools/store-core";
const todos = createStore({
items: [] as string[],
status: "idle" as "idle" | "loading",
});
function addTodo(text: string): void {
todos.merge((s) => ({ items: [...s.items, text] }));
}
async function fetchTodos(): Promise<void> {
todos.merge({ status: "loading" });
const items = await api.fetchTodos();
todos.set({ items, status: "idle" });
}Add structure with .use()
When a store earns it, each .use() registers a plugin: a declarative object
that defines methods and lifecycle hooks. Plugins can be top-level or
namespaced. Nothing wraps or patches the store underneath.
import { createStore } from "@kintools/store-core";
import { devtools, persist } from "@kintools/store-plugins";
const todos = createStore({
items: [] as string[],
status: "idle" as "idle" | "loading",
})
.use({
addTodo(text: string): void {
this.merge((s) => ({ items: [...s.items, text] }));
},
async fetchTodos(): Promise<void> {
this.merge({ status: "loading" });
const items = await api.fetchTodos();
this.set({ items, status: "idle" });
},
})
.use(devtools({ name: "todos" }))
.use("persist", persist({ key: "todos" }));
todos.addTodo("Buy milk"); // Full intellisense.
await todos.persist.hydrationComplete(); // Full intellisense.React
useStore and useSelector subscribe a component to a store or a slice of it,
built on useSyncExternalStore. useSelector re-renders only when the selected
value changes.
@kintools/store-react re-exports the core API, so a React app needs a single
install. See the React bindings.
Methods are called directly on the store, like todos.addTodo() in the handler:
no hooks or subscriptions needed.
import { createStore, useSelector } from "@kintools/store-react";
const todos = createStore({ items: [] as string[] }).use({
addTodo(text: string): void {
this.merge((s) => ({ items: [...s.items, text] }));
},
});
function TodoList() {
const items = useSelector(todos, (s) => s.items);
return (
<>
<ul>
{items.map((item) => <li key={item}>{item}</li>)}
</ul>
<button onClick={() => todos.addTodo("Buy milk")}>Add</button>
</>
);
}derive
Compose one or more stores into a lazy, dependency-tracked, read-only view. It
tracks only the stores you call get() on: no selector arrays, no dependency
graph to maintain by hand.
import { derive } from "@kintools/store-core";
const itemCount = derive((get) => get(todos).items.length);
itemCount.subscribe(function () {
console.log(this.get());
});
todos.addTodo("Buy milk"); // logs 1How it compares
| Kin Store | Zustand | Redux / RTK | Jotai | MobX | |
|---|---|---|---|---|---|
| Bundle size | 1.6 KB | 389 B | 17.5 KB | 4.0 KB | 15.6 KB |
| Zero dependencies | ✅ | ✅ | ❌ | ✅ | ✅ |
| 100% type-safe | ✅ | ⚠️ | ⚠️ | ✅ | ✅ |
| Low boilerplate | ✅ | ⚠️ | ❌ | ⚠️ | ⚠️ |
| Separate state and logic | ✅ | ❌ | ✅ | — | ✅ |
| Opt-in complexity | ✅ | ✅ | ❌ | ⚠️ | ❌ |
| No hidden magic | ✅ | ✅ | ✅ | ✅ | ❌ |
✅ full support · ⚠️ partial or conditional · — not applicable (different model)
Bundle sizes are each library's full package import, bundled with rolldown, minified, and gzipped. Tree-shaking down to only the APIs you use will land smaller across the board.
For full comparison, see the details →