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.
- package/README.md +56 -8
- package/dist/src/DynaRecord.d.ts +17 -1
- package/dist/src/DynaRecord.d.ts.map +1 -1
- package/dist/src/DynaRecord.js +24 -6
- package/dist/src/decorators/attributes/ObjectAttribute.d.ts +40 -17
- package/dist/src/decorators/attributes/ObjectAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/ObjectAttribute.js +77 -17
- package/dist/src/decorators/attributes/index.d.ts +1 -1
- package/dist/src/decorators/attributes/index.d.ts.map +1 -1
- package/dist/src/decorators/attributes/serializers.d.ts +49 -0
- package/dist/src/decorators/attributes/serializers.d.ts.map +1 -1
- package/dist/src/decorators/attributes/serializers.js +99 -0
- package/dist/src/decorators/attributes/types.d.ts +51 -14
- package/dist/src/decorators/attributes/types.d.ts.map +1 -1
- package/dist/src/metadata/AttributeMetadata.d.ts +3 -0
- package/dist/src/metadata/AttributeMetadata.d.ts.map +1 -1
- package/dist/src/metadata/AttributeMetadata.js +5 -0
- package/dist/src/metadata/EntityMetadata.d.ts.map +1 -1
- package/dist/src/metadata/EntityMetadata.js +8 -1
- package/dist/src/metadata/types.d.ts +11 -0
- package/dist/src/metadata/types.d.ts.map +1 -1
- package/dist/src/operations/Update/Update.d.ts +6 -1
- package/dist/src/operations/Update/Update.d.ts.map +1 -1
- package/dist/src/operations/Update/Update.js +25 -3
- package/dist/src/operations/Update/types.d.ts +26 -2
- package/dist/src/operations/Update/types.d.ts.map +1 -1
- package/dist/src/operations/utils/expressionBuilder.d.ts +4 -3
- package/dist/src/operations/utils/expressionBuilder.d.ts.map +1 -1
- package/dist/src/operations/utils/expressionBuilder.js +63 -2
- package/dist/src/operations/utils/flattenObjectForUpdate.d.ts +19 -0
- package/dist/src/operations/utils/flattenObjectForUpdate.d.ts.map +1 -0
- package/dist/src/operations/utils/flattenObjectForUpdate.js +45 -0
- package/dist/src/operations/utils/index.d.ts +2 -0
- package/dist/src/operations/utils/index.d.ts.map +1 -1
- package/dist/src/operations/utils/index.js +2 -0
- package/dist/src/operations/utils/mergePartialObjectAttributes.d.ts +8 -0
- package/dist/src/operations/utils/mergePartialObjectAttributes.d.ts.map +1 -0
- package/dist/src/operations/utils/mergePartialObjectAttributes.js +52 -0
- package/dist/src/operations/utils/types.d.ts +16 -0
- package/dist/src/operations/utils/types.d.ts.map +1 -1
- 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
|
|
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
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
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]
|
|
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
|
|
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;
|
|
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;
|
|
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.#
|
|
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":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACd,MAAM,GAAG,CAAC;AACX,OAAO,KAAK,UAAU,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;
|
|
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
|
-
* -
|
|
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;
|
|
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
|
-
* -
|
|
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
|
|
227
|
-
|
|
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
|
|
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,
|
|
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
|