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 +13 -1
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/entity.js +43 -16
- package/src/errors.js +6 -0
- package/src/service.js +8 -0
- package/src/util.js +2 -2
- package/src/validations.js +0 -2
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
|
-
> *
|
|
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
|
-
>
|
|
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
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 =>
|
|
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) {
|
package/src/validations.js
CHANGED
|
@@ -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
|
},
|