UNPKG

dynamodb-toolbox

Version:

Lightweight and type-safe query builder for DynamoDB and TypeScript.

165 lines (164 loc) 8.95 kB
import { EntityConditionParser } from '../../../../entity/actions/parseCondition/index.js'; import { EntityPathParser } from '../../../../entity/actions/parsePaths/index.js'; import { getEntityAttrOptionValue, isEntityAttrEnabled } from '../../../../entity/utils/index.js'; import { DynamoDBToolboxError } from '../../../../errors/index.js'; import { parseCapacityOption } from '../../../../options/capacity.js'; import { parseConsistentOption } from '../../../../options/consistent.js'; import { parseEntityAttrFilterOption } from '../../../../options/entityAttrFilter.js'; import { parseIndexOption } from '../../../../options/index.js'; import { parseLimitOption } from '../../../../options/limit.js'; import { parseMaxPagesOption } from '../../../../options/maxPages.js'; import { parseNoEntityMatchBehavior } from '../../../../options/noEntityMatchBehavior.js'; import { rejectExtraOptions } from '../../../../options/rejectExtraOptions.js'; import { parseSelectOption } from '../../../../options/select.js'; import { parseShowEntityAttrOption } from '../../../../options/showEntityAttr.js'; import { parseTableNameOption } from '../../../../options/tableName.js'; import { expressCondition } from '../../../../schema/actions/parseCondition/expressCondition/expressCondition.js'; import { ConditionParser } from '../../../../schema/actions/parseCondition/index.js'; import { Deduper } from '../../../../schema/actions/utils/deduper.js'; import { AnySchema } from '../../../../schema/any/schema.js'; import { isEmpty } from '../../../../utils/isEmpty.js'; import { isInteger } from '../../../../utils/validation/isInteger.js'; const defaultAnySchema = new AnySchema({ required: 'never' }); export const scanParams = (table, entities = [], options = {}) => { const { capacity, consistent, exclusiveStartKey, index, limit, maxPages, select, totalSegments, segment, filter, filters: _filters, attributes: _attributes, tableName, entityAttrFilter = entities.every(entity => isEntityAttrEnabled(entity.entityAttribute)), showEntityAttr, noEntityMatchBehavior, ...extraOptions } = options; rejectExtraOptions(extraOptions); const filters = (_filters !== null && _filters !== void 0 ? _filters : {}); const attributes = _attributes; if (tableName !== undefined) { parseTableNameOption(tableName); } const commandOptions = { TableName: tableName !== null && tableName !== void 0 ? tableName : table.getName() }; if (capacity !== undefined) { commandOptions.ReturnConsumedCapacity = parseCapacityOption(capacity); } if (consistent !== undefined) { commandOptions.ConsistentRead = parseConsistentOption(consistent, index !== undefined ? table.indexes[index] : undefined); } if (exclusiveStartKey !== undefined) { commandOptions.ExclusiveStartKey = exclusiveStartKey; } if (index !== undefined) { commandOptions.IndexName = parseIndexOption(table, index); } if (limit !== undefined) { commandOptions.Limit = parseLimitOption(limit); } if (maxPages !== undefined) { // maxPages is a meta-option, validated but not used here parseMaxPagesOption(maxPages); } if (select !== undefined) { commandOptions.Select = parseSelectOption(select, { index, attributes }); } // entityAttrFilter is a meta-option, validated but not used here parseEntityAttrFilterOption(entityAttrFilter, entities, filters); if (showEntityAttr !== undefined) { // showEntityAttr is a meta-option, validated but not used here parseShowEntityAttrOption(showEntityAttr); } if (noEntityMatchBehavior !== undefined) { // noEntityMatchBehavior is a meta-option, validated but not used here parseNoEntityMatchBehavior(noEntityMatchBehavior); } if (segment !== undefined) { if (totalSegments === undefined) { throw new DynamoDBToolboxError('scanCommand.invalidSegmentOption', { message: 'If a segment option has been provided, totalSegments must also be defined', payload: {} }); } if (!isInteger(totalSegments) || totalSegments < 1) { throw new DynamoDBToolboxError('scanCommand.invalidSegmentOption', { message: `Invalid totalSegments option: '${String(totalSegments)}'. 'totalSegments' must be a strictly positive integer.`, payload: { totalSegments } }); } if (!isInteger(segment) || segment < 0 || segment >= totalSegments) { throw new DynamoDBToolboxError('scanCommand.invalidSegmentOption', { message: `Invalid segment option: '${String(segment)}'. 'segment' must be a positive integer strictly lower than 'totalSegments'.`, payload: { segment, totalSegments } }); } commandOptions.TotalSegments = totalSegments; commandOptions.Segment = segment; } 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({ serializer: value => value }); for (const entity of entities) { const entityTransformedPaths = entity .build(EntityPathParser) .transform(attributes, { strict: false }); if (entityTransformedPaths.length === 0) { throw new DynamoDBToolboxError('scanCommand.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 = 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; } } } // --- FILTERS --- if (entities.length === 0 && filter !== undefined) { const { ExpressionAttributeNames: filterExpressionAttributeNames, ExpressionAttributeValues: filterExpressionAttributeValues, ConditionExpression: filterExpression } = new ConditionParser(defaultAnySchema).parse(filter); Object.assign(expressionAttributeNames, filterExpressionAttributeNames); Object.assign(expressionAttributeValues, filterExpressionAttributeValues); commandOptions.FilterExpression = filterExpression; } 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: getEntityAttrOptionValue(entity.entityAttribute, 'name'), eq: entity.entityName } : undefined; if (entityOptionsFilter === undefined && entityNameFilter === undefined) { continue; } const transformedFilter = entity.build(EntityConditionParser).transform({ and: [entityNameFilter, entityOptionsFilter].filter(Boolean) }); transformedFilters.push(transformedFilter); } if (transformedFilters.length > 0) { const expression = expressCondition({ or: transformedFilters }); Object.assign(expressionAttributeNames, expression.ExpressionAttributeNames); Object.assign(expressionAttributeValues, expression.ExpressionAttributeValues); commandOptions.FilterExpression = expression.ConditionExpression; } } if (!isEmpty(expressionAttributeNames)) { commandOptions.ExpressionAttributeNames = expressionAttributeNames; } if (!isEmpty(expressionAttributeValues)) { commandOptions.ExpressionAttributeValues = expressionAttributeValues; } return commandOptions; };