dyna-record 0.6.3 → 0.6.5

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 CHANGED
@@ -212,7 +212,7 @@ class Store extends MyTable {
212
212
  }
213
213
  ```
214
214
 
215
- - **Supported field types:** `"string"`, `"number"`, `"boolean"`, `"date"` (stored as ISO strings, exposed as `Date` objects), `"enum"` (via `values`), nested `"object"` (via `fields`), and `"array"` (via `items`)
215
+ - **Supported field types:** `"string"`, `"number"`, `"boolean"`, `"date"` (stored as ISO strings, exposed as `Date` objects), `"enum"` (via `values`), nested `"object"` (via `fields`), `"array"` (via `items`), and `"discriminatedUnion"` (via `discriminator` + `variants`)
216
216
  - **Nullable fields:** Set `nullable: true` on individual non-object fields within the schema to make them optional
217
217
  - **Object attributes are never nullable:** DynamoDB cannot update nested document paths (e.g., `address.geo.lat`) if the parent object does not exist. To prevent this, `@ObjectAttribute` fields always exist as at least an empty object `{}`. Nested object fields within the schema are also never nullable. Non-object fields (primitives, enums, dates, arrays) can still be nullable.
218
218
  - **Alias support:** Use the `alias` option to map to a different DynamoDB attribute name
@@ -249,6 +249,82 @@ const schema = {
249
249
 
250
250
  The schema must be declared with `as const satisfies ObjectSchema` so TypeScript preserves the literal string values for type inference. At runtime, providing an invalid value (e.g., `status: "unknown"`) throws a `ValidationError`.
251
251
 
252
+ ##### Discriminated union fields
253
+
254
+ Use `{ type: "discriminatedUnion", discriminator: "...", variants: { ... } }` to define a field that is a tagged union of object types. Each variant is an `ObjectSchema` keyed by its discriminator value. The discriminator key is automatically included in each variant's inferred type as a string literal.
255
+
256
+ ```typescript
257
+ const drawingSchema = {
258
+ shape: {
259
+ type: "discriminatedUnion",
260
+ discriminator: "kind",
261
+ variants: {
262
+ circle: { radius: { type: "number" } },
263
+ square: { side: { type: "number" } }
264
+ }
265
+ }
266
+ } as const satisfies ObjectSchema;
267
+
268
+ @Entity
269
+ class Drawing extends MyTable {
270
+ declare readonly type: "Drawing";
271
+
272
+ @ObjectAttribute({ alias: "Drawing", schema: drawingSchema })
273
+ public readonly drawing: InferObjectSchema<typeof drawingSchema>;
274
+ }
275
+
276
+ // TypeScript infers:
277
+ // drawing.shape → { kind: "circle"; radius: number } | { kind: "square"; side: number }
278
+ ```
279
+
280
+ Unlike nested `"object"` fields, discriminated union fields **can be nullable** (`nullable: true`) because they always use **full replacement** on update rather than document path expressions:
281
+
282
+ ```typescript
283
+ // Replaces the entire shape field — no partial merge
284
+ await Drawing.update("123", {
285
+ drawing: { shape: { kind: "square", side: 10 } }
286
+ });
287
+ ```
288
+
289
+ ##### Arrays of discriminated unions
290
+
291
+ Discriminated unions can be used as array items. Each element in the array is validated and serialized using variant-aware logic:
292
+
293
+ ```typescript
294
+ const dashboardSchema = {
295
+ widgets: {
296
+ type: "array",
297
+ items: {
298
+ type: "discriminatedUnion",
299
+ discriminator: "type",
300
+ variants: {
301
+ "metric-card": {
302
+ label: { type: "string" },
303
+ value: { type: "number" }
304
+ },
305
+ chart: {
306
+ title: { type: "string" },
307
+ chartType: { type: "enum", values: ["bar", "line", "pie"] }
308
+ }
309
+ }
310
+ }
311
+ }
312
+ } as const satisfies ObjectSchema;
313
+
314
+ // TypeScript infers:
315
+ // dashboard.widgets → Array<
316
+ // | { type: "metric-card"; label: string; value: number }
317
+ // | { type: "chart"; title: string; chartType: "bar" | "line" | "pie" }
318
+ // >
319
+ ```
320
+
321
+ Arrays of discriminated unions use **full replacement** on update (the entire array is replaced), consistent with all array fields.
322
+
323
+ **Scoping constraints:**
324
+
325
+ - Supported at the ObjectAttribute root level, as fields within an ObjectSchema, and as array items
326
+ - Not supported nested inside other discriminated unions
327
+
252
328
  ### Foreign Keys
253
329
 
254
330
  Define foreign keys in order to support [@BelongsTo](https://docs.dyna-record.com/functions/BelongsTo.html) relationships. A foreign key is required for [@HasOne](https://docs.dyna-record.com/functions/HasOne.html) and [@HasMany](https://docs.dyna-record.com/functions/HasMany.html) relationships.
@@ -990,6 +1066,15 @@ await Store.update("123", {
990
1066
  });
991
1067
  ```
992
1068
 
1069
+ **Discriminated unions** within objects are also **full replacement** (not merged):
1070
+
1071
+ ```typescript
1072
+ // Replaces the entire shape — switches from circle to square
1073
+ await Drawing.update("123", {
1074
+ drawing: { shape: { kind: "square", side: 10 } }
1075
+ });
1076
+ ```
1077
+
993
1078
  The instance `update` method returns a deep-merged result, preserving existing fields:
994
1079
 
995
1080
  ```typescript
@@ -10,7 +10,7 @@ import type { ObjectSchema, InferObjectSchema } from "./types";
10
10
  * `ValidationException: The document path provided in the update expression is invalid for update`.
11
11
  * To avoid this, `@ObjectAttribute` fields always exist as at least an empty object `{}`.
12
12
  *
13
- * The schema supports all {@link FieldDef} types: primitives, enums, nested objects, and arrays.
13
+ * The schema supports all {@link FieldDef} types: primitives, enums, nested objects, arrays, and discriminated unions.
14
14
  * Non-object fields within the schema may still be nullable.
15
15
  *
16
16
  * @template S The specific ObjectSchema type used for type inference
@@ -47,6 +47,7 @@ export interface ObjectAttributeOptions<S extends ObjectSchema> extends NonNullA
47
47
  * - `"date"` — dates stored as ISO strings (support `nullable: true`)
48
48
  * - `"object"` — nested objects, arbitrarily deep (**never nullable**)
49
49
  * - `"array"` — lists of any field type (support `nullable: true`, full replacement on update)
50
+ * - `"discriminatedUnion"` — tagged unions via `discriminator` + `variants` (support `nullable: true`, full replacement on update)
50
51
  *
51
52
  * Objects within arrays are not subject to the document path limitation because arrays
52
53
  * use full replacement on update. Partial updates of individual objects within arrays
@@ -102,6 +103,10 @@ export interface ObjectAttributeOptions<S extends ObjectSchema> extends NonNullA
102
103
  * await MyEntity.update("id", { address: { zip: null } });
103
104
  * ```
104
105
  *
106
+ * **Discriminated union fields** always use **full replacement** on update — the user
107
+ * must provide a complete variant object. See {@link DiscriminatedUnionFieldDef} for
108
+ * the rationale.
109
+ *
105
110
  * Object attributes support filtering in queries using dot-path notation for nested fields
106
111
  * and the {@link ContainsFilter | $contains} operator for List membership checks.
107
112
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ObjectAttribute.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/ObjectAttribute.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EACV,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAY,MAAM,SAAS,CAAC;AAGzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,YAAY,CAC5D,SAAQ,uBAAuB;IAC/B;;;;OAIG;IACH,MAAM,EAAE,CAAC,CAAC;CACX;AA6GD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,iBAAS,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,CAAC,CAAC,SAAS,YAAY,EACzE,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC,YAGtB,SAAS,WACR,yBAAyB,CAChC,CAAC,EACD,iBAAiB,CAAC,CAAC,CAAC,EACpB,sBAAsB,CAAC,CAAC,CAAC,CAC1B,UAmBJ;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"ObjectAttribute.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/ObjectAttribute.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAE/C,OAAO,KAAK,EACV,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EAGlB,MAAM,SAAS,CAAC;AAGjB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,YAAY,CAC5D,SAAQ,uBAAuB;IAC/B;;;;OAIG;IACH,MAAM,EAAE,CAAC,CAAC;CACX;AAkKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AACH,iBAAS,eAAe,CAAC,CAAC,SAAS,UAAU,EAAE,KAAK,CAAC,CAAC,SAAS,YAAY,EACzE,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC,YAGtB,SAAS,WACR,yBAAyB,CAChC,CAAC,EACD,iBAAiB,CAAC,CAAC,CAAC,EACpB,sBAAsB,CAAC,CAAC,CAAC,CAC1B,UAoBJ;AAED,eAAe,eAAe,CAAC"}
@@ -6,6 +6,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const zod_1 = require("zod");
7
7
  const metadata_1 = __importDefault(require("../../metadata"));
8
8
  const serializers_1 = require("./serializers");
9
+ /**
10
+ * Builds a Zod shape record from an {@link ObjectSchema} using the provided
11
+ * field converter function. Shared by both full and partial schema builders.
12
+ */
13
+ function buildZodShape(schema, fieldConverter) {
14
+ const shape = {};
15
+ for (const [key, fieldDef] of Object.entries(schema)) {
16
+ shape[key] = fieldConverter(fieldDef);
17
+ }
18
+ return shape;
19
+ }
9
20
  /**
10
21
  * Converts an {@link ObjectSchema} to a partial Zod schema for update validation.
11
22
  *
@@ -17,23 +28,26 @@ const serializers_1 = require("./serializers");
17
28
  * @returns A ZodType that validates partial objects matching the schema
18
29
  */
19
30
  function objectSchemaToZodPartial(schema) {
20
- const shape = {};
21
- for (const [key, fieldDef] of Object.entries(schema)) {
22
- shape[key] = fieldDefToZodPartial(fieldDef);
23
- }
24
- return zod_1.z.object(shape).partial();
31
+ return zod_1.z.object(buildZodShape(schema, fieldDefToZodPartial)).partial();
25
32
  }
26
33
  /**
27
34
  * Converts a single {@link FieldDef} to the corresponding partial Zod type.
28
35
  * Nested objects use partial schemas; all other types use the standard schema.
29
36
  * Object fields are never nullable — they always exist as at least `{}`.
37
+ * Discriminated union fields use the full schema (not partial) since they
38
+ * always use full replacement on update.
30
39
  */
31
40
  function fieldDefToZodPartial(fieldDef) {
32
- if (fieldDef.type === "object") {
33
- return objectSchemaToZodPartial(fieldDef.fields);
41
+ switch (fieldDef.type) {
42
+ case "object":
43
+ return objectSchemaToZodPartial(fieldDef.fields);
44
+ case "discriminatedUnion":
45
+ // Discriminated unions use full replacement — same schema as create
46
+ return discriminatedUnionToZod(fieldDef);
47
+ default:
48
+ // For non-object fields, use the standard schema (includes nullable wrapping)
49
+ return fieldDefToZod(fieldDef);
34
50
  }
35
- // For non-object fields, use the standard schema (includes nullable wrapping)
36
- return fieldDefToZod(fieldDef);
37
51
  }
38
52
  /**
39
53
  * Converts an {@link ObjectSchema} to a Zod schema for runtime validation.
@@ -42,11 +56,34 @@ function fieldDefToZodPartial(fieldDef) {
42
56
  * @returns A ZodType that validates objects matching the schema
43
57
  */
44
58
  function objectSchemaToZod(schema) {
45
- const shape = {};
46
- for (const [key, fieldDef] of Object.entries(schema)) {
47
- shape[key] = fieldDefToZod(fieldDef);
59
+ return zod_1.z.object(buildZodShape(schema, fieldDefToZod));
60
+ }
61
+ /**
62
+ * Builds a Zod `discriminatedUnion` schema from a {@link DiscriminatedUnionFieldDef}.
63
+ *
64
+ * Each variant's ObjectSchema is converted to a `z.object()` and extended with a
65
+ * `z.literal()` for the discriminator key. The resulting schemas are wrapped in
66
+ * `z.discriminatedUnion()`.
67
+ *
68
+ * @param fieldDef The discriminated union field definition
69
+ * @returns A ZodType that validates discriminated union values
70
+ */
71
+ function discriminatedUnionToZod(fieldDef) {
72
+ const variantEntries = Object.entries(fieldDef.variants);
73
+ if (variantEntries.length === 0) {
74
+ throw new Error("DiscriminatedUnionFieldDef requires at least one variant");
75
+ }
76
+ const variantSchemas = variantEntries.map(([variantKey, variantObjectSchema]) => {
77
+ const variantZod = objectSchemaToZod(variantObjectSchema);
78
+ return variantZod.extend({
79
+ [fieldDef.discriminator]: zod_1.z.literal(variantKey)
80
+ });
81
+ });
82
+ let zodType = zod_1.z.discriminatedUnion(fieldDef.discriminator, variantSchemas);
83
+ if (fieldDef.nullable === true) {
84
+ zodType = zodType.optional().nullable();
48
85
  }
49
- return zod_1.z.object(shape);
86
+ return zodType;
50
87
  }
51
88
  /**
52
89
  * Converts a single {@link FieldDef} to the corresponding Zod type for runtime validation.
@@ -54,6 +91,7 @@ function objectSchemaToZod(schema) {
54
91
  * Handles all field types:
55
92
  * - `"object"` → recursively builds a `z.object()` via {@link objectSchemaToZod}.
56
93
  * Object fields are never nullable — DynamoDB requires them to exist for document path updates.
94
+ * - `"discriminatedUnion"` → `z.discriminatedUnion()` via {@link discriminatedUnionToZod}
57
95
  * - `"array"` → `z.array()` wrapping a recursive call for the `items` type
58
96
  * - `"string"` → `z.string()`
59
97
  * - `"number"` → `z.number()`
@@ -66,9 +104,12 @@ function objectSchemaToZod(schema) {
66
104
  * @returns A ZodType that validates values matching the field definition
67
105
  */
68
106
  function fieldDefToZod(fieldDef) {
69
- // Object fields return early they are never nullable
70
- if (fieldDef.type === "object") {
71
- return objectSchemaToZod(fieldDef.fields);
107
+ // These types handle their own nullable semantics or are never nullable
108
+ switch (fieldDef.type) {
109
+ case "object":
110
+ return objectSchemaToZod(fieldDef.fields);
111
+ case "discriminatedUnion":
112
+ return discriminatedUnionToZod(fieldDef);
72
113
  }
73
114
  let zodType;
74
115
  switch (fieldDef.type) {
@@ -118,6 +159,7 @@ function fieldDefToZod(fieldDef) {
118
159
  * - `"date"` — dates stored as ISO strings (support `nullable: true`)
119
160
  * - `"object"` — nested objects, arbitrarily deep (**never nullable**)
120
161
  * - `"array"` — lists of any field type (support `nullable: true`, full replacement on update)
162
+ * - `"discriminatedUnion"` — tagged unions via `discriminator` + `variants` (support `nullable: true`, full replacement on update)
121
163
  *
122
164
  * Objects within arrays are not subject to the document path limitation because arrays
123
165
  * use full replacement on update. Partial updates of individual objects within arrays
@@ -173,6 +215,10 @@ function fieldDefToZod(fieldDef) {
173
215
  * await MyEntity.update("id", { address: { zip: null } });
174
216
  * ```
175
217
  *
218
+ * **Discriminated union fields** always use **full replacement** on update — the user
219
+ * must provide a complete variant object. See {@link DiscriminatedUnionFieldDef} for
220
+ * the rationale.
221
+ *
176
222
  * Object attributes support filtering in queries using dot-path notation for nested fields
177
223
  * and the {@link ContainsFilter | $contains} operator for List membership checks.
178
224
  *
@@ -188,11 +234,12 @@ function fieldDefToZod(fieldDef) {
188
234
  */
189
235
  function ObjectAttribute(props) {
190
236
  return function (_value, context) {
237
+ // Fail fast: surface schema validation errors at class definition time.
238
+ const { schema, ...restProps } = props;
239
+ const zodSchema = objectSchemaToZod(schema);
240
+ const partialZodSchema = objectSchemaToZodPartial(schema);
241
+ const serializers = (0, serializers_1.createObjectSerializer)(schema);
191
242
  context.addInitializer(function () {
192
- const { schema, ...restProps } = props;
193
- const zodSchema = objectSchemaToZod(schema);
194
- const partialZodSchema = objectSchemaToZodPartial(schema);
195
- const serializers = (0, serializers_1.createObjectSerializer)(schema);
196
243
  metadata_1.default.addEntityAttribute(this.constructor.name, {
197
244
  attributeName: context.name.toString(),
198
245
  type: zodSchema,
@@ -10,5 +10,5 @@ export { default as IdAttribute } from "./IdAttribute";
10
10
  export { default as ObjectAttribute } from "./ObjectAttribute";
11
11
  export type { ObjectAttributeOptions } from "./ObjectAttribute";
12
12
  export * from "./serializers";
13
- export type { ObjectSchema, InferObjectSchema, FieldDef, PrimitiveFieldDef, ObjectFieldDef, ArrayFieldDef, EnumFieldDef, DateFieldDef } from "./types";
13
+ export type { ObjectSchema, NonUnionObjectSchema, InferObjectSchema, InferDiscriminatedUnion, FieldDef, NonUnionFieldDef, PrimitiveFieldDef, ObjectFieldDef, ArrayFieldDef, EnumFieldDef, DateFieldDef, DiscriminatedUnionFieldDef } from "./types";
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,cAAc,eAAe,CAAC;AAC9B,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,YAAY,EACb,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,cAAc,eAAe,CAAC;AAC9B,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACvB,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,0BAA0B,EAC3B,MAAM,SAAS,CAAC"}
@@ -19,6 +19,8 @@ export declare const dateSerializer: {
19
19
  * - `"date"` fields are converted from `Date` objects to ISO 8601 strings.
20
20
  * - `"object"` fields recurse into their nested schema.
21
21
  * - `"array"` fields map each item through the same conversion.
22
+ * - `"discriminatedUnion"` fields look up the variant schema by discriminator value
23
+ * and recurse into that variant's object schema, preserving the discriminator key.
22
24
  * - `null` and `undefined` values are stripped from the result so that nullable
23
25
  * fields set to `null` are removed from the stored object rather than persisted
24
26
  * as `null` in DynamoDB.
@@ -29,6 +31,21 @@ export declare const dateSerializer: {
29
31
  * @returns A new object suitable for DynamoDB storage
30
32
  */
31
33
  export declare function objectToTableItem(schema: ObjectSchema, value: Record<string, unknown>): Record<string, unknown>;
34
+ /**
35
+ * Converts a single field value to its DynamoDB table representation based on
36
+ * the field definition.
37
+ *
38
+ * - `"date"` → ISO 8601 string
39
+ * - `"object"` → recursively converts via {@link objectToTableItem}
40
+ * - `"array"` → maps each item through the same conversion
41
+ * - `"discriminatedUnion"` → looks up the variant schema by discriminator value,
42
+ * converts via {@link objectToTableItem}, and preserves the discriminator key
43
+ * - All other types pass through unchanged
44
+ *
45
+ * @param fieldDef The {@link FieldDef} describing the field's type
46
+ * @param val The entity-level value to convert
47
+ * @returns The DynamoDB-compatible value
48
+ */
32
49
  export declare function convertFieldToTableItem(fieldDef: FieldDef, val: unknown): unknown;
33
50
  /**
34
51
  * Recursively walks an {@link ObjectSchema} and converts a DynamoDB table item
@@ -37,6 +54,8 @@ export declare function convertFieldToTableItem(fieldDef: FieldDef, val: unknown
37
54
  * - `"date"` fields are converted from ISO 8601 strings to `Date` objects.
38
55
  * - `"object"` fields recurse into their nested schema.
39
56
  * - `"array"` fields map each item through the same conversion.
57
+ * - `"discriminatedUnion"` fields look up the variant schema by discriminator value
58
+ * and recurse into that variant's object schema, preserving the discriminator key.
40
59
  * - `null` and `undefined` values are stripped from the result so that absent
41
60
  * fields are represented as `undefined` (omitted) on the entity, consistent
42
61
  * with root-level nullable attribute behaviour.
@@ -1 +1 @@
1
- {"version":3,"file":"serializers.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/serializers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;6BACA,oBAAoB,KAAG,OAAO;4BAM/B,OAAO;CAEhC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAaT;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAOxE"}
1
+ {"version":3,"file":"serializers.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/serializers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;6BACA,oBAAoB,KAAG,OAAO;4BAM/B,OAAO;CAEhC,CAAC;AAuBF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAwBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAmBT;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AA8BD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAOxE"}
@@ -21,6 +21,20 @@ exports.dateSerializer = {
21
21
  },
22
22
  toTableAttribute: (val) => val instanceof Date ? val.toISOString() : undefined
23
23
  };
24
+ /**
25
+ * Resolves the variant schema for a discriminated union value by reading the
26
+ * discriminator key and looking up the corresponding variant in the field definition.
27
+ *
28
+ * Uses `Object.hasOwn` to guard against prototype property pollution — only
29
+ * own-enumerable variant keys are matched.
30
+ */
31
+ function resolveVariantSchema(fieldDef, value) {
32
+ const discriminatorValue = value[fieldDef.discriminator];
33
+ if (!Object.hasOwn(fieldDef.variants, discriminatorValue)) {
34
+ return undefined;
35
+ }
36
+ return fieldDef.variants[discriminatorValue];
37
+ }
24
38
  /**
25
39
  * Recursively walks an {@link ObjectSchema} and converts the entity value to its
26
40
  * DynamoDB representation.
@@ -28,6 +42,8 @@ exports.dateSerializer = {
28
42
  * - `"date"` fields are converted from `Date` objects to ISO 8601 strings.
29
43
  * - `"object"` fields recurse into their nested schema.
30
44
  * - `"array"` fields map each item through the same conversion.
45
+ * - `"discriminatedUnion"` fields look up the variant schema by discriminator value
46
+ * and recurse into that variant's object schema, preserving the discriminator key.
31
47
  * - `null` and `undefined` values are stripped from the result so that nullable
32
48
  * fields set to `null` are removed from the stored object rather than persisted
33
49
  * as `null` in DynamoDB.
@@ -48,6 +64,33 @@ function objectToTableItem(schema, value) {
48
64
  }
49
65
  return result;
50
66
  }
67
+ /**
68
+ * Resolves and converts a discriminated union value using the provided schema
69
+ * converter function. Shared by both serialization and deserialization paths.
70
+ */
71
+ function convertDiscriminatedUnion(fieldDef, value, schemaConverter) {
72
+ const variantSchema = resolveVariantSchema(fieldDef, value);
73
+ if (variantSchema === undefined)
74
+ return value;
75
+ const result = schemaConverter(variantSchema, value);
76
+ result[fieldDef.discriminator] = value[fieldDef.discriminator];
77
+ return result;
78
+ }
79
+ /**
80
+ * Converts a single field value to its DynamoDB table representation based on
81
+ * the field definition.
82
+ *
83
+ * - `"date"` → ISO 8601 string
84
+ * - `"object"` → recursively converts via {@link objectToTableItem}
85
+ * - `"array"` → maps each item through the same conversion
86
+ * - `"discriminatedUnion"` → looks up the variant schema by discriminator value,
87
+ * converts via {@link objectToTableItem}, and preserves the discriminator key
88
+ * - All other types pass through unchanged
89
+ *
90
+ * @param fieldDef The {@link FieldDef} describing the field's type
91
+ * @param val The entity-level value to convert
92
+ * @returns The DynamoDB-compatible value
93
+ */
51
94
  function convertFieldToTableItem(fieldDef, val) {
52
95
  switch (fieldDef.type) {
53
96
  case "date":
@@ -56,6 +99,8 @@ function convertFieldToTableItem(fieldDef, val) {
56
99
  return objectToTableItem(fieldDef.fields, val);
57
100
  case "array":
58
101
  return val.map(item => convertFieldToTableItem(fieldDef.items, item));
102
+ case "discriminatedUnion":
103
+ return convertDiscriminatedUnion(fieldDef, val, objectToTableItem);
59
104
  default:
60
105
  return val;
61
106
  }
@@ -67,6 +112,8 @@ function convertFieldToTableItem(fieldDef, val) {
67
112
  * - `"date"` fields are converted from ISO 8601 strings to `Date` objects.
68
113
  * - `"object"` fields recurse into their nested schema.
69
114
  * - `"array"` fields map each item through the same conversion.
115
+ * - `"discriminatedUnion"` fields look up the variant schema by discriminator value
116
+ * and recurse into that variant's object schema, preserving the discriminator key.
70
117
  * - `null` and `undefined` values are stripped from the result so that absent
71
118
  * fields are represented as `undefined` (omitted) on the entity, consistent
72
119
  * with root-level nullable attribute behaviour.
@@ -87,6 +134,13 @@ function tableItemToObject(schema, value) {
87
134
  }
88
135
  return result;
89
136
  }
137
+ /**
138
+ * Converts a single DynamoDB field value back to its entity representation.
139
+ *
140
+ * Mirrors {@link convertFieldToTableItem} in reverse: dates become `Date` objects,
141
+ * objects and discriminated unions recurse through their schemas, arrays map each item,
142
+ * and all other types pass through unchanged.
143
+ */
90
144
  function convertFieldToEntityValue(fieldDef, val) {
91
145
  switch (fieldDef.type) {
92
146
  case "date":
@@ -95,6 +149,8 @@ function convertFieldToEntityValue(fieldDef, val) {
95
149
  return tableItemToObject(fieldDef.fields, val);
96
150
  case "array":
97
151
  return val.map(item => convertFieldToEntityValue(fieldDef.items, item));
152
+ case "discriminatedUnion":
153
+ return convertDiscriminatedUnion(fieldDef, val, tableItemToObject);
98
154
  default:
99
155
  return val;
100
156
  }
@@ -91,7 +91,12 @@ export interface ObjectFieldDef {
91
91
  export interface ArrayFieldDef {
92
92
  /** Must be `"array"` to indicate a list/array field. */
93
93
  type: "array";
94
- /** A {@link FieldDef} describing the type of each array element. */
94
+ /**
95
+ * A {@link FieldDef} describing the type of each array element.
96
+ * All field types are supported as array items, including discriminated unions.
97
+ * Arrays always use full replacement on update, so discriminated union items
98
+ * are serialized per-element using variant-aware logic.
99
+ */
95
100
  items: FieldDef;
96
101
  /** When `true`, the field becomes optional. */
97
102
  nullable?: boolean;
@@ -171,6 +176,70 @@ export interface DateFieldDef {
171
176
  /** When `true`, the field becomes optional (`Date | undefined`). */
172
177
  nullable?: boolean;
173
178
  }
179
+ /**
180
+ * A schema field definition for a discriminated union type.
181
+ *
182
+ * The `discriminator` names the key used to distinguish variants, and `variants`
183
+ * maps each discriminator value to an {@link ObjectSchema} describing that variant's
184
+ * fields. The discriminator key is automatically added to each variant's inferred type
185
+ * as a string literal.
186
+ *
187
+ * **Update semantics:** Discriminated union fields always use **full replacement** on
188
+ * update (`SET #field = :value`), never document path merging. This is because:
189
+ * - Different variants have different field sets — a partial document path update could
190
+ * leave orphaned fields from a previous variant leaking through when variants change.
191
+ * - Avoiding orphaned fields without full replacement would require a read-before-write
192
+ * to determine the current discriminator value.
193
+ * - This matches array update semantics (also full replacement).
194
+ *
195
+ * Unlike {@link ObjectFieldDef}, discriminated union fields **can be nullable** because
196
+ * they always use full replacement on update rather than document path expressions,
197
+ * so there is no risk of DynamoDB failing on a missing parent path.
198
+ *
199
+ * **Scoping constraints:**
200
+ * - Supported at the ObjectAttribute root level, as fields within an ObjectSchema,
201
+ * and as array items
202
+ * - Not supported nested inside other discriminated unions
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const schema = {
207
+ * shape: {
208
+ * type: "discriminatedUnion",
209
+ * discriminator: "kind",
210
+ * variants: {
211
+ * circle: { radius: { type: "number" } },
212
+ * square: { side: { type: "number" } }
213
+ * }
214
+ * }
215
+ * } as const satisfies ObjectSchema;
216
+ *
217
+ * type T = InferObjectSchema<typeof schema>;
218
+ * // { shape: { kind: "circle"; radius: number } | { kind: "square"; side: number } }
219
+ * ```
220
+ */
221
+ export interface DiscriminatedUnionFieldDef {
222
+ /** Must be `"discriminatedUnion"` to indicate a discriminated union field. */
223
+ type: "discriminatedUnion";
224
+ /** The key name used to discriminate between variants. */
225
+ discriminator: string;
226
+ /**
227
+ * A record mapping each discriminator value to a {@link NonUnionObjectSchema}
228
+ * describing that variant's fields (excluding the discriminator itself).
229
+ * Discriminated unions cannot be nested inside other discriminated unions.
230
+ */
231
+ variants: Readonly<Record<string, NonUnionObjectSchema>>;
232
+ /** When `true`, the field becomes optional (`T | undefined`). */
233
+ nullable?: boolean;
234
+ }
235
+ /**
236
+ * A field definition that excludes {@link DiscriminatedUnionFieldDef}.
237
+ *
238
+ * Used in contexts where discriminated unions are not allowed:
239
+ * - {@link DiscriminatedUnionFieldDef} `variants` — discriminated unions cannot
240
+ * be nested inside other discriminated unions
241
+ */
242
+ export type NonUnionFieldDef = PrimitiveFieldDef | DateFieldDef | ObjectFieldDef | ArrayFieldDef | EnumFieldDef;
174
243
  /**
175
244
  * A field definition within an {@link ObjectSchema}.
176
245
  *
@@ -180,10 +249,17 @@ export interface DateFieldDef {
180
249
  * - {@link ObjectFieldDef} — nested objects via `fields`
181
250
  * - {@link ArrayFieldDef} — arrays/lists via `items`
182
251
  * - {@link EnumFieldDef} — string literal enums via `values`
252
+ * - {@link DiscriminatedUnionFieldDef} — discriminated unions via `discriminator` + `variants`
183
253
  *
184
254
  * Each variant is discriminated by the `type` property.
185
255
  */
186
- export type FieldDef = PrimitiveFieldDef | DateFieldDef | ObjectFieldDef | ArrayFieldDef | EnumFieldDef;
256
+ export type FieldDef = NonUnionFieldDef | DiscriminatedUnionFieldDef;
257
+ /**
258
+ * ObjectSchema that excludes discriminated union fields.
259
+ * Used for {@link DiscriminatedUnionFieldDef} variant schemas where
260
+ * nested discriminated unions are not allowed.
261
+ */
262
+ export type NonUnionObjectSchema = Record<string, NonUnionFieldDef>;
187
263
  /**
188
264
  * Declarative schema for describing the shape of an object attribute.
189
265
  *
@@ -205,18 +281,36 @@ export type FieldDef = PrimitiveFieldDef | DateFieldDef | ObjectFieldDef | Array
205
281
  * ```
206
282
  */
207
283
  export type ObjectSchema = Record<string, FieldDef>;
284
+ /**
285
+ * Infers the TypeScript type of a {@link DiscriminatedUnionFieldDef}.
286
+ *
287
+ * Iterates over the variant keys and for each produces a union member that is
288
+ * `{ [discriminator]: VariantKey } & InferObjectSchema<VariantSchema>`.
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * // Given: discriminator: "kind", variants: { circle: { radius: { type: "number" } }, square: { side: { type: "number" } } }
293
+ * // Produces: { kind: "circle"; radius: number } | { kind: "square"; side: number }
294
+ * ```
295
+ */
296
+ export type InferDiscriminatedUnion<F extends DiscriminatedUnionFieldDef> = {
297
+ [V in keyof F["variants"] & string]: {
298
+ [D in F["discriminator"]]: V;
299
+ } & InferObjectSchema<F["variants"][V]>;
300
+ }[keyof F["variants"] & string];
208
301
  /**
209
302
  * Infers the TypeScript type of a single {@link FieldDef}.
210
303
  *
211
304
  * Used internally by {@link InferObjectSchema} and for recursive array item inference.
212
305
  *
213
306
  * Resolution order:
214
- * 1. {@link ArrayFieldDef} → `Array<InferFieldDef<items>>`
215
- * 2. {@link ObjectFieldDef} → `InferObjectSchema<fields>`
216
- * 3. {@link EnumFieldDef} → `values[number]` (string literal union)
217
- * 4. {@link PrimitiveFieldDef} → `PrimitiveTypeMap[type]`
307
+ * 1. {@link DiscriminatedUnionFieldDef} → `InferDiscriminatedUnion<F>`
308
+ * 2. {@link ArrayFieldDef} → `Array<InferFieldDef<items>>`
309
+ * 3. {@link ObjectFieldDef} → `InferObjectSchema<fields>`
310
+ * 4. {@link EnumFieldDef} → `values[number]` (string literal union)
311
+ * 5. {@link PrimitiveFieldDef} → `PrimitiveTypeMap[type]`
218
312
  */
219
- export type InferFieldDef<F extends FieldDef> = F extends ArrayFieldDef ? Array<InferFieldDef<F["items"]>> : F extends ObjectFieldDef ? InferObjectSchema<F["fields"]> : F extends EnumFieldDef ? F["values"][number] : F extends PrimitiveFieldDef ? PrimitiveTypeMap[F["type"]] : never;
313
+ export type InferFieldDef<F extends FieldDef> = F extends DiscriminatedUnionFieldDef ? InferDiscriminatedUnion<F> : F extends ArrayFieldDef ? Array<InferFieldDef<F["items"]>> : F extends ObjectFieldDef ? InferObjectSchema<F["fields"]> : F extends EnumFieldDef ? F["values"][number] : F extends PrimitiveFieldDef ? PrimitiveTypeMap[F["type"]] : never;
220
314
  /**
221
315
  * Infers the TypeScript type from an {@link ObjectSchema} definition.
222
316
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,gBAAgB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,IAAI,EAAE,kBAAkB,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,IAAI,EAAE,QAAQ,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,IAAI,EAAE,OAAO,CAAC;IACd,oEAAoE;IACpE,KAAK,EAAE,QAAQ,CAAC;IAChB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IACvC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,QAAQ,GAChB,iBAAiB,GACjB,YAAY,GACZ,cAAc,GACd,aAAa,GACb,YAAY,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,QAAQ,IAAI,CAAC,SAAS,aAAa,GACnE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAChC,CAAC,SAAS,cAAc,GACtB,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAC9B,CAAC,SAAS,YAAY,GACpB,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GACnB,CAAC,SAAS,iBAAiB,GACzB,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAC3B,KAAK,CAAC;AAEhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,YAAY,IAAI;KACrD,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,KAAK,GAAG,CAAC,GAAG,aAAa,CAC1E,CAAC,CAAC,CAAC,CAAC,CACL;CACF,GAAG;KACD,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,QAAQ,EAAE,IAAI,CAAA;KAAE,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,aAAa,CAC3E,CAAC,CAAC,CAAC,CAAC,CACL;CACF,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/types.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,8 @@ import type { DocumentPathOperation } from "./types";
8
8
  * - `undefined` → skip (not being updated)
9
9
  * - `null` → REMOVE operation
10
10
  * - Nested object (`fieldDef.type === "object"`) → recurse, prepending parent path
11
- * - Everything else (primitives, arrays, dates, enums) → serialize and SET
11
+ * - Everything else (primitives, arrays, dates, enums, discriminated unions) → serialize and SET
12
+ *
12
13
  *
13
14
  * @param parentPath Path segments leading to this object (e.g. ["address"] or ["address", "geo"])
14
15
  * @param schema The ObjectSchema describing the object shape
@@ -1 +1 @@
1
- {"version":3,"file":"flattenObjectForUpdate.d.ts","sourceRoot":"","sources":["../../../../src/operations/utils/flattenObjectForUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAAE,EACpB,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpC,qBAAqB,EAAE,CAmCzB"}
1
+ {"version":3,"file":"flattenObjectForUpdate.d.ts","sourceRoot":"","sources":["../../../../src/operations/utils/flattenObjectForUpdate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEtE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAAE,EACpB,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpC,qBAAqB,EAAE,CAkCzB"}
@@ -10,7 +10,8 @@ const serializers_1 = require("../../decorators/attributes/serializers");
10
10
  * - `undefined` → skip (not being updated)
11
11
  * - `null` → REMOVE operation
12
12
  * - Nested object (`fieldDef.type === "object"`) → recurse, prepending parent path
13
- * - Everything else (primitives, arrays, dates, enums) → serialize and SET
13
+ * - Everything else (primitives, arrays, dates, enums, discriminated unions) → serialize and SET
14
+ *
14
15
  *
15
16
  * @param parentPath Path segments leading to this object (e.g. ["address"] or ["address", "geo"])
16
17
  * @param schema The ObjectSchema describing the object shape
@@ -37,7 +38,6 @@ function flattenObjectForUpdate(parentPath, schema, partialValue) {
37
38
  ops.push(...nestedOps);
38
39
  continue;
39
40
  }
40
- // Primitives, arrays, dates, enums → serialize and SET
41
41
  const serialized = (0, serializers_1.convertFieldToTableItem)(fieldDef, val);
42
42
  ops.push({ type: "set", path: fieldPath, value: serialized });
43
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dyna-record",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Typescript Data Modeler and ORM for Dynamo",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",