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.

models/user.ts
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.

validate.ts
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 array
repo.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.

validate-partial.ts
// Only updating age, so "email" and "name" being absent is fine
const 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.

create.ts
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

rules.ts
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 it

No 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.