// 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
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.
- ~ 0 ms local reads
- ~ works offline first
- ~ zero config to start
~ the.difference
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.
// 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. Same UX. No spinners. Works offline. → race them yourself
~ sync.in.action
Two devices.
One source of truth.
Edit anywhere, online or off. Reconnect and edits converge — no manual conflict UI, no overwrites, no lost work.
- 01 offline
Both devices offline
Plane mode, dead zone, bad hotel wifi. Every edit applies locally and stays there.
device.adevice.b - 02 editing
Two people edit the same list
No coordination, no locks, no shared cursor. Just edits.
device.adevice.b - 03 synced
Reconnect — merged automatically
Hybrid logical clocks order the edits. CRDTs merge them. Nothing for you to write.
device.adevice.b
~ use.cases
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.
// 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.
~ quickstart
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.
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 }); - 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.
- 02
Read
getMap() returns a reactive CRDT. subscribe() fires whenever edits land — render straight from the map, no fetch state, no spinners.
- 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.
~ fundamentals
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
~ next.step
Ready to try it for real?
One command to scaffold a working real-time app on your machine.