electrodb 2.15.0 → 3.0.1

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,51 @@ 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.v2
2903
+ ) {
2904
+ return KeyAttributesComparisons[comparison];
2905
+ } else {
2906
+ return Comparisons[comparison];
2907
+ }
2908
+ }
2909
+
2884
2910
  /* istanbul ignore next */
2885
2911
  _makeComparisonQueryParams(
2886
2912
  index = TableIndex,
2887
2913
  comparison = "",
2888
2914
  filter = {},
2889
2915
  indexKeys = {},
2916
+ options = {},
2917
+ queryOptions = {},
2890
2918
  ) {
2891
2919
  const { pk } = indexKeys;
2892
2920
  const sk = indexKeys.sk[0];
2893
2921
 
2894
- let operator =
2895
- typeof sk === "number"
2896
- ? Comparisons[comparison]
2897
- : PartialComparisons[comparison];
2898
-
2922
+ let operator = this._getComparisonOperator(
2923
+ comparison,
2924
+ typeof sk,
2925
+ options.compare,
2926
+ );
2899
2927
  if (!operator) {
2900
2928
  throw new Error(
2901
2929
  `Unexpected comparison operator "${comparison}", expected ${u.commaSeparatedString(
2902
- Object.values(PartialComparisons),
2930
+ Object.keys(KeyAttributesComparisons),
2903
2931
  )}`,
2904
2932
  );
2905
2933
  }
2934
+
2935
+ let customExpressions = {
2936
+ names: (queryOptions.expressions && queryOptions.expressions.names) || {},
2937
+ values:
2938
+ (queryOptions.expressions && queryOptions.expressions.values) || {},
2939
+ expression:
2940
+ (queryOptions.expressions && queryOptions.expressions.expression) || "",
2941
+ };
2942
+
2906
2943
  let keyExpressions = this._queryKeyExpressionAttributeBuilder(
2907
2944
  index,
2908
2945
  pk,
@@ -2914,27 +2951,40 @@ class Entity {
2914
2951
  ExpressionAttributeNames: this._mergeExpressionsAttributes(
2915
2952
  filter.getNames(),
2916
2953
  keyExpressions.ExpressionAttributeNames,
2954
+ customExpressions.names,
2917
2955
  ),
2918
2956
  ExpressionAttributeValues: this._mergeExpressionsAttributes(
2919
2957
  filter.getValues(),
2920
2958
  keyExpressions.ExpressionAttributeValues,
2959
+ customExpressions.values,
2921
2960
  ),
2922
2961
  KeyConditionExpression: `#pk = :pk and #sk1 ${operator} :sk1`,
2923
2962
  };
2963
+
2924
2964
  if (index) {
2925
2965
  params["IndexName"] = index;
2926
2966
  }
2927
- if (filter.build()) {
2928
- params.FilterExpression = filter.build();
2967
+
2968
+ let expressions = [customExpressions.expression, filter.build()]
2969
+ .filter(Boolean)
2970
+ .join(" AND ");
2971
+
2972
+ if (expressions.length) {
2973
+ params.FilterExpression = expressions;
2929
2974
  }
2975
+
2930
2976
  return params;
2931
2977
  }
2932
2978
 
2933
- _expectIndexFacets(attributes, facets, { utilizeIncludedOnlyIndexes, skipConditionCheck } = {}) {
2979
+ _expectIndexFacets(
2980
+ attributes,
2981
+ facets,
2982
+ { utilizeIncludedOnlyIndexes, skipConditionCheck } = {},
2983
+ ) {
2934
2984
  let [isIncomplete, { incomplete, complete }] = this._getIndexImpact(
2935
2985
  attributes,
2936
2986
  facets,
2937
- { utilizeIncludedOnlyIndexes, skipConditionCheck },
2987
+ { utilizeIncludedOnlyIndexes, skipConditionCheck },
2938
2988
  );
2939
2989
 
2940
2990
  if (isIncomplete) {
@@ -2963,7 +3013,8 @@ class Entity {
2963
3013
  _makeKeysFromAttributes(indexes, attributes, conditions) {
2964
3014
  let indexKeys = {};
2965
3015
  for (let [index, keyTypes] of Object.entries(indexes)) {
2966
- const shouldMakeKeys = !this._indexConditionIsDefined(index) || conditions[index];
3016
+ const shouldMakeKeys =
3017
+ !this._indexConditionIsDefined(index) || conditions[index];
2967
3018
  if (!shouldMakeKeys && index !== TableIndex) {
2968
3019
  continue;
2969
3020
  }
@@ -2994,7 +3045,10 @@ class Entity {
2994
3045
  _makePutKeysFromAttributes(indexes, attributes) {
2995
3046
  let indexKeys = {};
2996
3047
  for (let index of indexes) {
2997
- const shouldMakeKeys = this.model.indexes[this.model.translations.indexes.fromIndexToAccessPattern[index]].condition(attributes);
3048
+ const shouldMakeKeys =
3049
+ this.model.indexes[
3050
+ this.model.translations.indexes.fromIndexToAccessPattern[index]
3051
+ ].condition(attributes);
2998
3052
  if (!shouldMakeKeys) {
2999
3053
  continue;
3000
3054
  }
@@ -3019,11 +3073,15 @@ class Entity {
3019
3073
  );
3020
3074
 
3021
3075
  let deletedKeys = [];
3022
- for (const [indexName, condition] of Object.entries(completeFacets.conditions)) {
3076
+ for (const [indexName, condition] of Object.entries(
3077
+ completeFacets.conditions,
3078
+ )) {
3023
3079
  if (!condition) {
3024
3080
  deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.pk]);
3025
3081
  if (this.model.translations.keys[indexName][KeyTypes.sk]) {
3026
- deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.sk]);
3082
+ deletedKeys.push(
3083
+ this.model.translations.keys[indexName][KeyTypes.sk],
3084
+ );
3027
3085
  }
3028
3086
  }
3029
3087
  }
@@ -3071,7 +3129,7 @@ class Entity {
3071
3129
  const removedKeyImpact = this._expectIndexFacets(
3072
3130
  { ...removed },
3073
3131
  { ...keyAttributes },
3074
- { skipConditionCheck: true }
3132
+ { skipConditionCheck: true },
3075
3133
  );
3076
3134
 
3077
3135
  // complete facets, only includes impacted facets which likely does not include the updateIndex which then needs to be added here.
@@ -3091,11 +3149,15 @@ class Entity {
3091
3149
  let updatedKeys = {};
3092
3150
  let deletedKeys = [];
3093
3151
  let indexKey = {};
3094
- for (const [indexName, condition] of Object.entries(completeFacets.conditions)) {
3152
+ for (const [indexName, condition] of Object.entries(
3153
+ completeFacets.conditions,
3154
+ )) {
3095
3155
  if (!condition) {
3096
3156
  deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.pk]);
3097
3157
  if (this.model.translations.keys[indexName][KeyTypes.sk]) {
3098
- deletedKeys.push(this.model.translations.keys[indexName][KeyTypes.sk]);
3158
+ deletedKeys.push(
3159
+ this.model.translations.keys[indexName][KeyTypes.sk],
3160
+ );
3099
3161
  }
3100
3162
  }
3101
3163
  }
@@ -3122,9 +3184,9 @@ class Entity {
3122
3184
  let hasPrefix =
3123
3185
  indexHasSk && this.model.prefixes[index].sk.prefix !== undefined;
3124
3186
  let hasPostfix =
3125
- indexHasSk && this.model.prefixes[index].sk.prefix !== undefined;
3187
+ indexHasSk && this.model.prefixes[index].sk.prefix !== undefined;
3126
3188
  if (noImpactSk && noAttributeSk) {
3127
- let key = hasPrefix ? this.model.prefixes[index].sk.prefix : '';
3189
+ let key = hasPrefix ? this.model.prefixes[index].sk.prefix : "";
3128
3190
  if (hasPostfix) {
3129
3191
  key = `${key}${this.model.prefixes[index].sk.postfix}`;
3130
3192
  }
@@ -3146,12 +3208,19 @@ class Entity {
3146
3208
  }
3147
3209
 
3148
3210
  _indexConditionIsDefined(index) {
3149
- const definition = this.model.indexes[this.model.translations.indexes.fromIndexToAccessPattern[index]];
3211
+ const definition =
3212
+ this.model.indexes[
3213
+ this.model.translations.indexes.fromIndexToAccessPattern[index]
3214
+ ];
3150
3215
  return definition && definition.conditionDefined;
3151
3216
  }
3152
3217
 
3153
3218
  /* istanbul ignore next */
3154
- _getIndexImpact(attributes = {}, included = {}, { utilizeIncludedOnlyIndexes, skipConditionCheck } = {}) {
3219
+ _getIndexImpact(
3220
+ attributes = {},
3221
+ included = {},
3222
+ { utilizeIncludedOnlyIndexes, skipConditionCheck } = {},
3223
+ ) {
3155
3224
  // beware: this entire algorithm stinks and needs to be completely refactored. It does redundant loops and fights
3156
3225
  // itself the whole way through. I am sorry.
3157
3226
  let includedFacets = Object.keys(included);
@@ -3166,21 +3235,26 @@ class Entity {
3166
3235
  facets[attribute] = attributes[attribute];
3167
3236
  indexes.forEach((definition) => {
3168
3237
  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];
3238
+ impactedIndexes[index] = impactedIndexes[index] || {};
3239
+ impactedIndexes[index][type] = impactedIndexes[index][type] || [];
3240
+ impactedIndexes[index][type].push(attribute);
3241
+ impactedIndexTypes[index] = impactedIndexTypes[index] || {};
3242
+ impactedIndexTypes[index][type] =
3243
+ this.model.translations.keys[index][type];
3174
3244
 
3175
- impactedIndexTypeSources[index] = impactedIndexTypeSources[index] || {};
3176
- impactedIndexTypeSources[index][type] = ImpactedIndexTypeSource.provided;
3245
+ impactedIndexTypeSources[index] =
3246
+ impactedIndexTypeSources[index] || {};
3247
+ impactedIndexTypeSources[index][type] =
3248
+ ImpactedIndexTypeSource.provided;
3177
3249
  });
3178
3250
  }
3179
3251
  }
3180
3252
 
3181
3253
  // 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
3254
  if (utilizeIncludedOnlyIndexes) {
3183
- for (const [index, { pk, sk }] of Object.entries(this.model.facets.byIndex)) {
3255
+ for (const [index, { pk, sk }] of Object.entries(
3256
+ this.model.facets.byIndex,
3257
+ )) {
3184
3258
  // The main table index is handled somewhere else (messy I know), and we only want to do this processing if an
3185
3259
  // index condition is defined for backwards compatibility. Backwards compatibility is not required for this
3186
3260
  // change, but I have paranoid concerns of breaking changes around sparse indexes.
@@ -3188,24 +3262,36 @@ class Entity {
3188
3262
  continue;
3189
3263
  }
3190
3264
 
3191
- if (pk && pk.length && pk.every(attr => included[attr] !== undefined)) {
3265
+ if (
3266
+ pk &&
3267
+ pk.length &&
3268
+ pk.every((attr) => included[attr] !== undefined)
3269
+ ) {
3192
3270
  pk.forEach((attr) => {
3193
3271
  facets[attr] = included[attr];
3194
3272
  });
3195
3273
  impactedIndexes[index] = impactedIndexes[index] || {};
3196
3274
  impactedIndexes[index][KeyTypes.pk] = [...pk];
3197
3275
  impactedIndexTypes[index] = impactedIndexTypes[index] || {};
3198
- impactedIndexTypes[index][KeyTypes.pk] = this.model.translations.keys[index][KeyTypes.pk];
3276
+ impactedIndexTypes[index][KeyTypes.pk] =
3277
+ this.model.translations.keys[index][KeyTypes.pk];
3199
3278
 
3200
3279
  // flagging the impactedIndexTypeSource as `composite` means the entire key is only being impacted because
3201
3280
  // all composites are in `included`. This will help us determine if we need to evaluate the `condition`
3202
3281
  // callback for the index. If both the `sk` and `pk` were impacted because of `included` then we can skip
3203
3282
  // 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;
3283
+ impactedIndexTypeSources[index] =
3284
+ impactedIndexTypeSources[index] || {};
3285
+ impactedIndexTypeSources[index][KeyTypes.pk] =
3286
+ impactedIndexTypeSources[index][KeyTypes.pk] ||
3287
+ ImpactedIndexTypeSource.composite;
3206
3288
  }
3207
3289
 
3208
- if (sk && sk.length && sk.every(attr => included[attr] !== undefined)) {
3290
+ if (
3291
+ sk &&
3292
+ sk.length &&
3293
+ sk.every((attr) => included[attr] !== undefined)
3294
+ ) {
3209
3295
  if (this.model.translations.keys[index][KeyTypes.sk]) {
3210
3296
  sk.forEach((attr) => {
3211
3297
  facets[attr] = included[attr];
@@ -3213,61 +3299,66 @@ class Entity {
3213
3299
  impactedIndexes[index] = impactedIndexes[index] || {};
3214
3300
  impactedIndexes[index][KeyTypes.sk] = [...sk];
3215
3301
  impactedIndexTypes[index] = impactedIndexTypes[index] || {};
3216
- impactedIndexTypes[index][KeyTypes.sk] = this.model.translations.keys[index][KeyTypes.sk];
3302
+ impactedIndexTypes[index][KeyTypes.sk] =
3303
+ this.model.translations.keys[index][KeyTypes.sk];
3217
3304
 
3218
3305
  // flagging the impactedIndexTypeSource as `composite` means the entire key is only being impacted because
3219
3306
  // all composites are in `included`. This will help us determine if we need to evaluate the `condition`
3220
3307
  // callback for the index. If both the `sk` and `pk` were impacted because of `included` then we can skip
3221
3308
  // 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;
3309
+ impactedIndexTypeSources[index] =
3310
+ impactedIndexTypeSources[index] || {};
3311
+ impactedIndexTypeSources[index][KeyTypes.sk] =
3312
+ impactedIndexTypeSources[index][KeyTypes.sk] ||
3313
+ ImpactedIndexTypeSource.composite;
3224
3314
  }
3225
3315
  }
3226
3316
  }
3227
3317
  }
3228
3318
 
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
- }
3319
+ let indexesWithMissingComposites = Object.entries(
3320
+ this.model.facets.byIndex,
3321
+ ).map(([index, definition]) => {
3322
+ const { pk, sk } = definition;
3323
+ let impacted = impactedIndexes[index];
3324
+ let impact = {
3325
+ index,
3326
+ definition,
3327
+ missing: [],
3328
+ };
3329
+ if (impacted) {
3330
+ let missingPk =
3331
+ impacted[KeyTypes.pk] && impacted[KeyTypes.pk].length !== pk.length;
3332
+ let missingSk =
3333
+ impacted[KeyTypes.sk] && impacted[KeyTypes.sk].length !== sk.length;
3334
+ if (missingPk) {
3335
+ impact.missing = [
3336
+ ...impact.missing,
3337
+ ...pk.filter((attr) => {
3338
+ return (
3339
+ !impacted[KeyTypes.pk].includes(attr) &&
3340
+ !includedFacets.includes(attr)
3341
+ );
3342
+ }),
3343
+ ];
3344
+ }
3345
+ if (missingSk) {
3346
+ impact.missing = [
3347
+ ...impact.missing,
3348
+ ...sk.filter(
3349
+ (attr) =>
3350
+ !impacted[KeyTypes.sk].includes(attr) &&
3351
+ !includedFacets.includes(attr),
3352
+ ),
3353
+ ];
3267
3354
  }
3355
+ if (!missingPk && !missingSk) {
3356
+ completedIndexes.push(index);
3357
+ }
3358
+ }
3268
3359
 
3269
- return impact;
3270
- });
3360
+ return impact;
3361
+ });
3271
3362
 
3272
3363
  let incomplete = [];
3273
3364
  for (const { index, missing, definition } of indexesWithMissingComposites) {
@@ -3277,28 +3368,53 @@ class Entity {
3277
3368
  // is meaningless and ElectroDB should uphold its obligation to keep keys and attributes in sync.
3278
3369
  // `index === TableIndex` is a special case where we don't need to check the condition because the main table is immutable
3279
3370
  // `!this._indexConditionIsDefined(index)` means the index doesn't have a condition defined, so we can skip the check
3280
- if (skipConditionCheck || index === TableIndex || !indexConditionIsDefined) {
3371
+ if (
3372
+ skipConditionCheck ||
3373
+ index === TableIndex ||
3374
+ !indexConditionIsDefined
3375
+ ) {
3281
3376
  incomplete.push({ index, missing });
3282
3377
  conditions[index] = true;
3283
3378
  continue;
3284
3379
  }
3285
3380
 
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);
3381
+ const memberAttributeIsImpacted =
3382
+ impactedIndexTypeSources[index] &&
3383
+ (impactedIndexTypeSources[index][KeyTypes.pk] ===
3384
+ ImpactedIndexTypeSource.provided ||
3385
+ impactedIndexTypeSources[index][KeyTypes.sk] ===
3386
+ ImpactedIndexTypeSource.provided);
3387
+ const allMemberAttributesAreIncluded = definition.all.every(
3388
+ ({ name }) => included[name] !== undefined,
3389
+ );
3288
3390
 
3289
3391
  if (memberAttributeIsImpacted || allMemberAttributesAreIncluded) {
3290
3392
  // the `missing` array will contain indexes that are partially provided, but that leaves cases where the pk or
3291
3393
  // sk of an index is complete but not both. Both cases are invalid if `indexConditionIsDefined=true`
3292
3394
  const missingAttributes = definition.all
3293
- .filter(({name}) => attributes[name] === undefined && included[name] === undefined || missing.includes(name))
3294
- .map(({name}) => name)
3395
+ .filter(
3396
+ ({ name }) =>
3397
+ (attributes[name] === undefined &&
3398
+ included[name] === undefined) ||
3399
+ missing.includes(name),
3400
+ )
3401
+ .map(({ name }) => name);
3295
3402
 
3296
3403
  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)}`);
3404
+ throw new e.ElectroError(
3405
+ e.ErrorCodes.IncompleteIndexCompositesAttributesProvided,
3406
+ `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(
3407
+ missingAttributes,
3408
+ )}`,
3409
+ );
3298
3410
  }
3299
3411
 
3300
- const accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[index];
3301
- let shouldMakeKeys = !!this.model.indexes[accessPattern].condition({...attributes, ...included});
3412
+ const accessPattern =
3413
+ this.model.translations.indexes.fromIndexToAccessPattern[index];
3414
+ let shouldMakeKeys = !!this.model.indexes[accessPattern].condition({
3415
+ ...attributes,
3416
+ ...included,
3417
+ });
3302
3418
 
3303
3419
  // this helps identify which conditions were checked (key is present) and what the result was (true/false)
3304
3420
  conditions[index] = shouldMakeKeys;
@@ -3313,7 +3429,12 @@ class Entity {
3313
3429
  incomplete = incomplete.filter(({ missing }) => missing.length);
3314
3430
 
3315
3431
  let isIncomplete = !!incomplete.length;
3316
- let complete = { facets, indexes: completedIndexes, impactedIndexTypes, conditions };
3432
+ let complete = {
3433
+ facets,
3434
+ indexes: completedIndexes,
3435
+ impactedIndexTypes,
3436
+ conditions,
3437
+ };
3317
3438
  return [isIncomplete, { incomplete, complete }];
3318
3439
  }
3319
3440
 
@@ -3555,34 +3676,32 @@ class Entity {
3555
3676
  return prefix;
3556
3677
  }
3557
3678
 
3558
- _makeKeyTransforms(queryType) {
3679
+ _makeKeyTransforms(queryType, options = {}) {
3559
3680
  const transforms = [];
3560
3681
  const shiftUp = (val) => u.shiftSortOrder(val, 1);
3561
3682
  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;
3683
+ if (options.compare !== ComparisonTypes.v2) {
3684
+ transforms.push(noop);
3685
+ } else if (queryType === QueryTypes.between) {
3686
+ transforms.push(noop, shiftUp);
3687
+ } else if (queryType === QueryTypes.lte || queryType === QueryTypes.gt) {
3688
+ transforms.push(shiftUp);
3689
+ } else {
3690
+ transforms.push(noop);
3573
3691
  }
3692
+
3574
3693
  return transforms;
3575
3694
  }
3576
3695
 
3577
3696
  /* istanbul ignore next */
3578
- _makeIndexKeysWithoutTail(state = {}, skFacets = []) {
3697
+ _makeIndexKeysWithoutTail(state = {}, skFacets = [], options) {
3579
3698
  const index = state.query.index || TableIndex;
3580
3699
  this._validateIndex(index);
3581
3700
  const pkFacets = state.query.keys.pk || {};
3582
3701
  const excludePostfix =
3583
3702
  state.query.options.indexType === IndexTypes.clustered &&
3584
3703
  state.query.options._isCollectionQuery;
3585
- const transforms = this._makeKeyTransforms(state.query.type);
3704
+ const transforms = this._makeKeyTransforms(state.query.type, options);
3586
3705
  if (!skFacets.length) {
3587
3706
  skFacets.push({});
3588
3707
  }
@@ -4078,8 +4197,8 @@ class Entity {
4078
4197
  let indexScope = index.scope || "";
4079
4198
  if (index.index === undefined && v.isFunction(index.condition)) {
4080
4199
  throw new e.ElectroError(
4081
- e.ErrorCodes.InvalidIndexCondition,
4082
- `The index option 'condition' is only allowed on secondary indexes`,
4200
+ e.ErrorCodes.InvalidIndexCondition,
4201
+ `The index option 'condition' is only allowed on secondary indexes`,
4083
4202
  );
4084
4203
  }
4085
4204
 
@@ -4184,7 +4303,7 @@ class Entity {
4184
4303
  }
4185
4304
  }
4186
4305
 
4187
- let definition= {
4306
+ let definition = {
4188
4307
  pk,
4189
4308
  sk,
4190
4309
  hasSk,