electrodb 2.15.0 → 3.0.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/src/entity.js CHANGED
@@ -23,10 +23,12 @@ const {
23
23
  ResultOrderOption,
24
24
  ResultOrderParam,
25
25
  IndexTypes,
26
- PartialComparisons,
26
+ KeyAttributesComparisons,
27
27
  MethodTypeTranslation,
28
28
  TransactionCommitSymbol,
29
29
  CastKeyOptions,
30
+ ComparisonTypes,
31
+ DataOptions,
30
32
  } = require("./types");
31
33
  const { FilterFactory } = require("./filters");
32
34
  const { FilterOperations } = require("./operations");
@@ -40,9 +42,9 @@ const e = require("./errors");
40
42
  const v = require("./validations");
41
43
 
42
44
  const ImpactedIndexTypeSource = {
43
- composite: 'composite',
44
- provided: 'provided',
45
- }
45
+ composite: "composite",
46
+ provided: "provided",
47
+ };
46
48
 
47
49
  class Entity {
48
50
  constructor(model, config = {}) {
@@ -76,27 +78,6 @@ class Entity {
76
78
  );
77
79
 
78
80
  this.query = {};
79
- this.conversions = {
80
- fromComposite: {
81
- toKeys: (composite, options = {}) =>
82
- this._fromCompositeToKeys({ provided: composite }, options),
83
- toCursor: (composite) =>
84
- this._fromCompositeToCursor(
85
- { provided: composite },
86
- { strict: "all" },
87
- ),
88
- },
89
- fromKeys: {
90
- toCursor: (keys) => this._fromKeysToCursor({ provided: keys }, {}),
91
- toComposite: (keys) => this._fromKeysToComposite({ provided: keys }),
92
- },
93
- fromCursor: {
94
- toKeys: (cursor) => this._fromCursorToKeys({ provided: cursor }),
95
- toComposite: (cursor) =>
96
- this._fromCursorToComposite({ provided: cursor }),
97
- },
98
- byAccessPattern: {},
99
- };
100
81
  for (let accessPattern in this.model.indexes) {
101
82
  let index = this.model.indexes[accessPattern].index;
102
83
  this.query[accessPattern] = (...values) => {
@@ -111,42 +92,6 @@ class Entity {
111
92
  options,
112
93
  ).query(...values);
113
94
  };
114
-
115
- this.conversions.byAccessPattern[accessPattern] = {
116
- fromKeys: {
117
- toCursor: (keys) =>
118
- this._fromKeysToCursorByIndex({ indexName: index, provided: keys }),
119
- toComposite: (keys) =>
120
- this._fromKeysToCompositeByIndex({
121
- indexName: index,
122
- provided: keys,
123
- }),
124
- },
125
- fromCursor: {
126
- toKeys: (cursor) =>
127
- this._fromCursorToKeysByIndex({
128
- indexName: index,
129
- provided: cursor,
130
- }),
131
- toComposite: (cursor) =>
132
- this._fromCursorToCompositeByIndex({
133
- indexName: index,
134
- provided: cursor,
135
- }),
136
- },
137
- fromComposite: {
138
- toCursor: (composite) =>
139
- this._fromCompositeToCursorByIndex(
140
- { indexName: index, provided: composite },
141
- { strict: "all" },
142
- ),
143
- toKeys: (composite, options = {}) =>
144
- this._fromCompositeToKeysByIndex(
145
- { indexName: index, provided: composite },
146
- options,
147
- ),
148
- },
149
- };
150
95
  }
151
96
 
152
97
  this.config.identifiers = config.identifiers || {};
@@ -633,7 +578,7 @@ class Entity {
633
578
  _safeMinimum(...values) {
634
579
  let eligibleNumbers = [];
635
580
  for (let value of values) {
636
- if (typeof value === 'number') {
581
+ if (typeof value === "number") {
637
582
  eligibleNumbers.push(value);
638
583
  }
639
584
  }
@@ -733,16 +678,14 @@ class Entity {
733
678
  ExclusiveStartKey = undefined;
734
679
  }
735
680
  let pages = this._normalizePagesValue(config.pages);
736
- let max = this._normalizeLimitValue(config.limit);
737
681
  let iterations = 0;
738
682
  let count = 0;
739
683
  let hydratedUnprocessed = [];
740
684
  const shouldHydrate = config.hydrate && method === MethodTypes.query;
741
685
  do {
742
- let limit = max === undefined ? parameters.Limit : max - count;
743
686
  let response = await this._exec(
744
687
  method,
745
- { ExclusiveStartKey, ...parameters, Limit: limit },
688
+ { ExclusiveStartKey, ...parameters },
746
689
  config,
747
690
  );
748
691
 
@@ -750,17 +693,17 @@ class Entity {
750
693
 
751
694
  response = this.formatResponse(response, parameters.IndexName, {
752
695
  ...config,
753
- includeKeys: shouldHydrate || config.includeKeys,
696
+ data:
697
+ shouldHydrate &&
698
+ (!config.data || config.data === DataOptions.attributes)
699
+ ? "includeKeys"
700
+ : config.data,
754
701
  ignoreOwnership: shouldHydrate || config.ignoreOwnership,
755
702
  });
756
-
757
- if (config.raw) {
703
+ if (config.data === DataOptions.raw) {
758
704
  return response;
759
705
  } else if (config._isCollectionQuery) {
760
706
  for (const entity in response.data) {
761
- if (max) {
762
- count += response.data[entity].length;
763
- }
764
707
  let items = response.data[entity];
765
708
  if (shouldHydrate && items.length) {
766
709
  const hydrated = await config.hydrator(
@@ -778,8 +721,8 @@ class Entity {
778
721
  results[entity] = [...results[entity], ...items];
779
722
  }
780
723
  } else if (Array.isArray(response.data)) {
781
- let prevCount = count
782
- if (!!max || !!config.count) {
724
+ let prevCount = count;
725
+ if (config.count) {
783
726
  count += response.data.length;
784
727
  }
785
728
  let items = response.data;
@@ -801,7 +744,10 @@ class Entity {
801
744
  results = [...results, ...items];
802
745
  if (moreItemsThanRequired || count === config.count) {
803
746
  const lastItem = results[results.length - 1];
804
- ExclusiveStartKey = this._fromCompositeToKeysByIndex({ indexName, provided: lastItem });
747
+ ExclusiveStartKey = this._fromCompositeToKeysByIndex({
748
+ indexName,
749
+ provided: lastItem,
750
+ });
805
751
  break;
806
752
  }
807
753
  } else {
@@ -810,8 +756,9 @@ class Entity {
810
756
  iterations++;
811
757
  } while (
812
758
  ExclusiveStartKey &&
813
- (pages === AllPages || (config.count !== undefined || iterations < pages)) &&
814
- (max === undefined || count < max) &&
759
+ (pages === AllPages ||
760
+ config.count !== undefined ||
761
+ iterations < pages) &&
815
762
  (config.count === undefined || count < config.count)
816
763
  );
817
764
 
@@ -869,14 +816,13 @@ class Entity {
869
816
  }
870
817
 
871
818
  cleanseRetrievedData(item = {}, options = {}) {
872
- let { includeKeys } = options;
873
819
  let data = {};
874
820
  let names = this.model.schema.translationForRetrieval;
875
821
  for (let [attr, value] of Object.entries(item)) {
876
822
  let name = names[attr];
877
823
  if (name) {
878
824
  data[name] = value;
879
- } else if (includeKeys) {
825
+ } else if (options.data === DataOptions.includeKeys) {
880
826
  data[attr] = value;
881
827
  }
882
828
  }
@@ -963,14 +909,14 @@ class Entity {
963
909
  let results = {};
964
910
  if (validations.isFunction(config.parse)) {
965
911
  results = config.parse(config, response);
966
- } else if (config.raw && !config._isPagination) {
912
+ } else if (config.data === DataOptions.raw && !config._isPagination) {
967
913
  if (response.TableName) {
968
914
  results = {};
969
915
  } else {
970
916
  results = response;
971
917
  }
972
918
  } else if (
973
- config.raw &&
919
+ config.data === DataOptions.raw &&
974
920
  (config._isPagination || config.lastEvaluatedKeyRaw)
975
921
  ) {
976
922
  results = response;
@@ -1372,7 +1318,7 @@ class Entity {
1372
1318
 
1373
1319
  _formatReturnPager(config, lastEvaluatedKey) {
1374
1320
  let page = lastEvaluatedKey || null;
1375
- if (config.raw || config.pager === Pager.raw) {
1321
+ if (config.data === DataOptions.raw || config.pager === Pager.raw) {
1376
1322
  return page;
1377
1323
  }
1378
1324
  return config.formatCursor.serialize(page) || null;
@@ -1380,7 +1326,7 @@ class Entity {
1380
1326
 
1381
1327
  _formatExclusiveStartKey({ config, indexName = TableIndex }) {
1382
1328
  let exclusiveStartKey = config.cursor;
1383
- if (config.raw || config.pager === Pager.raw) {
1329
+ if (config.data === DataOptions.raw || config.pager === Pager.raw) {
1384
1330
  return (
1385
1331
  this._trimKeysToIndex({ provided: exclusiveStartKey, indexName }) ||
1386
1332
  null
@@ -1683,6 +1629,9 @@ class Entity {
1683
1629
  response: "default",
1684
1630
  cursor: null,
1685
1631
  data: "attributes",
1632
+ consistent: undefined,
1633
+ compare: ComparisonTypes.keys,
1634
+ complete: false,
1686
1635
  ignoreOwnership: false,
1687
1636
  _providedIgnoreOwnership: false,
1688
1637
  _isPagination: false,
@@ -1717,6 +1666,23 @@ class Entity {
1717
1666
  }
1718
1667
  }
1719
1668
 
1669
+ if (typeof option.compare === "string") {
1670
+ const type = ComparisonTypes[option.compare.toLowerCase()];
1671
+ if (type) {
1672
+ config.compare = type;
1673
+ if (type === ComparisonTypes.v2 && option.complete === undefined) {
1674
+ config.complete = true;
1675
+ }
1676
+ } else {
1677
+ throw new e.ElectroError(
1678
+ e.ErrorCodes.InvalidOptions,
1679
+ `Invalid value for query option "compare" provided. Valid options include ${u.commaSeparatedString(
1680
+ Object.keys(ComparisonTypes),
1681
+ )}, received: "${option.compare}"`,
1682
+ );
1683
+ }
1684
+ }
1685
+
1720
1686
  if (typeof option.response === "string" && option.response.length) {
1721
1687
  const format = ReturnValues[option.response];
1722
1688
  if (format === undefined) {
@@ -1728,13 +1694,14 @@ class Entity {
1728
1694
  Object.keys(ReturnValues),
1729
1695
  )}.`,
1730
1696
  );
1731
- }
1732
- config.response = format;
1733
- if (context.operation === MethodTypes.transactWrite) {
1734
- config.params.ReturnValuesOnConditionCheckFailure =
1735
- FormatToReturnValues[format];
1736
- } else {
1737
- config.params.ReturnValues = FormatToReturnValues[format];
1697
+ } else if (format !== ReturnValues.default) {
1698
+ config.response = format;
1699
+ if (context.operation === MethodTypes.transactWrite) {
1700
+ config.params.ReturnValuesOnConditionCheckFailure =
1701
+ FormatToReturnValues[format];
1702
+ } else {
1703
+ config.params.ReturnValues = FormatToReturnValues[format];
1704
+ }
1738
1705
  }
1739
1706
  }
1740
1707
 
@@ -1801,12 +1768,20 @@ class Entity {
1801
1768
  }
1802
1769
 
1803
1770
  if (option.data) {
1771
+ if (!DataOptions[option.data]) {
1772
+ throw new e.ElectroError(
1773
+ e.ErrorCodes.InvalidOptions,
1774
+ `Query option 'data' must be one of ${u.commaSeparatedString(
1775
+ Object.keys(DataOptions),
1776
+ )}.`,
1777
+ );
1778
+ }
1804
1779
  config.data = option.data;
1805
1780
  switch (option.data) {
1806
- case "raw":
1781
+ case DataOptions.raw:
1807
1782
  config.raw = true;
1808
1783
  break;
1809
- case "includeKeys":
1784
+ case DataOptions.includeKeys:
1810
1785
  config.includeKeys = true;
1811
1786
  break;
1812
1787
  }
@@ -1814,11 +1789,19 @@ class Entity {
1814
1789
 
1815
1790
  if (option.count !== undefined) {
1816
1791
  if (typeof option.count !== "number" || option.count < 1) {
1817
- throw new e.ElectroError(e.ErrorCodes.InvalidOptions, `Query option 'count' must be of type 'number' and greater than zero.`);
1792
+ throw new e.ElectroError(
1793
+ e.ErrorCodes.InvalidOptions,
1794
+ `Query option 'count' must be of type 'number' and greater than zero.`,
1795
+ );
1818
1796
  }
1819
1797
  config.count = option.count;
1820
1798
  }
1821
1799
 
1800
+ if (option.consistent === true) {
1801
+ config.consistent = true;
1802
+ config.params.ConsistentRead = true;
1803
+ }
1804
+
1822
1805
  if (option.limit !== undefined) {
1823
1806
  config.limit = option.limit;
1824
1807
  config.params.Limit = option.limit;
@@ -2064,7 +2047,7 @@ class Entity {
2064
2047
  return parameters;
2065
2048
  }
2066
2049
 
2067
- const requiresRawResponse = !!config.raw;
2050
+ const requiresRawResponse = config.data === DataOptions.raw;
2068
2051
  const enforcesOwnership = !config.ignoreOwnership;
2069
2052
  const requiresUserInvolvedPagination =
2070
2053
  TerminalOperation[config.terminalOperation] === TerminalOperation.page;
@@ -2258,7 +2241,7 @@ class Entity {
2258
2241
  let keys = this._makeParameterKey(indexBase, pk, ...sk);
2259
2242
  // trim empty key values (this can occur when keys are defined by users)
2260
2243
  for (let key in keys) {
2261
- if (keys[key] === undefined || keys[key] === '') {
2244
+ if (keys[key] === undefined || keys[key] === "") {
2262
2245
  delete keys[key];
2263
2246
  }
2264
2247
  }
@@ -2266,26 +2249,25 @@ class Entity {
2266
2249
  let keyExpressions = this._expressionAttributeBuilder(keys);
2267
2250
 
2268
2251
  const expressionAttributeNames = this._mergeExpressionsAttributes(
2269
- filter.getNames(),
2270
- keyExpressions.ExpressionAttributeNames,
2252
+ filter.getNames(),
2253
+ keyExpressions.ExpressionAttributeNames,
2271
2254
  );
2272
2255
 
2273
2256
  const expressionAttributeValues = this._mergeExpressionsAttributes(
2274
- filter.getValues(),
2275
- keyExpressions.ExpressionAttributeValues,
2257
+ filter.getValues(),
2258
+ keyExpressions.ExpressionAttributeValues,
2276
2259
  );
2277
2260
 
2278
-
2279
2261
  let params = {
2280
2262
  TableName: this.getTableName(),
2281
2263
  };
2282
2264
 
2283
2265
  if (Object.keys(expressionAttributeNames).length) {
2284
- params['ExpressionAttributeNames'] = expressionAttributeNames;
2266
+ params["ExpressionAttributeNames"] = expressionAttributeNames;
2285
2267
  }
2286
2268
 
2287
2269
  if (Object.keys(expressionAttributeValues).length) {
2288
- params['ExpressionAttributeValues'] = expressionAttributeValues;
2270
+ params["ExpressionAttributeValues"] = expressionAttributeValues;
2289
2271
  }
2290
2272
 
2291
2273
  let filterExpressions = [];
@@ -2306,7 +2288,7 @@ class Entity {
2306
2288
  }
2307
2289
 
2308
2290
  if (filterExpressions.length) {
2309
- params.FilterExpression = filterExpressions.join(' AND ');
2291
+ params.FilterExpression = filterExpressions.join(" AND ");
2310
2292
  }
2311
2293
 
2312
2294
  return params;
@@ -2365,7 +2347,13 @@ class Entity {
2365
2347
  indexKey,
2366
2348
  updatedKeys,
2367
2349
  deletedKeys = [],
2368
- } = this._getUpdatedKeys(pk, sk, attributesAndComposites, removed, update.composites);
2350
+ } = this._getUpdatedKeys(
2351
+ pk,
2352
+ sk,
2353
+ attributesAndComposites,
2354
+ removed,
2355
+ update.composites,
2356
+ );
2369
2357
  const accessPattern =
2370
2358
  this.model.translations.indexes.fromIndexToAccessPattern[TableIndex];
2371
2359
  for (const path of Object.keys(preparedUpdateValues)) {
@@ -2645,7 +2633,7 @@ class Entity {
2645
2633
  return expressions;
2646
2634
  }
2647
2635
 
2648
- _makeQueryKeys(state) {
2636
+ _makeQueryKeys(state, options) {
2649
2637
  let consolidatedQueryFacets = this._consolidateQueryFacets(
2650
2638
  state.query.keys.sk,
2651
2639
  );
@@ -2660,13 +2648,17 @@ class Entity {
2660
2648
  isCollection: state.query.options._isCollectionQuery,
2661
2649
  });
2662
2650
  default:
2663
- return this._makeIndexKeysWithoutTail(state, consolidatedQueryFacets);
2651
+ return this._makeIndexKeysWithoutTail(
2652
+ state,
2653
+ consolidatedQueryFacets,
2654
+ options,
2655
+ );
2664
2656
  }
2665
2657
  }
2666
2658
 
2667
2659
  /* istanbul ignore next */
2668
2660
  _queryParams(state = {}, options = {}) {
2669
- const indexKeys = this._makeQueryKeys(state);
2661
+ const indexKeys = this._makeQueryKeys(state, options);
2670
2662
  let parameters = {};
2671
2663
  switch (state.query.type) {
2672
2664
  case QueryTypes.is:
@@ -2707,6 +2699,7 @@ class Entity {
2707
2699
  break;
2708
2700
  case QueryTypes.between:
2709
2701
  parameters = this._makeBetweenQueryParams(
2702
+ state.query.options,
2710
2703
  state.query.index,
2711
2704
  state.query.filter[ExpressionTypes.FilterExpression],
2712
2705
  indexKeys.pk,
@@ -2722,6 +2715,8 @@ class Entity {
2722
2715
  state.query.type,
2723
2716
  state.query.filter[ExpressionTypes.FilterExpression],
2724
2717
  indexKeys,
2718
+ options,
2719
+ state.query.options,
2725
2720
  );
2726
2721
  break;
2727
2722
  default:
@@ -2739,31 +2734,50 @@ class Entity {
2739
2734
  });
2740
2735
  }
2741
2736
 
2742
- _makeBetweenQueryParams(index, filter, pk, ...sk) {
2737
+ _makeBetweenQueryParams(queryOptions, index, filter, pk, ...sk) {
2743
2738
  let keyExpressions = this._queryKeyExpressionAttributeBuilder(
2744
2739
  index,
2745
2740
  pk,
2746
2741
  ...sk,
2747
2742
  );
2743
+
2748
2744
  delete keyExpressions.ExpressionAttributeNames["#sk2"];
2745
+
2746
+ const customExpressions = {
2747
+ names: (queryOptions.expressions && queryOptions.expressions.names) || {},
2748
+ values:
2749
+ (queryOptions.expressions && queryOptions.expressions.values) || {},
2750
+ expression:
2751
+ (queryOptions.expressions && queryOptions.expressions.expression) || "",
2752
+ };
2753
+
2749
2754
  let params = {
2750
2755
  TableName: this.getTableName(),
2751
2756
  ExpressionAttributeNames: this._mergeExpressionsAttributes(
2752
2757
  filter.getNames(),
2753
2758
  keyExpressions.ExpressionAttributeNames,
2759
+ customExpressions.names,
2754
2760
  ),
2755
2761
  ExpressionAttributeValues: this._mergeExpressionsAttributes(
2756
2762
  filter.getValues(),
2757
2763
  keyExpressions.ExpressionAttributeValues,
2764
+ customExpressions.values,
2758
2765
  ),
2759
2766
  KeyConditionExpression: `#pk = :pk and #sk1 BETWEEN :sk1 AND :sk2`,
2760
2767
  };
2768
+
2761
2769
  if (index) {
2762
2770
  params["IndexName"] = index;
2763
2771
  }
2764
- if (filter.build()) {
2765
- params.FilterExpression = filter.build();
2772
+
2773
+ let expressions = [customExpressions.expression, filter.build()]
2774
+ .filter(Boolean)
2775
+ .join(" AND ");
2776
+
2777
+ if (expressions.length) {
2778
+ params.FilterExpression = expressions;
2766
2779
  }
2780
+
2767
2781
  return params;
2768
2782
  }
2769
2783
 
@@ -2881,28 +2895,52 @@ class Entity {
2881
2895
  return merged;
2882
2896
  }
2883
2897
 
2898
+ _getComparisonOperator(comparison, skType, comparisonType) {
2899
+ if (skType === "number") {
2900
+ return Comparisons[comparison];
2901
+ } else if (
2902
+ comparisonType === ComparisonTypes.attributes ||
2903
+ comparisonType === ComparisonTypes.v2
2904
+ ) {
2905
+ return KeyAttributesComparisons[comparison];
2906
+ } else {
2907
+ return Comparisons[comparison];
2908
+ }
2909
+ }
2910
+
2884
2911
  /* istanbul ignore next */
2885
2912
  _makeComparisonQueryParams(
2886
2913
  index = TableIndex,
2887
2914
  comparison = "",
2888
2915
  filter = {},
2889
2916
  indexKeys = {},
2917
+ options = {},
2918
+ queryOptions = {},
2890
2919
  ) {
2891
2920
  const { pk } = indexKeys;
2892
2921
  const sk = indexKeys.sk[0];
2893
2922
 
2894
- let operator =
2895
- typeof sk === "number"
2896
- ? Comparisons[comparison]
2897
- : PartialComparisons[comparison];
2898
-
2923
+ let operator = this._getComparisonOperator(
2924
+ comparison,
2925
+ typeof sk,
2926
+ options.compare,
2927
+ );
2899
2928
  if (!operator) {
2900
2929
  throw new Error(
2901
2930
  `Unexpected comparison operator "${comparison}", expected ${u.commaSeparatedString(
2902
- Object.values(PartialComparisons),
2931
+ Object.keys(KeyAttributesComparisons),
2903
2932
  )}`,
2904
2933
  );
2905
2934
  }
2935
+
2936
+ let customExpressions = {
2937
+ names: (queryOptions.expressions && queryOptions.expressions.names) || {},
2938
+ values:
2939
+ (queryOptions.expressions && queryOptions.expressions.values) || {},
2940
+ expression:
2941
+ (queryOptions.expressions && queryOptions.expressions.expression) || "",
2942
+ };
2943
+
2906
2944
  let keyExpressions = this._queryKeyExpressionAttributeBuilder(
2907
2945
  index,
2908
2946
  pk,
@@ -2914,27 +2952,40 @@ class Entity {
2914
2952
  ExpressionAttributeNames: this._mergeExpressionsAttributes(
2915
2953
  filter.getNames(),
2916
2954
  keyExpressions.ExpressionAttributeNames,
2955
+ customExpressions.names,
2917
2956
  ),
2918
2957
  ExpressionAttributeValues: this._mergeExpressionsAttributes(
2919
2958
  filter.getValues(),
2920
2959
  keyExpressions.ExpressionAttributeValues,
2960
+ customExpressions.values,
2921
2961
  ),
2922
2962
  KeyConditionExpression: `#pk = :pk and #sk1 ${operator} :sk1`,
2923
2963
  };
2964
+
2924
2965
  if (index) {
2925
2966
  params["IndexName"] = index;
2926
2967
  }
2927
- if (filter.build()) {
2928
- params.FilterExpression = filter.build();
2968
+
2969
+ let expressions = [customExpressions.expression, filter.build()]
2970
+ .filter(Boolean)
2971
+ .join(" AND ");
2972
+
2973
+ if (expressions.length) {
2974
+ params.FilterExpression = expressions;
2929
2975
  }
2976
+
2930
2977
  return params;
2931
2978
  }
2932
2979
 
2933
- _expectIndexFacets(attributes, facets, { utilizeIncludedOnlyIndexes, skipConditionCheck } = {}) {
2980
+ _expectIndexFacets(
2981
+ attributes,
2982
+ facets,
2983
+ { utilizeIncludedOnlyIndexes, skipConditionCheck } = {},
2984
+ ) {
2934
2985
  let [isIncomplete, { incomplete, complete }] = this._getIndexImpact(
2935
2986
  attributes,
2936
2987
  facets,
2937
- { utilizeIncludedOnlyIndexes, skipConditionCheck },
2988
+ { utilizeIncludedOnlyIndexes, skipConditionCheck },
2938
2989
  );
2939
2990
 
2940
2991
  if (isIncomplete) {
@@ -2963,7 +3014,8 @@ class Entity {
2963
3014
  _makeKeysFromAttributes(indexes, attributes, conditions) {
2964
3015
  let indexKeys = {};
2965
3016
  for (let [index, keyTypes] of Object.entries(indexes)) {
2966
- const shouldMakeKeys = !this._indexConditionIsDefined(index) || conditions[index];
3017
+ const shouldMakeKeys =
3018
+ !this._indexConditionIsDefined(index) || conditions[index];
2967
3019
  if (!shouldMakeKeys && index !== TableIndex) {
2968
3020
  continue;
2969
3021
  }
@@ -2994,7 +3046,10 @@ class Entity {
2994
3046
  _makePutKeysFromAttributes(indexes, attributes) {
2995
3047
  let indexKeys = {};
2996
3048
  for (let index of indexes) {
2997
- const shouldMakeKeys = this.model.indexes[this.model.translations.indexes.fromIndexToAccessPattern[index]].condition(attributes);
3049
+ const shouldMakeKeys =
3050
+ this.model.indexes[
3051
+ this.model.translations.indexes.fromIndexToAccessPattern[index]
3052
+ ].condition(attributes);
2998
3053
  if (!shouldMakeKeys) {
2999
3054
  continue;
3000
3055
  }
@@ -3019,11 +3074,15 @@ class Entity {
3019
3074
  );
3020
3075
 
3021
3076
  let deletedKeys = [];
3022
- for (const [indexName, condition] of Object.entries(completeFacets.conditions)) {
3077
+ for (const [indexName, condition] of Object.entries(
3078
+ completeFacets.conditions,
3079
+ )) {
3023
3080
  if (!condition) {
3024
3081
  deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.pk]);
3025
3082
  if (this.model.translations.keys[indexName][KeyTypes.sk]) {
3026
- deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.sk]);
3083
+ deletedKeys.push(
3084
+ this.model.translations.keys[indexName][KeyTypes.sk],
3085
+ );
3027
3086
  }
3028
3087
  }
3029
3088
  }
@@ -3071,7 +3130,7 @@ class Entity {
3071
3130
  const removedKeyImpact = this._expectIndexFacets(
3072
3131
  { ...removed },
3073
3132
  { ...keyAttributes },
3074
- { skipConditionCheck: true }
3133
+ { skipConditionCheck: true },
3075
3134
  );
3076
3135
 
3077
3136
  // complete facets, only includes impacted facets which likely does not include the updateIndex which then needs to be added here.
@@ -3091,11 +3150,15 @@ class Entity {
3091
3150
  let updatedKeys = {};
3092
3151
  let deletedKeys = [];
3093
3152
  let indexKey = {};
3094
- for (const [indexName, condition] of Object.entries(completeFacets.conditions)) {
3153
+ for (const [indexName, condition] of Object.entries(
3154
+ completeFacets.conditions,
3155
+ )) {
3095
3156
  if (!condition) {
3096
3157
  deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.pk]);
3097
3158
  if (this.model.translations.keys[indexName][KeyTypes.sk]) {
3098
- deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.sk]);
3159
+ deletedKeys.push(
3160
+ this.model.translations.keys[indexName][KeyTypes.sk],
3161
+ );
3099
3162
  }
3100
3163
  }
3101
3164
  }
@@ -3122,9 +3185,9 @@ class Entity {
3122
3185
  let hasPrefix =
3123
3186
  indexHasSk && this.model.prefixes[index].sk.prefix !== undefined;
3124
3187
  let hasPostfix =
3125
- indexHasSk && this.model.prefixes[index].sk.prefix !== undefined;
3188
+ indexHasSk && this.model.prefixes[index].sk.prefix !== undefined;
3126
3189
  if (noImpactSk && noAttributeSk) {
3127
- let key = hasPrefix ? this.model.prefixes[index].sk.prefix : '';
3190
+ let key = hasPrefix ? this.model.prefixes[index].sk.prefix : "";
3128
3191
  if (hasPostfix) {
3129
3192
  key = `${key}${this.model.prefixes[index].sk.postfix}`;
3130
3193
  }
@@ -3146,12 +3209,19 @@ class Entity {
3146
3209
  }
3147
3210
 
3148
3211
  _indexConditionIsDefined(index) {
3149
- const definition = this.model.indexes[this.model.translations.indexes.fromIndexToAccessPattern[index]];
3212
+ const definition =
3213
+ this.model.indexes[
3214
+ this.model.translations.indexes.fromIndexToAccessPattern[index]
3215
+ ];
3150
3216
  return definition && definition.conditionDefined;
3151
3217
  }
3152
3218
 
3153
3219
  /* istanbul ignore next */
3154
- _getIndexImpact(attributes = {}, included = {}, { utilizeIncludedOnlyIndexes, skipConditionCheck } = {}) {
3220
+ _getIndexImpact(
3221
+ attributes = {},
3222
+ included = {},
3223
+ { utilizeIncludedOnlyIndexes, skipConditionCheck } = {},
3224
+ ) {
3155
3225
  // beware: this entire algorithm stinks and needs to be completely refactored. It does redundant loops and fights
3156
3226
  // itself the whole way through. I am sorry.
3157
3227
  let includedFacets = Object.keys(included);
@@ -3166,21 +3236,26 @@ class Entity {
3166
3236
  facets[attribute] = attributes[attribute];
3167
3237
  indexes.forEach((definition) => {
3168
3238
  const { index, type } = definition;
3169
- impactedIndexes[index] = impactedIndexes[index] || {};
3170
- impactedIndexes[index][type] = impactedIndexes[index][type] || [];
3171
- impactedIndexes[index][type].push(attribute);
3172
- impactedIndexTypes[index] = impactedIndexTypes[index] || {};
3173
- impactedIndexTypes[index][type] = this.model.translations.keys[index][type];
3239
+ impactedIndexes[index] = impactedIndexes[index] || {};
3240
+ impactedIndexes[index][type] = impactedIndexes[index][type] || [];
3241
+ impactedIndexes[index][type].push(attribute);
3242
+ impactedIndexTypes[index] = impactedIndexTypes[index] || {};
3243
+ impactedIndexTypes[index][type] =
3244
+ this.model.translations.keys[index][type];
3174
3245
 
3175
- impactedIndexTypeSources[index] = impactedIndexTypeSources[index] || {};
3176
- impactedIndexTypeSources[index][type] = ImpactedIndexTypeSource.provided;
3246
+ impactedIndexTypeSources[index] =
3247
+ impactedIndexTypeSources[index] || {};
3248
+ impactedIndexTypeSources[index][type] =
3249
+ ImpactedIndexTypeSource.provided;
3177
3250
  });
3178
3251
  }
3179
3252
  }
3180
3253
 
3181
3254
  // this function is used to determine key impact for update `set`, update `delete`, and `put`. This block is currently only used by update `set`
3182
3255
  if (utilizeIncludedOnlyIndexes) {
3183
- for (const [index, { pk, sk }] of Object.entries(this.model.facets.byIndex)) {
3256
+ for (const [index, { pk, sk }] of Object.entries(
3257
+ this.model.facets.byIndex,
3258
+ )) {
3184
3259
  // The main table index is handled somewhere else (messy I know), and we only want to do this processing if an
3185
3260
  // index condition is defined for backwards compatibility. Backwards compatibility is not required for this
3186
3261
  // change, but I have paranoid concerns of breaking changes around sparse indexes.
@@ -3188,24 +3263,36 @@ class Entity {
3188
3263
  continue;
3189
3264
  }
3190
3265
 
3191
- if (pk && pk.length && pk.every(attr => included[attr] !== undefined)) {
3266
+ if (
3267
+ pk &&
3268
+ pk.length &&
3269
+ pk.every((attr) => included[attr] !== undefined)
3270
+ ) {
3192
3271
  pk.forEach((attr) => {
3193
3272
  facets[attr] = included[attr];
3194
3273
  });
3195
3274
  impactedIndexes[index] = impactedIndexes[index] || {};
3196
3275
  impactedIndexes[index][KeyTypes.pk] = [...pk];
3197
3276
  impactedIndexTypes[index] = impactedIndexTypes[index] || {};
3198
- impactedIndexTypes[index][KeyTypes.pk] = this.model.translations.keys[index][KeyTypes.pk];
3277
+ impactedIndexTypes[index][KeyTypes.pk] =
3278
+ this.model.translations.keys[index][KeyTypes.pk];
3199
3279
 
3200
3280
  // flagging the impactedIndexTypeSource as `composite` means the entire key is only being impacted because
3201
3281
  // all composites are in `included`. This will help us determine if we need to evaluate the `condition`
3202
3282
  // callback for the index. If both the `sk` and `pk` were impacted because of `included` then we can skip
3203
3283
  // the condition check because the index doesn't need to be recalculated;
3204
- impactedIndexTypeSources[index] = impactedIndexTypeSources[index] || {};
3205
- impactedIndexTypeSources[index][KeyTypes.pk] = impactedIndexTypeSources[index][KeyTypes.pk] || ImpactedIndexTypeSource.composite;
3284
+ impactedIndexTypeSources[index] =
3285
+ impactedIndexTypeSources[index] || {};
3286
+ impactedIndexTypeSources[index][KeyTypes.pk] =
3287
+ impactedIndexTypeSources[index][KeyTypes.pk] ||
3288
+ ImpactedIndexTypeSource.composite;
3206
3289
  }
3207
3290
 
3208
- if (sk && sk.length && sk.every(attr => included[attr] !== undefined)) {
3291
+ if (
3292
+ sk &&
3293
+ sk.length &&
3294
+ sk.every((attr) => included[attr] !== undefined)
3295
+ ) {
3209
3296
  if (this.model.translations.keys[index][KeyTypes.sk]) {
3210
3297
  sk.forEach((attr) => {
3211
3298
  facets[attr] = included[attr];
@@ -3213,61 +3300,66 @@ class Entity {
3213
3300
  impactedIndexes[index] = impactedIndexes[index] || {};
3214
3301
  impactedIndexes[index][KeyTypes.sk] = [...sk];
3215
3302
  impactedIndexTypes[index] = impactedIndexTypes[index] || {};
3216
- impactedIndexTypes[index][KeyTypes.sk] = this.model.translations.keys[index][KeyTypes.sk];
3303
+ impactedIndexTypes[index][KeyTypes.sk] =
3304
+ this.model.translations.keys[index][KeyTypes.sk];
3217
3305
 
3218
3306
  // flagging the impactedIndexTypeSource as `composite` means the entire key is only being impacted because
3219
3307
  // all composites are in `included`. This will help us determine if we need to evaluate the `condition`
3220
3308
  // callback for the index. If both the `sk` and `pk` were impacted because of `included` then we can skip
3221
3309
  // the condition check because the index doesn't need to be recalculated;
3222
- impactedIndexTypeSources[index] = impactedIndexTypeSources[index] || {};
3223
- impactedIndexTypeSources[index][KeyTypes.sk] = impactedIndexTypeSources[index][KeyTypes.sk] || ImpactedIndexTypeSource.composite;
3310
+ impactedIndexTypeSources[index] =
3311
+ impactedIndexTypeSources[index] || {};
3312
+ impactedIndexTypeSources[index][KeyTypes.sk] =
3313
+ impactedIndexTypeSources[index][KeyTypes.sk] ||
3314
+ ImpactedIndexTypeSource.composite;
3224
3315
  }
3225
3316
  }
3226
3317
  }
3227
3318
  }
3228
3319
 
3229
- let indexesWithMissingComposites = Object.entries(this.model.facets.byIndex)
3230
- .map(([index, definition]) => {
3231
- const { pk, sk } = definition;
3232
- let impacted = impactedIndexes[index];
3233
- let impact = {
3234
- index,
3235
- definition,
3236
- missing: []
3237
- };
3238
- if (impacted) {
3239
- let missingPk =
3240
- impacted[KeyTypes.pk] && impacted[KeyTypes.pk].length !== pk.length;
3241
- let missingSk =
3242
- impacted[KeyTypes.sk] && impacted[KeyTypes.sk].length !== sk.length;
3243
- if (missingPk) {
3244
- impact.missing = [
3245
- ...impact.missing,
3246
- ...pk.filter((attr) => {
3247
- return (
3248
- !impacted[KeyTypes.pk].includes(attr) &&
3249
- !includedFacets.includes(attr)
3250
- );
3251
- }),
3252
- ];
3253
- }
3254
- if (missingSk) {
3255
- impact.missing = [
3256
- ...impact.missing,
3257
- ...sk.filter(
3258
- (attr) =>
3259
- !impacted[KeyTypes.sk].includes(attr) &&
3260
- !includedFacets.includes(attr),
3261
- ),
3262
- ];
3263
- }
3264
- if (!missingPk && !missingSk) {
3265
- completedIndexes.push(index);
3266
- }
3320
+ let indexesWithMissingComposites = Object.entries(
3321
+ this.model.facets.byIndex,
3322
+ ).map(([index, definition]) => {
3323
+ const { pk, sk } = definition;
3324
+ let impacted = impactedIndexes[index];
3325
+ let impact = {
3326
+ index,
3327
+ definition,
3328
+ missing: [],
3329
+ };
3330
+ if (impacted) {
3331
+ let missingPk =
3332
+ impacted[KeyTypes.pk] && impacted[KeyTypes.pk].length !== pk.length;
3333
+ let missingSk =
3334
+ impacted[KeyTypes.sk] && impacted[KeyTypes.sk].length !== sk.length;
3335
+ if (missingPk) {
3336
+ impact.missing = [
3337
+ ...impact.missing,
3338
+ ...pk.filter((attr) => {
3339
+ return (
3340
+ !impacted[KeyTypes.pk].includes(attr) &&
3341
+ !includedFacets.includes(attr)
3342
+ );
3343
+ }),
3344
+ ];
3345
+ }
3346
+ if (missingSk) {
3347
+ impact.missing = [
3348
+ ...impact.missing,
3349
+ ...sk.filter(
3350
+ (attr) =>
3351
+ !impacted[KeyTypes.sk].includes(attr) &&
3352
+ !includedFacets.includes(attr),
3353
+ ),
3354
+ ];
3267
3355
  }
3356
+ if (!missingPk && !missingSk) {
3357
+ completedIndexes.push(index);
3358
+ }
3359
+ }
3268
3360
 
3269
- return impact;
3270
- });
3361
+ return impact;
3362
+ });
3271
3363
 
3272
3364
  let incomplete = [];
3273
3365
  for (const { index, missing, definition } of indexesWithMissingComposites) {
@@ -3277,28 +3369,53 @@ class Entity {
3277
3369
  // is meaningless and ElectroDB should uphold its obligation to keep keys and attributes in sync.
3278
3370
  // `index === TableIndex` is a special case where we don't need to check the condition because the main table is immutable
3279
3371
  // `!this._indexConditionIsDefined(index)` means the index doesn't have a condition defined, so we can skip the check
3280
- if (skipConditionCheck || index === TableIndex || !indexConditionIsDefined) {
3372
+ if (
3373
+ skipConditionCheck ||
3374
+ index === TableIndex ||
3375
+ !indexConditionIsDefined
3376
+ ) {
3281
3377
  incomplete.push({ index, missing });
3282
3378
  conditions[index] = true;
3283
3379
  continue;
3284
3380
  }
3285
3381
 
3286
- const memberAttributeIsImpacted = impactedIndexTypeSources[index] && (impactedIndexTypeSources[index][KeyTypes.pk] === ImpactedIndexTypeSource.provided || impactedIndexTypeSources[index][KeyTypes.sk] === ImpactedIndexTypeSource.provided);
3287
- const allMemberAttributesAreIncluded = definition.all.every(({name}) => included[name] !== undefined);
3382
+ const memberAttributeIsImpacted =
3383
+ impactedIndexTypeSources[index] &&
3384
+ (impactedIndexTypeSources[index][KeyTypes.pk] ===
3385
+ ImpactedIndexTypeSource.provided ||
3386
+ impactedIndexTypeSources[index][KeyTypes.sk] ===
3387
+ ImpactedIndexTypeSource.provided);
3388
+ const allMemberAttributesAreIncluded = definition.all.every(
3389
+ ({ name }) => included[name] !== undefined,
3390
+ );
3288
3391
 
3289
3392
  if (memberAttributeIsImpacted || allMemberAttributesAreIncluded) {
3290
3393
  // the `missing` array will contain indexes that are partially provided, but that leaves cases where the pk or
3291
3394
  // sk of an index is complete but not both. Both cases are invalid if `indexConditionIsDefined=true`
3292
3395
  const missingAttributes = definition.all
3293
- .filter(({name}) => attributes[name] === undefined && included[name] === undefined || missing.includes(name))
3294
- .map(({name}) => name)
3396
+ .filter(
3397
+ ({ name }) =>
3398
+ (attributes[name] === undefined &&
3399
+ included[name] === undefined) ||
3400
+ missing.includes(name),
3401
+ )
3402
+ .map(({ name }) => name);
3295
3403
 
3296
3404
  if (missingAttributes.length) {
3297
- throw new e.ElectroError(e.ErrorCodes.IncompleteIndexCompositesAttributesProvided, `Incomplete composite attributes provided for index ${index}. Write operations that include composite attributes, for indexes with a condition callback defined, must always provide values for every index composite. This is to ensure consistency between index values and attribute values. Missing composite attributes identified: ${u.commaSeparatedString(missingAttributes)}`);
3405
+ throw new e.ElectroError(
3406
+ e.ErrorCodes.IncompleteIndexCompositesAttributesProvided,
3407
+ `Incomplete composite attributes provided for index ${index}. Write operations that include composite attributes, for indexes with a condition callback defined, must always provide values for every index composite. This is to ensure consistency between index values and attribute values. Missing composite attributes identified: ${u.commaSeparatedString(
3408
+ missingAttributes,
3409
+ )}`,
3410
+ );
3298
3411
  }
3299
3412
 
3300
- const accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[index];
3301
- let shouldMakeKeys = !!this.model.indexes[accessPattern].condition({...attributes, ...included});
3413
+ const accessPattern =
3414
+ this.model.translations.indexes.fromIndexToAccessPattern[index];
3415
+ let shouldMakeKeys = !!this.model.indexes[accessPattern].condition({
3416
+ ...attributes,
3417
+ ...included,
3418
+ });
3302
3419
 
3303
3420
  // this helps identify which conditions were checked (key is present) and what the result was (true/false)
3304
3421
  conditions[index] = shouldMakeKeys;
@@ -3313,7 +3430,12 @@ class Entity {
3313
3430
  incomplete = incomplete.filter(({ missing }) => missing.length);
3314
3431
 
3315
3432
  let isIncomplete = !!incomplete.length;
3316
- let complete = { facets, indexes: completedIndexes, impactedIndexTypes, conditions };
3433
+ let complete = {
3434
+ facets,
3435
+ indexes: completedIndexes,
3436
+ impactedIndexTypes,
3437
+ conditions,
3438
+ };
3317
3439
  return [isIncomplete, { incomplete, complete }];
3318
3440
  }
3319
3441
 
@@ -3555,34 +3677,32 @@ class Entity {
3555
3677
  return prefix;
3556
3678
  }
3557
3679
 
3558
- _makeKeyTransforms(queryType) {
3680
+ _makeKeyTransforms(queryType, options = {}) {
3559
3681
  const transforms = [];
3560
3682
  const shiftUp = (val) => u.shiftSortOrder(val, 1);
3561
3683
  const noop = (val) => val;
3562
- switch (queryType) {
3563
- case QueryTypes.between:
3564
- transforms.push(noop, shiftUp);
3565
- break;
3566
- case QueryTypes.lte:
3567
- case QueryTypes.gt:
3568
- transforms.push(shiftUp);
3569
- break;
3570
- default:
3571
- transforms.push(noop);
3572
- break;
3684
+ if (options.compare !== ComparisonTypes.v2) {
3685
+ transforms.push(noop);
3686
+ } else if (queryType === QueryTypes.between) {
3687
+ transforms.push(noop, shiftUp);
3688
+ } else if (queryType === QueryTypes.lte || queryType === QueryTypes.gt) {
3689
+ transforms.push(shiftUp);
3690
+ } else {
3691
+ transforms.push(noop);
3573
3692
  }
3693
+
3574
3694
  return transforms;
3575
3695
  }
3576
3696
 
3577
3697
  /* istanbul ignore next */
3578
- _makeIndexKeysWithoutTail(state = {}, skFacets = []) {
3698
+ _makeIndexKeysWithoutTail(state = {}, skFacets = [], options) {
3579
3699
  const index = state.query.index || TableIndex;
3580
3700
  this._validateIndex(index);
3581
3701
  const pkFacets = state.query.keys.pk || {};
3582
3702
  const excludePostfix =
3583
3703
  state.query.options.indexType === IndexTypes.clustered &&
3584
3704
  state.query.options._isCollectionQuery;
3585
- const transforms = this._makeKeyTransforms(state.query.type);
3705
+ const transforms = this._makeKeyTransforms(state.query.type, options);
3586
3706
  if (!skFacets.length) {
3587
3707
  skFacets.push({});
3588
3708
  }
@@ -4078,8 +4198,8 @@ class Entity {
4078
4198
  let indexScope = index.scope || "";
4079
4199
  if (index.index === undefined && v.isFunction(index.condition)) {
4080
4200
  throw new e.ElectroError(
4081
- e.ErrorCodes.InvalidIndexCondition,
4082
- `The index option 'condition' is only allowed on secondary indexes`,
4201
+ e.ErrorCodes.InvalidIndexCondition,
4202
+ `The index option 'condition' is only allowed on secondary indexes`,
4083
4203
  );
4084
4204
  }
4085
4205
 
@@ -4184,7 +4304,7 @@ class Entity {
4184
4304
  }
4185
4305
  }
4186
4306
 
4187
- let definition= {
4307
+ let definition = {
4188
4308
  pk,
4189
4309
  sk,
4190
4310
  hasSk,