CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ts-pattern

The exhaustive Pattern Matching library for TypeScript with smart type inference

Pending
Overview
Eval results
Files

validation.mddocs/

Type Validation

Runtime type validation and type guard generation using pattern matching. Perfect for validating API responses, user input, and unknown data structures.

Capabilities

Type Guard Generation

Creates type guard functions from patterns for runtime type checking with compile-time type safety.

/**
 * Creates a type guard function from a pattern
 * @param pattern - Pattern to match against
 * @returns Type guard function that narrows unknown to matched type
 */
function isMatching<const p extends Pattern<unknown>>(
  pattern: p
): (value: unknown) => value is P.infer<p>;

Usage Examples:

import { isMatching, P } from "ts-pattern";

// Create type guards
const isUser = isMatching({ 
  id: P.number, 
  name: P.string, 
  email: P.string.regex(/\S+@\S+\.\S+/) 
});

const isApiResponse = isMatching({
  success: P.boolean,
  data: P.optional(P.any),
  error: P.optional(P.string)
});

// Use type guards
function processUnknownData(data: unknown) {
  if (isUser(data)) {
    // data is now typed as { id: number; name: string; email: string }
    console.log(`User: ${data.name} (${data.email})`);
    return data.id;
  }
  
  if (isApiResponse(data)) {
    // data is now typed as the API response interface
    if (data.success && data.data) {
      return data.data;
    } else {
      throw new Error(data.error || 'Unknown API error');
    }
  }
  
  throw new Error('Invalid data format');
}

Direct Pattern Validation

Validates if a value matches a pattern directly without creating a type guard function.

/**
 * Validates if a value matches a pattern
 * @param pattern - Pattern to match against
 * @param value - Value to validate  
 * @returns Boolean indicating if value matches pattern
 */
function isMatching<const T, const P extends Pattern<T>>(
  pattern: P,
  value: T
): value is T & P.infer<P>;

Usage Examples:

// Direct validation
const data = { name: "Alice", age: 25, active: true };

if (isMatching({ name: P.string, age: P.number.gte(18) }, data)) {
  // data is validated and typed properly
  console.log(`Adult user: ${data.name}, age ${data.age}`);
}

// Validate API responses
async function fetchUserData(userId: number) {
  const response = await fetch(`/api/users/${userId}`);
  const data = await response.json();
  
  if (isMatching({ 
    id: P.number, 
    name: P.string,
    profile: P.optional({
      avatar: P.string.startsWith('http'),
      bio: P.string
    })
  }, data)) {
    return data; // properly typed user data
  }
  
  throw new Error('Invalid user data format');
}

Complex Validation Patterns

Advanced patterns for validating complex data structures.

// Pattern constraint type for additional properties
type PatternConstraint<T> = T extends readonly any[]
  ? P.Pattern<T>
  : T extends object
  ? P.Pattern<T> & UnknownProperties
  : P.Pattern<T>;

// Unknown properties interface for object patterns
interface UnknownProperties {
  [key: string]: unknown;
}

Usage Examples:

// Validate nested structures
const isNestedConfig = isMatching({
  database: {
    host: P.string,
    port: P.number.between(1, 65535),
    credentials: P.union(
      { type: 'password', username: P.string, password: P.string },
      { type: 'token', token: P.string }
    )
  },
  features: P.array(P.string),
  debug: P.boolean.optional()
});

// Validate arrays with specific patterns
const isUserList = isMatching(P.array({
  id: P.number,
  name: P.string.minLength(1),
  roles: P.array(P.union('admin', 'user', 'guest')),
  lastLogin: P.union(P.instanceOf(Date), P.nullish)
}));

// Validate with custom predicates
const isValidEventPayload = isMatching({
  type: P.string,
  timestamp: P.when(ts => ts instanceof Date && ts <= new Date()),
  payload: P.shape(P.any), // allows any object structure
  metadata: P.optional(P.any)
});

// Use in validation functions
function validateUserInput(input: unknown) {
  if (!isNestedConfig(input)) {
    throw new Error('Invalid configuration format');
  }
  
  // input is now properly typed
  const { database, features, debug = false } = input;
  
  if (database.credentials.type === 'password') {
    // TypeScript knows this is password credentials
    return connectWithPassword(database.host, database.port, 
                             database.credentials.username, 
                             database.credentials.password);
  } else {
    // TypeScript knows this is token credentials
    return connectWithToken(database.host, database.port, 
                          database.credentials.token);
  }
}

Validation with Error Handling

Combining validation with proper error handling and user feedback.

Usage Examples:

// Validation with detailed error messages
function validateAndProcessData<T>(
  data: unknown,
  pattern: Pattern<T>,
  errorMessage: string
): T {
  if (isMatching(pattern, data)) {
    return data;
  }
  
  throw new Error(`${errorMessage}. Received: ${JSON.stringify(data)}`);
}

// Form validation
interface UserForm {
  email: string;
  password: string;
  age: number;
  terms: boolean;
}

const userFormPattern = {
  email: P.string.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/),
  password: P.string.minLength(8).regex(/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),
  age: P.number.int().between(13, 120),
  terms: P.when((x: boolean) => x === true)
};

function validateUserForm(formData: unknown): UserForm {
  try {
    return validateAndProcessData(
      formData,
      userFormPattern,
      'Invalid user form data'
    );
  } catch (error) {
    // Add specific validation error details
    if (typeof formData === 'object' && formData !== null) {
      const data = formData as Record<string, unknown>;
      
      if (!isMatching(P.string.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/), data.email)) {
        throw new Error('Invalid email format');
      }
      
      if (!isMatching(P.string.minLength(8), data.password)) {
        throw new Error('Password must be at least 8 characters');
      }
      
      if (!isMatching(P.number.int().between(13, 120), data.age)) {
        throw new Error('Age must be between 13 and 120');
      }
      
      if (data.terms !== true) {
        throw new Error('Terms and conditions must be accepted');
      }
    }
    
    throw error;
  }
}

// API response validation
async function safeApiCall<T>(
  url: string,
  responsePattern: Pattern<T>
): Promise<T> {
  try {
    const response = await fetch(url);
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    const data = await response.json();
    
    if (isMatching(responsePattern, data)) {
      return data;
    }
    
    throw new Error(`Invalid API response format from ${url}`);
  } catch (error) {
    console.error('API call failed:', error);
    throw error;
  }
}

// Usage
const userData = await safeApiCall('/api/user/123', {
  id: P.number,
  name: P.string,
  email: P.string,
  preferences: P.optional({
    theme: P.union('light', 'dark'),
    notifications: P.boolean
  })
});

Install with Tessl CLI

npx tessl i tessl/npm-ts-pattern

docs

index.md

pattern-matching.md

patterns.md

validation.md

tile.json