@digitaldefiance/branded-enum 0.0.1

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.
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Type guards for branded enums.
3
+ *
4
+ * Provides runtime type checking and assertion functions
5
+ * for validating values against branded enums.
6
+ */ import { ENUM_ID, ENUM_VALUES } from './types.js';
7
+ /**
8
+ * Checks if an object is a branded enum (has Symbol metadata).
9
+ *
10
+ * @param obj - The object to check
11
+ * @returns true if obj is a branded enum
12
+ */ function isBrandedEnum(obj) {
13
+ return obj !== null && typeof obj === 'object' && ENUM_ID in obj && ENUM_VALUES in obj && typeof obj[ENUM_ID] === 'string' && obj[ENUM_VALUES] instanceof Set;
14
+ }
15
+ /**
16
+ * Checks if a value belongs to a specific branded enum.
17
+ *
18
+ * Returns true if and only if:
19
+ * - enumObj is a valid branded enum (has Symbol metadata)
20
+ * - value is a string
21
+ * - value exists in the enum's value Set
22
+ *
23
+ * This function provides TypeScript type narrowing - when it returns true,
24
+ * the value's type is narrowed to the enum's value type.
25
+ *
26
+ * @template E - The branded enum type
27
+ * @param value - The value to check. Can be any type; non-strings return false.
28
+ * @param enumObj - The branded enum to check against
29
+ * @returns `true` if value is in the enum (with type narrowing), `false` otherwise.
30
+ * Returns `false` for non-string values, null, undefined, or if enumObj
31
+ * is not a branded enum.
32
+ *
33
+ * @example
34
+ * // Basic type guard usage
35
+ * const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);
36
+ *
37
+ * function handleStatus(value: unknown) {
38
+ * if (isFromEnum(value, Status)) {
39
+ * // value is narrowed to 'active' | 'inactive'
40
+ * console.log('Valid status:', value);
41
+ * } else {
42
+ * console.log('Invalid status');
43
+ * }
44
+ * }
45
+ *
46
+ * @example
47
+ * // Returns false for non-string values
48
+ * isFromEnum(123, Status); // false
49
+ * isFromEnum(null, Status); // false
50
+ * isFromEnum(undefined, Status); // false
51
+ *
52
+ * @example
53
+ * // Returns false for non-branded enum objects
54
+ * isFromEnum('active', { Active: 'active' }); // false (not a branded enum)
55
+ */ export function isFromEnum(value, enumObj) {
56
+ // Return false for non-string values
57
+ if (typeof value !== 'string') {
58
+ return false;
59
+ }
60
+ // Return false if enumObj is not a branded enum
61
+ if (!isBrandedEnum(enumObj)) {
62
+ return false;
63
+ }
64
+ // Check if value exists in enum's value Set
65
+ return enumObj[ENUM_VALUES].has(value);
66
+ }
67
+ /**
68
+ * Asserts that a value belongs to a branded enum, throwing if not.
69
+ *
70
+ * Use this function when you want to validate a value and throw an error
71
+ * if it's invalid, rather than handling the false case manually.
72
+ *
73
+ * @template E - The branded enum type
74
+ * @param value - The value to check. Can be any type.
75
+ * @param enumObj - The branded enum to check against
76
+ * @returns The value with narrowed type if valid
77
+ * @throws {Error} Throws `Error` with message `Second argument is not a branded enum`
78
+ * if enumObj is not a valid branded enum.
79
+ * @throws {Error} Throws `Error` with message `Value "${value}" is not a member of enum "${enumId}"`
80
+ * if the value is not found in the enum.
81
+ *
82
+ * @example
83
+ * // Successful assertion
84
+ * const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);
85
+ * const validated = assertFromEnum('active', Status);
86
+ * // validated is typed as 'active' | 'inactive'
87
+ *
88
+ * @example
89
+ * // Throws for invalid value
90
+ * try {
91
+ * assertFromEnum('unknown', Status);
92
+ * } catch (e) {
93
+ * console.log(e.message); // 'Value "unknown" is not a member of enum "status"'
94
+ * }
95
+ *
96
+ * @example
97
+ * // Throws for non-branded enum
98
+ * try {
99
+ * assertFromEnum('active', { Active: 'active' });
100
+ * } catch (e) {
101
+ * console.log(e.message); // 'Second argument is not a branded enum'
102
+ * }
103
+ */ export function assertFromEnum(value, enumObj) {
104
+ // Check if enumObj is a branded enum
105
+ if (!isBrandedEnum(enumObj)) {
106
+ throw new Error('Second argument is not a branded enum');
107
+ }
108
+ // Check if value is in the enum
109
+ if (!isFromEnum(value, enumObj)) {
110
+ const enumId = enumObj[ENUM_ID];
111
+ throw new Error(`Value "${value}" is not a member of enum "${enumId}"`);
112
+ }
113
+ return value;
114
+ }
115
+ /**
116
+ * Safely parses a value against a branded enum, returning a default if invalid.
117
+ *
118
+ * This is a non-throwing alternative to `assertFromEnum`. Instead of throwing
119
+ * an error when the value is not in the enum, it returns the provided default value.
120
+ *
121
+ * Use this function when you want to handle invalid values gracefully without
122
+ * try/catch blocks, such as when parsing user input or external data.
123
+ *
124
+ * @template E - The branded enum type
125
+ * @param value - The value to parse. Can be any type; non-strings will return the default.
126
+ * @param enumObj - The branded enum to validate against
127
+ * @param defaultValue - The value to return if parsing fails. Must be a valid enum value.
128
+ * @returns The original value if it exists in the enum, otherwise the default value.
129
+ * The return type is narrowed to the enum's value type.
130
+ *
131
+ * @example
132
+ * // Basic usage with default fallback
133
+ * const Status = createBrandedEnum('status', {
134
+ * Active: 'active',
135
+ * Inactive: 'inactive',
136
+ * Pending: 'pending',
137
+ * } as const);
138
+ *
139
+ * // Valid value returns as-is
140
+ * parseEnum('active', Status, Status.Pending); // 'active'
141
+ *
142
+ * // Invalid value returns default
143
+ * parseEnum('unknown', Status, Status.Pending); // 'pending'
144
+ *
145
+ * // Non-string value returns default
146
+ * parseEnum(null, Status, Status.Inactive); // 'inactive'
147
+ * parseEnum(123, Status, Status.Inactive); // 'inactive'
148
+ *
149
+ * @example
150
+ * // Parsing user input safely
151
+ * function handleUserStatus(input: unknown): void {
152
+ * const status = parseEnum(input, Status, Status.Pending);
153
+ * // status is guaranteed to be 'active' | 'inactive' | 'pending'
154
+ * console.log('Processing status:', status);
155
+ * }
156
+ *
157
+ * @example
158
+ * // Parsing API response with fallback
159
+ * interface ApiResponse {
160
+ * status?: string;
161
+ * }
162
+ *
163
+ * function processResponse(response: ApiResponse) {
164
+ * const status = parseEnum(response.status, Status, Status.Inactive);
165
+ * // Handles undefined, null, or invalid status values gracefully
166
+ * return { status };
167
+ * }
168
+ *
169
+ * @example
170
+ * // Chaining with optional values
171
+ * const userStatus = parseEnum(
172
+ * localStorage.getItem('userStatus'),
173
+ * Status,
174
+ * Status.Active
175
+ * );
176
+ */ export function parseEnum(value, enumObj, defaultValue) {
177
+ // If the value is valid, return it
178
+ if (isFromEnum(value, enumObj)) {
179
+ return value;
180
+ }
181
+ // Otherwise return the default
182
+ return defaultValue;
183
+ }
184
+ /**
185
+ * Safely parses a value against a branded enum, returning a result object.
186
+ *
187
+ * This function provides validated deserialization with detailed error information.
188
+ * Unlike `parseEnum` which returns a default value on failure, or `assertFromEnum`
189
+ * which throws an error, `safeParseEnum` returns a discriminated union result
190
+ * that allows for explicit success/failure handling.
191
+ *
192
+ * The result is either:
193
+ * - `{ success: true, value: T }` - The value is valid and typed correctly
194
+ * - `{ success: false, error: SafeParseError }` - The value is invalid with details
195
+ *
196
+ * Error codes:
197
+ * - `INVALID_ENUM_OBJECT` - The enumObj is not a valid branded enum
198
+ * - `INVALID_VALUE_TYPE` - The value is not a string
199
+ * - `VALUE_NOT_IN_ENUM` - The value is a string but not in the enum
200
+ *
201
+ * @template E - The branded enum type
202
+ * @param value - The value to parse. Can be any type.
203
+ * @param enumObj - The branded enum to validate against
204
+ * @returns A SafeParseResult containing either the validated value or error details
205
+ *
206
+ * @example
207
+ * // Basic usage with success
208
+ * const Status = createBrandedEnum('status', {
209
+ * Active: 'active',
210
+ * Inactive: 'inactive',
211
+ * } as const);
212
+ *
213
+ * const result = safeParseEnum('active', Status);
214
+ * if (result.success) {
215
+ * console.log('Valid status:', result.value); // 'active'
216
+ * } else {
217
+ * console.log('Error:', result.error.message);
218
+ * }
219
+ *
220
+ * @example
221
+ * // Handling invalid value
222
+ * const result = safeParseEnum('unknown', Status);
223
+ * if (!result.success) {
224
+ * console.log(result.error.code); // 'VALUE_NOT_IN_ENUM'
225
+ * console.log(result.error.message); // 'Value "unknown" is not a member of enum "status"'
226
+ * console.log(result.error.validValues); // ['active', 'inactive']
227
+ * }
228
+ *
229
+ * @example
230
+ * // Handling non-string input
231
+ * const result = safeParseEnum(123, Status);
232
+ * if (!result.success) {
233
+ * console.log(result.error.code); // 'INVALID_VALUE_TYPE'
234
+ * console.log(result.error.message); // 'Expected a string value, received number'
235
+ * }
236
+ *
237
+ * @example
238
+ * // Parsing API response
239
+ * interface ApiResponse {
240
+ * status?: string;
241
+ * }
242
+ *
243
+ * function processResponse(response: ApiResponse) {
244
+ * const result = safeParseEnum(response.status, Status);
245
+ * if (result.success) {
246
+ * return { status: result.value };
247
+ * } else {
248
+ * // Log detailed error for debugging
249
+ * console.error('Invalid status:', result.error);
250
+ * return { status: Status.Inactive }; // fallback
251
+ * }
252
+ * }
253
+ *
254
+ * @example
255
+ * // Form validation with detailed errors
256
+ * function validateForm(data: Record<string, unknown>) {
257
+ * const statusResult = safeParseEnum(data.status, Status);
258
+ * const errors: string[] = [];
259
+ *
260
+ * if (!statusResult.success) {
261
+ * errors.push(`Status: ${statusResult.error.message}`);
262
+ * }
263
+ *
264
+ * return {
265
+ * isValid: errors.length === 0,
266
+ * errors,
267
+ * data: statusResult.success ? { status: statusResult.value } : null,
268
+ * };
269
+ * }
270
+ *
271
+ * @example
272
+ * // Type narrowing with result
273
+ * const result = safeParseEnum(userInput, Status);
274
+ * if (result.success) {
275
+ * // result.value is typed as 'active' | 'inactive'
276
+ * handleStatus(result.value);
277
+ * } else {
278
+ * // result.error is typed as SafeParseError
279
+ * showError(result.error.message);
280
+ * }
281
+ */ export function safeParseEnum(value, enumObj) {
282
+ // Check if enumObj is a branded enum
283
+ if (!isBrandedEnum(enumObj)) {
284
+ return {
285
+ success: false,
286
+ error: {
287
+ message: 'Second argument is not a branded enum',
288
+ code: 'INVALID_ENUM_OBJECT',
289
+ input: value
290
+ }
291
+ };
292
+ }
293
+ const enumId = enumObj[ENUM_ID];
294
+ const validValues = Array.from(enumObj[ENUM_VALUES]).sort();
295
+ // Check if value is a string
296
+ if (typeof value !== 'string') {
297
+ const valueType = value === null ? 'null' : typeof value;
298
+ return {
299
+ success: false,
300
+ error: {
301
+ message: `Expected a string value, received ${valueType}`,
302
+ code: 'INVALID_VALUE_TYPE',
303
+ input: value,
304
+ enumId,
305
+ validValues
306
+ }
307
+ };
308
+ }
309
+ // Check if value is in the enum
310
+ if (!enumObj[ENUM_VALUES].has(value)) {
311
+ return {
312
+ success: false,
313
+ error: {
314
+ message: `Value "${value}" is not a member of enum "${enumId}"`,
315
+ code: 'VALUE_NOT_IN_ENUM',
316
+ input: value,
317
+ enumId,
318
+ validValues
319
+ }
320
+ };
321
+ }
322
+ // Success case
323
+ return {
324
+ success: true,
325
+ value: value
326
+ };
327
+ }
328
+
329
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/guards.ts"],"sourcesContent":["/**\n * Type guards for branded enums.\n *\n * Provides runtime type checking and assertion functions\n * for validating values against branded enums.\n */\n\nimport {\n AnyBrandedEnum,\n BrandedEnum,\n BrandedEnumValue,\n ENUM_ID,\n ENUM_VALUES,\n EnumValues,\n} from './types.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 * Checks if a value belongs to a specific branded enum.\n *\n * Returns true if and only if:\n * - enumObj is a valid branded enum (has Symbol metadata)\n * - value is a string\n * - value exists in the enum's value Set\n *\n * This function provides TypeScript type narrowing - when it returns true,\n * the value's type is narrowed to the enum's value type.\n *\n * @template E - The branded enum type\n * @param value - The value to check. Can be any type; non-strings return false.\n * @param enumObj - The branded enum to check against\n * @returns `true` if value is in the enum (with type narrowing), `false` otherwise.\n * Returns `false` for non-string values, null, undefined, or if enumObj\n * is not a branded enum.\n *\n * @example\n * // Basic type guard usage\n * const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);\n *\n * function handleStatus(value: unknown) {\n * if (isFromEnum(value, Status)) {\n * // value is narrowed to 'active' | 'inactive'\n * console.log('Valid status:', value);\n * } else {\n * console.log('Invalid status');\n * }\n * }\n *\n * @example\n * // Returns false for non-string values\n * isFromEnum(123, Status); // false\n * isFromEnum(null, Status); // false\n * isFromEnum(undefined, Status); // false\n *\n * @example\n * // Returns false for non-branded enum objects\n * isFromEnum('active', { Active: 'active' }); // false (not a branded enum)\n */\nexport function isFromEnum<E extends AnyBrandedEnum>(\n value: unknown,\n enumObj: E\n): value is EnumValues<E> {\n // Return false for non-string values\n if (typeof value !== 'string') {\n return false;\n }\n\n // Return false if enumObj is not a branded enum\n if (!isBrandedEnum(enumObj)) {\n return false;\n }\n\n // Check if value exists in enum's value Set\n return enumObj[ENUM_VALUES].has(value);\n}\n\n/**\n * Asserts that a value belongs to a branded enum, throwing if not.\n *\n * Use this function when you want to validate a value and throw an error\n * if it's invalid, rather than handling the false case manually.\n *\n * @template E - The branded enum type\n * @param value - The value to check. Can be any type.\n * @param enumObj - The branded enum to check against\n * @returns The value with narrowed type if valid\n * @throws {Error} Throws `Error` with message `Second argument is not a branded enum`\n * if enumObj is not a valid branded enum.\n * @throws {Error} Throws `Error` with message `Value \"${value}\" is not a member of enum \"${enumId}\"`\n * if the value is not found in the enum.\n *\n * @example\n * // Successful assertion\n * const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);\n * const validated = assertFromEnum('active', Status);\n * // validated is typed as 'active' | 'inactive'\n *\n * @example\n * // Throws for invalid value\n * try {\n * assertFromEnum('unknown', Status);\n * } catch (e) {\n * console.log(e.message); // 'Value \"unknown\" is not a member of enum \"status\"'\n * }\n *\n * @example\n * // Throws for non-branded enum\n * try {\n * assertFromEnum('active', { Active: 'active' });\n * } catch (e) {\n * console.log(e.message); // 'Second argument is not a branded enum'\n * }\n */\nexport function assertFromEnum<E extends AnyBrandedEnum>(\n value: unknown,\n enumObj: E\n): EnumValues<E> {\n // Check if enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n throw new Error('Second argument is not a branded enum');\n }\n\n // Check if value is in the enum\n if (!isFromEnum(value, enumObj)) {\n const enumId = enumObj[ENUM_ID];\n throw new Error(`Value \"${value}\" is not a member of enum \"${enumId}\"`);\n }\n\n return value as EnumValues<E>;\n}\n\n/**\n * Safely parses a value against a branded enum, returning a default if invalid.\n *\n * This is a non-throwing alternative to `assertFromEnum`. Instead of throwing\n * an error when the value is not in the enum, it returns the provided default value.\n *\n * Use this function when you want to handle invalid values gracefully without\n * try/catch blocks, such as when parsing user input or external data.\n *\n * @template E - The branded enum type\n * @param value - The value to parse. Can be any type; non-strings will return the default.\n * @param enumObj - The branded enum to validate against\n * @param defaultValue - The value to return if parsing fails. Must be a valid enum value.\n * @returns The original value if it exists in the enum, otherwise the default value.\n * The return type is narrowed to the enum's value type.\n *\n * @example\n * // Basic usage with default fallback\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * Pending: 'pending',\n * } as const);\n *\n * // Valid value returns as-is\n * parseEnum('active', Status, Status.Pending); // 'active'\n *\n * // Invalid value returns default\n * parseEnum('unknown', Status, Status.Pending); // 'pending'\n *\n * // Non-string value returns default\n * parseEnum(null, Status, Status.Inactive); // 'inactive'\n * parseEnum(123, Status, Status.Inactive); // 'inactive'\n *\n * @example\n * // Parsing user input safely\n * function handleUserStatus(input: unknown): void {\n * const status = parseEnum(input, Status, Status.Pending);\n * // status is guaranteed to be 'active' | 'inactive' | 'pending'\n * console.log('Processing status:', status);\n * }\n *\n * @example\n * // Parsing API response with fallback\n * interface ApiResponse {\n * status?: string;\n * }\n *\n * function processResponse(response: ApiResponse) {\n * const status = parseEnum(response.status, Status, Status.Inactive);\n * // Handles undefined, null, or invalid status values gracefully\n * return { status };\n * }\n *\n * @example\n * // Chaining with optional values\n * const userStatus = parseEnum(\n * localStorage.getItem('userStatus'),\n * Status,\n * Status.Active\n * );\n */\nexport function parseEnum<E extends AnyBrandedEnum>(\n value: unknown,\n enumObj: E,\n defaultValue: EnumValues<E>\n): EnumValues<E> {\n // If the value is valid, return it\n if (isFromEnum(value, enumObj)) {\n return value;\n }\n\n // Otherwise return the default\n return defaultValue;\n}\n\n// =============================================================================\n// Safe Parse Result Types\n// =============================================================================\n\n/**\n * Represents a successful parse result from safeParseEnum.\n *\n * @template T - The type of the successfully parsed value\n */\nexport interface SafeParseSuccess<T> {\n /** Indicates the parse was successful */\n readonly success: true;\n /** The validated enum value */\n readonly value: T;\n}\n\n/**\n * Represents a failed parse result from safeParseEnum.\n *\n * Contains detailed error information for debugging and user feedback.\n */\nexport interface SafeParseFailure {\n /** Indicates the parse failed */\n readonly success: false;\n /** Detailed error information */\n readonly error: SafeParseError;\n}\n\n/**\n * Detailed error information for a failed parse.\n */\nexport interface SafeParseError {\n /** Human-readable error message */\n readonly message: string;\n /** The code identifying the type of error */\n readonly code: SafeParseErrorCode;\n /** The input value that failed validation */\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 * Error codes for safe parse failures.\n */\nexport type SafeParseErrorCode =\n | 'INVALID_ENUM_OBJECT'\n | 'INVALID_VALUE_TYPE'\n | 'VALUE_NOT_IN_ENUM';\n\n/**\n * Union type representing the result of safeParseEnum.\n *\n * @template T - The type of the successfully parsed value\n */\nexport type SafeParseResult<T> = SafeParseSuccess<T> | SafeParseFailure;\n\n/**\n * Safely parses a value against a branded enum, returning a result object.\n *\n * This function provides validated deserialization with detailed error information.\n * Unlike `parseEnum` which returns a default value on failure, or `assertFromEnum`\n * which throws an error, `safeParseEnum` returns a discriminated union result\n * that allows for explicit success/failure handling.\n *\n * The result is either:\n * - `{ success: true, value: T }` - The value is valid and typed correctly\n * - `{ success: false, error: SafeParseError }` - The value is invalid with details\n *\n * Error codes:\n * - `INVALID_ENUM_OBJECT` - The enumObj is not a valid branded enum\n * - `INVALID_VALUE_TYPE` - The value is not a string\n * - `VALUE_NOT_IN_ENUM` - The value is a string but not in the enum\n *\n * @template E - The branded enum type\n * @param value - The value to parse. Can be any type.\n * @param enumObj - The branded enum to validate against\n * @returns A SafeParseResult containing either the validated value or error details\n *\n * @example\n * // Basic usage with success\n * const Status = createBrandedEnum('status', {\n * Active: 'active',\n * Inactive: 'inactive',\n * } as const);\n *\n * const result = safeParseEnum('active', Status);\n * if (result.success) {\n * console.log('Valid status:', result.value); // 'active'\n * } else {\n * console.log('Error:', result.error.message);\n * }\n *\n * @example\n * // Handling invalid value\n * const result = safeParseEnum('unknown', Status);\n * if (!result.success) {\n * console.log(result.error.code); // 'VALUE_NOT_IN_ENUM'\n * console.log(result.error.message); // 'Value \"unknown\" is not a member of enum \"status\"'\n * console.log(result.error.validValues); // ['active', 'inactive']\n * }\n *\n * @example\n * // Handling non-string input\n * const result = safeParseEnum(123, Status);\n * if (!result.success) {\n * console.log(result.error.code); // 'INVALID_VALUE_TYPE'\n * console.log(result.error.message); // 'Expected a string value, received number'\n * }\n *\n * @example\n * // Parsing API response\n * interface ApiResponse {\n * status?: string;\n * }\n *\n * function processResponse(response: ApiResponse) {\n * const result = safeParseEnum(response.status, Status);\n * if (result.success) {\n * return { status: result.value };\n * } else {\n * // Log detailed error for debugging\n * console.error('Invalid status:', result.error);\n * return { status: Status.Inactive }; // fallback\n * }\n * }\n *\n * @example\n * // Form validation with detailed errors\n * function validateForm(data: Record<string, unknown>) {\n * const statusResult = safeParseEnum(data.status, Status);\n * const errors: string[] = [];\n *\n * if (!statusResult.success) {\n * errors.push(`Status: ${statusResult.error.message}`);\n * }\n *\n * return {\n * isValid: errors.length === 0,\n * errors,\n * data: statusResult.success ? { status: statusResult.value } : null,\n * };\n * }\n *\n * @example\n * // Type narrowing with result\n * const result = safeParseEnum(userInput, Status);\n * if (result.success) {\n * // result.value is typed as 'active' | 'inactive'\n * handleStatus(result.value);\n * } else {\n * // result.error is typed as SafeParseError\n * showError(result.error.message);\n * }\n */\nexport function safeParseEnum<E extends AnyBrandedEnum>(\n value: unknown,\n enumObj: E\n): SafeParseResult<EnumValues<E>> {\n // Check if enumObj is a branded enum\n if (!isBrandedEnum(enumObj)) {\n return {\n success: false,\n error: {\n message: 'Second argument is not a branded enum',\n code: 'INVALID_ENUM_OBJECT',\n input: value,\n },\n };\n }\n\n const enumId = enumObj[ENUM_ID];\n const validValues = Array.from(enumObj[ENUM_VALUES]).sort();\n\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 code: 'INVALID_VALUE_TYPE',\n input: value,\n enumId,\n validValues,\n },\n };\n }\n\n // Check if value is in the enum\n if (!enumObj[ENUM_VALUES].has(value)) {\n return {\n success: false,\n error: {\n message: `Value \"${value}\" is not a member of enum \"${enumId}\"`,\n code: 'VALUE_NOT_IN_ENUM',\n input: value,\n enumId,\n validValues,\n },\n };\n }\n\n // Success case\n return {\n success: true,\n value: value as EnumValues<E>,\n };\n}\n"],"names":["ENUM_ID","ENUM_VALUES","isBrandedEnum","obj","Set","isFromEnum","value","enumObj","has","assertFromEnum","Error","enumId","parseEnum","defaultValue","safeParseEnum","success","error","message","code","input","validValues","Array","from","sort","valueType"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC,GAED,SAIEA,OAAO,EACPC,WAAW,QAEN,aAAa;AAEpB;;;;;CAKC,GACD,SAASC,cAAcC,GAAY;IACjC,OACEA,QAAQ,QACR,OAAOA,QAAQ,YACfH,WAAWG,OACXF,eAAeE,OACf,OAAO,AAACA,GAAsB,CAACH,QAAQ,KAAK,YAC5C,AAACG,GAAsB,CAACF,YAAY,YAAYG;AAEpD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCC,GACD,OAAO,SAASC,WACdC,KAAc,EACdC,OAAU;IAEV,qCAAqC;IACrC,IAAI,OAAOD,UAAU,UAAU;QAC7B,OAAO;IACT;IAEA,gDAAgD;IAChD,IAAI,CAACJ,cAAcK,UAAU;QAC3B,OAAO;IACT;IAEA,4CAA4C;IAC5C,OAAOA,OAAO,CAACN,YAAY,CAACO,GAAG,CAACF;AAClC;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCC,GACD,OAAO,SAASG,eACdH,KAAc,EACdC,OAAU;IAEV,qCAAqC;IACrC,IAAI,CAACL,cAAcK,UAAU;QAC3B,MAAM,IAAIG,MAAM;IAClB;IAEA,gCAAgC;IAChC,IAAI,CAACL,WAAWC,OAAOC,UAAU;QAC/B,MAAMI,SAASJ,OAAO,CAACP,QAAQ;QAC/B,MAAM,IAAIU,MAAM,CAAC,OAAO,EAAEJ,MAAM,2BAA2B,EAAEK,OAAO,CAAC,CAAC;IACxE;IAEA,OAAOL;AACT;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6DC,GACD,OAAO,SAASM,UACdN,KAAc,EACdC,OAAU,EACVM,YAA2B;IAE3B,mCAAmC;IACnC,IAAIR,WAAWC,OAAOC,UAAU;QAC9B,OAAOD;IACT;IAEA,+BAA+B;IAC/B,OAAOO;AACT;AA6DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiGC,GACD,OAAO,SAASC,cACdR,KAAc,EACdC,OAAU;IAEV,qCAAqC;IACrC,IAAI,CAACL,cAAcK,UAAU;QAC3B,OAAO;YACLQ,SAAS;YACTC,OAAO;gBACLC,SAAS;gBACTC,MAAM;gBACNC,OAAOb;YACT;QACF;IACF;IAEA,MAAMK,SAASJ,OAAO,CAACP,QAAQ;IAC/B,MAAMoB,cAAcC,MAAMC,IAAI,CAACf,OAAO,CAACN,YAAY,EAAEsB,IAAI;IAEzD,6BAA6B;IAC7B,IAAI,OAAOjB,UAAU,UAAU;QAC7B,MAAMkB,YAAYlB,UAAU,OAAO,SAAS,OAAOA;QACnD,OAAO;YACLS,SAAS;YACTC,OAAO;gBACLC,SAAS,CAAC,kCAAkC,EAAEO,UAAU,CAAC;gBACzDN,MAAM;gBACNC,OAAOb;gBACPK;gBACAS;YACF;QACF;IACF;IAEA,gCAAgC;IAChC,IAAI,CAACb,OAAO,CAACN,YAAY,CAACO,GAAG,CAACF,QAAQ;QACpC,OAAO;YACLS,SAAS;YACTC,OAAO;gBACLC,SAAS,CAAC,OAAO,EAAEX,MAAM,2BAA2B,EAAEK,OAAO,CAAC,CAAC;gBAC/DO,MAAM;gBACNC,OAAOb;gBACPK;gBACAS;YACF;QACF;IACF;IAEA,eAAe;IACf,OAAO;QACLL,SAAS;QACTT,OAAOA;IACT;AACF"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Enum composition functions for branded enums.
3
+ *
4
+ * Enables merging multiple branded enums into a new combined enum
5
+ * while maintaining type safety and registry tracking.
6
+ */ import { ENUM_ID, ENUM_VALUES } from './types.js';
7
+ import { createBrandedEnum } from './factory.js';
8
+ /**
9
+ * Checks if an object is a branded enum.
10
+ */ function isBrandedEnum(obj) {
11
+ return typeof obj === 'object' && obj !== null && ENUM_ID in obj && ENUM_VALUES in obj;
12
+ }
13
+ /**
14
+ * Merges multiple branded enums into a new branded enum.
15
+ *
16
+ * Creates a new branded enum that contains all key-value pairs from all
17
+ * source enums. The merged enum is registered in the global registry
18
+ * as a new independent enum.
19
+ *
20
+ * Key collision handling:
21
+ * - Duplicate keys (same key in multiple enums) throw an error
22
+ * - Duplicate values (same value in multiple enums) are allowed
23
+ *
24
+ * @template T - Tuple of branded enum types being merged
25
+ * @param newId - Unique identifier for the merged enum. Must not already
26
+ * be registered.
27
+ * @param enums - One or more branded enums to merge
28
+ * @returns A new branded enum containing all values from source enums
29
+ * @throws {Error} Throws `Error` with message
30
+ * `Cannot merge enums: duplicate key "${key}" found in enums "${enumId1}" and "${enumId2}"`
31
+ * if the same key exists in multiple source enums.
32
+ * @throws {Error} Throws `Error` with message
33
+ * `Branded enum with ID "${newId}" already exists` if newId is already registered.
34
+ * @throws {Error} Throws `Error` with message `All arguments must be branded enums`
35
+ * if any argument is not a valid branded enum.
36
+ *
37
+ * @example
38
+ * // Basic merge
39
+ * const Colors = createBrandedEnum('colors', { Red: 'red', Blue: 'blue' } as const);
40
+ * const Sizes = createBrandedEnum('sizes', { Small: 'small', Large: 'large' } as const);
41
+ *
42
+ * const Combined = mergeEnums('combined', Colors, Sizes);
43
+ * // Combined has: Red, Blue, Small, Large
44
+ *
45
+ * Combined.Red; // 'red'
46
+ * Combined.Small; // 'small'
47
+ *
48
+ * @example
49
+ * // Duplicate values are allowed
50
+ * const Status1 = createBrandedEnum('status1', { Active: 'active' } as const);
51
+ * const Status2 = createBrandedEnum('status2', { Enabled: 'active' } as const);
52
+ *
53
+ * const Merged = mergeEnums('merged', Status1, Status2);
54
+ * // Both Active and Enabled have value 'active' - this is allowed
55
+ *
56
+ * @example
57
+ * // Duplicate keys throw an error
58
+ * const Enum1 = createBrandedEnum('enum1', { Key: 'value1' } as const);
59
+ * const Enum2 = createBrandedEnum('enum2', { Key: 'value2' } as const);
60
+ *
61
+ * try {
62
+ * mergeEnums('merged', Enum1, Enum2);
63
+ * } catch (e) {
64
+ * console.log(e.message);
65
+ * // 'Cannot merge enums: duplicate key "Key" found in enums "enum1" and "enum2"'
66
+ * }
67
+ */ export function mergeEnums(newId, ...enums) {
68
+ // Collect all key-value pairs, checking for duplicate keys
69
+ const mergedValues = {};
70
+ const seenKeys = new Map(); // key -> source enumId
71
+ for (const enumObj of enums){
72
+ if (!isBrandedEnum(enumObj)) {
73
+ throw new Error('All arguments must be branded enums');
74
+ }
75
+ const sourceEnumId = enumObj[ENUM_ID];
76
+ // Iterate over enumerable properties (user-defined keys only)
77
+ for (const [key, value] of Object.entries(enumObj)){
78
+ // Check for duplicate keys
79
+ if (seenKeys.has(key)) {
80
+ const originalEnumId = seenKeys.get(key);
81
+ throw new Error(`Cannot merge enums: duplicate key "${key}" found in enums "${originalEnumId}" and "${sourceEnumId}"`);
82
+ }
83
+ seenKeys.set(key, sourceEnumId);
84
+ mergedValues[key] = value;
85
+ }
86
+ }
87
+ // Create and return the new branded enum (this handles registration)
88
+ return createBrandedEnum(newId, mergedValues);
89
+ }
90
+
91
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +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":["ENUM_ID","ENUM_VALUES","createBrandedEnum","isBrandedEnum","obj","mergeEnums","newId","enums","mergedValues","seenKeys","Map","enumObj","Error","sourceEnumId","key","value","Object","entries","has","originalEnumId","get","set"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC,GAED,SAAsCA,OAAO,EAAEC,WAAW,QAAQ,aAAa;AAC/E,SAASC,iBAAiB,QAAQ,eAAe;AAcjD;;CAEC,GACD,SAASC,cAAcC,GAAY;IACjC,OACE,OAAOA,QAAQ,YACfA,QAAQ,QACRJ,WAAWI,OACXH,eAAeG;AAEnB;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDC,GACD,OAAO,SAASC,WACdC,KAAa,EACb,GAAGC,KAAQ;IAEX,2DAA2D;IAC3D,MAAMC,eAAuC,CAAC;IAC9C,MAAMC,WAAW,IAAIC,OAAuB,uBAAuB;IAEnE,KAAK,MAAMC,WAAWJ,MAAO;QAC3B,IAAI,CAACJ,cAAcQ,UAAU;YAC3B,MAAM,IAAIC,MAAM;QAClB;QAEA,MAAMC,eAAeF,OAAO,CAACX,QAAQ;QAErC,8DAA8D;QAC9D,KAAK,MAAM,CAACc,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,OAAOb,kBAAkBI,OAAOE;AAClC"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Global registry for branded enums.
3
+ *
4
+ * Uses globalThis to ensure cross-bundle compatibility - all instances
5
+ * of the library share the same registry regardless of how they're bundled.
6
+ */ import { REGISTRY_KEY, ENUM_ID, ENUM_VALUES } from './types.js';
7
+ /**
8
+ * Gets the global registry, initializing it lazily if needed.
9
+ * Uses globalThis for cross-bundle compatibility.
10
+ *
11
+ * The registry is shared across all instances of the library, even when
12
+ * bundled separately or loaded as different module formats (ESM/CJS).
13
+ *
14
+ * @returns The global branded enum registry containing all registered enums
15
+ * and a value index for reverse lookups.
16
+ *
17
+ * @example
18
+ * const registry = getRegistry();
19
+ * console.log(registry.enums.size); // Number of registered enums
20
+ */ export function getRegistry() {
21
+ const global = globalThis;
22
+ if (!(REGISTRY_KEY in global) || !global[REGISTRY_KEY]) {
23
+ global[REGISTRY_KEY] = {
24
+ enums: new Map(),
25
+ valueIndex: new Map()
26
+ };
27
+ }
28
+ return global[REGISTRY_KEY];
29
+ }
30
+ /**
31
+ * Registers a branded enum in the global registry.
32
+ * Also updates the value index for reverse lookups.
33
+ *
34
+ * @param enumObj - The branded enum to register
35
+ * @throws Error if an enum with the same ID is already registered
36
+ */ export function registerEnum(enumObj) {
37
+ const registry = getRegistry();
38
+ const enumId = enumObj[ENUM_ID];
39
+ const values = enumObj[ENUM_VALUES];
40
+ // Check for duplicate ID
41
+ if (registry.enums.has(enumId)) {
42
+ throw new Error(`Branded enum with ID "${enumId}" already exists`);
43
+ }
44
+ // Create registry entry
45
+ const entry = {
46
+ enumId,
47
+ enumObj: enumObj,
48
+ values
49
+ };
50
+ // Add to enums map
51
+ registry.enums.set(enumId, entry);
52
+ // Update value index for reverse lookups
53
+ for (const value of values){
54
+ let enumIds = registry.valueIndex.get(value);
55
+ if (!enumIds) {
56
+ enumIds = new Set();
57
+ registry.valueIndex.set(value, enumIds);
58
+ }
59
+ enumIds.add(enumId);
60
+ }
61
+ }
62
+ /**
63
+ * Gets all registered enum IDs.
64
+ *
65
+ * Returns an array of all enum IDs that have been registered via
66
+ * `createBrandedEnum`. Useful for debugging or introspection.
67
+ *
68
+ * @returns Array of all registered enum IDs. Returns empty array if no
69
+ * enums have been registered.
70
+ *
71
+ * @example
72
+ * createBrandedEnum('colors', { Red: 'red' } as const);
73
+ * createBrandedEnum('sizes', { Small: 'small' } as const);
74
+ *
75
+ * getAllEnumIds(); // ['colors', 'sizes']
76
+ */ export function getAllEnumIds() {
77
+ const registry = getRegistry();
78
+ return Array.from(registry.enums.keys());
79
+ }
80
+ /**
81
+ * Gets a branded enum by its ID.
82
+ *
83
+ * Retrieves a previously registered branded enum from the global registry.
84
+ * Useful when you need to access an enum dynamically by its ID.
85
+ *
86
+ * @param enumId - The enum ID to look up
87
+ * @returns The branded enum object if found, or `undefined` if no enum
88
+ * with the given ID has been registered.
89
+ *
90
+ * @example
91
+ * const Status = createBrandedEnum('status', { Active: 'active' } as const);
92
+ *
93
+ * const retrieved = getEnumById('status');
94
+ * console.log(retrieved === Status); // true
95
+ *
96
+ * const notFound = getEnumById('nonexistent');
97
+ * console.log(notFound); // undefined
98
+ */ export function getEnumById(enumId) {
99
+ const registry = getRegistry();
100
+ const entry = registry.enums.get(enumId);
101
+ return entry?.enumObj;
102
+ }
103
+ /**
104
+ * Finds all enum IDs that contain a given value.
105
+ *
106
+ * Performs a reverse lookup to find which enums contain a specific value.
107
+ * Useful for debugging value collisions or routing values to handlers.
108
+ *
109
+ * @param value - The string value to search for
110
+ * @returns Array of enum IDs that contain the value. Returns empty array
111
+ * if no enums contain the value.
112
+ *
113
+ * @example
114
+ * // Single enum containing value
115
+ * createBrandedEnum('colors', { Red: 'red', Blue: 'blue' } as const);
116
+ * findEnumSources('red'); // ['colors']
117
+ *
118
+ * @example
119
+ * // Multiple enums with same value (collision detection)
120
+ * createBrandedEnum('status1', { Active: 'active' } as const);
121
+ * createBrandedEnum('status2', { Enabled: 'active' } as const);
122
+ * findEnumSources('active'); // ['status1', 'status2']
123
+ *
124
+ * @example
125
+ * // Value not found
126
+ * findEnumSources('nonexistent'); // []
127
+ */ export function findEnumSources(value) {
128
+ const registry = getRegistry();
129
+ const enumIds = registry.valueIndex.get(value);
130
+ return enumIds ? Array.from(enumIds) : [];
131
+ }
132
+
133
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/registry.ts"],"sourcesContent":["/**\n * Global registry for branded enums.\n *\n * Uses globalThis to ensure cross-bundle compatibility - all instances\n * of the library share the same registry regardless of how they're bundled.\n */\n\nimport {\n BrandedEnum,\n BrandedEnumRegistry,\n RegistryEntry,\n REGISTRY_KEY,\n ENUM_ID,\n ENUM_VALUES,\n} from './types.js';\n\n/**\n * Gets the global registry, initializing it lazily if needed.\n * Uses globalThis for cross-bundle compatibility.\n *\n * The registry is shared across all instances of the library, even when\n * bundled separately or loaded as different module formats (ESM/CJS).\n *\n * @returns The global branded enum registry containing all registered enums\n * and a value index for reverse lookups.\n *\n * @example\n * const registry = getRegistry();\n * console.log(registry.enums.size); // Number of registered enums\n */\nexport function getRegistry(): BrandedEnumRegistry {\n const global = globalThis as typeof globalThis & {\n [REGISTRY_KEY]?: BrandedEnumRegistry;\n };\n\n if (!(REGISTRY_KEY in global) || !global[REGISTRY_KEY]) {\n global[REGISTRY_KEY] = {\n enums: new Map<string, RegistryEntry>(),\n valueIndex: new Map<string, Set<string>>(),\n };\n }\n\n return global[REGISTRY_KEY];\n}\n\n/**\n * Registers a branded enum in the global registry.\n * Also updates the value index for reverse lookups.\n *\n * @param enumObj - The branded enum to register\n * @throws Error if an enum with the same ID is already registered\n */\nexport function registerEnum<T extends Record<string, string>>(\n enumObj: BrandedEnum<T>\n): void {\n const registry = getRegistry();\n const enumId = enumObj[ENUM_ID];\n const values = enumObj[ENUM_VALUES];\n\n // Check for duplicate ID\n if (registry.enums.has(enumId)) {\n throw new Error(`Branded enum with ID \"${enumId}\" already exists`);\n }\n\n // Create registry entry\n const entry: RegistryEntry = {\n enumId,\n enumObj: enumObj as BrandedEnum<Record<string, string>>,\n values,\n };\n\n // Add to enums map\n registry.enums.set(enumId, entry);\n\n // Update value index for reverse lookups\n for (const value of values) {\n let enumIds = registry.valueIndex.get(value);\n if (!enumIds) {\n enumIds = new Set<string>();\n registry.valueIndex.set(value, enumIds);\n }\n enumIds.add(enumId);\n }\n}\n\n/**\n * Gets all registered enum IDs.\n *\n * Returns an array of all enum IDs that have been registered via\n * `createBrandedEnum`. Useful for debugging or introspection.\n *\n * @returns Array of all registered enum IDs. Returns empty array if no\n * enums have been registered.\n *\n * @example\n * createBrandedEnum('colors', { Red: 'red' } as const);\n * createBrandedEnum('sizes', { Small: 'small' } as const);\n *\n * getAllEnumIds(); // ['colors', 'sizes']\n */\nexport function getAllEnumIds(): string[] {\n const registry = getRegistry();\n return Array.from(registry.enums.keys());\n}\n\n/**\n * Gets a branded enum by its ID.\n *\n * Retrieves a previously registered branded enum from the global registry.\n * Useful when you need to access an enum dynamically by its ID.\n *\n * @param enumId - The enum ID to look up\n * @returns The branded enum object if found, or `undefined` if no enum\n * with the given ID has been registered.\n *\n * @example\n * const Status = createBrandedEnum('status', { Active: 'active' } as const);\n *\n * const retrieved = getEnumById('status');\n * console.log(retrieved === Status); // true\n *\n * const notFound = getEnumById('nonexistent');\n * console.log(notFound); // undefined\n */\nexport function getEnumById(\n enumId: string\n): BrandedEnum<Record<string, string>> | undefined {\n const registry = getRegistry();\n const entry = registry.enums.get(enumId);\n return entry?.enumObj;\n}\n\n/**\n * Finds all enum IDs that contain a given value.\n *\n * Performs a reverse lookup to find which enums contain a specific value.\n * Useful for debugging value collisions or routing values to handlers.\n *\n * @param value - The string value to search for\n * @returns Array of enum IDs that contain the value. Returns empty array\n * if no enums contain the value.\n *\n * @example\n * // Single enum containing value\n * createBrandedEnum('colors', { Red: 'red', Blue: 'blue' } as const);\n * findEnumSources('red'); // ['colors']\n *\n * @example\n * // Multiple enums with same value (collision detection)\n * createBrandedEnum('status1', { Active: 'active' } as const);\n * createBrandedEnum('status2', { Enabled: 'active' } as const);\n * findEnumSources('active'); // ['status1', 'status2']\n *\n * @example\n * // Value not found\n * findEnumSources('nonexistent'); // []\n */\nexport function findEnumSources(value: string): string[] {\n const registry = getRegistry();\n const enumIds = registry.valueIndex.get(value);\n return enumIds ? Array.from(enumIds) : [];\n}\n"],"names":["REGISTRY_KEY","ENUM_ID","ENUM_VALUES","getRegistry","global","globalThis","enums","Map","valueIndex","registerEnum","enumObj","registry","enumId","values","has","Error","entry","set","value","enumIds","get","Set","add","getAllEnumIds","Array","from","keys","getEnumById","findEnumSources"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA;;;;;CAKC,GAED,SAIEA,YAAY,EACZC,OAAO,EACPC,WAAW,QACN,aAAa;AAEpB;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC;IACd,MAAMC,SAASC;IAIf,IAAI,CAAEL,CAAAA,gBAAgBI,MAAK,KAAM,CAACA,MAAM,CAACJ,aAAa,EAAE;QACtDI,MAAM,CAACJ,aAAa,GAAG;YACrBM,OAAO,IAAIC;YACXC,YAAY,IAAID;QAClB;IACF;IAEA,OAAOH,MAAM,CAACJ,aAAa;AAC7B;AAEA;;;;;;CAMC,GACD,OAAO,SAASS,aACdC,OAAuB;IAEvB,MAAMC,WAAWR;IACjB,MAAMS,SAASF,OAAO,CAACT,QAAQ;IAC/B,MAAMY,SAASH,OAAO,CAACR,YAAY;IAEnC,yBAAyB;IACzB,IAAIS,SAASL,KAAK,CAACQ,GAAG,CAACF,SAAS;QAC9B,MAAM,IAAIG,MAAM,CAAC,sBAAsB,EAAEH,OAAO,gBAAgB,CAAC;IACnE;IAEA,wBAAwB;IACxB,MAAMI,QAAuB;QAC3BJ;QACAF,SAASA;QACTG;IACF;IAEA,mBAAmB;IACnBF,SAASL,KAAK,CAACW,GAAG,CAACL,QAAQI;IAE3B,yCAAyC;IACzC,KAAK,MAAME,SAASL,OAAQ;QAC1B,IAAIM,UAAUR,SAASH,UAAU,CAACY,GAAG,CAACF;QACtC,IAAI,CAACC,SAAS;YACZA,UAAU,IAAIE;YACdV,SAASH,UAAU,CAACS,GAAG,CAACC,OAAOC;QACjC;QACAA,QAAQG,GAAG,CAACV;IACd;AACF;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASW;IACd,MAAMZ,WAAWR;IACjB,OAAOqB,MAAMC,IAAI,CAACd,SAASL,KAAK,CAACoB,IAAI;AACvC;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,YACdf,MAAc;IAEd,MAAMD,WAAWR;IACjB,MAAMa,QAAQL,SAASL,KAAK,CAACc,GAAG,CAACR;IACjC,OAAOI,OAAON;AAChB;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,OAAO,SAASkB,gBAAgBV,KAAa;IAC3C,MAAMP,WAAWR;IACjB,MAAMgB,UAAUR,SAASH,UAAU,CAACY,GAAG,CAACF;IACxC,OAAOC,UAAUK,MAAMC,IAAI,CAACN,WAAW,EAAE;AAC3C"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Core type definitions for branded-enum library
3
+ *
4
+ * These types enable runtime-identifiable enum-like objects in TypeScript
5
+ * with zero runtime overhead for value access.
6
+ */ /**
7
+ * Symbol key for storing the enum ID metadata.
8
+ * Using a Symbol prevents collision with user-defined keys.
9
+ */ export const ENUM_ID = Symbol('ENUM_ID');
10
+ /**
11
+ * Symbol key for storing the enum values Set.
12
+ * Using a Symbol prevents collision with user-defined keys.
13
+ */ export const ENUM_VALUES = Symbol('ENUM_VALUES');
14
+ /**
15
+ * The key used to store the registry on globalThis.
16
+ * Namespaced to avoid collisions with other libraries.
17
+ */ export const REGISTRY_KEY = '__brandedEnumRegistry__';
18
+ /**
19
+ * The key used to store the enum consumer registry on globalThis.
20
+ * Tracks which classes consume which branded enums.
21
+ */ export const CONSUMER_REGISTRY_KEY = '__brandedEnumConsumerRegistry__';
22
+
23
+ //# sourceMappingURL=types.js.map