Skip to content

Validation Guide ​

Every generated Atscript type has a .validator() method for runtime validation.

In normal application code, you usually use it for:

  • validating request or form input
  • narrowing unknown data to a real TypeScript type
  • stripping or rejecting unexpected properties
  • handling partial updates safely
typescript
import { Product } from './product.as'

const validator = Product.validator()

1. Validate One Value ​

Safe Mode ​

Safe mode returns false on failure and acts as a type guard:

typescript
if (validator.validate(data, true)) {
  // TypeScript now knows data is Product
  console.log(data.name, data.price)
} else {
  console.log(validator.errors)
}

This is the most practical mode for request handlers and form submissions.

Throwing Mode ​

Throwing mode raises ValidatorError on failure:

typescript
try {
  validator.validate(data)
  // data passed validation
} catch (error) {
  console.error(error.message)
  console.error(error.errors)
}

2. Narrow unknown Data ​

The validator doubles as a type guard, so it works well at the edge of your app:

typescript
import { Product } from './product.as'

function handleRequest(body: unknown) {
  if (Product.validator().validate(body, true)) {
    // body is Product here
    saveProduct(body)
  }
}

No manual generic parameters are needed. Atscript already knows the data shape from the model.

3. Control Unexpected Properties ​

Use unknownProps when inputs may contain extra fields:

typescript
const validator = Product.validator({
  unknownProps: 'strip',
})

Options:

  • 'error' — fail validation if an extra field is present
  • 'ignore' — leave extra fields alone
  • 'strip' — remove extra fields from the value, once the whole value has passed (a failed validation leaves it unchanged)

'strip' is often useful for request payloads.

4. Validate Partial Updates ​

Use partial when validating patch-like payloads:

typescript
const patchValidator = Product.validator({
  partial: true,
})

Options:

  • false — all required properties must be present
  • true — allow missing properties at the top level
  • 'deep' — allow missing properties at every level

'deep' is useful for nested patch payloads.

5. Put Rules On The Model ​

Validation rules come from your .as file:

atscript
export interface User {
  @expect.minLength 3
  username: string

  email: string.email

  @expect.min 18
  age: number.int
}

That means one model definition can give you:

  • static typing
  • runtime validation
  • metadata for other tools

6. Write Better Error Messages ​

Most validation annotations accept a custom message as the last argument:

atscript
export interface SignupForm {
  @meta.required "Please enter your name"
  @expect.minLength 3, "Username must be at least 3 characters"
  username: string
}

When validation fails, the custom message is used instead of the default one. The @meta.required message also covers a missing username (since 0.2.10). For a value of the wrong type (not one of the allowed values of 'active' | 'blocked', a string where a number belongs), use @expect.message:

atscript
export interface Account {
  @expect.message "Choose a valid status"
  status: 'active' | 'blocked'
}

Every error also carries a stable code and its params (MIN_LENGTH, { min: 3, actual: 1 }), so you can translate messages or react to a specific failure without parsing text. See Error Codes.

7. Know The Common Built-Ins ​

The most common validation rules come from:

  • semantic types like string.email, string.required, number.int.positive
  • @expect.minLength
  • @expect.maxLength
  • @expect.min
  • @expect.max
  • @expect.pattern

For array uniqueness and other less common rules, see the Validation Reference.

DOs and DON'Ts ​

  • DO expect extra fields to fail validation by default. unknownProps defaults to 'error' — if a payload may carry extra fields, choose 'strip' or 'ignore' deliberately.
  • DON'T forget that 'strip' mutates your input. When validation passes, it deletes the extra fields from the object you passed in — clone the payload first if you still need the original.
  • DO create the validator once and reuse it in hot paths. .validator() builds a fresh instance on every call. Reuse is safe: each validate() call resets validator.errors before running.
  • DO raise errorLimit when a form needs every message. Validation stops collecting after 10 errors by default, so long forms may show an incomplete error list.
  • DO consider compiled validators for server builds. They are opt-in and make validating valid input faster without changing results.

When To Read The Reference ​

Use the reference page when you need:

  • every validator option in one place
  • the exact validate() signature
  • ValidatorError, error codes and translating errors (localizeErrors)
  • array uniqueness rules
  • plugin hooks and external context
  • manual Validator construction from runtime types

Next Steps ​

Released under the MIT License.