electrodb 1.4.3 → 1.4.4

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/CHANGELOG.md CHANGED
@@ -88,4 +88,12 @@ All notable changes to this project will be documented in this file. Breaking ch
88
88
 
89
89
  ## [1.4.3] - 2021-10-03
90
90
  ### Fixed
91
- - ElectroDB would throw when an `undefined` property was passed to query. This has been changed to not throw if a partial query on that index can be accomplished with the data provided.
91
+ - ElectroDB would throw when an `undefined` property was passed to query. This has been changed to not throw if a partial query on that index can be accomplished with the data provided.
92
+
93
+ ## [1.4.4] - 2021-10-16
94
+ ### Added
95
+ - Updates did not include composite attributes involved in primary index. Though these values cannot be changed, they should be `set` on update method calls in case the update results in an item insert. [[read more]](./README.md#updates-to-composite-attributes)
96
+
97
+ ## [0.11.1] - 2021-10-17
98
+ ### Patched
99
+ - Updates did not include composite attributes involved in primary index. Though these values cannot be changed, they should be `set` on update method calls in case the update results in an item insert. [[read more]](./README.md#updates-to-composite-attributes)
package/README.md CHANGED
@@ -176,6 +176,7 @@ tasks
176
176
  + [Put Record](#put-record)
177
177
  + [Batch Write Put Records](#batch-write-put-records)
178
178
  + [Update Record](#update-record)
179
+ - [Updates to Composite Attributes](#updates-to-composite-attributes)
179
180
  - [Update Method: Set](#update-method-set)
180
181
  - [Update Method: Remove](#update-method-remove)
181
182
  - [Update Method: Add](#update-method-add)
@@ -3129,6 +3130,99 @@ Update Method | Attribute Types
3129
3130
  [delete](#update-method-delete) | `any` `set` | `object`
3130
3131
  [data](#update-method-data) | `*` | `callback`
3131
3132
 
3133
+ #### Updates to Composite Attributes
3134
+
3135
+ ElectroDB adds some constraints to update calls to prevent the accidental loss of data. If an access pattern is defined with multiple composite attributes, then ElectroDB ensure the attributes cannot be updated individually. If an attribute involved in an index composite is updated, then the index key also must be updated, and if the whole key cannot be formed by the attributes supplied to the update, then it cannot create a composite key without overwriting the old data.
3136
+
3137
+ This example shows why a partial update to a composite key is prevented by ElectroDB:
3138
+
3139
+ ```json
3140
+ {
3141
+ "index": "my-gsi",
3142
+ "pk": {
3143
+ "field": "gsi1pk",
3144
+ "composite": ["attr1"]
3145
+ },
3146
+ "sk": {
3147
+ "field": "gsi1sk",
3148
+ "composite": ["attr2", "attr3"]
3149
+ }
3150
+ }
3151
+ ```
3152
+
3153
+ The above secondary index definition would generate the following index keys:
3154
+
3155
+ ```json
3156
+ {
3157
+ "gsi1pk": "$service#attr1_value1",
3158
+ "gsi1sk": "$entity_version#attr2_value2#attr3_value6"
3159
+ }
3160
+ ```
3161
+
3162
+ If a user attempts to update the attribute `attr2`, then ElectroDB has no way of knowing value of the attribute `attr3` or if forming the composite key without it would overwrite its value. The same problem exists if a user were to update `attr3`, ElectroDB cannot update the key without knowing each composite attribute's value.
3163
+
3164
+ In the event that a secondary index includes composite values from the table's primary index, ElectroDB will draw from the values supplied for the update key to address index gaps in the secondary index. For example:
3165
+
3166
+ For the defined indexes:
3167
+
3168
+ ```json
3169
+ {
3170
+ "accessPattern1": {
3171
+ "pk": {
3172
+ "field": "pk",
3173
+ "composite": ["attr1"]
3174
+ },
3175
+ "sk": {
3176
+ "field": "sk",
3177
+ "composite": ["attr2"]
3178
+ }
3179
+ },
3180
+ "accessPattern2": {
3181
+ "index": "my-gsi",
3182
+ "pk": {
3183
+ "field": "gsi1pk",
3184
+ "composite": ["attr3"]
3185
+ },
3186
+ "sk": {
3187
+ "field": "gsi1sk",
3188
+ "composite": ["attr2", "attr4"]
3189
+ }
3190
+ }
3191
+ }
3192
+ ```
3193
+
3194
+ A user could update `attr4` alone because ElectroDB is able to leverage the value for `attr2` from values supplied to the `update()` method:
3195
+
3196
+ ```typescript
3197
+ entity.update({ attr1: "value1", attr2: "value2" })
3198
+ .set({ attr4: "value4" })
3199
+ .go();
3200
+
3201
+ {
3202
+ "UpdateExpression": "SET #attr4 = :attr4_u0, #gsi1sk = :gsi1sk_u0, #attr1 = :attr1_u0, #attr2 = :attr2_u0",
3203
+ "ExpressionAttributeNames": {
3204
+ "#attr4": "attr4",
3205
+ "#gsi1sk": "gsi1sk",
3206
+ "#attr1": "attr1",
3207
+ "#attr2": "attr2"
3208
+ },
3209
+ "ExpressionAttributeValues": {
3210
+ ":attr4_u0": "value6",
3211
+ // This index was successfully built
3212
+ ":gsi1sk_u0": "$update-edgecases_1#attr2_value2#attr4_value6",
3213
+ ":attr1_u0": "value1",
3214
+ ":attr2_u0": "value2"
3215
+ },
3216
+ "TableName": "test_table",
3217
+ "Key": {
3218
+ "pk": "$service#attr1_value1",
3219
+ "sk": "$entity_version#attr2_value2"
3220
+ }
3221
+ }
3222
+ ```
3223
+
3224
+ > Note: Included in the update are all attributes from the table's primary index. These values are automatically included on all updates in the event an update results in an insert.
3225
+
3132
3226
  #### Update Method: Set
3133
3227
 
3134
3228
  The `set()` method will accept all attributes defined on the model. Provide a value to apply or replace onto the item.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "description": "A library to more easily create and interact with multiple entities and heretical relationships in dynamodb",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/entity.js CHANGED
@@ -1003,6 +1003,7 @@ class Entity {
1003
1003
  }
1004
1004
 
1005
1005
  _makeUpdateParams(update = {}, pk = {}, sk = {}) {
1006
+ let primaryIndexAttributes = {...pk, ...sk};
1006
1007
  let modifiedAttributeValues = {};
1007
1008
  let modifiedAttributeNames = {};
1008
1009
  for (const path of Object.keys(update.paths)) {
@@ -1038,6 +1039,7 @@ class Entity {
1038
1039
  const wasNotAlreadyModified = modifiedAttributeNames[indexKey] === undefined;
1039
1040
  if (isNotTablePK && isNotTableSK && wasNotAlreadyModified) {
1040
1041
  update.set(indexKey, updatedKeys[indexKey]);
1042
+
1041
1043
  }
1042
1044
  }
1043
1045
 
@@ -1050,6 +1052,24 @@ class Entity {
1050
1052
  }
1051
1053
  }
1052
1054
 
1055
+ // This loop adds the composite attributes to the Primary Index. This is important
1056
+ // in the case an update results in an "upsert". We want to add the Primary Index
1057
+ // composite attributes to the update so they will be included on the item when it
1058
+ // is created. It is done after all of the above because it is not a true "update"
1059
+ // so it should not be subject to the above "rules".
1060
+ for (const primaryIndexAttribute of Object.keys(primaryIndexAttributes)) {
1061
+ // isNotTablePK and isNotTableSK is important to check in case these properties
1062
+ // are not also the name of the index (you cannot modify the PK or SK of an item
1063
+ // after its creation)
1064
+ const attribute = this.model.schema.attributes[primaryIndexAttribute];
1065
+ const isNotTablePK = !!(attribute && attribute.field !== this.model.indexes[accessPattern].pk.field);
1066
+ const isNotTableSK = !!(attribute && attribute.field !== this.model.indexes[accessPattern].sk.field);
1067
+ const wasNotAlreadyModified = modifiedAttributeNames[primaryIndexAttribute] === undefined;
1068
+ if (isNotTablePK && isNotTableSK && wasNotAlreadyModified) {
1069
+ update.set(primaryIndexAttribute, primaryIndexAttributes[primaryIndexAttribute]);
1070
+ }
1071
+ }
1072
+
1053
1073
  return {
1054
1074
  UpdateExpression: update.build(),
1055
1075
  ExpressionAttributeNames: update.getNames(),
@@ -1429,7 +1449,7 @@ class Entity {
1429
1449
  { ...keyAttributes },
1430
1450
  );
1431
1451
  const removedKeyImpact = this._expectIndexFacets(
1432
- {...removed},
1452
+ { ...removed },
1433
1453
  {...keyAttributes}
1434
1454
  )
1435
1455