Validation Architecture
Layer Responsibilities
code
core/ → SSOT: constraints + schemas (ArkType, Standard Schema) api/ → Gatekeeper: reject invalid requests (sValidator) front/ → UX: phase-based feedback (withValidationErrorMessages)
Design Decisions
Why constraints separate from schemas?
- •Constraints are plain values (reusable in messages, tests, etc.)
- •Schemas are validation logic (may change libraries)
- •Types derived from constraints (SSOT)
Why Standard Schema?
- •Library-agnostic interface (
@standard-schema/spec) - •ArkType today, can swap to Zod/Valibot later
- •Both api (sValidator) and front use same interface
Why phase-based validation in front?
- •
onChange: lenient (no required checks) → no red during typing - •
onDraftSubmit: intermediate - •
onConfirmedSubmit: strict (all rules) → block invalid submit
Core schema is for "final correctness". Front needs UX timing control.
Why validate twice (front + api)?
- •Front validation: UX improvement (optional, can fail)
- •API validation: security boundary (required, never trust client)
Key Files
- •
core/src/model/*/constraints.ts- constraint values - •
core/src/model/*/schema.ts- ArkType schemas - •
api/src/model/*/index.ts- sValidator usage - •
front/src/model/common/lib/validation.ts- phase-based utilities - •
front/src/model/*/detail/form/inputs/*/validation.ts- field validation