Validation API
Column validation rules and how write paths enforce them
Validation Column Options
interface ColumnConfig { required?: boolean; // Value must be present minLength?: number; // Min string length (validation) maxLength?: number; // Max string length (validation) pattern?: RegExp; // Regex validation customValidator?: (val: any) => boolean | string; // Custom validation unique?: boolean; // UNIQUE constraint}Parameters:
- required Value must be present on the entity
- minLength Minimum string length
- maxLength Maximum string length
- pattern A RegExp the value must match
- customValidator Caller-supplied predicate
- unique Enforced by the database index, not the runtime validator
There is no numeric min or max option. minLength and maxLength measure string length only.
At most one message per column is reported: each rule that fails ends that column's checks, so a value that is both too short and fails its pattern reports only the length failure.
required is not enforced on the auto-increment primary key. When the id column has a numeric type, that column is skipped by the required check — so a missing id is left for the database to generate rather than rejected.
customValidator
customValidator(val: any): boolean | stringReturn true to pass, or a string to fail with that message. The returned string is used verbatim as the validation message.
const User = defineModel({ tableName: "users", columns: { id: { type: DataTypes.STRING, required: true, unique: true }, email: { type: DataTypes.STRING, length: 255, required: true, customValidator: (val) => val.includes("@") ? true : "email must contain @", }, },});validateAll()
validateAll(entity: Partial<T>, skipRequired?: boolean): string[]Returns one message per invalid column. Empty array when valid. Never throws.
Parameters:
- entity Partial entity to check
- skipRequired Defaults to false. When true, only the required rule is skipped
const errors = userRepo.validateAll({ email: "nope" });// ["email must contain @", "Field name is required"]
const partialErrors = userRepo.validateAll( { email: "nope" }, true,);// ["email must contain @"] - required rule skippedWrite-Path Enforcement
validate(entity: Partial<T>, skipRequired?: boolean) // privateWrite paths run the private validate(), which throws on the FIRST failure only. It does not collect every message the way validateAll() does.
import { StabilizeError, generateUUID } from "stabilize-orm";
try { await userRepo.create({ id: generateUUID(), email: "nope" });} catch (err) { if (err instanceof StabilizeError && err.code === "VALIDATION_ERROR") { console.log(err.name); // "StabilizeError" }}VALIDATION_ERROR
class StabilizeError extends Error { code: string; originalError?: unknown;}Validation failures are thrown as StabilizeError with name === "StabilizeError", a .code, and a .originalError. The code for a failed rule is "VALIDATION_ERROR".
Message strings:
Field ${key} is requiredField ${key} too shortField ${key} too longField ${key} does not match pattern// or the string returned by customValidator, verbatimunique
unique exists on the column interface but is NOT enforced by the runtime validator. It is enforced by the database index, so a duplicate raises a constraint error, not a VALIDATION_ERROR.
const User = defineModel({ tableName: "users", columns: { email: { type: DataTypes.STRING, required: true, unique: true }, },});
// A duplicate insert fails at the database index,// not as a StabilizeError with code "VALIDATION_ERROR".