N identical processes, one socket path, shared live config. Start as many peers as you want — the first one listens, the rest connect. Kill any peer (including the listener) and the survivors heal automatically. Uses Loro CRDT for crash-tolerant convergence: every peer holds a full replica, so no data is lost when a peer dies.
pnpm install
# Terminal 1:
bun run peer
# Terminal 2:
bun run peer
# Terminal 3:
bun run peer
Each peer renders a TUI — a boxed config editor with 6 fields:
╭─── unix-socket-sync ───╮ (listening)
│ │
│ Dark Mode ● on │
│ Log Level info │
│ Region us-east │
│ Maintenance ○ off │
│ Max Requests 1000 │
│ Rate Limit 100 │
│ │
╰─────────────────────────╯
3 peers: peer-a1b2c3, peer-d4e5f6, peer-g7h8i9
↑↓/jk navigate ←→/hl change q quit
Arrow keys (or vim-style hjkl) navigate and change values. The footer shows the live peer count and peer IDs. Changes propagate instantly — edit a field in one terminal and watch it update in every other.
- Star topology: the first peer probes the socket path, finds nothing, and becomes the listener. Subsequent peers probe, find a listener, and connect as clients.
createUnixSocketPeerhandles all negotiation automatically — probe, decide, connect-or-listen, and heal on failure. One function call, no manual transport wiring.- Kill any peer (including the listener) and the survivors heal. When the listener dies, a connector detects the broken connection, re-probes, and promotes itself to listener. Other connectors then reconnect to the new listener.
- Loro CRDT means every peer holds a full replica of the config document. When peers reconnect after a failure, the Exchange handshake merges their Loro documents — all writes converge, no data lost.
exchange.peerschangefeed drives presence cleanup. When a peer departs, survivors receive apeer-leftevent and remove the departed peer from the shared document'speersrecord.
const ConfigSchema = Schema.struct({
darkMode: Schema.boolean(), // toggle
logLevel: Schema.string(), // cycles: debug → info → warn → error
region: Schema.string(), // cycles: us-east → eu-west → ap-south
maintenance: Schema.boolean(), // toggle
maxRequests: Schema.number(), // step ±100, range [0, 10000]
rateLimit: Schema.number(), // step ±10, range [0, 1000]
peers: Schema.record(Schema.boolean()), // presence map: peerId → alive
})
Each field type has a pure step function (stepBoolean, stepString, stepNumber) that computes the next value given a direction — no mutation, no side effects.
examples/unix-socket-sync/
├── src/
│ ├── peer.ts — entry point (Exchange, unix socket peer, TUI loop)
│ ├── schema.ts — Loro CRDT config document schema
│ ├── fields.ts — field descriptors and pure step functions
│ └── tui.ts — ANSI terminal renderer + keyboard input
├── package.json
├── tsconfig.json
└── README.md
- Leaderless topology — no peer is special. Any peer can die and be replaced. The listener role is emergent, not assigned.
- In-place healing — the peer is a single self-healing transport that swaps its socket mode (listener ↔ connector) and re-establishes its own channels. The Exchange only ever sees channel add/remove, so all documents and CRDT state survive a heal under one stable transport.
- Loro CRDT convergence — concurrent edits to different fields merge correctly. Two peers editing different config values at the same instant produces the union of both changes.
- Zero infrastructure — one socket file, no HTTP, no ports, no coordination service.
- No persistence — state is in-memory only. Kill all peers and the config resets.
- No authentication — any process that can reach the socket file can join.
- No browser UI — this is a terminal-only demo. See the
todo-reactexample for browser-based sync.