Your schema is your sync engine — a framework for collaborative, local-first apps.
Define your data once — sync, reactivity, validation, and persistence are derived from that single definition. Start on one machine with plain objects. Add peers, CRDTs, transports, and storage as you grow — without rewriting your app.
import { Schema, change } from "@kyneta/schema"
import { Exchange, sync } from "@kyneta/exchange"
import { loro } from "@kyneta/loro-schema"
import { createWebsocketClient } from "@kyneta/websocket-transport/browser"
// 1. Define your data
const TodoDoc = loro.bind(
Schema.struct({
title: Schema.text(),
items: Schema.list(
Schema.struct({
text: Schema.string(),
done: Schema.boolean(),
}),
),
})
)
// 2. Create an exchange, one per peer (or server/client)
const exchange = new Exchange({
identity: { peerId: "alice" },
transports: [createWebsocketClient({ url: "ws://localhost:3000/ws", WebSocket })],
})
// 3. Get a document — syncs automatically
const doc = exchange.get("my-todos", TodoDoc)
change(doc, d => {
d.title.insert(0, "My Todos")
d.items.push({ text: "Learn Kyneta", done: false })
})
doc.title() // "My Todos"
await sync(doc).waitForSync()React bindings are available today. It's easy to add other bindings.
import { useDocument, useValue } from "@kyneta/react"
import { TodoDoc } from "./schema.js"
function TodoApp() {
const doc = useDocument("my-todos", TodoDoc)
const title = useValue(doc.title)
return (
<div>
<h1>{title}</h1>
<button onClick={() => doc.items.push({ text: "New item", done: false })}>
Add
</button>
</div>
)
}Every step below is additive — earlier code doesn't change.
| Step | What changes | What stays the same |
|---|---|---|
| Local document | createDoc(schema) — no network, no exchange; type-safe, reactive doc, with validation |
— |
| Two peers, plain sync | Add Exchange + transport |
Schema, reads, writes |
| Switch to CRDTs | json.bind(schema) → loro.bind(schema) |
Exchange, transport, reads, writes |
| Add persistence | Add stores: [leveldb()] to exchange config |
Everything above |
| Add presence | ephemeral.bind(schema) alongside your collaborative docs |
Everything above |
| Add access control | Add route and authorize predicates |
Client code unchanged |
| Add a relay | One more Exchange with type: "relay" |
Client code unchanged |
See the @kyneta/exchange README for the full walkthrough with code.
Schemas should be walked once. A schema tree gets traversed for reading, mutation, observation, validation, sync, and more. Most frameworks implement these as parallel switch dispatches that drift apart. Kyneta's schema algebra collapses them into one catamorphism with pluggable interpreters — all behaviors are derived from the same structure. Your schema is the single source of truth not by convention, but by construction. Add a field and every behavior follows. There is nothing else to update.
Collaboration shouldn't require rewriting your app. Start with plain JS objects and createDoc(). When you need concurrent merge, swap to loro.bind() or yjs.bind() — reads, writes, and observation don't change. The exchange syncs documents via the Substrate interface — the exchange doesn't need to understand the inner workings of your CRDT library of choice. This is powerful, because you can mix collaborative CRDTs, authoritative json state, and ephemeral presence in one sync network.
Local-first means local first. Authority starts local — the hardest configuration to achieve, and the one you get for free. Centralize when your needs call for it. The network is additive, not load-bearing.
6,087 tests across the monorepo.
| Package | Description | Tests |
|---|---|---|
@kyneta/changefeed |
Universal reactive contract — a Moore machine identified by [CHANGEFEED]. Zero dependencies. |
47 |
@kyneta/schema |
Schema interpreter algebra. One recursive Schema type, one generic interpret() catamorphism, pluggable interpreters for reading, mutation, observation, and validation. Only dependency is @kyneta/changefeed. |
1,901 |
@kyneta/machine |
Universal Mealy machine algebra — pure state transitions with effect outputs. Powers the exchange synchronizer and all transport clients. Zero dependencies. | 45 |
A plain JS substrate is built into @kyneta/schema — no external package needed to get started. These packages add CRDT backends:
| Package | Description | Tests |
|---|---|---|
@kyneta/loro-schema |
Loro CRDT substrate for @kyneta/schema. Schema-aware typed reads, applyDiff-based writes, and a persistent event bridge. |
201 |
@kyneta/yjs-schema |
Yjs CRDT substrate for @kyneta/schema. Same Substrate interface as Loro — swap with a one-line import change. yjs.bind() validates composition-law compatibility at compile time via YjsLaws. |
217 |
| Package | Description | Tests |
|---|---|---|
@kyneta/exchange |
Substrate-agnostic state exchange. Four named binding targets (json, ephemeral, loro, yjs) with fixed sync protocols over a six-message sync protocol. Hosts heterogeneous documents — Loro CRDTs, Yjs CRDTs, plain JS, ephemeral presence — in one sync network. |
420 |
@kyneta/transport |
Transport infrastructure — base class, channel types, message vocabulary, and client utilities. | 8 |
@kyneta/wire |
Wire format codecs, framing, and fragmentation. CBOR and JSON codecs, 6-byte binary frames, and a fragmentation protocol for cloud WebSocket gateways. | 233 |
| Package | Description | Tests |
|---|---|---|
@kyneta/websocket-transport |
WebSocket transport. Client, server, and Bun-specific handlers with connection lifecycle, keepalive, and reconnection. | 56 |
@kyneta/sse-transport |
SSE transport. Client, server, and Express integration with reconnection state machine. | 44 |
@kyneta/webrtc-transport |
WebRTC data channel transport. BYODC (Bring Your Own Data Channel) with binary CBOR encoding and fragmentation. | 27 |
@kyneta/unix-socket-transport |
Unix domain socket transport. Stream-oriented, backpressure-aware server-to-server sync. | 82 |
| Package | Description | Tests |
|---|---|---|
@kyneta/leveldb-store |
LevelDB storage backend for @kyneta/exchange. Server-side persistent storage. |
24 |
| Package | Description | Tests |
|---|---|---|
@kyneta/index |
DBSP-grounded reactive indexing — Source, Collection, Index over ℤ-set algebra. | 143 |
| Package | Description | Tests |
|---|---|---|
@kyneta/react |
React bindings over @kyneta/schema + @kyneta/exchange. Hooks for document access, sync status, and reactive observation via useSyncExternalStore. |
84 |
@kyneta/changefeed (standalone — zero dependencies)
│
└──► @kyneta/schema (the algebra everything builds on)
│
├──► @kyneta/loro-schema (+ loro-crdt)
├──► @kyneta/yjs-schema (+ yjs)
│
├──► @kyneta/compiler (+ ts-morph) ── experimental
│ └──► @kyneta/cast (+ unplugin) ── experimental
│
├──► @kyneta/index (+ changefeed; optional peer-dep: exchange)
│
└──► @kyneta/exchange (+ transport)
│
├──► @kyneta/wire (+ tiny-cbor)
│ ├──► @kyneta/websocket-transport
│ ├──► @kyneta/sse-transport
│ ├──► @kyneta/webrtc-transport
│ └──► @kyneta/unix-socket-transport
│
├──► @kyneta/leveldb-store
└──► @kyneta/react (+ react)
@kyneta/machine (standalone — used by exchange, transports)
@kyneta/perspective (standalone — private, not published)
@kyneta/changefeed defines the universal reactive contract — the [CHANGEFEED] symbol protocol. @kyneta/schema builds the interpreter algebra on top of it. Everything else — substrates, exchange, transports, bindings — builds on schema's Substrate interface and changefeed's reactive protocol.
| Example | Description |
|---|---|
todo |
Minimal collaborative todo list — Cast compiler + Exchange + Yjs over WebSocket |
todo-react |
Same domain, React bindings — proves the sync layer is framework-agnostic |
bumper-cars |
Heterogeneous documents in one Exchange — collaborative CRDTs, authoritative server state, and ephemeral presence side by side |
unix-socket-sync |
Leaderless TUI config sync over Unix sockets — N identical processes, one socket path, Loro CRDT convergence |
prisma-counter |
Collaborative Loro counter with Prisma/Postgres persistence — survives server restart |
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run all tests
pnpm test
# Run tests for a specific package
cd packages/schema && pnpm testThese packages explore ideas at the frontier of the project. They are functional and well-tested, but represent research directions rather than stable APIs.
| Package | Description | Tests |
|---|---|---|
@kyneta/compiler |
Target-agnostic incremental view maintenance compiler. Transforms TypeScript AST into classified IR annotated with binding times, delta kinds, and incremental strategies. | 547 |
@kyneta/cast |
Web rendering target (codename Kinetic). Consumes compiler IR and produces DOM manipulation code that directly consumes CRDT deltas — character-level text patches, O(k) list updates, branch swapping — with no virtual DOM and no diffing. | 634 |
CRDTs already know what changed. When you insert a character, the CRDT emits a delta saying exactly where. Traditional UI frameworks ignore this — they diff output to rediscover changes. The compiler transforms natural TypeScript into code that directly consumes these deltas, achieving O(k) DOM updates where k is the number of operations. See the Kinetic status for details.
| Package | Description | Tests |
|---|---|---|
@kyneta/perspective |
Constraint-based CRDTs (codename Prism). Agents assert constraints, merge is set union, and a stratified Datalog evaluator derives shared reality. Includes an incremental pipeline based on DBSP. Private — not published to npm. | 1,374 |
Traditional CRDTs couple state representation with merge logic. Perspective separates them: the semilattice moves to constraint sets, and a Datalog solver derives state. Conflict resolution strategies become rules that travel inside the data. See the Perspective README for the full treatment.
- Bananas, Lenses, Envelopes and Barbed Wire — Meijer, Fokkinga & Paterson, 1991. F-algebras, catamorphisms, and recursion schemes over algebraic data types. The theoretical basis for the schema interpreter algebra.
- CRDTs — Shapiro, Preguiça, Baquero & Zawirski, 2011. Conflict-free Replicated Data Types. The merge semantics behind Loro and Yjs substrates.
- DBSP — Budiu, McSherry, Ryzhyk & Tannen. Algebraic incremental view maintenance via Z-sets. Foundation for the compiler's incremental pipeline.
- Concurrent Constraint Programming — Saraswat, 1993. Theoretical ancestor of Perspective's constraint-based CRDTs.
- CALM Theorem — Hellerstein, 2010. Consistency as logical monotonicity.
- Datalog — Ullman, 1988. The query language powering Perspective's solver.
MIT — see LICENSE.