@reharik/smart-enum 0.4.5 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,29 +1,608 @@
1
- import {
2
- getSubsetByProp,
3
- subsetByProp
4
- } from "./chunk-F65KCMGE.js";
5
- import {
6
- EnumRevivalError,
7
- prepareForDatabase,
8
- revivePayloadFromDatabase,
9
- reviveRowFromDatabase
10
- } from "./chunk-62LMAE3X.js";
11
- import {
12
- getGlobalEnumRegistry,
13
- initializeSmartEnumMappings,
14
- reviveAfterTransport,
15
- serializeForTransport
16
- } from "./chunk-QNY5W4SX.js";
17
- import {
18
- enumeration,
19
- isSmartEnum,
20
- isSmartEnumItem,
21
- resetDefaultSerializationMode,
22
- reviveEnumField,
23
- reviveSmartEnums,
24
- serializeSmartEnums,
25
- setDefaultSerializationMode
26
- } from "./chunk-WNCJN5E6.js";
1
+ // src/types.ts
2
+ var SMART_ENUM_ITEM = Symbol("smart-enum-item");
3
+ var SMART_ENUM_ID = Symbol("smart-enum-id");
4
+ var SMART_ENUM = Symbol("smart-enum");
5
+
6
+ // src/utilities/typeGuards.ts
7
+ var isSmartEnumItem = (x) => {
8
+ return !!x && typeof x === "object" && Reflect.get(x, SMART_ENUM_ITEM) === true;
9
+ };
10
+ var isSmartEnum = (x) => {
11
+ return !!x && typeof x === "object" && Reflect.get(x, SMART_ENUM) === true;
12
+ };
13
+ var isSerializedSmartEnumItem = (x) => {
14
+ return !!x && typeof x === "object" && Reflect.has(x, "__smart_enum_type") && Reflect.has(x, "value");
15
+ };
16
+
17
+ // src/utilities/enumItemsEqual.ts
18
+ var enumItemsEqual = (a, b) => {
19
+ if (!isSmartEnumItem(a) || !isSmartEnumItem(b)) {
20
+ return false;
21
+ }
22
+ return Reflect.get(a, SMART_ENUM_ID) === Reflect.get(b, SMART_ENUM_ID) && a.value === b.value;
23
+ };
24
+
25
+ // src/extensionMethods.ts
26
+ var addExtensionMethods = (enumItems) => {
27
+ const findBy = (field, target) => enumItems.find((item) => item[field] === target);
28
+ const requireBy = (field, target, label) => {
29
+ const item = findBy(field, target);
30
+ if (!item) {
31
+ throw new Error(`No enum ${label} found for '${String(target)}'`);
32
+ }
33
+ return item;
34
+ };
35
+ return {
36
+ fromValue: (value) => requireBy("value", value, "value"),
37
+ tryFromValue: (value) => value ? findBy("value", value) : void 0,
38
+ fromKey: (key) => requireBy("key", key, "key"),
39
+ tryFromKey: (key) => key ? findBy("key", key) : void 0,
40
+ equals: enumItemsEqual,
41
+ items: () => [...enumItems],
42
+ values: () => enumItems.map((item) => item.value),
43
+ keys: () => enumItems.map((item) => item.key)
44
+ };
45
+ };
46
+
47
+ // src/utilities/getSubsetByProp.ts
48
+ var RESERVED_ENUM_MEMBER_KEYS = /* @__PURE__ */ new Set([
49
+ "fromValue",
50
+ "tryFromValue",
51
+ "fromKey",
52
+ "tryFromKey",
53
+ "items",
54
+ "values",
55
+ "keys"
56
+ ]);
57
+ var getSubsetByProp = (enumInstance, prop, value) => {
58
+ const matching = [];
59
+ const memberEntries = {};
60
+ for (const key of Reflect.ownKeys(enumInstance)) {
61
+ if (typeof key !== "string" || RESERVED_ENUM_MEMBER_KEYS.has(key)) {
62
+ continue;
63
+ }
64
+ const entry = enumInstance[key];
65
+ if (!isSmartEnumItem(entry)) {
66
+ continue;
67
+ }
68
+ const item = entry;
69
+ if (Object.is(item[prop], value)) {
70
+ memberEntries[key] = item;
71
+ matching.push(item);
72
+ }
73
+ }
74
+ const methods = addExtensionMethods(
75
+ matching
76
+ );
77
+ return {
78
+ ...memberEntries,
79
+ ...methods
80
+ };
81
+ };
82
+ var subsetByProp = (prop) => (enumInstance, value) => (
83
+ // `getSubsetByProp` widens `prop` in its return; re-assert to keep curried literal `P` for Pick/Extract.
84
+ getSubsetByProp(
85
+ enumInstance,
86
+ prop,
87
+ value
88
+ )
89
+ );
90
+
91
+ // src/enumerations.ts
92
+ import { capitalCase, constantCase } from "case-anything";
93
+
94
+ // src/utilities/serializationMode.ts
95
+ var globalDefault;
96
+ var setDefaultSerializationMode = (mode) => {
97
+ globalDefault = mode;
98
+ };
99
+ var resetDefaultSerializationMode = () => {
100
+ globalDefault = void 0;
101
+ };
102
+ var resolveSerializationMode = (perEnum) => perEnum ?? globalDefault ?? "wrapped";
103
+
104
+ // src/enumerations.ts
105
+ function normalizeInput(input) {
106
+ if (Array.isArray(input)) {
107
+ return Object.fromEntries(
108
+ input.map((k) => [k, {}])
109
+ );
110
+ }
111
+ return input;
112
+ }
113
+ var finalizeEnumItem = (item, enumType, enumInstanceId, serializeAs) => {
114
+ Object.defineProperty(item, SMART_ENUM_ITEM, {
115
+ value: true,
116
+ enumerable: false
117
+ });
118
+ Object.defineProperty(item, SMART_ENUM_ID, {
119
+ value: enumInstanceId,
120
+ enumerable: false
121
+ });
122
+ Object.defineProperty(item, "__smart_enum_brand", {
123
+ value: true,
124
+ enumerable: false
125
+ });
126
+ Object.defineProperty(item, "__smart_enum_type", {
127
+ value: enumType,
128
+ enumerable: false
129
+ });
130
+ Object.defineProperty(item, "toJSON", {
131
+ value: () => {
132
+ const mode = resolveSerializationMode(serializeAs);
133
+ if (mode === "value") {
134
+ return item.value;
135
+ }
136
+ return { __smart_enum_type: enumType, value: item.value };
137
+ },
138
+ enumerable: false
139
+ });
140
+ Object.defineProperty(item, "toPostgres", {
141
+ value: () => item.value,
142
+ enumerable: false
143
+ });
144
+ Object.defineProperty(item, "equals", {
145
+ value: (other) => enumItemsEqual(item, other),
146
+ enumerable: false
147
+ });
148
+ return item;
149
+ };
150
+ var formatProperties = (k, formatters) => formatters.reduce(
151
+ (acc, formatter) => {
152
+ acc[formatter.key] = formatter.format(k);
153
+ return acc;
154
+ },
155
+ {
156
+ value: constantCase(k),
157
+ display: capitalCase(k)
158
+ }
159
+ );
160
+ function buildEnumFromObject(enumType, input, propertyAutoFormatters, serializeAs) {
161
+ const formattersWithDefaults = [
162
+ { key: "value", format: constantCase },
163
+ { key: "display", format: capitalCase },
164
+ ...propertyAutoFormatters ?? []
165
+ ];
166
+ const rawEnumItems = {};
167
+ const enumInstanceId = Symbol("smart-enum-instance");
168
+ let index = 0;
169
+ for (const key in input) {
170
+ if (Object.prototype.hasOwnProperty.call(input, key)) {
171
+ const typedKey = key;
172
+ const value = input[typedKey];
173
+ const enumItemBase = {
174
+ index,
175
+ key: typedKey,
176
+ ...formatProperties(typedKey, formattersWithDefaults),
177
+ ...value
178
+ };
179
+ const enumItem = finalizeEnumItem(
180
+ enumItemBase,
181
+ enumType,
182
+ enumInstanceId,
183
+ serializeAs
184
+ );
185
+ Object.freeze(enumItem);
186
+ rawEnumItems[typedKey] = enumItem;
187
+ index++;
188
+ }
189
+ }
190
+ const extensionMethods = addExtensionMethods(
191
+ Object.values(rawEnumItems)
192
+ );
193
+ const enumObject = {
194
+ ...rawEnumItems,
195
+ ...extensionMethods
196
+ };
197
+ Object.defineProperty(enumObject, SMART_ENUM, {
198
+ value: true,
199
+ enumerable: false
200
+ });
201
+ Object.freeze(enumObject);
202
+ return enumObject;
203
+ }
204
+ function enumeration(enumType, props) {
205
+ const normalized = normalizeInput(props.input);
206
+ return buildEnumFromObject(
207
+ enumType,
208
+ normalized,
209
+ props.propertyAutoFormatters,
210
+ props.serializeAs
211
+ );
212
+ }
213
+
214
+ // src/utilities/logger.ts
215
+ var consoleLogger = {
216
+ debug(message, ...args) {
217
+ console.debug(`[@reharik/smart-enum:debug] ${message}`, ...args);
218
+ },
219
+ info(message, ...args) {
220
+ console.info(`[@reharik/smart-enum:info] ${message}`, ...args);
221
+ },
222
+ warn(message, ...args) {
223
+ console.warn(`[@reharik/smart-enum:warn] ${message}`, ...args);
224
+ },
225
+ error(message, ...args) {
226
+ console.error(`[@reharik/smart-enum:error] ${message}`, ...args);
227
+ }
228
+ };
229
+ var globalLogger = consoleLogger;
230
+ function setLogger(logger) {
231
+ globalLogger = logger;
232
+ }
233
+ function getLogger() {
234
+ return globalLogger;
235
+ }
236
+ function debug(message, ...args) {
237
+ globalLogger.debug(message, ...args);
238
+ }
239
+ function info(message, ...args) {
240
+ globalLogger.info(message, ...args);
241
+ }
242
+
243
+ // src/utilities/transformation.ts
244
+ var isPlainObject = (x) => typeof x === "object" && x !== null && Object.getPrototypeOf(x) === Object.prototype;
245
+ function serializeSmartEnums(input) {
246
+ const seen = /* @__PURE__ */ new WeakMap();
247
+ const walk = (v) => {
248
+ if (isSmartEnumItem(v)) {
249
+ return {
250
+ __smart_enum_type: v.__smart_enum_type,
251
+ value: v.value
252
+ };
253
+ }
254
+ if (typeof v === "object" && v !== null && seen.has(v)) {
255
+ return seen.get(v);
256
+ }
257
+ if (Array.isArray(v)) {
258
+ const arr = [];
259
+ seen.set(v, arr);
260
+ for (const item of v) {
261
+ arr.push(walk(item));
262
+ }
263
+ return arr;
264
+ }
265
+ if (isPlainObject(v)) {
266
+ const out = {};
267
+ seen.set(v, out);
268
+ for (const [k, val] of Object.entries(v)) {
269
+ out[k] = walk(val);
270
+ }
271
+ return out;
272
+ }
273
+ return v;
274
+ };
275
+ return walk(input);
276
+ }
277
+ function reviveSmartEnums(input, registry) {
278
+ const seen = /* @__PURE__ */ new WeakMap();
279
+ const walk = (v) => {
280
+ if (isSerializedSmartEnumItem(v)) {
281
+ debug(`Found serialized smartEnum: ${v.__smart_enum_type}`);
282
+ const enumInstance = registry[v.__smart_enum_type];
283
+ if (enumInstance) {
284
+ debug(`Found enumInstance in registry: ${v.__smart_enum_type}`);
285
+ const enumItem = enumInstance.tryFromValue(v.value);
286
+ if (enumItem) {
287
+ debug(`Revived enumItem using value: ${v.value}`);
288
+ return enumItem;
289
+ }
290
+ const key = v.value.toLowerCase();
291
+ const enumItemFromKey = enumInstance.tryFromKey(key);
292
+ if (enumItemFromKey) {
293
+ debug(`Revived enumItem using key: ${key}`);
294
+ return enumItemFromKey;
295
+ }
296
+ }
297
+ return v;
298
+ }
299
+ if (typeof v === "object" && v !== null && seen.has(v)) {
300
+ return seen.get(v);
301
+ }
302
+ if (Array.isArray(v)) {
303
+ const arr = [];
304
+ seen.set(v, arr);
305
+ for (const item of v) arr.push(walk(item));
306
+ return arr;
307
+ }
308
+ if (isPlainObject(v)) {
309
+ const out = {};
310
+ seen.set(v, out);
311
+ for (const [k, val] of Object.entries(v)) {
312
+ out[k] = walk(val);
313
+ }
314
+ return out;
315
+ }
316
+ return v;
317
+ };
318
+ return walk(input);
319
+ }
320
+ var reviveEnumField = (value, smartEnum, strict = false) => {
321
+ if (typeof value !== "string") return void 0;
322
+ const revived = smartEnum.tryFromValue(value);
323
+ if (revived !== void 0) return revived;
324
+ if (strict) {
325
+ throw new Error(`Unknown enum value: ${JSON.stringify(value)}`);
326
+ }
327
+ return void 0;
328
+ };
329
+
330
+ // src/utilities/transport/transportRegistry.ts
331
+ var createLevelFilteredLogger = (logger, level) => {
332
+ const levels = {
333
+ debug: 0,
334
+ info: 1,
335
+ warn: 2,
336
+ error: 3
337
+ };
338
+ const currentLevel = levels[level];
339
+ return {
340
+ debug: (message, ...args) => {
341
+ if (currentLevel <= levels.debug) {
342
+ logger.debug(message, ...args);
343
+ }
344
+ },
345
+ info: (message, ...args) => {
346
+ if (currentLevel <= levels.info) {
347
+ logger.info(message, ...args);
348
+ }
349
+ },
350
+ warn: (message, ...args) => {
351
+ if (currentLevel <= levels.warn) {
352
+ logger.warn(message, ...args);
353
+ }
354
+ },
355
+ error: (message, ...args) => {
356
+ if (currentLevel <= levels.error) {
357
+ logger.error(message, ...args);
358
+ }
359
+ }
360
+ };
361
+ };
362
+ var globalEnumRegistry;
363
+ var initializeSmartEnumMappings = (config) => {
364
+ globalEnumRegistry = config.enumRegistry;
365
+ const logLevel = config.logLevel ?? "error";
366
+ const logger = config.logger ?? getLogger();
367
+ setLogger(createLevelFilteredLogger(logger, logLevel));
368
+ info("Initialized smart enum mappings", {
369
+ enumCount: Object.keys(config.enumRegistry).length,
370
+ enumTypes: Object.keys(config.enumRegistry),
371
+ logLevel
372
+ });
373
+ };
374
+ var getGlobalEnumRegistry = () => globalEnumRegistry;
375
+
376
+ // src/utilities/transport/reviveAfterTransport.ts
377
+ var reviveAfterTransport = (payload) => {
378
+ const registry = getGlobalEnumRegistry();
379
+ if (!registry) {
380
+ return payload;
381
+ }
382
+ return reviveSmartEnums(payload, registry);
383
+ };
384
+
385
+ // src/utilities/transport/serializeForTransport.ts
386
+ var serializeForTransport = (payload) => serializeSmartEnums(payload);
387
+
388
+ // src/db/prepareForDatabase.ts
389
+ var isPlainObject2 = (x) => typeof x === "object" && x !== null && Object.getPrototypeOf(x) === Object.prototype;
390
+ var prepareForDatabase = (payload) => {
391
+ const seen = /* @__PURE__ */ new WeakMap();
392
+ const walk = (v) => {
393
+ if (isSmartEnumItem(v)) {
394
+ return v.value;
395
+ }
396
+ if (typeof v === "object" && v !== null && seen.has(v)) {
397
+ return seen.get(v);
398
+ }
399
+ if (Array.isArray(v)) {
400
+ const arr = [];
401
+ seen.set(v, arr);
402
+ for (const item of v) {
403
+ arr.push(walk(item));
404
+ }
405
+ return arr;
406
+ }
407
+ if (isPlainObject2(v)) {
408
+ const out = {};
409
+ seen.set(v, out);
410
+ for (const [k, val] of Object.entries(v)) {
411
+ out[k] = walk(val);
412
+ }
413
+ return out;
414
+ }
415
+ return v;
416
+ };
417
+ return walk(payload);
418
+ };
419
+
420
+ // src/db/enumRevivalError.ts
421
+ var EnumRevivalError = class extends Error {
422
+ constructor(message, path, value) {
423
+ super(message);
424
+ this.path = path;
425
+ this.value = value;
426
+ this.name = "EnumRevivalError";
427
+ }
428
+ };
429
+
430
+ // src/db/reviveRowFromDatabase.ts
431
+ var reviveRowFromDatabase = (row, options) => {
432
+ const { fieldEnumMapping, strict = false } = options;
433
+ const out = { ...row };
434
+ for (const [field, smartEnum] of Object.entries(fieldEnumMapping)) {
435
+ if (!Object.hasOwn(out, field)) {
436
+ continue;
437
+ }
438
+ const raw = out[field];
439
+ if (typeof raw === "string") {
440
+ const revived = smartEnum.tryFromValue(raw);
441
+ if (revived !== void 0) {
442
+ out[field] = revived;
443
+ } else if (strict) {
444
+ throw new EnumRevivalError(
445
+ `Cannot revive field ${JSON.stringify(field)}: unknown enum value ${JSON.stringify(raw)}`,
446
+ field,
447
+ raw
448
+ );
449
+ }
450
+ continue;
451
+ }
452
+ if (Array.isArray(raw)) {
453
+ const revivedItems = [];
454
+ for (const item of raw) {
455
+ if (typeof item !== "string") {
456
+ revivedItems.push(item);
457
+ continue;
458
+ }
459
+ const revived = smartEnum.tryFromValue(item);
460
+ if (revived !== void 0) {
461
+ revivedItems.push(revived);
462
+ } else if (strict) {
463
+ throw new EnumRevivalError(
464
+ `Cannot revive field ${JSON.stringify(field)}: unknown enum value ${JSON.stringify(item)} in array`,
465
+ field,
466
+ item
467
+ );
468
+ } else {
469
+ revivedItems.push(item);
470
+ }
471
+ }
472
+ out[field] = revivedItems;
473
+ continue;
474
+ }
475
+ }
476
+ return out;
477
+ };
478
+
479
+ // src/db/revivePayloadFromDatabase.ts
480
+ var isObjectRecord = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
481
+ var parsePath = (pathStr) => {
482
+ const tokens = pathStr.split(".").filter(Boolean);
483
+ const segs = [];
484
+ for (const token of tokens) {
485
+ if (token.endsWith("[]")) {
486
+ const name = token.slice(0, -2);
487
+ if (name.length === 0) {
488
+ throw new Error(
489
+ `Invalid enum revival path "${pathStr}": empty key before []`
490
+ );
491
+ }
492
+ segs.push({ type: "prop", name }, { type: "arrayEach" });
493
+ } else {
494
+ segs.push({ type: "prop", name: token });
495
+ }
496
+ }
497
+ const last = segs.at(-1);
498
+ if (!last || last.type !== "prop") {
499
+ throw new Error(
500
+ `Invalid enum revival path "${pathStr}": must end with a property name (not [])`
501
+ );
502
+ }
503
+ return segs;
504
+ };
505
+ var reviveLeaf = (host, key, smartEnum, strict, pathLabel) => {
506
+ const raw = host[key];
507
+ if (typeof raw === "string") {
508
+ const revived = smartEnum.tryFromValue(raw);
509
+ if (revived !== void 0) {
510
+ host[key] = revived;
511
+ } else if (strict) {
512
+ throw new EnumRevivalError(
513
+ `Cannot revive path "${pathLabel}": unknown enum value ${JSON.stringify(raw)}`,
514
+ pathLabel,
515
+ raw
516
+ );
517
+ }
518
+ return;
519
+ }
520
+ if (Array.isArray(raw)) {
521
+ const revivedItems = [];
522
+ for (const item of raw) {
523
+ if (typeof item !== "string") {
524
+ revivedItems.push(item);
525
+ continue;
526
+ }
527
+ const revived = smartEnum.tryFromValue(item);
528
+ if (revived !== void 0) {
529
+ revivedItems.push(revived);
530
+ } else if (strict) {
531
+ throw new EnumRevivalError(
532
+ `Cannot revive path "${pathLabel}": unknown enum value ${JSON.stringify(item)} in array`,
533
+ pathLabel,
534
+ item
535
+ );
536
+ } else {
537
+ revivedItems.push(item);
538
+ }
539
+ }
540
+ host[key] = revivedItems;
541
+ return;
542
+ }
543
+ };
544
+ var walkPath = (value, segs, segIndex, smartEnum, strict, pathLabel) => {
545
+ const seg = segs[segIndex];
546
+ if (seg === void 0) {
547
+ return;
548
+ }
549
+ const isLast = segIndex === segs.length - 1;
550
+ if (isLast) {
551
+ if (seg.type !== "prop") {
552
+ return;
553
+ }
554
+ if (!isObjectRecord(value)) {
555
+ if (strict) {
556
+ throw new EnumRevivalError(
557
+ `Cannot revive path "${pathLabel}": expected object at parent of "${seg.name}"`,
558
+ pathLabel,
559
+ value
560
+ );
561
+ }
562
+ return;
563
+ }
564
+ reviveLeaf(value, seg.name, smartEnum, strict, pathLabel);
565
+ return;
566
+ }
567
+ if (seg.type === "prop") {
568
+ if (!isObjectRecord(value)) {
569
+ if (strict) {
570
+ throw new EnumRevivalError(
571
+ `Cannot revive path "${pathLabel}": expected object at "${seg.name}"`,
572
+ pathLabel,
573
+ value
574
+ );
575
+ }
576
+ return;
577
+ }
578
+ walkPath(value[seg.name], segs, segIndex + 1, smartEnum, strict, pathLabel);
579
+ return;
580
+ }
581
+ if (seg.type === "arrayEach") {
582
+ if (!Array.isArray(value)) {
583
+ if (strict) {
584
+ throw new EnumRevivalError(
585
+ `Cannot revive path "${pathLabel}": expected array before nested path`,
586
+ pathLabel,
587
+ value
588
+ );
589
+ }
590
+ return;
591
+ }
592
+ for (const el of value) {
593
+ walkPath(el, segs, segIndex + 1, smartEnum, strict, pathLabel);
594
+ }
595
+ }
596
+ };
597
+ var revivePayloadFromDatabase = (payload, options) => {
598
+ const { pathEnumMapping, strict = false } = options;
599
+ const root = structuredClone(payload);
600
+ for (const [pathStr, smartEnum] of Object.entries(pathEnumMapping)) {
601
+ const segs = parsePath(pathStr);
602
+ walkPath(root, segs, 0, smartEnum, strict, pathStr);
603
+ }
604
+ return root;
605
+ };
27
606
  export {
28
607
  EnumRevivalError,
29
608
  enumeration,