From 4d13570f0b4970aa35cc473e312fef6efd818c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iwo=20Strzebo=C5=84ski?= Date: Fri, 28 Aug 2026 17:02:10 +0000 Subject: [PATCH] Derive Elysia request validators from Drizzle tables via drizzle-typebox --- README.md | 5 ++++- package.json | 2 ++ src/db/validators.ts | 22 ++++++++++++++++++++++ src/routes/printers.ts | 29 ++++++++--------------------- 4 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 src/db/validators.ts diff --git a/README.md b/README.md index 9eda821..b59fa52 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,10 @@ bun run dev ## Layout - `src/db/` — Drizzle schema (`printers`, `farms`, `print_jobs`, `frame_embeddings`) - and client, pointed at pgCat. + and client, pointed at pgCat. `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/` — Redis client + `XADD` helper pushing onto `stream:camera_frames` diff --git a/package.json b/package.json index 3893495..263fea4 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "@aws-sdk/client-s3": "^3.658.1", "@aws-sdk/s3-request-presigner": "^3.658.1", "drizzle-orm": "^0.33.0", + "drizzle-typebox": "^0.2.1", + "@sinclair/typebox": "^0.33.12", "postgres": "^3.4.4", "ioredis": "^5.4.1" }, diff --git a/src/db/validators.ts b/src/db/validators.ts new file mode 100644 index 0000000..c6d129e --- /dev/null +++ b/src/db/validators.ts @@ -0,0 +1,22 @@ +import { createInsertSchema, createSelectSchema } from "drizzle-typebox"; +import { t } from "elysia"; +import { printers, printJobs } from "./schema"; + +// Generated straight from the Drizzle table definitions in `./schema` — this +// is the *only* place these shapes are defined. The Postgres column and the +// Elysia request/response validator can no longer drift apart: change a +// column in `./schema/printers.ts` and both the DB migration and the API +// validator below pick it up automatically. +// +// The second argument overrides fields Drizzle can't infer correctly for an +// insert — `id` is DB-generated (defaultRandom()) so it must be optional on +// the way in, even though it's required on the way out. +export const PrinterInsertSchema = createInsertSchema(printers, { + id: t.Optional(t.String({ format: "uuid" })), +}); +export const PrinterSelectSchema = createSelectSchema(printers); + +export const PrintJobInsertSchema = createInsertSchema(printJobs, { + id: t.Optional(t.String({ format: "uuid" })), +}); +export const PrintJobSelectSchema = createSelectSchema(printJobs); diff --git a/src/routes/printers.ts b/src/routes/printers.ts index d9d8e9b..3540b50 100644 --- a/src/routes/printers.ts +++ b/src/routes/printers.ts @@ -2,6 +2,7 @@ import { Elysia, t } from "elysia"; import { eq } from "drizzle-orm"; import { db } from "../db"; import { printers } from "../db/schema"; +import { PrinterInsertSchema } from "../db/validators"; export const printerRoutes = new Elysia({ prefix: "/printers" }) .get("/", async ({ query }) => { @@ -21,28 +22,14 @@ export const printerRoutes = new Elysia({ prefix: "/printers" }) .post( "/", async ({ body, set }) => { - const [printer] = await db - .insert(printers) - .values({ - farmId: body.farmId, - name: body.name, - vendor: body.vendor, - lanAddress: body.lanAddress, - serialNumber: body.serialNumber, - accessCode: body.accessCode, - }) - .returning(); + // `body` is already validated + shaped against the Drizzle table by + // PrinterInsertSchema, so it can go straight into `.values()` with no + // hand-mapping of individual fields. + const [printer] = await db.insert(printers).values(body).returning(); set.status = 201; return printer; }, - { - body: t.Object({ - farmId: t.String({ format: "uuid" }), - name: t.String({ minLength: 1 }), - vendor: t.Union([t.Literal("bambu"), t.Literal("prusa"), t.Literal("klipper")]), - lanAddress: t.Optional(t.String()), - serialNumber: t.Optional(t.String()), - accessCode: t.Optional(t.String()), - }), - }, + // Derived from `printers` via drizzle-typebox — see src/db/validators.ts. + // Add or rename a column in the schema and this validator updates itself. + { body: PrinterInsertSchema }, );