Type-safe dynamic model factory for effector
Contracts, reactive instances, and ORM-like queries — with full SSR support.
Tentacles turns effector into a batteries-included state framework. Declare a contract with a fluent chain builder, instantiate as many independent models as you like, and query them like an in-memory ORM — all reactive, all SSR-safe, all fully typed with zero manual annotations.
import { createContract, createModel, eq } from "@kbml-tentacles/core"
const todoContract = createContract()
.store("id", (s) => s<number>().autoincrement())
.store("title", (s) => s<string>())
.store("done", (s) => s<boolean>().default(false))
.event("toggle", (e) => e<void>())
.pk("id")
const todoModel = createModel({
contract: todoContract,
fn: ({ $done, toggle }) => {
$done.on(toggle, (d) => !d)
return {}
},
})
todoModel.create({ title: "Learn Tentacles" })
todoModel.create({ title: "Ship it" })
const active = todoModel.query().where("done", eq(false))
active.$list // Store<Row[]> — auto-updates
// Mutate through the same query API:
todoModel.query().where("id", eq(1)).update({ done: true })
todoModel.query().where("done", eq(true)).delete()Writing non-trivial effector apps means hand-rolling the same scaffolding over and over: record maps, id lists, setters, getters, combiners, SIDs. Tentacles collapses that into a one-line-per-field contract.
- Contract-first — add a field, add one line. Types flow everywhere.
- Dynamic instances — hundreds of isolated instances from one model, each with deterministic SIDs.
- Zero-cost field proxies — per-instance
$fieldaccessors are proxy objects, not real effector stores. Stores materialize only when needed (e.g.combine,sample). - Reactive queries —
WHERE/ORDER BY/LIMIT/GROUP BYas composable stores. Incremental updates: O(1) on field mutations, not O(N). - Refs & relationships — one-to-one, one-to-many, inverse refs, self-references, compound FKs. Inline create, cascade delete.
- SSR out of the box — serialize/fork hydration with zero config.
- Forms, done right —
@kbml-tentacles/formsbrings the same contract-driven design to form state, validation, and arrays.
npm install effector @kbml-tentacles/core
# or
yarn add effector @kbml-tentacles/core
# or
pnpm add effector @kbml-tentacles/core// todo.ts
import { createContract, createModel } from "@kbml-tentacles/core"
const todoContract = createContract()
.store("id", (s) => s<number>().autoincrement())
.store("title", (s) => s<string>())
.store("done", (s) => s<boolean>().default(false))
.event("toggle", (e) => e<void>())
.pk("id")
export const todoModel = createModel({
contract: todoContract,
fn: ({ $done, toggle }) => {
$done.on(toggle, (d) => !d)
return {}
},
})// todo-view.ts
import { createViewContract, createViewModel, eq } from "@kbml-tentacles/core"
import { todoModel } from "./todo"
const todoViewContract = createViewContract()
.store("draftTitle", (s) => s<string>().default(""))
export const todoViewModel = createViewModel({
contract: todoViewContract,
fn: ({ $draftTitle }) => ({
$draftTitle,
$activeCount: todoModel.query().where("done", eq(false)).$count,
$doneCount: todoModel.query().where("done", eq(true)).$count,
}),
})// App.tsx
import { useUnit } from "effector-react"
import { Each, View, useModel } from "@kbml-tentacles/react"
import { todoModel } from "./todo"
import { todoViewModel } from "./todo-view"
function TodoItem() {
const todo = useModel(todoModel)
const title = useUnit(todo.$title)
const done = useUnit(todo.$done)
return (
<li style={{ textDecoration: done ? "line-through" : "none" }}>
<input type="checkbox" checked={done} onChange={todo.toggle} />
{title}
</li>
)
}
export default function App() {
return (
<View model={todoViewModel}>
<ul>
<Each model={todoModel} source={todoModel.$ids}>
<TodoItem />
</Each>
</ul>
</View>
)
}<View> is the primary pattern — it scopes a view model to a subtree and exposes its shape via context. Descendants pick it up with useModel(todoViewModel).
| Package | Description |
|---|---|
@kbml-tentacles/core |
Contracts, models, queries, view-models, SSR scope isolation |
@kbml-tentacles/forms |
Contract-driven reactive forms with validation, arrays, submission |
@kbml-tentacles/react |
React adapter — <View>, <Each>, useModel, useView |
@kbml-tentacles/vue |
Vue adapter |
@kbml-tentacles/solid |
Solid adapter |
@kbml-tentacles/forms-react |
React form bindings |
@kbml-tentacles/forms-vue |
Vue form bindings |
@kbml-tentacles/forms-solid |
Solid form bindings |
@kbml-tentacles/forms-zod |
Zod schema adapter |
@kbml-tentacles/forms-yup |
Yup schema adapter |
@kbml-tentacles/forms-joi |
Joi schema adapter |
@kbml-tentacles/forms-valibot |
Valibot schema adapter |
@kbml-tentacles/forms-arktype |
ArkType schema adapter |
Full documentation at tentacles docs (run yarn docs to preview locally).
- Tutorials — your first model, React/Vue/Solid todo apps, your first form
- How-to guides — contracts, refs, queries, SSR, validation, form arrays
- Reference — full API
yarn install
yarn build # build all packages
yarn test # run vitest
yarn test:leaks # memory / scope leak suite
yarn typecheck
yarn lint
yarn docs # serve docs locally
yarn example # run the Next.js SSR exampleMIT © Nikita Lumpov