@digitaldefiance/branded-enum 0.0.1 → 0.0.3
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/dist/cjs/index.cjs +149 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/lib/accessors.cjs +55 -0
- package/dist/cjs/lib/accessors.cjs.map +1 -0
- package/dist/cjs/lib/advanced.cjs +558 -0
- package/dist/cjs/lib/advanced.cjs.map +1 -0
- package/dist/cjs/lib/branded-enum.cjs +15 -0
- package/dist/cjs/lib/branded-enum.cjs.map +1 -0
- package/dist/cjs/lib/decorators.cjs +202 -0
- package/dist/cjs/lib/decorators.cjs.map +1 -0
- package/dist/cjs/lib/factory.cjs +45 -0
- package/dist/cjs/lib/factory.cjs.map +1 -0
- package/dist/cjs/lib/guards.cjs +119 -0
- package/dist/cjs/lib/guards.cjs.map +1 -0
- package/dist/cjs/lib/merge.cjs +47 -0
- package/dist/cjs/lib/merge.cjs.map +1 -0
- package/dist/cjs/lib/registry.cjs +85 -0
- package/dist/cjs/lib/registry.cjs.map +1 -0
- package/dist/cjs/lib/types.cjs +38 -0
- package/dist/cjs/lib/types.cjs.map +1 -0
- package/dist/cjs/lib/utils.cjs +80 -0
- package/dist/cjs/lib/utils.cjs.map +1 -0
- package/dist/esm/index.d.ts +21 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/accessors.d.ts +79 -0
- package/dist/esm/lib/accessors.d.ts.map +1 -0
- package/dist/esm/lib/accessors.js.map +1 -0
- package/dist/esm/lib/advanced.d.ts +1422 -0
- package/dist/esm/lib/advanced.d.ts.map +1 -0
- package/dist/esm/lib/advanced.js.map +1 -0
- package/dist/esm/lib/branded-enum.d.ts +2 -0
- package/dist/esm/lib/branded-enum.d.ts.map +1 -0
- package/dist/esm/lib/branded-enum.js.map +1 -0
- package/dist/esm/lib/decorators.d.ts +147 -0
- package/dist/esm/lib/decorators.d.ts.map +1 -0
- package/dist/esm/lib/decorators.js.map +1 -0
- package/dist/esm/lib/factory.d.ts +52 -0
- package/dist/esm/lib/factory.d.ts.map +1 -0
- package/dist/esm/lib/factory.js.map +1 -0
- package/dist/esm/lib/guards.d.ts +297 -0
- package/dist/esm/lib/guards.d.ts.map +1 -0
- package/dist/esm/lib/guards.js.map +1 -0
- package/dist/esm/lib/merge.d.ts +64 -0
- package/dist/esm/lib/merge.d.ts.map +1 -0
- package/dist/esm/lib/merge.js.map +1 -0
- package/dist/esm/lib/registry.d.ts +93 -0
- package/dist/esm/lib/registry.d.ts.map +1 -0
- package/dist/esm/lib/registry.js.map +1 -0
- package/dist/esm/lib/types.d.ts +258 -0
- package/dist/esm/lib/types.d.ts.map +1 -0
- package/dist/esm/lib/types.js.map +1 -0
- package/dist/esm/lib/utils.d.ts +121 -0
- package/dist/esm/lib/utils.d.ts.map +1 -0
- package/dist/esm/lib/utils.js.map +1 -0
- package/package.json +31 -18
- package/dist/index.js.map +0 -1
- package/dist/lib/accessors.js.map +0 -1
- package/dist/lib/advanced.js.map +0 -1
- package/dist/lib/branded-enum.js.map +0 -1
- package/dist/lib/decorators.js.map +0 -1
- package/dist/lib/factory.js.map +0 -1
- package/dist/lib/guards.js.map +0 -1
- package/dist/lib/merge.js.map +0 -1
- package/dist/lib/registry.js.map +0 -1
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
- package/dist/package.json +0 -120
- /package/dist/{index.js → esm/index.js} +0 -0
- /package/dist/{lib → esm/lib}/accessors.js +0 -0
- /package/dist/{lib → esm/lib}/advanced.js +0 -0
- /package/dist/{lib → esm/lib}/branded-enum.js +0 -0
- /package/dist/{lib → esm/lib}/decorators.js +0 -0
- /package/dist/{lib → esm/lib}/factory.js +0 -0
- /package/dist/{lib → esm/lib}/guards.js +0 -0
- /package/dist/{lib → esm/lib}/merge.js +0 -0
- /package/dist/{lib → esm/lib}/registry.js +0 -0
- /package/dist/{lib → esm/lib}/types.js +0 -0
- /package/dist/{lib → esm/lib}/utils.js +0 -0
|
@@ -0,0 +1,558 @@
|
|
|
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
|
+
*/ "use strict";
|
|
7
|
+
Object.defineProperty(exports, "__esModule", {
|
|
8
|
+
value: true
|
|
9
|
+
});
|
|
10
|
+
function _export(target, all) {
|
|
11
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: all[name]
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
_export(exports, {
|
|
17
|
+
clearAllEnumWatchers: function() {
|
|
18
|
+
return clearAllEnumWatchers;
|
|
19
|
+
},
|
|
20
|
+
enumDiff: function() {
|
|
21
|
+
return enumDiff;
|
|
22
|
+
},
|
|
23
|
+
enumExclude: function() {
|
|
24
|
+
return enumExclude;
|
|
25
|
+
},
|
|
26
|
+
enumFromKeys: function() {
|
|
27
|
+
return enumFromKeys;
|
|
28
|
+
},
|
|
29
|
+
enumIntersect: function() {
|
|
30
|
+
return enumIntersect;
|
|
31
|
+
},
|
|
32
|
+
enumMap: function() {
|
|
33
|
+
return enumMap;
|
|
34
|
+
},
|
|
35
|
+
enumSerializer: function() {
|
|
36
|
+
return enumSerializer;
|
|
37
|
+
},
|
|
38
|
+
enumSubset: function() {
|
|
39
|
+
return enumSubset;
|
|
40
|
+
},
|
|
41
|
+
enumToRecord: function() {
|
|
42
|
+
return enumToRecord;
|
|
43
|
+
},
|
|
44
|
+
exhaustive: function() {
|
|
45
|
+
return exhaustive;
|
|
46
|
+
},
|
|
47
|
+
exhaustiveGuard: function() {
|
|
48
|
+
return exhaustiveGuard;
|
|
49
|
+
},
|
|
50
|
+
getEnumWatcherCount: function() {
|
|
51
|
+
return getEnumWatcherCount;
|
|
52
|
+
},
|
|
53
|
+
getGlobalWatcherCount: function() {
|
|
54
|
+
return getGlobalWatcherCount;
|
|
55
|
+
},
|
|
56
|
+
toJsonSchema: function() {
|
|
57
|
+
return toJsonSchema;
|
|
58
|
+
},
|
|
59
|
+
toZodSchema: function() {
|
|
60
|
+
return toZodSchema;
|
|
61
|
+
},
|
|
62
|
+
watchAllEnums: function() {
|
|
63
|
+
return watchAllEnums;
|
|
64
|
+
},
|
|
65
|
+
watchEnum: function() {
|
|
66
|
+
return watchEnum;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const _types = require("./types.js");
|
|
70
|
+
const _factory = require("./factory.js");
|
|
71
|
+
/**
|
|
72
|
+
* Checks if an object is a branded enum (has Symbol metadata).
|
|
73
|
+
*
|
|
74
|
+
* @param obj - The object to check
|
|
75
|
+
* @returns true if obj is a branded enum
|
|
76
|
+
*/ function isBrandedEnum(obj) {
|
|
77
|
+
return obj !== null && typeof obj === 'object' && _types.ENUM_ID in obj && _types.ENUM_VALUES in obj && typeof obj[_types.ENUM_ID] === 'string' && obj[_types.ENUM_VALUES] instanceof Set;
|
|
78
|
+
}
|
|
79
|
+
function enumSubset(newId, sourceEnum, keys) {
|
|
80
|
+
// Validate that sourceEnum is a branded enum
|
|
81
|
+
if (!isBrandedEnum(sourceEnum)) {
|
|
82
|
+
throw new Error('enumSubset requires a branded enum as the source');
|
|
83
|
+
}
|
|
84
|
+
// Validate that keys array is not empty
|
|
85
|
+
if (keys.length === 0) {
|
|
86
|
+
throw new Error('enumSubset requires at least one key');
|
|
87
|
+
}
|
|
88
|
+
const sourceEnumId = sourceEnum[_types.ENUM_ID];
|
|
89
|
+
// Build the subset values object
|
|
90
|
+
const subsetValues = {};
|
|
91
|
+
for (const key of keys){
|
|
92
|
+
// Validate that the key exists in the source enum
|
|
93
|
+
if (!(key in sourceEnum)) {
|
|
94
|
+
throw new Error(`Key "${key}" does not exist in enum "${sourceEnumId}"`);
|
|
95
|
+
}
|
|
96
|
+
// Copy the key-value pair
|
|
97
|
+
subsetValues[key] = sourceEnum[key];
|
|
98
|
+
}
|
|
99
|
+
// Create and return the new branded enum (this handles registration)
|
|
100
|
+
return (0, _factory.createBrandedEnum)(newId, subsetValues);
|
|
101
|
+
}
|
|
102
|
+
function enumExclude(newId, sourceEnum, keysToExclude) {
|
|
103
|
+
// Validate that sourceEnum is a branded enum
|
|
104
|
+
if (!isBrandedEnum(sourceEnum)) {
|
|
105
|
+
throw new Error('enumExclude requires a branded enum as the source');
|
|
106
|
+
}
|
|
107
|
+
const sourceEnumId = sourceEnum[_types.ENUM_ID];
|
|
108
|
+
// Validate that all keys to exclude exist in the source enum
|
|
109
|
+
for (const key of keysToExclude){
|
|
110
|
+
if (!(key in sourceEnum)) {
|
|
111
|
+
throw new Error(`Key "${key}" does not exist in enum "${sourceEnumId}"`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Create a Set for O(1) lookup of excluded keys
|
|
115
|
+
const excludeSet = new Set(keysToExclude);
|
|
116
|
+
// Get all keys from the source enum (excluding Symbol metadata)
|
|
117
|
+
const allKeys = Object.keys(sourceEnum);
|
|
118
|
+
// Build the result values object with non-excluded keys
|
|
119
|
+
const resultValues = {};
|
|
120
|
+
for (const key of allKeys){
|
|
121
|
+
if (!excludeSet.has(key)) {
|
|
122
|
+
resultValues[key] = sourceEnum[key];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Validate that we have at least one key remaining
|
|
126
|
+
if (Object.keys(resultValues).length === 0) {
|
|
127
|
+
throw new Error('enumExclude: excluding all keys would result in an empty enum');
|
|
128
|
+
}
|
|
129
|
+
// Create and return the new branded enum (this handles registration)
|
|
130
|
+
return (0, _factory.createBrandedEnum)(newId, resultValues);
|
|
131
|
+
}
|
|
132
|
+
function enumMap(newId, sourceEnum, mapper) {
|
|
133
|
+
// Validate that sourceEnum is a branded enum
|
|
134
|
+
if (!isBrandedEnum(sourceEnum)) {
|
|
135
|
+
throw new Error('enumMap requires a branded enum as the source');
|
|
136
|
+
}
|
|
137
|
+
// Get all keys from the source enum (excluding Symbol metadata)
|
|
138
|
+
const allKeys = Object.keys(sourceEnum);
|
|
139
|
+
// Build the result values object with transformed values
|
|
140
|
+
const resultValues = {};
|
|
141
|
+
for (const key of allKeys){
|
|
142
|
+
const originalValue = sourceEnum[key];
|
|
143
|
+
const transformedValue = mapper(originalValue, key);
|
|
144
|
+
// Validate that the mapper returned a string
|
|
145
|
+
if (typeof transformedValue !== 'string') {
|
|
146
|
+
throw new Error('enumMap mapper must return a string');
|
|
147
|
+
}
|
|
148
|
+
resultValues[key] = transformedValue;
|
|
149
|
+
}
|
|
150
|
+
// Create and return the new branded enum (this handles registration)
|
|
151
|
+
return (0, _factory.createBrandedEnum)(newId, resultValues);
|
|
152
|
+
}
|
|
153
|
+
function enumFromKeys(enumId, keys) {
|
|
154
|
+
// Validate that keys array is not empty
|
|
155
|
+
if (keys.length === 0) {
|
|
156
|
+
throw new Error('enumFromKeys requires at least one key');
|
|
157
|
+
}
|
|
158
|
+
// Track seen keys to detect duplicates
|
|
159
|
+
const seenKeys = new Set();
|
|
160
|
+
// Build the values object where each key maps to itself
|
|
161
|
+
const values = {};
|
|
162
|
+
for (const key of keys){
|
|
163
|
+
// Validate that each key is a non-empty string
|
|
164
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
165
|
+
throw new Error('enumFromKeys requires all keys to be non-empty strings');
|
|
166
|
+
}
|
|
167
|
+
// Check for duplicate keys
|
|
168
|
+
if (seenKeys.has(key)) {
|
|
169
|
+
throw new Error(`enumFromKeys: duplicate key "${key}" found`);
|
|
170
|
+
}
|
|
171
|
+
seenKeys.add(key);
|
|
172
|
+
values[key] = key;
|
|
173
|
+
}
|
|
174
|
+
// Create and return the new branded enum (this handles registration)
|
|
175
|
+
return (0, _factory.createBrandedEnum)(enumId, values);
|
|
176
|
+
}
|
|
177
|
+
function enumDiff(firstEnum, secondEnum) {
|
|
178
|
+
// Validate that both arguments are branded enums
|
|
179
|
+
if (!isBrandedEnum(firstEnum) || !isBrandedEnum(secondEnum)) {
|
|
180
|
+
throw new Error('enumDiff requires branded enums as arguments');
|
|
181
|
+
}
|
|
182
|
+
// Get all keys from both enums (excluding Symbol metadata)
|
|
183
|
+
const firstKeys = new Set(Object.keys(firstEnum));
|
|
184
|
+
const secondKeys = new Set(Object.keys(secondEnum));
|
|
185
|
+
const onlyInFirst = [];
|
|
186
|
+
const onlyInSecond = [];
|
|
187
|
+
const differentValues = [];
|
|
188
|
+
const sameValues = [];
|
|
189
|
+
// Find keys only in first enum and keys in both
|
|
190
|
+
for (const key of firstKeys){
|
|
191
|
+
const firstValue = firstEnum[key];
|
|
192
|
+
if (!secondKeys.has(key)) {
|
|
193
|
+
// Key only exists in first enum
|
|
194
|
+
onlyInFirst.push({
|
|
195
|
+
key,
|
|
196
|
+
value: firstValue
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
// Key exists in both - compare values
|
|
200
|
+
const secondValue = secondEnum[key];
|
|
201
|
+
if (firstValue === secondValue) {
|
|
202
|
+
sameValues.push({
|
|
203
|
+
key,
|
|
204
|
+
value: firstValue
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
differentValues.push({
|
|
208
|
+
key,
|
|
209
|
+
firstValue,
|
|
210
|
+
secondValue
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Find keys only in second enum
|
|
216
|
+
for (const key of secondKeys){
|
|
217
|
+
if (!firstKeys.has(key)) {
|
|
218
|
+
const secondValue = secondEnum[key];
|
|
219
|
+
onlyInSecond.push({
|
|
220
|
+
key,
|
|
221
|
+
value: secondValue
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
onlyInFirst,
|
|
227
|
+
onlyInSecond,
|
|
228
|
+
differentValues,
|
|
229
|
+
sameValues
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function enumIntersect(...enums) {
|
|
233
|
+
// Validate that at least two enums are provided
|
|
234
|
+
if (enums.length < 2) {
|
|
235
|
+
throw new Error('enumIntersect requires at least two branded enums');
|
|
236
|
+
}
|
|
237
|
+
// Validate that all arguments are branded enums
|
|
238
|
+
for (const enumObj of enums){
|
|
239
|
+
if (!isBrandedEnum(enumObj)) {
|
|
240
|
+
throw new Error('enumIntersect requires all arguments to be branded enums');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Build a map of value -> Set of enum IDs containing that value
|
|
244
|
+
const valueToEnumIds = new Map();
|
|
245
|
+
for (const enumObj of enums){
|
|
246
|
+
const enumId = enumObj[_types.ENUM_ID];
|
|
247
|
+
const values = enumObj[_types.ENUM_VALUES];
|
|
248
|
+
for (const value of values){
|
|
249
|
+
if (!valueToEnumIds.has(value)) {
|
|
250
|
+
valueToEnumIds.set(value, new Set());
|
|
251
|
+
}
|
|
252
|
+
valueToEnumIds.get(value).add(enumId);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Filter to only values that appear in multiple enums
|
|
256
|
+
const result = [];
|
|
257
|
+
for (const [value, enumIds] of valueToEnumIds){
|
|
258
|
+
if (enumIds.size >= 2) {
|
|
259
|
+
result.push({
|
|
260
|
+
value,
|
|
261
|
+
enumIds: Array.from(enumIds).sort()
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Sort by value for consistent ordering
|
|
266
|
+
result.sort((a, b)=>a.value.localeCompare(b.value));
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
function enumToRecord(enumObj) {
|
|
270
|
+
// Validate that enumObj is a branded enum
|
|
271
|
+
if (!isBrandedEnum(enumObj)) {
|
|
272
|
+
throw new Error('enumToRecord requires a branded enum');
|
|
273
|
+
}
|
|
274
|
+
// Create a new plain object with only the enumerable key-value pairs
|
|
275
|
+
const result = {};
|
|
276
|
+
for (const key of Object.keys(enumObj)){
|
|
277
|
+
result[key] = enumObj[key];
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The key used to store the enum watcher registry on globalThis.
|
|
283
|
+
* Namespaced to avoid collisions with other libraries.
|
|
284
|
+
*/ const WATCHER_REGISTRY_KEY = '__brandedEnumWatcherRegistry__';
|
|
285
|
+
/**
|
|
286
|
+
* Gets or initializes the watcher registry on globalThis.
|
|
287
|
+
*/ function getWatcherRegistry() {
|
|
288
|
+
const global = globalThis;
|
|
289
|
+
if (!global[WATCHER_REGISTRY_KEY]) {
|
|
290
|
+
global[WATCHER_REGISTRY_KEY] = {
|
|
291
|
+
watchers: new Map(),
|
|
292
|
+
globalWatchers: new Set()
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return global[WATCHER_REGISTRY_KEY];
|
|
296
|
+
}
|
|
297
|
+
function watchEnum(enumObj, callback) {
|
|
298
|
+
// Validate that enumObj is a branded enum
|
|
299
|
+
if (!isBrandedEnum(enumObj)) {
|
|
300
|
+
throw new Error('watchEnum requires a branded enum');
|
|
301
|
+
}
|
|
302
|
+
const enumId = enumObj[_types.ENUM_ID];
|
|
303
|
+
const registry = getWatcherRegistry();
|
|
304
|
+
// Add callback to the registry for this enum
|
|
305
|
+
if (!registry.watchers.has(enumId)) {
|
|
306
|
+
registry.watchers.set(enumId, new Set());
|
|
307
|
+
}
|
|
308
|
+
registry.watchers.get(enumId).add(callback);
|
|
309
|
+
// Track if this watcher is still active
|
|
310
|
+
let isActive = true;
|
|
311
|
+
// Create a Proxy to intercept access
|
|
312
|
+
const proxy = new Proxy(enumObj, {
|
|
313
|
+
get (target, prop, receiver) {
|
|
314
|
+
const value = Reflect.get(target, prop, receiver);
|
|
315
|
+
// Only trigger callback for active watchers and string keys (not symbols)
|
|
316
|
+
if (isActive && typeof prop === 'string') {
|
|
317
|
+
const event = {
|
|
318
|
+
enumId,
|
|
319
|
+
accessType: 'get',
|
|
320
|
+
key: prop,
|
|
321
|
+
value: typeof value === 'string' ? value : undefined,
|
|
322
|
+
timestamp: Date.now()
|
|
323
|
+
};
|
|
324
|
+
// Call the specific callback
|
|
325
|
+
callback(event);
|
|
326
|
+
// Also call any global watchers
|
|
327
|
+
for (const globalCallback of registry.globalWatchers){
|
|
328
|
+
globalCallback(event);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return value;
|
|
332
|
+
},
|
|
333
|
+
has (target, prop) {
|
|
334
|
+
const result = Reflect.has(target, prop);
|
|
335
|
+
// Only trigger callback for active watchers and string keys
|
|
336
|
+
if (isActive && typeof prop === 'string') {
|
|
337
|
+
const event = {
|
|
338
|
+
enumId,
|
|
339
|
+
accessType: 'has',
|
|
340
|
+
key: prop,
|
|
341
|
+
timestamp: Date.now()
|
|
342
|
+
};
|
|
343
|
+
callback(event);
|
|
344
|
+
for (const globalCallback of registry.globalWatchers){
|
|
345
|
+
globalCallback(event);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
349
|
+
},
|
|
350
|
+
ownKeys (target) {
|
|
351
|
+
const keys = Reflect.ownKeys(target);
|
|
352
|
+
if (isActive) {
|
|
353
|
+
const event = {
|
|
354
|
+
enumId,
|
|
355
|
+
accessType: 'keys',
|
|
356
|
+
timestamp: Date.now()
|
|
357
|
+
};
|
|
358
|
+
callback(event);
|
|
359
|
+
for (const globalCallback of registry.globalWatchers){
|
|
360
|
+
globalCallback(event);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return keys;
|
|
364
|
+
},
|
|
365
|
+
getOwnPropertyDescriptor (target, prop) {
|
|
366
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
// Create unwatch function
|
|
370
|
+
const unwatch = ()=>{
|
|
371
|
+
isActive = false;
|
|
372
|
+
const callbacks = registry.watchers.get(enumId);
|
|
373
|
+
if (callbacks) {
|
|
374
|
+
callbacks.delete(callback);
|
|
375
|
+
if (callbacks.size === 0) {
|
|
376
|
+
registry.watchers.delete(enumId);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
watched: proxy,
|
|
382
|
+
unwatch
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function watchAllEnums(callback) {
|
|
386
|
+
const registry = getWatcherRegistry();
|
|
387
|
+
registry.globalWatchers.add(callback);
|
|
388
|
+
return ()=>{
|
|
389
|
+
registry.globalWatchers.delete(callback);
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function clearAllEnumWatchers() {
|
|
393
|
+
const registry = getWatcherRegistry();
|
|
394
|
+
registry.watchers.clear();
|
|
395
|
+
registry.globalWatchers.clear();
|
|
396
|
+
}
|
|
397
|
+
function getEnumWatcherCount(enumId) {
|
|
398
|
+
const registry = getWatcherRegistry();
|
|
399
|
+
return registry.watchers.get(enumId)?.size ?? 0;
|
|
400
|
+
}
|
|
401
|
+
function getGlobalWatcherCount() {
|
|
402
|
+
const registry = getWatcherRegistry();
|
|
403
|
+
return registry.globalWatchers.size;
|
|
404
|
+
}
|
|
405
|
+
function exhaustive(value, message) {
|
|
406
|
+
const errorMessage = message ?? `Exhaustive check failed: unexpected value "${String(value)}"`;
|
|
407
|
+
throw new Error(errorMessage);
|
|
408
|
+
}
|
|
409
|
+
function exhaustiveGuard(enumObj) {
|
|
410
|
+
// Validate that enumObj is a branded enum
|
|
411
|
+
if (!isBrandedEnum(enumObj)) {
|
|
412
|
+
throw new Error('exhaustiveGuard requires a branded enum');
|
|
413
|
+
}
|
|
414
|
+
const enumId = enumObj[_types.ENUM_ID];
|
|
415
|
+
return (value)=>{
|
|
416
|
+
throw new Error(`Exhaustive check failed for enum "${enumId}": unexpected value "${String(value)}"`);
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Maps schema version options to their corresponding $schema URIs.
|
|
421
|
+
*/ const SCHEMA_VERSION_URIS = {
|
|
422
|
+
'draft-04': 'http://json-schema.org/draft-04/schema#',
|
|
423
|
+
'draft-06': 'http://json-schema.org/draft-06/schema#',
|
|
424
|
+
'draft-07': 'http://json-schema.org/draft-07/schema#',
|
|
425
|
+
'2019-09': 'https://json-schema.org/draft/2019-09/schema',
|
|
426
|
+
'2020-12': 'https://json-schema.org/draft/2020-12/schema'
|
|
427
|
+
};
|
|
428
|
+
function toJsonSchema(enumObj, options = {}) {
|
|
429
|
+
// Validate that enumObj is a branded enum
|
|
430
|
+
if (!isBrandedEnum(enumObj)) {
|
|
431
|
+
throw new Error('toJsonSchema requires a branded enum');
|
|
432
|
+
}
|
|
433
|
+
const enumId = enumObj[_types.ENUM_ID];
|
|
434
|
+
const values = enumObj[_types.ENUM_VALUES];
|
|
435
|
+
// Extract options with defaults
|
|
436
|
+
const { title = enumId, description = `Enum values for ${enumId}`, includeSchema = true, schemaVersion = 'draft-07' } = options;
|
|
437
|
+
// Build the schema object
|
|
438
|
+
const schema = {
|
|
439
|
+
title,
|
|
440
|
+
description,
|
|
441
|
+
type: 'string',
|
|
442
|
+
enum: Array.from(values).sort()
|
|
443
|
+
};
|
|
444
|
+
// Add $schema if requested
|
|
445
|
+
if (includeSchema) {
|
|
446
|
+
return {
|
|
447
|
+
$schema: SCHEMA_VERSION_URIS[schemaVersion],
|
|
448
|
+
...schema
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return schema;
|
|
452
|
+
}
|
|
453
|
+
function toZodSchema(enumObj, options = {}) {
|
|
454
|
+
// Validate that enumObj is a branded enum
|
|
455
|
+
if (!isBrandedEnum(enumObj)) {
|
|
456
|
+
throw new Error('toZodSchema requires a branded enum');
|
|
457
|
+
}
|
|
458
|
+
const enumId = enumObj[_types.ENUM_ID];
|
|
459
|
+
const values = enumObj[_types.ENUM_VALUES];
|
|
460
|
+
// Convert Set to sorted array
|
|
461
|
+
const valuesArray = Array.from(values).sort();
|
|
462
|
+
// Zod's z.enum() requires at least one value
|
|
463
|
+
if (valuesArray.length === 0) {
|
|
464
|
+
throw new Error('toZodSchema requires an enum with at least one value');
|
|
465
|
+
}
|
|
466
|
+
// Build the schema definition
|
|
467
|
+
const definition = {
|
|
468
|
+
typeName: 'ZodEnum',
|
|
469
|
+
values: valuesArray,
|
|
470
|
+
enumId
|
|
471
|
+
};
|
|
472
|
+
// Add description if provided
|
|
473
|
+
if (options.description !== undefined) {
|
|
474
|
+
return {
|
|
475
|
+
...definition,
|
|
476
|
+
description: options.description
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return definition;
|
|
480
|
+
}
|
|
481
|
+
function enumSerializer(enumObj, options = {}) {
|
|
482
|
+
// Validate that enumObj is a branded enum
|
|
483
|
+
if (!isBrandedEnum(enumObj)) {
|
|
484
|
+
throw new Error('enumSerializer requires a branded enum');
|
|
485
|
+
}
|
|
486
|
+
const enumId = enumObj[_types.ENUM_ID];
|
|
487
|
+
const values = enumObj[_types.ENUM_VALUES];
|
|
488
|
+
const validValues = Array.from(values).sort();
|
|
489
|
+
const { serialize: serializeTransform, deserialize: deserializeTransform } = options;
|
|
490
|
+
return {
|
|
491
|
+
enumObj,
|
|
492
|
+
enumId,
|
|
493
|
+
serialize (value) {
|
|
494
|
+
// Apply custom transform if provided
|
|
495
|
+
if (serializeTransform) {
|
|
496
|
+
return serializeTransform(value);
|
|
497
|
+
}
|
|
498
|
+
return value;
|
|
499
|
+
},
|
|
500
|
+
deserialize (value) {
|
|
501
|
+
// Check if value is a string
|
|
502
|
+
if (typeof value !== 'string') {
|
|
503
|
+
const valueType = value === null ? 'null' : typeof value;
|
|
504
|
+
return {
|
|
505
|
+
success: false,
|
|
506
|
+
error: {
|
|
507
|
+
message: `Expected a string value, received ${valueType}`,
|
|
508
|
+
input: value,
|
|
509
|
+
enumId,
|
|
510
|
+
validValues
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// Apply custom deserialize transform if provided
|
|
515
|
+
let transformedValue = value;
|
|
516
|
+
if (deserializeTransform) {
|
|
517
|
+
try {
|
|
518
|
+
transformedValue = deserializeTransform(value);
|
|
519
|
+
} catch (e) {
|
|
520
|
+
return {
|
|
521
|
+
success: false,
|
|
522
|
+
error: {
|
|
523
|
+
message: `Deserialize transform failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
524
|
+
input: value,
|
|
525
|
+
enumId,
|
|
526
|
+
validValues
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Validate against the enum
|
|
532
|
+
if (!values.has(transformedValue)) {
|
|
533
|
+
return {
|
|
534
|
+
success: false,
|
|
535
|
+
error: {
|
|
536
|
+
message: `Value "${transformedValue}" is not a member of enum "${enumId}"`,
|
|
537
|
+
input: value,
|
|
538
|
+
enumId,
|
|
539
|
+
validValues
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
success: true,
|
|
545
|
+
value: transformedValue
|
|
546
|
+
};
|
|
547
|
+
},
|
|
548
|
+
deserializeOrThrow (value) {
|
|
549
|
+
const result = this.deserialize(value);
|
|
550
|
+
if (!result.success) {
|
|
551
|
+
throw new Error(result.error.message);
|
|
552
|
+
}
|
|
553
|
+
return result.value;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
//# sourceMappingURL=advanced.cjs.map
|