Validation
Declare rules on your columns and Stabilize enforces them on every write.
Rules live next to the column they guard, so the model stays the single source of truth.
Declaring Rules
Four column options drive validation — required, minLength, maxLength, pattern — plus customValidator for anything else.
import { defineModel, DataTypes } from "stabilize-orm";
export const User = defineModel({ tableName: "users", columns: { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, email: { type: DataTypes.STRING, required: true, unique: true, pattern: /^[^@\s]+@[^@\s]+\.[^@\s]+$/, }, name: { type: DataTypes.STRING, required: true, minLength: 3, maxLength: 50, }, age: { type: DataTypes.INTEGER, customValidator: (val) => val >= 18 || "Signups must be 18 or over", }, },});Checking Without Writing
validateAll() runs every rule and returns one message per invalid column — an empty array means valid. It never throws, which makes it the right call for form handling where you want to show all errors at once.
const errors = repo.validateAll({ email: "nope", name: "ab", age: 12,});
// [// "Field email does not match pattern",// "Field name too short",// "Signups must be 18 or over"// ]
if (errors.length > 0) { return renderForm(errors);}
// A valid payload returns an empty arrayrepo.validateAll({ email: "ada@example.com", name: "Ada", age: 30 });// => []Skipping Required Fields
Partial updates often legitimately omit required columns. Pass true as the second argument to skip only the required rule — length, pattern and custom rules still run against whatever you did supply.
// Only updating age, so "email" and "name" being absent is fineconst errors = repo.validateAll({ age: 30 }, true);// => []On Write
You do not call validation yourself on the write path. create(), update() and upsert() validate first and throw a StabilizeError with code VALIDATION_ERROR. The write path reports the first failure, not all of them — use validateAll() when you need the complete list.
import { StabilizeError } from "stabilize-orm";
try { await repo.create({ email: "nope", name: "ab" });} catch (error) { if (error instanceof StabilizeError && error.code === "VALIDATION_ERROR") { console.error(error.message); // "Field email does not match pattern" }}Rule Reference
required: true // "Field <key> is required"minLength: 3 // "Field <key> too short"maxLength: 50 // "Field <key> too long"pattern: /^\d{5}$/ // "Field <key> does not match pattern"customValidator: (val) => // return true to pass, val > 0 || "Must be positive" // or a string to fail with itNo numeric range rule
There is no min/max for numbers — minLength and maxLength measure string length. For a numeric bound, use customValidator, as the age column above does. Note also that unique is declared on the column but is not checked by the validator — it is enforced by the database index, so a duplicate surfaces as a constraint error rather than a VALIDATION_ERROR.