Skip to content
Back to skills

Stacks Validation

ASecurity

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.

  • 631 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 5, 2026
developmenttypescriptgobashapidatabase

Works with

  • api

Security analysis

A100/100

Scanned September 3, 2026

npx -y skills add stacksjs/stacks --skill stacks-validation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Stacks Validation?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Stacks Validation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/stacksjs-stacks-validation/badge)](https://www.skillsdirectory.com/skills/stacksjs-stacks-validation)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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:
```typescript
// 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:

```typescript
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`:

```typescript
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`)

```typescript
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.

```typescript
import { schema } from '@stacksjs/validation'
```

### Available Schema Types

```typescript
// 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:

```typescript
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.

```typescript
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:
1. Finds the model file by name in user models or framework defaults
2. Extracts validation rules from model attributes
3. Converts attribute names to snake_case for validation
4. Sets custom error messages via `MessageProvider`
5. Validates using `schema.object(ruleObject).validate(params)`
6. Throws `HttpError(422)` with error JSON on validation failure
7. Throws `HttpError(404)` if model not found
8. 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):

```typescript
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)`

```typescript
isObjectNotEmpty({})            // false
isObjectNotEmpty({ a: 1 })      // true
isObjectNotEmpty(undefined)     // false
```

## Error Reporter (`reporter.ts`)

Simple error accumulator for validation:

```typescript
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:

```typescript
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:

- `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:
```typescript
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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.