Phase 4 of #139. Depends on Phase 1 (seam) and Phase 3 (Postgres, used as the server's backing store). Phase 2 (SQLite) is also useful as the server's local-dev backing store.
Goal
Stand up a relayburn collector service so burn instances running on remote/ephemeral hosts can ship their data over HTTP instead of needing direct database credentials. Two pieces:
@relayburn/server — a new package: a minimal Node HTTP service that holds one StorageAdapter instance internally and exposes its operations as a REST API.
HttpAdapter — an adapter inside @relayburn/ledger that talks to that server.
Selected via RELAYBURN_STORAGE=http + RELAYBURN_SERVER_URL=https://… + RELAYBURN_API_KEY=….
@relayburn/server package
packages/server/ — minimal node:http + tiny route table (no Express/Fastify). Uses one of the durable adapters internally (Postgres in production, SQLite for local dev) by reading the same RELAYBURN_STORAGE / RELAYBURN_DATABASE_URL env vars.
- Auth via static API-key list:
RELAYBURN_API_KEYS=key1,key2,…. Authorization: Bearer <key> required on every endpoint; 401 otherwise.
- CLI entry:
relayburn server --port 8080 (wired into packages/cli/src/cli.ts).
- Endpoints under
/v1/:
POST /v1/turns, POST /v1/content, POST /v1/compactions, POST /v1/relationships, POST /v1/tool-result-events, POST /v1/user-turns, POST /v1/stamps — append batches; idempotent via content-addressed hashes from index-sidecar.ts.
GET /v1/turns?… (and parallel endpoints) — streaming NDJSON response feeding the client's queryTurns async iterable.
POST /v1/locks/{name}/acquire + POST /v1/locks/{name}/release — cross-host coordination via the server's underlying adapter withLock.
HttpAdapter
packages/ledger/src/adapters/http-adapter.ts — plain fetch client implementing the StorageAdapter interface.
- Bearer-token auth from
RELAYBURN_API_KEY.
- Retries with exponential backoff on transient errors (5xx, network).
- No client-side dedup beyond what the server's underlying adapter applies — keeps the client thin.
- Streaming reads parse NDJSON line-by-line into the
queryTurns async iterable.
Files
- New package:
packages/server/ with src/server.ts, src/routes.ts, src/auth.ts, package.json, tests.
- New:
packages/ledger/src/adapters/http-adapter.ts.
- Modified:
packages/ledger/src/adapters/factory.ts — wire up http branch.
- Modified:
packages/cli/src/cli.ts — add relayburn server subcommand.
- Modified: root
package.json — add packages/server to the workspace.
Verification
- End-to-end: spin up
relayburn server against SQLite locally; run CLI with RELAYBURN_STORAGE=http RELAYBURN_SERVER_URL=http://localhost:8080; ingest a fixture; query results from both client (over HTTP) and server (direct adapter) match exactly.
- Auth: missing/invalid
RELAYBURN_API_KEY → 401.
- Resilience: kill the server mid-ingest, restart it — no data loss, no duplicates after retry.
- Parameterized adapter test suite (from Phase 2) passes against the HttpAdapter pointed at an in-process server backed by SQLite.
Phase 4 of #139. Depends on Phase 1 (seam) and Phase 3 (Postgres, used as the server's backing store). Phase 2 (SQLite) is also useful as the server's local-dev backing store.
Goal
Stand up a relayburn collector service so burn instances running on remote/ephemeral hosts can ship their data over HTTP instead of needing direct database credentials. Two pieces:
@relayburn/server— a new package: a minimal Node HTTP service that holds oneStorageAdapterinstance internally and exposes its operations as a REST API.HttpAdapter— an adapter inside@relayburn/ledgerthat talks to that server.Selected via
RELAYBURN_STORAGE=http+RELAYBURN_SERVER_URL=https://…+RELAYBURN_API_KEY=….@relayburn/serverpackagepackages/server/— minimalnode:http+ tiny route table (no Express/Fastify). Uses one of the durable adapters internally (Postgres in production, SQLite for local dev) by reading the sameRELAYBURN_STORAGE/RELAYBURN_DATABASE_URLenv vars.RELAYBURN_API_KEYS=key1,key2,….Authorization: Bearer <key>required on every endpoint; 401 otherwise.relayburn server --port 8080(wired intopackages/cli/src/cli.ts)./v1/:POST /v1/turns,POST /v1/content,POST /v1/compactions,POST /v1/relationships,POST /v1/tool-result-events,POST /v1/user-turns,POST /v1/stamps— append batches; idempotent via content-addressed hashes fromindex-sidecar.ts.GET /v1/turns?…(and parallel endpoints) — streaming NDJSON response feeding the client'squeryTurnsasync iterable.POST /v1/locks/{name}/acquire+POST /v1/locks/{name}/release— cross-host coordination via the server's underlying adapterwithLock.HttpAdapterpackages/ledger/src/adapters/http-adapter.ts— plainfetchclient implementing theStorageAdapterinterface.RELAYBURN_API_KEY.queryTurnsasync iterable.Files
packages/server/withsrc/server.ts,src/routes.ts,src/auth.ts,package.json, tests.packages/ledger/src/adapters/http-adapter.ts.packages/ledger/src/adapters/factory.ts— wire uphttpbranch.packages/cli/src/cli.ts— addrelayburn serversubcommand.package.json— addpackages/serverto the workspace.Verification
relayburn serveragainst SQLite locally; run CLI withRELAYBURN_STORAGE=http RELAYBURN_SERVER_URL=http://localhost:8080; ingest a fixture; query results from both client (over HTTP) and server (direct adapter) match exactly.RELAYBURN_API_KEY→ 401.