UNPKG

typesafe-ts

Version:

TypeScript utilities for type-safe error handling and optional values

126 lines 5.87 kB
/** * Unique symbol used to store brand identifiers on branded values. * Guarantees no conflicts with properties from other libraries or user code. */ declare const brand_symbol: unique symbol; type BrandableTypes = ((...args: unknown[]) => unknown) | object; /** * A branded type that combines a base type with a unique brand identifier. * Brands create nominal types in TypeScript, making structurally equivalent values non-assignable. * This is the type returned by `apply_brand()` / `brand.apply()`. * The runtime brand set by apply_brand/brand.apply can be used as a union discriminant at runtime. * * The brand is stored using a unique symbol (`brand.symbol`), which guarantees it will not conflict * with any properties from other tools, libraries, or your own code. * * @template T - The underlying type to be branded * @template Brand - A string literal type that serves as the unique brand identifier */ type Brand<T extends BrandableTypes, Brand extends string> = T & { readonly [brand_symbol]: Brand; }; /** * Error type for cleaner IDE error messages when attempting to brand an already branded type. * * @template T - The underlying type of the already branded value * @template BrandString - The existing brand string of the already branded type */ type TypeAlreadyBrandedError<T, BrandString extends string> = { error: `The type you've provided is already branded with the brand "${BrandString}"`; underlyingType: T; }; /** * Modifies a value by applying a runtime brand. * Brands can be used to create nominal types in TypeScript so that structurally equivalent values are not assignable. * At runtime the brand can be used to discriminate union types and works reliably even in cases where `instanceof` does not. * * The brand is stored using a unique symbol (`brand.symbol`), which guarantees it will not conflict * with any properties from other tools, libraries, or your own code. * * @param value The value to apply a brand to. * @param brand_string A string literal used to create a unique brand type and runtime brand. * @returns The input value with its type modified to include the brand, and a non-enumerable property added to the runtime value. * * @example Use a brand to create nominal types * ```ts * import { brand, type Brand } from "typesafe-ts/brand"; * * type UserId = Brand<{ id: string }, "UserId">; * type ProductId = Brand<{ id: string }, "ProductId">; * * const userId: UserId = brand.apply({ id: "user-123" }, "UserId"); * const productId: ProductId = brand.apply({ id: "prod-456" }, "ProductId"); * * function getUser(id: UserId) { ... } * * getUser(userId); // OK * getUser(productId); // Type error: ProductId is not assignable to UserId * ``` */ declare function apply_brand<T extends BrandableTypes, const BrandString extends string>(value: T extends Brand<infer UnderlyingType, infer ExistingBrand> ? TypeAlreadyBrandedError<UnderlyingType, ExistingBrand> : T, brand_string: BrandString): Brand<T, BrandString>; /** * Creates a branded error class factory. * The returned class can be extended with custom error data. * * @param brand_string A string literal used as the error name and brand identifier * @returns A class constructor that creates branded Error instances * @example Create discriminated error types for validation * ```ts * import { brand } from "typesafe-ts/brand"; * * class TooShortError extends brand.Error("PasswordTooShort")<{ minLength: number }> {} * class NoNumberError extends brand.Error("PasswordNoNumber") {} * * function validatePassword(password: string) { * if (password.length < 8) return new TooShortError({ minLength: 8 }); * if (!/\d/.test(password)) return new NoNumberError(); * return null; * } * * const validationError = validatePassword("short"); * if (!validationError) return; * * const errorBrand = validationError[brand.symbol]; * switch (errorBrand) { * case "PasswordTooShort": * // validationError is narrowed to TooShortError * console.log(`Password must be at least ${validationError.minLength} characters`); * break; * case "PasswordNoNumber": * // validationError is narrowed to NoNumberError * console.log("Password must contain a number"); * break; * } * ``` */ declare function branded_error<const BrandString extends string>(brand_string: BrandString): new <ErrorData extends Record<string, unknown> = Record<string, never>>(args: [Record<string, never>] extends [ErrorData] ? void | { message?: string; cause?: unknown; } : { [Key in keyof ErrorData as Key extends keyof Error ? never : Key]: ErrorData[Key]; } & { message?: string; cause?: unknown; }) => Error & { [Key in keyof ErrorData as Key extends keyof Error ? never : Key]: ErrorData[Key]; } & { message?: string; cause?: unknown; } & { readonly [brand_symbol]: BrandString; }; /** * Factory namespace for creating and managing branded types. * Provides utilities to apply runtime brands to values, create discriminated error types, * and access the brand symbol for type narrowing in union discriminants. * * @property apply - Applies a runtime brand to a value, creating a nominal type * @property symbol - A unique symbol used for brands. Guaranteed not to conflict with other properties * @property Error - Factory for branded error classes that can be reliably discriminated at runtime. */ declare const brand: { /** Applies a runtime brand to a value, creating a nominal type. */ apply: typeof apply_brand; /** Unique symbol used to store and access brand identifiers. */ symbol: symbol; /** Creates a branded error class factory for discriminated error handling. */ Error: typeof branded_error; }; export { brand, type Brand, apply_brand, brand_symbol, branded_error }; //# sourceMappingURL=brand.d.ts.map