Storage contract
Mosaic's hub never has a database. Where its preferences and app registry live beyond this browser is your choice between three tiers — and every one of them is infrastructure you own:
- Self-hosted (max privacy) — you run a tiny store server implementing the REST API below, e.g. on your own machine behind
tailscale serve. Mosaic syncs to it directly; it never touches this site's infrastructure. - Convex (recommended public provider) — a managed database in your own Convex account. Deploy the reference functions once and point mosaic at your deployment. No server to run, no CORS to configure; still your data, in your account.
- Browser-only (fallback / default) — IndexedDB/localStorage. Zero setup; data dies with the browser profile.
Both remote tiers store the same thing — namespaced JSON documents — and mosaic's own deploy is never in the path either way.
Self-hosted: the REST API (v1)
Namespaced JSON documents behind a bearer token. Deliberately tiny so any stack can implement it in an afternoon:
GET /v1/health → { "ok": true }
GET /v1/docs/:namespace → { "keys": ["…"] }
GET /v1/docs/:namespace/:key → stored JSON (404 if absent)
PUT /v1/docs/:namespace/:key body: any JSON → { "ok": true }
DELETE /v1/docs/:namespace/:key → { "ok": true }
Authorization: Bearer <token> (all requests)
namespace, key match [A-Za-z0-9._-]{1,128}Server requirements
- HTTPS. Mosaic is served over HTTPS, so browsers block calls to an http:// server (mixed content). On a tailnet,
tailscale serveterminates TLS with a ts.net certificate. - CORS. Allow the mosaic origin:
Access-Control-Allow-Originfor it,-Headers: Authorization, Content-Type,-Methods: GET, PUT, DELETE, OPTIONS. - One bearer token, chosen by you, checked on every request. Mosaic generates a random one you copy to the server's config.
Reference implementation
apps/store-server in the mosaic repo is a ~150-line Hono + SQLite server implementing exactly this, with setup docs for the tailnet case. Use it, or treat it as executable documentation.
Convex: the same model as functions
Prefer a managed database to a box you run? Deploy the reference functions in apps/store-convex to your own Convex deployment and mosaic talks to it directly. The token travels as an argument, checked server-side against MOSAIC_STORE_TOKEN — parity with the bearer token above:
query docs:list { token, namespace } → string[]
query docs:get { token, namespace, key } → JSON | null
mutation docs:put { token, namespace, key, value } → { ok: true }
mutation docs:remove { token, namespace, key } → { ok: true }
namespace, key match [A-Za-z0-9._-]{1,128}No CORS or HTTPS setup — Convex's *.convex.cloudserves browser clients directly. See apps/store-convex/README.md for the deploy steps.
What mosaic stores remotely
Preferences and the app registry — not secrets. OAuth tokens and API credentials always stay in the browser's localStorage, whichever tier you choose; the sync deliberately excludes any key that looks like a credential or token.
External apps
This contract is for mosaic's own persistence. External apps own their storage entirely — bring whatever backend you like, or implement this same API if the bring-your-own-server pattern fits your app (etch does exactly that with its Overland ingest).