dyna-record 0.4.10 → 0.5.0

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.
Files changed (41) hide show
  1. package/README.md +56 -8
  2. package/dist/src/DynaRecord.d.ts +17 -1
  3. package/dist/src/DynaRecord.d.ts.map +1 -1
  4. package/dist/src/DynaRecord.js +24 -6
  5. package/dist/src/decorators/attributes/ObjectAttribute.d.ts +40 -17
  6. package/dist/src/decorators/attributes/ObjectAttribute.d.ts.map +1 -1
  7. package/dist/src/decorators/attributes/ObjectAttribute.js +77 -17
  8. package/dist/src/decorators/attributes/index.d.ts +1 -1
  9. package/dist/src/decorators/attributes/index.d.ts.map +1 -1
  10. package/dist/src/decorators/attributes/serializers.d.ts +49 -0
  11. package/dist/src/decorators/attributes/serializers.d.ts.map +1 -1
  12. package/dist/src/decorators/attributes/serializers.js +99 -0
  13. package/dist/src/decorators/attributes/types.d.ts +51 -14
  14. package/dist/src/decorators/attributes/types.d.ts.map +1 -1
  15. package/dist/src/metadata/AttributeMetadata.d.ts +3 -0
  16. package/dist/src/metadata/AttributeMetadata.d.ts.map +1 -1
  17. package/dist/src/metadata/AttributeMetadata.js +5 -0
  18. package/dist/src/metadata/EntityMetadata.d.ts.map +1 -1
  19. package/dist/src/metadata/EntityMetadata.js +8 -1
  20. package/dist/src/metadata/types.d.ts +11 -0
  21. package/dist/src/metadata/types.d.ts.map +1 -1
  22. package/dist/src/operations/Update/Update.d.ts +6 -1
  23. package/dist/src/operations/Update/Update.d.ts.map +1 -1
  24. package/dist/src/operations/Update/Update.js +25 -3
  25. package/dist/src/operations/Update/types.d.ts +26 -2
  26. package/dist/src/operations/Update/types.d.ts.map +1 -1
  27. package/dist/src/operations/utils/expressionBuilder.d.ts +4 -3
  28. package/dist/src/operations/utils/expressionBuilder.d.ts.map +1 -1
  29. package/dist/src/operations/utils/expressionBuilder.js +63 -2
  30. package/dist/src/operations/utils/flattenObjectForUpdate.d.ts +19 -0
  31. package/dist/src/operations/utils/flattenObjectForUpdate.d.ts.map +1 -0
  32. package/dist/src/operations/utils/flattenObjectForUpdate.js +45 -0
  33. package/dist/src/operations/utils/index.d.ts +2 -0
  34. package/dist/src/operations/utils/index.d.ts.map +1 -1
  35. package/dist/src/operations/utils/index.js +2 -0
  36. package/dist/src/operations/utils/mergePartialObjectAttributes.d.ts +8 -0
  37. package/dist/src/operations/utils/mergePartialObjectAttributes.d.ts.map +1 -0
  38. package/dist/src/operations/utils/mergePartialObjectAttributes.js +52 -0
  39. package/dist/src/operations/utils/types.d.ts +16 -0
  40. package/dist/src/operations/utils/types.d.ts.map +1 -1
  41. package/package.json +1 -1
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dateSerializer = void 0;
4
+ exports.objectToTableItem = objectToTableItem;
5
+ exports.convertFieldToTableItem = convertFieldToTableItem;
6
+ exports.tableItemToObject = tableItemToObject;
7
+ exports.createObjectSerializer = createObjectSerializer;
4
8
  /**
5
9
  * Provides serialization and deserialization functions for date attributes when interfacing with a DynamoDB table, enabling the conversion between the table's string-based date representation and JavaScript's `Date` object. DynamoDb dos not support Date types naturally, this utility allows for Date attributes to be serialized to an entity and stored as ISO strings in Dynamo.
6
10
  *
@@ -17,3 +21,98 @@ exports.dateSerializer = {
17
21
  },
18
22
  toTableAttribute: (val) => val?.toISOString() ?? undefined
19
23
  };
24
+ /**
25
+ * Recursively walks an {@link ObjectSchema} and converts the entity value to its
26
+ * DynamoDB representation.
27
+ *
28
+ * - `"date"` fields are converted from `Date` objects to ISO 8601 strings.
29
+ * - `"object"` fields recurse into their nested schema.
30
+ * - `"array"` fields map each item through the same conversion.
31
+ * - `null` and `undefined` values are stripped from the result so that nullable
32
+ * fields set to `null` are removed from the stored object rather than persisted
33
+ * as `null` in DynamoDB.
34
+ * - All other field types pass through unchanged.
35
+ *
36
+ * @param schema The {@link ObjectSchema} describing the object shape
37
+ * @param value The entity-level object value to convert
38
+ * @returns A new object suitable for DynamoDB storage
39
+ */
40
+ function objectToTableItem(schema, value) {
41
+ const result = {};
42
+ for (const [key, fieldDef] of Object.entries(schema)) {
43
+ const val = value[key];
44
+ if (val === undefined || val === null) {
45
+ continue;
46
+ }
47
+ result[key] = convertFieldToTableItem(fieldDef, val);
48
+ }
49
+ return result;
50
+ }
51
+ function convertFieldToTableItem(fieldDef, val) {
52
+ switch (fieldDef.type) {
53
+ case "date":
54
+ return val.toISOString();
55
+ case "object":
56
+ return objectToTableItem(fieldDef.fields, val);
57
+ case "array":
58
+ return val.map(item => convertFieldToTableItem(fieldDef.items, item));
59
+ default:
60
+ return val;
61
+ }
62
+ }
63
+ /**
64
+ * Recursively walks an {@link ObjectSchema} and converts a DynamoDB table item
65
+ * back to its entity representation.
66
+ *
67
+ * - `"date"` fields are converted from ISO 8601 strings to `Date` objects.
68
+ * - `"object"` fields recurse into their nested schema.
69
+ * - `"array"` fields map each item through the same conversion.
70
+ * - `null` and `undefined` values are stripped from the result so that absent
71
+ * fields are represented as `undefined` (omitted) on the entity, consistent
72
+ * with root-level nullable attribute behaviour.
73
+ * - All other field types pass through unchanged.
74
+ *
75
+ * @param schema The {@link ObjectSchema} describing the object shape
76
+ * @param value The DynamoDB table item to convert
77
+ * @returns A new object with entity-level types (e.g. `Date` instead of string)
78
+ */
79
+ function tableItemToObject(schema, value) {
80
+ const result = {};
81
+ for (const [key, fieldDef] of Object.entries(schema)) {
82
+ const val = value[key];
83
+ if (val === undefined || val === null) {
84
+ continue;
85
+ }
86
+ result[key] = convertFieldToEntityValue(fieldDef, val);
87
+ }
88
+ return result;
89
+ }
90
+ function convertFieldToEntityValue(fieldDef, val) {
91
+ switch (fieldDef.type) {
92
+ case "date":
93
+ return new Date(val);
94
+ case "object":
95
+ return tableItemToObject(fieldDef.fields, val);
96
+ case "array":
97
+ return val.map(item => convertFieldToEntityValue(fieldDef.items, item));
98
+ default:
99
+ return val;
100
+ }
101
+ }
102
+ /**
103
+ * Creates a {@link Serializers} pair for an {@link ObjectSchema}.
104
+ *
105
+ * The returned serializers handle:
106
+ * - Converting `Date` fields to/from ISO 8601 strings for DynamoDB storage.
107
+ * - Stripping `null` and `undefined` values so that nullable fields set to
108
+ * `null` during updates are removed from the stored object.
109
+ *
110
+ * @param schema The {@link ObjectSchema} describing the object shape
111
+ * @returns A `Serializers` object with `toTableAttribute` and `toEntityAttribute` functions
112
+ */
113
+ function createObjectSerializer(schema) {
114
+ return {
115
+ toTableAttribute: (val) => objectToTableItem(schema, val),
116
+ toEntityAttribute: (val) => tableItemToObject(schema, val)
117
+ };
118
+ }
@@ -8,16 +8,18 @@
8
8
  * | `"string"` | `string` |
9
9
  * | `"number"` | `number` |
10
10
  * | `"boolean"` | `boolean` |
11
+ * | `"date"` | `Date` |
11
12
  */
12
13
  export interface PrimitiveTypeMap {
13
14
  string: string;
14
15
  number: number;
15
16
  boolean: boolean;
17
+ date: Date;
16
18
  }
17
19
  /**
18
20
  * The allowed primitive type strings for object schema fields.
19
21
  *
20
- * Derived from the keys of {@link PrimitiveTypeMap}: `"string" | "number" | "boolean"`.
22
+ * Derived from the keys of {@link PrimitiveTypeMap}: `"string" | "number" | "boolean" | "date"`.
21
23
  */
22
24
  export type PrimitiveFieldType = keyof PrimitiveTypeMap;
23
25
  /**
@@ -35,7 +37,7 @@ export type PrimitiveFieldType = keyof PrimitiveTypeMap;
35
37
  export interface PrimitiveFieldDef {
36
38
  /** The primitive type — `"string"`, `"number"`, or `"boolean"`. */
37
39
  type: PrimitiveFieldType;
38
- /** When `true`, the field accepts `null` and becomes optional (`T | null | undefined`). */
40
+ /** When `true`, the field becomes optional (`T | undefined`). */
39
41
  nullable?: boolean;
40
42
  }
41
43
  /**
@@ -43,6 +45,15 @@ export interface PrimitiveFieldDef {
43
45
  *
44
46
  * The `fields` property is itself an {@link ObjectSchema}, enabling arbitrarily deep nesting.
45
47
  *
48
+ * **Object fields are never nullable.** DynamoDB cannot update nested document paths
49
+ * (e.g. `address.geo.lat`) if an intermediate object does not exist, which causes:
50
+ * `ValidationException: The document path provided in the update expression is invalid for update`.
51
+ * To avoid this, object-type fields always exist as at least an empty object `{}`.
52
+ * Non-object fields within the object may still be nullable.
53
+ *
54
+ * Objects within arrays are not subject to this limitation because arrays use full
55
+ * replacement on update rather than document path expressions.
56
+ *
46
57
  * @example
47
58
  * ```typescript
48
59
  * const schema = {
@@ -50,7 +61,8 @@ export interface PrimitiveFieldDef {
50
61
  * type: "object",
51
62
  * fields: {
52
63
  * lat: { type: "number" },
53
- * lng: { type: "number" }
64
+ * lng: { type: "number" },
65
+ * notes: { type: "string", nullable: true }
54
66
  * }
55
67
  * }
56
68
  * } as const satisfies ObjectSchema;
@@ -61,8 +73,6 @@ export interface ObjectFieldDef {
61
73
  type: "object";
62
74
  /** The nested {@link ObjectSchema} describing the object's shape. */
63
75
  fields: ObjectSchema;
64
- /** When `true`, the field accepts `null` and becomes optional. */
65
- nullable?: boolean;
66
76
  }
67
77
  /**
68
78
  * A schema field definition for an array/list type.
@@ -83,7 +93,7 @@ export interface ArrayFieldDef {
83
93
  type: "array";
84
94
  /** A {@link FieldDef} describing the type of each array element. */
85
95
  items: FieldDef;
86
- /** When `true`, the field accepts `null` and becomes optional. */
96
+ /** When `true`, the field becomes optional. */
87
97
  nullable?: boolean;
88
98
  }
89
99
  /**
@@ -120,7 +130,7 @@ export interface ArrayFieldDef {
120
130
  * type T = InferObjectSchema<typeof schema>;
121
131
  * // {
122
132
  * // status: "active" | "inactive";
123
- * // category?: "home" | "work" | "other" | null;
133
+ * // category?: "home" | "work" | "other";
124
134
  * // geo: { accuracy: "precise" | "approximate" };
125
135
  * // roles: ("admin" | "user" | "guest")[];
126
136
  * // }
@@ -138,7 +148,27 @@ export interface EnumFieldDef {
138
148
  * Must contain at least one value (enforced by the `[string, ...string[]]` tuple type).
139
149
  */
140
150
  values: readonly [string, ...string[]];
141
- /** When `true`, the field accepts `null` and becomes optional. */
151
+ /** When `true`, the field becomes optional. */
152
+ nullable?: boolean;
153
+ }
154
+ /**
155
+ * A schema field definition for a date type.
156
+ *
157
+ * Date fields are stored as ISO 8601 strings in DynamoDB and exposed as
158
+ * JavaScript `Date` objects on entities, mirroring `@DateAttribute` behavior.
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * const schema = {
163
+ * createdDate: { type: "date" },
164
+ * deletedAt: { type: "date", nullable: true }
165
+ * } as const satisfies ObjectSchema;
166
+ * ```
167
+ */
168
+ export interface DateFieldDef {
169
+ /** Must be `"date"` to indicate a date field. */
170
+ type: "date";
171
+ /** When `true`, the field becomes optional (`Date | undefined`). */
142
172
  nullable?: boolean;
143
173
  }
144
174
  /**
@@ -146,13 +176,14 @@ export interface EnumFieldDef {
146
176
  *
147
177
  * This is the union of all supported field types:
148
178
  * - {@link PrimitiveFieldDef} — `"string"`, `"number"`, `"boolean"`
179
+ * - {@link DateFieldDef} — dates stored as ISO strings, exposed as `Date` objects
149
180
  * - {@link ObjectFieldDef} — nested objects via `fields`
150
181
  * - {@link ArrayFieldDef} — arrays/lists via `items`
151
182
  * - {@link EnumFieldDef} — string literal enums via `values`
152
183
  *
153
184
  * Each variant is discriminated by the `type` property.
154
185
  */
155
- export type FieldDef = PrimitiveFieldDef | ObjectFieldDef | ArrayFieldDef | EnumFieldDef;
186
+ export type FieldDef = PrimitiveFieldDef | DateFieldDef | ObjectFieldDef | ArrayFieldDef | EnumFieldDef;
156
187
  /**
157
188
  * Declarative schema for describing the shape of an object attribute.
158
189
  *
@@ -191,9 +222,11 @@ export type InferFieldDef<F extends FieldDef> = F extends ArrayFieldDef ? Array<
191
222
  *
192
223
  * - Primitive fields map to their TS equivalents via {@link PrimitiveTypeMap}
193
224
  * - Enum fields become a union of their `values` (`values[number]`)
194
- * - Nested object fields recurse through `InferObjectSchema`
225
+ * - Nested object fields recurse through `InferObjectSchema` — always required (never nullable)
195
226
  * - Array fields become `T[]` where `T` is inferred from `items`
196
- * - Fields with `nullable: true` become optional and accept `null` (`T | null | undefined`)
227
+ * - Fields with `nullable: true` become optional (`T | undefined`)
228
+ * - Object fields are always required because DynamoDB cannot update nested document paths
229
+ * if an intermediate object does not exist
197
230
  *
198
231
  * @example
199
232
  * ```typescript
@@ -211,13 +244,17 @@ export type InferFieldDef<F extends FieldDef> = F extends ArrayFieldDef ? Array<
211
244
  * // status: "active" | "inactive";
212
245
  * // tags: string[];
213
246
  * // geo: { lat: number; lng: number };
214
- * // age?: number | null;
247
+ * // age?: number;
215
248
  * // }
216
249
  * ```
217
250
  */
218
251
  export type InferObjectSchema<S extends ObjectSchema> = {
219
- [K in keyof S as S[K]["nullable"] extends true ? never : K]: InferFieldDef<S[K]>;
252
+ [K in keyof S as S[K] extends {
253
+ nullable: true;
254
+ } ? never : K]: InferFieldDef<S[K]>;
220
255
  } & {
221
- [K in keyof S as S[K]["nullable"] extends true ? K : never]?: InferFieldDef<S[K]> | null;
256
+ [K in keyof S as S[K] extends {
257
+ nullable: true;
258
+ } ? K : never]?: InferFieldDef<S[K]>;
222
259
  };
223
260
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/decorators/attributes/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,gBAAgB,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,IAAI,EAAE,kBAAkB,CAAC;IACzB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,IAAI,EAAE,QAAQ,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,YAAY,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,IAAI,EAAE,OAAO,CAAC;IACd,oEAAoE;IACpE,KAAK,EAAE,QAAQ,CAAC;IAChB,kEAAkE;IAClE,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,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAChB,iBAAiB,GACjB,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,YAAY,IAAI;KACrD,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,GAAG,aAAa,CACxE,CAAC,CAAC,CAAC,CAAC,CACL;CACF,GAAG;KACD,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,aAAa,CACzE,CAAC,CAAC,CAAC,CAAC,CACL,GAAG,IAAI;CACT,CAAC"}
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"}
@@ -2,6 +2,7 @@ import { type ZodType } from "zod";
2
2
  import type DynaRecord from "../DynaRecord";
3
3
  import type { AttributeMetadataOptions, Serializers } from "./types";
4
4
  import type { EntityClass } from "../types";
5
+ import type { ObjectSchema } from "../decorators/attributes/types";
5
6
  /**
6
7
  * Represents the metadata for an attribute of an entity, including its name, alias (if any), nullability, and serialization strategies.
7
8
  *
@@ -23,6 +24,8 @@ declare class AttributeMetadata {
23
24
  readonly serializers?: Serializers;
24
25
  readonly type: ZodType;
25
26
  readonly foreignKeyTarget?: EntityClass<DynaRecord>;
27
+ readonly objectSchema?: ObjectSchema;
28
+ readonly partialType?: ZodType;
26
29
  constructor(options: AttributeMetadataOptions);
27
30
  }
28
31
  export default AttributeMetadata;
@@ -1 +1 @@
1
- {"version":3,"file":"AttributeMetadata.d.ts","sourceRoot":"","sources":["../../../src/metadata/AttributeMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,KAAK,UAAU,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,cAAM,iBAAiB;IACrB,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,QAAQ,EAAE,OAAO,CAAC;IAClC,SAAgB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1C,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,SAAgB,gBAAgB,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;gBAE/C,OAAO,EAAE,wBAAwB;CAa9C;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"AttributeMetadata.d.ts","sourceRoot":"","sources":["../../../src/metadata/AttributeMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,KAAK,UAAU,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAEnE;;;;;;;;;;;;;GAaG;AACH,cAAM,iBAAiB;IACrB,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,QAAQ,EAAE,OAAO,CAAC;IAClC,SAAgB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1C,SAAgB,IAAI,EAAE,OAAO,CAAC;IAC9B,SAAgB,gBAAgB,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAC3D,SAAgB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5C,SAAgB,WAAW,CAAC,EAAE,OAAO,CAAC;gBAE1B,OAAO,EAAE,wBAAwB;CAe9C;AAED,eAAe,iBAAiB,CAAC"}
@@ -21,17 +21,22 @@ class AttributeMetadata {
21
21
  serializers;
22
22
  type;
23
23
  foreignKeyTarget;
24
+ objectSchema;
25
+ partialType;
24
26
  constructor(options) {
25
27
  this.name = options.attributeName;
26
28
  this.alias = options.alias ?? options.attributeName;
27
29
  this.nullable = options.nullable ?? false;
28
30
  this.serializers = options.serializers;
29
31
  this.foreignKeyTarget = options.foreignKeyTarget;
32
+ this.objectSchema = options.objectSchema;
30
33
  if (options.nullable === true) {
31
34
  this.type = options.type.optional().nullable();
35
+ this.partialType = options.partialType?.optional().nullable();
32
36
  }
33
37
  else {
34
38
  this.type = options.type;
39
+ this.partialType = options.partialType;
35
40
  }
36
41
  }
37
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EntityMetadata.d.ts","sourceRoot":"","sources":["../../../src/metadata/EntityMetadata.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,2BAA2B,EAC3B,oBAAoB,EACpB,mBAAmB,EACnB,8BAA8B,EAC9B,2BAA2B,EAC5B,MAAM,GAAG,CAAC;AACX,OAAO,KAAK,UAAU,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAY7D,KAAK,WAAW,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,KAAK,UAAU,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,cAAM,cAAc;;IAClB;;OAEG;IACH,SAAgB,cAAc,EAAE,MAAM,CAAC;IACvC;;OAEG;IACH,SAAgB,UAAU,EAAE,wBAAwB,CAAC;IACrD;;OAEG;IACH,SAAgB,eAAe,EAAE,wBAAwB,CAAC;IAE1D;;OAEG;IACH,SAAgB,aAAa,EAAE,2BAA2B,CAAC;IAE3D,SAAgB,WAAW,EAAE,WAAW,CAAC;IAEzC;;OAEG;IACI,OAAO,EAAE,MAAM,CAAC;gBAiBX,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM;IAS5D;;;OAGG;IACI,YAAY,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAOtD;;;;;OAKG;IACI,+BAA+B,CACpC,UAAU,EAAE,uBAAuB,CAAC,UAAU,CAAC,GAC9C,uBAAuB,CAAC,UAAU,CAAC;IAatC;;;;;;OAMG;IACI,sCAAsC,CAC3C,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,GACvD,OAAO,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;IA0B/C;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;OAEG;IACH,IAAW,gBAAgB,IAAI,oBAAoB,EAAE,CAEpD;IAED;;OAEG;IACH,IAAW,sBAAsB,IAAI,qBAAqB,EAAE,CAI3D;IAED;;OAEG;IACH,IAAW,oBAAoB,IAAI,mBAAmB,EAAE,CAIvD;IAED;;OAEG;IACH,IAAW,+BAA+B,IAAI,8BAA8B,EAAE,CAE7E;IAED;;OAEG;IACH,IAAW,gBAAgB,IAAI,gBAAgB,CAO9C;IAED;;;OAGG;IACH,IAAW,oBAAoB,IAAI,2BAA2B,EAAE,CAE/D;IAED;;;OAGG;IACH,IAAW,8BAA8B,IAAI,2BAA2B,EAAE,CAUzE;CACF;AAED,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"EntityMetadata.d.ts","sourceRoot":"","sources":["../../../src/metadata/EntityMetadata.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,2BAA2B,EAC3B,oBAAoB,EACpB,mBAAmB,EACnB,8BAA8B,EAC9B,2BAA2B,EAC5B,MAAM,GAAG,CAAC;AACX,OAAO,KAAK,UAAU,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAY7D,KAAK,WAAW,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,KAAK,UAAU,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,cAAM,cAAc;;IAClB;;OAEG;IACH,SAAgB,cAAc,EAAE,MAAM,CAAC;IACvC;;OAEG;IACH,SAAgB,UAAU,EAAE,wBAAwB,CAAC;IACrD;;OAEG;IACH,SAAgB,eAAe,EAAE,wBAAwB,CAAC;IAE1D;;OAEG;IACH,SAAgB,aAAa,EAAE,2BAA2B,CAAC;IAE3D,SAAgB,WAAW,EAAE,WAAW,CAAC;IAEzC;;OAEG;IACI,OAAO,EAAE,MAAM,CAAC;gBAuBX,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM;IAS5D;;;OAGG;IACI,YAAY,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAStD;;;;;OAKG;IACI,+BAA+B,CACpC,UAAU,EAAE,uBAAuB,CAAC,UAAU,CAAC,GAC9C,uBAAuB,CAAC,UAAU,CAAC;IAatC;;;;;;OAMG;IACI,sCAAsC,CAC3C,UAAU,EAAE,OAAO,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,GACvD,OAAO,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC;IA0B/C;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;OAEG;IACH,IAAW,gBAAgB,IAAI,oBAAoB,EAAE,CAEpD;IAED;;OAEG;IACH,IAAW,sBAAsB,IAAI,qBAAqB,EAAE,CAI3D;IAED;;OAEG;IACH,IAAW,oBAAoB,IAAI,mBAAmB,EAAE,CAIvD;IAED;;OAEG;IACH,IAAW,+BAA+B,IAAI,8BAA8B,EAAE,CAE7E;IAED;;OAEG;IACH,IAAW,gBAAgB,IAAI,gBAAgB,CAO9C;IAED;;;OAGG;IACH,IAAW,oBAAoB,IAAI,2BAA2B,EAAE,CAE/D;IAED;;;OAGG;IACH,IAAW,8BAA8B,IAAI,2BAA2B,EAAE,CAUzE;CACF;AAED,eAAe,cAAc,CAAC"}
@@ -53,6 +53,11 @@ class EntityMetadata {
53
53
  * Object containing zod attributes. Built programmatically via decorators and used to create schemas
54
54
  */
55
55
  #zodAttributes = {};
56
+ /**
57
+ * Object containing zod attributes for partial (update) validation.
58
+ * ObjectAttributes use their partialType; others use the standard type.
59
+ */
60
+ #zodPartialAttributes = {};
56
61
  constructor(entityClass, tableClassName) {
57
62
  this.EntityClass = entityClass;
58
63
  this.tableClassName = tableClassName;
@@ -68,6 +73,8 @@ class EntityMetadata {
68
73
  this.attributes[attrMeta.name] = attrMeta;
69
74
  this.tableAttributes[attrMeta.alias] = attrMeta;
70
75
  this.#zodAttributes[attrMeta.name] = attrMeta.type;
76
+ this.#zodPartialAttributes[attrMeta.name] =
77
+ attrMeta.partialType ?? attrMeta.type;
71
78
  }
72
79
  /**
73
80
  * Parse raw entity defined attributes (not reserved/relationship attributes) from input and ensure they are entity defined attributes.
@@ -98,7 +105,7 @@ class EntityMetadata {
98
105
  if (this.#schemaPartial === undefined) {
99
106
  const tableMeta = _1.default.getTable(this.tableClassName);
100
107
  this.#schemaPartial = zod_1.z
101
- .object(this.#zodAttributes)
108
+ .object(this.#zodPartialAttributes)
102
109
  .omit(tableMeta.reservedKeys)
103
110
  .partial()
104
111
  .transform((data) => {
@@ -3,6 +3,7 @@ import type { AttributeMetadata, BelongsToRelationship, EntityMetadata, JoinTabl
3
3
  import type DynaRecord from "../DynaRecord";
4
4
  import type { EntityClass, MakeOptional } from "../types";
5
5
  import type { ZodType } from "zod";
6
+ import type { ObjectSchema } from "../decorators/attributes/types";
6
7
  /**
7
8
  * Represents relationship metadata that includes a foreign key reference to another entity.
8
9
  */
@@ -99,6 +100,16 @@ export interface AttributeMetadataOptions {
99
100
  * Used to enforce referential integrity even when a relationship decorator is not present.
100
101
  */
101
102
  foreignKeyTarget?: EntityClass<DynaRecord>;
103
+ /**
104
+ * When the attribute is an ObjectAttribute, stores the original ObjectSchema
105
+ * for use in partial update operations (document path expressions).
106
+ */
107
+ objectSchema?: ObjectSchema;
108
+ /**
109
+ * When present, an alternative Zod type used for partial (update) validation.
110
+ * ObjectAttributes use this to allow partial field updates.
111
+ */
112
+ partialType?: ZodType;
102
113
  }
103
114
  /**
104
115
  * A relationship that is either BelongsTo (bi-directional to parent) or OwnedBy (uni directional to parent)
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/metadata/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/metadata/types.ts"],"names":[],"mappings":"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"}
@@ -85,7 +85,12 @@ declare class Update<T extends DynaRecord> extends OperationBase<T> {
85
85
  *
86
86
  * **What it does:**
87
87
  * - Merges the provided attributes with `updatedAt` (automatically set to the current time).
88
- * - Converts the updated attributes into a DynamoDB update expression.
88
+ * - For `@ObjectAttribute` fields with non-null values, flattens the partial object into
89
+ * {@link DocumentPathOperation | document path operations} (e.g., `SET #address.#street = :address_street`)
90
+ * instead of replacing the entire map. Nested objects are recursively flattened.
91
+ * - For regular attributes and `@ObjectAttribute` fields set to `null`, uses the standard
92
+ * expression builder (existing full-replacement / REMOVE behavior).
93
+ * - Combines both regular and document path expressions into a single DynamoDB update expression.
89
94
  *
90
95
  * @param attributes - The partial attributes to be updated on the entity.
91
96
  * @returns An object containing:
@@ -1 +1 @@
1
- {"version":3,"file":"Update.d.ts","sourceRoot":"","sources":["../../../../src/operations/Update/Update.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAGL,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAmB5B,OAAO,aAAa,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACvB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAmB,WAAW,EAAgB,MAAM,aAAa,CAAC;AA4D9E;;;;;;;;;;;;;;;;;GAiBG;AACH,cAAM,MAAM,CAAC,CAAC,SAAS,UAAU,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;IACzD,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,oBAAoB,CAAC;gBAG1D,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,kBAAkB,CAAC,EAAE,oBAAoB;IAO3C;;;;;;;;;;;;;;OAcG;IACU,GAAG,CACd,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,aAAa,CAAC,UAAU,CAAC,EACrC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;cAoDhB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlD;;;;OAIG;IACH,OAAO,CAAC,uCAAuC;IAY/C;;;;;;OAMG;IACH,OAAO,CAAC,sCAAsC;IAoC9C;;;;;;;;;;;;;;;OAeG;YACW,QAAQ;IAsDtB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA4B7B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IAyBlC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,0BAA0B;IA0DlC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,iCAAiC;IAuBzC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,8BAA8B;IAkCtC;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAkBlC;;;;;OAKG;IACH,OAAO,CAAC,mCAAmC;IAsB3C;;;;;;;;OAQG;IACH,OAAO,CAAC,sCAAsC;IAqC9C;;;;;;;;;;OAUG;IACH,OAAO,CAAC,6BAA6B;IAuCrC;;;;;;;;;OASG;IACH,OAAO,CAAC,4BAA4B;IAgCpC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,6BAA6B;IAwBrC;;;;;;OAMG;IACH,OAAO,CAAC,gCAAgC;IAgBxC;;;OAGG;IACH,OAAO,CAAC,iCAAiC;CAU1C;AAED,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Update.d.ts","sourceRoot":"","sources":["../../../../src/operations/Update/Update.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAGL,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAqB5B,OAAO,aAAa,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACvB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAmB,WAAW,EAAgB,MAAM,aAAa,CAAC;AA4D9E;;;;;;;;;;;;;;;;;GAiBG;AACH,cAAM,MAAM,CAAC,CAAC,SAAS,UAAU,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;IACzD,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,oBAAoB,CAAC;gBAG1D,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,kBAAkB,CAAC,EAAE,oBAAoB;IAO3C;;;;;;;;;;;;;;OAcG;IACU,GAAG,CACd,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,aAAa,CAAC,UAAU,CAAC,EACrC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;cAoDhB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIlD;;;;OAIG;IACH,OAAO,CAAC,uCAAuC;IAY/C;;;;;;OAMG;IACH,OAAO,CAAC,sCAAsC;IAoC9C;;;;;;;;;;;;;;;OAeG;YACW,QAAQ;IAsDtB;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IA4B7B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,mBAAmB;IAuC3B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,0BAA0B;IAyBlC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,0BAA0B;IA0DlC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,iCAAiC;IAuBzC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,8BAA8B;IAkCtC;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAkBlC;;;;;OAKG;IACH,OAAO,CAAC,mCAAmC;IAsB3C;;;;;;;;OAQG;IACH,OAAO,CAAC,sCAAsC;IAqC9C;;;;;;;;;;OAUG;IACH,OAAO,CAAC,6BAA6B;IAuCrC;;;;;;;;;OASG;IACH,OAAO,CAAC,4BAA4B;IAgCpC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,6BAA6B;IAwBrC;;;;;;OAMG;IACH,OAAO,CAAC,gCAAgC;IAgBxC;;;OAGG;IACH,OAAO,CAAC,iCAAiC;CAU1C;AAED,eAAe,MAAM,CAAC"}
@@ -210,7 +210,12 @@ class Update extends OperationBase_1.default {
210
210
  *
211
211
  * **What it does:**
212
212
  * - Merges the provided attributes with `updatedAt` (automatically set to the current time).
213
- * - Converts the updated attributes into a DynamoDB update expression.
213
+ * - For `@ObjectAttribute` fields with non-null values, flattens the partial object into
214
+ * {@link DocumentPathOperation | document path operations} (e.g., `SET #address.#street = :address_street`)
215
+ * instead of replacing the entire map. Nested objects are recursively flattened.
216
+ * - For regular attributes and `@ObjectAttribute` fields set to `null`, uses the standard
217
+ * expression builder (existing full-replacement / REMOVE behavior).
218
+ * - Combines both regular and document path expressions into a single DynamoDB update expression.
214
219
  *
215
220
  * @param attributes - The partial attributes to be updated on the entity.
216
221
  * @returns An object containing:
@@ -223,8 +228,25 @@ class Update extends OperationBase_1.default {
223
228
  ...attributes,
224
229
  updatedAt: new Date()
225
230
  };
226
- const tableAttrs = (0, utils_1.entityToTableItem)(this.EntityClass, updatedAttrs);
227
- const expression = (0, utils_2.expressionBuilder)(tableAttrs);
231
+ const entityAttrs = metadata_1.default.getEntityAttributes(this.EntityClass.name);
232
+ // Separate ObjectAttribute fields (non-null) for document path handling
233
+ const regularAttrs = {};
234
+ const allDocumentPathOps = [];
235
+ for (const [key, val] of Object.entries(updatedAttrs)) {
236
+ const attrMeta = entityAttrs[key];
237
+ if (attrMeta?.objectSchema !== undefined) {
238
+ // ObjectAttribute → flatten for document path updates (objects are never nullable)
239
+ const alias = attrMeta.alias;
240
+ const ops = (0, utils_2.flattenObjectForUpdate)([alias], attrMeta.objectSchema, val);
241
+ allDocumentPathOps.push(...ops);
242
+ }
243
+ else {
244
+ // Regular attribute → existing behavior
245
+ regularAttrs[key] = val;
246
+ }
247
+ }
248
+ const tableAttrs = (0, utils_1.entityToTableItem)(this.EntityClass, regularAttrs);
249
+ const expression = (0, utils_2.expressionBuilder)(tableAttrs, allDocumentPathOps);
228
250
  return { updatedAttrs, expression };
229
251
  }
230
252
  /**
@@ -9,23 +9,47 @@ import type { EntityDefinedAttributes } from "../types";
9
9
  type NullableProperties<T> = {
10
10
  [K in keyof T]: undefined extends T[K] ? K : never;
11
11
  }[keyof T];
12
+ /**
13
+ * Recursively resolves the value type for `AllowNullForNullable`.
14
+ *
15
+ * For plain object types (not `Date`, arrays, primitives, or functions),
16
+ * wraps with `Partial<>` and recurses via {@link AllowNullForNullable} so that:
17
+ * - All fields within `@ObjectAttribute` objects are optional in update payloads,
18
+ * matching the partial update semantics (only provided fields are modified).
19
+ * - Nullable fields at any nesting depth receive `| null` during updates.
20
+ *
21
+ * Primitives, `Date`, arrays, and functions pass through unchanged.
22
+ */
23
+ type AllowNullForNullableValue<T> = T extends Date | readonly unknown[] | string | number | boolean | null | undefined | ((...args: unknown[]) => unknown) ? T : T extends Record<string, unknown> ? Partial<AllowNullForNullable<T>> : T;
12
24
  /**
13
25
  * Transforms a type `T` by allowing `null` as an additional type for its nullable properties.
14
26
  *
27
+ * Recurses into plain object values (e.g. object schema attributes) so that
28
+ * nullable fields at any depth receive `| null`, matching root-level nullable
29
+ * attribute behavior during updates.
30
+ *
15
31
  * @typeParam T - The type whose properties are to be transformed.
16
32
  * @returns A new type with properties of `T` where each nullable property is also allowed to be `null`.
17
33
  */
18
34
  type AllowNullForNullable<T> = {
19
- [K in keyof T]: K extends NullableProperties<T> ? T[K] | null : T[K];
35
+ [K in keyof T]: K extends NullableProperties<T> ? AllowNullForNullableValue<NonNullable<T[K]>> | null | undefined : AllowNullForNullableValue<T[K]>;
20
36
  };
21
37
  /**
22
- * Attributes of an entity to update. Not all properties are required. Setting a nullable property to null will remove the attribute from the item
38
+ * Attributes of an entity to update. Not all properties are required. Setting a nullable property to null will remove the attribute from the item.
39
+ *
40
+ * For `@ObjectAttribute` fields, all nested fields are `Partial` — you only need to provide the
41
+ * fields you want to change. Omitted fields are preserved in DynamoDB via document path expressions.
23
42
  *
24
43
  * @example
25
44
  * await MockModel.update("123", {
26
45
  * nonNullableAttr: "new val", // Sets new value
27
46
  * nullableAttr: null // Remove the value. This will throw a compile time error if the property is not nullable
28
47
  * })
48
+ *
49
+ * @example Partial ObjectAttribute update
50
+ * await MockModel.update("123", {
51
+ * address: { street: "456 Oak Ave" } // Only updates street, preserves other fields
52
+ * })
29
53
  */
30
54
  export type UpdateOptions<T extends DynaRecord> = Partial<AllowNullForNullable<EntityDefinedAttributes<T>>>;
31
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/operations/Update/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;GAKG;AACH,KAAK,kBAAkB,CAAC,CAAC,IAAI;KAC1B,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK;CACnD,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX;;;;;GAKG;AACH,KAAK,oBAAoB,CAAC,CAAC,IAAI;KAC5B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;CACrE,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,UAAU,IAAI,OAAO,CACvD,oBAAoB,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,UAAU,IAAI,IAAI,CACxD,OAAO,CAAC,CAAC,CAAC,EACV,WAAW,CACZ,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/operations/Update/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;GAKG;AACH,KAAK,kBAAkB,CAAC,CAAC,IAAI;KAC1B,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK;CACnD,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX;;;;;;;;;;GAUG;AACH,KAAK,yBAAyB,CAAC,CAAC,IAAI,CAAC,SACjC,IAAI,GACJ,SAAS,OAAO,EAAE,GAClB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,GACjC,CAAC,GACD,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,GAChC,CAAC,CAAC;AAER;;;;;;;;;GASG;AACH,KAAK,oBAAoB,CAAC,CAAC,IAAI;KAC5B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,kBAAkB,CAAC,CAAC,CAAC,GAC3C,yBAAyB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GAC/D,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,UAAU,IAAI,OAAO,CACvD,oBAAoB,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,UAAU,IAAI,IAAI,CACxD,OAAO,CAAC,CAAC,CAAC,EACV,WAAW,CACZ,CAAC"}
@@ -1,9 +1,10 @@
1
1
  import type { DynamoTableItem } from "../../types";
2
- import type { UpdateExpression } from "./types";
2
+ import type { UpdateExpression, DocumentPathOperation } from "./types";
3
3
  /**
4
- * Builds a dynamo expression given the table attributes
4
+ * Builds a dynamo expression given the table attributes and optional document path operations
5
5
  * @param tableAttrs The table aliases of the entity attributes
6
+ * @param documentPathOps Optional document path operations for partial ObjectAttribute updates
6
7
  * @returns
7
8
  */
8
- export declare const expressionBuilder: (tableAttrs: DynamoTableItem) => UpdateExpression;
9
+ export declare const expressionBuilder: (tableAttrs: DynamoTableItem, documentPathOps?: DocumentPathOperation[]) => UpdateExpression;
9
10
  //# sourceMappingURL=expressionBuilder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"expressionBuilder.d.ts","sourceRoot":"","sources":["../../../../src/operations/utils/expressionBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EACV,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAUjB;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,eAChB,eAAe,KAC1B,gBAoBF,CAAC"}
1
+ {"version":3,"file":"expressionBuilder.d.ts","sourceRoot":"","sources":["../../../../src/operations/utils/expressionBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EACV,gBAAgB,EAGhB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAyBjB;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,eAChB,eAAe,oBACT,qBAAqB,EAAE,KACxC,gBAiEF,CAAC"}
@@ -2,14 +2,46 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.expressionBuilder = void 0;
4
4
  /**
5
- * Builds a dynamo expression given the table attributes
5
+ * Builds a dynamo expression given the table attributes and optional document path operations
6
6
  * @param tableAttrs The table aliases of the entity attributes
7
+ * @param documentPathOps Optional document path operations for partial ObjectAttribute updates
7
8
  * @returns
8
9
  */
9
- const expressionBuilder = (tableAttrs) => {
10
+ const expressionBuilder = (tableAttrs, documentPathOps) => {
10
11
  const sorted = sortAttributesByOperand(tableAttrs);
11
12
  const setExpression = buildUpdateSetExpression(sorted.set);
12
13
  const removeExpression = buildUpdateRemoveExpression(sorted.remove);
14
+ // Merge document path operations into the expressions
15
+ if (documentPathOps !== undefined && documentPathOps.length > 0) {
16
+ const docPathResult = buildDocumentPathExpressions(documentPathOps);
17
+ // Merge SET items
18
+ if (docPathResult.setItems.length > 0) {
19
+ Object.assign(setExpression.ExpressionAttributeNames, docPathResult.expressionAttributeNames);
20
+ Object.assign(setExpression.ExpressionAttributeValues, docPathResult.expressionAttributeValues);
21
+ const existingSet = setExpression.UpdateExpression;
22
+ const docSetClause = docPathResult.setItems.join(", ");
23
+ if (existingSet !== "") {
24
+ // Append to existing SET clause
25
+ setExpression.UpdateExpression = `${existingSet}, ${docSetClause}`;
26
+ }
27
+ else {
28
+ setExpression.UpdateExpression = `SET ${docSetClause}`;
29
+ }
30
+ }
31
+ // Merge REMOVE items
32
+ if (docPathResult.removeItems.length > 0) {
33
+ // Add names used in REMOVE paths
34
+ Object.assign(removeExpression.ExpressionAttributeNames, docPathResult.expressionAttributeNames);
35
+ const existingRemove = removeExpression.UpdateExpression;
36
+ const docRemoveClause = docPathResult.removeItems.join(", ");
37
+ if (existingRemove !== "") {
38
+ removeExpression.UpdateExpression = `${existingRemove}, ${docRemoveClause}`;
39
+ }
40
+ else {
41
+ removeExpression.UpdateExpression = `REMOVE ${docRemoveClause}`;
42
+ }
43
+ }
44
+ }
13
45
  return {
14
46
  // If the operation has only REMOVE actions, it will not have expression attribute values
15
47
  ExpressionAttributeValues: setExpression.ExpressionAttributeValues,
@@ -26,6 +58,35 @@ const expressionBuilder = (tableAttrs) => {
26
58
  };
27
59
  };
28
60
  exports.expressionBuilder = expressionBuilder;
61
+ /**
62
+ * Build document path expressions from DocumentPathOperations
63
+ */
64
+ const buildDocumentPathExpressions = (ops) => {
65
+ const result = {
66
+ setItems: [],
67
+ removeItems: [],
68
+ expressionAttributeNames: {},
69
+ expressionAttributeValues: {}
70
+ };
71
+ for (const op of ops) {
72
+ // Build the document path expression: #segment1.#segment2.#segment3
73
+ const pathExpr = op.path.map(seg => `#${seg}`).join(".");
74
+ // Build the value placeholder: :segment1_segment2_segment3
75
+ const valuePlaceholder = `:${op.path.join("_")}`;
76
+ // Register all path segment names
77
+ for (const seg of op.path) {
78
+ result.expressionAttributeNames[`#${seg}`] = seg;
79
+ }
80
+ if (op.type === "set") {
81
+ result.setItems.push(`${pathExpr} = ${valuePlaceholder}`);
82
+ result.expressionAttributeValues[valuePlaceholder] = op.value;
83
+ }
84
+ else {
85
+ result.removeItems.push(pathExpr);
86
+ }
87
+ }
88
+ return result;
89
+ };
29
90
  /**
30
91
  * Sort attributes based on their operand
31
92
  * @param tableAttrs