dynamodb-toolbox
Version:
Lightweight and type-safe query builder for DynamoDB and TypeScript.
180 lines (179 loc) • 10.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.queryParams = void 0;
const index_js_1 = require("../../../../entity/actions/parseCondition/index.js");
const index_js_2 = require("../../../../entity/actions/parsePaths/index.js");
const index_js_3 = require("../../../../entity/utils/index.js");
const index_js_4 = require("../../../../errors/index.js");
const capacity_js_1 = require("../../../../options/capacity.js");
const consistent_js_1 = require("../../../../options/consistent.js");
const entityAttrFilter_js_1 = require("../../../../options/entityAttrFilter.js");
const index_js_5 = require("../../../../options/index.js");
const limit_js_1 = require("../../../../options/limit.js");
const maxPages_js_1 = require("../../../../options/maxPages.js");
const noEntityMatchBehavior_js_1 = require("../../../../options/noEntityMatchBehavior.js");
const rejectExtraOptions_js_1 = require("../../../../options/rejectExtraOptions.js");
const select_js_1 = require("../../../../options/select.js");
const showEntityAttr_js_1 = require("../../../../options/showEntityAttr.js");
const tableName_js_1 = require("../../../../options/tableName.js");
const expressCondition_js_1 = require("../../../../schema/actions/parseCondition/expressCondition/expressCondition.js");
const index_js_6 = require("../../../../schema/actions/parseCondition/index.js");
const deduper_js_1 = require("../../../../schema/actions/utils/deduper.js");
const schema_js_1 = require("../../../../schema/any/schema.js");
const isEmpty_js_1 = require("../../../../utils/isEmpty.js");
const isBoolean_js_1 = require("../../../../utils/validation/isBoolean.js");
const parseQuery_js_1 = require("./parseQuery.js");
const defaultAnySchema = new schema_js_1.AnySchema({ required: 'never' });
const queryParams = (table, entities = [], query, options = {}) => {
var _a;
const { entityAttributeSavedAs, indexes } = table;
const { index } = query;
const { capacity, consistent, exclusiveStartKey, limit, maxPages, reverse, select, filter, filters: _filters, attributes: _attributes, tableName, entityAttrFilter = entities.every(entity => (0, index_js_3.isEntityAttrEnabled)(entity.entityAttribute)) &&
!isKeyAttribute(index !== undefined ? (_a = indexes[index]) !== null && _a !== void 0 ? _a : table : table, entityAttributeSavedAs), showEntityAttr, tagEntities, noEntityMatchBehavior, ...extraOptions } = options;
(0, rejectExtraOptions_js_1.rejectExtraOptions)(extraOptions);
const filters = (_filters !== null && _filters !== void 0 ? _filters : {});
const attributes = _attributes;
if (tableName !== undefined) {
(0, tableName_js_1.parseTableNameOption)(tableName);
}
const commandOptions = {
TableName: tableName !== null && tableName !== void 0 ? tableName : table.getName()
};
if (capacity !== undefined) {
commandOptions.ReturnConsumedCapacity = (0, capacity_js_1.parseCapacityOption)(capacity);
}
if (index !== undefined) {
commandOptions.IndexName = (0, index_js_5.parseIndexOption)(table, index);
}
if (consistent !== undefined) {
commandOptions.ConsistentRead = (0, consistent_js_1.parseConsistentOption)(consistent, index !== undefined ? table.indexes[index] : undefined);
}
if (exclusiveStartKey !== undefined) {
commandOptions.ExclusiveStartKey = exclusiveStartKey;
}
if (limit !== undefined) {
commandOptions.Limit = (0, limit_js_1.parseLimitOption)(limit);
}
if (maxPages !== undefined) {
// maxPages is a meta-option, validated but not used here
(0, maxPages_js_1.parseMaxPagesOption)(maxPages);
}
if (reverse !== undefined) {
if (!(0, isBoolean_js_1.isBoolean)(reverse)) {
throw new index_js_4.DynamoDBToolboxError('queryCommand.invalidReverseOption', {
message: 'Invalid "reverse" options: Must be a boolean',
payload: { reverse }
});
}
commandOptions.ScanIndexForward = !reverse;
}
if (select !== undefined) {
commandOptions.Select = (0, select_js_1.parseSelectOption)(select, { index, attributes });
}
// entityAttrFilter is a meta-option, validated but not used here
(0, entityAttrFilter_js_1.parseEntityAttrFilterOption)(entityAttrFilter, entities, filters);
if (showEntityAttr !== undefined) {
// showEntityAttr is a meta-option, validated but not used here
(0, showEntityAttr_js_1.parseShowEntityAttrOption)(showEntityAttr);
}
if (tagEntities !== undefined) {
// tagEntities is a meta-option, validated but not used here
if (!(0, isBoolean_js_1.isBoolean)(tagEntities)) {
throw new index_js_4.DynamoDBToolboxError('queryCommand.invalidTagEntitiesOption', {
message: `Invalid 'tagEntities' option: '${String(tagEntities)}'. 'tagEntities' must be a boolean.`,
payload: { tagEntities }
});
}
}
if (noEntityMatchBehavior !== undefined) {
// noEntityMatchBehavior is a meta-option, validated but not used here
(0, noEntityMatchBehavior_js_1.parseNoEntityMatchBehavior)(noEntityMatchBehavior);
}
const expressionAttributeNames = {};
const expressionAttributeValues = {};
// --- PROJECTION ---
if (attributes !== undefined && attributes.length > 0) {
if (entities.length === 0) {
// TODO: Handle projections even without entities
}
else {
const transformedPaths = new deduper_js_1.Deduper({ serializer: value => value });
for (const entity of entities) {
const entityTransformedPaths = entity
.build(index_js_2.EntityPathParser)
.transform(attributes, { strict: false });
if (entityTransformedPaths.length === 0) {
throw new index_js_4.DynamoDBToolboxError('queryCommand.invalidProjectionExpression', {
message: `Unable to match any expression attribute path with entity: ${entity.entityName}`,
payload: { entity: entity.entityName }
});
}
for (const transformedPath of entityTransformedPaths) {
transformedPaths.push(transformedPath);
}
}
const expression = index_js_2.EntityPathParser.express(transformedPaths.values);
Object.assign(expressionAttributeNames, expression.ExpressionAttributeNames);
// include the entityAttrSavedAs for faster formatting
const { entityAttributeSavedAs } = table;
if (!Object.values(expression.ExpressionAttributeNames).includes(entityAttributeSavedAs)) {
commandOptions.ProjectionExpression = [expression.ProjectionExpression, '#_et'].join(', ');
expressionAttributeNames['#_et'] = entityAttributeSavedAs;
}
else {
commandOptions.ProjectionExpression = expression.ProjectionExpression;
}
}
}
// --- KEY CONDITION ---
const { KeyConditionExpression, ExpressionAttributeNames: keyConditionExpressionAttributeNames, ExpressionAttributeValues: keyConditionExpressionAttributeValues } = (0, parseQuery_js_1.parseQuery)(table, query);
commandOptions.KeyConditionExpression = KeyConditionExpression;
Object.assign(expressionAttributeNames, keyConditionExpressionAttributeNames);
Object.assign(expressionAttributeValues, keyConditionExpressionAttributeValues);
// --- FILTERS ---
if (entities.length === 0 && filter !== undefined) {
const expression = new index_js_6.ConditionParser(defaultAnySchema).parse(filter, {
// Distinguish from KeyConditionExpression
expressionId: '1'
});
Object.assign(expressionAttributeNames, expression.ExpressionAttributeNames);
Object.assign(expressionAttributeValues, expression.ExpressionAttributeValues);
commandOptions.FilterExpression = expression.ConditionExpression;
}
if (entities.length > 0) {
const transformedFilters = [];
for (const entity of entities) {
const entityOptionsFilter = filters[entity.entityName];
const entityNameFilter = entityAttrFilter
? // NOTE: We validated that all entities have entityAttr enabled
{
attr: (0, index_js_3.getEntityAttrOptionValue)(entity.entityAttribute, 'name'),
eq: entity.entityName
}
: undefined;
if (entityOptionsFilter === undefined && entityNameFilter === undefined) {
continue;
}
const transformedFilter = entity.build(index_js_1.EntityConditionParser).transform({
and: [entityNameFilter, entityOptionsFilter].filter(Boolean)
});
transformedFilters.push(transformedFilter);
}
if (transformedFilters.length > 0) {
// Prefix w. '1' to distinguish from KeyConditionExpression
const expression = (0, expressCondition_js_1.expressCondition)({ or: transformedFilters }, '1');
Object.assign(expressionAttributeNames, expression.ExpressionAttributeNames);
Object.assign(expressionAttributeValues, expression.ExpressionAttributeValues);
commandOptions.FilterExpression = expression.ConditionExpression;
}
}
if (!(0, isEmpty_js_1.isEmpty)(expressionAttributeNames)) {
commandOptions.ExpressionAttributeNames = expressionAttributeNames;
}
if (!(0, isEmpty_js_1.isEmpty)(expressionAttributeValues)) {
commandOptions.ExpressionAttributeValues = expressionAttributeValues;
}
return commandOptions;
};
exports.queryParams = queryParams;
const isKeyAttribute = ({ partitionKey, sortKey }, attrSavedAs) => (partitionKey === null || partitionKey === void 0 ? void 0 : partitionKey.name) === attrSavedAs || (sortKey === null || sortKey === void 0 ? void 0 : sortKey.name) === attrSavedAs;