@digitaldefiance/branded-enum 0.0.1 → 0.0.2

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,1422 @@
1
+ /**
2
+ * Advanced enum operations for branded enums.
3
+ *
4
+ * Provides functions for deriving new enums from existing ones,
5
+ * including subsetting, exclusion, and transformation operations.
6
+ */
7
+ import { AnyBrandedEnum, BrandedEnum, EnumValues } from './types.js';
8
+ /**
9
+ * Creates a new branded enum containing only the specified keys from the source enum.
10
+ *
11
+ * This function derives a subset of an existing branded enum by selecting specific keys.
12
+ * The resulting enum is registered as an independent enum in the global registry.
13
+ *
14
+ * Type safety is maintained - the resulting enum's type reflects only the selected keys.
15
+ *
16
+ * @template E - The source branded enum type
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.
19
+ * @param sourceEnum - The branded enum to derive the subset from
20
+ * @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
+ * @throws {Error} Throws `Error` with message `enumSubset requires a branded enum as the source`
23
+ * if sourceEnum is not a valid branded enum.
24
+ * @throws {Error} Throws `Error` with message `enumSubset requires at least one key`
25
+ * if keys array is empty.
26
+ * @throws {Error} Throws `Error` with message `Key "${key}" does not exist in enum "${enumId}"`
27
+ * 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
+ *
31
+ * @example
32
+ * // Basic usage - create a subset of colors
33
+ * const AllColors = createBrandedEnum('all-colors', {
34
+ * Red: 'red',
35
+ * Green: 'green',
36
+ * Blue: 'blue',
37
+ * Yellow: 'yellow',
38
+ * } as const);
39
+ *
40
+ * const PrimaryColors = enumSubset('primary-colors', AllColors, ['Red', 'Blue', 'Yellow']);
41
+ * // PrimaryColors has: Red, Blue, Yellow (no Green)
42
+ *
43
+ * PrimaryColors.Red; // 'red'
44
+ * PrimaryColors.Blue; // 'blue'
45
+ * // PrimaryColors.Green; // TypeScript error - Green doesn't exist
46
+ *
47
+ * @example
48
+ * // Type safety with subset
49
+ * const Status = createBrandedEnum('status', {
50
+ * Active: 'active',
51
+ * Inactive: 'inactive',
52
+ * Pending: 'pending',
53
+ * Archived: 'archived',
54
+ * } as const);
55
+ *
56
+ * const ActiveStatuses = enumSubset('active-statuses', Status, ['Active', 'Pending']);
57
+ * type ActiveStatusValue = typeof ActiveStatuses[keyof typeof ActiveStatuses];
58
+ * // ActiveStatusValue = 'active' | 'pending'
59
+ *
60
+ * @example
61
+ * // Error handling for invalid keys
62
+ * try {
63
+ * enumSubset('invalid', AllColors, ['Red', 'Purple']); // Purple doesn't exist
64
+ * } catch (e) {
65
+ * console.log(e.message);
66
+ * // 'Key "Purple" does not exist in enum "all-colors"'
67
+ * }
68
+ */
69
+ export declare function enumSubset<E extends AnyBrandedEnum, K extends keyof E & string>(newId: string, sourceEnum: E, keys: readonly K[]): BrandedEnum<Pick<E, K> & Record<string, string>>;
70
+ /**
71
+ * Creates a new branded enum by excluding the specified keys from the source enum.
72
+ *
73
+ * This function derives a new enum from an existing branded enum by removing specific keys.
74
+ * It is the complement of `enumSubset` - instead of specifying which keys to include,
75
+ * you specify which keys to exclude.
76
+ *
77
+ * The resulting enum is registered as an independent enum in the global registry.
78
+ * Type safety is maintained - the resulting enum's type reflects only the remaining keys.
79
+ *
80
+ * @template E - The source branded enum type
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.
83
+ * @param sourceEnum - The branded enum to derive from
84
+ * @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
+ * @throws {Error} Throws `Error` with message `enumExclude requires a branded enum as the source`
87
+ * if sourceEnum is not a valid branded enum.
88
+ * @throws {Error} Throws `Error` with message `enumExclude: excluding all keys would result in an empty enum`
89
+ * if excluding all keys would leave no keys remaining.
90
+ * @throws {Error} Throws `Error` with message `Key "${key}" does not exist in enum "${enumId}"`
91
+ * 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
+ *
95
+ * @example
96
+ * // Basic usage - exclude specific colors
97
+ * const AllColors = createBrandedEnum('all-colors', {
98
+ * Red: 'red',
99
+ * Green: 'green',
100
+ * Blue: 'blue',
101
+ * Yellow: 'yellow',
102
+ * } as const);
103
+ *
104
+ * const NonPrimaryColors = enumExclude('non-primary', AllColors, ['Red', 'Blue', 'Yellow']);
105
+ * // NonPrimaryColors has only: Green
106
+ *
107
+ * NonPrimaryColors.Green; // 'green'
108
+ * // NonPrimaryColors.Red; // TypeScript error - Red was excluded
109
+ *
110
+ * @example
111
+ * // Exclude deprecated values
112
+ * const Status = createBrandedEnum('status', {
113
+ * Active: 'active',
114
+ * Inactive: 'inactive',
115
+ * Pending: 'pending',
116
+ * Deprecated: 'deprecated',
117
+ * } as const);
118
+ *
119
+ * const CurrentStatuses = enumExclude('current-statuses', Status, ['Deprecated']);
120
+ * type CurrentStatusValue = typeof CurrentStatuses[keyof typeof CurrentStatuses];
121
+ * // CurrentStatusValue = 'active' | 'inactive' | 'pending'
122
+ *
123
+ * @example
124
+ * // Error handling for invalid keys
125
+ * try {
126
+ * enumExclude('invalid', AllColors, ['Purple']); // Purple doesn't exist
127
+ * } catch (e) {
128
+ * console.log(e.message);
129
+ * // 'Key "Purple" does not exist in enum "all-colors"'
130
+ * }
131
+ */
132
+ export declare function enumExclude<E extends AnyBrandedEnum, K extends keyof E & string>(newId: string, sourceEnum: E, keysToExclude: readonly K[]): BrandedEnum<Omit<E, K> & Record<string, string>>;
133
+ /**
134
+ * Type representing the result of mapping enum values through a transform function.
135
+ * Preserves the keys but transforms the value types.
136
+ */
137
+ type MappedEnumValues<E extends Record<string, string>> = {
138
+ [K in keyof E]: string;
139
+ };
140
+ /**
141
+ * Creates a new branded enum by transforming all values through a mapper function.
142
+ *
143
+ * This function derives a new enum from an existing branded enum by applying a
144
+ * transformation function to each value. The keys remain unchanged, but the values
145
+ * are transformed according to the provided mapper.
146
+ *
147
+ * Common use cases include:
148
+ * - Prefixing values (e.g., adding a namespace)
149
+ * - Suffixing values (e.g., adding a version)
150
+ * - Case transformation (e.g., uppercase, lowercase)
151
+ * - Custom transformations (e.g., encoding, formatting)
152
+ *
153
+ * The resulting enum is registered as an independent enum in the global registry.
154
+ *
155
+ * @template E - The source branded enum type
156
+ * @param newId - Unique identifier for the new enum. Must not already be registered.
157
+ * @param sourceEnum - The branded enum to derive from
158
+ * @param mapper - Function that transforms each value. Receives the original value
159
+ * and the key, and returns the transformed value.
160
+ * @returns A new branded enum with transformed values
161
+ * @throws {Error} Throws `Error` with message `enumMap requires a branded enum as the source`
162
+ * if sourceEnum is not a valid branded enum.
163
+ * @throws {Error} Throws `Error` with message `enumMap mapper must return a string`
164
+ * 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
+ *
168
+ * @example
169
+ * // Prefix all values with a namespace
170
+ * const Status = createBrandedEnum('status', {
171
+ * Active: 'active',
172
+ * Inactive: 'inactive',
173
+ * } as const);
174
+ *
175
+ * const PrefixedStatus = enumMap('prefixed-status', Status, (value) => `app.${value}`);
176
+ * // PrefixedStatus.Active === 'app.active'
177
+ * // PrefixedStatus.Inactive === 'app.inactive'
178
+ *
179
+ * @example
180
+ * // Uppercase all values
181
+ * const Colors = createBrandedEnum('colors', {
182
+ * Red: 'red',
183
+ * Green: 'green',
184
+ * Blue: 'blue',
185
+ * } as const);
186
+ *
187
+ * const UpperColors = enumMap('upper-colors', Colors, (value) => value.toUpperCase());
188
+ * // UpperColors.Red === 'RED'
189
+ * // UpperColors.Green === 'GREEN'
190
+ * // UpperColors.Blue === 'BLUE'
191
+ *
192
+ * @example
193
+ * // Transform with key context
194
+ * const Sizes = createBrandedEnum('sizes', {
195
+ * Small: 's',
196
+ * Medium: 'm',
197
+ * Large: 'l',
198
+ * } as const);
199
+ *
200
+ * const VerboseSizes = enumMap('verbose-sizes', Sizes, (value, key) => `${key.toLowerCase()}-${value}`);
201
+ * // VerboseSizes.Small === 'small-s'
202
+ * // VerboseSizes.Medium === 'medium-m'
203
+ * // VerboseSizes.Large === 'large-l'
204
+ */
205
+ export declare function enumMap<E extends AnyBrandedEnum>(newId: string, sourceEnum: E, mapper: (value: string, key: string) => string): BrandedEnum<MappedEnumValues<E> & Record<string, string>>;
206
+ /**
207
+ * Type representing an enum where each key maps to itself as a value.
208
+ */
209
+ type KeysAsValues<K extends readonly string[]> = {
210
+ [P in K[number]]: P;
211
+ };
212
+ /**
213
+ * Creates a branded enum from an array of keys where each key equals its value.
214
+ *
215
+ * This is a convenience function for the common pattern where enum keys and values
216
+ * are identical, such as `{ Active: 'Active', Inactive: 'Inactive' }`.
217
+ *
218
+ * The resulting enum is registered as an independent enum in the global registry.
219
+ * Type safety is maintained - the resulting enum's type reflects the exact literal
220
+ * types of the provided keys.
221
+ *
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.
224
+ * @param keys - Array of strings that will become both keys and values.
225
+ * Use `as const` for literal type inference.
226
+ * @returns A new branded enum where each key maps to itself
227
+ * @throws {Error} Throws `Error` with message `enumFromKeys requires at least one key`
228
+ * if keys array is empty.
229
+ * @throws {Error} Throws `Error` with message `enumFromKeys requires all keys to be non-empty strings`
230
+ * if any key is not a non-empty string.
231
+ * @throws {Error} Throws `Error` with message `enumFromKeys: duplicate key "${key}" found`
232
+ * 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
+ *
236
+ * @example
237
+ * // Basic usage - create enum from string array
238
+ * const Status = enumFromKeys('status', ['Active', 'Inactive', 'Pending'] as const);
239
+ * // Equivalent to: { Active: 'Active', Inactive: 'Inactive', Pending: 'Pending' }
240
+ *
241
+ * Status.Active; // 'Active'
242
+ * Status.Inactive; // 'Inactive'
243
+ * Status.Pending; // 'Pending'
244
+ *
245
+ * @example
246
+ * // Type inference with as const
247
+ * const Colors = enumFromKeys('colors', ['Red', 'Green', 'Blue'] as const);
248
+ * type ColorValue = typeof Colors[keyof typeof Colors];
249
+ * // ColorValue = 'Red' | 'Green' | 'Blue'
250
+ *
251
+ * @example
252
+ * // Useful for string literal unions
253
+ * const Directions = enumFromKeys('directions', ['North', 'South', 'East', 'West'] as const);
254
+ *
255
+ * function move(direction: typeof Directions[keyof typeof Directions]) {
256
+ * // direction is 'North' | 'South' | 'East' | 'West'
257
+ * }
258
+ *
259
+ * move(Directions.North); // OK
260
+ * move('North'); // Also OK due to literal type
261
+ *
262
+ * @example
263
+ * // Error handling
264
+ * try {
265
+ * enumFromKeys('empty', []); // Empty array
266
+ * } catch (e) {
267
+ * console.log(e.message);
268
+ * // 'enumFromKeys requires at least one key'
269
+ * }
270
+ */
271
+ export declare function enumFromKeys<K extends readonly string[]>(enumId: string, keys: K): BrandedEnum<KeysAsValues<K> & Record<string, string>>;
272
+ /**
273
+ * Result of comparing two branded enums with enumDiff.
274
+ */
275
+ export interface EnumDiffResult {
276
+ /** Keys that exist only in the first enum */
277
+ readonly onlyInFirst: ReadonlyArray<{
278
+ key: string;
279
+ value: string;
280
+ }>;
281
+ /** Keys that exist only in the second enum */
282
+ readonly onlyInSecond: ReadonlyArray<{
283
+ key: string;
284
+ value: string;
285
+ }>;
286
+ /** Keys that exist in both enums but have different values */
287
+ readonly differentValues: ReadonlyArray<{
288
+ key: string;
289
+ firstValue: string;
290
+ secondValue: string;
291
+ }>;
292
+ /** Keys that exist in both enums with the same values */
293
+ readonly sameValues: ReadonlyArray<{
294
+ key: string;
295
+ value: string;
296
+ }>;
297
+ }
298
+ /**
299
+ * Compares two branded enums and returns their differences.
300
+ *
301
+ * This function analyzes two branded enums and categorizes their keys into:
302
+ * - Keys only in the first enum
303
+ * - Keys only in the second enum
304
+ * - Keys in both with different values
305
+ * - Keys in both with the same values
306
+ *
307
+ * Useful for:
308
+ * - Migration: Identifying what changed between enum versions
309
+ * - Debugging: Understanding differences between similar enums
310
+ * - Validation: Ensuring enums have expected overlap or differences
311
+ *
312
+ * @template E1 - The first branded enum type
313
+ * @template E2 - The second branded enum type
314
+ * @param firstEnum - The first branded enum to compare
315
+ * @param secondEnum - The second branded enum to compare
316
+ * @returns An EnumDiffResult object containing categorized differences
317
+ * @throws {Error} Throws `Error` with message `enumDiff requires branded enums as arguments`
318
+ * if either argument is not a valid branded enum.
319
+ *
320
+ * @example
321
+ * // Compare two versions of a status enum
322
+ * const StatusV1 = createBrandedEnum('status-v1', {
323
+ * Active: 'active',
324
+ * Inactive: 'inactive',
325
+ * } as const);
326
+ *
327
+ * const StatusV2 = createBrandedEnum('status-v2', {
328
+ * Active: 'active',
329
+ * Inactive: 'disabled', // Changed value
330
+ * Pending: 'pending', // New key
331
+ * } as const);
332
+ *
333
+ * const diff = enumDiff(StatusV1, StatusV2);
334
+ * // diff.onlyInFirst: []
335
+ * // diff.onlyInSecond: [{ key: 'Pending', value: 'pending' }]
336
+ * // diff.differentValues: [{ key: 'Inactive', firstValue: 'inactive', secondValue: 'disabled' }]
337
+ * // diff.sameValues: [{ key: 'Active', value: 'active' }]
338
+ *
339
+ * @example
340
+ * // Find keys removed between versions
341
+ * const ColorsOld = createBrandedEnum('colors-old', {
342
+ * Red: 'red',
343
+ * Green: 'green',
344
+ * Blue: 'blue',
345
+ * } as const);
346
+ *
347
+ * const ColorsNew = createBrandedEnum('colors-new', {
348
+ * Red: 'red',
349
+ * Blue: 'blue',
350
+ * } as const);
351
+ *
352
+ * const diff = enumDiff(ColorsOld, ColorsNew);
353
+ * // diff.onlyInFirst: [{ key: 'Green', value: 'green' }]
354
+ * // diff.onlyInSecond: []
355
+ *
356
+ * @example
357
+ * // Check if enums are identical
358
+ * const diff = enumDiff(enumA, enumB);
359
+ * const areIdentical = diff.onlyInFirst.length === 0 &&
360
+ * diff.onlyInSecond.length === 0 &&
361
+ * diff.differentValues.length === 0;
362
+ */
363
+ export declare function enumDiff(firstEnum: AnyBrandedEnum, secondEnum: AnyBrandedEnum): EnumDiffResult;
364
+ /**
365
+ * Result entry for a shared value found across multiple enums.
366
+ */
367
+ export interface EnumIntersectEntry {
368
+ /** The shared value that exists in multiple enums */
369
+ readonly value: string;
370
+ /** Array of enum IDs that contain this value */
371
+ readonly enumIds: readonly string[];
372
+ }
373
+ /**
374
+ * Finds values that exist in multiple branded enums.
375
+ *
376
+ * This function analyzes multiple branded enums and identifies values that
377
+ * appear in more than one enum. For each shared value, it returns the value
378
+ * along with the IDs of all enums that contain it.
379
+ *
380
+ * Useful for:
381
+ * - Detecting value collisions across enums
382
+ * - Finding common values for potential refactoring
383
+ * - Debugging i18n key conflicts
384
+ * - Identifying intentional value overlaps
385
+ *
386
+ * @param enums - Array of branded enums to analyze for intersections
387
+ * @returns Array of EnumIntersectEntry objects, each containing a shared value
388
+ * and the IDs of enums containing that value. Only values appearing in 2+
389
+ * enums are included. Results are sorted by value for consistent ordering.
390
+ * @throws {Error} Throws `Error` with message `enumIntersect requires at least two branded enums`
391
+ * if fewer than two enums are provided.
392
+ * @throws {Error} Throws `Error` with message `enumIntersect requires all arguments to be branded enums`
393
+ * if any argument is not a valid branded enum.
394
+ *
395
+ * @example
396
+ * // Find shared values between color enums
397
+ * const PrimaryColors = createBrandedEnum('primary', {
398
+ * Red: 'red',
399
+ * Blue: 'blue',
400
+ * Yellow: 'yellow',
401
+ * } as const);
402
+ *
403
+ * const WarmColors = createBrandedEnum('warm', {
404
+ * Red: 'red',
405
+ * Orange: 'orange',
406
+ * Yellow: 'yellow',
407
+ * } as const);
408
+ *
409
+ * const shared = enumIntersect(PrimaryColors, WarmColors);
410
+ * // shared = [
411
+ * // { value: 'red', enumIds: ['primary', 'warm'] },
412
+ * // { value: 'yellow', enumIds: ['primary', 'warm'] }
413
+ * // ]
414
+ *
415
+ * @example
416
+ * // Detect i18n key collisions across multiple libraries
417
+ * const LibAKeys = createBrandedEnum('lib-a', {
418
+ * Submit: 'submit',
419
+ * Cancel: 'cancel',
420
+ * } as const);
421
+ *
422
+ * const LibBKeys = createBrandedEnum('lib-b', {
423
+ * Submit: 'submit',
424
+ * Reset: 'reset',
425
+ * } as const);
426
+ *
427
+ * const LibCKeys = createBrandedEnum('lib-c', {
428
+ * Submit: 'submit',
429
+ * Clear: 'clear',
430
+ * } as const);
431
+ *
432
+ * const collisions = enumIntersect(LibAKeys, LibBKeys, LibCKeys);
433
+ * // collisions = [
434
+ * // { value: 'submit', enumIds: ['lib-a', 'lib-b', 'lib-c'] }
435
+ * // ]
436
+ *
437
+ * @example
438
+ * // Check if enums have any overlap
439
+ * const shared = enumIntersect(enumA, enumB);
440
+ * if (shared.length === 0) {
441
+ * console.log('No shared values between enums');
442
+ * }
443
+ */
444
+ export declare function enumIntersect(...enums: AnyBrandedEnum[]): EnumIntersectEntry[];
445
+ /**
446
+ * Converts a branded enum to a plain Record object, stripping all metadata.
447
+ *
448
+ * This function creates a new plain object containing only the key-value pairs
449
+ * from the branded enum, without any Symbol metadata properties. The result is
450
+ * a simple `Record<string, string>` that can be safely serialized, spread, or
451
+ * used in contexts where branded enum metadata is not needed.
452
+ *
453
+ * Useful for:
454
+ * - Serialization scenarios where you need a plain object
455
+ * - Interoperability with APIs that expect plain objects
456
+ * - Creating snapshots of enum state without metadata
457
+ * - Passing enum data to external systems
458
+ *
459
+ * @template E - The branded enum type
460
+ * @param enumObj - The branded enum to convert
461
+ * @returns A plain Record<string, string> containing only the key-value pairs
462
+ * @throws {Error} Throws `Error` with message `enumToRecord requires a branded enum`
463
+ * if enumObj is not a valid branded enum.
464
+ *
465
+ * @example
466
+ * // Basic usage - convert to plain object
467
+ * const Status = createBrandedEnum('status', {
468
+ * Active: 'active',
469
+ * Inactive: 'inactive',
470
+ * Pending: 'pending',
471
+ * } as const);
472
+ *
473
+ * const plainStatus = enumToRecord(Status);
474
+ * // plainStatus = { Active: 'active', Inactive: 'inactive', Pending: 'pending' }
475
+ * // No Symbol metadata, just a plain object
476
+ *
477
+ * @example
478
+ * // Serialization scenario
479
+ * const Colors = createBrandedEnum('colors', {
480
+ * Red: 'red',
481
+ * Green: 'green',
482
+ * Blue: 'blue',
483
+ * } as const);
484
+ *
485
+ * // Send to an API that expects plain objects
486
+ * const payload = {
487
+ * availableColors: enumToRecord(Colors),
488
+ * };
489
+ * await fetch('/api/config', {
490
+ * method: 'POST',
491
+ * body: JSON.stringify(payload),
492
+ * });
493
+ *
494
+ * @example
495
+ * // Comparing with spread operator
496
+ * const Status = createBrandedEnum('status', { Active: 'active' } as const);
497
+ *
498
+ * // Both produce the same result for enumerable properties:
499
+ * const spread = { ...Status };
500
+ * const record = enumToRecord(Status);
501
+ * // spread and record are equivalent plain objects
502
+ *
503
+ * // But enumToRecord is explicit about intent and validates input
504
+ *
505
+ * @example
506
+ * // Type safety
507
+ * const Sizes = createBrandedEnum('sizes', {
508
+ * Small: 's',
509
+ * Medium: 'm',
510
+ * Large: 'l',
511
+ * } as const);
512
+ *
513
+ * const record = enumToRecord(Sizes);
514
+ * // record type is Record<string, string>
515
+ * // Can be used anywhere a plain object is expected
516
+ */
517
+ export declare function enumToRecord<E extends AnyBrandedEnum>(enumObj: E): Record<string, string>;
518
+ /**
519
+ * Type of access event that triggered the callback.
520
+ */
521
+ export type EnumAccessType = 'get' | 'has' | 'keys' | 'values' | 'entries';
522
+ /**
523
+ * Information about an enum access event.
524
+ */
525
+ export interface EnumAccessEvent {
526
+ /** The enum ID of the accessed enum */
527
+ readonly enumId: string;
528
+ /** The type of access that occurred */
529
+ readonly accessType: EnumAccessType;
530
+ /** The key that was accessed (for 'get' and 'has' types) */
531
+ readonly key?: string;
532
+ /** The value that was returned (for 'get' type) */
533
+ readonly value?: string;
534
+ /** Timestamp of the access */
535
+ readonly timestamp: number;
536
+ }
537
+ /**
538
+ * Callback function type for enum access events.
539
+ */
540
+ export type EnumWatchCallback = (event: EnumAccessEvent) => void;
541
+ /**
542
+ * Result of calling watchEnum, containing the watched proxy and unwatch function.
543
+ */
544
+ export interface WatchEnumResult<E extends AnyBrandedEnum> {
545
+ /** The proxied enum that triggers callbacks on access */
546
+ readonly watched: E;
547
+ /** Function to remove the watcher and stop receiving callbacks */
548
+ readonly unwatch: () => void;
549
+ }
550
+ /**
551
+ * Creates a watched version of a branded enum that triggers callbacks on access.
552
+ *
553
+ * This function wraps a branded enum in a Proxy that intercepts property access
554
+ * and calls registered callbacks. This is useful for debugging, development tooling,
555
+ * and understanding how enums are used throughout an application.
556
+ *
557
+ * The watched enum behaves identically to the original enum - all values, metadata,
558
+ * and type information are preserved. The only difference is that access events
559
+ * are reported to the callback.
560
+ *
561
+ * **Note:** This feature is intended for development and debugging purposes.
562
+ * Using watched enums in production may have performance implications due to
563
+ * the Proxy overhead.
564
+ *
565
+ * @template E - The branded enum type
566
+ * @param enumObj - The branded enum to watch
567
+ * @param callback - Function called whenever the enum is accessed
568
+ * @returns An object containing the watched enum proxy and an unwatch function
569
+ * @throws {Error} Throws `Error` with message `watchEnum requires a branded enum`
570
+ * if enumObj is not a valid branded enum.
571
+ *
572
+ * @example
573
+ * // Basic usage - log all enum accesses
574
+ * const Status = createBrandedEnum('status', {
575
+ * Active: 'active',
576
+ * Inactive: 'inactive',
577
+ * } as const);
578
+ *
579
+ * const { watched, unwatch } = watchEnum(Status, (event) => {
580
+ * console.log(`Accessed ${event.enumId}.${event.key} = ${event.value}`);
581
+ * });
582
+ *
583
+ * watched.Active; // Logs: "Accessed status.Active = active"
584
+ * watched.Inactive; // Logs: "Accessed status.Inactive = inactive"
585
+ *
586
+ * // Stop watching
587
+ * unwatch();
588
+ * watched.Active; // No longer logs
589
+ *
590
+ * @example
591
+ * // Track enum usage for debugging
592
+ * const accessLog: EnumAccessEvent[] = [];
593
+ *
594
+ * const { watched: WatchedColors } = watchEnum(Colors, (event) => {
595
+ * accessLog.push(event);
596
+ * });
597
+ *
598
+ * // Use the watched enum in your code
599
+ * doSomething(WatchedColors.Red);
600
+ * doSomethingElse(WatchedColors.Blue);
601
+ *
602
+ * // Later, analyze the access log
603
+ * console.log(`Enum accessed ${accessLog.length} times`);
604
+ * console.log('Keys accessed:', accessLog.map(e => e.key));
605
+ *
606
+ * @example
607
+ * // Detect unused enum values
608
+ * const usedKeys = new Set<string>();
609
+ *
610
+ * const { watched } = watchEnum(MyEnum, (event) => {
611
+ * if (event.key) usedKeys.add(event.key);
612
+ * });
613
+ *
614
+ * // After running your application/tests
615
+ * const allKeys = Object.keys(MyEnum);
616
+ * const unusedKeys = allKeys.filter(k => !usedKeys.has(k));
617
+ * console.log('Unused enum keys:', unusedKeys);
618
+ *
619
+ * @example
620
+ * // Performance monitoring
621
+ * const { watched } = watchEnum(Status, (event) => {
622
+ * if (event.accessType === 'get') {
623
+ * performance.mark(`enum-access-${event.enumId}-${event.key}`);
624
+ * }
625
+ * });
626
+ */
627
+ export declare function watchEnum<E extends AnyBrandedEnum>(enumObj: E, callback: EnumWatchCallback): WatchEnumResult<E>;
628
+ /**
629
+ * Registers a global callback that receives access events from all watched enums.
630
+ *
631
+ * This is useful for centralized logging or monitoring of enum usage across
632
+ * an entire application without needing to wrap each enum individually.
633
+ *
634
+ * @param callback - Function called for every enum access event
635
+ * @returns A function to unregister the global callback
636
+ *
637
+ * @example
638
+ * // Centralized enum access logging
639
+ * const unregister = watchAllEnums((event) => {
640
+ * console.log(`[${event.enumId}] ${event.accessType}: ${event.key}`);
641
+ * });
642
+ *
643
+ * // All watched enums will now trigger this callback
644
+ * watchedStatus.Active; // Logs: "[status] get: Active"
645
+ * watchedColors.Red; // Logs: "[colors] get: Red"
646
+ *
647
+ * // Stop global watching
648
+ * unregister();
649
+ */
650
+ export declare function watchAllEnums(callback: EnumWatchCallback): () => void;
651
+ /**
652
+ * Clears all enum watchers (both specific and global).
653
+ *
654
+ * This is primarily useful for testing or when you need to reset
655
+ * the watcher state completely.
656
+ *
657
+ * @example
658
+ * // In test cleanup
659
+ * afterEach(() => {
660
+ * clearAllEnumWatchers();
661
+ * });
662
+ */
663
+ export declare function clearAllEnumWatchers(): void;
664
+ /**
665
+ * Gets the number of active watchers for a specific enum.
666
+ *
667
+ * @param enumId - The enum ID to check
668
+ * @returns The number of active watchers for this enum
669
+ *
670
+ * @example
671
+ * const { watched } = watchEnum(Status, callback1);
672
+ * watchEnum(Status, callback2);
673
+ *
674
+ * getEnumWatcherCount('status'); // 2
675
+ */
676
+ export declare function getEnumWatcherCount(enumId: string): number;
677
+ /**
678
+ * Gets the number of global watchers.
679
+ *
680
+ * @returns The number of active global watchers
681
+ */
682
+ export declare function getGlobalWatcherCount(): number;
683
+ /**
684
+ * A helper function for exhaustiveness checking in switch statements.
685
+ *
686
+ * This function should be called in the `default` case of a switch statement
687
+ * when you want to ensure all cases are handled. If the switch is exhaustive,
688
+ * TypeScript will infer that this function is never called (the value is `never`).
689
+ * If a case is missing, TypeScript will show a compile-time error.
690
+ *
691
+ * At runtime, if this function is somehow called (e.g., due to a type assertion
692
+ * or JavaScript interop), it throws a descriptive error.
693
+ *
694
+ * @param value - The value that should be unreachable. TypeScript should infer this as `never`.
695
+ * @param message - Optional custom error message. Defaults to a descriptive message including the value.
696
+ * @returns Never returns - always throws an error if called at runtime.
697
+ * @throws {Error} Throws `Error` with message `Exhaustive check failed: unexpected value "${value}"`
698
+ * or the custom message if provided.
699
+ *
700
+ * @example
701
+ * // Basic usage with a branded enum
702
+ * const Status = createBrandedEnum('status', {
703
+ * Active: 'active',
704
+ * Inactive: 'inactive',
705
+ * Pending: 'pending',
706
+ * } as const);
707
+ *
708
+ * type StatusValue = typeof Status[keyof typeof Status];
709
+ *
710
+ * function handleStatus(status: StatusValue): string {
711
+ * switch (status) {
712
+ * case Status.Active:
713
+ * return 'User is active';
714
+ * case Status.Inactive:
715
+ * return 'User is inactive';
716
+ * case Status.Pending:
717
+ * return 'User is pending';
718
+ * default:
719
+ * // TypeScript knows this is unreachable if all cases are handled
720
+ * return exhaustive(status);
721
+ * }
722
+ * }
723
+ *
724
+ * @example
725
+ * // Compile-time error when a case is missing
726
+ * function handleStatusIncomplete(status: StatusValue): string {
727
+ * switch (status) {
728
+ * case Status.Active:
729
+ * return 'User is active';
730
+ * case Status.Inactive:
731
+ * return 'User is inactive';
732
+ * // Missing: case Status.Pending
733
+ * default:
734
+ * // TypeScript error: Argument of type '"pending"' is not assignable to parameter of type 'never'
735
+ * return exhaustive(status);
736
+ * }
737
+ * }
738
+ *
739
+ * @example
740
+ * // Custom error message
741
+ * function processValue(value: StatusValue): void {
742
+ * switch (value) {
743
+ * case Status.Active:
744
+ * case Status.Inactive:
745
+ * case Status.Pending:
746
+ * console.log('Handled:', value);
747
+ * break;
748
+ * default:
749
+ * exhaustive(value, `Unknown status value encountered: ${value}`);
750
+ * }
751
+ * }
752
+ *
753
+ * @example
754
+ * // Works with any discriminated union, not just branded enums
755
+ * type Shape =
756
+ * | { kind: 'circle'; radius: number }
757
+ * | { kind: 'square'; side: number }
758
+ * | { kind: 'rectangle'; width: number; height: number };
759
+ *
760
+ * function getArea(shape: Shape): number {
761
+ * switch (shape.kind) {
762
+ * case 'circle':
763
+ * return Math.PI * shape.radius ** 2;
764
+ * case 'square':
765
+ * return shape.side ** 2;
766
+ * case 'rectangle':
767
+ * return shape.width * shape.height;
768
+ * default:
769
+ * return exhaustive(shape);
770
+ * }
771
+ * }
772
+ */
773
+ export declare function exhaustive(value: never, message?: string): never;
774
+ /**
775
+ * Creates an exhaustiveness guard function bound to a specific branded enum.
776
+ *
777
+ * This factory function returns a guard function that can be used in switch
778
+ * statement default cases. The returned function provides better error messages
779
+ * by including the enum ID in the error.
780
+ *
781
+ * This is useful when you want to:
782
+ * - Have consistent error messages that include the enum name
783
+ * - Create reusable guards for specific enums
784
+ * - Provide more context in error messages for debugging
785
+ *
786
+ * @template E - The branded enum type
787
+ * @param enumObj - The branded enum to create a guard for
788
+ * @returns A function that throws an error with the enum ID included in the message
789
+ * @throws {Error} Throws `Error` with message `exhaustiveGuard requires a branded enum`
790
+ * if enumObj is not a valid branded enum.
791
+ *
792
+ * @example
793
+ * // Create a guard for a specific enum
794
+ * const Status = createBrandedEnum('status', {
795
+ * Active: 'active',
796
+ * Inactive: 'inactive',
797
+ * Pending: 'pending',
798
+ * } as const);
799
+ *
800
+ * const assertStatusExhaustive = exhaustiveGuard(Status);
801
+ *
802
+ * type StatusValue = typeof Status[keyof typeof Status];
803
+ *
804
+ * function handleStatus(status: StatusValue): string {
805
+ * switch (status) {
806
+ * case Status.Active:
807
+ * return 'Active';
808
+ * case Status.Inactive:
809
+ * return 'Inactive';
810
+ * case Status.Pending:
811
+ * return 'Pending';
812
+ * default:
813
+ * // Error message will include "status" enum ID
814
+ * return assertStatusExhaustive(status);
815
+ * }
816
+ * }
817
+ *
818
+ * @example
819
+ * // Inline usage without storing the guard
820
+ * function processStatus(status: StatusValue): void {
821
+ * switch (status) {
822
+ * case Status.Active:
823
+ * case Status.Inactive:
824
+ * case Status.Pending:
825
+ * console.log('Processing:', status);
826
+ * break;
827
+ * default:
828
+ * exhaustiveGuard(Status)(status);
829
+ * }
830
+ * }
831
+ *
832
+ * @example
833
+ * // Runtime error message example
834
+ * // If somehow called with an unexpected value at runtime:
835
+ * // Error: Exhaustive check failed for enum "status": unexpected value "unknown"
836
+ *
837
+ * @example
838
+ * // Compile-time error when case is missing
839
+ * function incompleteHandler(status: StatusValue): string {
840
+ * switch (status) {
841
+ * case Status.Active:
842
+ * return 'Active';
843
+ * // Missing: Inactive and Pending cases
844
+ * default:
845
+ * // TypeScript error: Argument of type '"inactive" | "pending"' is not assignable to parameter of type 'never'
846
+ * return assertStatusExhaustive(status);
847
+ * }
848
+ * }
849
+ */
850
+ export declare function exhaustiveGuard<E extends AnyBrandedEnum>(enumObj: E): (value: never) => never;
851
+ /**
852
+ * Options for customizing JSON Schema generation.
853
+ */
854
+ export interface ToJsonSchemaOptions {
855
+ /**
856
+ * Custom title for the schema. Defaults to the enum ID.
857
+ */
858
+ readonly title?: string;
859
+ /**
860
+ * Description for the schema. If not provided, a default description
861
+ * mentioning the enum ID will be used.
862
+ */
863
+ readonly description?: string;
864
+ /**
865
+ * Whether to include the $schema property. Defaults to true.
866
+ */
867
+ readonly includeSchema?: boolean;
868
+ /**
869
+ * The JSON Schema draft version to use. Defaults to 'draft-07'.
870
+ */
871
+ readonly schemaVersion?: 'draft-04' | 'draft-06' | 'draft-07' | '2019-09' | '2020-12';
872
+ }
873
+ /**
874
+ * JSON Schema representation of a branded enum.
875
+ *
876
+ * This interface represents the structure of the generated JSON Schema,
877
+ * following the JSON Schema specification.
878
+ */
879
+ export interface EnumJsonSchema {
880
+ /**
881
+ * The JSON Schema version URI (if includeSchema is true).
882
+ */
883
+ readonly $schema?: string;
884
+ /**
885
+ * The title of the schema (typically the enum ID).
886
+ */
887
+ readonly title: string;
888
+ /**
889
+ * Description of the schema.
890
+ */
891
+ readonly description: string;
892
+ /**
893
+ * The type constraint - always 'string' for branded enums.
894
+ */
895
+ readonly type: 'string';
896
+ /**
897
+ * The enum constraint containing all valid values.
898
+ */
899
+ readonly enum: readonly string[];
900
+ }
901
+ /**
902
+ * Generates a JSON Schema from a branded enum.
903
+ *
904
+ * This function creates a JSON Schema object that validates strings against
905
+ * the values of a branded enum. The generated schema can be used with any
906
+ * JSON Schema validator to ensure that input values are valid enum members.
907
+ *
908
+ * The schema includes:
909
+ * - `$schema`: The JSON Schema version URI (optional, defaults to draft-07)
910
+ * - `title`: The enum ID or a custom title
911
+ * - `description`: A description of the enum
912
+ * - `type`: Always 'string' for branded enums
913
+ * - `enum`: An array of all valid enum values
914
+ *
915
+ * @template E - The branded enum type
916
+ * @param enumObj - The branded enum to generate a schema for
917
+ * @param options - Optional configuration for schema generation
918
+ * @returns A JSON Schema object that validates against the enum values
919
+ * @throws {Error} Throws `Error` with message `toJsonSchema requires a branded enum`
920
+ * if enumObj is not a valid branded enum.
921
+ *
922
+ * @example
923
+ * // Basic usage - generate schema with defaults
924
+ * const Status = createBrandedEnum('status', {
925
+ * Active: 'active',
926
+ * Inactive: 'inactive',
927
+ * Pending: 'pending',
928
+ * } as const);
929
+ *
930
+ * const schema = toJsonSchema(Status);
931
+ * // {
932
+ * // $schema: 'http://json-schema.org/draft-07/schema#',
933
+ * // title: 'status',
934
+ * // description: 'Enum values for status',
935
+ * // type: 'string',
936
+ * // enum: ['active', 'inactive', 'pending']
937
+ * // }
938
+ *
939
+ * @example
940
+ * // Custom title and description
941
+ * const Priority = createBrandedEnum('priority', {
942
+ * High: 'high',
943
+ * Medium: 'medium',
944
+ * Low: 'low',
945
+ * } as const);
946
+ *
947
+ * const schema = toJsonSchema(Priority, {
948
+ * title: 'Task Priority',
949
+ * description: 'The priority level of a task',
950
+ * });
951
+ * // {
952
+ * // $schema: 'http://json-schema.org/draft-07/schema#',
953
+ * // title: 'Task Priority',
954
+ * // description: 'The priority level of a task',
955
+ * // type: 'string',
956
+ * // enum: ['high', 'medium', 'low']
957
+ * // }
958
+ *
959
+ * @example
960
+ * // Without $schema property
961
+ * const schema = toJsonSchema(Status, { includeSchema: false });
962
+ * // {
963
+ * // title: 'status',
964
+ * // description: 'Enum values for status',
965
+ * // type: 'string',
966
+ * // enum: ['active', 'inactive', 'pending']
967
+ * // }
968
+ *
969
+ * @example
970
+ * // Using a different schema version
971
+ * const schema = toJsonSchema(Status, { schemaVersion: '2020-12' });
972
+ * // {
973
+ * // $schema: 'https://json-schema.org/draft/2020-12/schema',
974
+ * // title: 'status',
975
+ * // description: 'Enum values for status',
976
+ * // type: 'string',
977
+ * // enum: ['active', 'inactive', 'pending']
978
+ * // }
979
+ *
980
+ * @example
981
+ * // Use with JSON Schema validators
982
+ * import Ajv from 'ajv';
983
+ *
984
+ * const schema = toJsonSchema(Status);
985
+ * const ajv = new Ajv();
986
+ * const validate = ajv.compile(schema);
987
+ *
988
+ * validate('active'); // true
989
+ * validate('inactive'); // true
990
+ * validate('unknown'); // false
991
+ *
992
+ * @example
993
+ * // Embed in a larger schema
994
+ * const userSchema = {
995
+ * type: 'object',
996
+ * properties: {
997
+ * name: { type: 'string' },
998
+ * status: toJsonSchema(Status, { includeSchema: false }),
999
+ * },
1000
+ * required: ['name', 'status'],
1001
+ * };
1002
+ *
1003
+ * @example
1004
+ * // Generate schemas for API documentation
1005
+ * const schemas = {
1006
+ * Status: toJsonSchema(Status),
1007
+ * Priority: toJsonSchema(Priority),
1008
+ * };
1009
+ *
1010
+ * // Export for OpenAPI/Swagger
1011
+ * const openApiComponents = {
1012
+ * schemas: Object.fromEntries(
1013
+ * Object.entries(schemas).map(([name, schema]) => [
1014
+ * name,
1015
+ * { ...schema, $schema: undefined }, // OpenAPI doesn't use $schema
1016
+ * ])
1017
+ * ),
1018
+ * };
1019
+ */
1020
+ export declare function toJsonSchema<E extends AnyBrandedEnum>(enumObj: E, options?: ToJsonSchemaOptions): EnumJsonSchema;
1021
+ /**
1022
+ * Options for customizing Zod schema definition generation.
1023
+ */
1024
+ export interface ToZodSchemaOptions {
1025
+ /**
1026
+ * Optional description to include in the schema definition.
1027
+ * When provided, the generated schema will include a `description` field
1028
+ * that can be used with Zod's `.describe()` method.
1029
+ */
1030
+ readonly description?: string;
1031
+ }
1032
+ /**
1033
+ * Zod-compatible schema definition for a branded enum.
1034
+ *
1035
+ * This interface represents a schema definition object that can be used
1036
+ * to construct a Zod enum schema without depending on Zod at runtime.
1037
+ * The definition follows Zod's internal structure for enum schemas.
1038
+ *
1039
+ * To use with Zod:
1040
+ * ```typescript
1041
+ * import { z } from 'zod';
1042
+ * const def = toZodSchema(MyEnum);
1043
+ * const schema = z.enum(def.values);
1044
+ * if (def.description) {
1045
+ * schema.describe(def.description);
1046
+ * }
1047
+ * ```
1048
+ */
1049
+ export interface ZodEnumSchemaDefinition<T extends readonly [string, ...string[]]> {
1050
+ /**
1051
+ * The type identifier for this schema definition.
1052
+ * Always 'ZodEnum' to indicate this is an enum schema.
1053
+ */
1054
+ readonly typeName: 'ZodEnum';
1055
+ /**
1056
+ * The enum values as a readonly tuple.
1057
+ * This matches Zod's requirement for `z.enum()` which requires
1058
+ * at least one value (hence the `[string, ...string[]]` type).
1059
+ */
1060
+ readonly values: T;
1061
+ /**
1062
+ * Optional description for the schema.
1063
+ * Can be used with Zod's `.describe()` method.
1064
+ */
1065
+ readonly description?: string;
1066
+ /**
1067
+ * The enum ID from the branded enum.
1068
+ * Useful for debugging and documentation purposes.
1069
+ */
1070
+ readonly enumId: string;
1071
+ }
1072
+ /**
1073
+ * Generates a Zod-compatible schema definition from a branded enum.
1074
+ *
1075
+ * This function creates a schema definition object that can be used to
1076
+ * construct a Zod enum schema. The library maintains zero dependencies
1077
+ * by returning a definition object rather than a Zod instance.
1078
+ *
1079
+ * The returned definition includes:
1080
+ * - `typeName`: Always 'ZodEnum' to identify the schema type
1081
+ * - `values`: A tuple of all enum values (sorted for consistency)
1082
+ * - `description`: Optional description for the schema
1083
+ * - `enumId`: The branded enum's ID for reference
1084
+ *
1085
+ * **Zero Dependencies**: This function does not import or depend on Zod.
1086
+ * It returns a plain object that you can use to construct a Zod schema
1087
+ * in your own code where Zod is available.
1088
+ *
1089
+ * @template E - The branded enum type
1090
+ * @param enumObj - The branded enum to generate a schema definition for
1091
+ * @param options - Optional configuration for schema generation
1092
+ * @returns A Zod-compatible schema definition object
1093
+ * @throws {Error} Throws `Error` with message `toZodSchema requires a branded enum`
1094
+ * if enumObj is not a valid branded enum.
1095
+ * @throws {Error} Throws `Error` with message `toZodSchema requires an enum with at least one value`
1096
+ * if the enum has no values (Zod requires at least one value for z.enum()).
1097
+ *
1098
+ * @example
1099
+ * // Basic usage - generate schema definition
1100
+ * const Status = createBrandedEnum('status', {
1101
+ * Active: 'active',
1102
+ * Inactive: 'inactive',
1103
+ * Pending: 'pending',
1104
+ * } as const);
1105
+ *
1106
+ * const schemaDef = toZodSchema(Status);
1107
+ * // {
1108
+ * // typeName: 'ZodEnum',
1109
+ * // values: ['active', 'inactive', 'pending'],
1110
+ * // enumId: 'status'
1111
+ * // }
1112
+ *
1113
+ * @example
1114
+ * // Use with Zod to create an actual schema
1115
+ * import { z } from 'zod';
1116
+ *
1117
+ * const Status = createBrandedEnum('status', {
1118
+ * Active: 'active',
1119
+ * Inactive: 'inactive',
1120
+ * } as const);
1121
+ *
1122
+ * const def = toZodSchema(Status);
1123
+ * const statusSchema = z.enum(def.values);
1124
+ *
1125
+ * // Validate values
1126
+ * statusSchema.parse('active'); // 'active'
1127
+ * statusSchema.parse('invalid'); // throws ZodError
1128
+ *
1129
+ * @example
1130
+ * // With description
1131
+ * const Priority = createBrandedEnum('priority', {
1132
+ * High: 'high',
1133
+ * Medium: 'medium',
1134
+ * Low: 'low',
1135
+ * } as const);
1136
+ *
1137
+ * const def = toZodSchema(Priority, {
1138
+ * description: 'Task priority level',
1139
+ * });
1140
+ * // {
1141
+ * // typeName: 'ZodEnum',
1142
+ * // values: ['high', 'low', 'medium'],
1143
+ * // description: 'Task priority level',
1144
+ * // enumId: 'priority'
1145
+ * // }
1146
+ *
1147
+ * // Use with Zod
1148
+ * const schema = z.enum(def.values).describe(def.description!);
1149
+ *
1150
+ * @example
1151
+ * // Type-safe schema creation helper
1152
+ * import { z } from 'zod';
1153
+ *
1154
+ * function createZodEnumFromBranded<E extends BrandedEnum<Record<string, string>>>(
1155
+ * enumObj: E,
1156
+ * description?: string
1157
+ * ) {
1158
+ * const def = toZodSchema(enumObj, { description });
1159
+ * const schema = z.enum(def.values);
1160
+ * return description ? schema.describe(description) : schema;
1161
+ * }
1162
+ *
1163
+ * const statusSchema = createZodEnumFromBranded(Status, 'User status');
1164
+ *
1165
+ * @example
1166
+ * // Generate schemas for multiple enums
1167
+ * const schemas = {
1168
+ * status: toZodSchema(Status),
1169
+ * priority: toZodSchema(Priority),
1170
+ * category: toZodSchema(Category),
1171
+ * };
1172
+ *
1173
+ * // Later, construct Zod schemas as needed
1174
+ * import { z } from 'zod';
1175
+ * const zodSchemas = Object.fromEntries(
1176
+ * Object.entries(schemas).map(([key, def]) => [key, z.enum(def.values)])
1177
+ * );
1178
+ *
1179
+ * @example
1180
+ * // Use in form validation
1181
+ * import { z } from 'zod';
1182
+ *
1183
+ * const statusDef = toZodSchema(Status);
1184
+ * const priorityDef = toZodSchema(Priority);
1185
+ *
1186
+ * const taskSchema = z.object({
1187
+ * title: z.string().min(1),
1188
+ * status: z.enum(statusDef.values),
1189
+ * priority: z.enum(priorityDef.values),
1190
+ * });
1191
+ *
1192
+ * type Task = z.infer<typeof taskSchema>;
1193
+ * // { title: string; status: 'active' | 'inactive' | 'pending'; priority: 'high' | 'medium' | 'low' }
1194
+ */
1195
+ export declare function toZodSchema<E extends AnyBrandedEnum>(enumObj: E, options?: ToZodSchemaOptions): ZodEnumSchemaDefinition<readonly [string, ...string[]]>;
1196
+ /**
1197
+ * Options for customizing enum serialization behavior.
1198
+ */
1199
+ export interface EnumSerializerOptions<T extends string = string> {
1200
+ /**
1201
+ * Custom transform function applied during serialization.
1202
+ * Transforms the enum value before it is serialized.
1203
+ *
1204
+ * @param value - The original enum value
1205
+ * @returns The transformed value for serialization
1206
+ */
1207
+ readonly serialize?: (value: T) => string;
1208
+ /**
1209
+ * Custom transform function applied during deserialization.
1210
+ * Transforms the serialized value before validation.
1211
+ * This is applied BEFORE validation against the enum.
1212
+ *
1213
+ * @param value - The serialized value
1214
+ * @returns The transformed value to validate against the enum
1215
+ */
1216
+ readonly deserialize?: (value: string) => string;
1217
+ }
1218
+ /**
1219
+ * Result of a successful deserialization.
1220
+ *
1221
+ * @template T - The type of the deserialized value
1222
+ */
1223
+ export interface DeserializeSuccess<T> {
1224
+ /** Indicates the deserialization was successful */
1225
+ readonly success: true;
1226
+ /** The validated and deserialized enum value */
1227
+ readonly value: T;
1228
+ }
1229
+ /**
1230
+ * Result of a failed deserialization.
1231
+ */
1232
+ export interface DeserializeFailure {
1233
+ /** Indicates the deserialization failed */
1234
+ readonly success: false;
1235
+ /** Error information about the failure */
1236
+ readonly error: {
1237
+ /** Human-readable error message */
1238
+ readonly message: string;
1239
+ /** The input value that failed deserialization */
1240
+ readonly input: unknown;
1241
+ /** The enum ID (if available) */
1242
+ readonly enumId?: string;
1243
+ /** The valid values for the enum (if available) */
1244
+ readonly validValues?: readonly string[];
1245
+ };
1246
+ }
1247
+ /**
1248
+ * Union type representing the result of deserialization.
1249
+ *
1250
+ * @template T - The type of the successfully deserialized value
1251
+ */
1252
+ export type DeserializeResult<T> = DeserializeSuccess<T> | DeserializeFailure;
1253
+ /**
1254
+ * A serializer/deserializer pair for branded enum values.
1255
+ *
1256
+ * Provides methods to serialize enum values (optionally with transformation)
1257
+ * and deserialize values back with validation against the enum.
1258
+ *
1259
+ * @template E - The branded enum type
1260
+ */
1261
+ export interface EnumSerializer<E extends AnyBrandedEnum> {
1262
+ /**
1263
+ * The branded enum this serializer is bound to.
1264
+ */
1265
+ readonly enumObj: E;
1266
+ /**
1267
+ * The enum ID for reference.
1268
+ */
1269
+ readonly enumId: string;
1270
+ /**
1271
+ * Serializes an enum value to a string.
1272
+ *
1273
+ * If a custom serialize transform was provided, it is applied to the value.
1274
+ *
1275
+ * @param value - The enum value to serialize
1276
+ * @returns The serialized string value
1277
+ */
1278
+ serialize(value: EnumValues<E>): string;
1279
+ /**
1280
+ * Deserializes a string value back to an enum value.
1281
+ *
1282
+ * If a custom deserialize transform was provided, it is applied before validation.
1283
+ * Returns a result object indicating success or failure with detailed error info.
1284
+ *
1285
+ * @param value - The string value to deserialize
1286
+ * @returns A DeserializeResult indicating success or failure
1287
+ */
1288
+ deserialize(value: unknown): DeserializeResult<EnumValues<E>>;
1289
+ /**
1290
+ * Deserializes a string value, throwing an error if invalid.
1291
+ *
1292
+ * This is a convenience method that throws instead of returning a result object.
1293
+ *
1294
+ * @param value - The string value to deserialize
1295
+ * @returns The validated enum value
1296
+ * @throws Error if the value is not valid for the enum
1297
+ */
1298
+ deserializeOrThrow(value: unknown): EnumValues<E>;
1299
+ }
1300
+ /**
1301
+ * Creates a serializer/deserializer pair for a branded enum.
1302
+ *
1303
+ * The serializer provides methods to convert enum values to strings (with optional
1304
+ * transformation) and to deserialize strings back to validated enum values.
1305
+ *
1306
+ * This is useful for:
1307
+ * - Storing enum values in databases or localStorage with custom formats
1308
+ * - Transmitting enum values over APIs with encoding/decoding
1309
+ * - Migrating between different value formats
1310
+ * - Adding prefixes/suffixes for namespacing during serialization
1311
+ *
1312
+ * **Serialization Flow:**
1313
+ * 1. Take an enum value
1314
+ * 2. Apply custom `serialize` transform (if provided)
1315
+ * 3. Return the serialized string
1316
+ *
1317
+ * **Deserialization Flow:**
1318
+ * 1. Take a string input
1319
+ * 2. Apply custom `deserialize` transform (if provided)
1320
+ * 3. Validate the result against the enum
1321
+ * 4. Return success with the value, or failure with error details
1322
+ *
1323
+ * @template E - The branded enum type
1324
+ * @param enumObj - The branded enum to create a serializer for
1325
+ * @param options - Optional configuration for custom transforms
1326
+ * @returns An EnumSerializer object with serialize and deserialize methods
1327
+ * @throws {Error} Throws `Error` with message `enumSerializer requires a branded enum`
1328
+ * if enumObj is not a valid branded enum.
1329
+ *
1330
+ * @example
1331
+ * // Basic usage without transforms
1332
+ * const Status = createBrandedEnum('status', {
1333
+ * Active: 'active',
1334
+ * Inactive: 'inactive',
1335
+ * } as const);
1336
+ *
1337
+ * const serializer = enumSerializer(Status);
1338
+ *
1339
+ * // Serialize
1340
+ * const serialized = serializer.serialize(Status.Active); // 'active'
1341
+ *
1342
+ * // Deserialize
1343
+ * const result = serializer.deserialize('active');
1344
+ * if (result.success) {
1345
+ * console.log(result.value); // 'active'
1346
+ * }
1347
+ *
1348
+ * @example
1349
+ * // With custom transforms - add prefix during serialization
1350
+ * const Priority = createBrandedEnum('priority', {
1351
+ * High: 'high',
1352
+ * Medium: 'medium',
1353
+ * Low: 'low',
1354
+ * } as const);
1355
+ *
1356
+ * const serializer = enumSerializer(Priority, {
1357
+ * serialize: (value) => `priority:${value}`,
1358
+ * deserialize: (value) => value.replace('priority:', ''),
1359
+ * });
1360
+ *
1361
+ * // Serialize adds prefix
1362
+ * serializer.serialize(Priority.High); // 'priority:high'
1363
+ *
1364
+ * // Deserialize removes prefix and validates
1365
+ * const result = serializer.deserialize('priority:high');
1366
+ * if (result.success) {
1367
+ * console.log(result.value); // 'high'
1368
+ * }
1369
+ *
1370
+ * @example
1371
+ * // Base64 encoding for storage
1372
+ * const Secret = createBrandedEnum('secret', {
1373
+ * Token: 'token',
1374
+ * Key: 'key',
1375
+ * } as const);
1376
+ *
1377
+ * const serializer = enumSerializer(Secret, {
1378
+ * serialize: (value) => btoa(value),
1379
+ * deserialize: (value) => atob(value),
1380
+ * });
1381
+ *
1382
+ * serializer.serialize(Secret.Token); // 'dG9rZW4=' (base64 of 'token')
1383
+ * serializer.deserialize('dG9rZW4='); // { success: true, value: 'token' }
1384
+ *
1385
+ * @example
1386
+ * // Error handling
1387
+ * const result = serializer.deserialize('invalid');
1388
+ * if (!result.success) {
1389
+ * console.log(result.error.message);
1390
+ * // 'Value "invalid" is not a member of enum "status"'
1391
+ * console.log(result.error.validValues);
1392
+ * // ['active', 'inactive']
1393
+ * }
1394
+ *
1395
+ * @example
1396
+ * // Using deserializeOrThrow for simpler code when errors should throw
1397
+ * try {
1398
+ * const value = serializer.deserializeOrThrow('active');
1399
+ * console.log(value); // 'active'
1400
+ * } catch (e) {
1401
+ * console.error('Invalid value:', e.message);
1402
+ * }
1403
+ *
1404
+ * @example
1405
+ * // Case-insensitive deserialization
1406
+ * const Colors = createBrandedEnum('colors', {
1407
+ * Red: 'red',
1408
+ * Green: 'green',
1409
+ * Blue: 'blue',
1410
+ * } as const);
1411
+ *
1412
+ * const caseInsensitiveSerializer = enumSerializer(Colors, {
1413
+ * deserialize: (value) => value.toLowerCase(),
1414
+ * });
1415
+ *
1416
+ * caseInsensitiveSerializer.deserialize('RED'); // { success: true, value: 'red' }
1417
+ * caseInsensitiveSerializer.deserialize('Red'); // { success: true, value: 'red' }
1418
+ * caseInsensitiveSerializer.deserialize('red'); // { success: true, value: 'red' }
1419
+ */
1420
+ export declare function enumSerializer<E extends AnyBrandedEnum>(enumObj: E, options?: EnumSerializerOptions<EnumValues<E>>): EnumSerializer<E>;
1421
+ export {};
1422
+ //# sourceMappingURL=advanced.d.ts.map