dyna-record 0.1.5 → 0.2.1
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 +11 -13
- package/dist/src/DynaRecord.d.ts +36 -28
- package/dist/src/DynaRecord.d.ts.map +1 -1
- package/dist/src/DynaRecord.js +39 -36
- package/dist/src/decorators/Table.d.ts.map +1 -1
- package/dist/src/decorators/attributes/BooleanAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/DateAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/EnumAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/ForeignKeyAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/NumberAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/PartitionKeyAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/SortKeyAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/StringAttribute.d.ts.map +1 -1
- package/dist/src/decorators/attributes/serializers.d.ts +1 -1
- package/dist/src/decorators/relationships/BelongsTo.d.ts.map +1 -1
- package/dist/src/decorators/relationships/HasAndBelongsToMany.d.ts.map +1 -1
- package/dist/src/decorators/relationships/HasMany.d.ts +7 -0
- package/dist/src/decorators/relationships/HasMany.d.ts.map +1 -1
- package/dist/src/decorators/relationships/HasMany.js +12 -2
- package/dist/src/decorators/relationships/HasOne.d.ts.map +1 -1
- package/dist/src/decorators/types.d.ts +2 -2
- package/dist/src/decorators/types.d.ts.map +1 -1
- package/dist/src/dynamo-utils/TransactGetBuilder.d.ts +4 -0
- package/dist/src/dynamo-utils/TransactGetBuilder.d.ts.map +1 -1
- package/dist/src/dynamo-utils/TransactGetBuilder.js +6 -0
- package/dist/src/metadata/EntityMetadata.d.ts +21 -1
- package/dist/src/metadata/EntityMetadata.d.ts.map +1 -1
- package/dist/src/metadata/EntityMetadata.js +33 -0
- package/dist/src/metadata/TableMetadata.d.ts.map +1 -1
- package/dist/src/metadata/TableMetadata.js +1 -3
- package/dist/src/metadata/relationship-metadata/BelongsToRelationship.d.ts +1 -1
- package/dist/src/metadata/relationship-metadata/BelongsToRelationship.js +1 -1
- package/dist/src/metadata/relationship-metadata/HasManyRelationship.d.ts +2 -1
- package/dist/src/metadata/relationship-metadata/HasManyRelationship.d.ts.map +1 -1
- package/dist/src/metadata/relationship-metadata/HasManyRelationship.js +2 -1
- package/dist/src/metadata/relationship-metadata/OwnedByRelationship.d.ts +18 -0
- package/dist/src/metadata/relationship-metadata/OwnedByRelationship.d.ts.map +1 -0
- package/dist/src/metadata/relationship-metadata/OwnedByRelationship.js +26 -0
- package/dist/src/metadata/relationship-metadata/RelationshipMetadata.d.ts +1 -1
- package/dist/src/metadata/relationship-metadata/RelationshipMetadata.d.ts.map +1 -1
- package/dist/src/metadata/relationship-metadata/index.d.ts +2 -1
- package/dist/src/metadata/relationship-metadata/index.d.ts.map +1 -1
- package/dist/src/metadata/relationship-metadata/index.js +3 -1
- package/dist/src/metadata/relationship-metadata/types.d.ts +7 -2
- package/dist/src/metadata/relationship-metadata/types.d.ts.map +1 -1
- package/dist/src/metadata/relationship-metadata/utils.d.ts.map +1 -1
- package/dist/src/metadata/relationship-metadata/utils.js +3 -0
- package/dist/src/metadata/types.d.ts +7 -4
- package/dist/src/metadata/types.d.ts.map +1 -1
- package/dist/src/metadata/utils.d.ts +8 -4
- package/dist/src/metadata/utils.d.ts.map +1 -1
- package/dist/src/metadata/utils.js +8 -1
- package/dist/src/operations/Create/Create.d.ts +95 -16
- package/dist/src/operations/Create/Create.d.ts.map +1 -1
- package/dist/src/operations/Create/Create.js +171 -22
- package/dist/src/operations/Delete/Delete.d.ts +32 -23
- package/dist/src/operations/Delete/Delete.d.ts.map +1 -1
- package/dist/src/operations/Delete/Delete.js +117 -100
- package/dist/src/operations/Delete/types.d.ts +0 -3
- package/dist/src/operations/Delete/types.d.ts.map +1 -1
- package/dist/src/operations/FindById/FindById.d.ts +7 -28
- package/dist/src/operations/FindById/FindById.d.ts.map +1 -1
- package/dist/src/operations/FindById/FindById.js +36 -125
- package/dist/src/operations/FindById/types.d.ts +8 -9
- package/dist/src/operations/FindById/types.d.ts.map +1 -1
- package/dist/src/operations/Query/Query.d.ts +2 -1
- package/dist/src/operations/Query/Query.d.ts.map +1 -1
- package/dist/src/operations/Query/Query.js +17 -4
- package/dist/src/operations/Query/types.d.ts +39 -5
- package/dist/src/operations/Query/types.d.ts.map +1 -1
- package/dist/src/operations/Update/Update.d.ts +185 -24
- package/dist/src/operations/Update/Update.d.ts.map +1 -1
- package/dist/src/operations/Update/Update.js +409 -72
- package/dist/src/operations/Update/UpdateDryRun.d.ts +10 -0
- package/dist/src/operations/Update/UpdateDryRun.d.ts.map +1 -0
- package/dist/src/operations/Update/UpdateDryRun.js +15 -0
- package/dist/src/operations/Update/index.d.ts +1 -0
- package/dist/src/operations/Update/index.d.ts.map +1 -1
- package/dist/src/operations/Update/index.js +3 -1
- package/dist/src/operations/types.d.ts +6 -1
- package/dist/src/operations/types.d.ts.map +1 -1
- package/dist/src/operations/utils/expressionBuilder.d.ts.map +1 -1
- package/dist/src/operations/utils/expressionBuilder.js +1 -4
- package/dist/src/operations/utils/index.d.ts +0 -1
- package/dist/src/operations/utils/index.d.ts.map +1 -1
- package/dist/src/operations/utils/index.js +0 -6
- package/dist/src/operations/utils/utils.d.ts +13 -3
- package/dist/src/operations/utils/utils.d.ts.map +1 -1
- package/dist/src/operations/utils/utils.js +33 -3
- package/dist/src/query-utils/Filters.d.ts +1 -1
- package/dist/src/query-utils/Filters.d.ts.map +1 -1
- package/dist/src/query-utils/Filters.js +7 -15
- package/dist/src/query-utils/QueryBuilder.d.ts.map +1 -1
- package/dist/src/query-utils/QueryBuilder.js +9 -1
- package/dist/src/relationships/JoinTable.d.ts +17 -7
- package/dist/src/relationships/JoinTable.d.ts.map +1 -1
- package/dist/src/relationships/JoinTable.js +77 -20
- package/dist/src/relationships/index.d.ts +0 -1
- package/dist/src/relationships/index.d.ts.map +1 -1
- package/dist/src/relationships/index.js +1 -3
- package/dist/src/types.d.ts +6 -8
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils.d.ts +10 -32
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +13 -59
- package/package.json +5 -5
- package/dist/src/operations/utils/RelationshipTransactions.d.ts +0 -64
- package/dist/src/operations/utils/RelationshipTransactions.d.ts.map +0 -1
- package/dist/src/operations/utils/RelationshipTransactions.js +0 -125
- package/dist/src/relationships/BelongsToLink.d.ts +0 -51
- package/dist/src/relationships/BelongsToLink.d.ts.map +0 -1
- package/dist/src/relationships/BelongsToLink.js +0 -57
|
@@ -8,12 +8,22 @@ const dynamo_utils_1 = require("../../dynamo-utils");
|
|
|
8
8
|
const utils_1 = require("../../utils");
|
|
9
9
|
const OperationBase_1 = __importDefault(require("../OperationBase"));
|
|
10
10
|
const utils_2 = require("../utils");
|
|
11
|
+
const utils_3 = require("../../metadata/utils");
|
|
11
12
|
/**
|
|
12
|
-
* Represents
|
|
13
|
+
* Represents an operation to create a new entity record in DynamoDB, including all necessary
|
|
14
|
+
* denormalized relationship records. This ensures that "BelongsTo" and "HasMany" relationships
|
|
15
|
+
* are properly maintained at the time of entity creation.
|
|
13
16
|
*
|
|
14
|
-
*
|
|
17
|
+
* **What it does:**
|
|
18
|
+
* - Converts the given attributes into a DynamoDB-compatible format.
|
|
19
|
+
* - Inserts a new entity record, ensuring no duplicate primary key conflicts.
|
|
20
|
+
* - For each "BelongsTo" relationship that includes a foreign key:
|
|
21
|
+
* - Verifies the referenced entity exists.
|
|
22
|
+
* - Creates a denormalized link record in the related entity's partition.
|
|
23
|
+
* - If the entity's relationships imply additional denormalized records in its own partition,
|
|
24
|
+
* those are also created after verifying the related entities exist.
|
|
15
25
|
*
|
|
16
|
-
* Only attributes defined on the model can be
|
|
26
|
+
* Only attributes defined on the entity model can be set, validated both at compile-time and runtime.
|
|
17
27
|
*
|
|
18
28
|
* @template T - The type of the entity being created, extending `DynaRecord`.
|
|
19
29
|
*/
|
|
@@ -23,11 +33,22 @@ class Create extends OperationBase_1.default {
|
|
|
23
33
|
super(Entity);
|
|
24
34
|
this.#transactionBuilder = new dynamo_utils_1.TransactWriteBuilder();
|
|
25
35
|
}
|
|
26
|
-
// TODO I need to handle the error where this throws because an item already exists and throw a better error...
|
|
27
36
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
37
|
+
* Executes the create operation.
|
|
38
|
+
*
|
|
39
|
+
* **What it does:**
|
|
40
|
+
* - Parses and validates the provided attributes against the entity schema.
|
|
41
|
+
* - Generates any required reserved attributes (like `id`, `createdAt`, and `updatedAt`).
|
|
42
|
+
* - Inserts the new entity record into DynamoDB, ensuring it doesn't already exist.
|
|
43
|
+
* - For each defined "BelongsTo" relationship, ensures the related entity exists and creates
|
|
44
|
+
* a corresponding denormalized "link" record.
|
|
45
|
+
* - If the entity's creation implies that related records must also be denormalized into its own
|
|
46
|
+
* partition (due to "BelongsTo" links), retrieves and inserts those link records.
|
|
47
|
+
*
|
|
48
|
+
* @param attributes - Attributes to initialize the new entity. Must be defined on the model and valid per schema constraints.
|
|
49
|
+
* @returns A promise that resolves to the newly created entity with all attributes, including automatically set fields.
|
|
50
|
+
* @throws If the entity already exists, a uniqueness violation error is raised.
|
|
51
|
+
* @throws If a required foreign key does not correspond to an existing entity, an error is raised.
|
|
31
52
|
*/
|
|
32
53
|
async run(attributes) {
|
|
33
54
|
const entityAttrs = this.entityMetadata.parseRawEntityDefinedAttributes(attributes);
|
|
@@ -35,18 +56,30 @@ class Create extends OperationBase_1.default {
|
|
|
35
56
|
const entityData = { ...reservedAttrs, ...entityAttrs };
|
|
36
57
|
const tableItem = (0, utils_1.entityToTableItem)(this.EntityClass, entityData);
|
|
37
58
|
this.buildPutItemTransaction(tableItem, entityData.id);
|
|
38
|
-
|
|
59
|
+
this.buildBelongsToTransactions(entityData, tableItem);
|
|
60
|
+
// Attempt to fetch all belongs-to entities to properly create reverse denormalization links
|
|
61
|
+
const belongsToTableItems = await this.getBelongsToTableItems(entityData);
|
|
62
|
+
// If there are any belongs-to relationships, add the inverse link records into the new entity's partition
|
|
63
|
+
if (belongsToTableItems.length > 0) {
|
|
64
|
+
this.buildAddBelongsToLinkToSelfTransactions(reservedAttrs.id, belongsToTableItems);
|
|
65
|
+
}
|
|
39
66
|
await this.#transactionBuilder.executeTransaction();
|
|
40
67
|
return (0, utils_1.tableItemToEntity)(this.EntityClass, tableItem);
|
|
41
68
|
}
|
|
42
69
|
/**
|
|
43
|
-
* Builds
|
|
44
|
-
*
|
|
45
|
-
*
|
|
70
|
+
* Builds and returns entity attributes that must be reserved for system usage.
|
|
71
|
+
*
|
|
72
|
+
* **What it does:**
|
|
73
|
+
* - Generates a unique entity ID if the entity's schema does not specify an `id` field.
|
|
74
|
+
* - Sets `createdAt` and `updatedAt` to the current time.
|
|
75
|
+
* - Builds the partition and sort key values based on the entity's class and generated ID.
|
|
76
|
+
*
|
|
77
|
+
* @param entityAttrs - The user-provided entity attributes.
|
|
78
|
+
* @returns The combined attributes including all reserved fields.
|
|
79
|
+
* @private
|
|
46
80
|
*/
|
|
47
81
|
buildReservedAttributes(entityAttrs) {
|
|
48
82
|
const { idField } = this.entityMetadata;
|
|
49
|
-
// If the entity has has a custom id field use that, otherwise generate a uuid
|
|
50
83
|
const id = idField === undefined
|
|
51
84
|
? (0, uuid_1.v4)()
|
|
52
85
|
: entityAttrs[idField];
|
|
@@ -66,28 +99,144 @@ class Create extends OperationBase_1.default {
|
|
|
66
99
|
return { ...keys, ...defaultAttrs };
|
|
67
100
|
}
|
|
68
101
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
102
|
+
* Adds a "PutItem" transaction for the new entity record.
|
|
103
|
+
*
|
|
104
|
+
* **What it does:**
|
|
105
|
+
* - Ensures the primary key does not already exist, preventing duplication.
|
|
106
|
+
*
|
|
107
|
+
* @param tableItem - The DynamoDB table item for the entity to put.
|
|
108
|
+
* @param id - The unique identifier of the new entity.
|
|
109
|
+
* @private
|
|
71
110
|
*/
|
|
72
111
|
buildPutItemTransaction(tableItem, id) {
|
|
73
112
|
const { name: tableName } = this.tableMetadata;
|
|
74
113
|
const putExpression = {
|
|
75
114
|
TableName: tableName,
|
|
76
115
|
Item: tableItem,
|
|
77
|
-
ConditionExpression: `attribute_not_exists(${this.partitionKeyAlias})`
|
|
116
|
+
ConditionExpression: `attribute_not_exists(${this.partitionKeyAlias})`
|
|
78
117
|
};
|
|
79
118
|
this.#transactionBuilder.addPut(putExpression, `${this.EntityClass.name} with id: ${id} already exists`);
|
|
80
119
|
}
|
|
81
120
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
121
|
+
* Adds "PutItem" transactions to create denormalized "BelongsTo" link records in the related entity's partitions.
|
|
122
|
+
*
|
|
123
|
+
* **What it does:**
|
|
124
|
+
* - For each "BelongsTo" relationship with a defined foreign key, checks that the related entity exists.
|
|
125
|
+
* - Inserts a "link" item into the related entity's partition to maintain denormalized relationships.
|
|
126
|
+
*
|
|
127
|
+
* @param entityData - The complete set of entity attributes for the new entity.
|
|
128
|
+
* @param tableItem - The main entity's DynamoDB table item.
|
|
129
|
+
* @private
|
|
84
130
|
*/
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
131
|
+
buildBelongsToTransactions(entityData, tableItem) {
|
|
132
|
+
const tableName = this.tableMetadata.name;
|
|
133
|
+
const relMetadata = this.entityMetadata.belongsToOrOwnedByRelationships;
|
|
134
|
+
for (const relMeta of relMetadata) {
|
|
135
|
+
const foreignKey = (0, utils_2.extractForeignKeyFromEntity)(relMeta, entityData);
|
|
136
|
+
if (foreignKey !== undefined) {
|
|
137
|
+
// Ensure referenced entity exists before linking
|
|
138
|
+
this.buildRelationshipExistsConditionTransaction(relMeta, foreignKey);
|
|
139
|
+
const key = (0, utils_2.buildBelongsToLinkKey)(this.EntityClass, entityData.id, relMeta, foreignKey);
|
|
140
|
+
this.#transactionBuilder.addPut({
|
|
141
|
+
TableName: tableName,
|
|
142
|
+
Item: { ...tableItem, ...key },
|
|
143
|
+
ConditionExpression: `attribute_not_exists(${this.partitionKeyAlias})`
|
|
144
|
+
}, `${relMeta.target.name} with id: ${foreignKey} already has an associated ${this.EntityClass.name}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Retrieves the DynamoDB items for all entities that the new entity references via "BelongsTo" relationships.
|
|
150
|
+
*
|
|
151
|
+
* **What it does:**
|
|
152
|
+
* - For each "BelongsTo" relationship, queries DynamoDB for the related entity record.
|
|
153
|
+
* - Returns all found related items as an array.
|
|
154
|
+
* - If no relationships or no foreign keys are present, returns an empty array.
|
|
155
|
+
*
|
|
156
|
+
* @param entityData - The attributes of the entity being created.
|
|
157
|
+
* @returns A promise that resolves to an array of related DynamoDB items.
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
async getBelongsToTableItems(entityData) {
|
|
161
|
+
const { name: tableName } = this.tableMetadata;
|
|
162
|
+
const transactionBuilder = new dynamo_utils_1.TransactGetBuilder();
|
|
163
|
+
const relMetas = this.entityMetadata.relationships;
|
|
164
|
+
const belongsToRelMetas = Object.values(relMetas).filter(relMeta => (0, utils_3.isBelongsToRelationship)(relMeta));
|
|
165
|
+
belongsToRelMetas.forEach(relMeta => {
|
|
166
|
+
const fk = (0, utils_2.extractForeignKeyFromEntity)(relMeta, entityData);
|
|
167
|
+
if (fk !== undefined) {
|
|
168
|
+
transactionBuilder.addGet({
|
|
169
|
+
TableName: tableName,
|
|
170
|
+
Key: {
|
|
171
|
+
[this.partitionKeyAlias]: relMeta.target.partitionKeyValue(fk),
|
|
172
|
+
[this.sortKeyAlias]: relMeta.target.name
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (transactionBuilder.hasTransactions()) {
|
|
178
|
+
const results = await transactionBuilder.executeTransaction();
|
|
179
|
+
return results.reduce((acc, res) => {
|
|
180
|
+
if (res.Item !== undefined)
|
|
181
|
+
acc.push(res.Item);
|
|
182
|
+
return acc;
|
|
183
|
+
}, []);
|
|
184
|
+
}
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Adds a condition check transaction to ensure that the entity referenced by a "BelongsTo" foreign key exists.
|
|
189
|
+
*
|
|
190
|
+
* **What it does:**
|
|
191
|
+
* - Checks the existence of the related entity before creating the link item.
|
|
192
|
+
* - If the related entity does not exist, the transaction will fail, preventing creation of dangling references.
|
|
193
|
+
*
|
|
194
|
+
* @param rel - The "BelongsTo" relationship metadata.
|
|
195
|
+
* @param relationshipId - The foreign key value referencing the related entity.
|
|
196
|
+
* @private
|
|
197
|
+
*/
|
|
198
|
+
buildRelationshipExistsConditionTransaction(rel, relationshipId) {
|
|
199
|
+
const { name: tableName } = this.tableMetadata;
|
|
200
|
+
const errMsg = `${rel.target.name} with ID '${relationshipId}' does not exist`;
|
|
201
|
+
const conditionCheck = {
|
|
202
|
+
TableName: tableName,
|
|
203
|
+
Key: {
|
|
204
|
+
[this.partitionKeyAlias]: rel.target.partitionKeyValue(relationshipId),
|
|
205
|
+
[this.sortKeyAlias]: rel.target.name
|
|
206
|
+
},
|
|
207
|
+
ConditionExpression: `attribute_exists(${this.partitionKeyAlias})`
|
|
208
|
+
};
|
|
209
|
+
this.#transactionBuilder.addConditionCheck(conditionCheck, errMsg);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* For each related entity referenced by a "BelongsTo" relationship, insert a denormalized copy of that entity
|
|
213
|
+
* into the new entity's partition. This maintains a consistent, denormalized view of relationships.
|
|
214
|
+
*
|
|
215
|
+
* **What it does:**
|
|
216
|
+
* - Adds "PutItem" operations to create link records in the newly created entity's partition.
|
|
217
|
+
* - Ensures these link records don't already exist.
|
|
218
|
+
*
|
|
219
|
+
* @param entityId - The newly created entity's ID.
|
|
220
|
+
* @param belongsToTableItems - The table items representing each related "BelongsTo" entity.
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
buildAddBelongsToLinkToSelfTransactions(entityId, belongsToTableItems) {
|
|
224
|
+
const pk = this.EntityClass.partitionKeyValue(entityId);
|
|
225
|
+
const typeAlias = this.tableMetadata.defaultAttributes.type.alias;
|
|
226
|
+
belongsToTableItems.forEach(tableItem => {
|
|
227
|
+
const relationshipType = tableItem[typeAlias];
|
|
228
|
+
const key = {
|
|
229
|
+
[this.partitionKeyAlias]: pk,
|
|
230
|
+
[this.sortKeyAlias]: relationshipType
|
|
231
|
+
};
|
|
232
|
+
this.#transactionBuilder.addPut({
|
|
233
|
+
TableName: this.tableMetadata.name,
|
|
234
|
+
Item: { ...tableItem, ...key },
|
|
235
|
+
ConditionExpression: `attribute_not_exists(${this.partitionKeyAlias})`
|
|
236
|
+
},
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
238
|
+
`${this.EntityClass.name} already has an associated ${relationshipType}`);
|
|
89
239
|
});
|
|
90
|
-
await relationshipTransactions.build(entityData);
|
|
91
240
|
}
|
|
92
241
|
}
|
|
93
242
|
exports.default = Create;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import DynaRecord from "../../DynaRecord";
|
|
1
|
+
import type DynaRecord from "../../DynaRecord";
|
|
2
2
|
import type { EntityClass } from "../../types";
|
|
3
3
|
import OperationBase from "../OperationBase";
|
|
4
4
|
/**
|
|
5
5
|
* Implements the operation for deleting an entity and its related data from the database within the ORM framework.
|
|
6
6
|
*
|
|
7
|
-
* Delete an entity, everything in its partition,
|
|
7
|
+
* Delete an entity, everything in its partition, denormalized records and nullifies ForeignKeys on attributes that BelongTo it
|
|
8
8
|
* If the foreign key is non nullable than it will throw a NullConstraintViolationError
|
|
9
9
|
*
|
|
10
10
|
* The `Delete` operation supports complex scenarios, such as deleting related entities in "BelongsTo" relationships, nullifying or removing foreign keys to maintain data integrity, and handling many-to-many relationships through join tables.
|
|
@@ -18,21 +18,38 @@ declare class Delete<T extends DynaRecord> extends OperationBase<T> {
|
|
|
18
18
|
* Delete an item by id
|
|
19
19
|
* - Deletes the given entity
|
|
20
20
|
* - Deletes each item in the entity's partition
|
|
21
|
-
* - For each item in the entity's partition which is
|
|
21
|
+
* - For each item in the entity's partition which is a denormalized record it:
|
|
22
22
|
* - Will nullify the associated relationship's ForeignKey attribute if the attribute is nullable
|
|
23
23
|
* @param id
|
|
24
24
|
*/
|
|
25
25
|
run(id: string): Promise<void>;
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @param
|
|
27
|
+
* Prefetch the item being deleted including items in its partition from denormalized associated records
|
|
28
|
+
* @param id
|
|
29
|
+
* @returns - The item and its associated links denormalized
|
|
30
|
+
*/
|
|
31
|
+
private preFetch;
|
|
32
|
+
/**
|
|
33
|
+
* Returns true if the linked entity needs to have a foreign key nullified
|
|
34
|
+
* @param relMeta
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
private doesEntityNeedForeignKeyNullified;
|
|
38
|
+
/**
|
|
39
|
+
* Delete the entity and denormalized links from BelongsTo relationships
|
|
40
|
+
* @param self
|
|
41
|
+
*/
|
|
42
|
+
private buildDeleteSelfTransactions;
|
|
43
|
+
/**
|
|
44
|
+
* Deletes an item when given the table keys
|
|
45
|
+
* @param keys - The key to delete representing the table keys, as opposed to the entities keys
|
|
29
46
|
*/
|
|
30
47
|
private buildDeleteItemTransaction;
|
|
31
48
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param
|
|
49
|
+
* Deletes an item when given the keys of the entity. Converts the keys to the table alias
|
|
50
|
+
* @param keys - The key to delete representing the entity keys, as opposed to the table keys
|
|
34
51
|
*/
|
|
35
|
-
private
|
|
52
|
+
private buildDeleteEntityTransaction;
|
|
36
53
|
/**
|
|
37
54
|
* If the item being deleted has a foreign key reference, nullify the associated relationship's ForeignKey attribute
|
|
38
55
|
* If the ForeignKey is non nullable than it throws a NullConstraintViolationError
|
|
@@ -46,29 +63,21 @@ declare class Delete<T extends DynaRecord> extends OperationBase<T> {
|
|
|
46
63
|
*/
|
|
47
64
|
private buildDeleteAssociatedBelongsTransaction;
|
|
48
65
|
/**
|
|
49
|
-
*
|
|
50
|
-
* @param
|
|
51
|
-
* @param entityId
|
|
52
|
-
* @param foreignKeyValue
|
|
53
|
-
*/
|
|
54
|
-
private buildDeleteBelongsToHasManyTransaction;
|
|
55
|
-
/**
|
|
56
|
-
* Deletes associated BelongsToLink for a BelongsTo HasOne relationship
|
|
57
|
-
* @param relMeta
|
|
58
|
-
* @param foreignKeyValue
|
|
66
|
+
* If the item has a JoinTable entry (is part of HasAndBelongsToMany relationship) then delete both JoinTable entries
|
|
67
|
+
* @param item - Denormalized record from HasAndBelongsToMany relationship
|
|
59
68
|
*/
|
|
60
|
-
private
|
|
69
|
+
private buildDeleteJoinTableLinkTransaction;
|
|
61
70
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @param
|
|
64
|
-
* @returns
|
|
71
|
+
* If the entity being deleted is owned by another entity via a unidirectional relationship, delete the denormalized records
|
|
72
|
+
* @param self - The entity being deleted
|
|
65
73
|
*/
|
|
66
|
-
private
|
|
74
|
+
private buildDeleteOwnedByRelationships;
|
|
67
75
|
/**
|
|
68
76
|
* Track validation errors and throw AggregateError after all validations have been run
|
|
69
77
|
* @param err
|
|
70
78
|
*/
|
|
71
79
|
private trackValidationError;
|
|
80
|
+
private buildKeyObject;
|
|
72
81
|
}
|
|
73
82
|
export default Delete;
|
|
74
83
|
//# sourceMappingURL=Delete.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Delete.d.ts","sourceRoot":"","sources":["../../../../src/operations/Delete/Delete.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"Delete.d.ts","sourceRoot":"","sources":["../../../../src/operations/Delete/Delete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,kBAAkB,CAAC;AAY/C,OAAO,KAAK,EAEV,WAAW,EAEZ,MAAM,aAAa,CAAC;AAErB,OAAO,aAAa,MAAM,kBAAkB,CAAC;AA2B7C;;;;;;;;;GASG;AACH,cAAM,MAAM,CAAC,CAAC,SAAS,UAAU,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;;gBAS7C,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IAiBlC;;;;;;;OAOG;IACU,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC3C;;;;OAIG;YACW,QAAQ;IA+BtB;;;;OAIG;IACH,OAAO,CAAC,iCAAiC;IASzC;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAOnC;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAalC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAkBpC;;;;OAIG;YACW,iCAAiC;IA2B/C;;;;OAIG;IACH,OAAO,CAAC,uCAAuC;IA2B/C;;;OAGG;IACH,OAAO,CAAC,mCAAmC;IAiB3C;;;OAGG;IACH,OAAO,CAAC,+BAA+B;IAqBvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,cAAc;CAcvB;AAED,eAAe,MAAM,CAAC"}
|
|
@@ -3,19 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const DynaRecord_1 = __importDefault(require("../../DynaRecord"));
|
|
7
6
|
const dynamo_utils_1 = require("../../dynamo-utils");
|
|
8
7
|
const errors_1 = require("../../errors");
|
|
9
8
|
const metadata_1 = __importDefault(require("../../metadata"));
|
|
10
9
|
const utils_1 = require("../../metadata/utils");
|
|
11
|
-
const relationships_1 = require("../../relationships");
|
|
12
10
|
const utils_2 = require("../../utils");
|
|
13
11
|
const OperationBase_1 = __importDefault(require("../OperationBase"));
|
|
12
|
+
const Update_1 = require("../Update");
|
|
14
13
|
const utils_3 = require("../utils");
|
|
15
14
|
/**
|
|
16
15
|
* Implements the operation for deleting an entity and its related data from the database within the ORM framework.
|
|
17
16
|
*
|
|
18
|
-
* Delete an entity, everything in its partition,
|
|
17
|
+
* Delete an entity, everything in its partition, denormalized records and nullifies ForeignKeys on attributes that BelongTo it
|
|
19
18
|
* If the foreign key is non nullable than it will throw a NullConstraintViolationError
|
|
20
19
|
*
|
|
21
20
|
* The `Delete` operation supports complex scenarios, such as deleting related entities in "BelongsTo" relationships, nullifying or removing foreign keys to maintain data integrity, and handling many-to-many relationships through join tables.
|
|
@@ -45,33 +44,23 @@ class Delete extends OperationBase_1.default {
|
|
|
45
44
|
* Delete an item by id
|
|
46
45
|
* - Deletes the given entity
|
|
47
46
|
* - Deletes each item in the entity's partition
|
|
48
|
-
* - For each item in the entity's partition which is
|
|
47
|
+
* - For each item in the entity's partition which is a denormalized record it:
|
|
49
48
|
* - Will nullify the associated relationship's ForeignKey attribute if the attribute is nullable
|
|
50
49
|
* @param id
|
|
51
50
|
*/
|
|
52
51
|
async run(id) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.buildDeleteItemTransaction(item, {
|
|
66
|
-
errorMessage: `Failed to delete BelongsToLink with keys: ${JSON.stringify({
|
|
67
|
-
[this.#partitionKeyField]: item[this.#partitionKeyField],
|
|
68
|
-
[this.#sortKeyField]: item[this.#sortKeyField]
|
|
69
|
-
})}`
|
|
70
|
-
});
|
|
71
|
-
this.buildNullifyForeignKeyTransaction(item);
|
|
72
|
-
this.buildDeleteJoinTableLinkTransaction(item);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
52
|
+
const preFetchRes = await this.preFetch(id);
|
|
53
|
+
this.buildDeleteSelfTransactions(preFetchRes.self);
|
|
54
|
+
preFetchRes.linkedEntities.forEach(item => {
|
|
55
|
+
this.buildDeleteEntityTransaction(item, {
|
|
56
|
+
errorMessage: `Failed to delete denormalized record with keys: ${JSON.stringify(this.buildKeyObject(item, this.#partitionKeyField, this.#sortKeyField))}`
|
|
57
|
+
});
|
|
58
|
+
this.buildDeleteJoinTableLinkTransaction(item);
|
|
59
|
+
});
|
|
60
|
+
this.buildDeleteOwnedByRelationships(preFetchRes.self);
|
|
61
|
+
await Promise.all(preFetchRes.linkedEntitiesWithFkRef.map(async (entity) => {
|
|
62
|
+
await this.buildNullifyForeignKeyTransaction(entity);
|
|
63
|
+
}));
|
|
75
64
|
if (this.#validationErrors.length === 0) {
|
|
76
65
|
await this.#transactionBuilder.executeTransaction();
|
|
77
66
|
}
|
|
@@ -80,35 +69,75 @@ class Delete extends OperationBase_1.default {
|
|
|
80
69
|
}
|
|
81
70
|
}
|
|
82
71
|
/**
|
|
83
|
-
*
|
|
84
|
-
* @param
|
|
72
|
+
* Prefetch the item being deleted including items in its partition from denormalized associated records
|
|
73
|
+
* @param id
|
|
74
|
+
* @returns - The item and its associated links denormalized
|
|
75
|
+
*/
|
|
76
|
+
async preFetch(id) {
|
|
77
|
+
const items = await this.EntityClass.query(id);
|
|
78
|
+
const prefetchResult = items.reduce((acc, item) => {
|
|
79
|
+
const isItemSelf = item.id === id && item instanceof this.EntityClass;
|
|
80
|
+
if (isItemSelf) {
|
|
81
|
+
acc.self = item;
|
|
82
|
+
}
|
|
83
|
+
else if (this.doesEntityNeedForeignKeyNullified(item)) {
|
|
84
|
+
acc.linkedEntitiesWithFkRef.push(item);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
acc.linkedEntities.push(item);
|
|
88
|
+
}
|
|
89
|
+
return acc;
|
|
90
|
+
}, { linkedEntities: [], linkedEntitiesWithFkRef: [] });
|
|
91
|
+
if (prefetchResult.self === undefined) {
|
|
92
|
+
throw new errors_1.NotFoundError(`Item does not exist: ${id}`);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
self: prefetchResult.self,
|
|
96
|
+
linkedEntities: prefetchResult.linkedEntities,
|
|
97
|
+
linkedEntitiesWithFkRef: prefetchResult.linkedEntitiesWithFkRef
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Returns true if the linked entity needs to have a foreign key nullified
|
|
102
|
+
* @param relMeta
|
|
103
|
+
* @returns
|
|
104
|
+
*/
|
|
105
|
+
doesEntityNeedForeignKeyNullified(linkedEntity) {
|
|
106
|
+
const relMeta = this.#relationsLookup[linkedEntity.type];
|
|
107
|
+
return (!(0, utils_1.isBelongsToRelationship)(relMeta) &&
|
|
108
|
+
(0, utils_1.isRelationshipMetadataWithForeignKey)(relMeta));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Delete the entity and denormalized links from BelongsTo relationships
|
|
112
|
+
* @param self
|
|
113
|
+
*/
|
|
114
|
+
buildDeleteSelfTransactions(self) {
|
|
115
|
+
this.buildDeleteEntityTransaction(self, {
|
|
116
|
+
errorMessage: `Failed to delete ${this.EntityClass.name} with Id: ${self.id}`
|
|
117
|
+
});
|
|
118
|
+
this.buildDeleteAssociatedBelongsTransaction(self.id, self);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Deletes an item when given the table keys
|
|
122
|
+
* @param keys - The key to delete representing the table keys, as opposed to the entities keys
|
|
85
123
|
*/
|
|
86
|
-
buildDeleteItemTransaction(
|
|
87
|
-
const pkField = this.#partitionKeyField;
|
|
88
|
-
const skField = this.#sortKeyField;
|
|
124
|
+
buildDeleteItemTransaction(keys, options) {
|
|
89
125
|
this.#transactionBuilder.addDelete({
|
|
90
126
|
TableName: this.#tableName,
|
|
91
|
-
Key:
|
|
92
|
-
[this.partitionKeyAlias]: item[pkField],
|
|
93
|
-
[this.sortKeyAlias]: item[skField]
|
|
94
|
-
}
|
|
127
|
+
Key: keys
|
|
95
128
|
}, options.errorMessage);
|
|
96
129
|
}
|
|
97
130
|
/**
|
|
98
|
-
*
|
|
99
|
-
* @param
|
|
131
|
+
* Deletes an item when given the keys of the entity. Converts the keys to the table alias
|
|
132
|
+
* @param keys - The key to delete representing the entity keys, as opposed to the table keys
|
|
100
133
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
[this
|
|
107
|
-
|
|
108
|
-
};
|
|
109
|
-
this.buildDeleteItemTransaction(belongsToLinksKeys, {
|
|
110
|
-
errorMessage: `Failed to delete BelongsToLink with keys: ${JSON.stringify(belongsToLinksKeys)}`
|
|
111
|
-
});
|
|
134
|
+
buildDeleteEntityTransaction(keys, options) {
|
|
135
|
+
if ((0, utils_2.isKeyOfObject)(keys, this.#partitionKeyField) &&
|
|
136
|
+
(0, utils_2.isKeyOfObject)(keys, this.#sortKeyField)) {
|
|
137
|
+
this.buildDeleteItemTransaction({
|
|
138
|
+
[this.partitionKeyAlias]: keys[this.#partitionKeyField],
|
|
139
|
+
[this.sortKeyAlias]: keys[this.#sortKeyField]
|
|
140
|
+
}, options);
|
|
112
141
|
}
|
|
113
142
|
}
|
|
114
143
|
/**
|
|
@@ -116,28 +145,18 @@ class Delete extends OperationBase_1.default {
|
|
|
116
145
|
* If the ForeignKey is non nullable than it throws a NullConstraintViolationError
|
|
117
146
|
* @param item
|
|
118
147
|
*/
|
|
119
|
-
buildNullifyForeignKeyTransaction(item) {
|
|
120
|
-
const relMeta = this.#relationsLookup[item.
|
|
148
|
+
async buildNullifyForeignKeyTransaction(item) {
|
|
149
|
+
const relMeta = this.#relationsLookup[item.type];
|
|
121
150
|
if ((0, utils_1.isRelationshipMetadataWithForeignKey)(relMeta)) {
|
|
122
151
|
const entityAttrs = metadata_1.default.getEntityAttributes(relMeta.target.name);
|
|
123
152
|
const attrMeta = Object.values(entityAttrs).find(attr => attr.name === relMeta.foreignKey);
|
|
124
153
|
if (attrMeta?.nullable === false) {
|
|
125
154
|
this.trackValidationError(new errors_1.NullConstraintViolationError(`Cannot set ${relMeta.target.name} with id: '${item.id}' attribute '${relMeta.foreignKey}' to null`));
|
|
126
155
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
[
|
|
130
|
-
}
|
|
131
|
-
const tableAttrs = (0, utils_2.entityToTableItem)(relMeta.target, {
|
|
132
|
-
[relMeta.foreignKey]: null
|
|
133
|
-
});
|
|
134
|
-
const expression = (0, utils_3.expressionBuilder)(tableAttrs);
|
|
135
|
-
this.#transactionBuilder.addUpdate({
|
|
136
|
-
TableName: this.#tableName,
|
|
137
|
-
Key: tableKeys,
|
|
138
|
-
UpdateExpression: expression.UpdateExpression,
|
|
139
|
-
ExpressionAttributeNames: expression.ExpressionAttributeNames
|
|
140
|
-
}, `Failed to remove foreign key attribute from ${relMeta.target.name} with Id: ${item.foreignKey}`);
|
|
156
|
+
else {
|
|
157
|
+
const op = new Update_1.UpdateDryRun(relMeta.target, this.#transactionBuilder);
|
|
158
|
+
await op.run(item.id, { [relMeta.foreignKey]: null });
|
|
159
|
+
}
|
|
141
160
|
}
|
|
142
161
|
}
|
|
143
162
|
/**
|
|
@@ -150,52 +169,41 @@ class Delete extends OperationBase_1.default {
|
|
|
150
169
|
if ((0, utils_2.isKeyOfObject)(item, relMeta.foreignKey) &&
|
|
151
170
|
item[relMeta.foreignKey] !== undefined) {
|
|
152
171
|
const foreignKeyValue = item[relMeta.foreignKey];
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.buildDeleteBelongsToHasOneTransaction(relMeta, foreignKeyValue);
|
|
158
|
-
}
|
|
172
|
+
const belongsToLinksKeys = (0, utils_3.buildBelongsToLinkKey)(this.EntityClass, entityId, relMeta, foreignKeyValue);
|
|
173
|
+
this.buildDeleteItemTransaction(belongsToLinksKeys, {
|
|
174
|
+
errorMessage: `Failed to delete denormalized record with keys: ${JSON.stringify(belongsToLinksKeys)}`
|
|
175
|
+
});
|
|
159
176
|
}
|
|
160
177
|
});
|
|
161
178
|
}
|
|
162
179
|
/**
|
|
163
|
-
*
|
|
164
|
-
* @param
|
|
165
|
-
* @param entityId
|
|
166
|
-
* @param foreignKeyValue
|
|
180
|
+
* If the item has a JoinTable entry (is part of HasAndBelongsToMany relationship) then delete both JoinTable entries
|
|
181
|
+
* @param item - Denormalized record from HasAndBelongsToMany relationship
|
|
167
182
|
*/
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
183
|
+
buildDeleteJoinTableLinkTransaction(item) {
|
|
184
|
+
const relMeta = this.#relationsLookup[item.type];
|
|
185
|
+
if ((0, utils_1.isHasAndBelongsToManyRelationship)(relMeta)) {
|
|
186
|
+
const belongsToLinksKeys = this.buildKeyObject(item, this.#sortKeyField, this.#partitionKeyField);
|
|
187
|
+
this.buildDeleteEntityTransaction(belongsToLinksKeys, {
|
|
188
|
+
errorMessage: `Failed to delete denormalized record with keys: ${JSON.stringify(belongsToLinksKeys)}`
|
|
189
|
+
});
|
|
190
|
+
}
|
|
176
191
|
}
|
|
177
192
|
/**
|
|
178
|
-
*
|
|
179
|
-
* @param
|
|
180
|
-
* @param foreignKeyValue
|
|
193
|
+
* If the entity being deleted is owned by another entity via a unidirectional relationship, delete the denormalized records
|
|
194
|
+
* @param self - The entity being deleted
|
|
181
195
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
buildDeleteOwnedByRelationships(self) {
|
|
197
|
+
this.entityMetadata.ownedByRelationships.forEach(ownedByRelMeta => {
|
|
198
|
+
if ((0, utils_2.isKeyOfObject)(self, ownedByRelMeta.foreignKey)) {
|
|
199
|
+
const fkValue = self[ownedByRelMeta.foreignKey];
|
|
200
|
+
const keys = (0, utils_3.buildBelongsToLinkKey)(this.EntityClass, self.id, ownedByRelMeta, fkValue);
|
|
201
|
+
this.buildDeleteItemTransaction(keys, {
|
|
202
|
+
errorMessage: `Failed to delete denormalized record with keys: ${JSON.stringify(keys)}`
|
|
203
|
+
});
|
|
204
|
+
}
|
|
189
205
|
});
|
|
190
206
|
}
|
|
191
|
-
/**
|
|
192
|
-
* Type guard to check if the item being evaluated is the currentClass
|
|
193
|
-
* @param item
|
|
194
|
-
* @returns
|
|
195
|
-
*/
|
|
196
|
-
isEntityClass(item) {
|
|
197
|
-
return item instanceof DynaRecord_1.default;
|
|
198
|
-
}
|
|
199
207
|
/**
|
|
200
208
|
* Track validation errors and throw AggregateError after all validations have been run
|
|
201
209
|
* @param err
|
|
@@ -203,5 +211,14 @@ class Delete extends OperationBase_1.default {
|
|
|
203
211
|
trackValidationError(err) {
|
|
204
212
|
this.#validationErrors.push(err);
|
|
205
213
|
}
|
|
214
|
+
buildKeyObject(item, pkField, skField) {
|
|
215
|
+
if ((0, utils_2.isKeyOfObject)(item, pkField) && (0, utils_2.isKeyOfObject)(item, skField)) {
|
|
216
|
+
return {
|
|
217
|
+
[this.#partitionKeyField]: item[pkField],
|
|
218
|
+
[this.#sortKeyField]: item[skField]
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
throw new Error("Invalid keys");
|
|
222
|
+
}
|
|
206
223
|
}
|
|
207
224
|
exports.default = Delete;
|