/** * Invariant assertion helpers for runtime validation. * * These utilities provide type-safe assertions that narrow types and * throw descriptive errors when invariants are violated. */ /** * Custom error class for invariant violations. * Provides a distinct error type that can be caught and identified. */ export class InvariantError extends Error { constructor(message: string) { super(message); this.name = "InvariantError"; // Maintains proper stack trace in V8 environments (Node, Chrome) // captureStackTrace is V8-specific, not in standard ES const ErrorWithCapture = Error as typeof Error & { captureStackTrace?: (target: object, constructor: unknown) => void; }; if (ErrorWithCapture.captureStackTrace) { ErrorWithCapture.captureStackTrace(this, InvariantError); } } } /** * Message can be a string or a function that returns a string. * Using a function allows for lazy evaluation of expensive messages. */ export type InvariantMessage = string | (() => string); /** * Assert that a condition is truthy. Throws InvariantError if false. * * This function acts as a type guard - TypeScript will narrow the type * after the assertion. Useful for null/undefined checks. * * @param condition - The condition to check (must be truthy) * @param message - Error message or function returning message * @throws InvariantError if condition is falsy * * @example * ```ts * const user: User | null = getUser(); * invariant(user !== null, "User must be logged in"); * // TypeScript now knows user is User, not null * console.log(user.name); * ``` * * @example * ```ts * // Lazy message evaluation for performance * invariant(isValid, () => `Invalid state: ${JSON.stringify(state)}`); * ``` */ export function invariant( condition: unknown, message: InvariantMessage ): asserts condition { if (!condition) { const errorMessage = typeof message === "function" ? message() : message; throw new InvariantError(errorMessage); } } /** * Assert that a value should never be reached (exhaustive type checking). * * Use this in the default case of switch statements to ensure all * union members are handled. TypeScript will error at compile time * if a case is missing. * * @param value - The value that should be of type `never` * @returns never - This function always throws * @throws InvariantError with a description of the unexpected value * * @example * ```ts * type Status = "pending" | "complete" | "failed"; * * function handleStatus(status: Status): string { * switch (status) { * case "pending": return "Waiting..."; * case "complete": return "Done!"; * case "failed": return "Error occurred"; * default: * // If a new status is added, TypeScript will error here * return assertNever(status); * } * } * ``` */ export function assertNever(value: never): never { const description = formatValue(value); throw new InvariantError(`Unexpected value: ${description}`); } /** * Format a value for display in error messages. * Handles objects, arrays, and primitives. */ function formatValue(value: unknown): string { if (value === null) return "null"; if (value === undefined) return "undefined"; if (typeof value === "object") { try { return JSON.stringify(value); } catch { return String(value); } } return String(value); }