/** * Factory API for creating tagged errors with $variable interpolation in messages. * * Extracts variable names from the message template and requires them in the constructor. * Use `class X extends createTaggedError({...}) {}` so X is both a value and a type. * * @example * class NotFoundError extends createTaggedError({ * name: 'NotFoundError', * message: 'User $id not found in $database' * }) {} * * throw new NotFoundError({ id: '123', database: 'users' }) * // err._tag = 'NotFoundError' * // err.message = 'User 123 not found in users' * // err.id = '123' * // err.database = 'users' */ type Alpha = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '_'; type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; type AlphaNum = Alpha | Digit; /** * Recursively consume valid identifier characters to extract a variable name. * Returns [extractedVar, remainingString] as a tuple encoded in an object. */ type ConsumeVar = S extends `${infer C}${infer Rest}` ? C extends AlphaNum ? ConsumeVar : { var: Acc; rest: S; } : { var: Acc; rest: ''; }; /** * Extract variable names from a message template containing $variable placeholders. * Only extracts valid identifiers (alphanumeric + underscore). */ type ExtractVars = S extends `${string}$${infer AfterDollar}` ? AfterDollar extends `${Alpha}${string}` ? ConsumeVar extends { var: infer V extends string; rest: infer R extends string; } ? V extends '' ? ExtractVars : V | ExtractVars : never : ExtractVars : never; /** * Build props type from extracted variable names. * Each variable becomes a required property accepting string or number. */ type VarProps = ExtractVars extends never ? {} : { [K in ExtractVars]: string | number; }; /** * Props with optional cause for error chaining. */ type PropsWithCause = VarProps & { cause?: unknown; }; /** * Any class that extends Error */ type ErrorClass = new (...args: any[]) => Error; /** * Instance type for factory-created tagged errors. */ export type FactoryTaggedErrorInstance = Base & { readonly _tag: Tag; readonly message: string; /** The original message template with $variable placeholders (e.g. 'User $id not found') */ readonly messageTemplate: Msg; /** Stable fingerprint for error grouping in Sentry/logging. Returns [_tag, messageTemplate]. */ readonly fingerprint: readonly [Tag, Msg]; toJSON(): object; /** Walk the .cause chain to find an ancestor matching a specific error class. */ findCause(ErrorClass: new (...args: any[]) => T): T | undefined; } & Readonly>; /** * Class type returned by createTaggedError factory. */ export type FactoryTaggedErrorClass = { new (...args: ExtractVars extends never ? [args?: { cause?: unknown; }] : [args: PropsWithCause]): FactoryTaggedErrorInstance; /** Type guard for this error class */ is(value: unknown): value is FactoryTaggedErrorInstance; /** The tag/name of this error class */ readonly tag: Tag; }; /** * Factory for creating tagged error classes with $variable message interpolation. * * Variables in the message template (prefixed with $) are automatically extracted * and required in the constructor. They are interpolated into the message and * also available as properties on the error instance. * * Use `class X extends createTaggedError({...}) {}` pattern so the error is both * a value and a type (no need for `InstanceType`). * * @example * class NotFoundError extends createTaggedError({ * name: 'NotFoundError', * message: 'User $id not found in $database' * }) {} * * const err = new NotFoundError({ id: '123', database: 'users' }) * err._tag // 'NotFoundError' * err.message // 'User 123 not found in users' * err.id // '123' * err.database // 'users' * * // Type guard * NotFoundError.is(err) // true * * @example * // Error without variables * class EmptyError extends createTaggedError({ * name: 'EmptyError', * message: 'Something went wrong' * }) {} * * throw new EmptyError() // no args required * * @example * // Message omitted — caller provides message at construction time * class AppError extends createTaggedError({ * name: 'AppError', * }) {} * * throw new AppError({ message: 'something went wrong' }) * // err.fingerprint is stable across different messages * * @example * // With cause for error chaining * class WrapperError extends createTaggedError({ * name: 'WrapperError', * message: 'Failed to process $item' * }) {} * * try { * // ... * } catch (e) { * throw new WrapperError({ item: 'data', cause: e }) * } * * @example * // With custom base class * class AppError extends Error { * statusCode = 500 * report() { return `[${this.statusCode}] ${this.message}` } * } * * class NotFoundError extends createTaggedError({ * name: 'NotFoundError', * message: 'Resource $id not found', * extends: AppError * }) {} * * const err = new NotFoundError({ id: '123' }) * err.statusCode // 500 (inherited) * err.report() // works */ export declare function createTaggedError(opts: { name: Name; message?: Msg; extends?: BaseClass; }): FactoryTaggedErrorClass>; export {}; //# sourceMappingURL=factory.d.ts.map