dynamodb-toolbox
Version:
Lightweight and type-safe query builder for DynamoDB and TypeScript.
150 lines (149 loc) • 6.27 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseQuery = void 0;
const index_js_1 = require("../../../../errors/index.js");
const index_js_2 = require("../../../../schema/actions/parseCondition/index.js");
const schema_js_1 = require("../../../../schema/binary/schema.js");
const schema_js_2 = require("../../../../schema/item/schema.js");
const schema_js_3 = require("../../../../schema/number/schema.js");
const schema_js_4 = require("../../../../schema/string/schema.js");
const index_js_3 = require("../../../../table/index.js");
const pick_js_1 = require("../../../../utils/pick.js");
const isArray_js_1 = require("../../../../utils/validation/isArray.js");
const isObject_js_1 = require("../../../../utils/validation/isObject.js");
const queryOperatorSet = new Set([
'eq',
'gt',
'gte',
'lt',
'lte',
'between',
'beginsWith'
]);
const getIndexKeySchema = (key) => {
switch (key.type) {
case 'number':
return new schema_js_3.NumberSchema({ big: true });
case 'string':
return new schema_js_4.StringSchema({});
case 'binary':
return new schema_js_1.BinarySchema({});
}
};
const parseQuery = (table, query) => {
const queryIndex = getQueryIndex(table, query);
const { partitionKeys, sortKeys } = flattenQuerySchema(table, queryIndex);
const { partition, range } = query;
const isPartitionArray = (0, isArray_js_1.isArray)(partition);
const hasRange = range !== undefined;
const isRangeArray = hasRange && (0, isArray_js_1.isArray)(range);
if (!(queryIndex instanceof index_js_3.Table) && queryIndex.type === 'global') {
if ('partitionKeys' in queryIndex && !isPartitionArray) {
throw new index_js_1.DynamoDBToolboxError('queryCommand.invalidPartition', {
message: 'Invalid query partition. Expected: Array.',
payload: { partition }
});
}
if (hasRange && 'sortKeys' in queryIndex && !isRangeArray) {
throw new index_js_1.DynamoDBToolboxError('queryCommand.invalidRange', {
message: 'Invalid query range. Expected: Array.',
payload: { range }
});
}
}
const partitions = isPartitionArray ? partition : [partition];
if (partitions.length !== partitionKeys.length) {
throw new index_js_1.DynamoDBToolboxError('queryCommand.invalidPartition', {
message: `Invalid number of query partitions. Expected: ${partitionKeys.length}.`,
payload: { partition: partitions }
});
}
const ranges = hasRange ? (isRangeArray ? range : [range]) : [];
if (ranges.length > sortKeys.length) {
throw new index_js_1.DynamoDBToolboxError('queryCommand.invalidRange', {
message: `Invalid number of query ranges. Expected: Less than or equal to ${sortKeys.length}.`,
payload: { range: ranges }
});
}
const keyConditions = [];
let partitionIndex = 0;
for (const partition of partitions) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
keyConditions.push({ attr: partitionKeys[partitionIndex].name, eq: partition });
partitionIndex++;
}
if (hasRange) {
let hasRangeObject = false;
let rangeIndex = 0;
for (const range of ranges) {
if (hasRangeObject) {
throw new index_js_1.DynamoDBToolboxError('queryCommand.invalidRange', {
message: 'Invalid query range: Range object must be provided last.',
payload: { range }
});
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const sortKey = sortKeys[rangeIndex];
if ((0, isObject_js_1.isObject)(range)) {
keyConditions.push({
attr: sortKey.name,
...(0, pick_js_1.pick)(range, ...queryOperatorSet)
});
hasRangeObject = true;
}
else {
keyConditions.push({ attr: sortKey.name, eq: range });
}
rangeIndex++;
}
}
const indexSchema = new schema_js_2.ItemSchema(Object.fromEntries([...partitionKeys, ...sortKeys].map(key => [key.name, getIndexKeySchema(key)])));
const conditionParser = new index_js_2.ConditionParser(indexSchema);
const { ConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues } = conditionParser.parse({ and: keyConditions }, { expressionId: '0' });
return {
KeyConditionExpression: ConditionExpression,
ExpressionAttributeNames,
ExpressionAttributeValues
};
};
exports.parseQuery = parseQuery;
const getQueryIndex = (table, { index }) => {
if (index === undefined) {
return table;
}
const indexSchema = table.indexes[index];
if (indexSchema === undefined) {
const indexes = Object.keys(table.indexes);
const hasIndex = indexes.length > 0;
throw new index_js_1.DynamoDBToolboxError('queryCommand.invalidIndex', {
message: `Unknown index: ${index}. ${hasIndex ? ` Expected one of: ${indexes.join(', ')}.` : ''}`,
payload: { received: index, ...(hasIndex ? { expected: indexes } : {}) }
});
}
return indexSchema;
};
const flattenQuerySchema = (table, queryIndex) => {
var _a;
if (queryIndex instanceof index_js_3.Table) {
return {
partitionKeys: [queryIndex.partitionKey],
sortKeys: queryIndex.sortKey ? [queryIndex.sortKey] : []
};
}
switch (queryIndex.type) {
case 'global':
return {
partitionKeys: (_a = queryIndex.partitionKeys) !== null && _a !== void 0 ? _a : [queryIndex.partitionKey],
sortKeys: queryIndex.sortKeys
? queryIndex.sortKeys
: queryIndex.sortKey
? [queryIndex.sortKey]
: []
};
case 'local':
return {
partitionKeys: [table.partitionKey],
sortKeys: [queryIndex.sortKey]
};
}
};