@awsless/dynamodb-server 0.1.5 → 0.1.6
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/dist/index.d.ts +2 -0
- package/dist/index.js +257 -176
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -218,6 +218,8 @@ declare class Table {
|
|
|
218
218
|
getIndexKeySchema(indexName: string): KeySchemaElement[] | undefined;
|
|
219
219
|
private attributeEquals;
|
|
220
220
|
private compareAttributes;
|
|
221
|
+
private compareByKeySchema;
|
|
222
|
+
private queryBySchema;
|
|
221
223
|
getAllItems(): AttributeMap[];
|
|
222
224
|
clear(): void;
|
|
223
225
|
onStreamRecord(callback: StreamCallback): () => void;
|
package/dist/index.js
CHANGED
|
@@ -188,9 +188,15 @@ function validateAndCreate(store, input) {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
+
if (input.GlobalSecondaryIndexes) {
|
|
192
|
+
for (const gsi of input.GlobalSecondaryIndexes) {
|
|
193
|
+
validateSecondaryIndexKeySchema(gsi.IndexName, gsi.KeySchema, definedAttrs, true);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
191
196
|
if (input.LocalSecondaryIndexes) {
|
|
192
197
|
const tableHashKey = hashKeys[0].AttributeName;
|
|
193
198
|
for (const lsi of input.LocalSecondaryIndexes) {
|
|
199
|
+
validateSecondaryIndexKeySchema(lsi.IndexName, lsi.KeySchema, definedAttrs, false);
|
|
194
200
|
const lsiHashKey = lsi.KeySchema.find((k) => k.KeyType === "HASH");
|
|
195
201
|
if (!lsiHashKey || lsiHashKey.AttributeName !== tableHashKey) {
|
|
196
202
|
throw new ValidationException(
|
|
@@ -215,6 +221,43 @@ function createTable(store, input) {
|
|
|
215
221
|
const tableDescription = validateAndCreate(store, input);
|
|
216
222
|
return { TableDescription: tableDescription };
|
|
217
223
|
}
|
|
224
|
+
function validateSecondaryIndexKeySchema(indexName, keySchema, definedAttrs, isGlobal) {
|
|
225
|
+
if (!keySchema.length) {
|
|
226
|
+
throw new ValidationException(`Index ${indexName} must define a key schema`);
|
|
227
|
+
}
|
|
228
|
+
const hashKeys = keySchema.filter((k) => k.KeyType === "HASH");
|
|
229
|
+
const rangeKeys = keySchema.filter((k) => k.KeyType === "RANGE");
|
|
230
|
+
const maxHashKeys = isGlobal ? 4 : 1;
|
|
231
|
+
const maxRangeKeys = isGlobal ? 4 : 1;
|
|
232
|
+
if (hashKeys.length === 0 || hashKeys.length > maxHashKeys) {
|
|
233
|
+
throw new ValidationException(
|
|
234
|
+
isGlobal ? `Global secondary index ${indexName} must have between 1 and 4 partition key attributes` : `Local secondary index ${indexName} must have exactly one hash key`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (rangeKeys.length > maxRangeKeys) {
|
|
238
|
+
throw new ValidationException(
|
|
239
|
+
isGlobal ? `Global secondary index ${indexName} can have at most 4 sort key attributes` : `Local secondary index ${indexName} can have at most one range key`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (isGlobal && hashKeys.length + rangeKeys.length > 8) {
|
|
243
|
+
throw new ValidationException(`Global secondary index ${indexName} can have at most 8 key attributes`);
|
|
244
|
+
}
|
|
245
|
+
let seenRange = false;
|
|
246
|
+
for (const keyElement of keySchema) {
|
|
247
|
+
if (!definedAttrs.has(keyElement.AttributeName)) {
|
|
248
|
+
throw new ValidationException(
|
|
249
|
+
`Attribute ${keyElement.AttributeName} is specified in index ${indexName} but not in AttributeDefinitions`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
if (keyElement.KeyType === "RANGE") {
|
|
253
|
+
seenRange = true;
|
|
254
|
+
} else if (seenRange) {
|
|
255
|
+
throw new ValidationException(
|
|
256
|
+
`Index ${indexName} must list all partition key attributes before sort key attributes`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
218
261
|
|
|
219
262
|
// src/operations/delete-table.ts
|
|
220
263
|
function deleteTable(store, input) {
|
|
@@ -909,10 +952,30 @@ function getHashKey(keySchema) {
|
|
|
909
952
|
}
|
|
910
953
|
return hash.AttributeName;
|
|
911
954
|
}
|
|
955
|
+
function getHashKeys(keySchema) {
|
|
956
|
+
return keySchema.filter((k) => k.KeyType === "HASH").map((k) => k.AttributeName);
|
|
957
|
+
}
|
|
912
958
|
function getRangeKey(keySchema) {
|
|
913
959
|
const range = keySchema.find((k) => k.KeyType === "RANGE");
|
|
914
960
|
return range?.AttributeName;
|
|
915
961
|
}
|
|
962
|
+
function getRangeKeys(keySchema) {
|
|
963
|
+
return keySchema.filter((k) => k.KeyType === "RANGE").map((k) => k.AttributeName);
|
|
964
|
+
}
|
|
965
|
+
function hasCompleteKey(item, keySchema) {
|
|
966
|
+
return keySchema.every((element) => Boolean(item[element.AttributeName]));
|
|
967
|
+
}
|
|
968
|
+
function mergeKeySchemas(...schemas) {
|
|
969
|
+
const merged = [];
|
|
970
|
+
for (const schema of schemas) {
|
|
971
|
+
for (const element of schema) {
|
|
972
|
+
if (!merged.some((existing) => existing.AttributeName === element.AttributeName)) {
|
|
973
|
+
merged.push(element);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return merged;
|
|
978
|
+
}
|
|
916
979
|
function deepClone(obj) {
|
|
917
980
|
return JSON.parse(JSON.stringify(obj));
|
|
918
981
|
}
|
|
@@ -922,8 +985,10 @@ function estimateItemSize(item) {
|
|
|
922
985
|
|
|
923
986
|
// src/expressions/key-condition.ts
|
|
924
987
|
function parseKeyCondition(expression, keySchema, context) {
|
|
925
|
-
const
|
|
926
|
-
const
|
|
988
|
+
const hashKeyNames = getHashKeys(keySchema);
|
|
989
|
+
const rangeKeyNames = getRangeKeys(keySchema);
|
|
990
|
+
const hashKeyName = hashKeyNames[0];
|
|
991
|
+
const rangeKeyName = rangeKeyNames[0];
|
|
927
992
|
const resolvedNames = context.expressionAttributeNames || {};
|
|
928
993
|
const resolvedValues = context.expressionAttributeValues || {};
|
|
929
994
|
function resolveName(name) {
|
|
@@ -968,17 +1033,17 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
968
1033
|
return s;
|
|
969
1034
|
}
|
|
970
1035
|
const normalizedExpression = stripOuterParens(expression);
|
|
971
|
-
const parts = normalizedExpression
|
|
972
|
-
|
|
973
|
-
|
|
1036
|
+
const parts = splitKeyConditions(normalizedExpression);
|
|
1037
|
+
const hashConditions = /* @__PURE__ */ new Map();
|
|
1038
|
+
const rangeConditions = /* @__PURE__ */ new Map();
|
|
974
1039
|
for (const part of parts) {
|
|
975
1040
|
const trimmed = stripOuterParens(part);
|
|
976
1041
|
const beginsWithMatch = trimmed.match(/^begins_with\s*\(\s*([#\w]+)\s*,\s*(:\w+)\s*\)$/i);
|
|
977
1042
|
if (beginsWithMatch) {
|
|
978
1043
|
const attrName = resolveName(beginsWithMatch[1]);
|
|
979
1044
|
const value = resolveValue(beginsWithMatch[2]);
|
|
980
|
-
if (attrName
|
|
981
|
-
|
|
1045
|
+
if (rangeKeyNames.includes(attrName)) {
|
|
1046
|
+
rangeConditions.set(attrName, { operator: "begins_with", value });
|
|
982
1047
|
} else {
|
|
983
1048
|
throw new ValidationException(`begins_with can only be used on sort key`);
|
|
984
1049
|
}
|
|
@@ -989,8 +1054,8 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
989
1054
|
const attrName = resolveName(betweenMatch[1]);
|
|
990
1055
|
const value1 = resolveValue(betweenMatch[2]);
|
|
991
1056
|
const value2 = resolveValue(betweenMatch[3]);
|
|
992
|
-
if (attrName
|
|
993
|
-
|
|
1057
|
+
if (rangeKeyNames.includes(attrName)) {
|
|
1058
|
+
rangeConditions.set(attrName, { operator: "BETWEEN", value: value1, value2 });
|
|
994
1059
|
} else {
|
|
995
1060
|
throw new ValidationException(`BETWEEN can only be used on sort key`);
|
|
996
1061
|
}
|
|
@@ -1001,13 +1066,13 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
1001
1066
|
const attrName = resolveName(comparisonMatch[1]);
|
|
1002
1067
|
const operator = comparisonMatch[2];
|
|
1003
1068
|
const value = resolveValue(comparisonMatch[3]);
|
|
1004
|
-
if (attrName
|
|
1069
|
+
if (hashKeyNames.includes(attrName)) {
|
|
1005
1070
|
if (operator !== "=") {
|
|
1006
|
-
throw new ValidationException(`
|
|
1071
|
+
throw new ValidationException(`Partition key condition must use = operator`);
|
|
1007
1072
|
}
|
|
1008
|
-
|
|
1009
|
-
} else if (attrName
|
|
1010
|
-
|
|
1073
|
+
hashConditions.set(attrName, value);
|
|
1074
|
+
} else if (rangeKeyNames.includes(attrName)) {
|
|
1075
|
+
rangeConditions.set(attrName, { operator, value });
|
|
1011
1076
|
} else {
|
|
1012
1077
|
throw new ValidationException(`Key condition references unknown attribute: ${attrName}`);
|
|
1013
1078
|
}
|
|
@@ -1015,30 +1080,112 @@ function parseKeyCondition(expression, keySchema, context) {
|
|
|
1015
1080
|
}
|
|
1016
1081
|
throw new ValidationException(`Invalid key condition expression: ${trimmed}`);
|
|
1017
1082
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1083
|
+
for (const key of hashKeyNames) {
|
|
1084
|
+
if (!hashConditions.has(key)) {
|
|
1085
|
+
throw new ValidationException(`Key condition must specify equality for partition key ${key}`);
|
|
1086
|
+
}
|
|
1020
1087
|
}
|
|
1088
|
+
let lastSortKeyIndex = -1;
|
|
1089
|
+
for (const [index, key] of rangeKeyNames.entries()) {
|
|
1090
|
+
const condition = rangeConditions.get(key);
|
|
1091
|
+
if (!condition) {
|
|
1092
|
+
if (lastSortKeyIndex !== -1) {
|
|
1093
|
+
throw new ValidationException(`Sort key conditions must reference a contiguous prefix of the key schema`);
|
|
1094
|
+
}
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (index > 0 && !rangeConditions.has(rangeKeyNames[index - 1])) {
|
|
1098
|
+
throw new ValidationException(`Sort key conditions must reference a contiguous prefix of the key schema`);
|
|
1099
|
+
}
|
|
1100
|
+
if (lastSortKeyIndex !== -1) {
|
|
1101
|
+
const previous = rangeConditions.get(rangeKeyNames[lastSortKeyIndex]);
|
|
1102
|
+
if (previous && previous.operator !== "=") {
|
|
1103
|
+
throw new ValidationException(`Only the last sort key condition can use a range operator`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
lastSortKeyIndex = index;
|
|
1107
|
+
}
|
|
1108
|
+
const orderedHashConditions = hashKeyNames.map((key) => ({
|
|
1109
|
+
key,
|
|
1110
|
+
value: hashConditions.get(key)
|
|
1111
|
+
}));
|
|
1112
|
+
const orderedRangeConditions = rangeKeyNames.filter((key) => rangeConditions.has(key)).map((key) => {
|
|
1113
|
+
const condition = rangeConditions.get(key);
|
|
1114
|
+
return {
|
|
1115
|
+
key,
|
|
1116
|
+
operator: condition.operator,
|
|
1117
|
+
value: condition.value,
|
|
1118
|
+
value2: condition.value2
|
|
1119
|
+
};
|
|
1120
|
+
});
|
|
1021
1121
|
return {
|
|
1022
1122
|
hashKey: hashKeyName,
|
|
1023
|
-
hashValue,
|
|
1123
|
+
hashValue: hashConditions.get(hashKeyName),
|
|
1024
1124
|
rangeKey: rangeKeyName,
|
|
1025
|
-
rangeCondition
|
|
1125
|
+
rangeCondition: orderedRangeConditions[0] ? {
|
|
1126
|
+
operator: orderedRangeConditions[0].operator,
|
|
1127
|
+
value: orderedRangeConditions[0].value,
|
|
1128
|
+
value2: orderedRangeConditions[0].value2
|
|
1129
|
+
} : void 0,
|
|
1130
|
+
hashConditions: orderedHashConditions,
|
|
1131
|
+
rangeConditions: orderedRangeConditions
|
|
1026
1132
|
};
|
|
1027
1133
|
}
|
|
1028
1134
|
function matchesKeyCondition(item, condition) {
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1135
|
+
for (const hashCondition of condition.hashConditions) {
|
|
1136
|
+
const itemHashValue = item[hashCondition.key];
|
|
1137
|
+
if (!itemHashValue || !attributeEquals(itemHashValue, hashCondition.value)) {
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1032
1140
|
}
|
|
1033
|
-
|
|
1034
|
-
const itemRangeValue = item[
|
|
1141
|
+
for (const rangeCondition of condition.rangeConditions) {
|
|
1142
|
+
const itemRangeValue = item[rangeCondition.key];
|
|
1035
1143
|
if (!itemRangeValue) {
|
|
1036
1144
|
return false;
|
|
1037
1145
|
}
|
|
1038
|
-
|
|
1146
|
+
if (!matchesRangeCondition(itemRangeValue, rangeCondition)) {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1039
1149
|
}
|
|
1040
1150
|
return true;
|
|
1041
1151
|
}
|
|
1152
|
+
function splitKeyConditions(expression) {
|
|
1153
|
+
const parts = [];
|
|
1154
|
+
let depth = 0;
|
|
1155
|
+
let segmentStart = 0;
|
|
1156
|
+
let betweenPending = false;
|
|
1157
|
+
for (let index = 0; index < expression.length; index++) {
|
|
1158
|
+
const char = expression[index];
|
|
1159
|
+
if (char === "(") {
|
|
1160
|
+
depth++;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (char === ")") {
|
|
1164
|
+
depth--;
|
|
1165
|
+
continue;
|
|
1166
|
+
}
|
|
1167
|
+
if (depth !== 0) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (/^BETWEEN\b/i.test(expression.slice(index))) {
|
|
1171
|
+
betweenPending = true;
|
|
1172
|
+
index += "BETWEEN".length - 1;
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
if (/^\s+AND\s+/i.test(expression.slice(index))) {
|
|
1176
|
+
if (betweenPending) {
|
|
1177
|
+
betweenPending = false;
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
parts.push(expression.slice(segmentStart, index).trim());
|
|
1181
|
+
const andMatch = expression.slice(index).match(/^\s+AND\s+/i);
|
|
1182
|
+
index += andMatch[0].length - 1;
|
|
1183
|
+
segmentStart = index + 1;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
parts.push(expression.slice(segmentStart).trim());
|
|
1187
|
+
return parts.filter(Boolean);
|
|
1188
|
+
}
|
|
1042
1189
|
function matchesRangeCondition(value, condition) {
|
|
1043
1190
|
const cmp5 = compareValues2(value, condition.value);
|
|
1044
1191
|
switch (condition.operator) {
|
|
@@ -1659,9 +1806,10 @@ function query(store, input) {
|
|
|
1659
1806
|
let items;
|
|
1660
1807
|
let lastEvaluatedKey;
|
|
1661
1808
|
if (input.IndexName) {
|
|
1809
|
+
const hashValues = Object.fromEntries(keyCondition.hashConditions.map((condition) => [condition.key, condition.value]));
|
|
1662
1810
|
const result = table.queryIndex(
|
|
1663
1811
|
input.IndexName,
|
|
1664
|
-
|
|
1812
|
+
hashValues,
|
|
1665
1813
|
{
|
|
1666
1814
|
scanIndexForward: input.ScanIndexForward,
|
|
1667
1815
|
exclusiveStartKey: input.ExclusiveStartKey
|
|
@@ -1670,8 +1818,9 @@ function query(store, input) {
|
|
|
1670
1818
|
items = result.items;
|
|
1671
1819
|
lastEvaluatedKey = result.lastEvaluatedKey;
|
|
1672
1820
|
} else {
|
|
1821
|
+
const hashValues = Object.fromEntries(keyCondition.hashConditions.map((condition) => [condition.key, condition.value]));
|
|
1673
1822
|
const result = table.queryByHashKey(
|
|
1674
|
-
|
|
1823
|
+
hashValues,
|
|
1675
1824
|
{
|
|
1676
1825
|
scanIndexForward: input.ScanIndexForward,
|
|
1677
1826
|
exclusiveStartKey: input.ExclusiveStartKey
|
|
@@ -1696,24 +1845,11 @@ function query(store, input) {
|
|
|
1696
1845
|
items = items.slice(0, input.Limit);
|
|
1697
1846
|
if (items.length > 0) {
|
|
1698
1847
|
const lastItem = items[items.length - 1];
|
|
1699
|
-
lastEvaluatedKey =
|
|
1700
|
-
const hashKey = table.getHashKeyName();
|
|
1701
|
-
const rangeKey = table.getRangeKeyName();
|
|
1702
|
-
if (lastItem[hashKey]) {
|
|
1703
|
-
lastEvaluatedKey[hashKey] = lastItem[hashKey];
|
|
1704
|
-
}
|
|
1705
|
-
if (rangeKey && lastItem[rangeKey]) {
|
|
1706
|
-
lastEvaluatedKey[rangeKey] = lastItem[rangeKey];
|
|
1707
|
-
}
|
|
1848
|
+
lastEvaluatedKey = extractKey(lastItem, table.keySchema);
|
|
1708
1849
|
if (input.IndexName) {
|
|
1709
1850
|
const indexKeySchema = table.getIndexKeySchema(input.IndexName);
|
|
1710
1851
|
if (indexKeySchema) {
|
|
1711
|
-
|
|
1712
|
-
const attrValue = lastItem[key.AttributeName];
|
|
1713
|
-
if (attrValue) {
|
|
1714
|
-
lastEvaluatedKey[key.AttributeName] = attrValue;
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1852
|
+
lastEvaluatedKey = extractKey(lastItem, mergeKeySchemas(indexKeySchema, table.keySchema));
|
|
1717
1853
|
}
|
|
1718
1854
|
}
|
|
1719
1855
|
}
|
|
@@ -1780,24 +1916,11 @@ function scan(store, input) {
|
|
|
1780
1916
|
items = items.slice(0, input.Limit);
|
|
1781
1917
|
if (items.length > 0) {
|
|
1782
1918
|
const lastItem = items[items.length - 1];
|
|
1783
|
-
lastEvaluatedKey =
|
|
1784
|
-
const hashKey = table.getHashKeyName();
|
|
1785
|
-
const rangeKey = table.getRangeKeyName();
|
|
1786
|
-
if (lastItem[hashKey]) {
|
|
1787
|
-
lastEvaluatedKey[hashKey] = lastItem[hashKey];
|
|
1788
|
-
}
|
|
1789
|
-
if (rangeKey && lastItem[rangeKey]) {
|
|
1790
|
-
lastEvaluatedKey[rangeKey] = lastItem[rangeKey];
|
|
1791
|
-
}
|
|
1919
|
+
lastEvaluatedKey = extractKey(lastItem, table.keySchema);
|
|
1792
1920
|
if (input.IndexName) {
|
|
1793
1921
|
const indexKeySchema = table.getIndexKeySchema(input.IndexName);
|
|
1794
1922
|
if (indexKeySchema) {
|
|
1795
|
-
|
|
1796
|
-
const attrValue = lastItem[key.AttributeName];
|
|
1797
|
-
if (attrValue) {
|
|
1798
|
-
lastEvaluatedKey[key.AttributeName] = attrValue;
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1923
|
+
lastEvaluatedKey = extractKey(lastItem, mergeKeySchemas(indexKeySchema, table.keySchema));
|
|
1801
1924
|
}
|
|
1802
1925
|
}
|
|
1803
1926
|
}
|
|
@@ -2594,12 +2717,7 @@ var Table = class {
|
|
|
2594
2717
|
}
|
|
2595
2718
|
}
|
|
2596
2719
|
buildIndexKey(item, keySchema) {
|
|
2597
|
-
|
|
2598
|
-
if (!item[hashAttr]) {
|
|
2599
|
-
return null;
|
|
2600
|
-
}
|
|
2601
|
-
const rangeAttr = getRangeKey(keySchema);
|
|
2602
|
-
if (rangeAttr && !item[rangeAttr]) {
|
|
2720
|
+
if (!hasCompleteKey(item, keySchema)) {
|
|
2603
2721
|
return null;
|
|
2604
2722
|
}
|
|
2605
2723
|
return serializeKey(extractKey(item, keySchema), keySchema);
|
|
@@ -2645,47 +2763,7 @@ var Table = class {
|
|
|
2645
2763
|
};
|
|
2646
2764
|
}
|
|
2647
2765
|
queryByHashKey(hashValue, options) {
|
|
2648
|
-
|
|
2649
|
-
const rangeAttr = this.getRangeKeyName();
|
|
2650
|
-
const matchingItems = [];
|
|
2651
|
-
for (const item of this.items.values()) {
|
|
2652
|
-
const itemHashValue = item[hashAttr];
|
|
2653
|
-
const queryHashValue = hashValue[hashAttr];
|
|
2654
|
-
if (itemHashValue && queryHashValue && this.attributeEquals(itemHashValue, queryHashValue)) {
|
|
2655
|
-
matchingItems.push(deepClone(item));
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
if (rangeAttr) {
|
|
2659
|
-
matchingItems.sort((a, b) => {
|
|
2660
|
-
const aVal = a[rangeAttr];
|
|
2661
|
-
const bVal = b[rangeAttr];
|
|
2662
|
-
if (!aVal && !bVal) return 0;
|
|
2663
|
-
if (!aVal) return 1;
|
|
2664
|
-
if (!bVal) return -1;
|
|
2665
|
-
const cmp5 = this.compareAttributes(aVal, bVal);
|
|
2666
|
-
return options?.scanIndexForward === false ? -cmp5 : cmp5;
|
|
2667
|
-
});
|
|
2668
|
-
}
|
|
2669
|
-
let startIdx = 0;
|
|
2670
|
-
if (options?.exclusiveStartKey) {
|
|
2671
|
-
const startKey = serializeKey(options.exclusiveStartKey, this.keySchema);
|
|
2672
|
-
startIdx = matchingItems.findIndex(
|
|
2673
|
-
(item) => serializeKey(extractKey(item, this.keySchema), this.keySchema) === startKey
|
|
2674
|
-
);
|
|
2675
|
-
if (startIdx !== -1) {
|
|
2676
|
-
startIdx++;
|
|
2677
|
-
} else {
|
|
2678
|
-
startIdx = 0;
|
|
2679
|
-
}
|
|
2680
|
-
}
|
|
2681
|
-
const limit = options?.limit;
|
|
2682
|
-
const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
|
|
2683
|
-
const hasMore = limit ? startIdx + limit < matchingItems.length : false;
|
|
2684
|
-
const lastItem = sliced[sliced.length - 1];
|
|
2685
|
-
return {
|
|
2686
|
-
items: sliced,
|
|
2687
|
-
lastEvaluatedKey: hasMore && lastItem ? extractKey(lastItem, this.keySchema) : void 0
|
|
2688
|
-
};
|
|
2766
|
+
return this.queryBySchema(this.keySchema, hashValue, this.keySchema, false, options);
|
|
2689
2767
|
}
|
|
2690
2768
|
queryIndex(indexName, hashValue, options) {
|
|
2691
2769
|
const gsi = this.globalSecondaryIndexes.get(indexName);
|
|
@@ -2694,69 +2772,16 @@ var Table = class {
|
|
|
2694
2772
|
if (!indexData) {
|
|
2695
2773
|
throw new Error(`Index ${indexName} not found`);
|
|
2696
2774
|
}
|
|
2697
|
-
const
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
continue;
|
|
2705
|
-
}
|
|
2706
|
-
if (indexRangeAttr && !item[indexRangeAttr]) {
|
|
2707
|
-
continue;
|
|
2708
|
-
}
|
|
2709
|
-
matchingItems.push(deepClone(item));
|
|
2710
|
-
}
|
|
2711
|
-
if (indexRangeAttr) {
|
|
2712
|
-
matchingItems.sort((a, b) => {
|
|
2713
|
-
const aVal = a[indexRangeAttr];
|
|
2714
|
-
const bVal = b[indexRangeAttr];
|
|
2715
|
-
if (!aVal && !bVal) return 0;
|
|
2716
|
-
if (!aVal) return 1;
|
|
2717
|
-
if (!bVal) return -1;
|
|
2718
|
-
const cmp5 = this.compareAttributes(aVal, bVal);
|
|
2719
|
-
return options?.scanIndexForward === false ? -cmp5 : cmp5;
|
|
2720
|
-
});
|
|
2721
|
-
}
|
|
2722
|
-
let startIdx = 0;
|
|
2723
|
-
if (options?.exclusiveStartKey) {
|
|
2724
|
-
const combinedKeySchema = [...indexData.keySchema];
|
|
2725
|
-
const tableRangeKey = this.getRangeKeyName();
|
|
2726
|
-
if (tableRangeKey && !combinedKeySchema.some((k) => k.AttributeName === tableRangeKey)) {
|
|
2727
|
-
combinedKeySchema.push({ AttributeName: tableRangeKey, KeyType: "RANGE" });
|
|
2728
|
-
}
|
|
2729
|
-
const tableHashKey = this.getHashKeyName();
|
|
2730
|
-
if (!combinedKeySchema.some((k) => k.AttributeName === tableHashKey)) {
|
|
2731
|
-
combinedKeySchema.push({ AttributeName: tableHashKey, KeyType: "HASH" });
|
|
2732
|
-
}
|
|
2733
|
-
const startKey = serializeKey(options.exclusiveStartKey, combinedKeySchema);
|
|
2734
|
-
startIdx = matchingItems.findIndex(
|
|
2735
|
-
(item) => serializeKey(extractKey(item, combinedKeySchema), combinedKeySchema) === startKey
|
|
2736
|
-
);
|
|
2737
|
-
if (startIdx !== -1) {
|
|
2738
|
-
startIdx++;
|
|
2739
|
-
} else {
|
|
2740
|
-
startIdx = 0;
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
const limit = options?.limit;
|
|
2744
|
-
const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
|
|
2745
|
-
const hasMore = limit ? startIdx + limit < matchingItems.length : false;
|
|
2746
|
-
const lastItem = sliced[sliced.length - 1];
|
|
2747
|
-
let lastEvaluatedKey;
|
|
2748
|
-
if (hasMore && lastItem) {
|
|
2749
|
-
lastEvaluatedKey = extractKey(lastItem, this.keySchema);
|
|
2750
|
-
for (const keyElement of indexData.keySchema) {
|
|
2751
|
-
const attrValue = lastItem[keyElement.AttributeName];
|
|
2752
|
-
if (attrValue) {
|
|
2753
|
-
lastEvaluatedKey[keyElement.AttributeName] = attrValue;
|
|
2754
|
-
}
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2775
|
+
const result = this.queryBySchema(
|
|
2776
|
+
indexData.keySchema,
|
|
2777
|
+
hashValue,
|
|
2778
|
+
mergeKeySchemas(indexData.keySchema, this.keySchema),
|
|
2779
|
+
true,
|
|
2780
|
+
options
|
|
2781
|
+
);
|
|
2757
2782
|
return {
|
|
2758
|
-
items:
|
|
2759
|
-
lastEvaluatedKey,
|
|
2783
|
+
items: result.items,
|
|
2784
|
+
lastEvaluatedKey: result.lastEvaluatedKey,
|
|
2760
2785
|
indexKeySchema: indexData.keySchema
|
|
2761
2786
|
};
|
|
2762
2787
|
}
|
|
@@ -2767,18 +2792,19 @@ var Table = class {
|
|
|
2767
2792
|
if (!indexData) {
|
|
2768
2793
|
throw new Error(`Index ${indexName} not found`);
|
|
2769
2794
|
}
|
|
2770
|
-
const indexHashAttr = getHashKey(indexData.keySchema);
|
|
2771
2795
|
const matchingItems = [];
|
|
2772
2796
|
for (const item of this.items.values()) {
|
|
2773
|
-
if (item
|
|
2797
|
+
if (hasCompleteKey(item, indexData.keySchema)) {
|
|
2774
2798
|
matchingItems.push(deepClone(item));
|
|
2775
2799
|
}
|
|
2776
2800
|
}
|
|
2801
|
+
matchingItems.sort((a, b) => this.compareByKeySchema(a, b, indexData.keySchema, true));
|
|
2777
2802
|
let startIdx = 0;
|
|
2778
2803
|
if (exclusiveStartKey) {
|
|
2779
|
-
const
|
|
2804
|
+
const paginationKeySchema = mergeKeySchemas(indexData.keySchema, this.keySchema);
|
|
2805
|
+
const startKey = serializeKey(exclusiveStartKey, paginationKeySchema);
|
|
2780
2806
|
startIdx = matchingItems.findIndex(
|
|
2781
|
-
(item) => serializeKey(extractKey(item,
|
|
2807
|
+
(item) => serializeKey(extractKey(item, paginationKeySchema), paginationKeySchema) === startKey
|
|
2782
2808
|
);
|
|
2783
2809
|
if (startIdx !== -1) {
|
|
2784
2810
|
startIdx++;
|
|
@@ -2791,13 +2817,7 @@ var Table = class {
|
|
|
2791
2817
|
const lastItem = sliced[sliced.length - 1];
|
|
2792
2818
|
let lastEvaluatedKey;
|
|
2793
2819
|
if (hasMore && lastItem) {
|
|
2794
|
-
lastEvaluatedKey = extractKey(lastItem, this.keySchema);
|
|
2795
|
-
for (const keyElement of indexData.keySchema) {
|
|
2796
|
-
const attrValue = lastItem[keyElement.AttributeName];
|
|
2797
|
-
if (attrValue) {
|
|
2798
|
-
lastEvaluatedKey[keyElement.AttributeName] = attrValue;
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2820
|
+
lastEvaluatedKey = extractKey(lastItem, mergeKeySchemas(indexData.keySchema, this.keySchema));
|
|
2801
2821
|
}
|
|
2802
2822
|
return {
|
|
2803
2823
|
items: sliced,
|
|
@@ -2823,6 +2843,67 @@ var Table = class {
|
|
|
2823
2843
|
if ("B" in a && "B" in b) return a.B.localeCompare(b.B);
|
|
2824
2844
|
return 0;
|
|
2825
2845
|
}
|
|
2846
|
+
compareByKeySchema(a, b, keySchema, scanIndexForward = true) {
|
|
2847
|
+
for (const attributeName of [...getHashKeys(keySchema), ...getRangeKeys(keySchema)]) {
|
|
2848
|
+
const aVal = a[attributeName];
|
|
2849
|
+
const bVal = b[attributeName];
|
|
2850
|
+
if (!aVal && !bVal) {
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
if (!aVal) {
|
|
2854
|
+
return 1;
|
|
2855
|
+
}
|
|
2856
|
+
if (!bVal) {
|
|
2857
|
+
return -1;
|
|
2858
|
+
}
|
|
2859
|
+
const cmp5 = this.compareAttributes(aVal, bVal);
|
|
2860
|
+
if (cmp5 !== 0) {
|
|
2861
|
+
return scanIndexForward ? cmp5 : -cmp5;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
return 0;
|
|
2865
|
+
}
|
|
2866
|
+
queryBySchema(keySchema, hashValue, paginationKeySchema, requireCompleteKey, options) {
|
|
2867
|
+
const hashAttrs = getHashKeys(keySchema);
|
|
2868
|
+
const rangeAttrs = getRangeKeys(keySchema);
|
|
2869
|
+
const matchingItems = [];
|
|
2870
|
+
for (const item of this.items.values()) {
|
|
2871
|
+
if (requireCompleteKey && !hasCompleteKey(item, keySchema)) {
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
const matchesPartitionKey = hashAttrs.every((attr) => {
|
|
2875
|
+
const itemHashValue = item[attr];
|
|
2876
|
+
const queryHashValue = hashValue[attr];
|
|
2877
|
+
return itemHashValue && queryHashValue && this.attributeEquals(itemHashValue, queryHashValue);
|
|
2878
|
+
});
|
|
2879
|
+
if (matchesPartitionKey) {
|
|
2880
|
+
matchingItems.push(deepClone(item));
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
if (rangeAttrs.length > 0) {
|
|
2884
|
+
matchingItems.sort((a, b) => this.compareByKeySchema(a, b, keySchema, options?.scanIndexForward !== false));
|
|
2885
|
+
}
|
|
2886
|
+
let startIdx = 0;
|
|
2887
|
+
if (options?.exclusiveStartKey) {
|
|
2888
|
+
const startKey = serializeKey(options.exclusiveStartKey, paginationKeySchema);
|
|
2889
|
+
startIdx = matchingItems.findIndex(
|
|
2890
|
+
(item) => serializeKey(extractKey(item, paginationKeySchema), paginationKeySchema) === startKey
|
|
2891
|
+
);
|
|
2892
|
+
if (startIdx !== -1) {
|
|
2893
|
+
startIdx++;
|
|
2894
|
+
} else {
|
|
2895
|
+
startIdx = 0;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
const limit = options?.limit;
|
|
2899
|
+
const sliced = limit ? matchingItems.slice(startIdx, startIdx + limit) : matchingItems.slice(startIdx);
|
|
2900
|
+
const hasMore = limit ? startIdx + limit < matchingItems.length : false;
|
|
2901
|
+
const lastItem = sliced[sliced.length - 1];
|
|
2902
|
+
return {
|
|
2903
|
+
items: sliced,
|
|
2904
|
+
lastEvaluatedKey: hasMore && lastItem ? extractKey(lastItem, paginationKeySchema) : void 0
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2826
2907
|
getAllItems() {
|
|
2827
2908
|
return Array.from(this.items.values()).map((item) => deepClone(item));
|
|
2828
2909
|
}
|