Skip to content

Docs Getting started

Project status

Kizuna is Alpha: the repository contains a working client engine, drivers, a SQL pack, a native CLI, native bindings, tests, and the five examples in Playground. Apps install one npm package, kizunasync, which holds the CLI, the SQL pack, every JavaScript entry point as a subpath, and the React Native module. npm also installs the one @kizunasync/<platform> package that matches the machine, which carries the CLI binary and the N-API engine. The release workflows publish the five platform packages and then kizunasync on a v* tag matching the workspace version. The workspaces behind the subpaths and @kizunasync/utilities, @kizunasync/ui, @kizunasync/protocol, and @kizunasync/supabase-pack stay private, and Rust crates carry publish = false.

The native CLI's user-facing VERSION constant and the SQL pack's _provisions.pack_version both read 0.2.6-alpha.3 from Cargo, matching every other workspace; kizunasync --version prints that value.

This page separates two states: "Implemented in the repository" describes a source-backed surface, and "Verified" applies only when the named automated or manual evidence exists. Code that exists and a check that proves the code behaves are different things, and no line below lets one stand in for another.

Surface matrix#

SurfaceRepository stateDistribution and evidence
Shared app clientImplemented: local query subset evaluated inside the Rust kernel on every runtime, optimistic writes, transactional SQLite-backed outbox, pull/push scheduler, rejection journal, reset, events, inspector snapshot, attachmentsnpm kizunasync; abrupt-termination evidence remains driver/platform-specific
Rust engineImplemented across the store, query, protocol, engine, transfer, remote HTTP, N-API, UniFFI, and wasm crates, and it is the only engine any client runsCrates are workspace-internal; workflows build native artifacts for the bindings above
Browser driverImplemented as the Rust engine compiled to WebAssembly in a dedicated worker, over the OPFS synchronous-access-handle pool or the relaxed IndexedDB fallback, with one leading tab per database and the others proxied to itnpm kizunasync, subpath kizunasync/web
Expo driverImplemented with expo-sqlite on device, the kizunasync/web driver on Expo web, plus file storage and connectivitynpm kizunasync, subpath kizunasync/expo. The Expo web target is wired to the kizunasync/web driver, and a Metro web export bundles the kizunasync/web worker as its own bundle.
React Native op-sqlite driverImplemented behind the common driver API with a verification helperOpt-in; no documented physical-device release evidence
React and Vue bindingsImplemented providers, hooks/composables, query invalidation, sync state, rejections, and attachmentsnpm kizunasync, subpaths kizunasync/react and kizunasync/vue
Supabase adaptersImplemented RPC remote, Realtime doorbell, standard Storage transfer, and resumable TUS transfernpm kizunasync, subpath kizunasync/supabase
SQL packImplemented as one installable migration plus generated per-project table configuration, including the opt-in server-side conflict journalSource-available under PolyForm Shield; installs through kizunasync init
CLINative Rust commands for init, sync, status, doctor, lint, upgrade, deprovision, jobs, mock seed, mock churn, and version, plus the global -v / --version flags; a guided wizard for init and sync, and Management API parity for bothInvoke as npx kizunasync, pnpm dlx kizunasync, yarn dlx kizunasync, or bunx kizunasync
Direct Swift and KotlinImplemented: typed KizunaSyncClient over UniFFI matching IKizunaSync's write builders, catalog error codes, and verdict-ring inspector (journal, events, transforms, opt-in attachmentRoot attachments, host KizunaSyncScheduler with health()/onHealth) plus iOS and Android example projectsSwift package kizunasync/kizunasync-swift and Maven com.kizunasync:kizunasync; Android AAR/APK uses Gradle wrapper 8.7 with AGP 8.6.1
React Native Rust pathrn-uniffi loader and generated ubrn Turbo Modulenpm kizunasync, which ships the React Native module and autolinks it; needs a native rebuild
Protocol and corpusSchemas, decision registry, TypeScript oracle, golden transcripts, Rust runner, and six TLA+ property modelsPublic source; no standalone third-party TCK package
ExamplesExpo, React, Vue, native iOS, and native AndroidSource projects with individually documented runnable lanes; Android AAR/APK configure under wrapper 8.7 / AGP 8.6.1 when the SDK is present; JVM host tests unset the SDK variables
Sync inspectorRead-only Next.js inspector exists in apps/sync-inspectorLocal development tool bound to 127.0.0.1; not deployed
Documentation websiteNext.js renderer loads the public Markdown registryDeploys to Vercel through deploy-website.yml when a v* tag on main runs the release pipeline

Engine selection#

The app-facing JavaScript app client, createKizunaSync, chooses its engine on its first use, and only an engine that can open the same SQLite database as the driver, as How Kizuna works explains:

  • A browser app runs Rust through WebAssembly. The kizunasync/web driver carries the engine itself: the worker owns the store, and the driver exposes no SQL methods of its own, so the transport is the only route to those rows.
  • Node and Bun run Rust through N-API when the addon loads. The driver names the store as databasePath, or null for a private in-memory database.
  • React Native runs Rust through UniFFI when the Turbo Module is linked. The kizunasync/expo drivers load it through their loadNativeEngine, so the kizunasync entry point never imports the module. The same databasePath rule applies.
  • Where none of the three resolves, the app client's first use fails with ENGINE_UNAVAILABLE, which names the artifact to install plus every path the addon loader tried, and every later engine call fails with the same error. Building the client never throws it. There is no engine to fall back to, and no environment variable that changes the outcome.

The JavaScript app client exposes the attachment queue, which Media and attachments documents. Direct Swift/Kotlin hosts opt in with attachmentRoot on KizunaSyncClient.create (fromFile, resolveDownload, vacuum, watch). React Native UniFFI must omit attachment_root so the JavaScript host's attachment queue owns bytes.

Attachment status#

The JavaScript Supabase transfer chooses by byte size:

  • A file of at most 6 MiB takes the standard Storage upload.
  • A file above 6 MiB takes a resumable TUS upload in 6 MiB chunks.
  • A new TUS session URL is persisted before the first chunk and reused to recover the server offset.

Each attachment carries an attempt budget, attachmentAttempts in defineConfig, default 5. The budget's sixth failure, and a Storage 404 on download, move the queued entry to a permanent terminal state rather than retrying it forever; retry, cancel, and remove are the app-facing controls over that lifecycle.

The repository includes TUS unit coverage and Rust interruption/resume coverage. A normal unit test is not evidence of a successful physical-device upload to a hosted Supabase project. Match the test to the claim states which suite backs which claim.

Protocol evidence#

The corpus manifest, which the Protocol reference describes, contains 50 cases:

  • 49 cases execute. The JavaScript reference executor runs them against the oracle server, and three client lanes run them against the engine: the Rust conformance runner, the N-API transport lane in the core workspace, and the browser lane in the web workspace.
  • 1 case is skipped; wakeup/002-wakeup-payload has no transcript bytes because the channel payload remains an open protocol decision.
  • Six TLA+ models cover cursor monotonicity, no lost committed change, exactly-once effect, session guarantees, atomic checkpoint, and no resurrection.

The live SQL replay used by the database job pins 46 replayable transcript families:

  • 45 families replay as PASS-SEMANTIC.
  • 0 families replay as DIVERGED.
  • 1 family is UNSUPPORTED, because a synchronous single-connection replay cannot deterministically reproduce the held-transaction fencing race.
  • 0 families end in ERROR.

The live-SQL count and the 50-case corpus count measure different things and must not be combined. One counts recorded protocol cases and the other counts transcript families replayed against a live Postgres, so a sum of the two would describe nothing.

SQL pack status#

packages/supabase-pack/pack.manifest.json separates:

  • 0001_kizuna_init.sql is the installable engine pack.
  • 0002_example.sql is the local demo fixture.

The pack creates Kizuna-owned schema objects, itemized in What is installed. Registering an application table also installs Kizuna triggers on that table; it does not add Kizuna columns or replace the table's RLS policies. Buckets select rows but do not authorize them.

Turning on a table's conflict_journal column enables server-side recording of overwritten column values in kizunasync._conflict_journal, which Conflict history explains. It defaults to false. Pull may attach matching rows as optional conflicts, omitted when empty. The engine persists _kizunasync_overwrites and emits COLUMN_OVERWRITTEN. Authenticated callers have no SELECT; service_role can read the audit rows.

kizunasync._settings also carries retention knobs and job schedules. The three schedules are the tombstone reaper, the changelog compactor, and the client pruner, each a UTC crontab that kizunasync._schedule_jobs() writes into pg_cron. The knobs are client TTL, HLC skew, and project tombstone retention. The pack tries to enable pg_cron itself and installs cleanly either way. kizunasync jobs runs the three functions by hand over a service connection, whether or not pg_cron is present.

Current limitations#

  1. Distribution covers npm, the Swift package URL, Maven Central, and GitHub Releases; the Rust crates do not go to crates.io. Release contract names what each channel still needs.
  2. The local query API is a subset. Unsupported PostgREST operations throw LOCAL_UNSUPPORTED; there is no transparent network fallback. Supported query operators lists what the subset answers.
  3. Offline acceptance is optimistic. Row Level Security, constraints, preconditions, and push policies can reject a queued mutation after reconnect, and Offline writes shows how to surface that verdict.
  4. Swift/Kotlin KizunaSyncClient matches IKizunaSync, and the Swift package and Maven artifacts publish from a v* tag. Android configuration uses wrapper 8.7 with AGP 8.6.1. The file picker is the app's (fromFile takes a path). There are no native React-style hooks; Swift and Kotlin shows the pattern that replaces them.
  5. Physical-device release evidence is incomplete. CI defines host, iOS simulator, Expo simulator, and Android assembly lanes, and it records no physical-device matrix. AAR and APK jobs produce CI artifacts.
  6. The wakeup byte format is not final, because the wakeup payload decision remains blocked. Polling and explicit pull preserve correctness meanwhile.
  7. Kizuna does not package third-party driver tooling. The corpus and headless harness exist, and no standalone TCK command ships.
  8. The Android connectivity path monitor and foreground source run under Robolectric on the :android lane, which exercises KizunaSyncConnectivityPathMonitor and KizunaSyncProcessForegroundSource against Android's real APIs on the JVM. Only device behavior, real radios and the real process lifecycle, is unverified.
  9. The CLI refuses --require-atomic. Ordinary writes are non-atomic, so enabling the flag would dead-letter them. kizunasync doctor fails when _settings.require_atomic is already on. kizunasync sync --no-require-atomic turns it off.
  10. LocalStore wraps one rusqlite::Connection, which is Send and not Sync: one store serves one thread at a time. Every binding gives it a dedicated thread of its own instead of sharing it: N-API runs each command as a task on a LocalSet over one thread, and UniFFI runs a per-handle actor thread that receives closures and returns their replies. Engine selection names which client uses which lane.
  11. The protocol corpus leaves five cases uncovered. They are pagination under concurrency, transforms on an absent or RLS-hidden row, and transforms on insert or delete. The other two are integers at or above 2^31 and the KZP push-policy error codes. The pack test pull-fencing-continuation.test.ts covers the continuation-page fencing case the corpus omits. Each gap is a hole in test coverage, not a second implementation of the engine.
  12. A synced table needs a primary key whose columns are uuid, text, character varying, smallint, integer, or bigint, and a table with an attachment column needs a single uuid key named id. On a read-write table keyed by an identity or serial column, an insert made offline has to carry the key value, because the device cannot generate one. Row keys recommends uuid keys for the tables devices create rows in.

Sequenced future work lives on the Roadmap, not on this page.

Next steps#

  • Roadmap: sequenced future work, with each item's status.
  • Swift and Kotlin: the native client setup this page scopes.
  • CI and CD: the workflows behind the evidence claims above.
  • Status taxonomy: what implemented, verified, and published each mean.

Found a problem? Edit this page on GitHub