Builder API
The Builder API is the core of Luq's validation system. It provides a fluent, type-safe interface for constructing validators with plugins and field definitions.
Overview
The Builder follows a fluent API pattern with these key steps:
- Create - Initialize a new Builder instance
 - Configure - Add plugins with 
use() - Type - Set the validation target type with 
for<T>() - Define - Add field validations with 
v() - Build - Create the final validator with 
build() 
Creating a Builder
import { Builder } from '@maroonedog/luq/core';
// Create a new builder instance
const builder = Builder();  
The Builder() function creates a new builder instance. It's a factory function that returns a chainable builder object.
use() - Adding Plugins
The use() method adds plugins to the builder. Plugins provide validation methods that become available on field builders.
import { 
  requiredPlugin, 
  stringMinPlugin, 
  numberMinPlugin,
  stringEmailPlugin 
} from '@maroonedog/luq/plugin';
// Add plugins one by one
const builder = Builder()
  .use(requiredPlugin)
  .use(stringMinPlugin)
  .use(numberMinPlugin)
  .use(stringEmailPlugin);
// Or add multiple plugins at once
const builder = Builder()
  .use(
    requiredPlugin,
    stringMinPlugin,
    numberMinPlugin,
    stringEmailPlugin
  );  💡 Plugin Loading
- • Plugins must be added before calling 
for() - • Each plugin is loaded only once (duplicates are ignored)
 - • Plugins determine available validation methods
 - • Order doesn't matter for most plugins
 
for() - Setting Type
The for<T>() method sets the TypeScript type that the validator will validate. This enables type-safe field paths and type inference.
type UserProfile = {
  name: string;
  age: number;
  email: string;
  preferences?: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
};
// Specify the type your validator will validate
const builder = Builder()
  .use(requiredPlugin)
  .use(stringMinPlugin)
  .for<UserProfile>(); // Type parameter sets the validation target  Field Definition Methods
v() - Define Field
The v() method (short for "validate") defines validation rules for a field. It takes a field path and a builder function.
// Basic field definition
builder.v('name', b => b.string.required().min(2));
// Nested field using dot notation
builder.v('preferences.theme', b => b.string.oneOf(['light', 'dark']));
// Array element validation
builder.v('tags[*]', b => b.string.required().min(1));
// Complex validation chain
builder.v('email', b => 
  b.string
    .required()
    .email()
    .transform(email => email.toLowerCase())
);  📝 Field Path Syntax
- • 
fieldName- Top-level field - • 
nested.field.path- Nested object fields - • 
array[*]- All array elements - • 
nested.array[*].field- Fields in array objects 
Field Options
The v() method accepts an optional third parameter for field-specific configuration:
interface FieldOptions<T> {
  default?: T | (() => T);      // Default value when undefined/null
  applyDefaultToNull?: boolean; // Apply default to null (default: true)
  description?: string;          // Field description for documentation
  deprecated?: boolean | string; // Mark field as deprecated
  metadata?: Record<string, any>; // Custom metadata
}
// Using field options with default value
builder.v('theme',
  b => b.string.optional(),
  {
    default: 'light',
    description: 'User theme preference',
    metadata: { group: 'preferences' }
  }
);
// Shorthand syntax - just default value
builder.v('language',
  b => b.string.optional(),
  'en' // Default value shorthand
);
// Function as default value
builder.v('timestamp',
  b => b.number.optional(),
  () => Date.now() // Dynamic default
);
// With deprecated field
builder.v('oldField',
  b => b.string.optional(),
  {
    deprecated: 'Use newField instead',
    default: ''
  }
);  useField() - Reuse Rules
The useField() method allows you to reuse pre-defined field rules across multiple fields. Field rules are created using the Plugin Registry's createFieldRule() method:
import { createPluginRegistry } from '@maroonedog/luq/core/registry';
import { 
  requiredPlugin, 
  stringEmailPlugin,
  stringMinPlugin 
} from '@maroonedog/luq/plugin';
// Create registry with plugins
const registry = createPluginRegistry()
  .register(requiredPlugin)
  .register(stringEmailPlugin)
  .register(stringMinPlugin);
// Create reusable field rules using registry
const emailRule = registry.createFieldRule<string>(
  b => b.string.required().email(),
  { 
    name: 'email', 
    description: 'Valid email address',
    fieldOptions: {
      default: '',
      metadata: { validation: 'strict' }
    }
  }
);
const passwordRule = registry.createFieldRule<string>(
  b => b.string.required().min(8),
  { name: 'password', description: 'Secure password' }
);
// Use field rules in builder
const builder = registry
  .toBuilder()
  .for<{ email: string; password: string; backupEmail: string }>()
  .useField('email', emailRule)
  .useField('backupEmail', emailRule)
  .useField('password', passwordRule);  📌 About FieldRule
- • FieldRule objects are created through 
registry.createFieldRule() - • They encapsulate validation logic and field options
 - • Each rule can be tested individually with 
rule.validate(value) - • Rules are reusable across multiple fields and validators
 - • See the Plugin Registry API for details
 
Builder Modes
strict() Mode
The strict() method enables compile-time type checking to ensure all fields in the type have validation rules defined. When fields are missing, it shows errors in your editor/IDE and prevents compilation:
type User = {
  id: string;
  name: string;
  email: string;
  age: number;
};
// Without strict mode - no type checking for missing fields
const validator = Builder()
  .use(requiredPlugin)
  .for<User>()
  .v('name', b => b.string.required())
  .v('email', b => b.string.required())
  .build(); // Missing 'id' and 'age' - builds successfully
// With strict mode - type error if fields are missing
const strictValidator = Builder()
  .use(requiredPlugin)
  .for<User>()
  .v('id', b => b.string.required())
  .v('name', b => b.string.required())
  .v('email', b => b.string.required())
  .v('age', b => b.number.required())
  .strict() // ✅ All fields defined - returns builder
  .build();
// Attempting strict with missing fields
const incompleteValidator = Builder()
  .use(requiredPlugin)
  .for<User>()
  .v('name', b => b.string.required())
  .strict() // ❌ Type error: Returns error object instead of builder
  .build(); // TypeScript error: Property 'build' does not exist on type
// Can continue adding validations after strict()
const continueAfterStrict = Builder()
  .use(requiredPlugin, stringMinPlugin)
  .for<User>()
  .v('id', b => b.string.required())
  .v('name', b => b.string.required())
  .v('email', b => b.string.required())
  .v('age', b => b.number.required())
  .strict() // All fields present - returns builder
  .v('name', b => b.string.required().min(2)) // Can add more validations
  .build(); // Works fine  💡 Strict Mode Behavior
- • Editor Warning: Shows type errors in your editor/IDE when fields are missing
 - • Compile-Time Check: Prevents TypeScript compilation if not all fields have validators
 - • Return Type: Uses conditional types - returns error type if fields missing, builder if complete
 - • Continuable: If all fields are present, you can continue chaining more validations after 
strict() - • Position Flexible: Can be called at any point in the chain, not just at the end
 - • NO Runtime Effect: Does NOT validate for extra/undefined fields at runtime
 - • Error Prevention: Error type lacks 
build()method, preventing incomplete validators from being built 
📝 Note
There's also a strictOnEditor() method which is just an alias for strict(). They are identical - use strict() as it's more concise.
⚠️ Common Misconception
 strict() does NOT validate for extra properties at runtime. It only ensures all type fields have validators at compile time.
To reject extra properties at runtime, use the objectAdditionalProperties plugin with additionalProperties(false):
// To reject extra properties at runtime, use objectAdditionalProperties
import { objectAdditionalPropertiesPlugin } from '@maroonedog/luq/plugin';
type User = {
  name: string;
  age: number;
  email: string;
};
const validator = Builder()
  .use(requiredPlugin)
  .use(objectAdditionalPropertiesPlugin)
  .for<{ user: User }>()
  .v('user', b => b.object.additionalProperties(false, {
    allowedProperties: ['name', 'age', 'email']
  }))
  .build();
// This will fail validation
const result = validator.validate({
  user: {
    name: 'John',
    age: 30,
    email: 'john@example.com',
    extra: 'field' // ❌ Extra property not allowed
  }
});  build() - Create Validator
The build() method finalizes the builder and returns a validator instance:
// build() creates the final validator instance
const validator = Builder()
  .use(requiredPlugin)
  .use(stringMinPlugin)
  .for<UserProfile>()
  .v('name', b => b.string.required().min(2))
  .v('age', b => b.number.required().min(18))
  .build();
// The validator has two main methods:
// 1. validate() - validates and returns original values
const validateResult = validator.validate({
  name: 'John',
  age: 25
});
// 2. parse() - validates and returns transformed values
const parseResult = validator.parse({
  name: 'John',
  age: 25
});  ⚠️ validate() vs parse()
- • 
validate()- Returns original values (no transformations) - • 
parse()- Returns transformed values (applies all transformations) - • Use 
parse()when you have transform plugins 
Type Inference
Method Chaining
The Builder uses advanced TypeScript features to provide complete type safety and inference throughout the chain:
type FormData = {
  username: string;
  age: string;  // String from form input
  tags: string; // Comma-separated string
};
type ProcessedData = {
  username: string;    // Stays string
  age: number;        // Transformed to number
  tags: string[];     // Transformed to array
};
const validator = Builder()
  .use(requiredPlugin)
  .use(transformPlugin)
  .for<FormData>()
  .v('username', b => b.string.required())
  .v('age', b => b.string.required().transform(v => parseInt(v, 10)))
  .v('tags', b => b.string.required().transform(v => v.split(',')))
  .build();
// TypeScript knows the types
const result = validator.parse({ 
  username: 'john',
  age: '25',
  tags: 'js,ts,react'
});
if (result.isValid()) {
  const data = result.unwrap();
  // data.age is number
  // data.tags is string[]
}  Complete Example
Here's a comprehensive example showing all Builder API features:
import { Builder } from '@maroonedog/luq/core';
import {
  requiredPlugin,
  optionalPlugin,
  stringMinPlugin,
  stringMaxPlugin,
  stringEmailPlugin,
  stringPatternPlugin,
  numberMinPlugin,
  numberMaxPlugin,
  arrayMinLengthPlugin,
  arrayMaxLengthPlugin,
  objectPlugin,
  oneOfPlugin,
  compareFieldPlugin,
  transformPlugin
} from '@maroonedog/luq/plugin';
// Define your data types
type RegistrationForm = {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
  age: string;  // From form input
  acceptTerms: boolean;
  preferences?: {
    newsletter: boolean;
    theme: 'light' | 'dark' | 'auto';
  };
  interests: string[];
};
// Build comprehensive validator
const registrationValidator = Builder()
  // Add all required plugins
  .use(
    requiredPlugin,
    optionalPlugin,
    stringMinPlugin,
    stringMaxPlugin,
    stringEmailPlugin,
    stringPatternPlugin,
    numberMinPlugin,
    arrayMinLengthPlugin,
    arrayMaxLengthPlugin,
    objectPlugin,
    oneOfPlugin,
    compareFieldPlugin,
    transformPlugin
  )
  // Set the type
  .for<RegistrationForm>()
  // Define field validations
  .v('username', b => 
    b.string
      .required()
      .min(3)
      .max(20)
      .pattern(/^[a-zA-Z0-9_]+$/)
  )
  .v('email', b => 
    b.string
      .required()
      .email()
      .transform(email => email.toLowerCase())
  )
  .v('password', b => 
    b.string
      .required()
      .min(8)
      .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
  )
  .v('confirmPassword', b => 
    b.string
      .required()
      .compareField('password')
  )
  .v('age', b => 
    b.string
      .required()
      .transform(v => parseInt(v, 10))
      .min(13)
      .max(120)
  )
  .v('acceptTerms', b => 
    b.boolean
      .required()
      .equals(true)
  )
  .v('preferences', b => 
    b.object.optional()
  )
  .v('preferences.newsletter', b => 
    b.boolean.optional()
  )
  .v('preferences.theme', b => 
    b.string
      .optional()
      .oneOf(['light', 'dark', 'auto'])
  )
  .v('interests', b => 
    b.array
      .required()
      .minLength(1)
      .maxLength(10)
  )
  .v('interests[*]', b => 
    b.string.required().min(2)
  )
  // Ensure all required fields are defined
  .strict()
  // Build the final validator
  .build();
// Use the validator
const formData = {
  username: 'john_doe',
  email: 'JOHN@EXAMPLE.COM',
  password: 'SecurePass123',
  confirmPassword: 'SecurePass123',
  age: '25',
  acceptTerms: true,
  preferences: {
    newsletter: true,
    theme: 'dark' as const
  },
  interests: ['programming', 'music', 'sports']
};
// Validate and transform
const result = registrationValidator.parse(formData);
if (result.isValid()) {
  const validatedData = result.unwrap();
  console.log('Registration successful!', validatedData);
  // validatedData.email is 'john@example.com' (lowercased)
  // validatedData.age is 25 (number)
} else {
  console.log('Validation errors:', result.errors);
}  API Reference
| Method | Returns | Description | 
|---|---|---|
Builder() |  IChainableBuilder | Creates new builder instance | 
use(...plugins) |  IChainableBuilder | Adds plugins to builder | 
for<T>() |  FieldBuilder | Sets validation target type | 
v(path, builder, options?) |  FieldBuilder | Defines field validation | 
useField(path, rule) |  FieldBuilder | Uses pre-defined rule | 
strict() |  FieldBuilder | Error | Type-check all fields defined | 
build() |  Validator | Creates final validator |