electrodb 2.8.1 → 2.9.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/index.d.ts CHANGED
@@ -810,6 +810,7 @@ export interface SetRecordActionOptionsTransaction<A extends string, F extends s
810
810
  append: SetRecordTransaction<A,F,C,S, AppendItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
811
811
  delete: SetRecordTransaction<A,F,C,S, DeleteItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
812
812
  data: DataUpdateMethodRecordTransaction<A,F,C,S, Item<A,F,C,S,S["attributes"]>,IndexCompositeAttributes,TableItem>;
813
+ composite: UpdateComposite<A,F,C,S, SetRecordActionOptionsTransaction<A,F,C,S,SetAttr,IndexCompositeAttributes,TableItem>>;
813
814
  where: WhereClause<A,F,C,S, Item<A,F,C,S,S["attributes"]>,SetRecordActionOptionsTransaction<A,F,C,S,SetAttr,IndexCompositeAttributes,TableItem>>;
814
815
  }
815
816
 
@@ -860,6 +861,7 @@ export interface SetRecordActionOptions<A extends string, F extends string, C ex
860
861
  subtract: SetRecord<A,F,C,S, SubtractItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
861
862
  append: SetRecord<A,F,C,S, AppendItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
862
863
  delete: SetRecord<A,F,C,S, DeleteItem<A,F,C,S>,IndexCompositeAttributes,TableItem>;
864
+ composite: UpdateComposite<A,F,C,S, SetRecordActionOptions<A,F,C,S,SetAttr,IndexCompositeAttributes,TableItem>>;
863
865
  data: DataUpdateMethodRecord<A,F,C,S, Item<A,F,C,S,S["attributes"]>,IndexCompositeAttributes,TableItem>;
864
866
  where: WhereClause<A,F,C,S, Item<A,F,C,S,S["attributes"]>,SetRecordActionOptions<A,F,C,S,SetAttr,IndexCompositeAttributes,TableItem>>;
865
867
  }
@@ -2171,6 +2173,12 @@ export type UpdatableItemAttribute<A extends Attribute> =
2171
2173
  : never
2172
2174
  : never
2173
2175
 
2176
+ type UpdateComposite<A extends string, F extends string, C extends string, S extends Schema<A,F,C>, Response> = (
2177
+ compositeAttributes: Partial<{
2178
+ [I in keyof S["indexes"] as I extends infer Name ? Name extends TableIndexName<A,F,C,S> ? never : Name : never]: IndexPKAttributes<A, F, C, S, I> & IndexSKAttributes<A, F, C, S, I>;
2179
+ }[keyof S["indexes"] extends infer KeyName ? KeyName extends TableIndexName<A,F,C,S> ? never : KeyName : never]>
2180
+ ) => Response;
2181
+
2174
2182
  export type RemovableItemAttribute<A extends Attribute> =
2175
2183
  A['type'] extends OpaquePrimitiveTypeName<infer T>
2176
2184
  ? T
@@ -2541,6 +2549,7 @@ export class Entity<A extends string, F extends string, C extends string, S exte
2541
2549
  append: SetRecord<A,F,C,S, AppendItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, Partial<ResponseItem<A,F,C,S>>>;
2542
2550
  delete: SetRecord<A,F,C,S, DeleteItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, Partial<ResponseItem<A,F,C,S>>>;
2543
2551
  data: DataUpdateMethodRecord<A,F,C,S, Item<A,F,C,S,S["attributes"]>, TableIndexCompositeAttributes<A,F,C,S>, Partial<ResponseItem<A,F,C,S>>>;
2552
+ composite: UpdateComposite<A,F,C,S, SetRecordActionOptions<A,F,C,S, SetItem<A,F,C,S>,TableIndexCompositeAttributes<A,F,C,S>, Partial<ResponseItem<A,F,C,S>>>>;
2544
2553
  };
2545
2554
  patch(key: AllTableIndexCompositeAttributes<A,F,C,S>): {
2546
2555
  set: SetRecord<A,F,C,S, SetItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
@@ -2550,6 +2559,7 @@ export class Entity<A extends string, F extends string, C extends string, S exte
2550
2559
  append: SetRecord<A,F,C,S, AppendItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2551
2560
  delete: SetRecord<A,F,C,S, DeleteItem<A,F,C,S>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2552
2561
  data: DataUpdateMethodRecord<A,F,C,S, Item<A,F,C,S,S["attributes"]>, TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>;
2562
+ composite: UpdateComposite<A,F,C,S, SetRecordActionOptions<A,F,C,S, SetItem<A,F,C,S>,TableIndexCompositeAttributes<A,F,C,S>, ResponseItem<A,F,C,S>>>;
2553
2563
  };
2554
2564
 
2555
2565
  find(record: Partial<Item<A,F,C,S,S["attributes"]>>): RecordsActionOptions<A,F,C,S, ResponseItem<A,F,C,S>[], AllTableIndexCompositeAttributes<A,F,C,S>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrodb",
3
- "version": "2.8.1",
3
+ "version": "2.9.0",
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": {
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "homepage": "https://github.com/tywalch/electrodb#readme",
33
33
  "devDependencies": {
34
- "@aws-sdk/client-dynamodb": "^3.54.1",
35
- "@aws-sdk/lib-dynamodb": "^3.54.1",
34
+ "@aws-sdk/client-dynamodb": "^3.395.0",
35
+ "@aws-sdk/lib-dynamodb": "^3.395.0",
36
36
  "@istanbuljs/nyc-config-typescript": "^1.0.2",
37
37
  "@types/chai": "^4.2.12",
38
38
  "@types/mocha": "^8.0.3",
@@ -50,8 +50,8 @@
50
50
  "nyc": "^15.1.0",
51
51
  "source-map-support": "^0.5.19",
52
52
  "ts-node": "^10.9.1",
53
- "tsd": "^0.21.0",
54
- "typescript": "^4.9.5",
53
+ "tsd": "^0.28.1",
54
+ "typescript": "^5.1.6",
55
55
  "uuid": "7.0.1"
56
56
  },
57
57
  "keywords": [
@@ -67,7 +67,6 @@
67
67
  "directory": "test"
68
68
  },
69
69
  "dependencies": {
70
- "@aws-sdk/lib-dynamodb": "^3.54.1",
71
70
  "jsonschema": "1.2.7"
72
71
  }
73
72
  }
package/src/clauses.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { QueryTypes, MethodTypes, ItemOperations, ExpressionTypes, TransactionCommitSymbol, TransactionOperations, TerminalOperation, KeyTypes, IndexTypes } = require("./types");
2
- const {AttributeOperationProxy, UpdateOperations, FilterOperationNames} = require("./operations");
2
+ const {AttributeOperationProxy, UpdateOperations, FilterOperationNames, UpdateOperationNames} = require("./operations");
3
3
  const {UpdateExpression} = require("./update");
4
4
  const {FilterExpression} = require("./where");
5
5
  const v = require("./validations");
@@ -366,7 +366,7 @@ let clauses = {
366
366
  return state;
367
367
  }
368
368
  },
369
- children: ["set", "append","updateRemove", "updateDelete", "add", "subtract", "data", "commit"],
369
+ children: ["set", "append","updateRemove", "updateDelete", "add", "subtract", "data", "composite", "commit"],
370
370
  },
371
371
  update: {
372
372
  name: "update",
@@ -393,7 +393,7 @@ let clauses = {
393
393
  return state;
394
394
  }
395
395
  },
396
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
396
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
397
397
  },
398
398
  data: {
399
399
  name: "data",
@@ -423,7 +423,7 @@ let clauses = {
423
423
  return state;
424
424
  }
425
425
  },
426
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
426
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
427
427
  },
428
428
  set: {
429
429
  name: "set",
@@ -440,7 +440,32 @@ let clauses = {
440
440
  return state;
441
441
  }
442
442
  },
443
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
443
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
444
+ },
445
+ composite: {
446
+ name: "composite",
447
+ action(entity, state, composites = {}) {
448
+ if (state.getError() !== null) {
449
+ return state;
450
+ }
451
+ try {
452
+ for (const attrName in composites) {
453
+ // todo: validate attrName is facet
454
+ if (entity.model.facets.byAttr[attrName]) {
455
+ const wasSet = state.query.update.addComposite(attrName, composites[attrName]);
456
+ if (!wasSet) {
457
+ throw new e.ElectroError(e.ErrorCodes.DuplicateUpdateCompositesProvided, `The composite attribute ${attrName} has been provided more than once with different values. Remove the duplication before running again`);
458
+ }
459
+ state.applyCondition(FilterOperationNames.eq, attrName, composites[attrName]);
460
+ }
461
+ }
462
+ return state;
463
+ } catch(err) {
464
+ state.setError(err);
465
+ return state;
466
+ }
467
+ },
468
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
444
469
  },
445
470
  append: {
446
471
  name: "append",
@@ -457,7 +482,7 @@ let clauses = {
457
482
  return state;
458
483
  }
459
484
  },
460
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
485
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
461
486
  },
462
487
  updateRemove: {
463
488
  name: "remove",
@@ -477,7 +502,7 @@ let clauses = {
477
502
  return state;
478
503
  }
479
504
  },
480
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
505
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
481
506
  },
482
507
  updateDelete: {
483
508
  name: "delete",
@@ -494,7 +519,7 @@ let clauses = {
494
519
  return state;
495
520
  }
496
521
  },
497
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
522
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
498
523
  },
499
524
  add: {
500
525
  name: "add",
@@ -511,7 +536,7 @@ let clauses = {
511
536
  return state;
512
537
  }
513
538
  },
514
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
539
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
515
540
  },
516
541
  subtract: {
517
542
  name: "subtract",
@@ -528,7 +553,7 @@ let clauses = {
528
553
  return state;
529
554
  }
530
555
  },
531
- children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit"],
556
+ children: ["data", "set", "append", "add", "updateRemove", "updateDelete", "go", "params", "subtract", "commit", "composite"],
532
557
  },
533
558
  query: {
534
559
  name: "query",
@@ -965,6 +990,17 @@ class ChainState {
965
990
  return this;
966
991
  }
967
992
 
993
+ applyCondition(operation, name, ...values) {
994
+ if (FilterOperationNames[operation] !== undefined && name !== undefined && values.length > 0) {
995
+ const attribute = this.attributes[name];
996
+ if (attribute !== undefined) {
997
+ const filter = this.query.filter[ExpressionTypes.ConditionExpression];
998
+ filter.unsafeSet(operation, attribute.field, ...values);
999
+ }
1000
+ }
1001
+ return this;
1002
+ }
1003
+
968
1004
  unsafeApplyFilter(operation, name, ...values) {
969
1005
  if (FilterOperationNames[operation] !== undefined & name !== undefined && values.length > 0) {
970
1006
  const filter = this.query.filter[ExpressionTypes.FilterExpression];
package/src/entity.js CHANGED
@@ -1884,14 +1884,26 @@ class Entity {
1884
1884
  // We need to remove the pk/sk facets from before applying the Attribute setters because these values didnt
1885
1885
  // change, and we also don't want to trigger the setters of any attributes watching these facets because that
1886
1886
  // should only happen when an attribute is changed.
1887
- const { indexKey, updatedKeys, deletedKeys = [] } = this._getUpdatedKeys(pk, sk, preparedUpdateValues, removed);
1887
+ const attributesAndComposites = {
1888
+ ...update.composites,
1889
+ ...preparedUpdateValues,
1890
+ };
1891
+ const { indexKey, updatedKeys, deletedKeys = [] } = this._getUpdatedKeys(pk, sk, attributesAndComposites, removed);
1888
1892
  const accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[TableIndex];
1889
-
1890
1893
  for (const path of Object.keys(preparedUpdateValues)) {
1891
1894
  if (modifiedAttributeNames[path] !== undefined && preparedUpdateValues[path] !== undefined) {
1892
1895
  update.updateValue(modifiedAttributeNames[path], preparedUpdateValues[path]);
1893
1896
  } else if (preparedUpdateValues[path] !== undefined) {
1894
- update.set(path, preparedUpdateValues[path]);
1897
+ const attr = this.model.schema.getAttribute(path);
1898
+ if (attr) {
1899
+ // attributes might enter into this flow because they were triggered via a `watch` event and were
1900
+ // not supplied directly by the user. In this case we should set the field name.
1901
+ // TODO: This will only work with root attributes and should be refactored for nested attributes.
1902
+ update.set(attr.field, preparedUpdateValues[path]);
1903
+ } else {
1904
+ // this could be fields added by electro that don't apeear in the schema
1905
+ update.set(path, preparedUpdateValues[path]);
1906
+ }
1895
1907
  }
1896
1908
  }
1897
1909
 
@@ -1901,7 +1913,6 @@ class Entity {
1901
1913
  const wasNotAlreadyModified = modifiedAttributeNames[indexKey] === undefined;
1902
1914
  if (isNotTablePK && isNotTableSK && wasNotAlreadyModified) {
1903
1915
  update.set(indexKey, updatedKeys[indexKey]);
1904
-
1905
1916
  }
1906
1917
  }
1907
1918
 
@@ -1948,7 +1959,6 @@ class Entity {
1948
1959
  _makePutParams({ data } = {}, pk, sk) {
1949
1960
  let { updatedKeys, setAttributes } = this._getPutKeys(pk, sk && sk.facets, data);
1950
1961
  let translatedFields = this.model.schema.translateToFields(setAttributes);
1951
-
1952
1962
  return {
1953
1963
  Item: {
1954
1964
  ...translatedFields,
@@ -2337,7 +2347,7 @@ class Entity {
2337
2347
  let incompleteAccessPatterns = incomplete.map(({index}) => this.model.translations.indexes.fromIndexToAccessPattern[index]);
2338
2348
  let missingFacets = incomplete.reduce((result, { missing }) => [...result, ...missing], []);
2339
2349
  throw new e.ElectroError(e.ErrorCodes.IncompleteCompositeAttributes,
2340
- `Incomplete composite attributes: Without the composite attributes ${u.commaSeparatedString(missingFacets)} the following access patterns cannot be updated: ${u.commaSeparatedString(incompleteAccessPatterns.filter((val) => val !== undefined))} `,
2350
+ `Incomplete composite attributes: Without the composite attributes ${u.commaSeparatedString(missingFacets)} the following access patterns cannot be updated: ${u.commaSeparatedString(incompleteAccessPatterns.filter((val) => val !== undefined))}. If a composite attribute is readOnly and cannot be set, use the 'composite' chain method on update to supply the value for key formatting purposes.`,
2341
2351
  );
2342
2352
  }
2343
2353
  return complete;
@@ -2406,8 +2416,10 @@ class Entity {
2406
2416
  indexKey[sk] = keys.sk[0];
2407
2417
  }
2408
2418
  }
2409
- updatedKeys[pk] = keys.pk;
2410
- if (sk) {
2419
+ if (keys.pk !== undefined && keys.pk !== '') {
2420
+ updatedKeys[pk] = keys.pk;
2421
+ }
2422
+ if (sk && keys.sk[0] !== undefined && keys.sk[0] !== '') {
2411
2423
  updatedKeys[sk] = keys.sk[0];
2412
2424
  }
2413
2425
  }
@@ -3298,7 +3310,9 @@ class Entity {
3298
3310
  if (Array.isArray(sk.facets)) {
3299
3311
  let duplicates = pk.facets.filter(facet => sk.facets.includes(facet));
3300
3312
  if (duplicates.length !== 0) {
3301
- throw new e.ElectroError(e.ErrorCodes.DuplicateIndexCompositeAttributes, `The Access Pattern '${accessPattern}' contains duplicate references the composite attribute(s): ${u.commaSeparatedString(duplicates)}. Composite attributes may only be used once within an index. If this leaves the Sort Key (sk) without any composite attributes simply set this to be an empty array.`);
3313
+ if (sk.facets.length > 1) {
3314
+ throw new e.ElectroError(e.ErrorCodes.DuplicateIndexCompositeAttributes, `The Access Pattern '${accessPattern}' contains duplicate references the composite attribute(s): ${u.commaSeparatedString(duplicates)}. Composite attributes can only be used more than once in an index if your sort key is limitted to a single attribute. This is to prevent unexpected runtime errors related to the inability to generate keys.`);
3315
+ }
3302
3316
  }
3303
3317
  }
3304
3318
 
package/src/errors.js CHANGED
@@ -205,6 +205,12 @@ const ErrorCodes = {
205
205
  name: "InvalidConversionCompositeProvided",
206
206
  sym: ErrorCode,
207
207
  },
208
+ DuplicateUpdateCompositesProvided: {
209
+ code: 2010,
210
+ section: "duplicate-update-composites-provided",
211
+ name: "DuplicateUpdateCompositesProvided",
212
+ sym: ErrorCode,
213
+ },
208
214
  InvalidAttribute: {
209
215
  code: 3001,
210
216
  section: "invalid-attribute",
package/src/operations.js CHANGED
@@ -552,4 +552,9 @@ const FilterOperationNames = Object.keys(FilterOperations).reduce((ops, name) =>
552
552
  return ops;
553
553
  }, {});
554
554
 
555
- module.exports = {UpdateOperations, FilterOperations, FilterOperationNames, ExpressionState, AttributeOperationProxy};
555
+ const UpdateOperationNames = Object.keys(UpdateOperations).reduce((ops, name) => {
556
+ ops[name] = name;
557
+ return ops;
558
+ }, {});
559
+
560
+ module.exports = {UpdateOperations, UpdateOperationNames, FilterOperations, FilterOperationNames, ExpressionState, AttributeOperationProxy};
package/src/schema.js CHANGED
@@ -1171,7 +1171,7 @@ class Schema {
1171
1171
 
1172
1172
  if (facets.byAttr && facets.byAttr[definition.name] !== undefined && (!ValidFacetTypes.includes(definition.type) && !Array.isArray(definition.type))) {
1173
1173
  let assignedIndexes = facets.byAttr[name].map(assigned => assigned.index === "" ? "Table Index" : assigned.index);
1174
- throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid composite attribute definition: Composite attributes must be one of the following: ${ValidFacetTypes.join(", ")}. The attribute "${name}" is defined as being type "${attribute.type}" but is a composite attribute of the the following indexes: ${assignedIndexes.join(", ")}`);
1174
+ throw new e.ElectroError(e.ErrorCodes.InvalidAttributeDefinition, `Invalid composite attribute definition: Composite attributes must be one of the following: ${ValidFacetTypes.join(", ")}. The attribute "${name}" is defined as being type "${attribute.type}" but is a composite attribute of the following indexes: ${assignedIndexes.join(", ")}`);
1175
1175
  }
1176
1176
 
1177
1177
  if (usedAttrs[definition.field] || usedAttrs[name]) {
@@ -1438,9 +1438,9 @@ class Schema {
1438
1438
 
1439
1439
  checkUpdate(payload = {}, { allowReadOnly } = {}) {
1440
1440
  let record = {};
1441
- for (let [path, attribute] of this.traverser.getAll()) {
1442
- let value = payload[path];
1443
- if (value === undefined) {
1441
+ for (let [path, value] of Object.entries(payload)) {
1442
+ let attribute = this.traverser.paths.get(path);
1443
+ if (attribute === undefined) {
1444
1444
  continue;
1445
1445
  }
1446
1446
  if (attribute.readOnly && !allowReadOnly) {
package/src/service.js CHANGED
@@ -518,6 +518,8 @@ class Service {
518
518
  }
519
519
 
520
520
  _validateCollectionDefinition(definition = {}, providedIndex = {}) {
521
+ let isCustomMatchPK = definition.pk.isCustom === providedIndex.pk.isCustom;
522
+ let isCustomMatchSK = !!(definition.sk && definition.sk.isCustom) === !!(providedIndex.sk && providedIndex.sk.isCustom);
521
523
  let indexMatch = definition.index === providedIndex.index;
522
524
  let pkFieldMatch = definition.pk.field === providedIndex.pk.field;
523
525
  let pkFacetLengthMatch = definition.pk.facets.length === providedIndex.pk.facets.length;
@@ -526,37 +528,74 @@ class Service {
526
528
  let definitionIndexName = u.formatIndexNameForDisplay(definition.index);
527
529
  let providedIndexName = u.formatIndexNameForDisplay(providedIndex.index);
528
530
  let matchingKeyCasing = this._validateIndexCasingMatch(definition, providedIndex);
529
- if (pkFacetLengthMatch) {
530
- for (let i = 0; i < definition.pk.labels.length; i++) {
531
- let definitionFacet = definition.pk.labels[i].name;
532
- let definitionLabel = definition.pk.labels[i].label;
533
- let providedFacet = providedIndex.pk.labels[i].name;
534
- let providedLabel = providedIndex.pk.labels[i].label;
535
- let noLabels = definition.pk.labels[i].label === definition.pk.labels[i].name && providedIndex.pk.labels[i].label === providedIndex.pk.labels[i].name;
536
- if (definitionLabel !== providedLabel) {
537
- mismatchedFacetLabels.push({
538
- definitionFacet,
539
- definitionLabel,
540
- providedFacet,
541
- providedLabel,
542
- type: noLabels ? "facet" : "label"
543
- });
544
- } else if (definitionFacet !== providedFacet) {
545
- mismatchedFacetLabels.push({
546
- definitionFacet,
547
- definitionLabel,
548
- providedFacet,
549
- providedLabel,
550
- type: "facet"
551
- });
531
+
532
+ for (let i = 0; i < Math.max(definition.pk.labels.length, providedIndex.pk.labels.length); i++) {
533
+ let definitionFacet = definition.pk.labels[i] && definition.pk.labels[i].name;
534
+ let definitionLabel = definition.pk.labels[i] && definition.pk.labels[i].label;
535
+ let providedFacet = providedIndex.pk.labels[i] && providedIndex.pk.labels[i].name;
536
+ let providedLabel = providedIndex.pk.labels[i] && providedIndex.pk.labels[i].label;
537
+ let noLabels = definitionLabel === definitionFacet && providedLabel === providedFacet;
538
+ if (definitionLabel !== providedLabel) {
539
+ mismatchedFacetLabels.push({
540
+ definitionFacet,
541
+ definitionLabel,
542
+ providedFacet,
543
+ providedLabel,
544
+ kind: "Partition",
545
+ type: noLabels ? "facet" : "label"
546
+ });
547
+ break;
548
+ } else if (definitionFacet !== providedFacet) {
549
+ mismatchedFacetLabels.push({
550
+ definitionFacet,
551
+ definitionLabel,
552
+ providedFacet,
553
+ providedLabel,
554
+ kind: "Partition",
555
+ type: "facet"
556
+ });
557
+ break;
558
+ }
559
+ }
560
+
561
+ if (!isCustomMatchPK) {
562
+ collectionDifferences.push(`The usage of key templates the partition key on index ${definitionIndexName} must be consistent across all Entities, some entities provided use template while others do not`);
563
+ }
564
+
565
+ if (!isCustomMatchSK) {
566
+ collectionDifferences.push(`The usage of key templates the sort key on index ${definitionIndexName} must be consistent across all Entities, some entities provided use template while others do not`);
567
+ }
568
+
569
+ if (definition.type === "clustered") {
570
+ for (let i = 0; i < Math.min(definition.sk.labels.length, providedIndex.sk.labels.length); i++) {
571
+ let definitionFacet = definition.sk.labels[i] && definition.sk.labels[i].name;
572
+ let definitionLabel = definition.sk.labels[i] && definition.sk.labels[i].label;
573
+ let providedFacet = providedIndex.sk.labels[i] && providedIndex.sk.labels[i].name;
574
+ let providedLabel = providedIndex.sk.labels[i] && providedIndex.sk.labels[i].label;
575
+ let noLabels = definitionLabel === definitionFacet && providedLabel === providedFacet;
576
+ if (definitionFacet === providedFacet) {
577
+ if (definitionLabel !== providedLabel) {
578
+ mismatchedFacetLabels.push({
579
+ definitionFacet,
580
+ definitionLabel,
581
+ providedFacet,
582
+ providedLabel,
583
+ kind: "Sort",
584
+ type: noLabels ? "facet" : "label"
585
+ });
586
+ }
587
+ } else {
588
+ break;
552
589
  }
553
590
  }
554
591
  }
592
+
555
593
  if (!matchingKeyCasing.pk) {
556
594
  collectionDifferences.push(
557
595
  `The pk property "casing" provided "${providedIndex.pk.casing || KeyCasing.default}" does not match established casing "${definition.pk.casing || KeyCasing.default}" on index "${providedIndexName}". Index casing options must match across all entities participating in a collection`
558
596
  );
559
597
  }
598
+
560
599
  if (!matchingKeyCasing.sk) {
561
600
  const definedSk = definition.sk || {};
562
601
  const providedSk = providedIndex.sk || {};
@@ -564,6 +603,7 @@ class Service {
564
603
  `The sk property "casing" provided "${definedSk.casing || KeyCasing.default}" does not match established casing "${providedSk.casing || KeyCasing.default}" on index "${providedIndexName}". Index casing options must match across all entities participating in a collection`
565
604
  );
566
605
  }
606
+
567
607
  if (!indexMatch) {
568
608
  collectionDifferences.push(
569
609
  `Collection defined on provided index "${providedIndexName}" does not match collection established index "${definitionIndexName}". Collections must be defined on the same index across all entities within a service.`,
@@ -573,6 +613,7 @@ class Service {
573
613
  `Partition Key composite attributes provided "${providedIndex.pk.field}" for index "${providedIndexName}" do not match established field "${definition.pk.field}" on established index "${definitionIndexName}"`,
574
614
  );
575
615
  }
616
+
576
617
  if (!pkFacetLengthMatch) {
577
618
  collectionDifferences.push(
578
619
  `Partition Key composite attributes provided [${providedIndex.pk.facets.map(val => `"${val}"`).join(", ")}] for index "${providedIndexName}" do not match established composite attributes [${definition.pk.facets.map(val => `"${val}"`).join(", ")}] on established index "${definitionIndexName}"`,
@@ -583,11 +624,11 @@ class Service {
583
624
  for (let mismatch of mismatchedFacetLabels) {
584
625
  if (mismatch.type === "facet") {
585
626
  collectionDifferences.push(
586
- `Partition Key composite attributes provided for index "${providedIndexName}" do not match established composite attribute "${mismatch.definitionFacet}" on established index "${definitionIndexName}": "${mismatch.definitionLabel}" != "${mismatch.providedLabel}"; Composite attribute definitions must match between all members of a collection to ensure key structures will resolve to identical Partition Keys. Please ensure these composite attribute definitions are identical for all entities associated with this service.`
627
+ `${mismatch.kind} Key composite attributes provided for index "${providedIndexName}" do not match established composite attribute "${mismatch.definitionFacet}" on established index "${definitionIndexName}": "${mismatch.definitionLabel}" != "${mismatch.providedLabel}"; Composite attribute definitions must match between all members of a collection to ensure key structures will resolve to identical Partition Keys. Please ensure these composite attribute definitions are identical for all entities associated with this service.`
587
628
  );
588
629
  } else {
589
630
  collectionDifferences.push(
590
- `Partition Key composite attributes provided for index "${providedIndexName}" contain conflicting composite attribute labels for established composite attribute "${mismatch.definitionFacet}" on established index "${definitionIndexName}". Established composite attribute "${mismatch.definitionFacet}" on established index "${definitionIndexName}" was defined with label "${mismatch.definitionLabel}" while provided composite attribute "${mismatch.providedFacet}" on provided index "${providedIndexName}" is defined with label "${mismatch.providedLabel}". Composite attribute labels definitions must match between all members of a collection to ensure key structures will resolve to identical Partition Keys. Please ensure these labels definitions are identical for all entities associated with this service.`
631
+ `${mismatch.kind} Key composite attributes provided for index "${providedIndexName}" contain conflicting composite attribute labels for established composite attribute "${mismatch.definitionFacet || ""}" on established index "${definitionIndexName}". Established composite attribute "${mismatch.definitionFacet || ""}" on established index "${definitionIndexName}" was defined with label "${mismatch.definitionLabel}" while provided composite attribute "${mismatch.providedFacet || ""}" on provided index "${providedIndexName}" is defined with label "${mismatch.providedLabel}". Composite attribute labels definitions must match between all members of a collection to ensure key structures will resolve to identical Partition Keys. Please ensure these labels definitions are identical for all entities associated with this service.`
591
632
  );
592
633
  }
593
634
 
@@ -644,7 +685,7 @@ class Service {
644
685
  }
645
686
  const [invalidDefinition, invalidIndexMessages] = this._validateCollectionDefinition(definition, providedIndex);
646
687
  if (invalidDefinition) {
647
- throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Validation Error while joining entity, "${name}". ${invalidIndexMessages.join(", ")}`);
688
+ throw new e.ElectroError(e.ErrorCodes.InvalidJoin, `Validation Error while joining entity, "${name}". ${invalidIndexMessages.join("; ")}`);
648
689
  }
649
690
  const sharedSortKeyAttributes = [];
650
691
  const sharedSortKeyCompositeAttributeLabels = [];
package/src/update.js CHANGED
@@ -11,9 +11,19 @@ class UpdateExpression extends ExpressionState {
11
11
  subtract: new Set(),
12
12
  delete: new Set(),
13
13
  };
14
+ this.composites = {};
14
15
  this.seen = new Map();
15
16
  this.type = BuilderTypes.update;
16
17
  }
18
+ addComposite(attrName, value) {
19
+ if (value !== undefined) {
20
+ if (this.composites[attrName] === undefined || this.composites[attrName] === value) {
21
+ this.composites[attrName] = value;
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
17
27
 
18
28
  add(type, expression) {
19
29
  this.operations[type].add(expression);
package/src/where.js CHANGED
@@ -47,7 +47,7 @@ class FilterExpression extends ExpressionState {
47
47
  unsafeSet(operation, name, ...values) {
48
48
  const {template} = FilterOperations[operation] || {};
49
49
  if (template === undefined) {
50
- throw new Error(`Invalid operation: "${operation}". Please report`);
50
+ throw new Error(`Invalid operation: "${operation}". Please report this issue via a bug ticket.`);
51
51
  }
52
52
  const names = this.setName({}, name, name);
53
53
  const valueExpressions = values.map(value => this.setValue(name, value));