@digitaldefiance/branded-enum 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -159,8 +159,7 @@ function createBrandedEnum<T extends Record<string, string>>(
159
159
 
160
160
  - **enumId**: Unique identifier for this enum
161
161
  - **values**: Object with key-value pairs (use `as const` for literal types)
162
- - **Returns**: Frozen branded enum object
163
- - **Throws**: `Error` if enumId is already registered
162
+ - **Returns**: Frozen branded enum object (returns existing enum if ID already registered)
164
163
 
165
164
  ### Type Guards
166
165
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/advanced.ts"],"sourcesContent":["/**\n * Advanced enum operations for branded enums.\n *\n * Provides functions for deriving new enums from existing ones,\n * including subsetting, exclusion, and transformation operations.\n */\n\nimport { AnyBrandedEnum, BrandedEnum, ENUM_ID, ENUM_VALUES, EnumKeys, EnumValues } from './types.js';\nimport { createBrandedEnum } from './factory.js';\n\n/**\n * Checks if an object is a branded enum (has Symbol metadata).\n *\n * @param obj - The object to check\n * @returns true if obj is a branded enum\n */\nfunction isBrandedEnum(obj: unknown): obj is AnyBrandedEnum {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n ENUM_ID in obj &&\n ENUM_VALUES in obj &&\n typeof (obj as AnyBrandedEnum)[ENUM_ID] === 'string' &&\n (obj as AnyBrandedEnum)[ENUM_VALUES] instanceof Set\n );\n}\n\n/**\n * Creates a new branded enum containing only the specified keys from the source enum.\n *\n * This function derives a subset of an existing branded enum by selecting specific keys.\n * The resulting enum is registered as an independent enum in the global registry.\n *\n * Type safety is maintained - the resulting enum's type reflects only the selected keys.\n *\n * @template E - The source branded enum type\n * @template K - The keys to include in the subset (must be keys of E)\n * @param newId - Unique identifier for the new subset enum. Must not already be registered.\n * @param sourceEnum - The branded enum to derive the subset from\n * @param keys - Array of keys to include in the subset. All keys must exist in sourceEnum.\n * @returns A new branded enum containing only the specified key-value pairs\n * @throws {Error} Throws `Error` with message `enumSubset requires a branded enum as the source`\n * if sourceEnum is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `enumSubset requires at least one key`\n * if keys array is empty.\n * @throws {Error} Throws `Error` with message `Key \"${key}\" does not exist in enum \"${enumId}\"`\n * if any specified key does not exist in the source enum.\n * @throws {Error} Throws `Error` with message `Branded enum with ID \"${newId}\" already exists`\n * if newId is already registered.\n *\n * @example\n * // Basic usage - create a subset of colors\n * const AllColors = createBrandedEnum('all-colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * Yellow: 'yellow',\n * } as const);\n *\n * const PrimaryColors = enumSubset('primary-colors', AllColors, ['Red', 'Blue', 'Yellow']);\n * // PrimaryColors has: Red, Blue, Yellow (no Green)\n *\n * PrimaryColors.Red; // 'red'\n * PrimaryColors.Blue; // 'blue'\n * // PrimaryColors.Green; // TypeScript error - Green doesn't exist\n *\n * @example\n * // Type safety with subset\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * Archived: 'archived',\n * } as const);\n *\n * const ActiveStatuses = enumSubset('active-statuses', Status, ['Active', 'Pending']);\n * type ActiveStatusValue = typeof ActiveStatuses[keyof typeof ActiveStatuses];\n * // ActiveStatusValue = 'active' | 'pending'\n *\n * @example\n * // Error handling for invalid keys\n * try {\n * enumSubset('invalid', AllColors, ['Red', 'Purple']); // Purple doesn't exist\n * } catch (e) {\n * console.log(e.message);\n * // 'Key \"Purple\" does not exist in enum \"all-colors\"'\n * }\n */\nexport function enumSubset<\n E extends AnyBrandedEnum,\n K extends keyof E & string,\n>(\n newId: string,\n sourceEnum: E,\n keys: readonly K[]\n): BrandedEnum<Pick<E, K> & Record<string, string>> {\n // Validate that sourceEnum is a branded enum\n if (!isBrandedEnum(sourceEnum)) {\n throw new Error('enumSubset requires a branded enum as the source');\n }\n\n // Validate that keys array is not empty\n if (keys.length === 0) {\n throw new Error('enumSubset requires at least one key');\n }\n\n const sourceEnumId = sourceEnum[ENUM_ID];\n\n // Build the subset values object\n const subsetValues: Record<string, string> = {};\n\n for (const key of keys) {\n // Validate that the key exists in the source enum\n if (!(key in sourceEnum)) {\n throw new Error(\n `Key \"${key}\" does not exist in enum \"${sourceEnumId}\"`\n );\n }\n\n // Copy the key-value pair\n subsetValues[key] = (sourceEnum as Record<string, string>)[key];\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, subsetValues) as BrandedEnum<\n Pick<E, K> & Record<string, string>\n >;\n}\n\n/**\n * Creates a new branded enum by excluding the specified keys from the source enum.\n *\n * This function derives a new enum from an existing branded enum by removing specific keys.\n * It is the complement of `enumSubset` - instead of specifying which keys to include,\n * you specify which keys to exclude.\n *\n * The resulting enum is registered as an independent enum in the global registry.\n * Type safety is maintained - the resulting enum's type reflects only the remaining keys.\n *\n * @template E - The source branded enum type\n * @template K - The keys to exclude from the result (must be keys of E)\n * @param newId - Unique identifier for the new enum. Must not already be registered.\n * @param sourceEnum - The branded enum to derive from\n * @param keysToExclude - Array of keys to exclude. All keys must exist in sourceEnum.\n * @returns A new branded enum containing all key-value pairs except the excluded ones\n * @throws {Error} Throws `Error` with message `enumExclude requires a branded enum as the source`\n * if sourceEnum is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `enumExclude: excluding all keys would result in an empty enum`\n * if excluding all keys would leave no keys remaining.\n * @throws {Error} Throws `Error` with message `Key \"${key}\" does not exist in enum \"${enumId}\"`\n * if any specified key to exclude does not exist in the source enum.\n * @throws {Error} Throws `Error` with message `Branded enum with ID \"${newId}\" already exists`\n * if newId is already registered.\n *\n * @example\n * // Basic usage - exclude specific colors\n * const AllColors = createBrandedEnum('all-colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * Yellow: 'yellow',\n * } as const);\n *\n * const NonPrimaryColors = enumExclude('non-primary', AllColors, ['Red', 'Blue', 'Yellow']);\n * // NonPrimaryColors has only: Green\n *\n * NonPrimaryColors.Green; // 'green'\n * // NonPrimaryColors.Red; // TypeScript error - Red was excluded\n *\n * @example\n * // Exclude deprecated values\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * Deprecated: 'deprecated',\n * } as const);\n *\n * const CurrentStatuses = enumExclude('current-statuses', Status, ['Deprecated']);\n * type CurrentStatusValue = typeof CurrentStatuses[keyof typeof CurrentStatuses];\n * // CurrentStatusValue = 'active' | 'inactive' | 'pending'\n *\n * @example\n * // Error handling for invalid keys\n * try {\n * enumExclude('invalid', AllColors, ['Purple']); // Purple doesn't exist\n * } catch (e) {\n * console.log(e.message);\n * // 'Key \"Purple\" does not exist in enum \"all-colors\"'\n * }\n */\nexport function enumExclude<\n E extends AnyBrandedEnum,\n K extends keyof E & string,\n>(\n newId: string,\n sourceEnum: E,\n keysToExclude: readonly K[]\n): BrandedEnum<Omit<E, K> & Record<string, string>> {\n // Validate that sourceEnum is a branded enum\n if (!isBrandedEnum(sourceEnum)) {\n throw new Error('enumExclude requires a branded enum as the source');\n }\n\n const sourceEnumId = sourceEnum[ENUM_ID];\n\n // Validate that all keys to exclude exist in the source enum\n for (const key of keysToExclude) {\n if (!(key in sourceEnum)) {\n throw new Error(\n `Key \"${key}\" does not exist in enum \"${sourceEnumId}\"`\n );\n }\n }\n\n // Create a Set for O(1) lookup of excluded keys\n const excludeSet = new Set<string>(keysToExclude);\n\n // Get all keys from the source enum (excluding Symbol metadata)\n const allKeys = Object.keys(sourceEnum);\n\n // Build the result values object with non-excluded keys\n const resultValues: Record<string, string> = {};\n\n for (const key of allKeys) {\n if (!excludeSet.has(key)) {\n resultValues[key] = (sourceEnum as Record<string, string>)[key];\n }\n }\n\n // Validate that we have at least one key remaining\n if (Object.keys(resultValues).length === 0) {\n throw new Error(\n 'enumExclude: excluding all keys would result in an empty enum'\n );\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, resultValues) as BrandedEnum<\n Omit<E, K> & Record<string, string>\n >;\n}\n\n/**\n * Type representing the result of mapping enum values through a transform function.\n * Preserves the keys but transforms the value types.\n */\ntype MappedEnumValues<E extends Record<string, string>> = {\n [K in keyof E]: string;\n};\n\n/**\n * Creates a new branded enum by transforming all values through a mapper function.\n *\n * This function derives a new enum from an existing branded enum by applying a\n * transformation function to each value. The keys remain unchanged, but the values\n * are transformed according to the provided mapper.\n *\n * Common use cases include:\n * - Prefixing values (e.g., adding a namespace)\n * - Suffixing values (e.g., adding a version)\n * - Case transformation (e.g., uppercase, lowercase)\n * - Custom transformations (e.g., encoding, formatting)\n *\n * The resulting enum is registered as an independent enum in the global registry.\n *\n * @template E - The source branded enum type\n * @param newId - Unique identifier for the new enum. Must not already be registered.\n * @param sourceEnum - The branded enum to derive from\n * @param mapper - Function that transforms each value. Receives the original value\n * and the key, and returns the transformed value.\n * @returns A new branded enum with transformed values\n * @throws {Error} Throws `Error` with message `enumMap requires a branded enum as the source`\n * if sourceEnum is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `enumMap mapper must return a string`\n * if the mapper function returns a non-string value.\n * @throws {Error} Throws `Error` with message `Branded enum with ID \"${newId}\" already exists`\n * if newId is already registered.\n *\n * @example\n * // Prefix all values with a namespace\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const PrefixedStatus = enumMap('prefixed-status', Status, (value) => `app.${value}`);\n * // PrefixedStatus.Active === 'app.active'\n * // PrefixedStatus.Inactive === 'app.inactive'\n *\n * @example\n * // Uppercase all values\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * const UpperColors = enumMap('upper-colors', Colors, (value) => value.toUpperCase());\n * // UpperColors.Red === 'RED'\n * // UpperColors.Green === 'GREEN'\n * // UpperColors.Blue === 'BLUE'\n *\n * @example\n * // Transform with key context\n * const Sizes = createBrandedEnum('sizes', {\n * Small: 's',\n * Medium: 'm',\n * Large: 'l',\n * } as const);\n *\n * const VerboseSizes = enumMap('verbose-sizes', Sizes, (value, key) => `${key.toLowerCase()}-${value}`);\n * // VerboseSizes.Small === 'small-s'\n * // VerboseSizes.Medium === 'medium-m'\n * // VerboseSizes.Large === 'large-l'\n */\nexport function enumMap<E extends AnyBrandedEnum>(\n newId: string,\n sourceEnum: E,\n mapper: (value: string, key: string) => string\n): BrandedEnum<MappedEnumValues<E> & Record<string, string>> {\n // Validate that sourceEnum is a branded enum\n if (!isBrandedEnum(sourceEnum)) {\n throw new Error('enumMap requires a branded enum as the source');\n }\n\n // Get all keys from the source enum (excluding Symbol metadata)\n const allKeys = Object.keys(sourceEnum);\n\n // Build the result values object with transformed values\n const resultValues: Record<string, string> = {};\n\n for (const key of allKeys) {\n const originalValue = sourceEnum[key as keyof E] as string;\n const transformedValue = mapper(originalValue, key);\n\n // Validate that the mapper returned a string\n if (typeof transformedValue !== 'string') {\n throw new Error('enumMap mapper must return a string');\n }\n\n resultValues[key] = transformedValue;\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, resultValues) as BrandedEnum<\n MappedEnumValues<E> & Record<string, string>\n >;\n}\n\n/**\n * Type representing an enum where each key maps to itself as a value.\n */\ntype KeysAsValues<K extends readonly string[]> = {\n [P in K[number]]: P;\n};\n\n/**\n * Creates a branded enum from an array of keys where each key equals its value.\n *\n * This is a convenience function for the common pattern where enum keys and values\n * are identical, such as `{ Active: 'Active', Inactive: 'Inactive' }`.\n *\n * The resulting enum is registered as an independent enum in the global registry.\n * Type safety is maintained - the resulting enum's type reflects the exact literal\n * types of the provided keys.\n *\n * @template K - The array of string keys (use `as const` for literal types)\n * @param enumId - Unique identifier for the new enum. Must not already be registered.\n * @param keys - Array of strings that will become both keys and values.\n * Use `as const` for literal type inference.\n * @returns A new branded enum where each key maps to itself\n * @throws {Error} Throws `Error` with message `enumFromKeys requires at least one key`\n * if keys array is empty.\n * @throws {Error} Throws `Error` with message `enumFromKeys requires all keys to be non-empty strings`\n * if any key is not a non-empty string.\n * @throws {Error} Throws `Error` with message `enumFromKeys: duplicate key \"${key}\" found`\n * if the keys array contains duplicates.\n * @throws {Error} Throws `Error` with message `Branded enum with ID \"${enumId}\" already exists`\n * if enumId is already registered.\n *\n * @example\n * // Basic usage - create enum from string array\n * const Status = enumFromKeys('status', ['Active', 'Inactive', 'Pending'] as const);\n * // Equivalent to: { Active: 'Active', Inactive: 'Inactive', Pending: 'Pending' }\n *\n * Status.Active; // 'Active'\n * Status.Inactive; // 'Inactive'\n * Status.Pending; // 'Pending'\n *\n * @example\n * // Type inference with as const\n * const Colors = enumFromKeys('colors', ['Red', 'Green', 'Blue'] as const);\n * type ColorValue = typeof Colors[keyof typeof Colors];\n * // ColorValue = 'Red' | 'Green' | 'Blue'\n *\n * @example\n * // Useful for string literal unions\n * const Directions = enumFromKeys('directions', ['North', 'South', 'East', 'West'] as const);\n *\n * function move(direction: typeof Directions[keyof typeof Directions]) {\n * // direction is 'North' | 'South' | 'East' | 'West'\n * }\n *\n * move(Directions.North); // OK\n * move('North'); // Also OK due to literal type\n *\n * @example\n * // Error handling\n * try {\n * enumFromKeys('empty', []); // Empty array\n * } catch (e) {\n * console.log(e.message);\n * // 'enumFromKeys requires at least one key'\n * }\n */\nexport function enumFromKeys<K extends readonly string[]>(\n enumId: string,\n keys: K\n): BrandedEnum<KeysAsValues<K> & Record<string, string>> {\n // Validate that keys array is not empty\n if (keys.length === 0) {\n throw new Error('enumFromKeys requires at least one key');\n }\n\n // Track seen keys to detect duplicates\n const seenKeys = new Set<string>();\n\n // Build the values object where each key maps to itself\n const values: Record<string, string> = {};\n\n for (const key of keys) {\n // Validate that each key is a non-empty string\n if (typeof key !== 'string' || key.length === 0) {\n throw new Error('enumFromKeys requires all keys to be non-empty strings');\n }\n\n // Check for duplicate keys\n if (seenKeys.has(key)) {\n throw new Error(`enumFromKeys: duplicate key \"${key}\" found`);\n }\n\n seenKeys.add(key);\n values[key] = key;\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(enumId, values) as BrandedEnum<\n KeysAsValues<K> & Record<string, string>\n >;\n}\n\n/**\n * Result of comparing two branded enums with enumDiff.\n */\nexport interface EnumDiffResult {\n /** Keys that exist only in the first enum */\n readonly onlyInFirst: ReadonlyArray<{ key: string; value: string }>;\n /** Keys that exist only in the second enum */\n readonly onlyInSecond: ReadonlyArray<{ key: string; value: string }>;\n /** Keys that exist in both enums but have different values */\n readonly differentValues: ReadonlyArray<{\n key: string;\n firstValue: string;\n secondValue: string;\n }>;\n /** Keys that exist in both enums with the same values */\n readonly sameValues: ReadonlyArray<{ key: string; value: string }>;\n}\n\n/**\n * Compares two branded enums and returns their differences.\n *\n * This function analyzes two branded enums and categorizes their keys into:\n * - Keys only in the first enum\n * - Keys only in the second enum\n * - Keys in both with different values\n * - Keys in both with the same values\n *\n * Useful for:\n * - Migration: Identifying what changed between enum versions\n * - Debugging: Understanding differences between similar enums\n * - Validation: Ensuring enums have expected overlap or differences\n *\n * @template E1 - The first branded enum type\n * @template E2 - The second branded enum type\n * @param firstEnum - The first branded enum to compare\n * @param secondEnum - The second branded enum to compare\n * @returns An EnumDiffResult object containing categorized differences\n * @throws {Error} Throws `Error` with message `enumDiff requires branded enums as arguments`\n * if either argument is not a valid branded enum.\n *\n * @example\n * // Compare two versions of a status enum\n * const StatusV1 = createBrandedEnum('status-v1', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const StatusV2 = createBrandedEnum('status-v2', {\n * Active: 'active',\n * Inactive: 'disabled', // Changed value\n * Pending: 'pending', // New key\n * } as const);\n *\n * const diff = enumDiff(StatusV1, StatusV2);\n * // diff.onlyInFirst: []\n * // diff.onlyInSecond: [{ key: 'Pending', value: 'pending' }]\n * // diff.differentValues: [{ key: 'Inactive', firstValue: 'inactive', secondValue: 'disabled' }]\n * // diff.sameValues: [{ key: 'Active', value: 'active' }]\n *\n * @example\n * // Find keys removed between versions\n * const ColorsOld = createBrandedEnum('colors-old', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * const ColorsNew = createBrandedEnum('colors-new', {\n * Red: 'red',\n * Blue: 'blue',\n * } as const);\n *\n * const diff = enumDiff(ColorsOld, ColorsNew);\n * // diff.onlyInFirst: [{ key: 'Green', value: 'green' }]\n * // diff.onlyInSecond: []\n *\n * @example\n * // Check if enums are identical\n * const diff = enumDiff(enumA, enumB);\n * const areIdentical = diff.onlyInFirst.length === 0 &&\n * diff.onlyInSecond.length === 0 &&\n * diff.differentValues.length === 0;\n */\nexport function enumDiff(\n firstEnum: AnyBrandedEnum,\n secondEnum: AnyBrandedEnum\n): EnumDiffResult {\n // Validate that both arguments are branded enums\n if (!isBrandedEnum(firstEnum) || !isBrandedEnum(secondEnum)) {\n throw new Error('enumDiff requires branded enums as arguments');\n }\n\n // Get all keys from both enums (excluding Symbol metadata)\n const firstKeys = new Set(Object.keys(firstEnum));\n const secondKeys = new Set(Object.keys(secondEnum));\n\n const onlyInFirst: Array<{ key: string; value: string }> = [];\n const onlyInSecond: Array<{ key: string; value: string }> = [];\n const differentValues: Array<{\n key: string;\n firstValue: string;\n secondValue: string;\n }> = [];\n const sameValues: Array<{ key: string; value: string }> = [];\n\n // Find keys only in first enum and keys in both\n for (const key of firstKeys) {\n const firstValue = (firstEnum as Record<string, string>)[key];\n\n if (!secondKeys.has(key)) {\n // Key only exists in first enum\n onlyInFirst.push({ key, value: firstValue });\n } else {\n // Key exists in both - compare values\n const secondValue = (secondEnum as Record<string, string>)[key];\n\n if (firstValue === secondValue) {\n sameValues.push({ key, value: firstValue });\n } else {\n differentValues.push({ key, firstValue, secondValue });\n }\n }\n }\n\n // Find keys only in second enum\n for (const key of secondKeys) {\n if (!firstKeys.has(key)) {\n const secondValue = (secondEnum as Record<string, string>)[key];\n onlyInSecond.push({ key, value: secondValue });\n }\n }\n\n return {\n onlyInFirst,\n onlyInSecond,\n differentValues,\n sameValues,\n };\n}\n\n/**\n * Result entry for a shared value found across multiple enums.\n */\nexport interface EnumIntersectEntry {\n /** The shared value that exists in multiple enums */\n readonly value: string;\n /** Array of enum IDs that contain this value */\n readonly enumIds: readonly string[];\n}\n\n/**\n * Finds values that exist in multiple branded enums.\n *\n * This function analyzes multiple branded enums and identifies values that\n * appear in more than one enum. For each shared value, it returns the value\n * along with the IDs of all enums that contain it.\n *\n * Useful for:\n * - Detecting value collisions across enums\n * - Finding common values for potential refactoring\n * - Debugging i18n key conflicts\n * - Identifying intentional value overlaps\n *\n * @param enums - Array of branded enums to analyze for intersections\n * @returns Array of EnumIntersectEntry objects, each containing a shared value\n * and the IDs of enums containing that value. Only values appearing in 2+\n * enums are included. Results are sorted by value for consistent ordering.\n * @throws {Error} Throws `Error` with message `enumIntersect requires at least two branded enums`\n * if fewer than two enums are provided.\n * @throws {Error} Throws `Error` with message `enumIntersect requires all arguments to be branded enums`\n * if any argument is not a valid branded enum.\n *\n * @example\n * // Find shared values between color enums\n * const PrimaryColors = createBrandedEnum('primary', {\n * Red: 'red',\n * Blue: 'blue',\n * Yellow: 'yellow',\n * } as const);\n *\n * const WarmColors = createBrandedEnum('warm', {\n * Red: 'red',\n * Orange: 'orange',\n * Yellow: 'yellow',\n * } as const);\n *\n * const shared = enumIntersect(PrimaryColors, WarmColors);\n * // shared = [\n * // { value: 'red', enumIds: ['primary', 'warm'] },\n * // { value: 'yellow', enumIds: ['primary', 'warm'] }\n * // ]\n *\n * @example\n * // Detect i18n key collisions across multiple libraries\n * const LibAKeys = createBrandedEnum('lib-a', {\n * Submit: 'submit',\n * Cancel: 'cancel',\n * } as const);\n *\n * const LibBKeys = createBrandedEnum('lib-b', {\n * Submit: 'submit',\n * Reset: 'reset',\n * } as const);\n *\n * const LibCKeys = createBrandedEnum('lib-c', {\n * Submit: 'submit',\n * Clear: 'clear',\n * } as const);\n *\n * const collisions = enumIntersect(LibAKeys, LibBKeys, LibCKeys);\n * // collisions = [\n * // { value: 'submit', enumIds: ['lib-a', 'lib-b', 'lib-c'] }\n * // ]\n *\n * @example\n * // Check if enums have any overlap\n * const shared = enumIntersect(enumA, enumB);\n * if (shared.length === 0) {\n * console.log('No shared values between enums');\n * }\n */\nexport function enumIntersect(\n ...enums: AnyBrandedEnum[]\n): EnumIntersectEntry[] {\n // Validate that at least two enums are provided\n if (enums.length < 2) {\n throw new Error('enumIntersect requires at least two branded enums');\n }\n\n // Validate that all arguments are branded enums\n for (const enumObj of enums) {\n if (!isBrandedEnum(enumObj)) {\n throw new Error('enumIntersect requires all arguments to be branded enums');\n }\n }\n\n // Build a map of value -> Set of enum IDs containing that value\n const valueToEnumIds = new Map<string, Set<string>>();\n\n for (const enumObj of enums) {\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n\n for (const value of values) {\n if (!valueToEnumIds.has(value)) {\n valueToEnumIds.set(value, new Set());\n }\n valueToEnumIds.get(value)!.add(enumId);\n }\n }\n\n // Filter to only values that appear in multiple enums\n const result: EnumIntersectEntry[] = [];\n\n for (const [value, enumIds] of valueToEnumIds) {\n if (enumIds.size >= 2) {\n result.push({\n value,\n enumIds: Array.from(enumIds).sort(),\n });\n }\n }\n\n // Sort by value for consistent ordering\n result.sort((a, b) => a.value.localeCompare(b.value));\n\n return result;\n}\n\n/**\n * Converts a branded enum to a plain Record object, stripping all metadata.\n *\n * This function creates a new plain object containing only the key-value pairs\n * from the branded enum, without any Symbol metadata properties. The result is\n * a simple `Record<string, string>` that can be safely serialized, spread, or\n * used in contexts where branded enum metadata is not needed.\n *\n * Useful for:\n * - Serialization scenarios where you need a plain object\n * - Interoperability with APIs that expect plain objects\n * - Creating snapshots of enum state without metadata\n * - Passing enum data to external systems\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to convert\n * @returns A plain Record<string, string> containing only the key-value pairs\n * @throws {Error} Throws `Error` with message `enumToRecord requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Basic usage - convert to plain object\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const plainStatus = enumToRecord(Status);\n * // plainStatus = { Active: 'active', Inactive: 'inactive', Pending: 'pending' }\n * // No Symbol metadata, just a plain object\n *\n * @example\n * // Serialization scenario\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * // Send to an API that expects plain objects\n * const payload = {\n * availableColors: enumToRecord(Colors),\n * };\n * await fetch('/api/config', {\n * method: 'POST',\n * body: JSON.stringify(payload),\n * });\n *\n * @example\n * // Comparing with spread operator\n * const Status = createBrandedEnum('status', { Active: 'active' } as const);\n *\n * // Both produce the same result for enumerable properties:\n * const spread = { ...Status };\n * const record = enumToRecord(Status);\n * // spread and record are equivalent plain objects\n *\n * // But enumToRecord is explicit about intent and validates input\n *\n * @example\n * // Type safety\n * const Sizes = createBrandedEnum('sizes', {\n * Small: 's',\n * Medium: 'm',\n * Large: 'l',\n * } as const);\n *\n * const record = enumToRecord(Sizes);\n * // record type is Record<string, string>\n * // Can be used anywhere a plain object is expected\n */\nexport function enumToRecord<E extends AnyBrandedEnum>(\n enumObj: E\n): Record<string, string> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('enumToRecord requires a branded enum');\n }\n\n // Create a new plain object with only the enumerable key-value pairs\n const result: Record<string, string> = {};\n\n for (const key of Object.keys(enumObj)) {\n result[key] = enumObj[key as keyof E] as string;\n }\n\n return result;\n}\n\n/**\n * The key used to store the enum watcher registry on globalThis.\n * Namespaced to avoid collisions with other libraries.\n */\nconst WATCHER_REGISTRY_KEY = '__brandedEnumWatcherRegistry__' as const;\n\n/**\n * Type of access event that triggered the callback.\n */\nexport type EnumAccessType = 'get' | 'has' | 'keys' | 'values' | 'entries';\n\n/**\n * Information about an enum access event.\n */\nexport interface EnumAccessEvent {\n /** The enum ID of the accessed enum */\n readonly enumId: string;\n /** The type of access that occurred */\n readonly accessType: EnumAccessType;\n /** The key that was accessed (for 'get' and 'has' types) */\n readonly key?: string;\n /** The value that was returned (for 'get' type) */\n readonly value?: string;\n /** Timestamp of the access */\n readonly timestamp: number;\n}\n\n/**\n * Callback function type for enum access events.\n */\nexport type EnumWatchCallback = (event: EnumAccessEvent) => void;\n\n/**\n * Internal registry for enum watchers.\n */\ninterface EnumWatcherRegistry {\n /** Map from enum ID to Set of callbacks */\n readonly watchers: Map<string, Set<EnumWatchCallback>>;\n /** Global callbacks that receive all enum access events */\n readonly globalWatchers: Set<EnumWatchCallback>;\n}\n\n/**\n * Gets or initializes the watcher registry on globalThis.\n */\nfunction getWatcherRegistry(): EnumWatcherRegistry {\n const global = globalThis as typeof globalThis & {\n [WATCHER_REGISTRY_KEY]?: EnumWatcherRegistry;\n };\n\n if (!global[WATCHER_REGISTRY_KEY]) {\n global[WATCHER_REGISTRY_KEY] = {\n watchers: new Map(),\n globalWatchers: new Set(),\n };\n }\n\n return global[WATCHER_REGISTRY_KEY];\n}\n\n/**\n * Result of calling watchEnum, containing the watched proxy and unwatch function.\n */\nexport interface WatchEnumResult<E extends AnyBrandedEnum> {\n /** The proxied enum that triggers callbacks on access */\n readonly watched: E;\n /** Function to remove the watcher and stop receiving callbacks */\n readonly unwatch: () => void;\n}\n\n/**\n * Creates a watched version of a branded enum that triggers callbacks on access.\n *\n * This function wraps a branded enum in a Proxy that intercepts property access\n * and calls registered callbacks. This is useful for debugging, development tooling,\n * and understanding how enums are used throughout an application.\n *\n * The watched enum behaves identically to the original enum - all values, metadata,\n * and type information are preserved. The only difference is that access events\n * are reported to the callback.\n *\n * **Note:** This feature is intended for development and debugging purposes.\n * Using watched enums in production may have performance implications due to\n * the Proxy overhead.\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to watch\n * @param callback - Function called whenever the enum is accessed\n * @returns An object containing the watched enum proxy and an unwatch function\n * @throws {Error} Throws `Error` with message `watchEnum requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Basic usage - log all enum accesses\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const { watched, unwatch } = watchEnum(Status, (event) => {\n * console.log(`Accessed ${event.enumId}.${event.key} = ${event.value}`);\n * });\n *\n * watched.Active; // Logs: \"Accessed status.Active = active\"\n * watched.Inactive; // Logs: \"Accessed status.Inactive = inactive\"\n *\n * // Stop watching\n * unwatch();\n * watched.Active; // No longer logs\n *\n * @example\n * // Track enum usage for debugging\n * const accessLog: EnumAccessEvent[] = [];\n *\n * const { watched: WatchedColors } = watchEnum(Colors, (event) => {\n * accessLog.push(event);\n * });\n *\n * // Use the watched enum in your code\n * doSomething(WatchedColors.Red);\n * doSomethingElse(WatchedColors.Blue);\n *\n * // Later, analyze the access log\n * console.log(`Enum accessed ${accessLog.length} times`);\n * console.log('Keys accessed:', accessLog.map(e => e.key));\n *\n * @example\n * // Detect unused enum values\n * const usedKeys = new Set<string>();\n *\n * const { watched } = watchEnum(MyEnum, (event) => {\n * if (event.key) usedKeys.add(event.key);\n * });\n *\n * // After running your application/tests\n * const allKeys = Object.keys(MyEnum);\n * const unusedKeys = allKeys.filter(k => !usedKeys.has(k));\n * console.log('Unused enum keys:', unusedKeys);\n *\n * @example\n * // Performance monitoring\n * const { watched } = watchEnum(Status, (event) => {\n * if (event.accessType === 'get') {\n * performance.mark(`enum-access-${event.enumId}-${event.key}`);\n * }\n * });\n */\nexport function watchEnum<E extends AnyBrandedEnum>(\n enumObj: E,\n callback: EnumWatchCallback\n): WatchEnumResult<E> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('watchEnum requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const registry = getWatcherRegistry();\n\n // Add callback to the registry for this enum\n if (!registry.watchers.has(enumId)) {\n registry.watchers.set(enumId, new Set());\n }\n registry.watchers.get(enumId)!.add(callback);\n\n // Track if this watcher is still active\n let isActive = true;\n\n // Create a Proxy to intercept access\n const proxy = new Proxy(enumObj, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n // Only trigger callback for active watchers and string keys (not symbols)\n if (isActive && typeof prop === 'string') {\n const event: EnumAccessEvent = {\n enumId,\n accessType: 'get',\n key: prop,\n value: typeof value === 'string' ? value : undefined,\n timestamp: Date.now(),\n };\n\n // Call the specific callback\n callback(event);\n\n // Also call any global watchers\n for (const globalCallback of registry.globalWatchers) {\n globalCallback(event);\n }\n }\n\n return value;\n },\n\n has(target, prop) {\n const result = Reflect.has(target, prop);\n\n // Only trigger callback for active watchers and string keys\n if (isActive && typeof prop === 'string') {\n const event: EnumAccessEvent = {\n enumId,\n accessType: 'has',\n key: prop,\n timestamp: Date.now(),\n };\n\n callback(event);\n\n for (const globalCallback of registry.globalWatchers) {\n globalCallback(event);\n }\n }\n\n return result;\n },\n\n ownKeys(target) {\n const keys = Reflect.ownKeys(target);\n\n if (isActive) {\n const event: EnumAccessEvent = {\n enumId,\n accessType: 'keys',\n timestamp: Date.now(),\n };\n\n callback(event);\n\n for (const globalCallback of registry.globalWatchers) {\n globalCallback(event);\n }\n }\n\n return keys;\n },\n\n getOwnPropertyDescriptor(target, prop) {\n return Reflect.getOwnPropertyDescriptor(target, prop);\n },\n });\n\n // Create unwatch function\n const unwatch = () => {\n isActive = false;\n const callbacks = registry.watchers.get(enumId);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n registry.watchers.delete(enumId);\n }\n }\n };\n\n return {\n watched: proxy as E,\n unwatch,\n };\n}\n\n/**\n * Registers a global callback that receives access events from all watched enums.\n *\n * This is useful for centralized logging or monitoring of enum usage across\n * an entire application without needing to wrap each enum individually.\n *\n * @param callback - Function called for every enum access event\n * @returns A function to unregister the global callback\n *\n * @example\n * // Centralized enum access logging\n * const unregister = watchAllEnums((event) => {\n * console.log(`[${event.enumId}] ${event.accessType}: ${event.key}`);\n * });\n *\n * // All watched enums will now trigger this callback\n * watchedStatus.Active; // Logs: \"[status] get: Active\"\n * watchedColors.Red; // Logs: \"[colors] get: Red\"\n *\n * // Stop global watching\n * unregister();\n */\nexport function watchAllEnums(callback: EnumWatchCallback): () => void {\n const registry = getWatcherRegistry();\n registry.globalWatchers.add(callback);\n\n return () => {\n registry.globalWatchers.delete(callback);\n };\n}\n\n/**\n * Clears all enum watchers (both specific and global).\n *\n * This is primarily useful for testing or when you need to reset\n * the watcher state completely.\n *\n * @example\n * // In test cleanup\n * afterEach(() => {\n * clearAllEnumWatchers();\n * });\n */\nexport function clearAllEnumWatchers(): void {\n const registry = getWatcherRegistry();\n registry.watchers.clear();\n registry.globalWatchers.clear();\n}\n\n/**\n * Gets the number of active watchers for a specific enum.\n *\n * @param enumId - The enum ID to check\n * @returns The number of active watchers for this enum\n *\n * @example\n * const { watched } = watchEnum(Status, callback1);\n * watchEnum(Status, callback2);\n *\n * getEnumWatcherCount('status'); // 2\n */\nexport function getEnumWatcherCount(enumId: string): number {\n const registry = getWatcherRegistry();\n return registry.watchers.get(enumId)?.size ?? 0;\n}\n\n/**\n * Gets the number of global watchers.\n *\n * @returns The number of active global watchers\n */\nexport function getGlobalWatcherCount(): number {\n const registry = getWatcherRegistry();\n return registry.globalWatchers.size;\n}\n\n\n/**\n * A helper function for exhaustiveness checking in switch statements.\n *\n * This function should be called in the `default` case of a switch statement\n * when you want to ensure all cases are handled. If the switch is exhaustive,\n * TypeScript will infer that this function is never called (the value is `never`).\n * If a case is missing, TypeScript will show a compile-time error.\n *\n * At runtime, if this function is somehow called (e.g., due to a type assertion\n * or JavaScript interop), it throws a descriptive error.\n *\n * @param value - The value that should be unreachable. TypeScript should infer this as `never`.\n * @param message - Optional custom error message. Defaults to a descriptive message including the value.\n * @returns Never returns - always throws an error if called at runtime.\n * @throws {Error} Throws `Error` with message `Exhaustive check failed: unexpected value \"${value}\"`\n * or the custom message if provided.\n *\n * @example\n * // Basic usage with a branded enum\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * type StatusValue = typeof Status[keyof typeof Status];\n *\n * function handleStatus(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'User is active';\n * case Status.Inactive:\n * return 'User is inactive';\n * case Status.Pending:\n * return 'User is pending';\n * default:\n * // TypeScript knows this is unreachable if all cases are handled\n * return exhaustive(status);\n * }\n * }\n *\n * @example\n * // Compile-time error when a case is missing\n * function handleStatusIncomplete(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'User is active';\n * case Status.Inactive:\n * return 'User is inactive';\n * // Missing: case Status.Pending\n * default:\n * // TypeScript error: Argument of type '\"pending\"' is not assignable to parameter of type 'never'\n * return exhaustive(status);\n * }\n * }\n *\n * @example\n * // Custom error message\n * function processValue(value: StatusValue): void {\n * switch (value) {\n * case Status.Active:\n * case Status.Inactive:\n * case Status.Pending:\n * console.log('Handled:', value);\n * break;\n * default:\n * exhaustive(value, `Unknown status value encountered: ${value}`);\n * }\n * }\n *\n * @example\n * // Works with any discriminated union, not just branded enums\n * type Shape =\n * | { kind: 'circle'; radius: number }\n * | { kind: 'square'; side: number }\n * | { kind: 'rectangle'; width: number; height: number };\n *\n * function getArea(shape: Shape): number {\n * switch (shape.kind) {\n * case 'circle':\n * return Math.PI * shape.radius ** 2;\n * case 'square':\n * return shape.side ** 2;\n * case 'rectangle':\n * return shape.width * shape.height;\n * default:\n * return exhaustive(shape);\n * }\n * }\n */\nexport function exhaustive(value: never, message?: string): never {\n const errorMessage =\n message ?? `Exhaustive check failed: unexpected value \"${String(value)}\"`;\n throw new Error(errorMessage);\n}\n\n/**\n * Creates an exhaustiveness guard function bound to a specific branded enum.\n *\n * This factory function returns a guard function that can be used in switch\n * statement default cases. The returned function provides better error messages\n * by including the enum ID in the error.\n *\n * This is useful when you want to:\n * - Have consistent error messages that include the enum name\n * - Create reusable guards for specific enums\n * - Provide more context in error messages for debugging\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to create a guard for\n * @returns A function that throws an error with the enum ID included in the message\n * @throws {Error} Throws `Error` with message `exhaustiveGuard requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Create a guard for a specific enum\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const assertStatusExhaustive = exhaustiveGuard(Status);\n *\n * type StatusValue = typeof Status[keyof typeof Status];\n *\n * function handleStatus(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'Active';\n * case Status.Inactive:\n * return 'Inactive';\n * case Status.Pending:\n * return 'Pending';\n * default:\n * // Error message will include \"status\" enum ID\n * return assertStatusExhaustive(status);\n * }\n * }\n *\n * @example\n * // Inline usage without storing the guard\n * function processStatus(status: StatusValue): void {\n * switch (status) {\n * case Status.Active:\n * case Status.Inactive:\n * case Status.Pending:\n * console.log('Processing:', status);\n * break;\n * default:\n * exhaustiveGuard(Status)(status);\n * }\n * }\n *\n * @example\n * // Runtime error message example\n * // If somehow called with an unexpected value at runtime:\n * // Error: Exhaustive check failed for enum \"status\": unexpected value \"unknown\"\n *\n * @example\n * // Compile-time error when case is missing\n * function incompleteHandler(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'Active';\n * // Missing: Inactive and Pending cases\n * default:\n * // TypeScript error: Argument of type '\"inactive\" | \"pending\"' is not assignable to parameter of type 'never'\n * return assertStatusExhaustive(status);\n * }\n * }\n */\nexport function exhaustiveGuard<E extends AnyBrandedEnum>(\n enumObj: E\n): (value: never) => never {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('exhaustiveGuard requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n\n return (value: never): never => {\n throw new Error(\n `Exhaustive check failed for enum \"${enumId}\": unexpected value \"${String(value)}\"`\n );\n };\n}\n\n// =============================================================================\n// JSON Schema Generation\n// =============================================================================\n\n/**\n * Options for customizing JSON Schema generation.\n */\nexport interface ToJsonSchemaOptions {\n /**\n * Custom title for the schema. Defaults to the enum ID.\n */\n readonly title?: string;\n\n /**\n * Description for the schema. If not provided, a default description\n * mentioning the enum ID will be used.\n */\n readonly description?: string;\n\n /**\n * Whether to include the $schema property. Defaults to true.\n */\n readonly includeSchema?: boolean;\n\n /**\n * The JSON Schema draft version to use. Defaults to 'draft-07'.\n */\n readonly schemaVersion?: 'draft-04' | 'draft-06' | 'draft-07' | '2019-09' | '2020-12';\n}\n\n/**\n * JSON Schema representation of a branded enum.\n *\n * This interface represents the structure of the generated JSON Schema,\n * following the JSON Schema specification.\n */\nexport interface EnumJsonSchema {\n /**\n * The JSON Schema version URI (if includeSchema is true).\n */\n readonly $schema?: string;\n\n /**\n * The title of the schema (typically the enum ID).\n */\n readonly title: string;\n\n /**\n * Description of the schema.\n */\n readonly description: string;\n\n /**\n * The type constraint - always 'string' for branded enums.\n */\n readonly type: 'string';\n\n /**\n * The enum constraint containing all valid values.\n */\n readonly enum: readonly string[];\n}\n\n/**\n * Maps schema version options to their corresponding $schema URIs.\n */\nconst SCHEMA_VERSION_URIS: Record<NonNullable<ToJsonSchemaOptions['schemaVersion']>, string> = {\n 'draft-04': 'http://json-schema.org/draft-04/schema#',\n 'draft-06': 'http://json-schema.org/draft-06/schema#',\n 'draft-07': 'http://json-schema.org/draft-07/schema#',\n '2019-09': 'https://json-schema.org/draft/2019-09/schema',\n '2020-12': 'https://json-schema.org/draft/2020-12/schema',\n};\n\n/**\n * Generates a JSON Schema from a branded enum.\n *\n * This function creates a JSON Schema object that validates strings against\n * the values of a branded enum. The generated schema can be used with any\n * JSON Schema validator to ensure that input values are valid enum members.\n *\n * The schema includes:\n * - `$schema`: The JSON Schema version URI (optional, defaults to draft-07)\n * - `title`: The enum ID or a custom title\n * - `description`: A description of the enum\n * - `type`: Always 'string' for branded enums\n * - `enum`: An array of all valid enum values\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to generate a schema for\n * @param options - Optional configuration for schema generation\n * @returns A JSON Schema object that validates against the enum values\n * @throws {Error} Throws `Error` with message `toJsonSchema requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Basic usage - generate schema with defaults\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const schema = toJsonSchema(Status);\n * // {\n * // $schema: 'http://json-schema.org/draft-07/schema#',\n * // title: 'status',\n * // description: 'Enum values for status',\n * // type: 'string',\n * // enum: ['active', 'inactive', 'pending']\n * // }\n *\n * @example\n * // Custom title and description\n * const Priority = createBrandedEnum('priority', {\n * High: 'high',\n * Medium: 'medium',\n * Low: 'low',\n * } as const);\n *\n * const schema = toJsonSchema(Priority, {\n * title: 'Task Priority',\n * description: 'The priority level of a task',\n * });\n * // {\n * // $schema: 'http://json-schema.org/draft-07/schema#',\n * // title: 'Task Priority',\n * // description: 'The priority level of a task',\n * // type: 'string',\n * // enum: ['high', 'medium', 'low']\n * // }\n *\n * @example\n * // Without $schema property\n * const schema = toJsonSchema(Status, { includeSchema: false });\n * // {\n * // title: 'status',\n * // description: 'Enum values for status',\n * // type: 'string',\n * // enum: ['active', 'inactive', 'pending']\n * // }\n *\n * @example\n * // Using a different schema version\n * const schema = toJsonSchema(Status, { schemaVersion: '2020-12' });\n * // {\n * // $schema: 'https://json-schema.org/draft/2020-12/schema',\n * // title: 'status',\n * // description: 'Enum values for status',\n * // type: 'string',\n * // enum: ['active', 'inactive', 'pending']\n * // }\n *\n * @example\n * // Use with JSON Schema validators\n * import Ajv from 'ajv';\n *\n * const schema = toJsonSchema(Status);\n * const ajv = new Ajv();\n * const validate = ajv.compile(schema);\n *\n * validate('active'); // true\n * validate('inactive'); // true\n * validate('unknown'); // false\n *\n * @example\n * // Embed in a larger schema\n * const userSchema = {\n * type: 'object',\n * properties: {\n * name: { type: 'string' },\n * status: toJsonSchema(Status, { includeSchema: false }),\n * },\n * required: ['name', 'status'],\n * };\n *\n * @example\n * // Generate schemas for API documentation\n * const schemas = {\n * Status: toJsonSchema(Status),\n * Priority: toJsonSchema(Priority),\n * };\n *\n * // Export for OpenAPI/Swagger\n * const openApiComponents = {\n * schemas: Object.fromEntries(\n * Object.entries(schemas).map(([name, schema]) => [\n * name,\n * { ...schema, $schema: undefined }, // OpenAPI doesn't use $schema\n * ])\n * ),\n * };\n */\nexport function toJsonSchema<E extends AnyBrandedEnum>(\n enumObj: E,\n options: ToJsonSchemaOptions = {}\n): EnumJsonSchema {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('toJsonSchema requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n\n // Extract options with defaults\n const {\n title = enumId,\n description = `Enum values for ${enumId}`,\n includeSchema = true,\n schemaVersion = 'draft-07',\n } = options;\n\n // Build the schema object\n const schema: EnumJsonSchema = {\n title,\n description,\n type: 'string',\n enum: Array.from(values).sort(),\n };\n\n // Add $schema if requested\n if (includeSchema) {\n return {\n $schema: SCHEMA_VERSION_URIS[schemaVersion],\n ...schema,\n };\n }\n\n return schema;\n}\n\n// =============================================================================\n// Zod Schema Generation\n// =============================================================================\n\n/**\n * Options for customizing Zod schema definition generation.\n */\nexport interface ToZodSchemaOptions {\n /**\n * Optional description to include in the schema definition.\n * When provided, the generated schema will include a `description` field\n * that can be used with Zod's `.describe()` method.\n */\n readonly description?: string;\n}\n\n/**\n * Zod-compatible schema definition for a branded enum.\n *\n * This interface represents a schema definition object that can be used\n * to construct a Zod enum schema without depending on Zod at runtime.\n * The definition follows Zod's internal structure for enum schemas.\n *\n * To use with Zod:\n * ```typescript\n * import { z } from 'zod';\n * const def = toZodSchema(MyEnum);\n * const schema = z.enum(def.values);\n * if (def.description) {\n * schema.describe(def.description);\n * }\n * ```\n */\nexport interface ZodEnumSchemaDefinition<T extends readonly [string, ...string[]]> {\n /**\n * The type identifier for this schema definition.\n * Always 'ZodEnum' to indicate this is an enum schema.\n */\n readonly typeName: 'ZodEnum';\n\n /**\n * The enum values as a readonly tuple.\n * This matches Zod's requirement for `z.enum()` which requires\n * at least one value (hence the `[string, ...string[]]` type).\n */\n readonly values: T;\n\n /**\n * Optional description for the schema.\n * Can be used with Zod's `.describe()` method.\n */\n readonly description?: string;\n\n /**\n * The enum ID from the branded enum.\n * Useful for debugging and documentation purposes.\n */\n readonly enumId: string;\n}\n\n/**\n * Generates a Zod-compatible schema definition from a branded enum.\n *\n * This function creates a schema definition object that can be used to\n * construct a Zod enum schema. The library maintains zero dependencies\n * by returning a definition object rather than a Zod instance.\n *\n * The returned definition includes:\n * - `typeName`: Always 'ZodEnum' to identify the schema type\n * - `values`: A tuple of all enum values (sorted for consistency)\n * - `description`: Optional description for the schema\n * - `enumId`: The branded enum's ID for reference\n *\n * **Zero Dependencies**: This function does not import or depend on Zod.\n * It returns a plain object that you can use to construct a Zod schema\n * in your own code where Zod is available.\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to generate a schema definition for\n * @param options - Optional configuration for schema generation\n * @returns A Zod-compatible schema definition object\n * @throws {Error} Throws `Error` with message `toZodSchema requires a branded enum`\n * if enumObj is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `toZodSchema requires an enum with at least one value`\n * if the enum has no values (Zod requires at least one value for z.enum()).\n *\n * @example\n * // Basic usage - generate schema definition\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const schemaDef = toZodSchema(Status);\n * // {\n * // typeName: 'ZodEnum',\n * // values: ['active', 'inactive', 'pending'],\n * // enumId: 'status'\n * // }\n *\n * @example\n * // Use with Zod to create an actual schema\n * import { z } from 'zod';\n *\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const def = toZodSchema(Status);\n * const statusSchema = z.enum(def.values);\n *\n * // Validate values\n * statusSchema.parse('active'); // 'active'\n * statusSchema.parse('invalid'); // throws ZodError\n *\n * @example\n * // With description\n * const Priority = createBrandedEnum('priority', {\n * High: 'high',\n * Medium: 'medium',\n * Low: 'low',\n * } as const);\n *\n * const def = toZodSchema(Priority, {\n * description: 'Task priority level',\n * });\n * // {\n * // typeName: 'ZodEnum',\n * // values: ['high', 'low', 'medium'],\n * // description: 'Task priority level',\n * // enumId: 'priority'\n * // }\n *\n * // Use with Zod\n * const schema = z.enum(def.values).describe(def.description!);\n *\n * @example\n * // Type-safe schema creation helper\n * import { z } from 'zod';\n *\n * function createZodEnumFromBranded<E extends BrandedEnum<Record<string, string>>>(\n * enumObj: E,\n * description?: string\n * ) {\n * const def = toZodSchema(enumObj, { description });\n * const schema = z.enum(def.values);\n * return description ? schema.describe(description) : schema;\n * }\n *\n * const statusSchema = createZodEnumFromBranded(Status, 'User status');\n *\n * @example\n * // Generate schemas for multiple enums\n * const schemas = {\n * status: toZodSchema(Status),\n * priority: toZodSchema(Priority),\n * category: toZodSchema(Category),\n * };\n *\n * // Later, construct Zod schemas as needed\n * import { z } from 'zod';\n * const zodSchemas = Object.fromEntries(\n * Object.entries(schemas).map(([key, def]) => [key, z.enum(def.values)])\n * );\n *\n * @example\n * // Use in form validation\n * import { z } from 'zod';\n *\n * const statusDef = toZodSchema(Status);\n * const priorityDef = toZodSchema(Priority);\n *\n * const taskSchema = z.object({\n * title: z.string().min(1),\n * status: z.enum(statusDef.values),\n * priority: z.enum(priorityDef.values),\n * });\n *\n * type Task = z.infer<typeof taskSchema>;\n * // { title: string; status: 'active' | 'inactive' | 'pending'; priority: 'high' | 'medium' | 'low' }\n */\nexport function toZodSchema<E extends AnyBrandedEnum>(\n enumObj: E,\n options: ToZodSchemaOptions = {}\n): ZodEnumSchemaDefinition<readonly [string, ...string[]]> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('toZodSchema requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n\n // Convert Set to sorted array\n const valuesArray = Array.from(values).sort();\n\n // Zod's z.enum() requires at least one value\n if (valuesArray.length === 0) {\n throw new Error('toZodSchema requires an enum with at least one value');\n }\n\n // Build the schema definition\n const definition: ZodEnumSchemaDefinition<readonly [string, ...string[]]> = {\n typeName: 'ZodEnum',\n values: valuesArray as unknown as readonly [string, ...string[]],\n enumId,\n };\n\n // Add description if provided\n if (options.description !== undefined) {\n return {\n ...definition,\n description: options.description,\n };\n }\n\n return definition;\n}\n\n// =============================================================================\n// Enum Serializer\n// =============================================================================\n\n/**\n * Options for customizing enum serialization behavior.\n */\nexport interface EnumSerializerOptions<T extends string = string> {\n /**\n * Custom transform function applied during serialization.\n * Transforms the enum value before it is serialized.\n * \n * @param value - The original enum value\n * @returns The transformed value for serialization\n */\n readonly serialize?: (value: T) => string;\n\n /**\n * Custom transform function applied during deserialization.\n * Transforms the serialized value before validation.\n * This is applied BEFORE validation against the enum.\n * \n * @param value - The serialized value\n * @returns The transformed value to validate against the enum\n */\n readonly deserialize?: (value: string) => string;\n}\n\n/**\n * Result of a successful deserialization.\n * \n * @template T - The type of the deserialized value\n */\nexport interface DeserializeSuccess<T> {\n /** Indicates the deserialization was successful */\n readonly success: true;\n /** The validated and deserialized enum value */\n readonly value: T;\n}\n\n/**\n * Result of a failed deserialization.\n */\nexport interface DeserializeFailure {\n /** Indicates the deserialization failed */\n readonly success: false;\n /** Error information about the failure */\n readonly error: {\n /** Human-readable error message */\n readonly message: string;\n /** The input value that failed deserialization */\n readonly input: unknown;\n /** The enum ID (if available) */\n readonly enumId?: string;\n /** The valid values for the enum (if available) */\n readonly validValues?: readonly string[];\n };\n}\n\n/**\n * Union type representing the result of deserialization.\n * \n * @template T - The type of the successfully deserialized value\n */\nexport type DeserializeResult<T> = DeserializeSuccess<T> | DeserializeFailure;\n\n/**\n * A serializer/deserializer pair for branded enum values.\n * \n * Provides methods to serialize enum values (optionally with transformation)\n * and deserialize values back with validation against the enum.\n * \n * @template E - The branded enum type\n */\nexport interface EnumSerializer<E extends AnyBrandedEnum> {\n /**\n * The branded enum this serializer is bound to.\n */\n readonly enumObj: E;\n\n /**\n * The enum ID for reference.\n */\n readonly enumId: string;\n\n /**\n * Serializes an enum value to a string.\n * \n * If a custom serialize transform was provided, it is applied to the value.\n * \n * @param value - The enum value to serialize\n * @returns The serialized string value\n */\n serialize(value: EnumValues<E>): string;\n\n /**\n * Deserializes a string value back to an enum value.\n * \n * If a custom deserialize transform was provided, it is applied before validation.\n * Returns a result object indicating success or failure with detailed error info.\n * \n * @param value - The string value to deserialize\n * @returns A DeserializeResult indicating success or failure\n */\n deserialize(value: unknown): DeserializeResult<EnumValues<E>>;\n\n /**\n * Deserializes a string value, throwing an error if invalid.\n * \n * This is a convenience method that throws instead of returning a result object.\n * \n * @param value - The string value to deserialize\n * @returns The validated enum value\n * @throws Error if the value is not valid for the enum\n */\n deserializeOrThrow(value: unknown): EnumValues<E>;\n}\n\n/**\n * Creates a serializer/deserializer pair for a branded enum.\n * \n * The serializer provides methods to convert enum values to strings (with optional\n * transformation) and to deserialize strings back to validated enum values.\n * \n * This is useful for:\n * - Storing enum values in databases or localStorage with custom formats\n * - Transmitting enum values over APIs with encoding/decoding\n * - Migrating between different value formats\n * - Adding prefixes/suffixes for namespacing during serialization\n * \n * **Serialization Flow:**\n * 1. Take an enum value\n * 2. Apply custom `serialize` transform (if provided)\n * 3. Return the serialized string\n * \n * **Deserialization Flow:**\n * 1. Take a string input\n * 2. Apply custom `deserialize` transform (if provided)\n * 3. Validate the result against the enum\n * 4. Return success with the value, or failure with error details\n * \n * @template E - The branded enum type\n * @param enumObj - The branded enum to create a serializer for\n * @param options - Optional configuration for custom transforms\n * @returns An EnumSerializer object with serialize and deserialize methods\n * @throws {Error} Throws `Error` with message `enumSerializer requires a branded enum`\n * if enumObj is not a valid branded enum.\n * \n * @example\n * // Basic usage without transforms\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n * \n * const serializer = enumSerializer(Status);\n * \n * // Serialize\n * const serialized = serializer.serialize(Status.Active); // 'active'\n * \n * // Deserialize\n * const result = serializer.deserialize('active');\n * if (result.success) {\n * console.log(result.value); // 'active'\n * }\n * \n * @example\n * // With custom transforms - add prefix during serialization\n * const Priority = createBrandedEnum('priority', {\n * High: 'high',\n * Medium: 'medium',\n * Low: 'low',\n * } as const);\n * \n * const serializer = enumSerializer(Priority, {\n * serialize: (value) => `priority:${value}`,\n * deserialize: (value) => value.replace('priority:', ''),\n * });\n * \n * // Serialize adds prefix\n * serializer.serialize(Priority.High); // 'priority:high'\n * \n * // Deserialize removes prefix and validates\n * const result = serializer.deserialize('priority:high');\n * if (result.success) {\n * console.log(result.value); // 'high'\n * }\n * \n * @example\n * // Base64 encoding for storage\n * const Secret = createBrandedEnum('secret', {\n * Token: 'token',\n * Key: 'key',\n * } as const);\n * \n * const serializer = enumSerializer(Secret, {\n * serialize: (value) => btoa(value),\n * deserialize: (value) => atob(value),\n * });\n * \n * serializer.serialize(Secret.Token); // 'dG9rZW4=' (base64 of 'token')\n * serializer.deserialize('dG9rZW4='); // { success: true, value: 'token' }\n * \n * @example\n * // Error handling\n * const result = serializer.deserialize('invalid');\n * if (!result.success) {\n * console.log(result.error.message);\n * // 'Value \"invalid\" is not a member of enum \"status\"'\n * console.log(result.error.validValues);\n * // ['active', 'inactive']\n * }\n * \n * @example\n * // Using deserializeOrThrow for simpler code when errors should throw\n * try {\n * const value = serializer.deserializeOrThrow('active');\n * console.log(value); // 'active'\n * } catch (e) {\n * console.error('Invalid value:', e.message);\n * }\n * \n * @example\n * // Case-insensitive deserialization\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n * \n * const caseInsensitiveSerializer = enumSerializer(Colors, {\n * deserialize: (value) => value.toLowerCase(),\n * });\n * \n * caseInsensitiveSerializer.deserialize('RED'); // { success: true, value: 'red' }\n * caseInsensitiveSerializer.deserialize('Red'); // { success: true, value: 'red' }\n * caseInsensitiveSerializer.deserialize('red'); // { success: true, value: 'red' }\n */\nexport function enumSerializer<E extends AnyBrandedEnum>(\n enumObj: E,\n options: EnumSerializerOptions<EnumValues<E>> = {}\n): EnumSerializer<E> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('enumSerializer requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n const validValues = Array.from(values).sort();\n\n const { serialize: serializeTransform, deserialize: deserializeTransform } = options;\n\n return {\n enumObj,\n enumId,\n\n serialize(value: EnumValues<E>): string {\n // Apply custom transform if provided\n if (serializeTransform) {\n return serializeTransform(value);\n }\n return value;\n },\n\n deserialize(value: unknown): DeserializeResult<EnumValues<E>> {\n // Check if value is a string\n if (typeof value !== 'string') {\n const valueType = value === null ? 'null' : typeof value;\n return {\n success: false,\n error: {\n message: `Expected a string value, received ${valueType}`,\n input: value,\n enumId,\n validValues,\n },\n };\n }\n\n // Apply custom deserialize transform if provided\n let transformedValue = value;\n if (deserializeTransform) {\n try {\n transformedValue = deserializeTransform(value);\n } catch (e) {\n return {\n success: false,\n error: {\n message: `Deserialize transform failed: ${e instanceof Error ? e.message : String(e)}`,\n input: value,\n enumId,\n validValues,\n },\n };\n }\n }\n\n // Validate against the enum\n if (!values.has(transformedValue)) {\n return {\n success: false,\n error: {\n message: `Value \"${transformedValue}\" is not a member of enum \"${enumId}\"`,\n input: value,\n enumId,\n validValues,\n },\n };\n }\n\n return {\n success: true,\n value: transformedValue as EnumValues<E>,\n };\n },\n\n deserializeOrThrow(value: unknown): EnumValues<E> {\n const result = this.deserialize(value);\n if (!result.success) {\n throw new Error(result.error.message);\n }\n return result.value;\n },\n };\n}\n"],"names":["clearAllEnumWatchers","enumDiff","enumExclude","enumFromKeys","enumIntersect","enumMap","enumSerializer","enumSubset","enumToRecord","exhaustive","exhaustiveGuard","getEnumWatcherCount","getGlobalWatcherCount","toJsonSchema","toZodSchema","watchAllEnums","watchEnum","isBrandedEnum","obj","ENUM_ID","ENUM_VALUES","Set","newId","sourceEnum","keys","Error","length","sourceEnumId","subsetValues","key","createBrandedEnum","keysToExclude","excludeSet","allKeys","Object","resultValues","has","mapper","originalValue","transformedValue","enumId","seenKeys","values","add","firstEnum","secondEnum","firstKeys","secondKeys","onlyInFirst","onlyInSecond","differentValues","sameValues","firstValue","push","value","secondValue","enums","enumObj","valueToEnumIds","Map","set","get","result","enumIds","size","Array","from","sort","a","b","localeCompare","WATCHER_REGISTRY_KEY","getWatcherRegistry","global","globalThis","watchers","globalWatchers","callback","registry","isActive","proxy","Proxy","target","prop","receiver","Reflect","event","accessType","undefined","timestamp","Date","now","globalCallback","ownKeys","getOwnPropertyDescriptor","unwatch","callbacks","delete","watched","clear","message","errorMessage","String","SCHEMA_VERSION_URIS","options","title","description","includeSchema","schemaVersion","schema","type","enum","$schema","valuesArray","definition","typeName","validValues","serialize","serializeTransform","deserialize","deserializeTransform","valueType","success","error","input","e","deserializeOrThrow"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC;;;;;;;;;;;IAqlCeA,oBAAoB;eAApBA;;IAnkBAC,QAAQ;eAARA;;IAxVAC,WAAW;eAAXA;;IAiOAC,YAAY;eAAZA;;IAiQAC,aAAa;eAAbA;;IArWAC,OAAO;eAAPA;;IAgrDAC,cAAc;eAAdA;;IAp5DAC,UAAU;eAAVA;;IAisBAC,YAAY;eAAZA;;IA6bAC,UAAU;eAAVA;;IAkFAC,eAAe;eAAfA;;IA5LAC,mBAAmB;eAAnBA;;IAUAC,qBAAqB;eAArBA;;IAoYAC,YAAY;eAAZA;;IA8NAC,WAAW;eAAXA;;IAnpBAC,aAAa;eAAbA;;IAvIAC,SAAS;eAATA;;;uBAv7BwE;yBACtD;AAElC;;;;;CAKC,GACD,SAASC,cAAcC,GAAY;IACjC,OACEA,QAAQ,QACR,OAAOA,QAAQ,YACfC,cAAO,IAAID,OACXE,kBAAW,IAAIF,OACf,OAAO,AAACA,GAAsB,CAACC,cAAO,CAAC,KAAK,YAC5C,AAACD,GAAsB,CAACE,kBAAW,CAAC,YAAYC;AAEpD;AA+DO,SAASd,WAIde,KAAa,EACbC,UAAa,EACbC,IAAkB;IAElB,6CAA6C;IAC7C,IAAI,CAACP,cAAcM,aAAa;QAC9B,MAAM,IAAIE,MAAM;IAClB;IAEA,wCAAwC;IACxC,IAAID,KAAKE,MAAM,KAAK,GAAG;QACrB,MAAM,IAAID,MAAM;IAClB;IAEA,MAAME,eAAeJ,UAAU,CAACJ,cAAO,CAAC;IAExC,iCAAiC;IACjC,MAAMS,eAAuC,CAAC;IAE9C,KAAK,MAAMC,OAAOL,KAAM;QACtB,kDAAkD;QAClD,IAAI,CAAEK,CAAAA,OAAON,UAAS,GAAI;YACxB,MAAM,IAAIE,MACR,CAAC,KAAK,EAAEI,IAAI,0BAA0B,EAAEF,aAAa,CAAC,CAAC;QAE3D;QAEA,0BAA0B;QAC1BC,YAAY,CAACC,IAAI,GAAG,AAACN,UAAqC,CAACM,IAAI;IACjE;IAEA,qEAAqE;IACrE,OAAOC,IAAAA,0BAAiB,EAACR,OAAOM;AAGlC;AAgEO,SAAS1B,YAIdoB,KAAa,EACbC,UAAa,EACbQ,aAA2B;IAE3B,6CAA6C;IAC7C,IAAI,CAACd,cAAcM,aAAa;QAC9B,MAAM,IAAIE,MAAM;IAClB;IAEA,MAAME,eAAeJ,UAAU,CAACJ,cAAO,CAAC;IAExC,6DAA6D;IAC7D,KAAK,MAAMU,OAAOE,cAAe;QAC/B,IAAI,CAAEF,CAAAA,OAAON,UAAS,GAAI;YACxB,MAAM,IAAIE,MACR,CAAC,KAAK,EAAEI,IAAI,0BAA0B,EAAEF,aAAa,CAAC,CAAC;QAE3D;IACF;IAEA,gDAAgD;IAChD,MAAMK,aAAa,IAAIX,IAAYU;IAEnC,gEAAgE;IAChE,MAAME,UAAUC,OAAOV,IAAI,CAACD;IAE5B,wDAAwD;IACxD,MAAMY,eAAuC,CAAC;IAE9C,KAAK,MAAMN,OAAOI,QAAS;QACzB,IAAI,CAACD,WAAWI,GAAG,CAACP,MAAM;YACxBM,YAAY,CAACN,IAAI,GAAG,AAACN,UAAqC,CAACM,IAAI;QACjE;IACF;IAEA,mDAAmD;IACnD,IAAIK,OAAOV,IAAI,CAACW,cAAcT,MAAM,KAAK,GAAG;QAC1C,MAAM,IAAID,MACR;IAEJ;IAEA,qEAAqE;IACrE,OAAOK,IAAAA,0BAAiB,EAACR,OAAOa;AAGlC;AA2EO,SAAS9B,QACdiB,KAAa,EACbC,UAAa,EACbc,MAA8C;IAE9C,6CAA6C;IAC7C,IAAI,CAACpB,cAAcM,aAAa;QAC9B,MAAM,IAAIE,MAAM;IAClB;IAEA,gEAAgE;IAChE,MAAMQ,UAAUC,OAAOV,IAAI,CAACD;IAE5B,yDAAyD;IACzD,MAAMY,eAAuC,CAAC;IAE9C,KAAK,MAAMN,OAAOI,QAAS;QACzB,MAAMK,gBAAgBf,UAAU,CAACM,IAAe;QAChD,MAAMU,mBAAmBF,OAAOC,eAAeT;QAE/C,6CAA6C;QAC7C,IAAI,OAAOU,qBAAqB,UAAU;YACxC,MAAM,IAAId,MAAM;QAClB;QAEAU,YAAY,CAACN,IAAI,GAAGU;IACtB;IAEA,qEAAqE;IACrE,OAAOT,IAAAA,0BAAiB,EAACR,OAAOa;AAGlC;AAoEO,SAAShC,aACdqC,MAAc,EACdhB,IAAO;IAEP,wCAAwC;IACxC,IAAIA,KAAKE,MAAM,KAAK,GAAG;QACrB,MAAM,IAAID,MAAM;IAClB;IAEA,uCAAuC;IACvC,MAAMgB,WAAW,IAAIpB;IAErB,wDAAwD;IACxD,MAAMqB,SAAiC,CAAC;IAExC,KAAK,MAAMb,OAAOL,KAAM;QACtB,+CAA+C;QAC/C,IAAI,OAAOK,QAAQ,YAAYA,IAAIH,MAAM,KAAK,GAAG;YAC/C,MAAM,IAAID,MAAM;QAClB;QAEA,2BAA2B;QAC3B,IAAIgB,SAASL,GAAG,CAACP,MAAM;YACrB,MAAM,IAAIJ,MAAM,CAAC,6BAA6B,EAAEI,IAAI,OAAO,CAAC;QAC9D;QAEAY,SAASE,GAAG,CAACd;QACba,MAAM,CAACb,IAAI,GAAGA;IAChB;IAEA,qEAAqE;IACrE,OAAOC,IAAAA,0BAAiB,EAACU,QAAQE;AAGnC;AAqFO,SAASzC,SACd2C,SAAyB,EACzBC,UAA0B;IAE1B,iDAAiD;IACjD,IAAI,CAAC5B,cAAc2B,cAAc,CAAC3B,cAAc4B,aAAa;QAC3D,MAAM,IAAIpB,MAAM;IAClB;IAEA,2DAA2D;IAC3D,MAAMqB,YAAY,IAAIzB,IAAIa,OAAOV,IAAI,CAACoB;IACtC,MAAMG,aAAa,IAAI1B,IAAIa,OAAOV,IAAI,CAACqB;IAEvC,MAAMG,cAAqD,EAAE;IAC7D,MAAMC,eAAsD,EAAE;IAC9D,MAAMC,kBAID,EAAE;IACP,MAAMC,aAAoD,EAAE;IAE5D,gDAAgD;IAChD,KAAK,MAAMtB,OAAOiB,UAAW;QAC3B,MAAMM,aAAa,AAACR,SAAoC,CAACf,IAAI;QAE7D,IAAI,CAACkB,WAAWX,GAAG,CAACP,MAAM;YACxB,gCAAgC;YAChCmB,YAAYK,IAAI,CAAC;gBAAExB;gBAAKyB,OAAOF;YAAW;QAC5C,OAAO;YACL,sCAAsC;YACtC,MAAMG,cAAc,AAACV,UAAqC,CAAChB,IAAI;YAE/D,IAAIuB,eAAeG,aAAa;gBAC9BJ,WAAWE,IAAI,CAAC;oBAAExB;oBAAKyB,OAAOF;gBAAW;YAC3C,OAAO;gBACLF,gBAAgBG,IAAI,CAAC;oBAAExB;oBAAKuB;oBAAYG;gBAAY;YACtD;QACF;IACF;IAEA,gCAAgC;IAChC,KAAK,MAAM1B,OAAOkB,WAAY;QAC5B,IAAI,CAACD,UAAUV,GAAG,CAACP,MAAM;YACvB,MAAM0B,cAAc,AAACV,UAAqC,CAAChB,IAAI;YAC/DoB,aAAaI,IAAI,CAAC;gBAAExB;gBAAKyB,OAAOC;YAAY;QAC9C;IACF;IAEA,OAAO;QACLP;QACAC;QACAC;QACAC;IACF;AACF;AAmFO,SAAS/C,cACd,GAAGoD,KAAuB;IAE1B,gDAAgD;IAChD,IAAIA,MAAM9B,MAAM,GAAG,GAAG;QACpB,MAAM,IAAID,MAAM;IAClB;IAEA,gDAAgD;IAChD,KAAK,MAAMgC,WAAWD,MAAO;QAC3B,IAAI,CAACvC,cAAcwC,UAAU;YAC3B,MAAM,IAAIhC,MAAM;QAClB;IACF;IAEA,gEAAgE;IAChE,MAAMiC,iBAAiB,IAAIC;IAE3B,KAAK,MAAMF,WAAWD,MAAO;QAC3B,MAAMhB,SAASiB,OAAO,CAACtC,cAAO,CAAC;QAC/B,MAAMuB,SAASe,OAAO,CAACrC,kBAAW,CAAC;QAEnC,KAAK,MAAMkC,SAASZ,OAAQ;YAC1B,IAAI,CAACgB,eAAetB,GAAG,CAACkB,QAAQ;gBAC9BI,eAAeE,GAAG,CAACN,OAAO,IAAIjC;YAChC;YACAqC,eAAeG,GAAG,CAACP,OAAQX,GAAG,CAACH;QACjC;IACF;IAEA,sDAAsD;IACtD,MAAMsB,SAA+B,EAAE;IAEvC,KAAK,MAAM,CAACR,OAAOS,QAAQ,IAAIL,eAAgB;QAC7C,IAAIK,QAAQC,IAAI,IAAI,GAAG;YACrBF,OAAOT,IAAI,CAAC;gBACVC;gBACAS,SAASE,MAAMC,IAAI,CAACH,SAASI,IAAI;YACnC;QACF;IACF;IAEA,wCAAwC;IACxCL,OAAOK,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEd,KAAK,CAACgB,aAAa,CAACD,EAAEf,KAAK;IAEnD,OAAOQ;AACT;AA0EO,SAAStD,aACdiD,OAAU;IAEV,0CAA0C;IAC1C,IAAI,CAACxC,cAAcwC,UAAU;QAC3B,MAAM,IAAIhC,MAAM;IAClB;IAEA,qEAAqE;IACrE,MAAMqC,SAAiC,CAAC;IAExC,KAAK,MAAMjC,OAAOK,OAAOV,IAAI,CAACiC,SAAU;QACtCK,MAAM,CAACjC,IAAI,GAAG4B,OAAO,CAAC5B,IAAe;IACvC;IAEA,OAAOiC;AACT;AAEA;;;CAGC,GACD,MAAMS,uBAAuB;AAsC7B;;CAEC,GACD,SAASC;IACP,MAAMC,SAASC;IAIf,IAAI,CAACD,MAAM,CAACF,qBAAqB,EAAE;QACjCE,MAAM,CAACF,qBAAqB,GAAG;YAC7BI,UAAU,IAAIhB;YACdiB,gBAAgB,IAAIvD;QACtB;IACF;IAEA,OAAOoD,MAAM,CAACF,qBAAqB;AACrC;AAyFO,SAASvD,UACdyC,OAAU,EACVoB,QAA2B;IAE3B,0CAA0C;IAC1C,IAAI,CAAC5D,cAAcwC,UAAU;QAC3B,MAAM,IAAIhC,MAAM;IAClB;IAEA,MAAMe,SAASiB,OAAO,CAACtC,cAAO,CAAC;IAC/B,MAAM2D,WAAWN;IAEjB,6CAA6C;IAC7C,IAAI,CAACM,SAASH,QAAQ,CAACvC,GAAG,CAACI,SAAS;QAClCsC,SAASH,QAAQ,CAACf,GAAG,CAACpB,QAAQ,IAAInB;IACpC;IACAyD,SAASH,QAAQ,CAACd,GAAG,CAACrB,QAASG,GAAG,CAACkC;IAEnC,wCAAwC;IACxC,IAAIE,WAAW;IAEf,qCAAqC;IACrC,MAAMC,QAAQ,IAAIC,MAAMxB,SAAS;QAC/BI,KAAIqB,MAAM,EAAEC,IAAI,EAAEC,QAAQ;YACxB,MAAM9B,QAAQ+B,QAAQxB,GAAG,CAACqB,QAAQC,MAAMC;YAExC,0EAA0E;YAC1E,IAAIL,YAAY,OAAOI,SAAS,UAAU;gBACxC,MAAMG,QAAyB;oBAC7B9C;oBACA+C,YAAY;oBACZ1D,KAAKsD;oBACL7B,OAAO,OAAOA,UAAU,WAAWA,QAAQkC;oBAC3CC,WAAWC,KAAKC,GAAG;gBACrB;gBAEA,6BAA6B;gBAC7Bd,SAASS;gBAET,gCAAgC;gBAChC,KAAK,MAAMM,kBAAkBd,SAASF,cAAc,CAAE;oBACpDgB,eAAeN;gBACjB;YACF;YAEA,OAAOhC;QACT;QAEAlB,KAAI8C,MAAM,EAAEC,IAAI;YACd,MAAMrB,SAASuB,QAAQjD,GAAG,CAAC8C,QAAQC;YAEnC,4DAA4D;YAC5D,IAAIJ,YAAY,OAAOI,SAAS,UAAU;gBACxC,MAAMG,QAAyB;oBAC7B9C;oBACA+C,YAAY;oBACZ1D,KAAKsD;oBACLM,WAAWC,KAAKC,GAAG;gBACrB;gBAEAd,SAASS;gBAET,KAAK,MAAMM,kBAAkBd,SAASF,cAAc,CAAE;oBACpDgB,eAAeN;gBACjB;YACF;YAEA,OAAOxB;QACT;QAEA+B,SAAQX,MAAM;YACZ,MAAM1D,OAAO6D,QAAQQ,OAAO,CAACX;YAE7B,IAAIH,UAAU;gBACZ,MAAMO,QAAyB;oBAC7B9C;oBACA+C,YAAY;oBACZE,WAAWC,KAAKC,GAAG;gBACrB;gBAEAd,SAASS;gBAET,KAAK,MAAMM,kBAAkBd,SAASF,cAAc,CAAE;oBACpDgB,eAAeN;gBACjB;YACF;YAEA,OAAO9D;QACT;QAEAsE,0BAAyBZ,MAAM,EAAEC,IAAI;YACnC,OAAOE,QAAQS,wBAAwB,CAACZ,QAAQC;QAClD;IACF;IAEA,0BAA0B;IAC1B,MAAMY,UAAU;QACdhB,WAAW;QACX,MAAMiB,YAAYlB,SAASH,QAAQ,CAACd,GAAG,CAACrB;QACxC,IAAIwD,WAAW;YACbA,UAAUC,MAAM,CAACpB;YACjB,IAAImB,UAAUhC,IAAI,KAAK,GAAG;gBACxBc,SAASH,QAAQ,CAACsB,MAAM,CAACzD;YAC3B;QACF;IACF;IAEA,OAAO;QACL0D,SAASlB;QACTe;IACF;AACF;AAwBO,SAAShF,cAAc8D,QAA2B;IACvD,MAAMC,WAAWN;IACjBM,SAASF,cAAc,CAACjC,GAAG,CAACkC;IAE5B,OAAO;QACLC,SAASF,cAAc,CAACqB,MAAM,CAACpB;IACjC;AACF;AAcO,SAAS7E;IACd,MAAM8E,WAAWN;IACjBM,SAASH,QAAQ,CAACwB,KAAK;IACvBrB,SAASF,cAAc,CAACuB,KAAK;AAC/B;AAcO,SAASxF,oBAAoB6B,MAAc;IAChD,MAAMsC,WAAWN;IACjB,OAAOM,SAASH,QAAQ,CAACd,GAAG,CAACrB,SAASwB,QAAQ;AAChD;AAOO,SAASpD;IACd,MAAMkE,WAAWN;IACjB,OAAOM,SAASF,cAAc,CAACZ,IAAI;AACrC;AA6FO,SAASvD,WAAW6C,KAAY,EAAE8C,OAAgB;IACvD,MAAMC,eACJD,WAAW,CAAC,2CAA2C,EAAEE,OAAOhD,OAAO,CAAC,CAAC;IAC3E,MAAM,IAAI7B,MAAM4E;AAClB;AA8EO,SAAS3F,gBACd+C,OAAU;IAEV,0CAA0C;IAC1C,IAAI,CAACxC,cAAcwC,UAAU;QAC3B,MAAM,IAAIhC,MAAM;IAClB;IAEA,MAAMe,SAASiB,OAAO,CAACtC,cAAO,CAAC;IAE/B,OAAO,CAACmC;QACN,MAAM,IAAI7B,MACR,CAAC,kCAAkC,EAAEe,OAAO,qBAAqB,EAAE8D,OAAOhD,OAAO,CAAC,CAAC;IAEvF;AACF;AAiEA;;CAEC,GACD,MAAMiD,sBAAyF;IAC7F,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,WAAW;AACb;AAyHO,SAAS1F,aACd4C,OAAU,EACV+C,UAA+B,CAAC,CAAC;IAEjC,0CAA0C;IAC1C,IAAI,CAACvF,cAAcwC,UAAU;QAC3B,MAAM,IAAIhC,MAAM;IAClB;IAEA,MAAMe,SAASiB,OAAO,CAACtC,cAAO,CAAC;IAC/B,MAAMuB,SAASe,OAAO,CAACrC,kBAAW,CAAC;IAEnC,gCAAgC;IAChC,MAAM,EACJqF,QAAQjE,MAAM,EACdkE,cAAc,CAAC,gBAAgB,EAAElE,OAAO,CAAC,EACzCmE,gBAAgB,IAAI,EACpBC,gBAAgB,UAAU,EAC3B,GAAGJ;IAEJ,0BAA0B;IAC1B,MAAMK,SAAyB;QAC7BJ;QACAC;QACAI,MAAM;QACNC,MAAM9C,MAAMC,IAAI,CAACxB,QAAQyB,IAAI;IAC/B;IAEA,2BAA2B;IAC3B,IAAIwC,eAAe;QACjB,OAAO;YACLK,SAAST,mBAAmB,CAACK,cAAc;YAC3C,GAAGC,MAAM;QACX;IACF;IAEA,OAAOA;AACT;AAyLO,SAAS/F,YACd2C,OAAU,EACV+C,UAA8B,CAAC,CAAC;IAEhC,0CAA0C;IAC1C,IAAI,CAACvF,cAAcwC,UAAU;QAC3B,MAAM,IAAIhC,MAAM;IAClB;IAEA,MAAMe,SAASiB,OAAO,CAACtC,cAAO,CAAC;IAC/B,MAAMuB,SAASe,OAAO,CAACrC,kBAAW,CAAC;IAEnC,8BAA8B;IAC9B,MAAM6F,cAAchD,MAAMC,IAAI,CAACxB,QAAQyB,IAAI;IAE3C,6CAA6C;IAC7C,IAAI8C,YAAYvF,MAAM,KAAK,GAAG;QAC5B,MAAM,IAAID,MAAM;IAClB;IAEA,8BAA8B;IAC9B,MAAMyF,aAAsE;QAC1EC,UAAU;QACVzE,QAAQuE;QACRzE;IACF;IAEA,8BAA8B;IAC9B,IAAIgE,QAAQE,WAAW,KAAKlB,WAAW;QACrC,OAAO;YACL,GAAG0B,UAAU;YACbR,aAAaF,QAAQE,WAAW;QAClC;IACF;IAEA,OAAOQ;AACT;AAgPO,SAAS5G,eACdmD,OAAU,EACV+C,UAAgD,CAAC,CAAC;IAElD,0CAA0C;IAC1C,IAAI,CAACvF,cAAcwC,UAAU;QAC3B,MAAM,IAAIhC,MAAM;IAClB;IAEA,MAAMe,SAASiB,OAAO,CAACtC,cAAO,CAAC;IAC/B,MAAMuB,SAASe,OAAO,CAACrC,kBAAW,CAAC;IACnC,MAAMgG,cAAcnD,MAAMC,IAAI,CAACxB,QAAQyB,IAAI;IAE3C,MAAM,EAAEkD,WAAWC,kBAAkB,EAAEC,aAAaC,oBAAoB,EAAE,GAAGhB;IAE7E,OAAO;QACL/C;QACAjB;QAEA6E,WAAU/D,KAAoB;YAC5B,qCAAqC;YACrC,IAAIgE,oBAAoB;gBACtB,OAAOA,mBAAmBhE;YAC5B;YACA,OAAOA;QACT;QAEAiE,aAAYjE,KAAc;YACxB,6BAA6B;YAC7B,IAAI,OAAOA,UAAU,UAAU;gBAC7B,MAAMmE,YAAYnE,UAAU,OAAO,SAAS,OAAOA;gBACnD,OAAO;oBACLoE,SAAS;oBACTC,OAAO;wBACLvB,SAAS,CAAC,kCAAkC,EAAEqB,UAAU,CAAC;wBACzDG,OAAOtE;wBACPd;wBACA4E;oBACF;gBACF;YACF;YAEA,iDAAiD;YACjD,IAAI7E,mBAAmBe;YACvB,IAAIkE,sBAAsB;gBACxB,IAAI;oBACFjF,mBAAmBiF,qBAAqBlE;gBAC1C,EAAE,OAAOuE,GAAG;oBACV,OAAO;wBACLH,SAAS;wBACTC,OAAO;4BACLvB,SAAS,CAAC,8BAA8B,EAAEyB,aAAapG,QAAQoG,EAAEzB,OAAO,GAAGE,OAAOuB,GAAG,CAAC;4BACtFD,OAAOtE;4BACPd;4BACA4E;wBACF;oBACF;gBACF;YACF;YAEA,4BAA4B;YAC5B,IAAI,CAAC1E,OAAON,GAAG,CAACG,mBAAmB;gBACjC,OAAO;oBACLmF,SAAS;oBACTC,OAAO;wBACLvB,SAAS,CAAC,OAAO,EAAE7D,iBAAiB,2BAA2B,EAAEC,OAAO,CAAC,CAAC;wBAC1EoF,OAAOtE;wBACPd;wBACA4E;oBACF;gBACF;YACF;YAEA,OAAO;gBACLM,SAAS;gBACTpE,OAAOf;YACT;QACF;QAEAuF,oBAAmBxE,KAAc;YAC/B,MAAMQ,SAAS,IAAI,CAACyD,WAAW,CAACjE;YAChC,IAAI,CAACQ,OAAO4D,OAAO,EAAE;gBACnB,MAAM,IAAIjG,MAAMqC,OAAO6D,KAAK,CAACvB,OAAO;YACtC;YACA,OAAOtC,OAAOR,KAAK;QACrB;IACF;AACF"}
1
+ {"version":3,"sources":["../../../src/lib/advanced.ts"],"sourcesContent":["/**\n * Advanced enum operations for branded enums.\n *\n * Provides functions for deriving new enums from existing ones,\n * including subsetting, exclusion, and transformation operations.\n */\n\nimport { AnyBrandedEnum, BrandedEnum, ENUM_ID, ENUM_VALUES, EnumKeys, EnumValues } from './types.js';\nimport { createBrandedEnum } from './factory.js';\n\n/**\n * Checks if an object is a branded enum (has Symbol metadata).\n *\n * @param obj - The object to check\n * @returns true if obj is a branded enum\n */\nfunction isBrandedEnum(obj: unknown): obj is AnyBrandedEnum {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n ENUM_ID in obj &&\n ENUM_VALUES in obj &&\n typeof (obj as AnyBrandedEnum)[ENUM_ID] === 'string' &&\n (obj as AnyBrandedEnum)[ENUM_VALUES] instanceof Set\n );\n}\n\n/**\n * Creates a new branded enum containing only the specified keys from the source enum.\n *\n * This function derives a subset of an existing branded enum by selecting specific keys.\n * The resulting enum is registered as an independent enum in the global registry.\n *\n * Type safety is maintained - the resulting enum's type reflects only the selected keys.\n *\n * @template E - The source branded enum type\n * @template K - The keys to include in the subset (must be keys of E)\n * @param newId - Unique identifier for the new subset enum. If already registered,\n * returns the existing enum (idempotent behavior).\n * @param sourceEnum - The branded enum to derive the subset from\n * @param keys - Array of keys to include in the subset. All keys must exist in sourceEnum.\n * @returns A new branded enum containing only the specified key-value pairs,\n * or the existing enum if newId is already registered\n * @throws {Error} Throws `Error` with message `enumSubset requires a branded enum as the source`\n * if sourceEnum is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `enumSubset requires at least one key`\n * if keys array is empty.\n * @throws {Error} Throws `Error` with message `Key \"${key}\" does not exist in enum \"${enumId}\"`\n * if any specified key does not exist in the source enum.\n *\n * @example\n * // Basic usage - create a subset of colors\n * const AllColors = createBrandedEnum('all-colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * Yellow: 'yellow',\n * } as const);\n *\n * const PrimaryColors = enumSubset('primary-colors', AllColors, ['Red', 'Blue', 'Yellow']);\n * // PrimaryColors has: Red, Blue, Yellow (no Green)\n *\n * PrimaryColors.Red; // 'red'\n * PrimaryColors.Blue; // 'blue'\n * // PrimaryColors.Green; // TypeScript error - Green doesn't exist\n *\n * @example\n * // Type safety with subset\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * Archived: 'archived',\n * } as const);\n *\n * const ActiveStatuses = enumSubset('active-statuses', Status, ['Active', 'Pending']);\n * type ActiveStatusValue = typeof ActiveStatuses[keyof typeof ActiveStatuses];\n * // ActiveStatusValue = 'active' | 'pending'\n *\n * @example\n * // Error handling for invalid keys\n * try {\n * enumSubset('invalid', AllColors, ['Red', 'Purple']); // Purple doesn't exist\n * } catch (e) {\n * console.log(e.message);\n * // 'Key \"Purple\" does not exist in enum \"all-colors\"'\n * }\n */\nexport function enumSubset<\n E extends AnyBrandedEnum,\n K extends keyof E & string,\n>(\n newId: string,\n sourceEnum: E,\n keys: readonly K[]\n): BrandedEnum<Pick<E, K> & Record<string, string>> {\n // Validate that sourceEnum is a branded enum\n if (!isBrandedEnum(sourceEnum)) {\n throw new Error('enumSubset requires a branded enum as the source');\n }\n\n // Validate that keys array is not empty\n if (keys.length === 0) {\n throw new Error('enumSubset requires at least one key');\n }\n\n const sourceEnumId = sourceEnum[ENUM_ID];\n\n // Build the subset values object\n const subsetValues: Record<string, string> = {};\n\n for (const key of keys) {\n // Validate that the key exists in the source enum\n if (!(key in sourceEnum)) {\n throw new Error(\n `Key \"${key}\" does not exist in enum \"${sourceEnumId}\"`\n );\n }\n\n // Copy the key-value pair\n subsetValues[key] = (sourceEnum as Record<string, string>)[key];\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, subsetValues) as BrandedEnum<\n Pick<E, K> & Record<string, string>\n >;\n}\n\n/**\n * Creates a new branded enum by excluding the specified keys from the source enum.\n *\n * This function derives a new enum from an existing branded enum by removing specific keys.\n * It is the complement of `enumSubset` - instead of specifying which keys to include,\n * you specify which keys to exclude.\n *\n * The resulting enum is registered as an independent enum in the global registry.\n * Type safety is maintained - the resulting enum's type reflects only the remaining keys.\n *\n * @template E - The source branded enum type\n * @template K - The keys to exclude from the result (must be keys of E)\n * @param newId - Unique identifier for the new enum. If already registered,\n * returns the existing enum (idempotent behavior).\n * @param sourceEnum - The branded enum to derive from\n * @param keysToExclude - Array of keys to exclude. All keys must exist in sourceEnum.\n * @returns A new branded enum containing all key-value pairs except the excluded ones,\n * or the existing enum if newId is already registered\n * @throws {Error} Throws `Error` with message `enumExclude requires a branded enum as the source`\n * if sourceEnum is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `enumExclude: excluding all keys would result in an empty enum`\n * if excluding all keys would leave no keys remaining.\n * @throws {Error} Throws `Error` with message `Key \"${key}\" does not exist in enum \"${enumId}\"`\n * if any specified key to exclude does not exist in the source enum.\n *\n * @example\n * // Basic usage - exclude specific colors\n * const AllColors = createBrandedEnum('all-colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * Yellow: 'yellow',\n * } as const);\n *\n * const NonPrimaryColors = enumExclude('non-primary', AllColors, ['Red', 'Blue', 'Yellow']);\n * // NonPrimaryColors has only: Green\n *\n * NonPrimaryColors.Green; // 'green'\n * // NonPrimaryColors.Red; // TypeScript error - Red was excluded\n *\n * @example\n * // Exclude deprecated values\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * Deprecated: 'deprecated',\n * } as const);\n *\n * const CurrentStatuses = enumExclude('current-statuses', Status, ['Deprecated']);\n * type CurrentStatusValue = typeof CurrentStatuses[keyof typeof CurrentStatuses];\n * // CurrentStatusValue = 'active' | 'inactive' | 'pending'\n *\n * @example\n * // Error handling for invalid keys\n * try {\n * enumExclude('invalid', AllColors, ['Purple']); // Purple doesn't exist\n * } catch (e) {\n * console.log(e.message);\n * // 'Key \"Purple\" does not exist in enum \"all-colors\"'\n * }\n */\nexport function enumExclude<\n E extends AnyBrandedEnum,\n K extends keyof E & string,\n>(\n newId: string,\n sourceEnum: E,\n keysToExclude: readonly K[]\n): BrandedEnum<Omit<E, K> & Record<string, string>> {\n // Validate that sourceEnum is a branded enum\n if (!isBrandedEnum(sourceEnum)) {\n throw new Error('enumExclude requires a branded enum as the source');\n }\n\n const sourceEnumId = sourceEnum[ENUM_ID];\n\n // Validate that all keys to exclude exist in the source enum\n for (const key of keysToExclude) {\n if (!(key in sourceEnum)) {\n throw new Error(\n `Key \"${key}\" does not exist in enum \"${sourceEnumId}\"`\n );\n }\n }\n\n // Create a Set for O(1) lookup of excluded keys\n const excludeSet = new Set<string>(keysToExclude);\n\n // Get all keys from the source enum (excluding Symbol metadata)\n const allKeys = Object.keys(sourceEnum);\n\n // Build the result values object with non-excluded keys\n const resultValues: Record<string, string> = {};\n\n for (const key of allKeys) {\n if (!excludeSet.has(key)) {\n resultValues[key] = (sourceEnum as Record<string, string>)[key];\n }\n }\n\n // Validate that we have at least one key remaining\n if (Object.keys(resultValues).length === 0) {\n throw new Error(\n 'enumExclude: excluding all keys would result in an empty enum'\n );\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, resultValues) as BrandedEnum<\n Omit<E, K> & Record<string, string>\n >;\n}\n\n/**\n * Type representing the result of mapping enum values through a transform function.\n * Preserves the keys but transforms the value types.\n */\ntype MappedEnumValues<E extends Record<string, string>> = {\n [K in keyof E]: string;\n};\n\n/**\n * Creates a new branded enum by transforming all values through a mapper function.\n *\n * This function derives a new enum from an existing branded enum by applying a\n * transformation function to each value. The keys remain unchanged, but the values\n * are transformed according to the provided mapper.\n *\n * Common use cases include:\n * - Prefixing values (e.g., adding a namespace)\n * - Suffixing values (e.g., adding a version)\n * - Case transformation (e.g., uppercase, lowercase)\n * - Custom transformations (e.g., encoding, formatting)\n *\n * The resulting enum is registered as an independent enum in the global registry.\n *\n * @template E - The source branded enum type\n * @param newId - Unique identifier for the new enum. If already registered,\n * returns the existing enum (idempotent behavior).\n * @param sourceEnum - The branded enum to derive from\n * @param mapper - Function that transforms each value. Receives the original value\n * and the key, and returns the transformed value.\n * @returns A new branded enum with transformed values,\n * or the existing enum if newId is already registered\n * @throws {Error} Throws `Error` with message `enumMap requires a branded enum as the source`\n * if sourceEnum is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `enumMap mapper must return a string`\n * if the mapper function returns a non-string value.\n *\n * @example\n * // Prefix all values with a namespace\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const PrefixedStatus = enumMap('prefixed-status', Status, (value) => `app.${value}`);\n * // PrefixedStatus.Active === 'app.active'\n * // PrefixedStatus.Inactive === 'app.inactive'\n *\n * @example\n * // Uppercase all values\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * const UpperColors = enumMap('upper-colors', Colors, (value) => value.toUpperCase());\n * // UpperColors.Red === 'RED'\n * // UpperColors.Green === 'GREEN'\n * // UpperColors.Blue === 'BLUE'\n *\n * @example\n * // Transform with key context\n * const Sizes = createBrandedEnum('sizes', {\n * Small: 's',\n * Medium: 'm',\n * Large: 'l',\n * } as const);\n *\n * const VerboseSizes = enumMap('verbose-sizes', Sizes, (value, key) => `${key.toLowerCase()}-${value}`);\n * // VerboseSizes.Small === 'small-s'\n * // VerboseSizes.Medium === 'medium-m'\n * // VerboseSizes.Large === 'large-l'\n */\nexport function enumMap<E extends AnyBrandedEnum>(\n newId: string,\n sourceEnum: E,\n mapper: (value: string, key: string) => string\n): BrandedEnum<MappedEnumValues<E> & Record<string, string>> {\n // Validate that sourceEnum is a branded enum\n if (!isBrandedEnum(sourceEnum)) {\n throw new Error('enumMap requires a branded enum as the source');\n }\n\n // Get all keys from the source enum (excluding Symbol metadata)\n const allKeys = Object.keys(sourceEnum);\n\n // Build the result values object with transformed values\n const resultValues: Record<string, string> = {};\n\n for (const key of allKeys) {\n const originalValue = sourceEnum[key as keyof E] as string;\n const transformedValue = mapper(originalValue, key);\n\n // Validate that the mapper returned a string\n if (typeof transformedValue !== 'string') {\n throw new Error('enumMap mapper must return a string');\n }\n\n resultValues[key] = transformedValue;\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, resultValues) as BrandedEnum<\n MappedEnumValues<E> & Record<string, string>\n >;\n}\n\n/**\n * Type representing an enum where each key maps to itself as a value.\n */\ntype KeysAsValues<K extends readonly string[]> = {\n [P in K[number]]: P;\n};\n\n/**\n * Creates a branded enum from an array of keys where each key equals its value.\n *\n * This is a convenience function for the common pattern where enum keys and values\n * are identical, such as `{ Active: 'Active', Inactive: 'Inactive' }`.\n *\n * The resulting enum is registered as an independent enum in the global registry.\n * Type safety is maintained - the resulting enum's type reflects the exact literal\n * types of the provided keys.\n *\n * @template K - The array of string keys (use `as const` for literal types)\n * @param enumId - Unique identifier for the new enum. If already registered,\n * returns the existing enum (idempotent behavior).\n * @param keys - Array of strings that will become both keys and values.\n * Use `as const` for literal type inference.\n * @returns A new branded enum where each key maps to itself,\n * or the existing enum if enumId is already registered\n * @throws {Error} Throws `Error` with message `enumFromKeys requires at least one key`\n * if keys array is empty.\n * @throws {Error} Throws `Error` with message `enumFromKeys requires all keys to be non-empty strings`\n * if any key is not a non-empty string.\n * @throws {Error} Throws `Error` with message `enumFromKeys: duplicate key \"${key}\" found`\n * if the keys array contains duplicates.\n *\n * @example\n * // Basic usage - create enum from string array\n * const Status = enumFromKeys('status', ['Active', 'Inactive', 'Pending'] as const);\n * // Equivalent to: { Active: 'Active', Inactive: 'Inactive', Pending: 'Pending' }\n *\n * Status.Active; // 'Active'\n * Status.Inactive; // 'Inactive'\n * Status.Pending; // 'Pending'\n *\n * @example\n * // Type inference with as const\n * const Colors = enumFromKeys('colors', ['Red', 'Green', 'Blue'] as const);\n * type ColorValue = typeof Colors[keyof typeof Colors];\n * // ColorValue = 'Red' | 'Green' | 'Blue'\n *\n * @example\n * // Useful for string literal unions\n * const Directions = enumFromKeys('directions', ['North', 'South', 'East', 'West'] as const);\n *\n * function move(direction: typeof Directions[keyof typeof Directions]) {\n * // direction is 'North' | 'South' | 'East' | 'West'\n * }\n *\n * move(Directions.North); // OK\n * move('North'); // Also OK due to literal type\n *\n * @example\n * // Error handling\n * try {\n * enumFromKeys('empty', []); // Empty array\n * } catch (e) {\n * console.log(e.message);\n * // 'enumFromKeys requires at least one key'\n * }\n */\nexport function enumFromKeys<K extends readonly string[]>(\n enumId: string,\n keys: K\n): BrandedEnum<KeysAsValues<K> & Record<string, string>> {\n // Validate that keys array is not empty\n if (keys.length === 0) {\n throw new Error('enumFromKeys requires at least one key');\n }\n\n // Track seen keys to detect duplicates\n const seenKeys = new Set<string>();\n\n // Build the values object where each key maps to itself\n const values: Record<string, string> = {};\n\n for (const key of keys) {\n // Validate that each key is a non-empty string\n if (typeof key !== 'string' || key.length === 0) {\n throw new Error('enumFromKeys requires all keys to be non-empty strings');\n }\n\n // Check for duplicate keys\n if (seenKeys.has(key)) {\n throw new Error(`enumFromKeys: duplicate key \"${key}\" found`);\n }\n\n seenKeys.add(key);\n values[key] = key;\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(enumId, values) as BrandedEnum<\n KeysAsValues<K> & Record<string, string>\n >;\n}\n\n/**\n * Result of comparing two branded enums with enumDiff.\n */\nexport interface EnumDiffResult {\n /** Keys that exist only in the first enum */\n readonly onlyInFirst: ReadonlyArray<{ key: string; value: string }>;\n /** Keys that exist only in the second enum */\n readonly onlyInSecond: ReadonlyArray<{ key: string; value: string }>;\n /** Keys that exist in both enums but have different values */\n readonly differentValues: ReadonlyArray<{\n key: string;\n firstValue: string;\n secondValue: string;\n }>;\n /** Keys that exist in both enums with the same values */\n readonly sameValues: ReadonlyArray<{ key: string; value: string }>;\n}\n\n/**\n * Compares two branded enums and returns their differences.\n *\n * This function analyzes two branded enums and categorizes their keys into:\n * - Keys only in the first enum\n * - Keys only in the second enum\n * - Keys in both with different values\n * - Keys in both with the same values\n *\n * Useful for:\n * - Migration: Identifying what changed between enum versions\n * - Debugging: Understanding differences between similar enums\n * - Validation: Ensuring enums have expected overlap or differences\n *\n * @template E1 - The first branded enum type\n * @template E2 - The second branded enum type\n * @param firstEnum - The first branded enum to compare\n * @param secondEnum - The second branded enum to compare\n * @returns An EnumDiffResult object containing categorized differences\n * @throws {Error} Throws `Error` with message `enumDiff requires branded enums as arguments`\n * if either argument is not a valid branded enum.\n *\n * @example\n * // Compare two versions of a status enum\n * const StatusV1 = createBrandedEnum('status-v1', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const StatusV2 = createBrandedEnum('status-v2', {\n * Active: 'active',\n * Inactive: 'disabled', // Changed value\n * Pending: 'pending', // New key\n * } as const);\n *\n * const diff = enumDiff(StatusV1, StatusV2);\n * // diff.onlyInFirst: []\n * // diff.onlyInSecond: [{ key: 'Pending', value: 'pending' }]\n * // diff.differentValues: [{ key: 'Inactive', firstValue: 'inactive', secondValue: 'disabled' }]\n * // diff.sameValues: [{ key: 'Active', value: 'active' }]\n *\n * @example\n * // Find keys removed between versions\n * const ColorsOld = createBrandedEnum('colors-old', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * const ColorsNew = createBrandedEnum('colors-new', {\n * Red: 'red',\n * Blue: 'blue',\n * } as const);\n *\n * const diff = enumDiff(ColorsOld, ColorsNew);\n * // diff.onlyInFirst: [{ key: 'Green', value: 'green' }]\n * // diff.onlyInSecond: []\n *\n * @example\n * // Check if enums are identical\n * const diff = enumDiff(enumA, enumB);\n * const areIdentical = diff.onlyInFirst.length === 0 &&\n * diff.onlyInSecond.length === 0 &&\n * diff.differentValues.length === 0;\n */\nexport function enumDiff(\n firstEnum: AnyBrandedEnum,\n secondEnum: AnyBrandedEnum\n): EnumDiffResult {\n // Validate that both arguments are branded enums\n if (!isBrandedEnum(firstEnum) || !isBrandedEnum(secondEnum)) {\n throw new Error('enumDiff requires branded enums as arguments');\n }\n\n // Get all keys from both enums (excluding Symbol metadata)\n const firstKeys = new Set(Object.keys(firstEnum));\n const secondKeys = new Set(Object.keys(secondEnum));\n\n const onlyInFirst: Array<{ key: string; value: string }> = [];\n const onlyInSecond: Array<{ key: string; value: string }> = [];\n const differentValues: Array<{\n key: string;\n firstValue: string;\n secondValue: string;\n }> = [];\n const sameValues: Array<{ key: string; value: string }> = [];\n\n // Find keys only in first enum and keys in both\n for (const key of firstKeys) {\n const firstValue = (firstEnum as Record<string, string>)[key];\n\n if (!secondKeys.has(key)) {\n // Key only exists in first enum\n onlyInFirst.push({ key, value: firstValue });\n } else {\n // Key exists in both - compare values\n const secondValue = (secondEnum as Record<string, string>)[key];\n\n if (firstValue === secondValue) {\n sameValues.push({ key, value: firstValue });\n } else {\n differentValues.push({ key, firstValue, secondValue });\n }\n }\n }\n\n // Find keys only in second enum\n for (const key of secondKeys) {\n if (!firstKeys.has(key)) {\n const secondValue = (secondEnum as Record<string, string>)[key];\n onlyInSecond.push({ key, value: secondValue });\n }\n }\n\n return {\n onlyInFirst,\n onlyInSecond,\n differentValues,\n sameValues,\n };\n}\n\n/**\n * Result entry for a shared value found across multiple enums.\n */\nexport interface EnumIntersectEntry {\n /** The shared value that exists in multiple enums */\n readonly value: string;\n /** Array of enum IDs that contain this value */\n readonly enumIds: readonly string[];\n}\n\n/**\n * Finds values that exist in multiple branded enums.\n *\n * This function analyzes multiple branded enums and identifies values that\n * appear in more than one enum. For each shared value, it returns the value\n * along with the IDs of all enums that contain it.\n *\n * Useful for:\n * - Detecting value collisions across enums\n * - Finding common values for potential refactoring\n * - Debugging i18n key conflicts\n * - Identifying intentional value overlaps\n *\n * @param enums - Array of branded enums to analyze for intersections\n * @returns Array of EnumIntersectEntry objects, each containing a shared value\n * and the IDs of enums containing that value. Only values appearing in 2+\n * enums are included. Results are sorted by value for consistent ordering.\n * @throws {Error} Throws `Error` with message `enumIntersect requires at least two branded enums`\n * if fewer than two enums are provided.\n * @throws {Error} Throws `Error` with message `enumIntersect requires all arguments to be branded enums`\n * if any argument is not a valid branded enum.\n *\n * @example\n * // Find shared values between color enums\n * const PrimaryColors = createBrandedEnum('primary', {\n * Red: 'red',\n * Blue: 'blue',\n * Yellow: 'yellow',\n * } as const);\n *\n * const WarmColors = createBrandedEnum('warm', {\n * Red: 'red',\n * Orange: 'orange',\n * Yellow: 'yellow',\n * } as const);\n *\n * const shared = enumIntersect(PrimaryColors, WarmColors);\n * // shared = [\n * // { value: 'red', enumIds: ['primary', 'warm'] },\n * // { value: 'yellow', enumIds: ['primary', 'warm'] }\n * // ]\n *\n * @example\n * // Detect i18n key collisions across multiple libraries\n * const LibAKeys = createBrandedEnum('lib-a', {\n * Submit: 'submit',\n * Cancel: 'cancel',\n * } as const);\n *\n * const LibBKeys = createBrandedEnum('lib-b', {\n * Submit: 'submit',\n * Reset: 'reset',\n * } as const);\n *\n * const LibCKeys = createBrandedEnum('lib-c', {\n * Submit: 'submit',\n * Clear: 'clear',\n * } as const);\n *\n * const collisions = enumIntersect(LibAKeys, LibBKeys, LibCKeys);\n * // collisions = [\n * // { value: 'submit', enumIds: ['lib-a', 'lib-b', 'lib-c'] }\n * // ]\n *\n * @example\n * // Check if enums have any overlap\n * const shared = enumIntersect(enumA, enumB);\n * if (shared.length === 0) {\n * console.log('No shared values between enums');\n * }\n */\nexport function enumIntersect(\n ...enums: AnyBrandedEnum[]\n): EnumIntersectEntry[] {\n // Validate that at least two enums are provided\n if (enums.length < 2) {\n throw new Error('enumIntersect requires at least two branded enums');\n }\n\n // Validate that all arguments are branded enums\n for (const enumObj of enums) {\n if (!isBrandedEnum(enumObj)) {\n throw new Error('enumIntersect requires all arguments to be branded enums');\n }\n }\n\n // Build a map of value -> Set of enum IDs containing that value\n const valueToEnumIds = new Map<string, Set<string>>();\n\n for (const enumObj of enums) {\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n\n for (const value of values) {\n if (!valueToEnumIds.has(value)) {\n valueToEnumIds.set(value, new Set());\n }\n valueToEnumIds.get(value)!.add(enumId);\n }\n }\n\n // Filter to only values that appear in multiple enums\n const result: EnumIntersectEntry[] = [];\n\n for (const [value, enumIds] of valueToEnumIds) {\n if (enumIds.size >= 2) {\n result.push({\n value,\n enumIds: Array.from(enumIds).sort(),\n });\n }\n }\n\n // Sort by value for consistent ordering\n result.sort((a, b) => a.value.localeCompare(b.value));\n\n return result;\n}\n\n/**\n * Converts a branded enum to a plain Record object, stripping all metadata.\n *\n * This function creates a new plain object containing only the key-value pairs\n * from the branded enum, without any Symbol metadata properties. The result is\n * a simple `Record<string, string>` that can be safely serialized, spread, or\n * used in contexts where branded enum metadata is not needed.\n *\n * Useful for:\n * - Serialization scenarios where you need a plain object\n * - Interoperability with APIs that expect plain objects\n * - Creating snapshots of enum state without metadata\n * - Passing enum data to external systems\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to convert\n * @returns A plain Record<string, string> containing only the key-value pairs\n * @throws {Error} Throws `Error` with message `enumToRecord requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Basic usage - convert to plain object\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const plainStatus = enumToRecord(Status);\n * // plainStatus = { Active: 'active', Inactive: 'inactive', Pending: 'pending' }\n * // No Symbol metadata, just a plain object\n *\n * @example\n * // Serialization scenario\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * // Send to an API that expects plain objects\n * const payload = {\n * availableColors: enumToRecord(Colors),\n * };\n * await fetch('/api/config', {\n * method: 'POST',\n * body: JSON.stringify(payload),\n * });\n *\n * @example\n * // Comparing with spread operator\n * const Status = createBrandedEnum('status', { Active: 'active' } as const);\n *\n * // Both produce the same result for enumerable properties:\n * const spread = { ...Status };\n * const record = enumToRecord(Status);\n * // spread and record are equivalent plain objects\n *\n * // But enumToRecord is explicit about intent and validates input\n *\n * @example\n * // Type safety\n * const Sizes = createBrandedEnum('sizes', {\n * Small: 's',\n * Medium: 'm',\n * Large: 'l',\n * } as const);\n *\n * const record = enumToRecord(Sizes);\n * // record type is Record<string, string>\n * // Can be used anywhere a plain object is expected\n */\nexport function enumToRecord<E extends AnyBrandedEnum>(\n enumObj: E\n): Record<string, string> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('enumToRecord requires a branded enum');\n }\n\n // Create a new plain object with only the enumerable key-value pairs\n const result: Record<string, string> = {};\n\n for (const key of Object.keys(enumObj)) {\n result[key] = enumObj[key as keyof E] as string;\n }\n\n return result;\n}\n\n/**\n * The key used to store the enum watcher registry on globalThis.\n * Namespaced to avoid collisions with other libraries.\n */\nconst WATCHER_REGISTRY_KEY = '__brandedEnumWatcherRegistry__' as const;\n\n/**\n * Type of access event that triggered the callback.\n */\nexport type EnumAccessType = 'get' | 'has' | 'keys' | 'values' | 'entries';\n\n/**\n * Information about an enum access event.\n */\nexport interface EnumAccessEvent {\n /** The enum ID of the accessed enum */\n readonly enumId: string;\n /** The type of access that occurred */\n readonly accessType: EnumAccessType;\n /** The key that was accessed (for 'get' and 'has' types) */\n readonly key?: string;\n /** The value that was returned (for 'get' type) */\n readonly value?: string;\n /** Timestamp of the access */\n readonly timestamp: number;\n}\n\n/**\n * Callback function type for enum access events.\n */\nexport type EnumWatchCallback = (event: EnumAccessEvent) => void;\n\n/**\n * Internal registry for enum watchers.\n */\ninterface EnumWatcherRegistry {\n /** Map from enum ID to Set of callbacks */\n readonly watchers: Map<string, Set<EnumWatchCallback>>;\n /** Global callbacks that receive all enum access events */\n readonly globalWatchers: Set<EnumWatchCallback>;\n}\n\n/**\n * Gets or initializes the watcher registry on globalThis.\n */\nfunction getWatcherRegistry(): EnumWatcherRegistry {\n const global = globalThis as typeof globalThis & {\n [WATCHER_REGISTRY_KEY]?: EnumWatcherRegistry;\n };\n\n if (!global[WATCHER_REGISTRY_KEY]) {\n global[WATCHER_REGISTRY_KEY] = {\n watchers: new Map(),\n globalWatchers: new Set(),\n };\n }\n\n return global[WATCHER_REGISTRY_KEY];\n}\n\n/**\n * Result of calling watchEnum, containing the watched proxy and unwatch function.\n */\nexport interface WatchEnumResult<E extends AnyBrandedEnum> {\n /** The proxied enum that triggers callbacks on access */\n readonly watched: E;\n /** Function to remove the watcher and stop receiving callbacks */\n readonly unwatch: () => void;\n}\n\n/**\n * Creates a watched version of a branded enum that triggers callbacks on access.\n *\n * This function wraps a branded enum in a Proxy that intercepts property access\n * and calls registered callbacks. This is useful for debugging, development tooling,\n * and understanding how enums are used throughout an application.\n *\n * The watched enum behaves identically to the original enum - all values, metadata,\n * and type information are preserved. The only difference is that access events\n * are reported to the callback.\n *\n * **Note:** This feature is intended for development and debugging purposes.\n * Using watched enums in production may have performance implications due to\n * the Proxy overhead.\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to watch\n * @param callback - Function called whenever the enum is accessed\n * @returns An object containing the watched enum proxy and an unwatch function\n * @throws {Error} Throws `Error` with message `watchEnum requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Basic usage - log all enum accesses\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const { watched, unwatch } = watchEnum(Status, (event) => {\n * console.log(`Accessed ${event.enumId}.${event.key} = ${event.value}`);\n * });\n *\n * watched.Active; // Logs: \"Accessed status.Active = active\"\n * watched.Inactive; // Logs: \"Accessed status.Inactive = inactive\"\n *\n * // Stop watching\n * unwatch();\n * watched.Active; // No longer logs\n *\n * @example\n * // Track enum usage for debugging\n * const accessLog: EnumAccessEvent[] = [];\n *\n * const { watched: WatchedColors } = watchEnum(Colors, (event) => {\n * accessLog.push(event);\n * });\n *\n * // Use the watched enum in your code\n * doSomething(WatchedColors.Red);\n * doSomethingElse(WatchedColors.Blue);\n *\n * // Later, analyze the access log\n * console.log(`Enum accessed ${accessLog.length} times`);\n * console.log('Keys accessed:', accessLog.map(e => e.key));\n *\n * @example\n * // Detect unused enum values\n * const usedKeys = new Set<string>();\n *\n * const { watched } = watchEnum(MyEnum, (event) => {\n * if (event.key) usedKeys.add(event.key);\n * });\n *\n * // After running your application/tests\n * const allKeys = Object.keys(MyEnum);\n * const unusedKeys = allKeys.filter(k => !usedKeys.has(k));\n * console.log('Unused enum keys:', unusedKeys);\n *\n * @example\n * // Performance monitoring\n * const { watched } = watchEnum(Status, (event) => {\n * if (event.accessType === 'get') {\n * performance.mark(`enum-access-${event.enumId}-${event.key}`);\n * }\n * });\n */\nexport function watchEnum<E extends AnyBrandedEnum>(\n enumObj: E,\n callback: EnumWatchCallback\n): WatchEnumResult<E> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('watchEnum requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const registry = getWatcherRegistry();\n\n // Add callback to the registry for this enum\n if (!registry.watchers.has(enumId)) {\n registry.watchers.set(enumId, new Set());\n }\n registry.watchers.get(enumId)!.add(callback);\n\n // Track if this watcher is still active\n let isActive = true;\n\n // Create a Proxy to intercept access\n const proxy = new Proxy(enumObj, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n // Only trigger callback for active watchers and string keys (not symbols)\n if (isActive && typeof prop === 'string') {\n const event: EnumAccessEvent = {\n enumId,\n accessType: 'get',\n key: prop,\n value: typeof value === 'string' ? value : undefined,\n timestamp: Date.now(),\n };\n\n // Call the specific callback\n callback(event);\n\n // Also call any global watchers\n for (const globalCallback of registry.globalWatchers) {\n globalCallback(event);\n }\n }\n\n return value;\n },\n\n has(target, prop) {\n const result = Reflect.has(target, prop);\n\n // Only trigger callback for active watchers and string keys\n if (isActive && typeof prop === 'string') {\n const event: EnumAccessEvent = {\n enumId,\n accessType: 'has',\n key: prop,\n timestamp: Date.now(),\n };\n\n callback(event);\n\n for (const globalCallback of registry.globalWatchers) {\n globalCallback(event);\n }\n }\n\n return result;\n },\n\n ownKeys(target) {\n const keys = Reflect.ownKeys(target);\n\n if (isActive) {\n const event: EnumAccessEvent = {\n enumId,\n accessType: 'keys',\n timestamp: Date.now(),\n };\n\n callback(event);\n\n for (const globalCallback of registry.globalWatchers) {\n globalCallback(event);\n }\n }\n\n return keys;\n },\n\n getOwnPropertyDescriptor(target, prop) {\n return Reflect.getOwnPropertyDescriptor(target, prop);\n },\n });\n\n // Create unwatch function\n const unwatch = () => {\n isActive = false;\n const callbacks = registry.watchers.get(enumId);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n registry.watchers.delete(enumId);\n }\n }\n };\n\n return {\n watched: proxy as E,\n unwatch,\n };\n}\n\n/**\n * Registers a global callback that receives access events from all watched enums.\n *\n * This is useful for centralized logging or monitoring of enum usage across\n * an entire application without needing to wrap each enum individually.\n *\n * @param callback - Function called for every enum access event\n * @returns A function to unregister the global callback\n *\n * @example\n * // Centralized enum access logging\n * const unregister = watchAllEnums((event) => {\n * console.log(`[${event.enumId}] ${event.accessType}: ${event.key}`);\n * });\n *\n * // All watched enums will now trigger this callback\n * watchedStatus.Active; // Logs: \"[status] get: Active\"\n * watchedColors.Red; // Logs: \"[colors] get: Red\"\n *\n * // Stop global watching\n * unregister();\n */\nexport function watchAllEnums(callback: EnumWatchCallback): () => void {\n const registry = getWatcherRegistry();\n registry.globalWatchers.add(callback);\n\n return () => {\n registry.globalWatchers.delete(callback);\n };\n}\n\n/**\n * Clears all enum watchers (both specific and global).\n *\n * This is primarily useful for testing or when you need to reset\n * the watcher state completely.\n *\n * @example\n * // In test cleanup\n * afterEach(() => {\n * clearAllEnumWatchers();\n * });\n */\nexport function clearAllEnumWatchers(): void {\n const registry = getWatcherRegistry();\n registry.watchers.clear();\n registry.globalWatchers.clear();\n}\n\n/**\n * Gets the number of active watchers for a specific enum.\n *\n * @param enumId - The enum ID to check\n * @returns The number of active watchers for this enum\n *\n * @example\n * const { watched } = watchEnum(Status, callback1);\n * watchEnum(Status, callback2);\n *\n * getEnumWatcherCount('status'); // 2\n */\nexport function getEnumWatcherCount(enumId: string): number {\n const registry = getWatcherRegistry();\n return registry.watchers.get(enumId)?.size ?? 0;\n}\n\n/**\n * Gets the number of global watchers.\n *\n * @returns The number of active global watchers\n */\nexport function getGlobalWatcherCount(): number {\n const registry = getWatcherRegistry();\n return registry.globalWatchers.size;\n}\n\n\n/**\n * A helper function for exhaustiveness checking in switch statements.\n *\n * This function should be called in the `default` case of a switch statement\n * when you want to ensure all cases are handled. If the switch is exhaustive,\n * TypeScript will infer that this function is never called (the value is `never`).\n * If a case is missing, TypeScript will show a compile-time error.\n *\n * At runtime, if this function is somehow called (e.g., due to a type assertion\n * or JavaScript interop), it throws a descriptive error.\n *\n * @param value - The value that should be unreachable. TypeScript should infer this as `never`.\n * @param message - Optional custom error message. Defaults to a descriptive message including the value.\n * @returns Never returns - always throws an error if called at runtime.\n * @throws {Error} Throws `Error` with message `Exhaustive check failed: unexpected value \"${value}\"`\n * or the custom message if provided.\n *\n * @example\n * // Basic usage with a branded enum\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * type StatusValue = typeof Status[keyof typeof Status];\n *\n * function handleStatus(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'User is active';\n * case Status.Inactive:\n * return 'User is inactive';\n * case Status.Pending:\n * return 'User is pending';\n * default:\n * // TypeScript knows this is unreachable if all cases are handled\n * return exhaustive(status);\n * }\n * }\n *\n * @example\n * // Compile-time error when a case is missing\n * function handleStatusIncomplete(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'User is active';\n * case Status.Inactive:\n * return 'User is inactive';\n * // Missing: case Status.Pending\n * default:\n * // TypeScript error: Argument of type '\"pending\"' is not assignable to parameter of type 'never'\n * return exhaustive(status);\n * }\n * }\n *\n * @example\n * // Custom error message\n * function processValue(value: StatusValue): void {\n * switch (value) {\n * case Status.Active:\n * case Status.Inactive:\n * case Status.Pending:\n * console.log('Handled:', value);\n * break;\n * default:\n * exhaustive(value, `Unknown status value encountered: ${value}`);\n * }\n * }\n *\n * @example\n * // Works with any discriminated union, not just branded enums\n * type Shape =\n * | { kind: 'circle'; radius: number }\n * | { kind: 'square'; side: number }\n * | { kind: 'rectangle'; width: number; height: number };\n *\n * function getArea(shape: Shape): number {\n * switch (shape.kind) {\n * case 'circle':\n * return Math.PI * shape.radius ** 2;\n * case 'square':\n * return shape.side ** 2;\n * case 'rectangle':\n * return shape.width * shape.height;\n * default:\n * return exhaustive(shape);\n * }\n * }\n */\nexport function exhaustive(value: never, message?: string): never {\n const errorMessage =\n message ?? `Exhaustive check failed: unexpected value \"${String(value)}\"`;\n throw new Error(errorMessage);\n}\n\n/**\n * Creates an exhaustiveness guard function bound to a specific branded enum.\n *\n * This factory function returns a guard function that can be used in switch\n * statement default cases. The returned function provides better error messages\n * by including the enum ID in the error.\n *\n * This is useful when you want to:\n * - Have consistent error messages that include the enum name\n * - Create reusable guards for specific enums\n * - Provide more context in error messages for debugging\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to create a guard for\n * @returns A function that throws an error with the enum ID included in the message\n * @throws {Error} Throws `Error` with message `exhaustiveGuard requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Create a guard for a specific enum\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const assertStatusExhaustive = exhaustiveGuard(Status);\n *\n * type StatusValue = typeof Status[keyof typeof Status];\n *\n * function handleStatus(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'Active';\n * case Status.Inactive:\n * return 'Inactive';\n * case Status.Pending:\n * return 'Pending';\n * default:\n * // Error message will include \"status\" enum ID\n * return assertStatusExhaustive(status);\n * }\n * }\n *\n * @example\n * // Inline usage without storing the guard\n * function processStatus(status: StatusValue): void {\n * switch (status) {\n * case Status.Active:\n * case Status.Inactive:\n * case Status.Pending:\n * console.log('Processing:', status);\n * break;\n * default:\n * exhaustiveGuard(Status)(status);\n * }\n * }\n *\n * @example\n * // Runtime error message example\n * // If somehow called with an unexpected value at runtime:\n * // Error: Exhaustive check failed for enum \"status\": unexpected value \"unknown\"\n *\n * @example\n * // Compile-time error when case is missing\n * function incompleteHandler(status: StatusValue): string {\n * switch (status) {\n * case Status.Active:\n * return 'Active';\n * // Missing: Inactive and Pending cases\n * default:\n * // TypeScript error: Argument of type '\"inactive\" | \"pending\"' is not assignable to parameter of type 'never'\n * return assertStatusExhaustive(status);\n * }\n * }\n */\nexport function exhaustiveGuard<E extends AnyBrandedEnum>(\n enumObj: E\n): (value: never) => never {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('exhaustiveGuard requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n\n return (value: never): never => {\n throw new Error(\n `Exhaustive check failed for enum \"${enumId}\": unexpected value \"${String(value)}\"`\n );\n };\n}\n\n// =============================================================================\n// JSON Schema Generation\n// =============================================================================\n\n/**\n * Options for customizing JSON Schema generation.\n */\nexport interface ToJsonSchemaOptions {\n /**\n * Custom title for the schema. Defaults to the enum ID.\n */\n readonly title?: string;\n\n /**\n * Description for the schema. If not provided, a default description\n * mentioning the enum ID will be used.\n */\n readonly description?: string;\n\n /**\n * Whether to include the $schema property. Defaults to true.\n */\n readonly includeSchema?: boolean;\n\n /**\n * The JSON Schema draft version to use. Defaults to 'draft-07'.\n */\n readonly schemaVersion?: 'draft-04' | 'draft-06' | 'draft-07' | '2019-09' | '2020-12';\n}\n\n/**\n * JSON Schema representation of a branded enum.\n *\n * This interface represents the structure of the generated JSON Schema,\n * following the JSON Schema specification.\n */\nexport interface EnumJsonSchema {\n /**\n * The JSON Schema version URI (if includeSchema is true).\n */\n readonly $schema?: string;\n\n /**\n * The title of the schema (typically the enum ID).\n */\n readonly title: string;\n\n /**\n * Description of the schema.\n */\n readonly description: string;\n\n /**\n * The type constraint - always 'string' for branded enums.\n */\n readonly type: 'string';\n\n /**\n * The enum constraint containing all valid values.\n */\n readonly enum: readonly string[];\n}\n\n/**\n * Maps schema version options to their corresponding $schema URIs.\n */\nconst SCHEMA_VERSION_URIS: Record<NonNullable<ToJsonSchemaOptions['schemaVersion']>, string> = {\n 'draft-04': 'http://json-schema.org/draft-04/schema#',\n 'draft-06': 'http://json-schema.org/draft-06/schema#',\n 'draft-07': 'http://json-schema.org/draft-07/schema#',\n '2019-09': 'https://json-schema.org/draft/2019-09/schema',\n '2020-12': 'https://json-schema.org/draft/2020-12/schema',\n};\n\n/**\n * Generates a JSON Schema from a branded enum.\n *\n * This function creates a JSON Schema object that validates strings against\n * the values of a branded enum. The generated schema can be used with any\n * JSON Schema validator to ensure that input values are valid enum members.\n *\n * The schema includes:\n * - `$schema`: The JSON Schema version URI (optional, defaults to draft-07)\n * - `title`: The enum ID or a custom title\n * - `description`: A description of the enum\n * - `type`: Always 'string' for branded enums\n * - `enum`: An array of all valid enum values\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to generate a schema for\n * @param options - Optional configuration for schema generation\n * @returns A JSON Schema object that validates against the enum values\n * @throws {Error} Throws `Error` with message `toJsonSchema requires a branded enum`\n * if enumObj is not a valid branded enum.\n *\n * @example\n * // Basic usage - generate schema with defaults\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const schema = toJsonSchema(Status);\n * // {\n * // $schema: 'http://json-schema.org/draft-07/schema#',\n * // title: 'status',\n * // description: 'Enum values for status',\n * // type: 'string',\n * // enum: ['active', 'inactive', 'pending']\n * // }\n *\n * @example\n * // Custom title and description\n * const Priority = createBrandedEnum('priority', {\n * High: 'high',\n * Medium: 'medium',\n * Low: 'low',\n * } as const);\n *\n * const schema = toJsonSchema(Priority, {\n * title: 'Task Priority',\n * description: 'The priority level of a task',\n * });\n * // {\n * // $schema: 'http://json-schema.org/draft-07/schema#',\n * // title: 'Task Priority',\n * // description: 'The priority level of a task',\n * // type: 'string',\n * // enum: ['high', 'medium', 'low']\n * // }\n *\n * @example\n * // Without $schema property\n * const schema = toJsonSchema(Status, { includeSchema: false });\n * // {\n * // title: 'status',\n * // description: 'Enum values for status',\n * // type: 'string',\n * // enum: ['active', 'inactive', 'pending']\n * // }\n *\n * @example\n * // Using a different schema version\n * const schema = toJsonSchema(Status, { schemaVersion: '2020-12' });\n * // {\n * // $schema: 'https://json-schema.org/draft/2020-12/schema',\n * // title: 'status',\n * // description: 'Enum values for status',\n * // type: 'string',\n * // enum: ['active', 'inactive', 'pending']\n * // }\n *\n * @example\n * // Use with JSON Schema validators\n * import Ajv from 'ajv';\n *\n * const schema = toJsonSchema(Status);\n * const ajv = new Ajv();\n * const validate = ajv.compile(schema);\n *\n * validate('active'); // true\n * validate('inactive'); // true\n * validate('unknown'); // false\n *\n * @example\n * // Embed in a larger schema\n * const userSchema = {\n * type: 'object',\n * properties: {\n * name: { type: 'string' },\n * status: toJsonSchema(Status, { includeSchema: false }),\n * },\n * required: ['name', 'status'],\n * };\n *\n * @example\n * // Generate schemas for API documentation\n * const schemas = {\n * Status: toJsonSchema(Status),\n * Priority: toJsonSchema(Priority),\n * };\n *\n * // Export for OpenAPI/Swagger\n * const openApiComponents = {\n * schemas: Object.fromEntries(\n * Object.entries(schemas).map(([name, schema]) => [\n * name,\n * { ...schema, $schema: undefined }, // OpenAPI doesn't use $schema\n * ])\n * ),\n * };\n */\nexport function toJsonSchema<E extends AnyBrandedEnum>(\n enumObj: E,\n options: ToJsonSchemaOptions = {}\n): EnumJsonSchema {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('toJsonSchema requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n\n // Extract options with defaults\n const {\n title = enumId,\n description = `Enum values for ${enumId}`,\n includeSchema = true,\n schemaVersion = 'draft-07',\n } = options;\n\n // Build the schema object\n const schema: EnumJsonSchema = {\n title,\n description,\n type: 'string',\n enum: Array.from(values).sort(),\n };\n\n // Add $schema if requested\n if (includeSchema) {\n return {\n $schema: SCHEMA_VERSION_URIS[schemaVersion],\n ...schema,\n };\n }\n\n return schema;\n}\n\n// =============================================================================\n// Zod Schema Generation\n// =============================================================================\n\n/**\n * Options for customizing Zod schema definition generation.\n */\nexport interface ToZodSchemaOptions {\n /**\n * Optional description to include in the schema definition.\n * When provided, the generated schema will include a `description` field\n * that can be used with Zod's `.describe()` method.\n */\n readonly description?: string;\n}\n\n/**\n * Zod-compatible schema definition for a branded enum.\n *\n * This interface represents a schema definition object that can be used\n * to construct a Zod enum schema without depending on Zod at runtime.\n * The definition follows Zod's internal structure for enum schemas.\n *\n * To use with Zod:\n * ```typescript\n * import { z } from 'zod';\n * const def = toZodSchema(MyEnum);\n * const schema = z.enum(def.values);\n * if (def.description) {\n * schema.describe(def.description);\n * }\n * ```\n */\nexport interface ZodEnumSchemaDefinition<T extends readonly [string, ...string[]]> {\n /**\n * The type identifier for this schema definition.\n * Always 'ZodEnum' to indicate this is an enum schema.\n */\n readonly typeName: 'ZodEnum';\n\n /**\n * The enum values as a readonly tuple.\n * This matches Zod's requirement for `z.enum()` which requires\n * at least one value (hence the `[string, ...string[]]` type).\n */\n readonly values: T;\n\n /**\n * Optional description for the schema.\n * Can be used with Zod's `.describe()` method.\n */\n readonly description?: string;\n\n /**\n * The enum ID from the branded enum.\n * Useful for debugging and documentation purposes.\n */\n readonly enumId: string;\n}\n\n/**\n * Generates a Zod-compatible schema definition from a branded enum.\n *\n * This function creates a schema definition object that can be used to\n * construct a Zod enum schema. The library maintains zero dependencies\n * by returning a definition object rather than a Zod instance.\n *\n * The returned definition includes:\n * - `typeName`: Always 'ZodEnum' to identify the schema type\n * - `values`: A tuple of all enum values (sorted for consistency)\n * - `description`: Optional description for the schema\n * - `enumId`: The branded enum's ID for reference\n *\n * **Zero Dependencies**: This function does not import or depend on Zod.\n * It returns a plain object that you can use to construct a Zod schema\n * in your own code where Zod is available.\n *\n * @template E - The branded enum type\n * @param enumObj - The branded enum to generate a schema definition for\n * @param options - Optional configuration for schema generation\n * @returns A Zod-compatible schema definition object\n * @throws {Error} Throws `Error` with message `toZodSchema requires a branded enum`\n * if enumObj is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `toZodSchema requires an enum with at least one value`\n * if the enum has no values (Zod requires at least one value for z.enum()).\n *\n * @example\n * // Basic usage - generate schema definition\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * const schemaDef = toZodSchema(Status);\n * // {\n * // typeName: 'ZodEnum',\n * // values: ['active', 'inactive', 'pending'],\n * // enumId: 'status'\n * // }\n *\n * @example\n * // Use with Zod to create an actual schema\n * import { z } from 'zod';\n *\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const def = toZodSchema(Status);\n * const statusSchema = z.enum(def.values);\n *\n * // Validate values\n * statusSchema.parse('active'); // 'active'\n * statusSchema.parse('invalid'); // throws ZodError\n *\n * @example\n * // With description\n * const Priority = createBrandedEnum('priority', {\n * High: 'high',\n * Medium: 'medium',\n * Low: 'low',\n * } as const);\n *\n * const def = toZodSchema(Priority, {\n * description: 'Task priority level',\n * });\n * // {\n * // typeName: 'ZodEnum',\n * // values: ['high', 'low', 'medium'],\n * // description: 'Task priority level',\n * // enumId: 'priority'\n * // }\n *\n * // Use with Zod\n * const schema = z.enum(def.values).describe(def.description!);\n *\n * @example\n * // Type-safe schema creation helper\n * import { z } from 'zod';\n *\n * function createZodEnumFromBranded<E extends BrandedEnum<Record<string, string>>>(\n * enumObj: E,\n * description?: string\n * ) {\n * const def = toZodSchema(enumObj, { description });\n * const schema = z.enum(def.values);\n * return description ? schema.describe(description) : schema;\n * }\n *\n * const statusSchema = createZodEnumFromBranded(Status, 'User status');\n *\n * @example\n * // Generate schemas for multiple enums\n * const schemas = {\n * status: toZodSchema(Status),\n * priority: toZodSchema(Priority),\n * category: toZodSchema(Category),\n * };\n *\n * // Later, construct Zod schemas as needed\n * import { z } from 'zod';\n * const zodSchemas = Object.fromEntries(\n * Object.entries(schemas).map(([key, def]) => [key, z.enum(def.values)])\n * );\n *\n * @example\n * // Use in form validation\n * import { z } from 'zod';\n *\n * const statusDef = toZodSchema(Status);\n * const priorityDef = toZodSchema(Priority);\n *\n * const taskSchema = z.object({\n * title: z.string().min(1),\n * status: z.enum(statusDef.values),\n * priority: z.enum(priorityDef.values),\n * });\n *\n * type Task = z.infer<typeof taskSchema>;\n * // { title: string; status: 'active' | 'inactive' | 'pending'; priority: 'high' | 'medium' | 'low' }\n */\nexport function toZodSchema<E extends AnyBrandedEnum>(\n enumObj: E,\n options: ToZodSchemaOptions = {}\n): ZodEnumSchemaDefinition<readonly [string, ...string[]]> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('toZodSchema requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n\n // Convert Set to sorted array\n const valuesArray = Array.from(values).sort();\n\n // Zod's z.enum() requires at least one value\n if (valuesArray.length === 0) {\n throw new Error('toZodSchema requires an enum with at least one value');\n }\n\n // Build the schema definition\n const definition: ZodEnumSchemaDefinition<readonly [string, ...string[]]> = {\n typeName: 'ZodEnum',\n values: valuesArray as unknown as readonly [string, ...string[]],\n enumId,\n };\n\n // Add description if provided\n if (options.description !== undefined) {\n return {\n ...definition,\n description: options.description,\n };\n }\n\n return definition;\n}\n\n// =============================================================================\n// Enum Serializer\n// =============================================================================\n\n/**\n * Options for customizing enum serialization behavior.\n */\nexport interface EnumSerializerOptions<T extends string = string> {\n /**\n * Custom transform function applied during serialization.\n * Transforms the enum value before it is serialized.\n * \n * @param value - The original enum value\n * @returns The transformed value for serialization\n */\n readonly serialize?: (value: T) => string;\n\n /**\n * Custom transform function applied during deserialization.\n * Transforms the serialized value before validation.\n * This is applied BEFORE validation against the enum.\n * \n * @param value - The serialized value\n * @returns The transformed value to validate against the enum\n */\n readonly deserialize?: (value: string) => string;\n}\n\n/**\n * Result of a successful deserialization.\n * \n * @template T - The type of the deserialized value\n */\nexport interface DeserializeSuccess<T> {\n /** Indicates the deserialization was successful */\n readonly success: true;\n /** The validated and deserialized enum value */\n readonly value: T;\n}\n\n/**\n * Result of a failed deserialization.\n */\nexport interface DeserializeFailure {\n /** Indicates the deserialization failed */\n readonly success: false;\n /** Error information about the failure */\n readonly error: {\n /** Human-readable error message */\n readonly message: string;\n /** The input value that failed deserialization */\n readonly input: unknown;\n /** The enum ID (if available) */\n readonly enumId?: string;\n /** The valid values for the enum (if available) */\n readonly validValues?: readonly string[];\n };\n}\n\n/**\n * Union type representing the result of deserialization.\n * \n * @template T - The type of the successfully deserialized value\n */\nexport type DeserializeResult<T> = DeserializeSuccess<T> | DeserializeFailure;\n\n/**\n * A serializer/deserializer pair for branded enum values.\n * \n * Provides methods to serialize enum values (optionally with transformation)\n * and deserialize values back with validation against the enum.\n * \n * @template E - The branded enum type\n */\nexport interface EnumSerializer<E extends AnyBrandedEnum> {\n /**\n * The branded enum this serializer is bound to.\n */\n readonly enumObj: E;\n\n /**\n * The enum ID for reference.\n */\n readonly enumId: string;\n\n /**\n * Serializes an enum value to a string.\n * \n * If a custom serialize transform was provided, it is applied to the value.\n * \n * @param value - The enum value to serialize\n * @returns The serialized string value\n */\n serialize(value: EnumValues<E>): string;\n\n /**\n * Deserializes a string value back to an enum value.\n * \n * If a custom deserialize transform was provided, it is applied before validation.\n * Returns a result object indicating success or failure with detailed error info.\n * \n * @param value - The string value to deserialize\n * @returns A DeserializeResult indicating success or failure\n */\n deserialize(value: unknown): DeserializeResult<EnumValues<E>>;\n\n /**\n * Deserializes a string value, throwing an error if invalid.\n * \n * This is a convenience method that throws instead of returning a result object.\n * \n * @param value - The string value to deserialize\n * @returns The validated enum value\n * @throws Error if the value is not valid for the enum\n */\n deserializeOrThrow(value: unknown): EnumValues<E>;\n}\n\n/**\n * Creates a serializer/deserializer pair for a branded enum.\n * \n * The serializer provides methods to convert enum values to strings (with optional\n * transformation) and to deserialize strings back to validated enum values.\n * \n * This is useful for:\n * - Storing enum values in databases or localStorage with custom formats\n * - Transmitting enum values over APIs with encoding/decoding\n * - Migrating between different value formats\n * - Adding prefixes/suffixes for namespacing during serialization\n * \n * **Serialization Flow:**\n * 1. Take an enum value\n * 2. Apply custom `serialize` transform (if provided)\n * 3. Return the serialized string\n * \n * **Deserialization Flow:**\n * 1. Take a string input\n * 2. Apply custom `deserialize` transform (if provided)\n * 3. Validate the result against the enum\n * 4. Return success with the value, or failure with error details\n * \n * @template E - The branded enum type\n * @param enumObj - The branded enum to create a serializer for\n * @param options - Optional configuration for custom transforms\n * @returns An EnumSerializer object with serialize and deserialize methods\n * @throws {Error} Throws `Error` with message `enumSerializer requires a branded enum`\n * if enumObj is not a valid branded enum.\n * \n * @example\n * // Basic usage without transforms\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n * \n * const serializer = enumSerializer(Status);\n * \n * // Serialize\n * const serialized = serializer.serialize(Status.Active); // 'active'\n * \n * // Deserialize\n * const result = serializer.deserialize('active');\n * if (result.success) {\n * console.log(result.value); // 'active'\n * }\n * \n * @example\n * // With custom transforms - add prefix during serialization\n * const Priority = createBrandedEnum('priority', {\n * High: 'high',\n * Medium: 'medium',\n * Low: 'low',\n * } as const);\n * \n * const serializer = enumSerializer(Priority, {\n * serialize: (value) => `priority:${value}`,\n * deserialize: (value) => value.replace('priority:', ''),\n * });\n * \n * // Serialize adds prefix\n * serializer.serialize(Priority.High); // 'priority:high'\n * \n * // Deserialize removes prefix and validates\n * const result = serializer.deserialize('priority:high');\n * if (result.success) {\n * console.log(result.value); // 'high'\n * }\n * \n * @example\n * // Base64 encoding for storage\n * const Secret = createBrandedEnum('secret', {\n * Token: 'token',\n * Key: 'key',\n * } as const);\n * \n * const serializer = enumSerializer(Secret, {\n * serialize: (value) => btoa(value),\n * deserialize: (value) => atob(value),\n * });\n * \n * serializer.serialize(Secret.Token); // 'dG9rZW4=' (base64 of 'token')\n * serializer.deserialize('dG9rZW4='); // { success: true, value: 'token' }\n * \n * @example\n * // Error handling\n * const result = serializer.deserialize('invalid');\n * if (!result.success) {\n * console.log(result.error.message);\n * // 'Value \"invalid\" is not a member of enum \"status\"'\n * console.log(result.error.validValues);\n * // ['active', 'inactive']\n * }\n * \n * @example\n * // Using deserializeOrThrow for simpler code when errors should throw\n * try {\n * const value = serializer.deserializeOrThrow('active');\n * console.log(value); // 'active'\n * } catch (e) {\n * console.error('Invalid value:', e.message);\n * }\n * \n * @example\n * // Case-insensitive deserialization\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n * \n * const caseInsensitiveSerializer = enumSerializer(Colors, {\n * deserialize: (value) => value.toLowerCase(),\n * });\n * \n * caseInsensitiveSerializer.deserialize('RED'); // { success: true, value: 'red' }\n * caseInsensitiveSerializer.deserialize('Red'); // { success: true, value: 'red' }\n * caseInsensitiveSerializer.deserialize('red'); // { success: true, value: 'red' }\n */\nexport function enumSerializer<E extends AnyBrandedEnum>(\n enumObj: E,\n options: EnumSerializerOptions<EnumValues<E>> = {}\n): EnumSerializer<E> {\n // Validate that enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('enumSerializer requires a branded enum');\n }\n\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n const validValues = Array.from(values).sort();\n\n const { serialize: serializeTransform, deserialize: deserializeTransform } = options;\n\n return {\n enumObj,\n enumId,\n\n serialize(value: EnumValues<E>): string {\n // Apply custom transform if provided\n if (serializeTransform) {\n return serializeTransform(value);\n }\n return value;\n },\n\n deserialize(value: unknown): DeserializeResult<EnumValues<E>> {\n // Check if value is a string\n if (typeof value !== 'string') {\n const valueType = value === null ? 'null' : typeof value;\n return {\n success: false,\n error: {\n message: `Expected a string value, received ${valueType}`,\n input: value,\n enumId,\n validValues,\n },\n };\n }\n\n // Apply custom deserialize transform if provided\n let transformedValue = value;\n if (deserializeTransform) {\n try {\n transformedValue = deserializeTransform(value);\n } catch (e) {\n return {\n success: false,\n error: {\n message: `Deserialize transform failed: ${e instanceof Error ? e.message : String(e)}`,\n input: value,\n enumId,\n validValues,\n },\n };\n }\n }\n\n // Validate against the enum\n if (!values.has(transformedValue)) {\n return {\n success: false,\n error: {\n message: `Value \"${transformedValue}\" is not a member of enum \"${enumId}\"`,\n input: value,\n enumId,\n validValues,\n },\n };\n }\n\n return {\n success: true,\n value: transformedValue as EnumValues<E>,\n };\n },\n\n deserializeOrThrow(value: unknown): EnumValues<E> {\n const result = this.deserialize(value);\n if (!result.success) {\n throw new Error(result.error.message);\n }\n return result.value;\n },\n };\n}\n"],"names":["clearAllEnumWatchers","enumDiff","enumExclude","enumFromKeys","enumIntersect","enumMap","enumSerializer","enumSubset","enumToRecord","exhaustive","exhaustiveGuard","getEnumWatcherCount","getGlobalWatcherCount","toJsonSchema","toZodSchema","watchAllEnums","watchEnum","isBrandedEnum","obj","ENUM_ID","ENUM_VALUES","Set","newId","sourceEnum","keys","Error","length","sourceEnumId","subsetValues","key","createBrandedEnum","keysToExclude","excludeSet","allKeys","Object","resultValues","has","mapper","originalValue","transformedValue","enumId","seenKeys","values","add","firstEnum","secondEnum","firstKeys","secondKeys","onlyInFirst","onlyInSecond","differentValues","sameValues","firstValue","push","value","secondValue","enums","enumObj","valueToEnumIds","Map","set","get","result","enumIds","size","Array","from","sort","a","b","localeCompare","WATCHER_REGISTRY_KEY","getWatcherRegistry","global","globalThis","watchers","globalWatchers","callback","registry","isActive","proxy","Proxy","target","prop","receiver","Reflect","event","accessType","undefined","timestamp","Date","now","globalCallback","ownKeys","getOwnPropertyDescriptor","unwatch","callbacks","delete","watched","clear","message","errorMessage","String","SCHEMA_VERSION_URIS","options","title","description","includeSchema","schemaVersion","schema","type","enum","$schema","valuesArray","definition","typeName","validValues","serialize","serializeTransform","deserialize","deserializeTransform","valueType","success","error","input","e","deserializeOrThrow"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
@@ -16,6 +16,12 @@ Object.defineProperty(exports, "createBrandedEnum", {
16
16
  const _types = require("./types.js");
17
17
  const _registry = require("./registry.js");
18
18
  function createBrandedEnum(enumId, values) {
19
+ // Check if already registered - return existing enum (idempotent)
20
+ const registry = (0, _registry.getRegistry)();
21
+ const existing = registry.enums.get(enumId);
22
+ if (existing) {
23
+ return existing.enumObj;
24
+ }
19
25
  // Create the enum object with user values
20
26
  const enumObj = {
21
27
  ...values
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/factory.ts"],"sourcesContent":["/**\n * Factory function for creating branded enums.\n *\n * Creates enum-like objects with runtime metadata for identification,\n * while keeping values as raw strings for serialization compatibility.\n */\n\nimport {\n BrandedEnum,\n BrandedEnumMetadata,\n ENUM_ID,\n ENUM_VALUES,\n} from './types.js';\nimport { registerEnum } from './registry.js';\n\n/**\n * Creates a branded enum with runtime metadata.\n *\n * The returned object:\n * - Contains all provided key-value pairs as enumerable properties\n * - Has non-enumerable Symbol properties for metadata (ENUM_ID, ENUM_VALUES)\n * - Is frozen to prevent modification\n * - Is registered in the global registry\n *\n * @template T - The shape of the values object (use `as const` for literal types)\n * @param enumId - Unique identifier for this enum. Must be unique across all\n * branded enums in the application.\n * @param values - Object containing key-value pairs where keys are enum member\n * names and values are the string values. Use `as const` for literal type inference.\n * @returns A frozen branded enum object with attached metadata\n * @throws {Error} Throws `Error` with message `Branded enum with ID \"${enumId}\" already exists`\n * if an enum with the same ID has already been registered.\n *\n * @example\n * // Basic usage\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * Status.Active // 'active' (raw string)\n * getEnumId(Status) // 'status'\n *\n * @example\n * // Type inference with as const\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * type ColorValue = typeof Colors[keyof typeof Colors]; // 'red' | 'green' | 'blue'\n *\n * @example\n * // Error handling for duplicate IDs\n * createBrandedEnum('myEnum', { A: 'a' } as const);\n * createBrandedEnum('myEnum', { B: 'b' } as const); // Throws Error\n */\nexport function createBrandedEnum<T extends Record<string, string>>(\n enumId: string,\n values: T\n): BrandedEnum<T> {\n // Create the enum object with user values\n const enumObj = { ...values } as T & BrandedEnumMetadata;\n\n // Collect all values into a Set for O(1) membership checks\n const valueSet = new Set<string>(Object.values(values));\n\n // Attach non-enumerable Symbol properties for metadata\n Object.defineProperty(enumObj, ENUM_ID, {\n value: enumId,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n Object.defineProperty(enumObj, ENUM_VALUES, {\n value: valueSet,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n // Freeze the object to prevent modification\n const frozenEnum = Object.freeze(enumObj) as BrandedEnum<T>;\n\n // Register in global registry (throws if duplicate ID)\n registerEnum(frozenEnum);\n\n return frozenEnum;\n}\n"],"names":["createBrandedEnum","enumId","values","enumObj","valueSet","Set","Object","defineProperty","ENUM_ID","value","enumerable","writable","configurable","ENUM_VALUES","frozenEnum","freeze","registerEnum"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC;;;;+BAqDeA;;;eAAAA;;;uBA9CT;0BACsB;AA6CtB,SAASA,kBACdC,MAAc,EACdC,MAAS;IAET,0CAA0C;IAC1C,MAAMC,UAAU;QAAE,GAAGD,MAAM;IAAC;IAE5B,2DAA2D;IAC3D,MAAME,WAAW,IAAIC,IAAYC,OAAOJ,MAAM,CAACA;IAE/C,uDAAuD;IACvDI,OAAOC,cAAc,CAACJ,SAASK,cAAO,EAAE;QACtCC,OAAOR;QACPS,YAAY;QACZC,UAAU;QACVC,cAAc;IAChB;IAEAN,OAAOC,cAAc,CAACJ,SAASU,kBAAW,EAAE;QAC1CJ,OAAOL;QACPM,YAAY;QACZC,UAAU;QACVC,cAAc;IAChB;IAEA,4CAA4C;IAC5C,MAAME,aAAaR,OAAOS,MAAM,CAACZ;IAEjC,uDAAuD;IACvDa,IAAAA,sBAAY,EAACF;IAEb,OAAOA;AACT"}
1
+ {"version":3,"sources":["../../../src/lib/factory.ts"],"sourcesContent":["/**\n * Factory function for creating branded enums.\n *\n * Creates enum-like objects with runtime metadata for identification,\n * while keeping values as raw strings for serialization compatibility.\n */\n\nimport {\n BrandedEnum,\n BrandedEnumMetadata,\n ENUM_ID,\n ENUM_VALUES,\n} from './types.js';\nimport { getRegistry, registerEnum } from './registry.js';\n\n/**\n * Creates a branded enum with runtime metadata.\n *\n * The returned object:\n * - Contains all provided key-value pairs as enumerable properties\n * - Has non-enumerable Symbol properties for metadata (ENUM_ID, ENUM_VALUES)\n * - Is frozen to prevent modification\n * - Is registered in the global registry\n *\n * This function is idempotent - if an enum with the same ID already exists,\n * the existing enum is returned instead of creating a new one. This enables\n * safe usage in module-scoped code that may be re-executed in test environments\n * or hot-reload scenarios.\n *\n * @template T - The shape of the values object (use `as const` for literal types)\n * @param enumId - Unique identifier for this enum. Must be unique across all\n * branded enums in the application.\n * @param values - Object containing key-value pairs where keys are enum member\n * names and values are the string values. Use `as const` for literal type inference.\n * @returns A frozen branded enum object with attached metadata\n *\n * @example\n * // Basic usage\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * Status.Active // 'active' (raw string)\n * getEnumId(Status) // 'status'\n *\n * @example\n * // Type inference with as const\n * const Colors = createBrandedEnum('colors', {\n * Red: 'red',\n * Green: 'green',\n * Blue: 'blue',\n * } as const);\n *\n * type ColorValue = typeof Colors[keyof typeof Colors]; // 'red' | 'green' | 'blue'\n *\n * @example\n * // Safe re-registration (idempotent)\n * const Enum1 = createBrandedEnum('myEnum', { A: 'a' } as const);\n * const Enum2 = createBrandedEnum('myEnum', { A: 'a' } as const);\n * // Enum1 === Enum2 (same reference)\n */\nexport function createBrandedEnum<T extends Record<string, string>>(\n enumId: string,\n values: T\n): BrandedEnum<T> {\n // Check if already registered - return existing enum (idempotent)\n const registry = getRegistry();\n const existing = registry.enums.get(enumId);\n if (existing) {\n return existing.enumObj as BrandedEnum<T>;\n }\n\n // Create the enum object with user values\n const enumObj = { ...values } as T & BrandedEnumMetadata;\n\n // Collect all values into a Set for O(1) membership checks\n const valueSet = new Set<string>(Object.values(values));\n\n // Attach non-enumerable Symbol properties for metadata\n Object.defineProperty(enumObj, ENUM_ID, {\n value: enumId,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n Object.defineProperty(enumObj, ENUM_VALUES, {\n value: valueSet,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n\n // Freeze the object to prevent modification\n const frozenEnum = Object.freeze(enumObj) as BrandedEnum<T>;\n\n // Register in global registry (throws if duplicate ID)\n registerEnum(frozenEnum);\n\n return frozenEnum;\n}\n"],"names":["createBrandedEnum","enumId","values","registry","getRegistry","existing","enums","get","enumObj","valueSet","Set","Object","defineProperty","ENUM_ID","value","enumerable","writable","configurable","ENUM_VALUES","frozenEnum","freeze","registerEnum"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC;;;;+BAyDeA;;;eAAAA;;;uBAlDT;0BACmC;AAiDnC,SAASA,kBACdC,MAAc,EACdC,MAAS;IAET,kEAAkE;IAClE,MAAMC,WAAWC,IAAAA,qBAAW;IAC5B,MAAMC,WAAWF,SAASG,KAAK,CAACC,GAAG,CAACN;IACpC,IAAII,UAAU;QACZ,OAAOA,SAASG,OAAO;IACzB;IAEA,0CAA0C;IAC1C,MAAMA,UAAU;QAAE,GAAGN,MAAM;IAAC;IAE5B,2DAA2D;IAC3D,MAAMO,WAAW,IAAIC,IAAYC,OAAOT,MAAM,CAACA;IAE/C,uDAAuD;IACvDS,OAAOC,cAAc,CAACJ,SAASK,cAAO,EAAE;QACtCC,OAAOb;QACPc,YAAY;QACZC,UAAU;QACVC,cAAc;IAChB;IAEAN,OAAOC,cAAc,CAACJ,SAASU,kBAAW,EAAE;QAC1CJ,OAAOL;QACPM,YAAY;QACZC,UAAU;QACVC,cAAc;IAChB;IAEA,4CAA4C;IAC5C,MAAME,aAAaR,OAAOS,MAAM,CAACZ;IAEjC,uDAAuD;IACvDa,IAAAA,sBAAY,EAACF;IAEb,OAAOA;AACT"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/merge.ts"],"sourcesContent":["/**\n * Enum composition functions for branded enums.\n *\n * Enables merging multiple branded enums into a new combined enum\n * while maintaining type safety and registry tracking.\n */\n\nimport { AnyBrandedEnum, BrandedEnum, ENUM_ID, ENUM_VALUES } from './types.js';\nimport { createBrandedEnum } from './factory.js';\n\n/**\n * Type helper to extract the values type from a branded enum.\n */\ntype ExtractValues<E> = E extends BrandedEnum<infer T> ? T : never;\n\n/**\n * Type helper to merge multiple branded enum value types into one.\n */\ntype MergedValues<T extends AnyBrandedEnum[]> = {\n [K in keyof T]: ExtractValues<T[K]>;\n}[number];\n\n/**\n * Checks if an object is a branded enum.\n */\nfunction isBrandedEnum(obj: unknown): obj is AnyBrandedEnum {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n ENUM_ID in obj &&\n ENUM_VALUES in obj\n );\n}\n\n/**\n * Merges multiple branded enums into a new branded enum.\n *\n * Creates a new branded enum that contains all key-value pairs from all\n * source enums. The merged enum is registered in the global registry\n * as a new independent enum.\n *\n * Key collision handling:\n * - Duplicate keys (same key in multiple enums) throw an error\n * - Duplicate values (same value in multiple enums) are allowed\n *\n * @template T - Tuple of branded enum types being merged\n * @param newId - Unique identifier for the merged enum. Must not already\n * be registered.\n * @param enums - One or more branded enums to merge\n * @returns A new branded enum containing all values from source enums\n * @throws {Error} Throws `Error` with message\n * `Cannot merge enums: duplicate key \"${key}\" found in enums \"${enumId1}\" and \"${enumId2}\"`\n * if the same key exists in multiple source enums.\n * @throws {Error} Throws `Error` with message\n * `Branded enum with ID \"${newId}\" already exists` if newId is already registered.\n * @throws {Error} Throws `Error` with message `All arguments must be branded enums`\n * if any argument is not a valid branded enum.\n *\n * @example\n * // Basic merge\n * const Colors = createBrandedEnum('colors', { Red: 'red', Blue: 'blue' } as const);\n * const Sizes = createBrandedEnum('sizes', { Small: 'small', Large: 'large' } as const);\n *\n * const Combined = mergeEnums('combined', Colors, Sizes);\n * // Combined has: Red, Blue, Small, Large\n *\n * Combined.Red; // 'red'\n * Combined.Small; // 'small'\n *\n * @example\n * // Duplicate values are allowed\n * const Status1 = createBrandedEnum('status1', { Active: 'active' } as const);\n * const Status2 = createBrandedEnum('status2', { Enabled: 'active' } as const);\n *\n * const Merged = mergeEnums('merged', Status1, Status2);\n * // Both Active and Enabled have value 'active' - this is allowed\n *\n * @example\n * // Duplicate keys throw an error\n * const Enum1 = createBrandedEnum('enum1', { Key: 'value1' } as const);\n * const Enum2 = createBrandedEnum('enum2', { Key: 'value2' } as const);\n *\n * try {\n * mergeEnums('merged', Enum1, Enum2);\n * } catch (e) {\n * console.log(e.message);\n * // 'Cannot merge enums: duplicate key \"Key\" found in enums \"enum1\" and \"enum2\"'\n * }\n */\nexport function mergeEnums<T extends readonly AnyBrandedEnum[]>(\n newId: string,\n ...enums: T\n): BrandedEnum<Record<string, string>> {\n // Collect all key-value pairs, checking for duplicate keys\n const mergedValues: Record<string, string> = {};\n const seenKeys = new Map<string, string>(); // key -> source enumId\n\n for (const enumObj of enums) {\n if (!isBrandedEnum(enumObj)) {\n throw new Error('All arguments must be branded enums');\n }\n\n const sourceEnumId = enumObj[ENUM_ID];\n\n // Iterate over enumerable properties (user-defined keys only)\n for (const [key, value] of Object.entries(enumObj)) {\n // Check for duplicate keys\n if (seenKeys.has(key)) {\n const originalEnumId = seenKeys.get(key);\n throw new Error(\n `Cannot merge enums: duplicate key \"${key}\" found in enums \"${originalEnumId}\" and \"${sourceEnumId}\"`\n );\n }\n\n seenKeys.set(key, sourceEnumId);\n mergedValues[key] = value as string;\n }\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, mergedValues) as BrandedEnum<MergedValues<[...T]>>;\n}\n"],"names":["mergeEnums","isBrandedEnum","obj","ENUM_ID","ENUM_VALUES","newId","enums","mergedValues","seenKeys","Map","enumObj","Error","sourceEnumId","key","value","Object","entries","has","originalEnumId","get","set","createBrandedEnum"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC;;;;+BAoFeA;;;eAAAA;;;uBAlFkD;yBAChC;AAclC;;CAEC,GACD,SAASC,cAAcC,GAAY;IACjC,OACE,OAAOA,QAAQ,YACfA,QAAQ,QACRC,cAAO,IAAID,OACXE,kBAAW,IAAIF;AAEnB;AAyDO,SAASF,WACdK,KAAa,EACb,GAAGC,KAAQ;IAEX,2DAA2D;IAC3D,MAAMC,eAAuC,CAAC;IAC9C,MAAMC,WAAW,IAAIC,OAAuB,uBAAuB;IAEnE,KAAK,MAAMC,WAAWJ,MAAO;QAC3B,IAAI,CAACL,cAAcS,UAAU;YAC3B,MAAM,IAAIC,MAAM;QAClB;QAEA,MAAMC,eAAeF,OAAO,CAACP,cAAO,CAAC;QAErC,8DAA8D;QAC9D,KAAK,MAAM,CAACU,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACN,SAAU;YAClD,2BAA2B;YAC3B,IAAIF,SAASS,GAAG,CAACJ,MAAM;gBACrB,MAAMK,iBAAiBV,SAASW,GAAG,CAACN;gBACpC,MAAM,IAAIF,MACR,CAAC,mCAAmC,EAAEE,IAAI,kBAAkB,EAAEK,eAAe,OAAO,EAAEN,aAAa,CAAC,CAAC;YAEzG;YAEAJ,SAASY,GAAG,CAACP,KAAKD;YAClBL,YAAY,CAACM,IAAI,GAAGC;QACtB;IACF;IAEA,qEAAqE;IACrE,OAAOO,IAAAA,0BAAiB,EAAChB,OAAOE;AAClC"}
1
+ {"version":3,"sources":["../../../src/lib/merge.ts"],"sourcesContent":["/**\n * Enum composition functions for branded enums.\n *\n * Enables merging multiple branded enums into a new combined enum\n * while maintaining type safety and registry tracking.\n */\n\nimport { AnyBrandedEnum, BrandedEnum, ENUM_ID, ENUM_VALUES } from './types.js';\nimport { createBrandedEnum } from './factory.js';\n\n/**\n * Type helper to extract the values type from a branded enum.\n */\ntype ExtractValues<E> = E extends BrandedEnum<infer T> ? T : never;\n\n/**\n * Type helper to merge multiple branded enum value types into one.\n */\ntype MergedValues<T extends AnyBrandedEnum[]> = {\n [K in keyof T]: ExtractValues<T[K]>;\n}[number];\n\n/**\n * Checks if an object is a branded enum.\n */\nfunction isBrandedEnum(obj: unknown): obj is AnyBrandedEnum {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n ENUM_ID in obj &&\n ENUM_VALUES in obj\n );\n}\n\n/**\n * Merges multiple branded enums into a new branded enum.\n *\n * Creates a new branded enum that contains all key-value pairs from all\n * source enums. The merged enum is registered in the global registry\n * as a new independent enum.\n *\n * Key collision handling:\n * - Duplicate keys (same key in multiple enums) throw an error\n * - Duplicate values (same value in multiple enums) are allowed\n *\n * @template T - Tuple of branded enum types being merged\n * @param newId - Unique identifier for the merged enum. If already registered,\n * returns the existing enum (idempotent behavior).\n * @param enums - One or more branded enums to merge\n * @returns A new branded enum containing all values from source enums,\n * or the existing enum if newId is already registered\n * @throws {Error} Throws `Error` with message\n * `Cannot merge enums: duplicate key \"${key}\" found in enums \"${enumId1}\" and \"${enumId2}\"`\n * if the same key exists in multiple source enums.\n * @throws {Error} Throws `Error` with message `All arguments must be branded enums`\n * if any argument is not a valid branded enum.\n *\n * @example\n * // Basic merge\n * const Colors = createBrandedEnum('colors', { Red: 'red', Blue: 'blue' } as const);\n * const Sizes = createBrandedEnum('sizes', { Small: 'small', Large: 'large' } as const);\n *\n * const Combined = mergeEnums('combined', Colors, Sizes);\n * // Combined has: Red, Blue, Small, Large\n *\n * Combined.Red; // 'red'\n * Combined.Small; // 'small'\n *\n * @example\n * // Duplicate values are allowed\n * const Status1 = createBrandedEnum('status1', { Active: 'active' } as const);\n * const Status2 = createBrandedEnum('status2', { Enabled: 'active' } as const);\n *\n * const Merged = mergeEnums('merged', Status1, Status2);\n * // Both Active and Enabled have value 'active' - this is allowed\n *\n * @example\n * // Duplicate keys throw an error\n * const Enum1 = createBrandedEnum('enum1', { Key: 'value1' } as const);\n * const Enum2 = createBrandedEnum('enum2', { Key: 'value2' } as const);\n *\n * try {\n * mergeEnums('merged', Enum1, Enum2);\n * } catch (e) {\n * console.log(e.message);\n * // 'Cannot merge enums: duplicate key \"Key\" found in enums \"enum1\" and \"enum2\"'\n * }\n */\nexport function mergeEnums<T extends readonly AnyBrandedEnum[]>(\n newId: string,\n ...enums: T\n): BrandedEnum<Record<string, string>> {\n // Collect all key-value pairs, checking for duplicate keys\n const mergedValues: Record<string, string> = {};\n const seenKeys = new Map<string, string>(); // key -> source enumId\n\n for (const enumObj of enums) {\n if (!isBrandedEnum(enumObj)) {\n throw new Error('All arguments must be branded enums');\n }\n\n const sourceEnumId = enumObj[ENUM_ID];\n\n // Iterate over enumerable properties (user-defined keys only)\n for (const [key, value] of Object.entries(enumObj)) {\n // Check for duplicate keys\n if (seenKeys.has(key)) {\n const originalEnumId = seenKeys.get(key);\n throw new Error(\n `Cannot merge enums: duplicate key \"${key}\" found in enums \"${originalEnumId}\" and \"${sourceEnumId}\"`\n );\n }\n\n seenKeys.set(key, sourceEnumId);\n mergedValues[key] = value as string;\n }\n }\n\n // Create and return the new branded enum (this handles registration)\n return createBrandedEnum(newId, mergedValues) as BrandedEnum<MergedValues<[...T]>>;\n}\n"],"names":["mergeEnums","isBrandedEnum","obj","ENUM_ID","ENUM_VALUES","newId","enums","mergedValues","seenKeys","Map","enumObj","Error","sourceEnumId","key","value","Object","entries","has","originalEnumId","get","set","createBrandedEnum"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC;;;;+BAmFeA;;;eAAAA;;;uBAjFkD;yBAChC;AAclC;;CAEC,GACD,SAASC,cAAcC,GAAY;IACjC,OACE,OAAOA,QAAQ,YACfA,QAAQ,QACRC,cAAO,IAAID,OACXE,kBAAW,IAAIF;AAEnB;AAwDO,SAASF,WACdK,KAAa,EACb,GAAGC,KAAQ;IAEX,2DAA2D;IAC3D,MAAMC,eAAuC,CAAC;IAC9C,MAAMC,WAAW,IAAIC,OAAuB,uBAAuB;IAEnE,KAAK,MAAMC,WAAWJ,MAAO;QAC3B,IAAI,CAACL,cAAcS,UAAU;YAC3B,MAAM,IAAIC,MAAM;QAClB;QAEA,MAAMC,eAAeF,OAAO,CAACP,cAAO,CAAC;QAErC,8DAA8D;QAC9D,KAAK,MAAM,CAACU,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACN,SAAU;YAClD,2BAA2B;YAC3B,IAAIF,SAASS,GAAG,CAACJ,MAAM;gBACrB,MAAMK,iBAAiBV,SAASW,GAAG,CAACN;gBACpC,MAAM,IAAIF,MACR,CAAC,mCAAmC,EAAEE,IAAI,kBAAkB,EAAEK,eAAe,OAAO,EAAEN,aAAa,CAAC,CAAC;YAEzG;YAEAJ,SAASY,GAAG,CAACP,KAAKD;YAClBL,YAAY,CAACM,IAAI,GAAGC;QACtB;IACF;IAEA,qEAAqE;IACrE,OAAOO,IAAAA,0BAAiB,EAAChB,OAAOE;AAClC"}
@@ -15,18 +15,18 @@ import { AnyBrandedEnum, BrandedEnum, EnumValues } from './types.js';
15
15
  *
16
16
  * @template E - The source branded enum type
17
17
  * @template K - The keys to include in the subset (must be keys of E)
18
- * @param newId - Unique identifier for the new subset enum. Must not already be registered.
18
+ * @param newId - Unique identifier for the new subset enum. If already registered,
19
+ * returns the existing enum (idempotent behavior).
19
20
  * @param sourceEnum - The branded enum to derive the subset from
20
21
  * @param keys - Array of keys to include in the subset. All keys must exist in sourceEnum.
21
- * @returns A new branded enum containing only the specified key-value pairs
22
+ * @returns A new branded enum containing only the specified key-value pairs,
23
+ * or the existing enum if newId is already registered
22
24
  * @throws {Error} Throws `Error` with message `enumSubset requires a branded enum as the source`
23
25
  * if sourceEnum is not a valid branded enum.
24
26
  * @throws {Error} Throws `Error` with message `enumSubset requires at least one key`
25
27
  * if keys array is empty.
26
28
  * @throws {Error} Throws `Error` with message `Key "${key}" does not exist in enum "${enumId}"`
27
29
  * if any specified key does not exist in the source enum.
28
- * @throws {Error} Throws `Error` with message `Branded enum with ID "${newId}" already exists`
29
- * if newId is already registered.
30
30
  *
31
31
  * @example
32
32
  * // Basic usage - create a subset of colors
@@ -79,18 +79,18 @@ export declare function enumSubset<E extends AnyBrandedEnum, K extends keyof E &
79
79
  *
80
80
  * @template E - The source branded enum type
81
81
  * @template K - The keys to exclude from the result (must be keys of E)
82
- * @param newId - Unique identifier for the new enum. Must not already be registered.
82
+ * @param newId - Unique identifier for the new enum. If already registered,
83
+ * returns the existing enum (idempotent behavior).
83
84
  * @param sourceEnum - The branded enum to derive from
84
85
  * @param keysToExclude - Array of keys to exclude. All keys must exist in sourceEnum.
85
- * @returns A new branded enum containing all key-value pairs except the excluded ones
86
+ * @returns A new branded enum containing all key-value pairs except the excluded ones,
87
+ * or the existing enum if newId is already registered
86
88
  * @throws {Error} Throws `Error` with message `enumExclude requires a branded enum as the source`
87
89
  * if sourceEnum is not a valid branded enum.
88
90
  * @throws {Error} Throws `Error` with message `enumExclude: excluding all keys would result in an empty enum`
89
91
  * if excluding all keys would leave no keys remaining.
90
92
  * @throws {Error} Throws `Error` with message `Key "${key}" does not exist in enum "${enumId}"`
91
93
  * if any specified key to exclude does not exist in the source enum.
92
- * @throws {Error} Throws `Error` with message `Branded enum with ID "${newId}" already exists`
93
- * if newId is already registered.
94
94
  *
95
95
  * @example
96
96
  * // Basic usage - exclude specific colors
@@ -153,17 +153,17 @@ type MappedEnumValues<E extends Record<string, string>> = {
153
153
  * The resulting enum is registered as an independent enum in the global registry.
154
154
  *
155
155
  * @template E - The source branded enum type
156
- * @param newId - Unique identifier for the new enum. Must not already be registered.
156
+ * @param newId - Unique identifier for the new enum. If already registered,
157
+ * returns the existing enum (idempotent behavior).
157
158
  * @param sourceEnum - The branded enum to derive from
158
159
  * @param mapper - Function that transforms each value. Receives the original value
159
160
  * and the key, and returns the transformed value.
160
- * @returns A new branded enum with transformed values
161
+ * @returns A new branded enum with transformed values,
162
+ * or the existing enum if newId is already registered
161
163
  * @throws {Error} Throws `Error` with message `enumMap requires a branded enum as the source`
162
164
  * if sourceEnum is not a valid branded enum.
163
165
  * @throws {Error} Throws `Error` with message `enumMap mapper must return a string`
164
166
  * if the mapper function returns a non-string value.
165
- * @throws {Error} Throws `Error` with message `Branded enum with ID "${newId}" already exists`
166
- * if newId is already registered.
167
167
  *
168
168
  * @example
169
169
  * // Prefix all values with a namespace
@@ -220,18 +220,18 @@ type KeysAsValues<K extends readonly string[]> = {
220
220
  * types of the provided keys.
221
221
  *
222
222
  * @template K - The array of string keys (use `as const` for literal types)
223
- * @param enumId - Unique identifier for the new enum. Must not already be registered.
223
+ * @param enumId - Unique identifier for the new enum. If already registered,
224
+ * returns the existing enum (idempotent behavior).
224
225
  * @param keys - Array of strings that will become both keys and values.
225
226
  * Use `as const` for literal type inference.
226
- * @returns A new branded enum where each key maps to itself
227
+ * @returns A new branded enum where each key maps to itself,
228
+ * or the existing enum if enumId is already registered
227
229
  * @throws {Error} Throws `Error` with message `enumFromKeys requires at least one key`
228
230
  * if keys array is empty.
229
231
  * @throws {Error} Throws `Error` with message `enumFromKeys requires all keys to be non-empty strings`
230
232
  * if any key is not a non-empty string.
231
233
  * @throws {Error} Throws `Error` with message `enumFromKeys: duplicate key "${key}" found`
232
234
  * if the keys array contains duplicates.
233
- * @throws {Error} Throws `Error` with message `Branded enum with ID "${enumId}" already exists`
234
- * if enumId is already registered.
235
235
  *
236
236
  * @example
237
237
  * // Basic usage - create enum from string array