@reharik/smart-enum 0.4.0 → 0.4.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.
- package/dist/{chunk-6PARBLRO.js → chunk-GXKGPKPN.js} +67 -21
- package/dist/chunk-GXKGPKPN.js.map +1 -0
- package/dist/database.cjs +66 -20
- package/dist/database.cjs.map +1 -1
- package/dist/database.js +1 -1
- package/dist/index.cjs +66 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/README.md +0 -380
- package/dist/chunk-6PARBLRO.js.map +0 -1
package/README.md
DELETED
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
# @reharik/smart-enum
|
|
2
|
-
|
|
3
|
-
Type-safe, feature-rich enumerations for TypeScript. Every enum member is a frozen object with a key, a wire value, a display string, an index, and whatever custom fields you need — plus lookup, iteration, serialization, and database revival built in.
|
|
4
|
-
|
|
5
|
-
## Why not just use TypeScript enums?
|
|
6
|
-
|
|
7
|
-
TypeScript's built-in `enum` gives you a name-to-value mapping and nothing else. You can't iterate members, attach metadata, look up by value at runtime, or serialize/revive across a network boundary without writing boilerplate every time.
|
|
8
|
-
|
|
9
|
-
The common workarounds each solve one piece:
|
|
10
|
-
|
|
11
|
-
- **Plain objects** (`{ ACTIVE: 'active' }`) — no iteration, no type narrowing, no metadata, annoying text duplication.
|
|
12
|
-
- **String unions** (`type Status = 'active' | 'inactive'`) — compile-time only. No runtime lookup, no `.display`, no `.items()`.
|
|
13
|
-
- **Arrays** (`['active', 'inactive'] as const`) — iterable, but no keyed access or metadata.
|
|
14
|
-
- **Constants** (const ME = "me") — scattered and hard to iterate
|
|
15
|
-
|
|
16
|
-
Smart Enums give you all of it in one construct:
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
import { enumeration, type Enumeration } from '@reharik/smart-enum';
|
|
20
|
-
|
|
21
|
-
const Status = enumeration('Status', {
|
|
22
|
-
input: ['pending', 'active', 'completed'] as const,
|
|
23
|
-
});
|
|
24
|
-
type Status = Enumeration<typeof Status>;
|
|
25
|
-
|
|
26
|
-
Status.active;
|
|
27
|
-
// { key: 'active', value: 'ACTIVE', display: 'Active', index: 1 }
|
|
28
|
-
|
|
29
|
-
Status.fromValue('ACTIVE'); // Status.active
|
|
30
|
-
Status.tryFromValue('NOPE'); // undefined
|
|
31
|
-
Status.items(); // all members as a frozen array
|
|
32
|
-
Status.values(); // ['PENDING', 'ACTIVE', 'COMPLETED']
|
|
33
|
-
Status.keys(); // ['pending', 'active', 'completed']
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Type safety works the way you'd expect — a function that takes `Status` won't accept a member from a different enum, even if the shapes look similar.
|
|
37
|
-
|
|
38
|
-
## Install
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npm install @reharik/smart-enum
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Creating enums
|
|
45
|
-
|
|
46
|
-
### From an array
|
|
47
|
-
|
|
48
|
-
The simplest form. Keys are the array values, wire values are auto-derived as `CONSTANT_CASE`, display strings as `Title Case`.
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
const Color = enumeration('Color', {
|
|
52
|
-
input: ['red', 'blue', 'green'] as const,
|
|
53
|
-
});
|
|
54
|
-
type Color = Enumeration<typeof Color>;
|
|
55
|
-
|
|
56
|
-
Color.red.key; // 'red'
|
|
57
|
-
Color.red.value; // 'RED'
|
|
58
|
-
Color.red.display; // 'Red'
|
|
59
|
-
Color.red.index; // 0
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
> **`as const` is required** on array inputs. Without it, TypeScript widens the type to `string[]` and you lose literal inference on keys and values.
|
|
63
|
-
|
|
64
|
-
### From an object
|
|
65
|
-
|
|
66
|
-
When you need custom values, display strings, or extra fields per member:
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
const Priority = enumeration('Priority', {
|
|
70
|
-
input: {
|
|
71
|
-
low: { display: 'Low Priority' },
|
|
72
|
-
medium: { display: 'Medium Priority' },
|
|
73
|
-
high: { value: 'P1', display: 'High Priority' },
|
|
74
|
-
urgent: { value: 'P0', display: 'Urgent!!!' },
|
|
75
|
-
} as const,
|
|
76
|
-
});
|
|
77
|
-
type Priority = Enumeration<typeof Priority>;
|
|
78
|
-
|
|
79
|
-
Priority.high.value; // 'P1' (explicit)
|
|
80
|
-
Priority.low.value; // 'LOW' (auto-derived from key)
|
|
81
|
-
Priority.urgent.display; // 'Urgent!!!'
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
If you omit `value`, it's derived from the key via `constantCase`. If you omit `display`, it's derived via `capitalCase`. You can override either or both per member.
|
|
85
|
-
|
|
86
|
-
### Custom fields
|
|
87
|
-
|
|
88
|
-
Any extra properties you put on a member are preserved with full type inference:
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
const AppError = enumeration('AppError', {
|
|
92
|
-
input: {
|
|
93
|
-
notFound: { source: 'api', httpStatus: 404 },
|
|
94
|
-
unauthorized: { source: 'auth', httpStatus: 401 },
|
|
95
|
-
serverError: { source: 'api', httpStatus: 500 },
|
|
96
|
-
} as const,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
AppError.notFound.source; // 'api' (literal type)
|
|
100
|
-
AppError.notFound.httpStatus; // 404 (literal type)
|
|
101
|
-
|
|
102
|
-
// You can Extract by custom fields:
|
|
103
|
-
type ApiErrors = Extract<Enumeration<typeof AppError>, { source: 'api' }>;
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Custom auto-formatters
|
|
107
|
-
|
|
108
|
-
Override how `value`, `display`, or any auto-generated property is derived from the key:
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
const Routes = enumeration('Routes', {
|
|
112
|
-
input: ['userProfile', 'adminDashboard'] as const,
|
|
113
|
-
propertyAutoFormatters: [
|
|
114
|
-
{ key: 'value', format: k => `/${k}` },
|
|
115
|
-
{ key: 'slug', format: k => k.replace(/([A-Z])/g, '-$1').toLowerCase() },
|
|
116
|
-
],
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
Routes.userProfile.value; // '/userProfile'
|
|
120
|
-
Routes.userProfile.slug; // 'user-profile'
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## Lookup methods
|
|
124
|
-
|
|
125
|
-
Every enum object has these methods. Return types are fully narrowed to the enum's member union.
|
|
126
|
-
|
|
127
|
-
| Method | Description |
|
|
128
|
-
| --------------------- | ----------------------------------------------------- |
|
|
129
|
-
| `fromValue(value)` | Find by wire value. Throws if not found. |
|
|
130
|
-
| `tryFromValue(value)` | Find by wire value. Returns `undefined` if not found. |
|
|
131
|
-
| `fromKey(key)` | Find by key. Throws if not found. |
|
|
132
|
-
| `tryFromKey(key)` | Find by key. Returns `undefined` if not found. |
|
|
133
|
-
| `items()` | All members as a frozen array. |
|
|
134
|
-
| `values()` | All wire values as an array. |
|
|
135
|
-
| `keys()` | All keys as an array. |
|
|
136
|
-
|
|
137
|
-
## Subsetting by a custom field
|
|
138
|
-
|
|
139
|
-
Filter an enum down to members matching a property value. The result is a new enum-like object with its own `fromValue`, `items`, etc., scoped to the subset:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
import { getSubsetByProp, subsetByProp } from '@reharik/smart-enum';
|
|
143
|
-
|
|
144
|
-
const apiErrors = getSubsetByProp(AppError, 'source', 'api' as const);
|
|
145
|
-
|
|
146
|
-
apiErrors.notFound; // same object as AppError.notFound
|
|
147
|
-
apiErrors.items(); // only api-source members
|
|
148
|
-
apiErrors.fromValue('500'); // works, scoped to subset
|
|
149
|
-
// apiErrors.unauthorized → not present (source is 'auth')
|
|
150
|
-
|
|
151
|
-
// Curried form:
|
|
152
|
-
const bySource = subsetByProp('source');
|
|
153
|
-
const authErrors = bySource(AppError, 'auth' as const);
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Tree-shaking
|
|
157
|
-
|
|
158
|
-
The package has multiple entry points so you only pay for what you import:
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
// Core only — enumeration + type guards + subset helpers (~149 bytes)
|
|
162
|
-
import { enumeration } from '@reharik/smart-enum/core';
|
|
163
|
-
|
|
164
|
-
// Core + transport serialization/revival (~406 bytes)
|
|
165
|
-
import { serializeForTransport } from '@reharik/smart-enum/transport';
|
|
166
|
-
|
|
167
|
-
// Core + database serialization/revival (~379 bytes)
|
|
168
|
-
import { prepareForDatabase } from '@reharik/smart-enum/database';
|
|
169
|
-
|
|
170
|
-
// Everything (~598 bytes)
|
|
171
|
-
import {
|
|
172
|
-
enumeration,
|
|
173
|
-
serializeSmartEnums,
|
|
174
|
-
prepareForDatabase,
|
|
175
|
-
} from '@reharik/smart-enum';
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## Serialization and transport
|
|
179
|
-
|
|
180
|
-
Smart enum items serialize to self-describing `{ __smart_enum_type, value }` objects for JSON transport. On the receiving end, a registry maps them back to live enum instances.
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
import { serializeSmartEnums, reviveSmartEnums } from '@reharik/smart-enum';
|
|
184
|
-
|
|
185
|
-
const dto = { id: '1', status: Status.active, color: Color.red };
|
|
186
|
-
|
|
187
|
-
// Serialize
|
|
188
|
-
const wire = serializeSmartEnums(dto);
|
|
189
|
-
// { id: '1',
|
|
190
|
-
// status: { __smart_enum_type: 'Status', value: 'ACTIVE' },
|
|
191
|
-
// color: { __smart_enum_type: 'Color', value: 'RED' } }
|
|
192
|
-
|
|
193
|
-
// Revive
|
|
194
|
-
const revived = reviveSmartEnums(wire, { Status, Color });
|
|
195
|
-
// revived.status === Status.active ✓
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Global transport registry
|
|
199
|
-
|
|
200
|
-
For app-wide setup (e.g. Express middleware), register all enums once at startup:
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
import {
|
|
204
|
-
initializeSmartEnumMappings,
|
|
205
|
-
serializeForTransport,
|
|
206
|
-
reviveAfterTransport,
|
|
207
|
-
} from '@reharik/smart-enum/transport';
|
|
208
|
-
|
|
209
|
-
// At startup
|
|
210
|
-
initializeSmartEnumMappings({
|
|
211
|
-
enumRegistry: { Status, Priority, Color },
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// In a handler
|
|
215
|
-
const wire = serializeForTransport(responseData);
|
|
216
|
-
const revived = reviveAfterTransport(requestBody);
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## Database utilities
|
|
220
|
-
|
|
221
|
-
**Outbound:** `prepareForDatabase` recursively replaces enum items with their `.value` strings. Each item also has a `.toPostgres()` method for PostgreSQL drivers that honor it.
|
|
222
|
-
|
|
223
|
-
**Inbound:** Database columns are plain strings — they don't carry type information. Revival requires you to declare which columns map to which enums.
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
import {
|
|
227
|
-
prepareForDatabase,
|
|
228
|
-
reviveRowFromDatabase,
|
|
229
|
-
revivePayloadFromDatabase,
|
|
230
|
-
} from '@reharik/smart-enum/database';
|
|
231
|
-
|
|
232
|
-
// Write
|
|
233
|
-
const dbRow = prepareForDatabase({ name: 'Alice', status: Status.active });
|
|
234
|
-
// { name: 'Alice', status: 'ACTIVE' }
|
|
235
|
-
|
|
236
|
-
// Read — flat row
|
|
237
|
-
const revived = reviveRowFromDatabase(row, {
|
|
238
|
-
fieldEnumMapping: { status: Status, priority: Priority },
|
|
239
|
-
strict: true, // throw on unknown values instead of keeping raw string
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// Read — nested payload (e.g. JSONB column)
|
|
243
|
-
const doc = revivePayloadFromDatabase(payload, {
|
|
244
|
-
pathEnumMapping: {
|
|
245
|
-
'user.status': Status,
|
|
246
|
-
'items[].kind': ItemKind,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
`strict: true` throws `EnumRevivalError` when a mapped string doesn't match any member. Without it, unrecognized values are left as-is.
|
|
252
|
-
|
|
253
|
-
## Type guards
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
import { isSmartEnumItem, isSmartEnum } from '@reharik/smart-enum';
|
|
257
|
-
|
|
258
|
-
isSmartEnumItem(Status.active); // true
|
|
259
|
-
isSmartEnumItem({ key: 'x' }); // false (plain object)
|
|
260
|
-
|
|
261
|
-
isSmartEnum(Status); // true (the enum object)
|
|
262
|
-
isSmartEnum(Status.active); // false (a member, not the enum)
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
## GraphQL integration
|
|
266
|
-
|
|
267
|
-
### Server-side: resolvers returning smart-enum instances
|
|
268
|
-
|
|
269
|
-
Out of the box, if a GraphQL resolver returns a smart-enum item, the default `serialize` on `GraphQLEnumType` doesn't know how to extract the string value — it just passes the object through, which breaks the response.
|
|
270
|
-
|
|
271
|
-
`patchSchemaEnumSerializers` walks your executable schema at startup and patches every `GraphQLEnumType.serialize` to call `.value` on smart-enum instances. After that, resolvers can return smart-enum items directly:
|
|
272
|
-
|
|
273
|
-
```typescript
|
|
274
|
-
import { patchSchemaEnumSerializers } from '@reharik/smart-enum';
|
|
275
|
-
import { makeExecutableSchema } from '@graphql-tools/schema';
|
|
276
|
-
|
|
277
|
-
const schema = makeExecutableSchema({ typeDefs, resolvers });
|
|
278
|
-
|
|
279
|
-
// Call once at startup, after schema construction
|
|
280
|
-
patchSchemaEnumSerializers(schema);
|
|
281
|
-
|
|
282
|
-
// Now resolvers can return smart-enum items directly:
|
|
283
|
-
const resolvers = {
|
|
284
|
-
Query: {
|
|
285
|
-
order: () => ({
|
|
286
|
-
id: '1',
|
|
287
|
-
status: PaymentStatus.paid, // no .value needed
|
|
288
|
-
type: OrderType.online,
|
|
289
|
-
}),
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
The patched `serialize` does `val?.value ?? val` — smart-enum items return their `.value` string, and raw strings pass through unchanged. This means you can adopt it incrementally without breaking existing resolvers that already return strings.
|
|
295
|
-
|
|
296
|
-
### Client-side: Apollo cache rehydration
|
|
297
|
-
|
|
298
|
-
The [`@reharik/graphql-codegen-smart-enum`](https://www.npmjs.com/package/@reharik/graphql-codegen-smart-enum) package includes a second codegen plugin that generates Apollo `typePolicies` with `read` functions for every enum field. Spread the output into your `InMemoryCache` and components receive smart-enum instances from queries instead of raw strings:
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
import { InMemoryCache } from '@apollo/client';
|
|
302
|
-
import { smartEnumTypePolicies } from './generated/graphql-smart-enum-type-policies';
|
|
303
|
-
|
|
304
|
-
const cache = new InMemoryCache({
|
|
305
|
-
typePolicies: {
|
|
306
|
-
...smartEnumTypePolicies,
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// In a component:
|
|
311
|
-
const { data } = useQuery(GET_ORDER);
|
|
312
|
-
data.order.status.display; // 'Paid' — not just 'PAID'
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
See the [`type-policies` plugin README](https://www.npmjs.com/package/@reharik/graphql-codegen-smart-enum) for setup details.
|
|
316
|
-
|
|
317
|
-
## React
|
|
318
|
-
|
|
319
|
-
```tsx
|
|
320
|
-
function StatusPicker() {
|
|
321
|
-
const [selected, setSelected] = useState(Status.pending);
|
|
322
|
-
|
|
323
|
-
return (
|
|
324
|
-
<select
|
|
325
|
-
value={selected.value}
|
|
326
|
-
onChange={e => setSelected(Status.fromValue(e.target.value))}
|
|
327
|
-
>
|
|
328
|
-
{Status.items().map(item => (
|
|
329
|
-
<option key={item.value} value={item.value}>
|
|
330
|
-
{item.display}
|
|
331
|
-
</option>
|
|
332
|
-
))}
|
|
333
|
-
</select>
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
## The `enumType` string
|
|
339
|
-
|
|
340
|
-
The first argument to `enumeration()` is a string name used for serialization identity (`__smart_enum_type`) and `toJSON()`. It must be unique across your app and consistent between serialization and revival.
|
|
341
|
-
|
|
342
|
-
For hand-authored enums, keep it matching the variable name by convention:
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
const Status = enumeration('Status', { input: ... });
|
|
346
|
-
// ^^^^^^ ^^^^^^ ← keep these in sync
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
If you use the [`@reharik/graphql-codegen-smart-enum`](https://www.npmjs.com/package/@reharik/graphql-codegen-smart-enum) codegen plugin, this is handled automatically — the GraphQL enum type name becomes the `enumType` string.
|
|
350
|
-
|
|
351
|
-
## Logging
|
|
352
|
-
|
|
353
|
-
Transport initialization supports pluggable logging:
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
initializeSmartEnumMappings({
|
|
357
|
-
enumRegistry: { Status, Priority },
|
|
358
|
-
logLevel: 'debug', // 'debug' | 'info' | 'warn' | 'error'
|
|
359
|
-
logger: {
|
|
360
|
-
debug: (msg, ...args) => myLogger.debug(msg, ...args),
|
|
361
|
-
info: (msg, ...args) => myLogger.info(msg, ...args),
|
|
362
|
-
warn: (msg, ...args) => myLogger.warn(msg, ...args),
|
|
363
|
-
error: (msg, ...args) => myLogger.error(msg, ...args),
|
|
364
|
-
},
|
|
365
|
-
});
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
Default is `console` at `'error'` level.
|
|
369
|
-
|
|
370
|
-
## Related packages
|
|
371
|
-
|
|
372
|
-
| Package | Purpose |
|
|
373
|
-
| ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
|
|
374
|
-
| [`@reharik/smart-enum-knex`](https://www.npmjs.com/package/@reharik/smart-enum-knex) | Knex query-level enum revival via `postProcessResponse` |
|
|
375
|
-
| [`@reharik/graphql-codegen-smart-enum`](https://www.npmjs.com/package/@reharik/graphql-codegen-smart-enum) | Generate smart-enum definitions from GraphQL schema enums |
|
|
376
|
-
| [`@reharik/graphql-codegen-smart-enum/type-policies`](https://www.npmjs.com/package/@reharik/graphql-codegen-smart-enum) | Generate Apollo `typePolicies` for client-side enum rehydration |
|
|
377
|
-
|
|
378
|
-
## License
|
|
379
|
-
|
|
380
|
-
MIT
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/db/prepareForDatabase.ts","../src/db/enumRevivalError.ts","../src/db/reviveRowFromDatabase.ts","../src/db/revivePayloadFromDatabase.ts"],"sourcesContent":["import { isSmartEnumItem } from '../utilities/typeGuards.js';\nimport type { DatabaseFormat } from '../types.js';\n\ntype PlainObject = Record<string, unknown>;\n\nconst isPlainObject = (x: unknown): x is PlainObject =>\n typeof x === 'object' &&\n x !== null &&\n Object.getPrototypeOf(x) === Object.prototype;\n\n/**\n * Recursively replaces smart enum items with their `.value` strings for persistence.\n * Inbound revival requires explicit mapping (`reviveRowFromDatabase`, etc.).\n */\nexport const prepareForDatabase = <T>(payload: T): DatabaseFormat<T> => {\n const seen = new WeakMap<object, unknown>();\n\n const walk = (v: unknown): unknown => {\n if (isSmartEnumItem(v)) {\n return v.value;\n }\n\n if (typeof v === 'object' && v !== null && seen.has(v)) {\n return seen.get(v);\n }\n\n if (Array.isArray(v)) {\n const arr: unknown[] = [];\n seen.set(v, arr);\n for (const item of v) {\n arr.push(walk(item));\n }\n return arr;\n }\n if (isPlainObject(v)) {\n const out: PlainObject = {};\n seen.set(v, out);\n for (const [k, val] of Object.entries(v)) {\n out[k] = walk(val);\n }\n return out;\n }\n return v;\n };\n\n return walk(payload) as DatabaseFormat<T>;\n};\n","export class EnumRevivalError extends Error {\n public constructor(\n message: string,\n public readonly path: string,\n public readonly value: unknown,\n ) {\n super(message);\n this.name = 'EnumRevivalError';\n }\n}\n","import type { ReviveRowOptions } from '../types.js';\n\nimport { EnumRevivalError } from './enumRevivalError.js';\n\nexport const reviveRowFromDatabase = <T extends Record<string, unknown>>(\n row: T,\n options: ReviveRowOptions,\n): T => {\n const { fieldEnumMapping, strict = false } = options;\n const out = { ...row } as Record<string, unknown>;\n\n for (const [field, smartEnum] of Object.entries(fieldEnumMapping)) {\n if (!Object.hasOwn(out, field)) {\n continue;\n }\n const raw = out[field];\n if (typeof raw !== 'string') {\n continue;\n }\n const revived = smartEnum.tryFromValue(raw);\n if (revived !== undefined) {\n out[field] = revived as unknown;\n } else if (strict) {\n throw new EnumRevivalError(\n `Cannot revive field ${JSON.stringify(field)}: unknown enum value ${JSON.stringify(raw)}`,\n field,\n raw,\n );\n }\n }\n\n return out as T;\n};\n","import type { RevivePayloadOptions, SmartEnumLike } from '../types.js';\n\nimport { EnumRevivalError } from './enumRevivalError.js';\n\ntype PlainObject = Record<string, unknown>;\n\n/** Plain data objects (including `structuredClone` output where prototype may not be Object.prototype). */\nconst isObjectRecord = (x: unknown): x is PlainObject =>\n typeof x === 'object' && x !== null && !Array.isArray(x);\n\ntype PathSeg = { type: 'prop'; name: string } | { type: 'arrayEach' };\n\nconst parsePath = (pathStr: string): PathSeg[] => {\n const tokens = pathStr.split('.').filter(Boolean);\n const segs: PathSeg[] = [];\n\n for (const token of tokens) {\n if (token.endsWith('[]')) {\n const name = token.slice(0, -2);\n if (name.length === 0) {\n throw new Error(\n `Invalid enum revival path \"${pathStr}\": empty key before []`,\n );\n }\n segs.push({ type: 'prop', name }, { type: 'arrayEach' });\n } else {\n segs.push({ type: 'prop', name: token });\n }\n }\n\n const last = segs.at(-1);\n if (!last || last.type !== 'prop') {\n throw new Error(\n `Invalid enum revival path \"${pathStr}\": must end with a property name (not [])`,\n );\n }\n\n return segs;\n};\n\nconst reviveLeaf = (\n host: PlainObject,\n key: string,\n smartEnum: SmartEnumLike,\n strict: boolean,\n pathLabel: string,\n): void => {\n const raw = host[key];\n if (typeof raw !== 'string') {\n return;\n }\n const revived = smartEnum.tryFromValue(raw);\n if (revived !== undefined) {\n host[key] = revived as unknown;\n } else if (strict) {\n throw new EnumRevivalError(\n `Cannot revive path \"${pathLabel}\": unknown enum value ${JSON.stringify(raw)}`,\n pathLabel,\n raw,\n );\n }\n};\n\nconst walkPath = (\n value: unknown,\n segs: PathSeg[],\n segIndex: number,\n smartEnum: SmartEnumLike,\n strict: boolean,\n pathLabel: string,\n): void => {\n const seg = segs[segIndex];\n if (seg === undefined) {\n return;\n }\n\n const isLast = segIndex === segs.length - 1;\n\n if (isLast) {\n if (seg.type !== 'prop') {\n return;\n }\n if (!isObjectRecord(value)) {\n if (strict) {\n throw new EnumRevivalError(\n `Cannot revive path \"${pathLabel}\": expected object at parent of \"${seg.name}\"`,\n pathLabel,\n value,\n );\n }\n return;\n }\n reviveLeaf(value, seg.name, smartEnum, strict, pathLabel);\n return;\n }\n\n if (seg.type === 'prop') {\n if (!isObjectRecord(value)) {\n if (strict) {\n throw new EnumRevivalError(\n `Cannot revive path \"${pathLabel}\": expected object at \"${seg.name}\"`,\n pathLabel,\n value,\n );\n }\n return;\n }\n walkPath(value[seg.name], segs, segIndex + 1, smartEnum, strict, pathLabel);\n return;\n }\n\n if (seg.type === 'arrayEach') {\n if (!Array.isArray(value)) {\n if (strict) {\n throw new EnumRevivalError(\n `Cannot revive path \"${pathLabel}\": expected array before nested path`,\n pathLabel,\n value,\n );\n }\n return;\n }\n for (const el of value) {\n walkPath(el, segs, segIndex + 1, smartEnum, strict, pathLabel);\n }\n }\n};\n\nexport const revivePayloadFromDatabase = <T>(\n payload: T,\n options: RevivePayloadOptions,\n): T => {\n const { pathEnumMapping, strict = false } = options;\n const root = structuredClone(payload);\n\n for (const [pathStr, smartEnum] of Object.entries(pathEnumMapping)) {\n const segs = parsePath(pathStr);\n walkPath(root as unknown, segs, 0, smartEnum, strict, pathStr);\n }\n\n return root;\n};\n"],"mappings":"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","names":[]}
|