.env.example and the README still told people to use pgCat on :6432, the exact setup that hangs on every parameterised query. With no prod and one backend instance there is nothing for a pooler to multiplex, and postgres-js already pools in-process, so DATABASE_URL now points at Postgres on :5432 in .env.example, dev-env.sh and drizzle.config.ts. prepare:false is kept in src/db/index.ts on purpose, so adding PgBouncer later (tested working) is a config change rather than a code change. The long measurement narrative in that file's comment is cut to the decision and the pointer to PLAN.md, where the numbers now live.
87 lines
4.3 KiB
Markdown
87 lines
4.3 KiB
Markdown
# continuum-backend
|
|
|
|
Cloud control plane for Continuum. Bun + ElysiaJS, PostgreSQL (with pgvector for frame embeddings), Valkey Streams, and Cloudflare R2 storage.
|
|
|
|
## Run
|
|
|
|
```sh
|
|
bun install
|
|
scripts/dev-env.sh up # Valkey + Postgres/pgvector, and print the URLs
|
|
scripts/dev-env.sh schema # apply drizzle/ - same as bun run db:migrate
|
|
cp .env.example .env # fill in the URLs it printed, plus R2_*, SUPABASE_JWT_SECRET
|
|
bun run dev
|
|
```
|
|
|
|
`scripts/dev-env.sh` uses `docker-compose.yml` when a Docker daemon is
|
|
answering, and falls back to the same two services from apt when there is no
|
|
usable container runtime - some sandboxes cannot run Docker at all, because
|
|
dockerd needs `CAP_NET_ADMIN` for its bridge and user namespaces to unpack an
|
|
image, and neither can be granted from inside. `scripts/dev-env.sh status`
|
|
says which backend is in use.
|
|
|
|
Schema changes go through migrations: edit `src/db/schema/`, run
|
|
`bun run db:generate`, and commit the file it writes to `drizzle/` along with
|
|
the schema change. `scripts/dev-env.sh push` reshapes a local database straight
|
|
from the schema with no migration file, which is faster while a table is still
|
|
moving but records nothing and will drop a column to make things match - so
|
|
generate before committing. `drizzle/0000_*.sql` creates the `vector` extension
|
|
itself; that line is hand-added, since drizzle-kit does not emit extension
|
|
statements and the migration would otherwise only apply to a database somebody
|
|
had already prepared.
|
|
|
|
## Test
|
|
|
|
```sh
|
|
bun test # unit + protocol contract tests, no services needed
|
|
scripts/e2e-edge.sh # the whole edge path: real database, real printers
|
|
```
|
|
|
|
`scripts/e2e-edge.sh` seeds a farm, claims its printers, mints an edge token,
|
|
runs the real `continuum-proxy` against this backend, and asserts that live
|
|
telemetry reached Postgres and that a command reached a printer. It only ever
|
|
sends a fan speed, and only to a printer reporting `IDLE`.
|
|
`scripts/capture-gateway-frames.js` is the smaller tool: it stands in for this
|
|
backend and records what a gateway sends, for refreshing the fixtures in
|
|
`tests/fixtures/`.
|
|
|
|
## Layout
|
|
|
|
- `src/db/` - Drizzle schema (`printers`, `farms`, `print_jobs`, `frame_embeddings`)
|
|
and client, pointed straight at Postgres. `src/db/validators.ts` derives Elysia/TypeBox
|
|
request validators straight from the Drizzle tables via `drizzle-typebox`,
|
|
so the Postgres schema and the API validators come from one definition
|
|
instead of two hand-synced ones.
|
|
- `src/s3/` - R2 presigned URL helpers (`PUT` for client uploads, `GET` for the
|
|
edge proxy).
|
|
- `src/queue/` - Valkey client (`ioredis`) + `XADD` helper pushing onto
|
|
`stream:camera_frames` for `continuum-ai-worker`.
|
|
|
|
Valkey, not Redis: Redis 7.4+ is RSALv2/SSPL and 8.0+ adds AGPLv3, none of
|
|
which a commercial product can ship. Valkey is BSD-3-Clause and speaks the
|
|
same RESP protocol, so `ioredis` is unchanged - only the server differs. See
|
|
`continuum-common/docs/LICENSES.md`. `docker-compose.yml` here brings up
|
|
Valkey and Postgres+pgvector for local development.
|
|
- `src/routes/` - `/auth`, `/farms`, `/printers`, `/jobs`, and the edge-gateway
|
|
WebSocket at `/ws/edge/v1`. That route's wire format is not defined here: it
|
|
is `schemas/continuum/v1/edge/` in continuum-schemas, validated through its
|
|
TypeBox mirror in `@continuum/ts-types`, because both ends of it are separate
|
|
programs in different languages and they had already drifted apart once.
|
|
`printers.gateway_printer_id` is what maps a gateway's own printer ids
|
|
(`"p1p"`) onto rows here; telemetry for an unclaimed id is logged and skipped,
|
|
never inserted.
|
|
|
|
`frame_embeddings.embedding` requires the `vector` extension. The first
|
|
migration creates it, and `scripts/dev-env.sh up` also does so for the `push`
|
|
path, which does not run migrations.
|
|
|
|
## Dependency pins
|
|
|
|
`elysia`, `@sinclair/typebox` and the `drizzle-*` packages are pinned tightly
|
|
on purpose, not out of caution. Caret ranges here resolved into a combination
|
|
that could not boot: `elysia: ^1.1.26` installed 1.4.30, which calls
|
|
`t.Module` and so needs TypeBox >=0.34, while `drizzle-typebox: ^0.2.1`
|
|
installed a version whose peers demand `drizzle-orm >=0.36` against the
|
|
`^0.33.0` pinned here. Typechecking passed throughout - only starting the
|
|
server failed. `tests/dependency_identity.test.ts` guards the part of this that
|
|
can silently come back.
|