Offline-first data sync
A database that keeps its own time, and agrees later.
Read and write locally with no network in the loop. Devices converge whenever they can reach each other, over the local network, across the internet, or by scanning a QR code with no server involved at all.
npm install tangentfeed
Replica A0 ops
Replica B0 ops
How it works
Operations, not snapshots
Every write becomes one immutable operation against a single cell, addressed by table, row, and column. Nothing overwrites anything: the log is the truth, and the tables you read are a cache built from it.
Each operation carries a hybrid logical clock stamp, so replicas agree on ordering even when their wall clocks disagree. Merging is last writer wins per cell, which means two devices editing different fields of the same row both keep their edit. That is the difference between a sync layer you can trust and one that quietly loses work.
Because merging is commutative, associative, and idempotent, operations can arrive in any order, duplicated or delayed, and every replica still lands on identical state. That single property is what lets the transport layer be dumb, and it is why sync survives flaky networks without any coordination.
| id | The clock stamp, which is globally unique, so replaying an operation twice is a no-op |
|---|---|
| table / row / column | The cell being written. Concurrent edits to different columns never conflict |
| value | Any JSON value. Encrypted before it enters the log when encryption is on |
| hlc | Hybrid logical clock: millis-counter-device, ordered by plain string comparison |
| device | Which replica wrote it, and the final tiebreak when two stamps match |
Transports
Pick how devices meet
Transports move opaque messages and may lose, duplicate, or reorder them freely. Run several at once; the engine deduplicates.
Broadcast
Tabs and workers sync instantly through the browser. No infrastructure, nothing to configure.
WebRTC
Devices connect directly. A small signaling server introduces them, then gets out of the way. It never sees your data.
QR pairing
Two devices exchange codes by camera or copy and paste, then sync directly. Works with no internet at all.
import { openSpace, broadcast, webrtc } from "tangentfeed"; const db = await openSpace({ space: "kitchen-42", transports: [broadcast(), webrtc({ signaling: "wss://sync.example.com" })], encryption: { passphrase: userSecret }, }); const id = await db.insert("tasks", { title: "Order rice bran oil", done: false }); await db.update("tasks", id, { done: true }); db.subscribe(async () => render(await db.list("tasks")));
Properties
What you get, and what it costs
Storage you already have
IndexedDB in browsers, SQLite on servers and desktop. Same protocol, same guarantees. The SQLite file is an ordinary database you can query while the app runs.
Servers that cannot read you
Cell values are encrypted with XChaCha20-Poly1305 before they enter the log. Signaling servers and relays move ciphertext they have no key for.
Bounded growth
Compaction reclaims superseded operations once every known peer has acknowledged them, and reports which peer is holding it back when it cannot.
Portable by specification
The protocol is written down and pinned by language-neutral conformance vectors, so an implementation in another language can prove itself against the same rules.
The honest tradeoff: peers must overlap in time to sync directly. If two devices are never open together, run a small always-on replica so each has someone to reach. It holds a copy and nothing more, with no authority over anyone's data.
Get started
Three lines to a synced database
Start with tabs on one device, add a transport when you need more. Nothing else about your code changes.