TypeScript Validation Made Simple

Use your existing TypeScript types to build validators. No schema rewriting, just add validation rules to the types you already have.

Works Today
With existing types
Type Safe
Full autocomplete
19-23KB
Tree-shakable
695K ops/s
CSP-safe
npm install @maroonedog/luq@alpha

See It In Action

Traditional Schema-Based Approach

// Define schema (types are generated)
const UserSchema = z.object({
  name: z.string().min(3),
  email: z.string().email(),
  age: z.number().min(18),
  role: z.enum(['admin', 'user']),
  settings: z.object({
    notifications: z.boolean(),
    theme: z.enum(['light', 'dark'])
  })
});

// Extract type from schema
type User = z.infer<typeof UserSchema>;

// Custom business logic
const schema = UserSchema.refine(
  (data) => {
    // Inline custom validation
    if (data.role === 'admin' && data.age < 21) {
      return false;
    }
    return true;
  },
  { message: 'Admins must be 21 or older' }
);

// Validate
const result = schema.safeParse(userData);

Luq Type-First Approach

// Use existing TypeScript types
type User = {
  name: string;
  email: string;
  age: number;
  role: 'admin' | 'user';
  settings: {
    notifications: boolean;
    theme: 'light' | 'dark';
  };
};

// Create reusable business logic plugin
const adminAgePlugin = plugin({
  name: 'adminAge',
  methodName: 'adminAge',
  allowedTypes: ['number'] as const,
  category: 'standard',
  impl: (options?: ValidationOptions) => ({
    check: (value: any, context: any) => {
      const parent = context?.parent;
      return parent?.role !== 'admin' || value >= 21;
    },
    code: 'admin_age',
    getErrorMessage: () => 'Admins must be 21 or older',
    params: []
  })
});

// Build validator with plugins
const validator = Builder()
  .use(requiredPlugin)
  .use(stringMinPlugin)
  .use(stringEmailPlugin)
  .use(numberMinPlugin)
  .use(adminAgePlugin)
  .for<User>()
  .v('name', b => b.string.required().min(3))
  .v('email', b => b.string.required().email())
  .v('age', b => b.number.required().min(18).adminAge())
  .v('role', b => b.required())
  .build();

// Validate with full type safety
const result = validator.validate(userData);
Luq advantages highlighted
Business logic as reusable plugins

How It Works

Three simple steps to add validation to your existing TypeScript types

1

Use Your Types

Keep your existing TypeScript interfaces exactly as they are

2

Add Validation

Build validators using the plugins you need

3

Validate Data

Get type-safe results with detailed error messages

Your existing code

// Keep your types as-is
interface User {
  name: string;
  email: string;
  age: number;
}

// Your existing function
function saveUser(user: User) {
  // No validation... 😬
  return api.post('/users', user);
}

+ Add validation with Luq

// Same interface, no changes needed
interface User {
  name: string;
  email: string;
  age: number;
}

// Build validator from your type
const validator = Builder()
  .use(requiredPlugin)
  .use(stringEmailPlugin)
  .use(numberMinPlugin)
  .for<User>()
  .v("name", b => b.string.required())
  .v("email", b => b.string.required().email())
  .v("age", b => b.number.required().min(13))
  .build();

function saveUser(userData: unknown) {
  const result = validator.validate(userData);
  
  if (result.isValid()) {
    const user = result.unwrap(); // Type: User
    return api.post('/users', user);
  } else {
    throw new Error(result.errors[0].message);
  }
}

No Rewriting

Use your existing TypeScript interfaces. No need to convert to schemas or learn new syntax.

Type Safe

Full TypeScript integration. IDE autocomplete works perfectly with your field names and types.

Small Bundle

Import only the validation rules you need. Tree-shaking ensures minimal bundle size.

Why Choose Luq?

Built for teams who want validation without the complexity

Works With Your Existing Types

No need to rewrite your interfaces or convert to schemas. Luq works with the TypeScript types you already have.

example.ts
// Your existing interface
interface User {
  name: string;
  email: string;
  age: number;
}

// Just add validation
.for<User>() // Full type safety

Type-Safe Builder Pattern

IntelliSense shows only the methods available for each field type. String fields get string methods, numbers get number methods.

example.ts
// IDE autocomplete works perfectly
.v("name", b => b.string.required().min(2))
.v("age", b => b.number.required().min(13))
.v("email", b => b.string.required().email())

Small Bundle Size

Import only the validation plugins you need. Unused plugins are automatically tree-shaken from your bundle.

example.ts
// Only import what you use
import { requiredPlugin } from '@maroonedog/luq/plugins/required';
import { stringEmailPlugin } from '@maroonedog/luq/plugins/stringEmail';

// Final bundle: ~19KB gzipped

Cross-Field Validation

Validate fields that depend on other fields. Compare passwords, check conditional requirements, and more.

example.ts
// Password confirmation
.v("confirmPassword", b => 
  b.string.required().compareField("password")
)

// Conditional validation
.v("phone", b => 
  b.string.requiredIf((data) => data.contactMethod === "phone")
)

Ready to Start Using Type-Safe Validation?

Start using Luq today and experience type-safe, plugin-based validation