Docs Getting started
Introduction
Kizuna is offline-first sync for the Supabase project you already own. Your application keeps reading and writing a local SQLite cache while you are away from the network. When the connection returns, it pushes the queued mutations and pulls the rows Postgres has committed. No Kizuna-operated service sits in the data path, and there is no second application datastore to keep honest.
Vision#
That is the whole product, lived from the screen. You declare which tables belong in the synchronized set through defineConfig. Kizuna keeps a local copy of the rows the current user is allowed to pull, so every screen can query that copy as if the device were the database.
A write updates the local view at once and enters a transactional outbox, the queue of changes waiting for the server. An edit you make in a tunnel is therefore on screen before anything leaves the device. It comes back stamped applied or rejected once the client reaches your project again, which Offline writes walks through end to end.
Authority never leaves your project. Postgres remains the source of truth. Supabase Auth issues the session Kizuna forwards on every pull and push, and Kizuna holds no identity of its own. Your Row Level Security policies judge a queued mutation with the same rules an online write meets. An offline mutation is optimistic. It counts as the server's write only after the push RPC returns an applied or rejected verdict.
You provision that pack from the application repository with kizunasync (npx kizunasync, pnpm dlx kizunasync, yarn dlx kizunasync, or bunx kizunasync). kizunasync init installs the kizunasync schema, the protocol RPCs, and the triggers on the tables you register.
Hosted configuration is a plan rather than a supported path, so the terminal is how a project is born. The shape that work might take lives in Management and tooling surfaces.
Under those clients sits one Rust kernel. Swift and Kotlin reach it through generated UniFFI packages; JavaScript reaches the same kernel through a compiled N-API addon or a compiled WebAssembly worker. There is no second engine and no public Rust application SDK. The three app clients are peers: JavaScript createKizunaSync, Swift KizunaSyncClient, and Kotlin KizunaSyncClient. Browser apps run the kernel in the driver's worker. Node, Bun, and React Native load the matching compiled artifact; the driver names the store as databasePath, or null for a private in-memory database. Where the artifact cannot load, the app client's first use fails with ENGINE_UNAVAILABLE and names what to install. Project status lists every lane.
Features#
- Application rows never leave your Postgres: Kizuna installs a schema, protocol RPCs, and triggers on the tables you register, and it never moves those rows into another vendor's datastore.
- Screens query local SQLite, so a read does not wait on the network; an operator the local builder does not implement throws
LOCAL_UNSUPPORTEDinstead of falling through to Supabase, and Supported query operators says which operators exist. - Insert, update, and delete land in a durable outbox and replay after reconnect, so a tunnel, a flaky radio, or a closed laptop does not eat the write; whether that queue survives abrupt process death still depends on the driver and the platform.
- Two devices that touch different columns of the same row can both keep their edits; competing edits to one column follow server arrival order, or a Hybrid Logical Clock on tables configured that way, which Conflict resolution sets out in full.
increment,arrayUnion, andarrayRemoveare Firestore-style transforms on anupdate, not a fourth operation and not CRDTs, so counters and membership lists can move on more than one device without waiting for a lock.- Files travel with the same engine: at or below 6 MiB they use a standard Storage upload, and larger files use a resumable TUS upload, both queued so a disconnect does not abandon the transfer. Media and attachments is the guide, and native hosts opt in with
attachmentRoot. - A bucket chooses which rows you copy to the device, and the server still checks the caller's JWT on every pull and push, so selection never impersonates authorization.
- Consistency claims are executable. Schemas, decisions, and golden transcripts live in
packages/protocol/. A guarantee that cannot match that corpus is not a guarantee. The Protocol reference documents that corpus.
Use cases#
Kizuna fits an application that already lives on Supabase, that has to keep a declared set of rows usable when the radio drops, and that can accept reconciliation after reconnect: a field inspection that must be filled in a basement, an inventory count that cannot wait for the warehouse access point, a personal productivity app whose owner expects yesterday's list on a train.
It is the wrong instrument for a character-level collaborative editor, for a server-authoritative realtime game loop, for a global constraint that has to hold while every device is offline, or for a write path that commits synced-table transactions at a high sustained rate, because those commits are numbered one at a time. Unique inventory and rules like it cannot be guaranteed without contacting an authority; Kizuna surfaces the server's verdict when synchronization resumes, and What the model does not provide is the full list. Comparison with alternatives puts Kizuna next to PowerSync, Electric, Firestore, and others.
Next steps#
- Quick start: provision your Supabase app with
kizunasyncfrom the terminal. - Playground: the hosted demo and the five reference apps.
- How Kizuna works: outbox, pull, verdicts, and files in five steps.
- Architecture: kernel, SQL pack, and what deliberately does not exist.
Found a problem? Edit this page on GitHub