CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ow

Function argument validation for humans with expressive, chainable API

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

advanced-features.mddocs/

Advanced Features

Optional predicates, custom validation, error handling, TypeScript type utilities, and advanced validation patterns for complex use cases.

Capabilities

Optional Predicates

Make any predicate optional by allowing undefined values alongside the main type.

/** Optional predicates interface */
interface OptionalPredicates {
  // Primitive types
  readonly string: StringPredicate & BasePredicate<string | undefined>;
  readonly number: NumberPredicate & BasePredicate<number | undefined>;
  readonly boolean: BooleanPredicate & BasePredicate<boolean | undefined>;
  readonly bigint: BigIntPredicate & BasePredicate<bigint | undefined>;
  readonly symbol: Predicate<symbol | undefined>;
  
  // Special values
  readonly undefined: Predicate<undefined>; // Always passes
  readonly null: Predicate<null | undefined>;
  readonly nullOrUndefined: Predicate<null | undefined>; // Always passes
  readonly nan: Predicate<number | undefined>;
  
  // Built-in types
  readonly array: ArrayPredicate & BasePredicate<unknown[] | undefined>;
  readonly object: ObjectPredicate & BasePredicate<object | undefined>;
  readonly date: DatePredicate & BasePredicate<Date | undefined>;
  readonly error: ErrorPredicate & BasePredicate<Error | undefined>;
  readonly regExp: Predicate<RegExp | undefined>;
  readonly promise: Predicate<Promise<unknown> | undefined>;
  readonly function: Predicate<Function | undefined>;
  readonly buffer: Predicate<Buffer | undefined>;
  
  // Collection types
  readonly map: MapPredicate & BasePredicate<Map<unknown, unknown> | undefined>;
  readonly set: SetPredicate & BasePredicate<Set<unknown> | undefined>;
  readonly weakMap: WeakMapPredicate & BasePredicate<WeakMap<object, unknown> | undefined>;
  readonly weakSet: WeakSetPredicate & BasePredicate<WeakSet<object> | undefined>;
  readonly iterable: Predicate<Iterable<unknown> | undefined>;
  
  // Typed arrays
  readonly typedArray: TypedArrayPredicate<TypedArray | undefined>;
  readonly int8Array: TypedArrayPredicate<Int8Array | undefined>;
  readonly uint8Array: TypedArrayPredicate<Uint8Array | undefined>;
  readonly uint8ClampedArray: TypedArrayPredicate<Uint8ClampedArray | undefined>;
  readonly int16Array: TypedArrayPredicate<Int16Array | undefined>;
  readonly uint16Array: TypedArrayPredicate<Uint16Array | undefined>;
  readonly int32Array: TypedArrayPredicate<Int32Array | undefined>;
  readonly uint32Array: TypedArrayPredicate<Uint32Array | undefined>;
  readonly float32Array: TypedArrayPredicate<Float32Array | undefined>;
  readonly float64Array: TypedArrayPredicate<Float64Array | undefined>;
  readonly arrayBuffer: ArrayBufferPredicate<ArrayBuffer | undefined>;
  readonly sharedArrayBuffer: ArrayBufferPredicate<SharedArrayBuffer | undefined>;
  readonly dataView: DataViewPredicate & BasePredicate<DataView | undefined>;
}

/** Access to optional predicates */
const optional: OptionalPredicates;

Usage Examples:

import ow from 'ow';

// Optional primitive types
ow('hello', ow.optional.string);
ow(undefined, ow.optional.string);
ow(42, ow.optional.number.positive);
ow(undefined, ow.optional.number.positive);

// Optional objects and arrays
ow({ name: 'Alice' }, ow.optional.object.hasKeys('name'));
ow(undefined, ow.optional.object.hasKeys('name'));
ow([1, 2, 3], ow.optional.array.length(3));
ow(undefined, ow.optional.array.length(3));

// Function parameters with optional validation
function processUser(name: unknown, age?: unknown, email?: unknown) {
  ow(name, 'name', ow.string.nonEmpty);
  ow(age, 'age', ow.optional.number.integer.positive);
  ow(email, 'email', ow.optional.string.matches(/^.+@.+\..+$/));
  
  return {
    name: name as string,
    age: age as number | undefined,
    email: email as string | undefined
  };
}

// Usage
processUser('Alice', 30, 'alice@example.com');
processUser('Bob', undefined, undefined);
processUser('Charlie', 25); // age provided, email undefined

Custom Validation

Advanced custom validation with flexible validation functions and error messages.

/** Custom validation interfaces */
interface CustomValidator<T> {
  validator: boolean;
  message: string | MessageBuilder;
}

type MessageBuilder = (label?: string) => string;
type ValidationFunction<T> = (value: T) => boolean | string;

interface BasePredicate<T> {
  /** Custom validation function returning boolean or error string */
  is(validator: ValidationFunction<T>): BasePredicate<T>;
  
  /** Custom validation with validator/message object */
  validate(customValidator: CustomValidator<T>): BasePredicate<T>;
  
  /** Override default error message */
  message(newMessage: string | MessageBuilder<T>): BasePredicate<T>;
}

type MessageBuilder<T> = (value: T, label?: string) => string;

Custom Function Examples:

import ow from 'ow';

// Simple boolean validation
ow(8, ow.number.is(x => x % 2 === 0));

// Custom validation with error message
ow(7, ow.number.is(x => x % 2 === 0 || `Expected even number, got ${x}`));

// Complex string validation
ow('password123', ow.string.is(s => {
  if (s.length < 8) return 'Password must be at least 8 characters';
  if (!/\d/.test(s)) return 'Password must contain at least one digit';
  if (!/[a-z]/.test(s)) return 'Password must contain at least one lowercase letter';
  return true;
}));

// Array validation
ow([1, 2, 3, 4], ow.array.is(arr => 
  arr.every(x => typeof x === 'number') || 'All elements must be numbers'
));

Custom Validator Object Examples:

import ow from 'ow';

// Validator object with static message
ow(15, ow.number.validate(value => ({
  validator: value > 10,
  message: `Expected number greater than 10, got ${value}`
})));

// Validator object with dynamic message
ow(5, 'threshold', ow.number.validate(value => ({
  validator: value > 10,
  message: label => `Expected ${label} to be greater than 10, got ${value}`
})));

// Complex object validation
const userValidator = (user: any) => ({
  validator: user.age >= 18 && user.email.includes('@'),
  message: 'User must be 18+ with valid email'
});

ow({ name: 'Alice', age: 25, email: 'alice@example.com' }, 
   ow.object.validate(userValidator));

Custom Message Examples:

import ow from 'ow';

// Static custom message
ow('hi', 'greeting', ow.string.minLength(5).message('Greeting is too short'));

// Dynamic custom message
ow(3, ow.number.positive.message((value, label) => 
  `Expected positive ${label || 'number'}, got ${value}`
));

// Chained custom messages
ow('1234', ow.string
  .minLength(5).message('Password too short')
  .matches(/\d/).message('Password missing numbers')
);

TypeScript Type Utilities

Advanced TypeScript integration with type inference and assertion utilities.

/** Extract TypeScript type from predicate */
type Infer<P> = P extends BasePredicate<infer T> ? T : never;

/** Reusable validator function */
type ReusableValidator<T> = (value: unknown, label?: string) => void;

/** Type assertion version of reusable validator */
type AssertingValidator<T> = (value: unknown, label?: string) => asserts value is T;

/** Convert ReusableValidator to AssertingValidator */
function asAssertingValidator<T>(
  validator: ReusableValidator<T>
): AssertingValidator<T>;

Type Inference Examples:

import ow, { Infer } from 'ow';

// Extract types from predicates
const userPredicate = ow.object.exactShape({
  id: ow.number.integer.positive,
  name: ow.string.nonEmpty,
  email: ow.optional.string.matches(/^.+@.+\..+$/),
  roles: ow.array.ofType(ow.string.oneOf(['admin', 'user', 'guest']))
});

type User = Infer<typeof userPredicate>;
// Result: {
//   id: number;
//   name: string;
//   email?: string | undefined;
//   roles: string[];
// }

// Use inferred type
function processUser(userData: unknown): User {
  ow(userData, userPredicate);
  return userData as User; // TypeScript knows this is safe
}

// Complex nested type inference
const apiResponsePredicate = ow.object.exactShape({
  data: ow.array.ofType(userPredicate),
  meta: ow.object.exactShape({
    total: ow.number.integer.positive,
    page: ow.number.integer.positive,
    hasMore: ow.boolean
  })
});

type ApiResponse = Infer<typeof apiResponsePredicate>;

Reusable Validator Examples:

import ow from 'ow';

// Create reusable validators
const validateEmail = ow.create('email', ow.string.matches(/^.+@.+\..+$/));
const validateUserId = ow.create(ow.number.integer.positive);

// Use reusable validators
validateEmail('alice@example.com');
validateUserId(123);

// Create validator with specific label
const validatePassword = ow.create('password', 
  ow.string.minLength(8).is(s => /\d/.test(s) || 'Must contain digit')
);

validatePassword('secret123');

// Type assertion validators
function createUser(email: unknown, id: unknown) {
  validateEmail(email);
  validateUserId(id);
  
  // TypeScript knows email is string and id is number
  return {
    id: id as number,
    email: email as string,
    createdAt: new Date()
  };
}

Error Handling

Advanced error handling with ArgumentError and validation patterns.

/** Error thrown when validation fails */
class ArgumentError extends Error {
  readonly name: 'ArgumentError';
  constructor(message: string);
}

/** Boolean validation that doesn't throw */
function isValid<T>(value: unknown, predicate: BasePredicate<T>): value is T;

Error Handling Examples:

import ow, { ArgumentError } from 'ow';

// Basic error handling
try {
  ow('invalid-email', ow.string.matches(/^.+@.+\..+$/));
} catch (error) {
  if (error instanceof ArgumentError) {
    console.log('Validation failed:', error.message);
  }
}

// Non-throwing validation
function safeValidate(data: unknown) {
  if (ow.isValid(data, ow.object.hasKeys('id', 'name'))) {
    // TypeScript knows data is object with id and name properties
    return { success: true, data };
  }
  
  return { success: false, error: 'Invalid data structure' };
}

// Validation with error context
function validateApiRequest(request: unknown) {
  try {
    ow(request, 'request', ow.object.exactShape({
      method: ow.string.oneOf(['GET', 'POST', 'PUT', 'DELETE']),
      url: ow.string.url,
      headers: ow.optional.object,
      body: ow.optional.any(ow.object, ow.string)
    }));
    
    return { valid: true, request };
  } catch (error) {
    if (error instanceof ArgumentError) {
      return { 
        valid: false, 
        error: error.message,
        type: 'validation_error'
      };
    }
    
    throw error; // Re-throw unexpected errors
  }
}

Advanced Validation Patterns

Complex validation patterns for real-world scenarios.

import ow from 'ow';

// Conditional validation
function validateUser(userData: unknown, isAdmin = false) {
  const baseShape = {
    id: ow.number.integer.positive,
    name: ow.string.nonEmpty,
    email: ow.string.matches(/^.+@.+\..+$/)
  };
  
  const adminShape = {
    ...baseShape,
    permissions: ow.array.ofType(ow.string.nonEmpty),
    lastLogin: ow.optional.date
  };
  
  ow(userData, isAdmin ? ow.object.exactShape(adminShape) : ow.object.exactShape(baseShape));
}

// Multi-step validation
function processFormData(formData: unknown) {
  // Step 1: Validate basic structure
  ow(formData, ow.object);
  
  // Step 2: Validate required fields
  ow(formData, ow.object.hasKeys('name', 'email'));
  
  // Step 3: Validate field types and constraints
  const data = formData as Record<string, unknown>;
  ow(data.name, 'name', ow.string.nonEmpty.maxLength(100));
  ow(data.email, 'email', ow.string.matches(/^.+@.+\..+$/));
  
  // Step 4: Validate optional fields if present
  if ('age' in data) {
    ow(data.age, 'age', ow.number.integer.inRange(0, 120));
  }
  
  if ('website' in data) {
    ow(data.website, 'website', ow.string.url);
  }
  
  return data;
}

// Polymorphic validation
function validateShape(shape: unknown) {
  // First determine the shape type
  ow(shape, ow.object.hasKeys('type'));
  
  const typedShape = shape as { type: string };
  
  switch (typedShape.type) {
    case 'circle':
      ow(shape, ow.object.exactShape({
        type: ow.string.equals('circle'),
        radius: ow.number.positive
      }));
      break;
      
    case 'rectangle':
      ow(shape, ow.object.exactShape({
        type: ow.string.equals('rectangle'),
        width: ow.number.positive,
        height: ow.number.positive
      }));
      break;
      
    default:
      throw new Error(`Unknown shape type: ${typedShape.type}`);
  }
}

// Recursive validation
const treeNodePredicate = ow.object.exactShape({
  id: ow.string.nonEmpty,
  value: ow.any(ow.string, ow.number),
  children: ow.optional.array.ofType(ow.object.hasKeys('id', 'value'))
});

function validateTree(node: unknown) {
  ow(node, treeNodePredicate);
  
  const typedNode = node as any;
  if (typedNode.children) {
    typedNode.children.forEach((child: unknown, index: number) => {
      validateTree(child); // Recursive validation
    });
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-ow

docs

advanced-features.md

builtin-types.md

collection-types.md

index.md

object-array-validation.md

primitive-types.md

typed-arrays.md

tile.json