Intuitive
The same defineStore() shape Pinia users already know — state, getters, and actions, or a setup-style function. If you already think in stores, there's nothing new to learn.
Type Safe
End-to-end TypeScript inference, from defineStore() through useStore() and connectStore() — no manual generics, no casting, no drift between the store and the component reading it.
Fine-Grained Reactivity
A real Proxy-based reactivity engine, not manual selectors. Components re-render only for the exact — even deeply nested — properties they actually read.
Class Components, First-Class
connectStore() patches your class in place — no wrapper component, no giving up React.Component. Hooks were never the only way in.
SSR-Ready
@nekuta/next handles both the Pages Router and the App Router, with per-request state isolation already solved — not left for you to get right.
Schema or Hooks — Your Choice
Two equivalent ways to define a store, converging on one engine. Mix freely, or enforce one project-wide with @nekuta/eslint-plugin — it's up to you.
The same store, either side of your app
Define a store once with defineStore(). Read it from a function component with useStore(), or from a class component with connectStore() — both fully reactive, both fine-grained, both built on the same engine.
import { defineStore } from '@nekuta/core';
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment(this: { count: number }) {
this.count++;
}
}
});
// anywhere in a function component
const counter = useStore(useCounterStore);
counter.count; // 0 — reactive, fine-grained re-renders