typesafe-ts
Version:
TypeScript utilities for type-safe error handling and optional values
126 lines • 5.87 kB
TypeScript
/**
* 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