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. Check that all required plugins are imported and registered
- 2. Verify field paths match your data structure exactly
- 3. Test with simplified data to isolate the issue
- 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. Create async context with all async operations running in PARALLEL
- 2. Resolve all async data ONCE before validation
- 3. Pass pre-resolved context to validator using
withAsyncContext
- 4. Validation remains synchronous and fast
- 5. Plugins access async data via
getAsyncContext
(no await needed) - 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?
Check the documentation:
Review the core concepts and examples to ensure you're following best practices.
Create a minimal reproduction:
Strip down your code to the smallest possible example that demonstrates the issue.
Check GitHub Issues:
Search for similar issues in the GitHub repository.
Ask for help:
Open a new issue with your minimal reproduction and detailed description.