electrodb 1.8.1 → 1.8.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
@@ -170,4 +170,16 @@ All notable changes to this project will be documented in this file. Breaking ch
170
170
 
171
171
  ## [1.8.1] - 2022-03-29
172
172
  ### Fixed
173
- - Solidifying default application methodology: default values for nested properties will be applied up until an undefined default occurs or default callback returns undefined
173
+ - Solidifying default application methodology: default values for nested properties will be applied up until an undefined default occurs or default callback returns undefined
174
+
175
+ ## [1.8.2] - 2022-05-13
176
+ ### Fixed
177
+ - Issue impacting the successful propagation loggers and listeners from a Service definition to Entity children
178
+
179
+ ## [1.8.3] - 2022-05-14
180
+ ### Changed
181
+ - Removing validation that requires at least one attribute to be provided in a PK composite. This opens the door to static PKs if the user so chooses
182
+
183
+ ## [1.8.4] - 2022-05-18
184
+ ### Changed
185
+ - Removing validation that an attribute used for one index cannot be used by another. ElectroDB will now simply validate that all composite attributes associated with an indexed field are identical, and that a field is not used as both a PK and SK in separate indexes. This change allows for LSIs to be used with ElectroDB
package/README.md CHANGED
@@ -1071,7 +1071,7 @@ When using ElectroDB, indexes are referenced by their `AccessPatternName`. This
1071
1071
 
1072
1072
  All DynamoDB table start with at least a PartitionKey with an optional SortKey, this can be referred to as the _"Table Index"_. The `indexes` object requires at least the definition of this _Table Index_ **Partition Key** and (if applicable) **Sort Key**.
1073
1073
 
1074
- In your model, the _Table Index_ this is expressed as an _Access Pattern_ *without* an `index` property. For Secondary Indexes, use the `index` property to define the name of the index as defined on your DynamoDB table.
1074
+ In your model, the _Table Index_ this is expressed as an _Access Pattern_ *without* an `index` property. For Secondary Indexes (both GSIs and LSIs), use the `index` property to define the name of the index as defined on your DynamoDB table.
1075
1075
 
1076
1076
  Within these _AccessPatterns_, you define the PartitionKey and (optionally) SortKeys that are present on your DynamoDB table and map the key's name on the table with the `field` property.
1077
1077
 
@@ -2739,7 +2739,7 @@ const MallStore = new Entity(schema, {table: "StoreDirectory"});
2739
2739
  #### Partition Key Composite Attributes
2740
2740
  All queries require (*at minimum*) the **Composite Attributes** included in its defined **Partition Key**. **Composite Attributes** you define on the **Sort Key** can be partially supplied, but must be supplied in the order they are defined.
2741
2741
 
2742
- > *Important: Composite Attributes must be supplied in the order they are composed when invoking the **Access Pattern***. This is because composite attributes are used to form a concatenated key string, and if attributes supplied out of order, it is not possible to fill the gaps in that concatenation.
2742
+ > *IMPORTANT: Composite Attributes must be supplied in the order they are composed when invoking the **Access Pattern***. This is because composite attributes are used to form a concatenated key string, and if attributes supplied out of order, it is not possible to fill the gaps in that concatenation.
2743
2743
 
2744
2744
  ```javascript
2745
2745
  const MallStore = new Entity({
@@ -3245,7 +3245,7 @@ entity.update({ attr1: "value1", attr2: "value2" })
3245
3245
  }
3246
3246
  ```
3247
3247
 
3248
- > 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.
3248
+ > _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.__
3249
3249
 
3250
3250
  #### Update Method: Set
3251
3251
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "1.8.1",
3
+ "version": "1.8.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
@@ -899,6 +899,18 @@ class Entity {
899
899
  return {parameters, config};
900
900
  }
901
901
 
902
+ addListeners(logger) {
903
+ this.eventManager.add(logger);
904
+ }
905
+
906
+ _addLogger(logger) {
907
+ if (validations.isFunction(logger)) {
908
+ this.addListeners(logger);
909
+ } else {
910
+ throw new e.ElectroError(e.ErrorCodes.InvalidLoggerProvided, `Logger must be of type function`);
911
+ }
912
+ }
913
+
902
914
  _getPrimaryIndexFieldNames() {
903
915
  let hasSortKey = this.model.lookup.indexHasSortKeys[TableIndex];
904
916
  let accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[TableIndex];
@@ -2245,22 +2257,6 @@ class Entity {
2245
2257
  facets.fields.push(sk.field);
2246
2258
  }
2247
2259
 
2248
- if (seenIndexFields[pk.field] !== undefined) {
2249
- throw new e.ElectroError(e.ErrorCodes.DuplicateIndexFields, `Partition Key (pk) on Access Pattern '${accessPattern}' references the field '${pk.field}' which is already referenced by the Access Pattern '${seenIndexFields[pk.field]}'. Fields used for indexes need to be unique to avoid conflicts.`);
2250
- } else {
2251
- seenIndexFields[pk.field] = accessPattern;
2252
- }
2253
-
2254
- if (sk.field) {
2255
- if (sk.field === pk.field) {
2256
- throw new e.ElectroError(e.ErrorCodes.DuplicateIndexFields, `The Access Pattern '${accessPattern}' references the field '${sk.field}' as the field name for both the PK and SK. Fields used for indexes need to be unique to avoid conflicts.`);
2257
- } else if (seenIndexFields[sk.field] !== undefined) {
2258
- throw new e.ElectroError(e.ErrorCodes.DuplicateIndexFields, `Sort Key (sk) on Access Pattern '${accessPattern}' references the field '${sk.field}' which is already referenced by the Access Pattern '${seenIndexFields[sk.field]}'. Fields used for indexes need to be unique to avoid conflicts.`);
2259
- }else {
2260
- seenIndexFields[sk.field] = accessPattern;
2261
- }
2262
- }
2263
-
2264
2260
  if (Array.isArray(sk.facets)) {
2265
2261
  let duplicates = pk.facets.filter(facet => sk.facets.includes(facet));
2266
2262
  if (duplicates.length !== 0) {
@@ -2350,6 +2346,37 @@ class Entity {
2350
2346
  facets.byField[sk.field][indexName] = sk;
2351
2347
  }
2352
2348
 
2349
+ if (seenIndexFields[pk.field] !== undefined) {
2350
+ const definition = Object.values(facets.byField[pk.field]).find(definition => definition.index !== indexName)
2351
+ const definitionsMatch = validations.stringArrayMatch(pk.facets, definition.facets);
2352
+ if (!definitionsMatch) {
2353
+ throw new e.ElectroError(e.ErrorCodes.InconsistentIndexDefinition, `Partition Key (pk) on Access Pattern '${u.formatIndexNameForDisplay(accessPattern)}' is defined with the composite attribute(s) ${u.commaSeparatedString(pk.facets)}, but the accessPattern '${u.formatIndexNameForDisplay(definition.index)}' defines this field with the composite attributes ${u.commaSeparatedString(definition.facets)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`);
2354
+ }
2355
+ seenIndexFields[pk.field].push({accessPattern, type: 'pk'});
2356
+ } else {
2357
+ seenIndexFields[pk.field] = [];
2358
+ seenIndexFields[pk.field].push({accessPattern, type: 'pk'});
2359
+ }
2360
+
2361
+ if (sk.field) {
2362
+ if (sk.field === pk.field) {
2363
+ throw new e.ElectroError(e.ErrorCodes.DuplicateIndexFields, `The Access Pattern '${u.formatIndexNameForDisplay(accessPattern)}' references the field '${sk.field}' as the field name for both the PK and SK. Fields used for indexes need to be unique to avoid conflicts.`);
2364
+ } else if (seenIndexFields[sk.field] !== undefined) {
2365
+ const isAlsoDefinedAsPK = seenIndexFields[sk.field].find(field => field.type === "pk");
2366
+ if (isAlsoDefinedAsPK) {
2367
+ throw new e.ElectroError(e.ErrorCodes.InconsistentIndexDefinition, `The Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(accessPattern)}' references the field '${pk.field}' which is already referenced by the Access Pattern(s) '${u.formatIndexNameForDisplay(isAlsoDefinedAsPK.accessPattern)}' as a Partition Key. Fields mapped to Partition Keys cannot be also mapped to Sort Keys.`);
2368
+ }
2369
+ const definition = Object.values(facets.byField[sk.field]).find(definition => definition.index !== indexName)
2370
+ const definitionsMatch = validations.stringArrayMatch(sk.facets, definition.facets);
2371
+ if (!definitionsMatch) {
2372
+ throw new e.ElectroError(e.ErrorCodes.DuplicateIndexFields, `Sort Key (sk) on Access Pattern '${u.formatIndexNameForDisplay(accessPattern)}' is defined with the composite attribute(s) ${u.commaSeparatedString(sk.facets)}, but the accessPattern '${u.formatIndexNameForDisplay(definition.index)}' defines this field with the composite attributes ${u.commaSeparatedString(definition.facets)}'. Key fields must have the same composite attribute definitions across all indexes they are involved with`);
2373
+ }
2374
+ seenIndexFields[sk.field].push({accessPattern, type: 'sk'});
2375
+ } else {
2376
+ seenIndexFields[sk.field] = [];
2377
+ seenIndexFields[sk.field].push({accessPattern, type: 'sk'});
2378
+ }
2379
+ }
2353
2380
 
2354
2381
  attributes.forEach(({index, type, name}, j) => {
2355
2382
  let next = attributes[j + 1] !== undefined ? attributes[j + 1].name : "";
package/src/errors.js CHANGED
@@ -139,6 +139,12 @@ const ErrorCodes = {
139
139
  name: "InvalidClientProvided",
140
140
  sym: ErrorCode,
141
141
  },
142
+ InconsistentIndexDefinition: {
143
+ code: 1022,
144
+ section: "inconsistent-index-definition",
145
+ name: "InvalidClientProvided",
146
+ sym: ErrorCode,
147
+ },
142
148
  MissingAttribute: {
143
149
  code: 2001,
144
150
  section: "missing-attribute",
package/src/service.js CHANGED
@@ -191,6 +191,14 @@ class Service {
191
191
  entity._setClient(options.client);
192
192
  }
193
193
 
194
+ if (options.logger) {
195
+ entity._addLogger(options.logger);
196
+ }
197
+
198
+ if (options.listeners) {
199
+ entity.addListeners(options.listeners);
200
+ }
201
+
194
202
  if (this._modelVersion === ModelVersions.beta && this.service.version) {
195
203
  entity.model.version = this.service.version;
196
204
  }
package/src/util.js CHANGED
@@ -75,8 +75,8 @@ function batchItems(arr = [], size) {
75
75
  return batched;
76
76
  }
77
77
 
78
- function commaSeparatedString(array = []) {
79
- return array.map(value => `"${value}"`).join(", ");
78
+ function commaSeparatedString(array = [], prefix = '"', postfix = '"') {
79
+ return array.map(value => `${prefix}${value}${postfix}`).join(", ");
80
80
  }
81
81
 
82
82
  function formatStringCasing(str, casing, defaultCase) {
@@ -81,7 +81,6 @@ const Index = {
81
81
  },
82
82
  facets: {
83
83
  type: ["array", "string"],
84
- minItems: 1,
85
84
  items: {
86
85
  type: "string",
87
86
  },
@@ -89,7 +88,6 @@ const Index = {
89
88
  },
90
89
  composite: {
91
90
  type: ["array"],
92
- minItems: 1,
93
91
  items: {
94
92
  type: "string",
95
93
  },