Core Concepts

Luq's design centers around three key principles: type-first validation, plugin architecture, and runtime safety.

Type-First Design

Luq builds validators from your existing TypeScript types. Your types remain the single source of truth.

// Define your TypeScript type as usual
type User = {
  name: string;
  age: number;
};

// Build a validator from the existing type
const validator = Builder()
  .use(requiredPlugin)
  .use(stringMinPlugin)
  .for<User>()  // Reference your existing type
  .v('name', b => b.string.required())
  .v('age', b => b.number.required())
  .build();

Plugin Architecture

Validation methods come from plugins. Import only what you need for optimal bundle size.

import { Builder } from '@maroonedog/luq';
import { requiredPlugin } from '@maroonedog/luq/plugins/required';
import { stringMinPlugin } from '@maroonedog/luq/plugins/stringMin';
import { numberMinPlugin } from '@maroonedog/luq/plugins/numberMin';

// Only import what you need - tree-shaking friendly
const validator = Builder()
  .use(requiredPlugin)      // Adds .required() method
  .use(stringMinPlugin)     // Adds .min() for strings
  .use(numberMinPlugin)     // Adds .min() for numbers
  .for<UserProfile>()
  .v('name', b => b.string.required().min(2))
  .v('age', b => b.number.required().min(18))
  .build();

Validation & Results

Validators return Result objects for safe error handling.

const result = validator.validate({
  name: 'John',
  age: 25
});

if (result.isValid()) {
  const data = result.unwrap();
  // data is fully typed as UserProfile
} else {
  result.errors.forEach(error => {
    console.log(`${error.path}: ${error.message}`);
  });
}

validate() vs parse()

  • validate() - Returns original values, validates structure
  • parse() - Returns transformed values (when using transform plugins)

Field Path System

Access nested fields using dot notation. TypeScript provides autocomplete for valid paths.

type UserProfile = {
  name: string;
  address: {
    street: string;
    city: string;
  };
  tags: string[];
};

const validator = Builder()
  .use(requiredPlugin)
  .for<UserProfile>()
  .v('name', b => b.string.required())
  .v('address.street', b => b.string.required())  // Nested access
  .v('address.city', b => b.string.required())
  .v('tags', b => b.array.required())
  .build();