← Mosaic

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:

  1. 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.
  2. 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.
  3. 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 serve terminates TLS with a ts.net certificate.
  • CORS. Allow the mosaic origin: Access-Control-Allow-Origin for 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).