tangentfeed v0.1

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
Live demo Two replicas, running here, in this page Connected

Replica A0 ops

    Replica B0 ops

      Edit either side and watch operations travel. Cut the connection and they queue instead.

      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.

      idThe clock stamp, which is globally unique, so replaying an operation twice is a no-op
      table / row / columnThe cell being written. Concurrent edits to different columns never conflict
      valueAny JSON value. Encrypted before it enters the log when encryption is on
      hlcHybrid logical clock: millis-counter-device, ordered by plain string comparison
      deviceWhich 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.

      Same device

      Broadcast

      Tabs and workers sync instantly through the browser. No infrastructure, nothing to configure.

      Across networks

      WebRTC

      Devices connect directly. A small signaling server introduces them, then gets out of the way. It never sees your data.

      No server

      QR pairing

      Two devices exchange codes by camera or copy and paste, then sync directly. Works with no internet at all.

      app.ts
      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.