@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.
package/README.md ADDED
@@ -0,0 +1,756 @@
1
+ # @digitaldefiance/branded-enum
2
+
3
+ Runtime-identifiable enum-like types for TypeScript with zero runtime overhead.
4
+
5
+ ## Why branded-enum?
6
+
7
+ Standard TypeScript enums are erased at compile time, making it impossible to determine which enum a string value originated from at runtime. This becomes problematic in large codebases with multiple libraries that may have overlapping string values.
8
+
9
+ **branded-enum** solves this by:
10
+
11
+ - Creating enum-like objects with embedded metadata for runtime identification
12
+ - Providing type guards to check if a value belongs to a specific enum
13
+ - Maintaining a global registry to track all branded enums across bundles
14
+ - Keeping values as raw strings for zero runtime overhead and serialization compatibility
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @digitaldefiance/branded-enum
20
+ # or
21
+ yarn add @digitaldefiance/branded-enum
22
+ # or
23
+ pnpm add @digitaldefiance/branded-enum
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { createBrandedEnum, isFromEnum, getEnumId } from '@digitaldefiance/branded-enum';
30
+
31
+ // Create a branded enum (use `as const` for literal type inference)
32
+ const Status = createBrandedEnum('status', {
33
+ Active: 'active',
34
+ Inactive: 'inactive',
35
+ Pending: 'pending',
36
+ } as const);
37
+
38
+ // Values are raw strings - no wrapper overhead
39
+ console.log(Status.Active); // 'active'
40
+
41
+ // Type guard with automatic type narrowing
42
+ function handleValue(value: unknown) {
43
+ if (isFromEnum(value, Status)) {
44
+ // value is narrowed to 'active' | 'inactive' | 'pending'
45
+ console.log('Valid status:', value);
46
+ }
47
+ }
48
+
49
+ // Runtime identification
50
+ console.log(getEnumId(Status)); // 'status'
51
+ ```
52
+
53
+ ## Features
54
+
55
+ ### Runtime Identification
56
+
57
+ Unlike standard TypeScript enums, branded enums carry metadata that enables runtime identification:
58
+
59
+ ```typescript
60
+ import { createBrandedEnum, findEnumSources, getEnumById } from '@digitaldefiance/branded-enum';
61
+
62
+ const Colors = createBrandedEnum('colors', { Red: 'red', Blue: 'blue' } as const);
63
+ const Sizes = createBrandedEnum('sizes', { Small: 'small', Large: 'large' } as const);
64
+
65
+ // Find which enums contain a value
66
+ findEnumSources('red'); // ['colors']
67
+
68
+ // Retrieve enum by ID
69
+ const retrieved = getEnumById('colors');
70
+ console.log(retrieved === Colors); // true
71
+ ```
72
+
73
+ ### Type Guards
74
+
75
+ Validate values at runtime with automatic TypeScript type narrowing:
76
+
77
+ ```typescript
78
+ import { createBrandedEnum, isFromEnum, assertFromEnum } from '@digitaldefiance/branded-enum';
79
+
80
+ const Priority = createBrandedEnum('priority', {
81
+ High: 'high',
82
+ Medium: 'medium',
83
+ Low: 'low',
84
+ } as const);
85
+
86
+ // Soft check - returns boolean
87
+ if (isFromEnum(userInput, Priority)) {
88
+ // userInput is typed as 'high' | 'medium' | 'low'
89
+ }
90
+
91
+ // Hard check - throws on invalid value
92
+ const validated = assertFromEnum(userInput, Priority);
93
+ // Throws: 'Value "invalid" is not a member of enum "priority"'
94
+ ```
95
+
96
+ ### Serialization Compatible
97
+
98
+ Branded enums serialize cleanly to JSON - metadata is stored in non-enumerable Symbol properties:
99
+
100
+ ```typescript
101
+ const Status = createBrandedEnum('status', { Active: 'active' } as const);
102
+
103
+ JSON.stringify(Status);
104
+ // '{"Active":"active"}' - no metadata pollution
105
+
106
+ Object.keys(Status); // ['Active']
107
+ Object.values(Status); // ['active']
108
+ ```
109
+
110
+ ### Cross-Bundle Registry
111
+
112
+ The global registry uses `globalThis`, ensuring all branded enums are tracked across different bundles, ESM/CJS modules, and even different instances of the library:
113
+
114
+ ```typescript
115
+ import { getAllEnumIds, getEnumById } from '@digitaldefiance/branded-enum';
116
+
117
+ // List all registered enums
118
+ getAllEnumIds(); // ['status', 'colors', 'sizes', ...]
119
+
120
+ // Access any enum by ID
121
+ const enum = getEnumById('status');
122
+ ```
123
+
124
+ ### Enum Composition
125
+
126
+ Merge multiple enums into a new combined enum:
127
+
128
+ ```typescript
129
+ import { createBrandedEnum, mergeEnums } from '@digitaldefiance/branded-enum';
130
+
131
+ const HttpSuccess = createBrandedEnum('http-success', {
132
+ OK: '200',
133
+ Created: '201',
134
+ } as const);
135
+
136
+ const HttpError = createBrandedEnum('http-error', {
137
+ BadRequest: '400',
138
+ NotFound: '404',
139
+ } as const);
140
+
141
+ const HttpCodes = mergeEnums('http-codes', HttpSuccess, HttpError);
142
+ // HttpCodes has: OK, Created, BadRequest, NotFound
143
+ ```
144
+
145
+ ## API Reference
146
+
147
+ ### Factory
148
+
149
+ #### `createBrandedEnum(enumId, values)`
150
+
151
+ Creates a branded enum with runtime metadata.
152
+
153
+ ```typescript
154
+ function createBrandedEnum<T extends Record<string, string>>(
155
+ enumId: string,
156
+ values: T
157
+ ): BrandedEnum<T>
158
+ ```
159
+
160
+ - **enumId**: Unique identifier for this enum
161
+ - **values**: Object with key-value pairs (use `as const` for literal types)
162
+ - **Returns**: Frozen branded enum object
163
+ - **Throws**: `Error` if enumId is already registered
164
+
165
+ ### Type Guards
166
+
167
+ #### `isFromEnum(value, enumObj)`
168
+
169
+ Checks if a value belongs to a branded enum.
170
+
171
+ ```typescript
172
+ function isFromEnum<E extends BrandedEnum<Record<string, string>>>(
173
+ value: unknown,
174
+ enumObj: E
175
+ ): value is BrandedEnumValue<E>
176
+ ```
177
+
178
+ - Returns `true` with type narrowing if value is in the enum
179
+ - Returns `false` for non-string values or non-branded enum objects
180
+
181
+ #### `assertFromEnum(value, enumObj)`
182
+
183
+ Asserts a value belongs to a branded enum, throwing if not.
184
+
185
+ ```typescript
186
+ function assertFromEnum<E extends BrandedEnum<Record<string, string>>>(
187
+ value: unknown,
188
+ enumObj: E
189
+ ): BrandedEnumValue<E>
190
+ ```
191
+
192
+ - **Returns**: The value with narrowed type
193
+ - **Throws**: `Error` if value is not in the enum
194
+
195
+ ### Metadata Accessors
196
+
197
+ #### `getEnumId(enumObj)`
198
+
199
+ Gets the enum ID from a branded enum.
200
+
201
+ ```typescript
202
+ function getEnumId(enumObj: unknown): string | undefined
203
+ ```
204
+
205
+ #### `getEnumValues(enumObj)`
206
+
207
+ Gets all values from a branded enum as an array.
208
+
209
+ ```typescript
210
+ function getEnumValues<E extends BrandedEnum<Record<string, string>>>(
211
+ enumObj: E
212
+ ): BrandedEnumValue<E>[] | undefined
213
+ ```
214
+
215
+ #### `enumSize(enumObj)`
216
+
217
+ Gets the number of values in a branded enum.
218
+
219
+ ```typescript
220
+ function enumSize(enumObj: unknown): number | undefined
221
+ ```
222
+
223
+ ### Registry Functions
224
+
225
+ #### `getAllEnumIds()`
226
+
227
+ Returns an array of all registered enum IDs.
228
+
229
+ ```typescript
230
+ function getAllEnumIds(): string[]
231
+ ```
232
+
233
+ #### `getEnumById(enumId)`
234
+
235
+ Gets a branded enum by its ID.
236
+
237
+ ```typescript
238
+ function getEnumById(enumId: string): BrandedEnum<Record<string, string>> | undefined
239
+ ```
240
+
241
+ #### `findEnumSources(value)`
242
+
243
+ Finds all enum IDs that contain a given value.
244
+
245
+ ```typescript
246
+ function findEnumSources(value: string): string[]
247
+ ```
248
+
249
+ ### Utility Functions
250
+
251
+ #### `hasValue(enumObj, value)`
252
+
253
+ Checks if a value exists in a branded enum (reverse lookup).
254
+
255
+ ```typescript
256
+ function hasValue<E extends BrandedEnum<Record<string, string>>>(
257
+ enumObj: E,
258
+ value: unknown
259
+ ): value is BrandedEnumValue<E>
260
+ ```
261
+
262
+ #### `getKeyForValue(enumObj, value)`
263
+
264
+ Gets the key name for a value in a branded enum.
265
+
266
+ ```typescript
267
+ function getKeyForValue<E extends BrandedEnum<Record<string, string>>>(
268
+ enumObj: E,
269
+ value: string
270
+ ): keyof E | undefined
271
+ ```
272
+
273
+ #### `isValidKey(enumObj, key)`
274
+
275
+ Checks if a key exists in a branded enum.
276
+
277
+ ```typescript
278
+ function isValidKey<E extends BrandedEnum<Record<string, string>>>(
279
+ enumObj: E,
280
+ key: unknown
281
+ ): key is keyof E
282
+ ```
283
+
284
+ #### `enumEntries(enumObj)`
285
+
286
+ Returns an iterator of [key, value] pairs.
287
+
288
+ ```typescript
289
+ function* enumEntries<E extends BrandedEnum<Record<string, string>>>(
290
+ enumObj: E
291
+ ): IterableIterator<[keyof E, BrandedEnumValue<E>]>
292
+ ```
293
+
294
+ ### Composition
295
+
296
+ #### `mergeEnums(newId, ...enums)`
297
+
298
+ Merges multiple branded enums into a new one.
299
+
300
+ ```typescript
301
+ function mergeEnums<T extends readonly BrandedEnum<Record<string, string>>[]>(
302
+ newId: string,
303
+ ...enums: T
304
+ ): BrandedEnum<Record<string, string>>
305
+ ```
306
+
307
+ - **Throws**: `Error` if duplicate keys are found across enums
308
+ - Duplicate values are allowed (intentional overlaps)
309
+
310
+ ## Types
311
+
312
+ ```typescript
313
+ // The branded enum type
314
+ type BrandedEnum<T extends Record<string, string>> = Readonly<T> & BrandedEnumMetadata;
315
+
316
+ // Extract value union from a branded enum
317
+ type BrandedEnumValue<E extends BrandedEnum<Record<string, string>>> =
318
+ E extends BrandedEnum<infer T> ? T[keyof T] : never;
319
+ ```
320
+
321
+ ## Use Cases
322
+
323
+ ### i18n Key Management
324
+
325
+ ```typescript
326
+ const UserMessages = createBrandedEnum('user-messages', {
327
+ Welcome: 'user.welcome',
328
+ Goodbye: 'user.goodbye',
329
+ } as const);
330
+
331
+ const AdminMessages = createBrandedEnum('admin-messages', {
332
+ Welcome: 'admin.welcome', // Different value, same key name
333
+ } as const);
334
+
335
+ // Determine which translation namespace to use
336
+ function translate(key: string) {
337
+ const sources = findEnumSources(key);
338
+ if (sources.includes('user-messages')) {
339
+ return userTranslations[key];
340
+ }
341
+ if (sources.includes('admin-messages')) {
342
+ return adminTranslations[key];
343
+ }
344
+ }
345
+ ```
346
+
347
+ ### API Response Validation
348
+
349
+ ```typescript
350
+ const ApiStatus = createBrandedEnum('api-status', {
351
+ Success: 'success',
352
+ Error: 'error',
353
+ Pending: 'pending',
354
+ } as const);
355
+
356
+ function handleResponse(response: { status: unknown }) {
357
+ const status = assertFromEnum(response.status, ApiStatus);
358
+ // status is typed as 'success' | 'error' | 'pending'
359
+
360
+ switch (status) {
361
+ case ApiStatus.Success:
362
+ // TypeScript knows this is exhaustive
363
+ break;
364
+ case ApiStatus.Error:
365
+ break;
366
+ case ApiStatus.Pending:
367
+ break;
368
+ }
369
+ }
370
+ ```
371
+
372
+ ### Plugin Systems
373
+
374
+ ```typescript
375
+ // Core events
376
+ const CoreEvents = createBrandedEnum('core-events', {
377
+ Init: 'init',
378
+ Ready: 'ready',
379
+ } as const);
380
+
381
+ // Plugin events
382
+ const PluginEvents = createBrandedEnum('plugin-events', {
383
+ Load: 'plugin:load',
384
+ Unload: 'plugin:unload',
385
+ } as const);
386
+
387
+ // Combined for the event bus
388
+ const AllEvents = mergeEnums('all-events', CoreEvents, PluginEvents);
389
+
390
+ function emit(event: string) {
391
+ if (isFromEnum(event, CoreEvents)) {
392
+ handleCoreEvent(event);
393
+ } else if (isFromEnum(event, PluginEvents)) {
394
+ handlePluginEvent(event);
395
+ }
396
+ }
397
+ ```
398
+
399
+ ## Advanced Features
400
+
401
+ The library includes powerful advanced features for complex use cases.
402
+
403
+ ### Decorators
404
+
405
+ Runtime validation decorators for class properties that enforce enum membership.
406
+
407
+ #### `@EnumValue` - Property Validation
408
+
409
+ ```typescript
410
+ import { createBrandedEnum, EnumValue } from '@digitaldefiance/branded-enum';
411
+
412
+ const Status = createBrandedEnum('status', {
413
+ Active: 'active',
414
+ Inactive: 'inactive',
415
+ } as const);
416
+
417
+ class User {
418
+ @EnumValue(Status)
419
+ accessor status: string = Status.Active;
420
+ }
421
+
422
+ const user = new User();
423
+ user.status = Status.Active; // OK
424
+ user.status = 'invalid'; // Throws Error
425
+
426
+ // Optional and nullable support
427
+ class Config {
428
+ @EnumValue(Status, { optional: true })
429
+ accessor status: string | undefined;
430
+
431
+ @EnumValue(Status, { nullable: true })
432
+ accessor fallbackStatus: string | null = null;
433
+ }
434
+ ```
435
+
436
+ #### `@EnumClass` - Usage Tracking
437
+
438
+ ```typescript
439
+ import { createBrandedEnum, EnumClass, getEnumConsumers, getConsumedEnums } from '@digitaldefiance/branded-enum';
440
+
441
+ const Status = createBrandedEnum('status', { Active: 'active' } as const);
442
+ const Priority = createBrandedEnum('priority', { High: 'high' } as const);
443
+
444
+ @EnumClass(Status, Priority)
445
+ class Task {
446
+ status = Status.Active;
447
+ priority = Priority.High;
448
+ }
449
+
450
+ // Query enum usage
451
+ getEnumConsumers('status'); // ['Task']
452
+ getConsumedEnums('Task'); // ['status', 'priority']
453
+ ```
454
+
455
+ ### Enum Derivation
456
+
457
+ Create new enums from existing ones.
458
+
459
+ #### `enumSubset` - Select Keys
460
+
461
+ ```typescript
462
+ import { createBrandedEnum, enumSubset } from '@digitaldefiance/branded-enum';
463
+
464
+ const AllColors = createBrandedEnum('all-colors', {
465
+ Red: 'red', Green: 'green', Blue: 'blue', Yellow: 'yellow',
466
+ } as const);
467
+
468
+ const PrimaryColors = enumSubset('primary-colors', AllColors, ['Red', 'Blue', 'Yellow']);
469
+ // PrimaryColors has: Red, Blue, Yellow (no Green)
470
+ ```
471
+
472
+ #### `enumExclude` - Remove Keys
473
+
474
+ ```typescript
475
+ import { createBrandedEnum, enumExclude } from '@digitaldefiance/branded-enum';
476
+
477
+ const Status = createBrandedEnum('status', {
478
+ Active: 'active', Inactive: 'inactive', Deprecated: 'deprecated',
479
+ } as const);
480
+
481
+ const CurrentStatuses = enumExclude('current-statuses', Status, ['Deprecated']);
482
+ // CurrentStatuses has: Active, Inactive
483
+ ```
484
+
485
+ #### `enumMap` - Transform Values
486
+
487
+ ```typescript
488
+ import { createBrandedEnum, enumMap } from '@digitaldefiance/branded-enum';
489
+
490
+ const Status = createBrandedEnum('status', {
491
+ Active: 'active', Inactive: 'inactive',
492
+ } as const);
493
+
494
+ // Add prefix to all values
495
+ const PrefixedStatus = enumMap('prefixed-status', Status, (value) => `app.${value}`);
496
+ // PrefixedStatus.Active === 'app.active'
497
+
498
+ // Transform with key context
499
+ const VerboseStatus = enumMap('verbose-status', Status, (value, key) => `${key}: ${value}`);
500
+ ```
501
+
502
+ #### `enumFromKeys` - Keys as Values
503
+
504
+ ```typescript
505
+ import { enumFromKeys } from '@digitaldefiance/branded-enum';
506
+
507
+ const Directions = enumFromKeys('directions', ['North', 'South', 'East', 'West'] as const);
508
+ // Equivalent to: { North: 'North', South: 'South', East: 'East', West: 'West' }
509
+ ```
510
+
511
+ ### Enum Analysis
512
+
513
+ Compare and analyze enums.
514
+
515
+ #### `enumDiff` - Compare Enums
516
+
517
+ ```typescript
518
+ import { createBrandedEnum, enumDiff } from '@digitaldefiance/branded-enum';
519
+
520
+ const StatusV1 = createBrandedEnum('status-v1', {
521
+ Active: 'active', Inactive: 'inactive',
522
+ } as const);
523
+
524
+ const StatusV2 = createBrandedEnum('status-v2', {
525
+ Active: 'active', Inactive: 'disabled', Pending: 'pending',
526
+ } as const);
527
+
528
+ const diff = enumDiff(StatusV1, StatusV2);
529
+ // diff.onlyInFirst: []
530
+ // diff.onlyInSecond: [{ key: 'Pending', value: 'pending' }]
531
+ // diff.differentValues: [{ key: 'Inactive', firstValue: 'inactive', secondValue: 'disabled' }]
532
+ // diff.sameValues: [{ key: 'Active', value: 'active' }]
533
+ ```
534
+
535
+ #### `enumIntersect` - Find Shared Values
536
+
537
+ ```typescript
538
+ import { createBrandedEnum, enumIntersect } from '@digitaldefiance/branded-enum';
539
+
540
+ const PrimaryColors = createBrandedEnum('primary', { Red: 'red', Blue: 'blue' } as const);
541
+ const WarmColors = createBrandedEnum('warm', { Red: 'red', Orange: 'orange' } as const);
542
+
543
+ const shared = enumIntersect(PrimaryColors, WarmColors);
544
+ // [{ value: 'red', enumIds: ['primary', 'warm'] }]
545
+ ```
546
+
547
+ ### Safe Parsing
548
+
549
+ Parse values without throwing errors.
550
+
551
+ #### `parseEnum` - With Default
552
+
553
+ ```typescript
554
+ import { createBrandedEnum, parseEnum } from '@digitaldefiance/branded-enum';
555
+
556
+ const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);
557
+
558
+ const status = parseEnum(userInput, Status, Status.Active);
559
+ // Returns userInput if valid, otherwise Status.Active
560
+ ```
561
+
562
+ #### `safeParseEnum` - Result Object
563
+
564
+ ```typescript
565
+ import { createBrandedEnum, safeParseEnum } from '@digitaldefiance/branded-enum';
566
+
567
+ const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);
568
+
569
+ const result = safeParseEnum(userInput, Status);
570
+ if (result.success) {
571
+ console.log('Valid:', result.value);
572
+ } else {
573
+ console.log('Error:', result.error.message);
574
+ console.log('Valid values:', result.error.validValues);
575
+ }
576
+ ```
577
+
578
+ ### Exhaustiveness Checking
579
+
580
+ Ensure all enum cases are handled in switch statements.
581
+
582
+ #### `exhaustive` - Generic Helper
583
+
584
+ ```typescript
585
+ import { createBrandedEnum, exhaustive } from '@digitaldefiance/branded-enum';
586
+
587
+ const Status = createBrandedEnum('status', {
588
+ Active: 'active', Inactive: 'inactive', Pending: 'pending',
589
+ } as const);
590
+
591
+ type StatusValue = typeof Status[keyof typeof Status];
592
+
593
+ function handleStatus(status: StatusValue): string {
594
+ switch (status) {
595
+ case Status.Active: return 'User is active';
596
+ case Status.Inactive: return 'User is inactive';
597
+ case Status.Pending: return 'User is pending';
598
+ default: return exhaustive(status); // TypeScript error if case missing
599
+ }
600
+ }
601
+ ```
602
+
603
+ #### `exhaustiveGuard` - Enum-Specific Guard
604
+
605
+ ```typescript
606
+ import { createBrandedEnum, exhaustiveGuard } from '@digitaldefiance/branded-enum';
607
+
608
+ const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);
609
+ const assertStatusExhaustive = exhaustiveGuard(Status);
610
+
611
+ function handleStatus(status: typeof Status[keyof typeof Status]): string {
612
+ switch (status) {
613
+ case Status.Active: return 'Active';
614
+ case Status.Inactive: return 'Inactive';
615
+ default: return assertStatusExhaustive(status);
616
+ // Error includes enum ID: 'Exhaustive check failed for enum "status"'
617
+ }
618
+ }
619
+ ```
620
+
621
+ ### Schema Generation
622
+
623
+ Generate schemas for validation libraries.
624
+
625
+ #### `toJsonSchema` - JSON Schema
626
+
627
+ ```typescript
628
+ import { createBrandedEnum, toJsonSchema } from '@digitaldefiance/branded-enum';
629
+
630
+ const Status = createBrandedEnum('status', {
631
+ Active: 'active', Inactive: 'inactive',
632
+ } as const);
633
+
634
+ const schema = toJsonSchema(Status);
635
+ // {
636
+ // $schema: 'http://json-schema.org/draft-07/schema#',
637
+ // title: 'status',
638
+ // description: 'Enum values for status',
639
+ // type: 'string',
640
+ // enum: ['active', 'inactive']
641
+ // }
642
+
643
+ // Custom options
644
+ const customSchema = toJsonSchema(Status, {
645
+ title: 'User Status',
646
+ description: 'The current status of a user',
647
+ schemaVersion: '2020-12',
648
+ });
649
+ ```
650
+
651
+ #### `toZodSchema` - Zod-Compatible Definition
652
+
653
+ ```typescript
654
+ import { createBrandedEnum, toZodSchema } from '@digitaldefiance/branded-enum';
655
+ import { z } from 'zod';
656
+
657
+ const Status = createBrandedEnum('status', {
658
+ Active: 'active', Inactive: 'inactive',
659
+ } as const);
660
+
661
+ const def = toZodSchema(Status);
662
+ const statusSchema = z.enum(def.values);
663
+
664
+ statusSchema.parse('active'); // 'active'
665
+ statusSchema.parse('invalid'); // throws ZodError
666
+ ```
667
+
668
+ ### Serialization
669
+
670
+ Custom serialization/deserialization with transforms.
671
+
672
+ #### `enumSerializer` - Serializer Factory
673
+
674
+ ```typescript
675
+ import { createBrandedEnum, enumSerializer } from '@digitaldefiance/branded-enum';
676
+
677
+ const Status = createBrandedEnum('status', {
678
+ Active: 'active', Inactive: 'inactive',
679
+ } as const);
680
+
681
+ // Basic serializer
682
+ const serializer = enumSerializer(Status);
683
+ serializer.serialize(Status.Active); // 'active'
684
+ serializer.deserialize('active'); // { success: true, value: 'active' }
685
+
686
+ // With custom transforms (e.g., add prefix)
687
+ const prefixedSerializer = enumSerializer(Status, {
688
+ serialize: (value) => `status:${value}`,
689
+ deserialize: (value) => value.replace('status:', ''),
690
+ });
691
+
692
+ prefixedSerializer.serialize(Status.Active); // 'status:active'
693
+ prefixedSerializer.deserialize('status:active'); // { success: true, value: 'active' }
694
+ ```
695
+
696
+ ### Development Tooling
697
+
698
+ Debug and monitor enum usage.
699
+
700
+ #### `watchEnum` - Access Monitoring
701
+
702
+ ```typescript
703
+ import { createBrandedEnum, watchEnum } from '@digitaldefiance/branded-enum';
704
+
705
+ const Status = createBrandedEnum('status', { Active: 'active', Inactive: 'inactive' } as const);
706
+
707
+ const { watched, unwatch } = watchEnum(Status, (event) => {
708
+ console.log(`Accessed ${event.enumId}.${event.key} = ${event.value}`);
709
+ });
710
+
711
+ watched.Active; // Logs: "Accessed status.Active = active"
712
+ unwatch(); // Stop watching
713
+ ```
714
+
715
+ ### Utility Functions
716
+
717
+ #### `enumToRecord` - Strip Metadata
718
+
719
+ ```typescript
720
+ import { createBrandedEnum, enumToRecord } from '@digitaldefiance/branded-enum';
721
+
722
+ const Status = createBrandedEnum('status', { Active: 'active' } as const);
723
+ const plain = enumToRecord(Status);
724
+ // { Active: 'active' } - plain object, no Symbol metadata
725
+ ```
726
+
727
+ ### Compile-Time Types
728
+
729
+ TypeScript utility types for enhanced type safety.
730
+
731
+ ```typescript
732
+ import { createBrandedEnum, EnumKeys, EnumValues, ValidEnumValue, StrictEnumParam } from '@digitaldefiance/branded-enum';
733
+
734
+ const Status = createBrandedEnum('status', {
735
+ Active: 'active', Inactive: 'inactive',
736
+ } as const);
737
+
738
+ // Extract key union
739
+ type StatusKeys = EnumKeys<typeof Status>; // 'Active' | 'Inactive'
740
+
741
+ // Extract value union
742
+ type StatusValues = EnumValues<typeof Status>; // 'active' | 'inactive'
743
+
744
+ // Validate value at compile time
745
+ type Valid = ValidEnumValue<typeof Status, 'active'>; // 'active'
746
+ type Invalid = ValidEnumValue<typeof Status, 'unknown'>; // never
747
+
748
+ // Strict function parameters
749
+ function updateStatus(status: StrictEnumParam<typeof Status>): void {
750
+ // Only accepts 'active' | 'inactive'
751
+ }
752
+ ```
753
+
754
+ ## License
755
+
756
+ MIT © [Digital Defiance](https://github.com/Digital-Defiance)