Splitlist is a working multi-device shared checklist in ~130 lines, most of them comments — read the file, save it, open it. It shows the whole model: open-or-create, one indexed field, sealed payload fields, network-arbitrated ordering, invites as the only auth. Copy it and change the schema.
# the entire deployment procedure:
cp starter.html anywhere-a-browser-can-reach/
import { Owner, idbAdapter } from "https://blindrange.dev/blindrange.js?v=3";
const db = await Owner.create(idbAdapter("myapp"), passphrase, SCHEMA,
["seed.blindrange.dev:7501"],
{ networkSecret: "blindrange-public",
gateway: ["https://blindrange.dev/api"] }); // list = failover
await db.insert({ amount: 42, note: "sealed fields travel free" });
await db.query("amount", 0, 100); // range → [{...rec, _rid}]
await db.queryPrefix("name", "sa"); // str fields: prefix search
await db.delete(rid); // tombstone by _rid
await db.nextValue("invoice"); // unique across ALL writers
await db.nextValues("invoice", 10); // block, one round trip
db.invite(); // onboarding string = master key
await Owner.accept(adapter, pass, invite); // join from an invite
await Owner.open(adapter, pass); // reopen this device's state
await db.enableMirror("myapp-mirror"); // local-first reads, persistent
await db.drain(); // await hedged background copies
Schema fields are what you can range-query; every other key in a
record is carried sealed and costs nothing. BLUR
(leaf_width) is the resolution the network may ever
distinguish — the SQL guide explains the privacy
budget; the same rules apply here.
There are no accounts. Whoever holds the state (this device, unlocked
by the passphrase) or an invite (any device, any language — Python's
Owner.accept takes the same string) is a writer. This is the
honest scope of v1: apps for a person or an invited group — which
is most small software. Scoped capabilities (read-only, one table) are a
design item, not a feature; nothing on this page pretends otherwise.
blindrange.js next to your page (best) or pin the import
with an integrity hash; send a strict
Content-Security-Policy; and never add a third-party script,
analytics tag or CDN widget to a page that holds keys. XSS here is not a
cookie theft — it is the database.| operation | public demo network, via gateway |
|---|---|
| create / first open | ~3.5–4.5s |
| insert (quorum-acked) | ~0.6–1.5s — returns at 2 acks, third
copy lands in the background (drain() awaits it) |
| query, first, no mirror | ~8s. Not the data — round trips. Every phase of a query waits for the one before it, and a gateway hop costs 0.2–1.3s each way |
| query, repeat, no mirror | ~2s (cached chain counters) |
sync(), complete local mirror | ~8s for a small ledger, ~20s for a few hundred records |
| query, mirrored | 2–9ms — local, and it stays local while you keep the mirror fresh |
Read that table as one instruction: call sync() and
answer from the mirror. The network path is honest and slow; the
mirrored path is three milliseconds. Everything between those two rows is
the reason the mirror exists.
The gateway adds one hop: browsers refuse plain-HTTP from HTTPS pages,
so requests travel through a fronted node's /fwd. The
gateway sees which nodes you talk to — nodes always could; it reads
nothing — nodes never could. Pass several gateways; any direct node
behind any TLS proxy is one.
The demo network has a published secret and no durability promise — run your own nodes (and gateway: any node + Caddy) for anything real. Not in the browser client, deliberately: SQL dialect, compaction (run it from any Python writer of the same database), QUIC direct paths. The client is one dependency-free file whose every key derivation is pinned byte-identical to the reference by a cross-language test suite — if they ever disagree, the build fails, not your data.