TopGun
open-source · self-hostable · apache 2.0

Real-time apps that
survive offline.

Local writes are instant. Reconnect and everything syncs. Conflict handling is built in. One npm package on the client, pnpm start:server boots the embedded backend.

$ npx create-topgun-app
v2.0 · single-node stable · cluster mode in progress
  • ~ 0 ms local reads
  • ~ works offline first
  • ~ zero config to start

Round-trip every read?
Or just read.

TopGun keeps a live copy of your data on the device. Reads and writes never wait for the network. Sync happens in the background and never blocks the UI.

// classic.rest ~ 220 ms
// every interaction round-trips the server
const res = await fetch('/api/todos');
const todos = await res.json();
render(todos);

// user types → optimistic UI? you write it
// loses internet? you handle that too
· server roundtrip · spinner UX · offline = broken
// topgun.local-first ~ 0 ms
// reads are local — always
const todos = client.getMap('todos');
todos.subscribe(render);

// writes apply instantly, sync in background
todos.set(id, { text, done: false });

// offline? still works. reconnect? auto-merge.
· instant local read · optimistic by default · offline = normal

Same UX. No spinners. Works offline.    → race them yourself

Two devices.
One source of truth.

Edit anywhere, online or off. Reconnect and edits converge — no manual conflict UI, no overwrites, no lost work.

  1. 01 offline

    Both devices offline

    Plane mode, dead zone, bad hotel wifi. Every edit applies locally and stays there.

    device.a
    device.b
  2. 02 editing

    Two people edit the same list

    No coordination, no locks, no shared cursor. Just edits.

    device.a
    device.b
  3. 03 synced

    Reconnect — merged automatically

    Hybrid logical clocks order the edits. CRDTs merge them. Nothing for you to write.

    device.a
    device.b
See the merge happen live
~ crdt-based merge ~ hybrid logical clocks ~ no manual conflict ui

Pick a pattern.
See the code.

Figma-style cursors. Notion-style edits. Every client sees changes within one frame — no manual fan-out, no shared-state plumbing.

// cursors.ts typescript
// shared map — every user writes to their own key
const cursors = client.getMap('cursors:doc-1');

cursors.subscribe((entries) => render(entries));

// my cursor updates as I move
cursors.set(myUserId, { x, y, selection });

Whatever shape your app takes — TopGun handles the sync.

Install. Connect.
Sync.

Three lines of code to a real-time, offline-first app. The defaults get you running; the knobs are there when you need them.

// app.ts typescript
import { TopGunClient } from '@topgunbuild/client';
import { IDBAdapter } from '@topgunbuild/adapters';

// 1. connect — local-first by default, no server required
const client = new TopGunClient({
  storage: new IDBAdapter(),
});
client.start();

// 2. read — reactive, local, instant
const todos = client.getMap('todos');
todos.subscribe(render);

// 3. write — optimistic, persists to IndexedDB
todos.set('todo-1', { text, done: false });
  1. 01

    Connect

    Construct the client with a storage adapter, then call start() once to wire up IndexedDB. No server required for local-first apps — add a serverUrl later when you want sync.

  2. 02

    Read

    getMap() returns a reactive CRDT. subscribe() fires whenever edits land — render straight from the map, no fetch state, no spinners.

  3. 03

    Write

    set(key, value) applies instantly to memory and persists to IndexedDB. With a serverUrl, edits sync in the background; offline writes land on reconnect.

No tricks.
Just fundamentals.

TopGun is built on durable primitives — local-first storage, CRDTs, hybrid logical clocks. Boring under the hood, magic on the surface.

Local-first, by design

Every read is in-memory. Every write is optimistic. Offline is the default state, not the error state — the app keeps working when the network does not.

~ in-memory reads · optimistic writes

CRDTs handle the merge

No conflict UI. No last-write-wins surprises. Hybrid logical clocks order events; CRDTs converge them deterministically. Math, not heuristics.

~ deterministic · convergent · no manual merge

Self-host. Apache 2.0.

A single Rust binary on the server. Embedded storage out of the box. Swap in your own database when scale demands. No vendor, no lock-in.

~ one binary · embedded storage · apache 2.0

Ready to try it for real?

One command to scaffold a working real-time app on your machine.