Guide

Validation

Validate structure and domain rules before data reaches the adapter.

Create and update operations run structural, field, and table validation inside the mutation transaction.

Structural validation

Structural rules come from ColumnMetadata.

Metadata typeValid value
non-nullable columnneither null nor undefined
integerinteger number
bigintbigint
decimalstring or number
booleanboolean
timestampDate
uuid, string, text, datestring
jsonany value

Field validators

{
  name: 'email',
  columnName: 'email',
  type: 'string',
  nullable: false,
  primaryKey: false,
  unique: true,
  validators: [
    {
      name: 'email-format',
      validate(value, entity) {
        return typeof value === 'string' && value.includes('@')
          ? { valid: true }
          : { valid: false, message: 'Enter a valid email.' }
      }
    }
  ]
}

The validator receives the field value and the complete read-only entity. It may return immediately or return a promise.

{
  name: 'email-availability',
  async validate(value) {
    const available = await isEmailAvailable(String(value))

    return available
      ? { valid: true }
      : { valid: false, message: 'Email already exists.' }
  }
}

Database constraints should still enforce race-sensitive rules such as uniqueness.

Table validators

Use table validators when the rule depends on multiple fields.

{
  name: 'valid-period',
  fields: ['startsAt', 'endsAt'],
  validate(entity) {
    const startsAt = entity.startsAt
    const endsAt = entity.endsAt

    if (
      startsAt instanceof Date &&
      endsAt instanceof Date &&
      endsAt <= startsAt
    ) {
      return {
        valid: false,
        message: 'End must be after start.'
      }
    }

    return { valid: true }
  }
}

During partial updates, the validator runs when at least one field listed in fields is touched.

Create validation

Update validation

The engine loads current rows, applies before-update hooks, merges the resolved patch with each current entity, then validates only touched columns and relevant table validators.

This ensures cross-field validators see the post-update entity without rewriting untouched data.

ValidationError

try {
  await engine.create('users', input)
} catch (error) {
  if (error instanceof ValidationError) {
    console.error(error.message)
    console.error(error.table)
    console.error(error.failures)
  }
}

A failure contains a field and message:

interface FieldValidationFailure {
  field: string
  message: string
}

The error message now includes the aggregated failure summary:

Validation failed for table "users": email: Enter a valid email.; name: This field is required.

The structured failures array remains available for API error responses and form mapping.