work-hub-server/openspec/changes/swap-superstruct-valibot/design.md
Nicolas HOARAU 9314e72f3e feat(schema): add shared schema package with Drizzle ORM and Valibot validation
Create @workspace/schema package with Project and Task table definitions, Drizzle relations, Valibot validation schemas (select/insert/update), and wire as workspace dependency in API and Web apps.
2026-06-04 12:46:51 +04:00

38 lines
2.4 KiB
Markdown

## Context
The schema package currently has two manually maintained modules (`validation/` and `types/`) that mirror Drizzle table definitions. Drizzle ORM has built-in Valibot support (`drizzle-orm/valibot`) that can derive insert, update, and select schemas directly from `pgTable` definitions — eliminating the need for hand-written validators and separate type files.
The constant arrays in `drizzle/enums.ts` (`PROJECT_STATUSES`, `TASK_STATUSES`, `PRIORITIES`) already define the allowed values. These can feed into Valibot `picklist()` refinements via the customization callback in `createInsertSchema`/`createUpdateSchema`.
## Goals / Non-Goals
**Goals:**
- Replace Superstruct with Valibot as the validation library
- Derive insert/update schemas from Drizzle tables using `drizzle-orm/valibot`
- Remove the `validation/` and `types/` source directories
- Remove `superstruct` dependency; add `valibot`
- Keep DB-level CHECK constraints — Valibot refinements layer on top
- Ensure `createUpdateSchema` produces all-optional shapes (replacing manual partial logic)
**Non-Goals:**
- No change to `drizzle/` module structure or table definitions
- No change to the public import paths consumers use
- No migration or database changes
## Decisions
1. **Use `drizzle-orm/valibot` not `drizzle-valibot` package** — the integration is built into `drizzle-orm` itself (since v1.0.0-beta.15). No separate package needed.
2. **One composer file for all Valibot schemas** — a single `src/valibot.ts` (or inline in `drizzle/`) containing `createInsertSchema` and `createUpdateSchema` calls for each table. No separate `validation/` directory.
3. **Customization via the second argument callback** — enum-constrained fields use `(schema) => pipe(schema, picklist([...ARRAY]))` to add the allowed-value constraint on top of Drizzle's auto-generated type info.
4. **Types are inferred, not declared**`v.InferOutput<typeof projectInsert>` replaces the manual type aliases in `types/`. Consumers import the Valibot schema and derive types as needed.
## Risks / Trade-offs
- **[New dependency]** Valibot replaces Superstruct. The API surface is similar but not identical — consumers may need to adjust error handling (Valibot uses `ValiError` not `StructError`).
- **[Picklist sync]** The `picklist()` values reference the constant arrays, so they stay in sync automatically. No new drift risk.