Stacks Validation
Key Paths
- Core package:
storage/framework/core/validation/src/ - Package:
@stacksjs/validation
Architecture
The index.ts re-exports from multiple sources:
// Re-export everything from the validation library
export * from '@stacksjs/ts-validation'
// Local modules
export * from './reporter' // error reporting
export * from './validator' // model/custom validation
export { schema } from './schema' // schema builder instance
// Type guard functions defined directly in index.ts
export function isString(value: unknown): value is string
export function isNumber(value: unknown): value is number
export function isBoolean(value: unknown): value is boolean
export function isObject(value: unknown): value is Record<string, unknown>
export function isArray(value: unknown): value is unknown[]
export function isFunction(value: unknown): value is Function
export function isUndefined(value: unknown): value is undefined
export function isNull(value: unknown): value is null
export function isNullOrUndefined(value: unknown): value is null | undefined
Additional type guards are in is.ts (but not directly imported by index.ts -- available via separate import).
Type Guards (index.ts)
Basic type guards with TypeScript type narrowing:
import { isString, isNumber, isBoolean, isObject, isArray, isFunction } from '@stacksjs/validation'
isString('hello') // true -- typeof value === 'string'
isNumber(42) // true -- typeof value === 'number' && !Number.isNaN(value)
isNumber(NaN) // FALSE -- NaN is explicitly excluded
isBoolean(true) // true -- typeof value === 'boolean'
isObject({}) // true -- typeof === 'object' && !== null && !Array.isArray
isObject([]) // FALSE -- arrays are excluded
isObject(null) // FALSE -- null is excluded
isArray([]) // true -- Array.isArray()
isFunction(() => {}) // true -- typeof value === 'function'
isUndefined(undefined) // true
isNull(null) // true
isNullOrUndefined(null) // true
isNullOrUndefined(undefined) // true
Extended Type Guards (is.ts)
Additional guards using getTypeName() from @stacksjs/types and toString() from @stacksjs/strings:
isDef(value) // true if typeof value !== 'undefined'
isMap(new Map()) // true -- toString check '[object Map]'
isSet(new Set()) // true -- toString check '[object Set]'
isPromise(Promise.resolve()) // true -- toString check '[object Promise]'
isSymbol(Symbol()) // true
isDate(new Date()) // true
isRegExp(/test/) // true
isWindow(globalThis) // true in browser -- toString check '[object Window]'
isPrimitive(42) // true (string, number, boolean, null, undefined, symbol)
isPrimitive({}) // false
// Environment detection
isBrowser: boolean // typeof window !== 'undefined'
isServer: boolean // typeof document === 'undefined'
Numeric Checks (is.ts)
isInteger(42) // true -- Number.isInteger()
isInteger(42.0) // true -- 42.0 is an integer in JS
isFloat(3.14) // true -- isNumber && !Number.isInteger
isFloat(42) // false
isPositive(5) // true -- > 0
isNegative(-5) // true -- < 0
isEven(4) // true -- % 2 === 0
isOdd(3) // true -- % 2 !== 0
// Classification functions (return string, not boolean)
isEvenOrOdd(4) // 'even'
isEvenOrOdd(3) // 'odd'
isEvenOrOdd('not a number') // 'odd' -- non-numbers default to 'odd'
isPositiveOrNegative(5) // 'positive'
isPositiveOrNegative(-5) // 'negative'
isPositiveOrNegative('x') // 'negative' -- non-numbers default to 'negative'
isIntegerOrFloat(42) // 'integer'
isIntegerOrFloat(3.14) // 'float'
isIntegerOrFloat('x') // 'float' -- non-numbers default to 'float'
Schema Builder (schema.ts)
The schema object is an instance of ValidationInstance from @stacksjs/ts-validation. It provides fluent validators for model attribute definitions.
import { schema } from '@stacksjs/validation'
Available Schema Types
// String types
schema.string() // StringValidatorType
schema.string().min(2) // chain: min length
schema.string().max(100) // chain: max length
schema.string().email() // chain: must be email
schema.string().url() // chain: must be URL
schema.string().matches(/pattern/) // chain: regex match
schema.string().equals('exact') // chain: exact match
schema.string().alphanumeric() // chain: alphanumeric only
schema.string().alpha() // chain: letters only
schema.string().numeric() // chain: numeric string
schema.string().custom(fn, msg) // chain: custom validator
schema.text() // TextValidatorType (extends StringValidatorType)
// Number types
schema.number() // NumberValidatorType
schema.number().min(0).max(100) // chain: min/max
schema.bigint() // BigintValidatorType
schema.integer() // IntegerValidatorType
schema.smallint() // SmallintValidatorType
schema.float() // FloatValidatorType
schema.double() // DoubleValidatorType
schema.decimal() // DecimalValidatorType
// Boolean
schema.boolean() // BooleanValidatorType
// Enum
schema.enum(['draft', 'published', 'archived']) // EnumValidatorType
// Date/time types
schema.date() // DateValidatorType
schema.datetime() // DatetimeValidatorType
schema.timestamp() // TimestampValidatorType
schema.timestampTz() // TimestampTzValidatorType (with timezone)
schema.unix() // UnixValidatorType
schema.time() // TimeValidatorType
// Binary/blob
schema.binary() // BinaryValidatorType
schema.blob() // BlobValidatorType
// JSON
schema.json() // JsonValidatorType
// Password
schema.password() // PasswordValidatorType
// Array
schema.array<T>() // ArrayValidatorType<T>
// Object
schema.object<T>(shapeSchema?) // ObjectValidatorType<T>
// Custom
schema.custom<T>(validationFn, message) // CustomValidatorType<T>
Model Attribute Usage
Schema validators are used in defineModel() attribute validation:
import { defineModel } from '@stacksjs/config'
import { schema } from '@stacksjs/validation'
export default defineModel({
attributes: {
name: {
validation: {
rule: schema.string().min(2).max(100),
message: {
min: 'Name must be at least 2 characters',
max: 'Name cannot exceed 100 characters',
}
}
},
email: {
validation: {
rule: schema.string().email(),
message: {
email: 'Please provide a valid email address',
}
}
},
price: {
validation: {
rule: schema.number().min(1),
message: {
min: 'Price must be at least 1',
}
}
},
status: {
validation: {
rule: schema.enum(['active', 'inactive', 'archived']),
message: {
enum: 'Invalid status value',
}
}
},
bio: {
validation: {
rule: schema.string().max(500),
message: {
max: 'Bio cannot exceed 500 characters',
}
}
},
}
})
Model Validation (validator.ts)
validateField(modelFile, params)
Validates request data against a model's attribute rules. Used internally by the framework for API request validation.
import { validateField } from '@stacksjs/validation'
// Loads model file, extracts validation rules, validates params
const result = await validateField('User', { name: 'John', email: 'john@example.com' })
Behavior:
- Finds the model file by name in user models or framework defaults
- Extracts validation rules from model attributes
- Converts attribute names to snake_case for validation
- Sets custom error messages via
MessageProvider - Validates using
schema.object(ruleObject).validate(params) - Throws
HttpError(422)with error JSON on validation failure - Throws
HttpError(404)if model not found - Skips validation for attributes with default values unless
isRequiredis true
customValidate(attributes, params)
Validates request data against custom attribute rules (not tied to a model):
import { customValidate } from '@stacksjs/validation'
const result = await customValidate({
email: {
rule: schema.string().email(),
message: { email: 'Invalid email' }
},
age: {
rule: schema.number().min(18),
message: { min: 'Must be at least 18' }
}
}, requestData)
Uses schema.object().shape(ruleObject) for validation.
isObjectNotEmpty(obj)
isObjectNotEmpty({}) // false
isObjectNotEmpty({ a: 1 }) // true
isObjectNotEmpty(undefined) // false
Error Reporter (reporter.ts)
Simple error accumulator for validation:
import { reportError, getErrors } from '@stacksjs/validation'
reportError([{ message: 'Invalid', value: '', field: 'email' }])
const errors = getErrors() // returns accumulated MessageObject[]
Interface: { message: string, value: string, field: string }
Error Reporter Contract (from rules.ts)
VineJS-inspired types for the validation pipeline:
interface FieldContext {
value: unknown
data: any // top-level object under validation
meta: Record<string, any>
mutate: (newValue: any, field: FieldContext) => void
report: ErrorReporterContract['report']
isValid: boolean
isDefined: boolean
wildCardPath: string // nested pointer, '*' for array members
parent: any
name: string | number
isArrayMember: boolean
}
interface ErrorReporterContract {
hasErrors: boolean
createError: () => Error
report: (message: string, rule: string, field: FieldContext, args?: Record<string, any>) => any
}
Re-exports from @stacksjs/ts-validation
The package re-exports everything from @stacksjs/ts-validation, which includes:
vandschema-- the validation instancevalidator-- the validator library (default export from lib)MessageProvider,setCustomMessages-- custom message handling- All type definitions (ValidatorType, StringValidatorType, NumberValidatorType, etc.)
- Configuration utilities
Validation Types (types/index.ts)
Local type definitions:
interface ValidationResult {
valid: boolean
errors?: Array<{ message: string }>
}
interface ValidationRule {
validate: (value: unknown) => ValidationResult
}
type ValidationBoolean = ValidationRule
type ValidationEnum = ValidationRule
type ValidationNumber = ValidationRule
type ValidationString = ValidationRule
Gotchas
isNumber()inindex.tsreturns FALSE forNaN-- this differs fromtypeof NaN === 'number'isObject()inindex.tsexcludes arrays and null --isObject([])is falseisObject()inis.tsusestoString()check ([object Object]) which also excludes arrays/null but through a different mechanismisPrimitive()includes null and undefined (6 primitive types: string, number, boolean, null, undefined, symbol)- Numeric classification functions (
isEvenOrOdd, etc.) return string defaults for non-numbers rather than throwing - Schema validators map to database column types --
schema.integer()creates an integer column,schema.string()creates a varchar, etc. validateFieldconverts attribute names to snake_case usingsnakeCase()from@stacksjs/stringsvalidateFieldskips attributes with default values unlessisRequiredis explicitly set- Validation error messages are customizable per-rule in model definitions via the
messageobject - The
schemaexport is thevinstance from@stacksjs/ts-validation-- they are the same object - Custom validators can be added with
schema.custom<T>(fn, message)for types not covered by built-in validators customValidateusesschema.object().shape()whilevalidateFieldusesschema.object(ruleObject)-- slightly different API