Troubleshooting

Common issues and solutions when working with Luq. Find quick fixes for the most frequent problems.

TypeScript Errors

Plugin methods not available / TypeScript compilation errors

Symptom: TypeScript complains that validation methods like .email() or .min() don't exist.

Cause: The plugin providing the method hasn't been imported and registered with .use().

Solution:

// ❌ This will cause TypeScript errors
const validator = Builder()
  .for<User>()
  .v('email', b => b.string.required().email()) // Error: 'email' method not available
  .build();

// ✅ Import and use the plugin first
import { stringEmailPlugin } from '@maroonedog/luq/plugin';

const validator = Builder()
  .use(stringEmailPlugin) // Add plugin before using
  .for<User>()
  .v('email', b => b.string.required().email()) // Now works
  .build();
Field not found / Path errors

Symptom: TypeScript errors about fields not existing on your type.

Cause: Incorrect field paths or forgetting to use dot notation for nested properties.

Solution:

// ❌ Wrong - trying to validate non-existent field
type User = {
  profile: {
    name: string;
  };
};

const validator = Builder()
  .for<User>()
  .v('name', b => b.string.required()) // Error: 'name' doesn't exist on User
  .build();

// ✅ Correct - use dot notation for nested fields
const validator = Builder()
  .for<User>()
  .v('profile.name', b => b.string.required()) // Correct path
  .build();
Type inference issues

Symptom: TypeScript can't determine field types or shows generic errors.

Cause: Missing .for<YourType>() call in the builder chain.

Solution:

// ❌ Problem - TypeScript can't infer the type
const validator = Builder()
  .use(requiredPlugin)
  // Missing .for<Type>() specification
  .v('email', b => b.string.required()) // Type error
  .build();

// ✅ Solution - Always specify the type
const validator = Builder()
  .use(requiredPlugin)
  .for<User>() // Specify type here
  .v('email', b => b.string.required()) // Now TypeScript knows the structure
  .build();

Array Validation Issues

How to validate arrays and array items

Key concept: Luq uses [*] notation to validate array items. When you write items[*].name, it automatically validates the name field of ALL items in the array.

Common mistake: Trying to use specific index notation like items[0].name or trying to split into multiple validators - neither approach is correct in Luq.

The correct Luq pattern - everything in ONE validator:

// ❌ Common mistake - trying to validate array items with index notation
type UserList = {
  users: Array<{
    name: string;
    email: string;
  }>;
  tags: string[];  // Primitive array
  scores: number[]; // Another primitive array
};

const wrongValidator = Builder()
  .use(arrayMinLengthPlugin)
  .use(requiredPlugin)
  .for<UserList>()
  .v('users[0].name', b => b.string.required()) // Wrong! Specific index doesn't work
  .v('tags[0]', b => b.string.required())       // Wrong! Can't validate specific index
  .build();

// ✅ Correct - Luq uses [*] notation to validate ALL items in an array
const validator = Builder()
  .use(arrayMinLengthPlugin)
  .use(requiredPlugin)
  .use(stringMinPlugin)
  .use(stringEmailPlugin)
  .use(numberMinPlugin)
  .use(numberMaxPlugin)
  .for<UserList>()
  // Array of objects - validate the array and its object properties
  .v('users', b => b.array.required().minLength(1))     // Validate array itself
  .v('users[*].name', b => b.string.required().min(2))  // Validates name of ALL users
  .v('users[*].email', b => b.string.required().email()) // Validates email of ALL users
  
  // Primitive array (string[]) - validate array and its elements
  .v('tags', b => b.array.required().minLength(1).maxLength(10)) // Array validation
  .v('tags[*]', b => b.string.required().min(2).max(20))         // Each string element
  
  // Primitive array (number[]) - validate array and its elements  
  .v('scores', b => b.array.required())               // Array validation
  .v('scores[*]', b => b.number.required().min(0).max(100)) // Each number element
  .build();

// The [*] notation works for both object arrays and primitive arrays
// For primitive arrays: use 'fieldName[*]' to validate each element
Primitive arrays (string[], number[], boolean[])

Common need: Validating arrays of primitive types like tags, scores, IDs, flags, etc.

Key pattern: Use fieldName[*] to validate each primitive element in the array. Validate both the array itself (length, uniqueness) AND each element (min/max, pattern, etc.).

// Validating primitive arrays (string[], number[], boolean[], etc.)
type AppData = {
  keywords: string[];
  ratings: number[];
  categories: string[];
  flags: boolean[];
};

// ✅ Correct pattern for primitive arrays
const validator = Builder()
  .use(requiredPlugin)
  .use(arrayMinLengthPlugin)
  .use(arrayMaxLengthPlugin)
  .use(arrayUniquePlugin)
  .use(stringMinPlugin)
  .use(stringMaxPlugin)
  .use(stringPatternPlugin)
  .use(numberMinPlugin)
  .use(numberMaxPlugin)
  .use(booleanTruthyPlugin)
  .for<AppData>()
  
  // String array - validate both array and each string element
  .v('keywords', b => b.array.required().minLength(1).maxLength(10).unique()) // Array properties
  .v('keywords[*]', b => b.string.required().min(3).max(30).pattern(/^[a-z]+$/)) // Each string
  
  // Number array - validate both array and each number element
  .v('ratings', b => b.array.required().minLength(1))    // Array properties
  .v('ratings[*]', b => b.number.required().min(1).max(5)) // Each number must be 1-5
  
  // Another string array with different rules
  .v('categories', b => b.array.required().unique())      // No duplicates
  .v('categories[*]', b => b.string.required().min(2))    // Each category name
  
  // Boolean array
  .v('flags', b => b.array.required())                    // Array validation
  .v('flags[*]', b => b.boolean.required().truthy())      // Each flag must be true
  .build();

// Example usage:
const result = validator.validate({
  keywords: ['react', 'typescript', 'nodejs'],  // ✅ Valid
  ratings: [5, 4, 3, 5, 4],                     // ✅ Valid
  categories: ['frontend', 'backend'],          // ✅ Valid
  flags: [true, true, false]                    // ❌ Invalid - one is false
});

Remember:

  • .v('tags', ...) validates the array itself (length, uniqueness)
  • .v('tags[*]', ...) validates EACH element in the array
  • • Both validations work together in ONE validator
Complex nested structures (arrays and objects)

Real-world scenario: Validating complex data with arrays of objects, nested objects, and arrays within objects.

Luq's powerful pattern: Combine [*] notation for arrays with dot notation for objects. Everything stays in ONE validator!

// Most common case: Array of objects with nested structures
type Order = {
  orderId: string;
  items: Array<{
    productId: string;
    name: string;
    quantity: number;
    price: number;
    attributes: {
      color?: string;
      size?: string;
    };
  }>;
  customer: {
    name: string;
    addresses: Array<{
      type: 'billing' | 'shipping';
      street: string;
      city: string;
    }>;
  };
};

// ✅ Luq's elegant approach - use [*] notation for array items, dot notation for nested objects
const orderValidator = Builder()
  .use(requiredPlugin)
  .use(arrayMinLengthPlugin)
  .use(stringMinPlugin)
  .use(numberMinPlugin)
  .use(stringPatternPlugin)
  .use(optionalPlugin)
  .use(oneOfPlugin)
  .for<Order>()
  // Validate root fields
  .v('orderId', b => b.string.required().pattern(/^ORD-d{6}$/))
  
  // Validate array and its items
  .v('items', b => b.array.required().minLength(1))
  .v('items[*].productId', b => b.string.required().pattern(/^PROD-d{4}$/))
  .v('items[*].name', b => b.string.required().min(1))
  .v('items[*].quantity', b => b.number.required().min(1))
  .v('items[*].price', b => b.number.required().min(0))
  
  // Validate nested objects within array items
  .v('items[*].attributes.color', b => 
    b.string.optional().oneOf(['blue', 'red', 'green', 'black', 'white']))
  .v('items[*].attributes.size', b => 
    b.string.optional().oneOf(['S', 'M', 'L', 'XL']))
  
  // Validate nested object
  .v('customer.name', b => b.string.required().min(2))
  
  // Validate nested array within object
  .v('customer.addresses', b => b.array.required().minLength(1))
  .v('customer.addresses[*].type', b => b.string.required().oneOf(['billing', 'shipping']))
  .v('customer.addresses[*].street', b => b.string.required().min(5))
  .v('customer.addresses[*].city', b => b.string.required().min(2))
  .build();

// Everything in ONE validator using [*] notation for arrays and dot notation for objects
// No need to split validators or iterate manually!
Nested arrays (Array of arrays)

Multi-dimensional arrays: Validating matrices, 2D/3D arrays, or other nested array structures.

Luq's solution: Chain [*] notation for each level of nesting. data[*][*] validates all elements in a 2D array.

// Complex case: Array of arrays
type Matrix = {
  name: string;
  data: number[][];
  metadata: {
    rows: number;
    cols: number;
  };
};

// ❌ Wrong approach - specific index notation doesn't work
const badValidator = Builder()
  .for<Matrix>()
  .v('data[0][0]', b => b.number.required()) // Wrong! Specific indices don't work
  .build();

// ✅ Correct approach - Luq's [*] notation for nested arrays
const matrixValidator = Builder()
  .use(requiredPlugin)
  .use(arrayMinLengthPlugin)
  .use(numberMinPlugin)
  .for<Matrix>()
  .v('name', b => b.string.required())
  .v('data', b => b.array.required().minLength(1)) // Validate outer array
  .v('data[*]', b => b.array.required()) // Each item must be an array
  .v('data[*][*]', b => b.number.required().min(0)) // Each number in nested arrays
  .v('metadata.rows', b => b.number.required().min(1))
  .v('metadata.cols', b => b.number.required().min(1))
  .build();

// The [*] notation can be chained for nested arrays: data[*][*]
// This validates ALL elements at each level of nesting

Luq Array Validation Patterns:

  • tags[*] - Validates each string in a string[] array
  • scores[*] - Validates each number in a number[] array
  • items[*].name - Validates the name field of ALL items in an object array
  • items[*].subitems[*].value - Nested arrays within objects
  • matrix[*][*] - 2D arrays (matrix)
  • cube[*][*][*] - 3D arrays
  • items[*].tags[*] - Primitive array within object array
  • items[*].address.city - Combine [*] with dot notation for nested objects
  • • Everything in ONE validator - no splitting or manual iteration!

Bundle Size Issues

Bundle size larger than expected

Symptom: Your bundle includes more Luq code than you expected.

Causes: Importing from barrel exports or using wildcard imports.

Solution:

// ✅ Good - specific imports
import { requiredPlugin } from '@maroonedog/luq/plugin';
import { stringMinPlugin } from '@maroonedog/luq/plugin';

// ❌ Avoid - importing everything
import * as plugins from 'luq/plugins';

Bundle Analysis Tips:

  • • Use webpack-bundle-analyzer to visualize what's included
  • • Check your bundler's tree-shaking configuration
  • • Ensure you're using ES modules (import/export)
  • • Avoid importing entire plugin collections

Performance Problems

Slow validation performance

Common causes: Creating validators repeatedly, heavy transformations, or inefficient data structures.

Debugging validation performance:

// Debug slow validation
const start = performance.now();
const result = validator.validate(largeData);
const end = performance.now();

console.log(`Validation took ${end - start} milliseconds`);

// Check for heavy transformations
const validator = Builder()
  .for<Data>()
  .v('content', b => 
    b.string
      .required()
      .transform(content => {
        console.time('transform'); // Debug transform time
        const result = heavyProcessing(content);
        console.timeEnd('transform');
        return result;
      })
  )
  .build();

Performance Tips:

  • • Create validators once and reuse them
  • • Move heavy processing outside of transforms
  • • Use simpler data structures when possible
  • • Consider using abort-early validation for large objects
Memory leaks in long-running applications

Symptom: Memory usage grows over time in server applications.

Cause: Creating new validators instead of reusing them.

Solution:

// ❌ Memory leak - creating validators repeatedly
function validateData(data: User) {
  const validator = Builder() // New validator every call!
    .use(requiredPlugin)
    .for<User>()
    .v('email', b => b.string.required().email())
    .build();
    
  return validator.validate(data);
}

// ✅ Fix - Create validator once
const USER_VALIDATOR = Builder()
  .use(requiredPlugin)
  .for<User>()
  .v('email', b => b.string.required().email())
  .build();

function validateData(data: User) {
  return USER_VALIDATOR.validate(data); // Reuse validator
}

Runtime Issues

Unexpected validation results

Debugging steps:

  1. 1. Check that all required plugins are imported and registered
  2. 2. Verify field paths match your data structure exactly
  3. 3. Test with simplified data to isolate the issue
  4. 4. Check for type mismatches between expected and actual data

Debug validation step by step:

// Add logging to understand what's happening
const result = validator.validate(data);

console.log('Validation result:', result.isValid());
if (!result.isValid()) {
  console.log('Errors:', result.getErrors());
  console.log('Data being validated:', JSON.stringify(data, null, 2));
}
Error messages not user-friendly

Problem: Default error messages are too technical for end users.

Solution - Customize error messages:

// Default error messages might not be user-friendly
const result = validator.validate({ email: 'invalid-email' });
// Error: "stringEmail validation failed"

// ✅ Customize error messages for better UX
const validator = Builder()
  .use(stringEmailPlugin)
  .for<User>()
  .v('email', b => 
    b.string
      .required({ message: 'Email is required' })
      .email({ message: 'Please enter a valid email address' })
  )
  .build();

Advanced Issues

Async validation (database checks, API calls)

Luq's approach: Async validation uses a separate layer design - resolve all async data BEFORE validation, then pass it to the synchronous validator.

The correct async pattern with type safety:

// Luq's async validation pattern - separate async layer approach
import { createAsyncContext, addAsyncSupport } from '@maroonedog/luq/async.experimental';

// Define async context type
interface ValidationContext {
  emailExists: boolean;
  domainValid: boolean;
  quotaAvailable: { canCreate: boolean; limit: number };
}

// ✅ Correct pattern - Luq's async layer separation
// Step 1: Create normal synchronous validator
const syncValidator = Builder()
  .use(requiredPlugin)
  .use(stringEmailPlugin)
  .use(fromContextPlugin) // Plugin that can access async context
  .for<User>()
  .v('email', b => b.string.required().email())
  .build();

// Step 2: Add async support to the validator
const validator = addAsyncSupport(syncValidator);

// Step 3: Create and resolve async context BEFORE validation
const asyncContext = await createAsyncContext<ValidationContext>()
  .set('emailExists', checkEmailExists(data.email))        // Parallel
  .set('domainValid', validateDomain(data.email))          // Parallel
  .set('quotaAvailable', checkUserQuota(data.userId))      // Parallel
  .build(); // All async operations run in parallel

// Step 4: Validate with pre-resolved async context
const result = await validator
  .withAsyncContext<ValidationContext>(asyncContext)
  .validate(data);

// The async data is available in plugins via getAsyncContext
const emailExistsPlugin = {
  name: 'emailExists',
  validate: (value: string, context: any) => {
    // Get pre-resolved async data (no await needed here!)
    const asyncData = getAsyncContext<ValidationContext>(context);
    
    if (asyncData?.emailExists) {
      return { valid: false, message: 'Email already exists' };
    }
    
    return { valid: true };
  }
};

// Key benefits:
// 1. All async operations run in parallel (not sequential)
// 2. Async is resolved ONCE before validation
// 3. Validation itself remains synchronous and fast
// 4. Full type safety with TypeScript generics

Luq's Async Architecture:

  1. 1. Create async context with all async operations running in PARALLEL
  2. 2. Resolve all async data ONCE before validation
  3. 3. Pass pre-resolved context to validator using withAsyncContext
  4. 4. Validation remains synchronous and fast
  5. 5. Plugins access async data via getAsyncContext (no await needed)
  6. 6. Full type safety with TypeScript generics

Performance Benefits:

  • • All async operations run in parallel (200ms instead of 600ms sequential)
  • • Only ONE await during the entire validation process
  • • Synchronous validation performance is unaffected
  • • Zero overhead when not using async features

Getting Help

Still having issues?

1.

Check the documentation:

Review the core concepts and examples to ensure you're following best practices.

2.

Create a minimal reproduction:

Strip down your code to the smallest possible example that demonstrates the issue.

3.

Check GitHub Issues:

Search for similar issues in the GitHub repository.

4.

Ask for help:

Open a new issue with your minimal reproduction and detailed description.