Skip to content

Validator API Reference

The Validator class provides comprehensive data validation with built-in rules, custom validators, and type checking.


Table of Contents


Overview

The Validator class enables:

  • Field-level validation - Validate individual fields
  • Record-level validation - Validate entire records
  • Built-in validators - Common validation rules (required, type, min, max, pattern)
  • Custom validators - Write your own validation logic
  • Type checking - Validate data types (string, number, email, URL, etc.)
  • Async validation - Support for async validation functions
  • Default values - Apply defaults to records
  • Computed fields - Auto-computed field values
typescript
import { Validator } from 'bindra';

Constructor

typescript
constructor(fields: FieldConfig<T>[])

Creates a new Validator with field configurations.

Parameters:

  • fields (FieldConfig<T>[]) - Array of field configurations

Example:

typescript
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

const validator = new Validator<User>([
  {
    name: 'name',
    label: 'Name',
    validation: {
      required: true,
      type: 'string',
      min: 2,
      max: 50
    }
  },
  {
    name: 'email',
    label: 'Email',
    validation: {
      required: true,
      type: 'email'
    }
  },
  {
    name: 'age',
    label: 'Age',
    validation: {
      required: true,
      type: 'number',
      min: 18,
      max: 120
    }
  }
]);

Types

FieldConfig

Configuration for a single field.

typescript
interface FieldConfig<T> {
  name: keyof T;              // Field name
  label?: string;             // Display label
  validation?: FieldValidation<T>;  // Validation rules
  defaultValue?: any;         // Default value
  computed?: (record: T) => any;    // Computed function
  readOnly?: boolean;         // Read-only field
  transform?: (value: any) => any;  // Value transformer
}

FieldValidation

Validation rules for a field.

typescript
interface FieldValidation<T> {
  required?: boolean;         // Field is required
  type?: 'string' | 'number' | 'boolean' | 'date' | 
         'email' | 'url' | 'array' | 'object';
  min?: number;               // Min value/length
  max?: number;               // Max value/length
  pattern?: RegExp;           // RegEx pattern
  custom?: (value: any, record: Partial<T>) => 
    boolean | string | Promise<boolean | string>;
  message?: string;           // Custom error message
}

Methods

validateField()

Validate a single field value.

typescript
async validateField(
  fieldName: keyof T,
  value: any,
  record?: Partial<T>
): Promise<string | null>

Parameters

  • fieldName - Name of the field to validate
  • value - Value to validate
  • record - Full record for context (optional)

Returns

  • string - Error message if invalid
  • null - If valid

Example

typescript
// Validate a field
const error = await validator.validateField('email', 'invalid-email');

if (error) {
  console.error(error); // "Email must be a valid email"
}

// Valid field
const error2 = await validator.validateField('email', 'user@example.com');
console.log(error2); // null

validateRecord()

Validate an entire record.

typescript
async validateRecord(
  record: Partial<T>,
  options?: { skipReadOnly?: boolean; skipComputed?: boolean }
): Promise<Record<string, string> | null>

Parameters

  • record - Record to validate
  • options - Validation options
    • skipReadOnly - Skip read-only fields (default: true)
    • skipComputed - Skip computed fields (default: true)

Returns

  • Record<string, string> - Object with field errors
  • null - If valid

Example

typescript
const user = {
  name: 'A', // Too short
  email: 'invalid',
  age: 15 // Too young
};

const errors = await validator.validateRecord(user);

if (errors) {
  console.log(errors);
  // {
  //   name: "Name must have at least 2 characters",
  //   email: "Email must be a valid email",
  //   age: "Age must be at least 18"
  // }
}

applyDefaults()

Apply default values to a record.

typescript
applyDefaults(record: Partial<T>): Partial<T>

Parameters

  • record - Partial record

Returns

Record with defaults applied

Example

typescript
const validator = new Validator<User>([
  {
    name: 'role',
    defaultValue: 'user'
  },
  {
    name: 'active',
    defaultValue: true
  }
]);

const user = { name: 'Alice' };
const withDefaults = validator.applyDefaults(user);

console.log(withDefaults);
// { name: 'Alice', role: 'user', active: true }

applyComputed()

Apply computed fields to a record.

typescript
applyComputed(record: T): T

Parameters

  • record - Record to compute fields for

Returns

Record with computed fields

Example

typescript
const validator = new Validator<User>([
  {
    name: 'fullName',
    computed: (record) => `${record.firstName} ${record.lastName}`
  },
  {
    name: 'isAdult',
    computed: (record) => record.age >= 18
  }
]);

const user = {
  firstName: 'Alice',
  lastName: 'Smith',
  age: 25
};

const withComputed = validator.applyComputed(user);

console.log(withComputed);
// {
//   firstName: 'Alice',
//   lastName: 'Smith',
//   age: 25,
//   fullName: 'Alice Smith',
//   isAdult: true
// }

getField()

Get field configuration by name.

typescript
getField(name: keyof T): FieldConfig<T> | undefined

Parameters

  • name - Field name

Returns

Field configuration or undefined

Example

typescript
const nameConfig = validator.getField('name');

if (nameConfig) {
  console.log(nameConfig.label); // "Name"
  console.log(nameConfig.validation?.required); // true
}

hasField()

Check if a field exists in the validator.

typescript
hasField(name: keyof T): boolean

Parameters

  • name - Field name

Returns

boolean - True if field exists

Example

typescript
if (validator.hasField('email')) {
  console.log('Email field is configured');
}

Validation Rules

Required

Field must have a value.

typescript
{
  name: 'username',
  validation: {
    required: true,
    message: 'Username is required'
  }
}

Type Validation

Validate data types.

Available types:

  • string - String value
  • number - Numeric value
  • boolean - Boolean value
  • date - Valid date
  • email - Valid email address
  • url - Valid URL
  • array - Array
  • object - Object
typescript
{
  name: 'email',
  validation: {
    type: 'email',
    message: 'Must be a valid email'
  }
}

Min/Max

Minimum and maximum values or lengths.

typescript
// Number range
{
  name: 'age',
  validation: {
    type: 'number',
    min: 18,
    max: 120
  }
}

// String length
{
  name: 'username',
  validation: {
    type: 'string',
    min: 3,
    max: 20
  }
}

// Array length
{
  name: 'tags',
  validation: {
    type: 'array',
    min: 1,
    max: 5
  }
}

Pattern (RegEx)

Validate with regular expressions.

typescript
{
  name: 'phone',
  validation: {
    pattern: /^\d{3}-\d{3}-\d{4}$/,
    message: 'Phone must be in format: 123-456-7890'
  }
}

Custom Validators

Write custom validation logic.

typescript
// Synchronous
{
  name: 'password',
  validation: {
    custom: (value) => {
      if (value.length < 8) return 'Too short';
      if (!/[A-Z]/.test(value)) return 'Must have uppercase';
      if (!/[0-9]/.test(value)) return 'Must have number';
      return true; // Valid
    }
  }
}

// Asynchronous (e.g., check if username exists)
{
  name: 'username',
  validation: {
    custom: async (value) => {
      const exists = await checkUsernameExists(value);
      return exists ? 'Username already taken' : true;
    }
  }
}

// With record context
{
  name: 'confirmPassword',
  validation: {
    custom: (value, record) => {
      return value === record.password || 'Passwords must match';
    }
  }
}

Usage Examples

Basic Validation

typescript
import { Validator } from 'bindra';

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

const validator = new Validator<User>([
  {
    name: 'name',
    validation: {
      required: true,
      type: 'string',
      min: 2
    }
  },
  {
    name: 'email',
    validation: {
      required: true,
      type: 'email'
    }
  },
  {
    name: 'age',
    validation: {
      required: true,
      type: 'number',
      min: 18
    }
  }
]);

// Validate a record
const user = {
  name: 'Alice',
  email: 'alice@example.com',
  age: 25
};

const errors = await validator.validateRecord(user);

if (errors) {
  console.error('Validation failed:', errors);
} else {
  console.log('Validation passed!');
}

With DataSource Integration

typescript
import { DataSource, Validator } from 'bindra';

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

const validator = new Validator<User>([
  {
    name: 'name',
    validation: { required: true, min: 2 }
  },
  {
    name: 'email',
    validation: { required: true, type: 'email' }
  }
]);

const users = new DataSource<User>({
  url: '/api/users',
  validation: {
    validator: validator,
    validateOnCreate: true,
    validateOnUpdate: true
  }
});

// Create will automatically validate
try {
  const newUser = await users.create({
    name: 'A', // Too short!
    email: 'invalid'
  });
} catch (error) {
  console.error(error); // ValidationError
}

Form Validation

typescript
const formValidator = new Validator<FormData>([
  {
    name: 'username',
    label: 'Username',
    validation: {
      required: true,
      min: 3,
      max: 20,
      pattern: /^[a-zA-Z0-9_]+$/,
      message: 'Username must be 3-20 alphanumeric characters'
    }
  },
  {
    name: 'password',
    label: 'Password',
    validation: {
      required: true,
      custom: (value) => {
        if (value.length < 8) return 'Minimum 8 characters';
        if (!/[A-Z]/.test(value)) return 'Must contain uppercase';
        if (!/[a-z]/.test(value)) return 'Must contain lowercase';
        if (!/[0-9]/.test(value)) return 'Must contain number';
        return true;
      }
    }
  },
  {
    name: 'confirmPassword',
    label: 'Confirm Password',
    validation: {
      required: true,
      custom: (value, record) => {
        return value === record.password || 'Passwords do not match';
      }
    }
  },
  {
    name: 'email',
    label: 'Email',
    validation: {
      required: true,
      type: 'email'
    }
  }
]);

// Validate form submission
const formData = {
  username: 'alice',
  password: 'SecurePass123',
  confirmPassword: 'SecurePass123',
  email: 'alice@example.com'
};

const errors = await formValidator.validateRecord(formData);

if (!errors) {
  console.log('Form is valid!');
  // Submit form
}

Async Validation

typescript
const validator = new Validator<User>([
  {
    name: 'username',
    validation: {
      required: true,
      custom: async (value) => {
        // Check if username is already taken
        const response = await fetch(`/api/check-username?name=${value}`);
        const { available } = await response.json();
        return available || 'Username already taken';
      }
    }
  },
  {
    name: 'email',
    validation: {
      required: true,
      type: 'email',
      custom: async (value) => {
        // Check if email is already registered
        const response = await fetch(`/api/check-email?email=${value}`);
        const { available } = await response.json();
        return available || 'Email already registered';
      }
    }
  }
]);

With Defaults and Computed Fields

typescript
interface Order {
  id: number;
  items: Item[];
  subtotal: number;
  tax: number;
  total: number;
  discount: number;
  status: string;
  createdAt: Date;
}

const validator = new Validator<Order>([
  {
    name: 'status',
    defaultValue: 'pending'
  },
  {
    name: 'discount',
    defaultValue: 0
  },
  {
    name: 'subtotal',
    computed: (order) => {
      return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
    }
  },
  {
    name: 'tax',
    computed: (order) => order.subtotal * 0.08
  },
  {
    name: 'total',
    computed: (order) => order.subtotal + order.tax - order.discount
  },
  {
    name: 'createdAt',
    defaultValue: () => new Date(),
    readOnly: true
  }
]);

// Apply defaults
const order = { items: [{ price: 100, qty: 2 }] };
const withDefaults = validator.applyDefaults(order);

// Apply computed fields
const complete = validator.applyComputed(withDefaults);

console.log(complete);
// {
//   items: [...],
//   status: 'pending',
//   discount: 0,
//   subtotal: 200,
//   tax: 16,
//   total: 216,
//   createdAt: Date(...)
// }

Best Practices

1. Use Descriptive Labels

typescript
// ✅ Good
{
  name: 'email',
  label: 'Email Address',
  validation: { type: 'email' }
}

// ❌ Bad
{
  name: 'email',
  validation: { type: 'email' }
}

2. Provide Custom Error Messages

typescript
// ✅ Good
{
  name: 'age',
  validation: {
    min: 18,
    message: 'You must be 18 or older to register'
  }
}

// ❌ Bad - Generic message
{
  name: 'age',
  validation: { min: 18 }
}

3. Use Async Validators Sparingly

typescript
// ✅ Good - Only when necessary
{
  name: 'username',
  validation: {
    custom: async (value) => {
      // Only check on server if local validation passes
      if (value.length < 3) return 'Too short';
      return await checkAvailability(value);
    }
  }
}

4. Combine Built-in and Custom Validators

typescript
// ✅ Good - Use built-ins first
{
  name: 'password',
  validation: {
    required: true,
    type: 'string',
    min: 8,
    custom: (value) => {
      // Additional custom checks
      return /[A-Z]/.test(value) || 'Must contain uppercase';
    }
  }
}

5. Handle Validation Errors Gracefully

typescript
// ✅ Good
const errors = await validator.validateRecord(data);

if (errors) {
  // Show errors to user
  Object.entries(errors).forEach(([field, message]) => {
    showFieldError(field, message);
  });
} else {
  // Proceed with data
  await submitData(data);
}


← Back to API Reference

Released under the MIT License.