Files
logseq/docs/agent-guide/db-sync/protocol.md

121 lines
7.4 KiB
Markdown

# DB-Sync Client-Server Protocol
## Transport
- WebSocket `ws(s)` to `/sync/:graph-id`.
- Client builds URL from config and appends `?token=...` when available.
- Encoding: JSON objects; `tx` payloads are Transit strings.
- Note: keep this document in sync with the current implementation.
## Client -> Server
- `{"type":"hello","client":"<repo-id>"}`
- Initial handshake from client.
- `{"type":"presence","editing-block-uuid":"<uuid|null>"}`
- Update current editing block for presence (omit or null to clear).
- `{"type":"pull","since":<t>}`
- Request txs after `since` (defaults to 0).
- `{"type":"tx/batch","t-before":<t>,"txs":[{"tx":"<tx-transit>","tx-id":"<uuid?>","outliner-op":"<keyword?>"}, ...]}`
- Upload a batch of txs based on `t-before` (required).
- `tx-id` is optional but recommended for per-entry ack/reject mapping.
- `{"type":"ping"}`
- Optional keepalive; server replies `pong`.
## Server -> Client
- `{"type":"hello","t":<t>,"checksum":"<hex>"}`
- Server hello with current t and entity checksum.
- `{"type":"online-users","online-users":[{"user-id":"...","email":"...","username":"...","name":"..."}...]}`
- Presence update
- Optional `editing-block-uuid` indicates the block the user is editing.
- `{"type":"pull/ok","t":<t>,"checksum":"<hex>","txs":[{"t":<t>,"tx":"<tx-transit>","outliner-op":"<keyword?>"}...]}`
- Pull response with txs and post-apply entity checksum.
- `{"type":"tx/batch/ok","t":<t>,"checksum":"<hex>"}`
- Batch accepted; server advanced to t and returns the resulting entity checksum.
- `{"type":"changed","t":<t>}`
- Broadcast once after a handled `tx/batch` that advanced server state (`t` increased); client should pull.
- `{"type":"tx/reject","reason":"stale","t":<t>}`
- Client tx is based on stale t.
- `{"type":"tx/reject","reason":"db transact failed","t":<t>,"success-tx-ids":["<uuid>",...],"failed-tx-id":"<uuid>"}`
- Server-side transact/validation failed for one tx entry in the batch.
- `success-tx-ids` are entries already applied before the failure.
- `failed-tx-id` is the entry that failed.
- Legacy servers may return `data` with rejected tx payload for debugging.
- `{"type":"tx/reject","reason":"empty tx data"|"invalid tx"|"invalid t-before"|"snapshot upload in progress"}`
- Invalid batch.
- `{"type":"pong"}`
- Keepalive response.
- `{"type":"error","message":"..."}`
- Invalid/unknown message. Current messages: `"unknown type"`, `"invalid request"`, `"server error"`, `"invalid since"`.
## HTTP API
- Auth: Bearer token via `Authorization: Bearer <token>` or `?token=...`.
- JSON body/response unless noted.
- Auth required for `/graphs`, `/sync/:graph-id/*`, and `/assets/*`. Expect `401` (unauthorized) or `403` (forbidden) on access failure.
### Worker Health
- `GET /health`
- Worker health check. Response: `{"ok":true}`.
### Graphs (index DO)
- `GET /graphs`
- List graphs the user owns. Response: `{"graphs":[{graph-id, graph-name, schema-version?, graph-ready-for-use?, created-at, updated-at}...]}`.
- `POST /graphs`
- Create graph. Body: `{"graph-name":"...","schema-version":"<major>"}` (schema-version optional). Response: `{"graph-id":"...","graph-ready-for-use?":false}`.
- `graph-ready-for-use?` is persisted in the D1 `graphs` row. Existing graphs default to `true`; bootstrap uploads flip it to `false` until the final snapshot upload request completes.
- `GET /graphs/:graph-id/access`
- Access check. Response: `{"ok":true}`, `401` (unauthorized), `403` (forbidden), or `404` (not found).
- `GET /graphs/:graph-id/members`
- Graph members list. Response: `{"members":[{user-id, graph-id, role, invited-by, created-at, email?, username?}...]}`.
- `DELETE /graphs/:graph-id`
- Delete graph and reset data. Response: `{"graph-id":"...","deleted":true}` or `400` (missing graph id).
### E2EE (index DO)
- `GET /e2ee/user-keys`
- Fetch current user's RSA key pair. Response: `{"public-key":"<transit>","encrypted-private-key":"<transit>"}` or `{}` when missing.
- `POST /e2ee/user-keys`
- Upsert current user's RSA key pair. Body: `{"public-key":"<transit>","encrypted-private-key":"<transit>","reset-private-key":false?}`.
- Response mirrors the stored keys: `{"public-key":"<transit>","encrypted-private-key":"<transit>"}`.
- `GET /e2ee/user-public-key?email=<email>`
- Fetch a user's RSA public key by email. Response: `{"public-key":"<transit>"}` or `{}` when missing.
- `GET /e2ee/graphs/:graph-id/aes-key`
- Fetch current user's encrypted graph AES key. Response: `{"encrypted-aes-key":"<transit>"}` or `{}` when missing.
- `POST /e2ee/graphs/:graph-id/aes-key`
- Upsert current user's encrypted graph AES key. Body: `{"encrypted-aes-key":"<transit>"}`.
- Response: `{"encrypted-aes-key":"<transit>"}`.
- `POST /e2ee/graphs/:graph-id/grant-access`
- Manager-only. Upsert encrypted graph AES keys for members.
- Body: `{"target-user-email+encrypted-aes-key-coll":[{"user/email":"<email>","encrypted-aes-key":"<transit>"}...]}`.
- Response: `{"ok":true,"missing-users":["<email>", ...]?}`.
### Sync (per-graph DO, via `/sync/:graph-id/...`)
- `GET /sync/:graph-id/health`
- Health check. Response: `{"ok":true}`.
- `GET /sync/:graph-id/pull?since=<t>`
- Same as WS pull. Response: `{"type":"pull/ok","t":<t>,"checksum":"<hex>","txs":[{"t":<t>,"tx":"<tx-transit>","outliner-op":"<keyword?>"}...]}`.
- Error response (400): `{"error":"invalid since"}`.
- Error response (409): `{"error":"graph not ready"}` when bootstrap upload/import has not finished.
- `POST /sync/:graph-id/tx/batch`
- Same as WS tx/batch. Body: `{"t-before":<t>,"txs":[{"tx":"<tx-transit>","tx-id":"<uuid?>","outliner-op":"<keyword?>"}, ...]}`.
- Response: `{"type":"tx/batch/ok","t":<t>,"checksum":"<hex>"}` or `{"type":"tx/reject","reason":...}`.
- Error response (400): `{"error":"missing body"|"invalid tx"}`.
- Error response (409): `{"error":"graph not ready"}` when bootstrap upload/import has not finished.
- `GET /sync/:graph-id/snapshot/download`
- Build a snapshot file in R2 and return a download URL.
- Response: `{"ok":true,"key":"<graph-id>/<uuid>.snapshot","url":"<origin>/assets/:graph-id/<uuid>.snapshot","content-encoding":"gzip"}`.
- Error response (409): `{"error":"graph not ready"}` when bootstrap upload/import has not finished.
- The snapshot file stored in R2 is a framed Transit stream of sqlite `kvs` rows (`[addr, content, addresses]`), optionally gzip-compressed.
- `POST /sync/:graph-id/snapshot/upload?reset=true|false`
- Upload a snapshot stream for bootstrap import. Current upload format remains framed Transit JSON kvs rows, optionally gzip-compressed.
- Request body: binary stream; headers should include `content-type: application/transit+json` and `content-encoding: gzip` when compressed.
- Response: `{"ok":true,"count":<n>,"key":"<graph-id>/<uuid>.snapshot"}`.
- Error response (400): `{"error":"missing body"|"missing graph id"}`.
- `DELETE /sync/:graph-id/admin/reset`
- Drop/recreate per-graph tables. Response: `{"ok":true}`.
### Assets
- `GET /assets/:graph-id/:asset-uuid.:ext`
- Download asset (binary response, `content-type` set, `x-asset-type` header included).
- `PUT /assets/:graph-id/:asset-uuid.:ext`
- Upload asset (binary body). Size limit ~100MB. Response: `{"ok":true}`.
- `DELETE /assets/:graph-id/:asset-uuid.:ext`
- Delete asset. Response: `{"ok":true}`.
- Asset error responses: `{"error":"invalid asset path"}` (400), `{"error":"not found"}` (404), `{"error":"asset too large"}` (413), `{"error":"method not allowed"}` (405), `{"error":"missing assets bucket"}` (500).