| name | stacks-validation |
| description | Use when implementing validation in Stacks — type guards (isString, isNumber, isBoolean, isObject, isArray, isFunction, etc.), numeric checks (isPositive, isEven, isInteger), the schema builder for model attribute validation, or request validation. Covers @stacksjs/validation. |
| license | MIT |
| compatibility | Bun >= 1.3.0, TypeScript |
| allowed-tools | Read Edit Write Bash Grep Glob |
Stacks Validation
Key Paths
- Core package:
storage/framework/core/validation/src/
- Package:
@stacksjs/validation
Architecture
The index.ts re-exports from multiple sources:
export * from '@stacksjs/ts-validation'
export * from './reporter'
export * from './validator'
export { schema } from './schema'
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')
isNumber(42)
isNumber(NaN)
isBoolean(true)
isObject({})
isObject([])
isObject(null)
isArray([])
isFunction(() => {})
isUndefined(undefined)
isNull(null)
isNullOrUndefined(null)
isNullOrUndefined(undefined)
Extended Type Guards (is.ts)
Additional guards using getTypeName() from @stacksjs/types and toString() from @stacksjs/strings:
isDef(value)
isMap(new Map())
isSet(new Set())
isPromise(Promise.resolve())
isSymbol(Symbol())
isDate(new Date())
isRegExp(/test/)
isWindow(globalThis)
isPrimitive(42)
isPrimitive({})
isBrowser: boolean
isServer: boolean
Numeric Checks (is.ts)
isInteger(42)
isInteger(42.0)
isFloat(3.14)
isFloat(42)
isPositive(5)
isNegative(-5)
isEven(4)
isOdd(3)
isEvenOrOdd(4)
isEvenOrOdd(3)
isEvenOrOdd('not a number')
isPositiveOrNegative(5)
isPositiveOrNegative(-5)
isPositiveOrNegative('x')
isIntegerOrFloat(42)
()
()
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
schema.string()
schema.string().minLength(2)
schema.string().maxLength(100)
schema.string().email()
schema.string().url()
schema.string().matches(/pattern/)
schema.string().equals('exact')
schema.string().alphanumeric()
schema.string().alpha()
schema.string().numeric()
schema.string().custom(fn, msg)
schema.text()
schema.number()
schema.number().min().()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.([, , ])
schema.()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.()
schema.<T>()
schema.<T>(shapeSchema?)
schema.<T>(validationFn, message)
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().minLength(2).maxLength(100),
message: {
minLength: 'Name must be at least 2 characters',
maxLength: '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: {
: ,
}
}
},
: {
: {
: schema.().(),
: {
: ,
}
}
},
}
})
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'
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
isRequired is 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({})
isObjectNotEmpty({ a: 1 })
isObjectNotEmpty(undefined)
Error Reporter (reporter.ts)
Simple error accumulator for validation:
import { reportError, getErrors } from '@stacksjs/validation'
reportError([{ message: 'Invalid', value: '', field: 'email' }])
const errors = getErrors()
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
meta: Record<string, any>
mutate: (newValue: any, field: FieldContext) => void
report: ErrorReporterContract['report']
isValid: boolean
isDefined: boolean
wildCardPath: string
parent: any
name: string | number
isArrayMember: boolean
}
interface ErrorReporterContract {
hasErrors: boolean
createError: () => Error
report: (message: string, rule: string, field: FieldContext, args?: Record<string, >) =>
}
Re-exports from @stacksjs/ts-validation
The package re-exports everything from @stacksjs/ts-validation, which includes:
v and schema -- the validation instance
validator -- 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() in index.ts returns FALSE for NaN -- this differs from typeof NaN === 'number'
isObject() in index.ts excludes arrays and null -- isObject([]) is false
isObject() in is.ts uses toString() check ([object Object]) which also excludes arrays/null but through a different mechanism
isPrimitive() 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.
validateField converts attribute names to snake_case using snakeCase() from @stacksjs/strings
validateField skips attributes with default values unless isRequired is explicitly set
- Validation error messages are customizable per-rule in model definitions via the
message object
- The
schema export is the v instance 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
customValidate uses schema.object().shape() while validateField uses schema.object(ruleObject) -- slightly different API