// --- examples --- // // happy path const Priority = Enum("Low", "Normal", "High"); // ^? type Priority = Enum; // ^? // invalid key starting with number const InvalidPriority1 = Enum("1Low", "Normal", "High"); // ^? type InvalidPriority1 = Enum; // ^? // invalid key with special character const InvalidPriority2 = Enum("Low", "Normal", "H!gh"); // ^? type InvalidPriority2 = Enum; // ^? // invalid duplicate key const InvalidPriority3 = Enum("Low", "Normal", "Low"); // ^? type InvalidPriority3 = Enum; // ^? const Status = Enum("Status", { Open: "o", Closed: "c" }); // ^? type Status = Enum; // ^? // --- implementation --- // /** creates a numeric enum */ function Enum( k0: ValidateEnumKey, ...keys: UniqueAndValid ): Prettify>; /** creates a string enum */ function Enum, const B extends string>( name: B, t: T, ): Prettify>; // biome-ignore lint/suspicious/noExplicitAny: i like to party function Enum(...args: any[]) { if (typeof args[0] === "string" && typeof args[1] === "object") { return args[1]; } return NumericEnum(args[0], ...(args.slice(1) as never)); } // biome-ignore lint/suspicious/noRedeclare: intentionally redeclaring Enum type Enum = [T] extends [never] ? TSError<"Invalid Enum"> : T extends Record ? StringEnum : T extends Record ? NumericEnum : never; declare const TS_ERROR: unique symbol; /** used for showing errors in typescript */ type TSError = Satistifes & { [TS_ERROR]: Message; }; /** prettifies a type, so that it shows the underlying type instead of the type alias */ type Prettify = { [K in keyof T]: T[K] } & {}; /** splits a string into tuple of characters */ type StringToTuple = S extends `${infer F}${infer R}` ? [F, ...StringToTuple] : []; /** checks if a value is in a tuple */ type Includes = T extends [ infer First, ...infer Rest, ] ? First extends U ? true : Includes : false; /** checks if string includes character */ type StringIncludes = Includes< StringToTuple, Char >; /** checks if string starts with another string */ type StartsWith = T extends `${U}${string}` ? true : false; // biome-ignore format: long union type would be many lines type AlphaLower = "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 AlphaUpper = Uppercase; type Alpha = AlphaLower | AlphaUpper; /** valid starting characters for an enum key */ type ValidStart = Alpha | "_" | "$"; // biome-ignore format: long union type would be many lines /** a non-exhaustive list of special characters that are invalid in enum keys */ type InvalidSpecialChars = " " | "!" | "@" | "#" | "%" | "^" | "&" | "*" | "(" | ")" | "-" | "=" | "+" | "[" | "]" | "{" | "}" | ";" | ":" | "'" | '"' | "," | "." | "<" | ">" | "/" | "?" | "\\" | "|" | "`" | "~"; /** gets the index of a value in a tuple */ type IndexOf = T extends [ ...infer Rest, infer Last, ] ? V extends Last ? Rest["length"] : IndexOf : never; /** validates uniqueness of a tuple */ type IsUnique< T extends readonly unknown[], Cache extends readonly unknown[] = [], > = T extends [infer First, ...infer Rest] ? Includes extends true ? false : IsUnique : true; /** validates a key in an enum, returning a custom ts error if invalid */ type ValidateEnumKey = StartsWith extends true ? StringIncludes extends false ? T : TSError<"Keys must not contain invalid special characters"> : TSError<"Keys must start with a letter, underscore (_), or dollar sign ($)">; /** checks if all keys in a tuple are valid enum keys */ type AllKeysValid = T extends [ infer Head extends string, ...infer Tail extends readonly string[], ] ? ValidateEnumKey extends string ? AllKeysValid : false : true; // biome-ignore format: uglier w/ formatting /** checks if all keys in a tuple are unique and valid */ type UniqueAndValid = IsUnique<[K0, ...T]> extends true ? AllKeysValid<[K0, ...T]> extends true ? T : TSError< "One or more keys are invalid. Keys must start with a letter, underscore (_), or dollar sign ($), and not contain other special characters", readonly string[] > : TSError<"Keys must be unique", readonly string[]>; /** return type for numeric enum function */ type NumericEnumReturn< K0 extends string, T extends readonly string[], > = UniqueAndValid extends T ? { readonly [K in [K0, ...T][number]]: IndexOf<[K0, ...T], K> } : never; function NumericEnum< const K0 extends string, const T extends readonly string[], >( k0: ValidateEnumKey, ...keys: UniqueAndValid ): Prettify> { const x = Object.create(null); x[k0] = 0; for (const k of keys) { x[k] = keys.indexOf(k) + 1; } return x; } type NumericEnum> = Extract< T[keyof T], number >; declare const VARIANT: unique symbol; /** a branded type for variants of a string enum */ type Variant = T & { [VARIANT]: K }; /** return type for string enum function */ type StringEnumReturn, B extends string> = { [K in keyof T]: Variant<`${B}.${K extends string ? K : never}`, T[K]>; }; function StringEnum< const T extends Record, const B extends string, >(_name: B, t: T): StringEnumReturn { return t as never; } /** union of all string enum values */ type StringEnum> = Extract;