electrodb 2.1.2 → 2.2.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
@@ -20,7 +20,9 @@ const { AllPages,
20
20
  MaxBatchItems,
21
21
  TerminalOperation,
22
22
  ResultOrderOption,
23
- ResultOrderParam
23
+ ResultOrderParam,
24
+ IndexTypes,
25
+ PartialComparisons,
24
26
  } = require("./types");
25
27
  const { FilterFactory } = require("./filters");
26
28
  const { FilterOperations } = require("./operations");
@@ -57,7 +59,10 @@ class Entity {
57
59
  for (let accessPattern in this.model.indexes) {
58
60
  let index = this.model.indexes[accessPattern].index;
59
61
  this.query[accessPattern] = (...values) => {
60
- return this._makeChain(index, this._clausesWithFilters, clauses.index).query(...values);
62
+ const options = {
63
+ indexType: this.model.indexes[accessPattern].type || IndexTypes.isolated,
64
+ }
65
+ return this._makeChain(index, this._clausesWithFilters, clauses.index, options).query(...values);
61
66
  };
62
67
  }
63
68
  this.config.identifiers = config.identifiers || {};
@@ -191,14 +196,9 @@ class Entity {
191
196
  }
192
197
  }
193
198
 
194
- collection(collection = "", clauses = {}, facets = {}, {expressions = {}, parse} = {}) {
195
- const options = {
196
- parse,
197
- expressions: {
198
- names: expressions.names || {},
199
- values: expressions.values || {},
200
- expression: expressions.expression || ""
201
- },
199
+ collection(collection = "", clauses = {}, facets = {}, options = {}) {
200
+ const chainOptions = {
201
+ ...options,
202
202
  _isCollectionQuery: true,
203
203
  };
204
204
 
@@ -206,10 +206,18 @@ class Entity {
206
206
  if (index === undefined) {
207
207
  throw new Error(`Invalid collection: ${collection}`);
208
208
  }
209
- return this._makeChain(index, clauses, clauses.index, options).collection(
210
- collection,
211
- facets
212
- );
209
+ const chain = this._makeChain(index, clauses, clauses.index, chainOptions);
210
+ if (options.indexType === IndexTypes.clustered) {
211
+ return chain.clusteredCollection(
212
+ collection,
213
+ facets,
214
+ );
215
+ } else {
216
+ return chain.collection(
217
+ collection,
218
+ facets,
219
+ );
220
+ }
213
221
  }
214
222
 
215
223
  _validateModel(model) {
@@ -510,7 +518,7 @@ class Entity {
510
518
  if (!response || !response.UnprocessedItems) {
511
519
  return response;
512
520
  }
513
- const table = config.table || this._getTableName();
521
+ const table = config.table || this.getTableName();
514
522
  const index = TableIndex;
515
523
  let unprocessed = response.UnprocessedItems[table];
516
524
  if (Array.isArray(unprocessed) && unprocessed.length) {
@@ -541,7 +549,7 @@ class Entity {
541
549
  response = {},
542
550
  config = {},
543
551
  }) {
544
- const table = config.table || this._getTableName();
552
+ const table = config.table || this.getTableName();
545
553
  const index = TableIndex;
546
554
 
547
555
  if (!response.UnprocessedKeys || !response.Responses) {
@@ -667,18 +675,22 @@ class Entity {
667
675
  return config.formatCursor.deserialize(exclusiveStartKey) || null;
668
676
  }
669
677
 
670
- _getTableName() {
671
- return this.config.table;
678
+ setClient(client) {
679
+ if (client) {
680
+ this.client = c.normalizeClient(client);
681
+ }
672
682
  }
673
683
 
674
- _setTableName(table) {
675
- this.config.table = table;
684
+ setTableName(tableName) {
685
+ this.config.table = tableName;
676
686
  }
677
687
 
678
- _setClient(client) {
679
- if (client) {
680
- this.client = c.normalizeClient(client);
681
- }
688
+ getTableName() {
689
+ return this.config.table;
690
+ }
691
+
692
+ getTableName() {
693
+ return this.config.table;
682
694
  }
683
695
 
684
696
  _chain(state, clauses, clause) {
@@ -702,9 +714,9 @@ class Entity {
702
714
  let state = new ChainState({
703
715
  index,
704
716
  options,
705
- attributes: this.model.schema.attributes,
706
- hasSortKey: this.model.lookup.indexHasSortKeys[index],
707
- compositeAttributes: this.model.facets.byIndex[index]
717
+ attributes: options.attributes || this.model.schema.attributes,
718
+ hasSortKey: options.hasSortKey || this.model.lookup.indexHasSortKeys[index],
719
+ compositeAttributes: options.compositeAttributes || this.model.facets.byIndex[index],
708
720
  });
709
721
  return state.init(this, clauses, rootClause);
710
722
  }
@@ -837,9 +849,13 @@ class Entity {
837
849
  }
838
850
 
839
851
  _constructPagerIndex(index = TableIndex, item) {
840
- let pk = this._expectFacets(item, this.model.facets.byIndex[index].pk);
841
- let sk = this._expectFacets(item, this.model.facets.byIndex[index].sk);
842
- let keys = this._makeIndexKeys(index, pk, sk);
852
+ let pkAttributes = this._expectFacets(item, this.model.facets.byIndex[index].pk);
853
+ let skAttributes = this._expectFacets(item, this.model.facets.byIndex[index].sk);
854
+ let keys = this._makeIndexKeys({
855
+ index,
856
+ pkAttributes,
857
+ skAttributes: [skAttributes],
858
+ });
843
859
  return this._makeParameterKey(index, keys.pk, ...keys.sk);
844
860
  }
845
861
 
@@ -908,8 +924,8 @@ class Entity {
908
924
 
909
925
  if (option.formatCursor) {
910
926
  const isValid = ['serialize', 'deserialize'].every(method =>
911
- method in option.formatCursor &&
912
- validate.isFunction(option.formatCursor[method])
927
+ method in option.formatCursor &&
928
+ validations.isFunction(option.formatCursor[method])
913
929
  );
914
930
  if (isValid) {
915
931
  config.formatCursor = option.formatCursor;
@@ -1219,7 +1235,7 @@ class Entity {
1219
1235
  }
1220
1236
 
1221
1237
  _batchGetParams(state, config = {}) {
1222
- let table = config.table || this._getTableName();
1238
+ let table = config.table || this.getTableName();
1223
1239
  let userDefinedParams = config.params || {};
1224
1240
  let records = [];
1225
1241
  for (let itemState of state.subStates) {
@@ -1244,7 +1260,7 @@ class Entity {
1244
1260
  }
1245
1261
 
1246
1262
  _batchWriteParams(state, config = {}) {
1247
- let table = config.table || this._getTableName();
1263
+ let table = config.table || this.getTableName();
1248
1264
  let records = [];
1249
1265
  for (let itemState of state.subStates) {
1250
1266
  let method = itemState.query.method;
@@ -1292,14 +1308,14 @@ class Entity {
1292
1308
  let version = this.getVersion();
1293
1309
  return {
1294
1310
  names: {
1295
- [`#${this.identifiers.entity}_${alias}`]: this.identifiers.entity,
1296
- [`#${this.identifiers.version}_${alias}`]: this.identifiers.version,
1311
+ [`#${this.identifiers.entity}`]: this.identifiers.entity,
1312
+ [`#${this.identifiers.version}`]: this.identifiers.version,
1297
1313
  },
1298
1314
  values: {
1299
1315
  [`:${this.identifiers.entity}_${alias}`]: name,
1300
1316
  [`:${this.identifiers.version}_${alias}`]: version,
1301
1317
  },
1302
- expression: `(#${this.identifiers.entity}_${alias} = :${this.identifiers.entity}_${alias} AND #${this.identifiers.version}_${alias} = :${this.identifiers.version}_${alias})`
1318
+ expression: `(#${this.identifiers.entity} = :${this.identifiers.entity}_${alias} AND #${this.identifiers.version} = :${this.identifiers.version}_${alias})`
1303
1319
  }
1304
1320
  }
1305
1321
 
@@ -1309,11 +1325,13 @@ class Entity {
1309
1325
  let hasSortKey = this.model.lookup.indexHasSortKeys[indexBase];
1310
1326
  let accessPattern = this.model.translations.indexes.fromIndexToAccessPattern[indexBase];
1311
1327
  let pkField = this.model.indexes[accessPattern].pk.field;
1312
- let {pk, sk} = this._makeIndexKeys(indexBase);
1328
+ let {pk, sk} = this._makeIndexKeys({
1329
+ index: indexBase,
1330
+ });
1313
1331
  let keys = this._makeParameterKey(indexBase, pk, ...sk);
1314
1332
  let keyExpressions = this._expressionAttributeBuilder(keys);
1315
1333
  let params = {
1316
- TableName: this._getTableName(),
1334
+ TableName: this.getTableName(),
1317
1335
  ExpressionAttributeNames: this._mergeExpressionsAttributes(
1318
1336
  filter.getNames(),
1319
1337
  keyExpressions.ExpressionAttributeNames
@@ -1341,9 +1359,13 @@ class Entity {
1341
1359
 
1342
1360
  _makeSimpleIndexParams(partition, sort) {
1343
1361
  let index = TableIndex;
1344
- let keys = this._makeIndexKeys(index, partition, sort);
1362
+ let keys = this._makeIndexKeys({
1363
+ index,
1364
+ pkAttributes: partition,
1365
+ skAttributes: [sort],
1366
+ });
1345
1367
  let Key = this._makeParameterKey(index, keys.pk, ...keys.sk);
1346
- let TableName = this._getTableName();
1368
+ let TableName = this.getTableName();
1347
1369
  return {Key, TableName};
1348
1370
  }
1349
1371
 
@@ -1430,7 +1452,7 @@ class Entity {
1430
1452
  UpdateExpression: update.build(),
1431
1453
  ExpressionAttributeNames: update.getNames(),
1432
1454
  ExpressionAttributeValues: update.getValues(),
1433
- TableName: this._getTableName(),
1455
+ TableName: this.getTableName(),
1434
1456
  Key: indexKey,
1435
1457
  };
1436
1458
  }
@@ -1447,7 +1469,7 @@ class Entity {
1447
1469
  [this.identifiers.entity]: this.getName(),
1448
1470
  [this.identifiers.version]: this.getVersion(),
1449
1471
  },
1450
- TableName: this._getTableName(),
1472
+ TableName: this.getTableName(),
1451
1473
  };
1452
1474
  }
1453
1475
 
@@ -1551,17 +1573,28 @@ class Entity {
1551
1573
  return expressions;
1552
1574
  }
1553
1575
 
1554
- /* istanbul ignore next */
1555
- _queryParams(state = {}, options = {}) {
1576
+ _makeQueryKeys(state) {
1556
1577
  let consolidatedQueryFacets = this._consolidateQueryFacets(
1557
1578
  state.query.keys.sk,
1558
1579
  );
1559
- let indexKeys;
1560
- if (state.query.type === QueryTypes.is) {
1561
- indexKeys = this._makeIndexKeys(state.query.index, state.query.keys.pk, ...consolidatedQueryFacets);
1562
- } else {
1563
- indexKeys = this._makeIndexKeysWithoutTail(state.query.index, state.query.keys.pk, ...consolidatedQueryFacets);
1580
+ switch (state.query.type) {
1581
+ case QueryTypes.is:
1582
+ return this._makeIndexKeys({
1583
+ index: state.query.index,
1584
+ pkAttributes: state.query.keys.pk,
1585
+ skAttributes: consolidatedQueryFacets,
1586
+ indexType: state.query.options.indexType,
1587
+ queryType: state.query.type,
1588
+ isCollection: state.query.options._isCollectionQuery,
1589
+ });
1590
+ default:
1591
+ return this._makeIndexKeysWithoutTail(state, consolidatedQueryFacets);
1564
1592
  }
1593
+ }
1594
+
1595
+ /* istanbul ignore next */
1596
+ _queryParams(state = {}, options = {}) {
1597
+ const indexKeys = this._makeQueryKeys(state);
1565
1598
  let parameters = {};
1566
1599
  switch (state.query.type) {
1567
1600
  case QueryTypes.is:
@@ -1591,6 +1624,15 @@ class Entity {
1591
1624
  this._getCollectionSk(state.query.collection),
1592
1625
  );
1593
1626
  break;
1627
+ case QueryTypes.clustered_collection:
1628
+ parameters = this._makeBeginsWithQueryParams(
1629
+ state.query.options,
1630
+ state.query.index,
1631
+ state.query.filter[ExpressionTypes.FilterExpression],
1632
+ indexKeys.pk,
1633
+ ...indexKeys.sk,
1634
+ );
1635
+ break;
1594
1636
  case QueryTypes.between:
1595
1637
  parameters = this._makeBetweenQueryParams(
1596
1638
  state.query.index,
@@ -1607,8 +1649,7 @@ class Entity {
1607
1649
  state.query.index,
1608
1650
  state.query.type,
1609
1651
  state.query.filter[ExpressionTypes.FilterExpression],
1610
- indexKeys.pk,
1611
- ...indexKeys.sk,
1652
+ indexKeys,
1612
1653
  );
1613
1654
  break;
1614
1655
  default:
@@ -1626,7 +1667,7 @@ class Entity {
1626
1667
  );
1627
1668
  delete keyExpressions.ExpressionAttributeNames["#sk2"];
1628
1669
  let params = {
1629
- TableName: this._getTableName(),
1670
+ TableName: this.getTableName(),
1630
1671
  ExpressionAttributeNames: this._mergeExpressionsAttributes(
1631
1672
  filter.getNames(),
1632
1673
  keyExpressions.ExpressionAttributeNames,
@@ -1669,7 +1710,7 @@ class Entity {
1669
1710
 
1670
1711
  let params = {
1671
1712
  KeyConditionExpression,
1672
- TableName: this._getTableName(),
1713
+ TableName: this.getTableName(),
1673
1714
  ExpressionAttributeNames: this._mergeExpressionsAttributes(filter.getNames(), keyExpressions.ExpressionAttributeNames, customExpressions.names),
1674
1715
  ExpressionAttributeValues: this._mergeExpressionsAttributes(filter.getValues(), keyExpressions.ExpressionAttributeValues, customExpressions.values),
1675
1716
  };
@@ -1727,8 +1768,14 @@ class Entity {
1727
1768
  }
1728
1769
 
1729
1770
  /* istanbul ignore next */
1730
- _makeComparisonQueryParams(index = TableIndex, comparison = "", filter = {}, pk = {}, sk = {}) {
1731
- let operator = Comparisons[comparison];
1771
+ _makeComparisonQueryParams(index = TableIndex, comparison = "", filter = {}, indexKeys = {}) {
1772
+ const {pk, fulfilled} = indexKeys;
1773
+ const sk = indexKeys.sk[0];
1774
+ let operator = PartialComparisons[comparison];
1775
+ // fulfilled
1776
+ // ? Comparisons[comparison]
1777
+ // : PartialComparisons[comparison];
1778
+
1732
1779
  if (!operator) {
1733
1780
  throw new Error(`Unexpected comparison operator "${comparison}", expected ${u.commaSeparatedString(Object.values(Comparisons))}`);
1734
1781
  }
@@ -1738,7 +1785,7 @@ class Entity {
1738
1785
  sk,
1739
1786
  );
1740
1787
  let params = {
1741
- TableName: this._getTableName(),
1788
+ TableName: this.getTableName(),
1742
1789
  ExpressionAttributeNames: this._mergeExpressionsAttributes(
1743
1790
  filter.getNames(),
1744
1791
  keyExpressions.ExpressionAttributeNames,
@@ -1776,7 +1823,11 @@ class Entity {
1776
1823
  _makeKeysFromAttributes(indexes, attributes) {
1777
1824
  let indexKeys = {};
1778
1825
  for (let [index, keyTypes] of Object.entries(indexes)) {
1779
- let keys = this._makeIndexKeys(index, attributes, attributes);
1826
+ let keys = this._makeIndexKeys({
1827
+ index,
1828
+ pkAttributes: attributes,
1829
+ skAttributes: [attributes],
1830
+ });
1780
1831
  if (keyTypes.pk || keyTypes.sk) {
1781
1832
  indexKeys[index] = {};
1782
1833
  }
@@ -1798,7 +1849,11 @@ class Entity {
1798
1849
  _makePutKeysFromAttributes(indexes, attributes) {
1799
1850
  let indexKeys = {};
1800
1851
  for (let index of indexes) {
1801
- indexKeys[index] = this._makeIndexKeys(index, attributes, attributes);
1852
+ indexKeys[index] = this._makeIndexKeys({
1853
+ index,
1854
+ pkAttributes: attributes,
1855
+ skAttributes: [attributes],
1856
+ });
1802
1857
  }
1803
1858
  return indexKeys;
1804
1859
  }
@@ -2009,7 +2064,14 @@ class Entity {
2009
2064
  return [!!missing.length, missing, matching];
2010
2065
  }
2011
2066
 
2012
- _makeKeyPrefixes(service, entity, version = "1", tableIndex, modelVersion) {
2067
+ _makeKeyFixings({
2068
+ service,
2069
+ entity,
2070
+ version = "1",
2071
+ tableIndex,
2072
+ modelVersion,
2073
+ isClustered
2074
+ }) {
2013
2075
  /*
2014
2076
  Collections will prefix the sort key so they can be queried with
2015
2077
  a "begins_with" operator when crossing entities. It is also possible
@@ -2035,34 +2097,47 @@ class Entity {
2035
2097
 
2036
2098
  let pk = `$${service}`;
2037
2099
  let sk = "";
2038
-
2100
+ let entityKeys = "";
2101
+ let postfix = "";
2039
2102
  // If the index is in a collections, prepend the sk;
2040
2103
  let collectionPrefix = this._makeCollectionPrefix(tableIndex.collection);
2041
2104
  if (validations.isStringHasLength(collectionPrefix)) {
2042
- sk = `${collectionPrefix}#${entity}`;
2105
+ sk = `${collectionPrefix}`;
2106
+ entityKeys += `#${entity}`;
2043
2107
  } else {
2044
- sk = `$${entity}`;
2108
+ entityKeys += `$${entity}`;
2045
2109
  }
2046
2110
 
2047
2111
  /** start beta/v1 condition **/
2048
2112
  if (modelVersion === ModelVersions.beta) {
2049
2113
  pk = `${pk}_${version}`;
2050
2114
  } else {
2051
- sk = `${sk}_${version}`;
2115
+ entityKeys = `${entityKeys}_${version}`;
2052
2116
  }
2053
2117
  /** end beta/v1 condition **/
2054
2118
 
2119
+ if (isClustered) {
2120
+ postfix = entityKeys;
2121
+ } else {
2122
+ sk = `${sk}${entityKeys}`
2123
+ }
2124
+
2055
2125
  // If no sk, append the sk properties to the pk
2056
2126
  if (Object.keys(tableIndex.sk).length === 0) {
2057
2127
  pk += sk;
2128
+ if (isClustered) {
2129
+ pk += postfix;
2130
+ }
2058
2131
  }
2059
2132
 
2060
2133
  // If keys arent custom, set the prefixes
2061
2134
  if (!keys.pk.isCustom) {
2062
2135
  keys.pk.prefix = u.formatKeyCasing(pk, tableIndex.pk.casing);
2063
2136
  }
2137
+
2064
2138
  if (!keys.sk.isCustom) {
2065
2139
  keys.sk.prefix = u.formatKeyCasing(sk, tableIndex.sk.casing);
2140
+ keys.sk.postfix = u.formatKeyCasing(postfix, tableIndex.sk.casing);
2066
2141
  }
2067
2142
 
2068
2143
  return keys;
@@ -2107,9 +2182,32 @@ class Entity {
2107
2182
  return prefix;
2108
2183
  }
2109
2184
 
2185
+ _makeKeyTransforms(queryType) {
2186
+ const transforms = [];
2187
+ const shiftUp = (val) => u.shiftSortOrder(val, 1);
2188
+ const noop = (val) => val;
2189
+ switch (queryType) {
2190
+ case QueryTypes.between:
2191
+ transforms.push(noop, shiftUp);
2192
+ break;
2193
+ case QueryTypes.lte:
2194
+ case QueryTypes.gt:
2195
+ transforms.push(shiftUp);
2196
+ break;
2197
+ default:
2198
+ transforms.push(noop);
2199
+ break;
2200
+ }
2201
+ return transforms;
2202
+ }
2203
+
2110
2204
  /* istanbul ignore next */
2111
- _makeIndexKeysWithoutTail(index = TableIndex, pkFacets = {}, ...skFacets) {
2205
+ _makeIndexKeysWithoutTail(state = {}, skFacets = []) {
2206
+ const index = state.query.index || TableIndex;
2112
2207
  this._validateIndex(index);
2208
+ const pkFacets = state.query.keys.pk || {};
2209
+ const excludePostfix = state.query.options.indexType === IndexTypes.clustered && state.query.options._isCollectionQuery;
2210
+ const transforms = this._makeKeyTransforms(state.query.type);
2113
2211
  if (!skFacets.length) {
2114
2212
  skFacets.push({});
2115
2213
  }
@@ -2118,49 +2216,84 @@ class Entity {
2118
2216
  if (!prefixes) {
2119
2217
  throw new Error(`Invalid index: ${index}`);
2120
2218
  }
2121
- let pk = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk, {excludeLabelTail: true});
2219
+ let partitionKey = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk, {excludeLabelTail: true});
2220
+ let pk = partitionKey.key;
2122
2221
  let sk = [];
2222
+ let fulfilled = false;
2123
2223
  if (this.model.lookup.indexHasSortKeys[index]) {
2124
- for (let skFacet of skFacets) {
2224
+ for (let i = 0; i < skFacets.length; i++) {
2225
+ const skFacet = skFacets[i];
2226
+ const transform = transforms[i];
2125
2227
  let hasLabels = this.model.facets.labels[index] && Array.isArray(this.model.facets.labels[index].sk);
2126
2228
  let labels = hasLabels
2127
2229
  ? this.model.facets.labels[index].sk
2128
- : []
2129
- let sortKey = this._makeKey(prefixes.sk, facets.sk, skFacet, labels, {excludeLabelTail: true});
2130
- if (sortKey !== undefined) {
2131
- sk.push(sortKey);
2230
+ : [];
2231
+ let sortKey = this._makeKey(prefixes.sk, facets.sk, skFacet, labels, {
2232
+ excludeLabelTail: true,
2233
+ excludePostfix,
2234
+ transform,
2235
+ });
2236
+ if (sortKey.key !== undefined) {
2237
+ sk.push(sortKey.key);
2238
+ }
2239
+ if (sortKey.fulfilled) {
2240
+ fulfilled = true;
2132
2241
  }
2133
2242
  }
2134
2243
  }
2135
- return { pk, sk };
2244
+ return {
2245
+ pk,
2246
+ sk,
2247
+ fulfilled,
2248
+ };
2136
2249
  }
2137
2250
 
2138
2251
  /* istanbul ignore next */
2139
- _makeIndexKeys(index = TableIndex, pkFacets = {}, ...skFacets) {
2252
+ _makeIndexKeys({
2253
+ index = TableIndex,
2254
+ pkAttributes = {},
2255
+ skAttributes = [],
2256
+ queryType,
2257
+ indexType,
2258
+ isCollection = false,
2259
+ }) {
2260
+
2140
2261
  this._validateIndex(index);
2141
- if (!skFacets.length) {
2142
- skFacets.push({});
2262
+ const excludePostfix = indexType === IndexTypes.clustered && isCollection;
2263
+ const transforms = this._makeKeyTransforms(queryType);
2264
+ if (!skAttributes.length) {
2265
+ skAttributes.push({});
2143
2266
  }
2144
2267
  let facets = this.model.facets.byIndex[index];
2145
2268
  let prefixes = this.model.prefixes[index];
2146
2269
  if (!prefixes) {
2147
2270
  throw new Error(`Invalid index: ${index}`);
2148
2271
  }
2149
- let pk = this._makeKey(prefixes.pk, facets.pk, pkFacets, this.model.facets.labels[index].pk);
2272
+ let pk = this._makeKey(prefixes.pk, facets.pk, pkAttributes, this.model.facets.labels[index].pk);
2150
2273
  let sk = [];
2274
+ let fulfilled = false;
2151
2275
  if (this.model.lookup.indexHasSortKeys[index]) {
2152
- for (let skFacet of skFacets) {
2276
+ for (let i = 0; i < skAttributes.length; i++) {
2277
+ const skFacet = skAttributes[i];
2278
+ const transform = transforms[i];
2153
2279
  let hasLabels = this.model.facets.labels[index] && Array.isArray(this.model.facets.labels[index].sk);
2154
2280
  let labels = hasLabels
2155
2281
  ? this.model.facets.labels[index].sk
2156
2282
  : []
2157
- let sortKey = this._makeKey(prefixes.sk, facets.sk, skFacet, labels);
2158
- if (sortKey !== undefined) {
2159
- sk.push(sortKey);
2283
+ let sortKey = this._makeKey(prefixes.sk, facets.sk, skFacet, labels, {excludePostfix, transform});
2284
+ if (sortKey.key !== undefined) {
2285
+ sk.push(sortKey.key);
2286
+ }
2287
+ if (sortKey.fulfilled) {
2288
+ fulfilled = true;
2160
2289
  }
2161
2290
  }
2162
2291
  }
2163
- return { pk, sk };
2292
+ return {
2293
+ pk: pk.key,
2294
+ sk,
2295
+ fulfilled
2296
+ };
2164
2297
  }
2165
2298
 
2166
2299
  _isNumericKey(isCustom, facets = [], labels = []) {
@@ -2172,11 +2305,15 @@ class Entity {
2172
2305
  }
2173
2306
 
2174
2307
  /* istanbul ignore next */
2175
- _makeKey({prefix, isCustom, casing} = {}, facets = [], supplied = {}, labels = [], {excludeLabelTail} = {}) {
2308
+ _makeKey({prefix, isCustom, casing, postfix} = {}, facets = [], supplied = {}, labels = [], {excludeLabelTail, excludePostfix, transform = (val) => val} = {}) {
2176
2309
  if (this._isNumericKey(isCustom, facets, labels)) {
2177
- return supplied[facets[0]];
2310
+ return {
2311
+ fulfilled: supplied[facets[0]] !== undefined,
2312
+ key: supplied[facets[0]],
2313
+ };
2178
2314
  }
2179
2315
  let key = prefix;
2316
+ let foundCount = 0;
2180
2317
  for (let i = 0; i < labels.length; i++) {
2181
2318
  const { name, label } = labels[i];
2182
2319
  const attribute = this.model.schema.getAttribute(name);
@@ -2198,11 +2335,24 @@ class Entity {
2198
2335
  if (supplied[name] === undefined) {
2199
2336
  break;
2200
2337
  }
2201
-
2338
+ foundCount++;
2202
2339
  key = `${key}${value}`;
2203
2340
  }
2204
2341
 
2205
- return u.formatKeyCasing(key, casing);
2342
+
2343
+
2344
+ // when sort keys are fulfilled we need to add the entity postfix
2345
+ // this is used for cluster indexes
2346
+ const fulfilled = foundCount === labels.length;
2347
+ const shouldApplyPostfix = typeof postfix === 'string' && !excludePostfix;
2348
+ if (fulfilled && shouldApplyPostfix) {
2349
+ key += postfix;
2350
+ }
2351
+
2352
+ return {
2353
+ fulfilled,
2354
+ key: transform(u.formatKeyCasing(key, casing))
2355
+ };
2206
2356
  }
2207
2357
 
2208
2358
  _findBestIndexKeyMatch(attributes = {}) {
@@ -2450,6 +2600,7 @@ class Entity {
2450
2600
  let indexFieldTranslation = {};
2451
2601
  let indexHasSortKeys = {};
2452
2602
  let indexHasSubCollections = {};
2603
+ let clusteredIndexes = new Set();
2453
2604
  let indexAccessPatternTransaction = {
2454
2605
  fromAccessPatternToIndex: {},
2455
2606
  fromIndexToAccessPattern: {},
@@ -2480,6 +2631,10 @@ class Entity {
2480
2631
  let accessPattern = accessPatterns[i];
2481
2632
  let index = indexes[accessPattern];
2482
2633
  let indexName = index.index || TableIndex;
2634
+ let indexType = typeof index.type === 'string' ? index.type : IndexTypes.isolated;
2635
+ if (indexType === 'clustered') {
2636
+ clusteredIndexes.add(accessPattern);
2637
+ }
2483
2638
  if (seenIndexes[indexName] !== undefined) {
2484
2639
  if (indexName === TableIndex) {
2485
2640
  throw new e.ElectroError(e.ErrorCodes.DuplicateIndexes, `Duplicate index defined in model found in Access Pattern '${accessPattern}': '${u.formatIndexNameForDisplay(indexName)}'. This could be because you forgot to specify the index name of a secondary index defined in your model.`);
@@ -2554,8 +2709,10 @@ class Entity {
2554
2709
  pk,
2555
2710
  sk,
2556
2711
  collection,
2712
+ hasSk,
2557
2713
  customFacets,
2558
2714
  index: indexName,
2715
+ type: indexType,
2559
2716
  };
2560
2717
 
2561
2718
  indexHasSubCollections[indexName] = inCollection && Array.isArray(collection);
@@ -2699,6 +2856,7 @@ class Entity {
2699
2856
  facets,
2700
2857
  subCollections,
2701
2858
  indexHasSortKeys,
2859
+ clusteredIndexes,
2702
2860
  indexHasSubCollections,
2703
2861
  indexes: normalized,
2704
2862
  indexField: indexFieldTranslation,
@@ -2723,11 +2881,18 @@ class Entity {
2723
2881
  return normalized;
2724
2882
  }
2725
2883
 
2726
- _normalizePrefixes(service, entity, version, indexes, modelVersion) {
2884
+ _normalizeKeyFixings({service, entity, version, indexes, modelVersion, clusteredIndexes}) {
2727
2885
  let prefixes = {};
2728
2886
  for (let accessPattern of Object.keys(indexes)) {
2729
- let item = indexes[accessPattern];
2730
- prefixes[item.index] = this._makeKeyPrefixes(service, entity, version, item, modelVersion);
2887
+ let tableIndex = indexes[accessPattern];
2888
+ prefixes[tableIndex.index] = this._makeKeyFixings({
2889
+ service,
2890
+ entity,
2891
+ version,
2892
+ tableIndex,
2893
+ modelVersion,
2894
+ isClustered: clusteredIndexes.has(accessPattern),
2895
+ });
2731
2896
  }
2732
2897
  return prefixes;
2733
2898
  }
@@ -2867,13 +3032,15 @@ class Entity {
2867
3032
  collections,
2868
3033
  subCollections,
2869
3034
  indexCollection,
3035
+ clusteredIndexes,
2870
3036
  indexHasSortKeys,
2871
3037
  indexAccessPattern,
2872
3038
  indexHasSubCollections,
2873
3039
  } = this._normalizeIndexes(model.indexes);
2874
- let schema = new Schema(model.attributes, facets, {client});
3040
+ let schema = new Schema(model.attributes, facets, {client, isRoot: true});
2875
3041
  let filters = this._normalizeFilters(model.filters);
2876
- let prefixes = this._normalizePrefixes(service, entity, version, indexes, modelVersion);
3042
+ // todo: consider a rename
3043
+ let prefixes = this._normalizeKeyFixings({service, entity, version, indexes, modelVersion, clusteredIndexes});
2877
3044
 
2878
3045
  // apply model defined labels
2879
3046
  let schemaDefinedLabels = schema.getLabels();
@@ -2898,6 +3065,7 @@ class Entity {
2898
3065
  modelVersion,
2899
3066
  subCollections,
2900
3067
  lookup: {
3068
+ clusteredIndexes,
2901
3069
  indexHasSortKeys,
2902
3070
  indexHasSubCollections
2903
3071
  },