@env0/dynamo-easy
Version:
DynamoDB client for NodeJS and browser with a fluent api to build requests. We take care of the type mapping between JS and DynamoDB, customizable trough typescript decorators.
148 lines • 7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const property_metadata_model_1 = require("../../decorator/metadata/property-metadata.model");
const mapper_1 = require("../../mapper/mapper");
const util_1 = require("../../mapper/util");
const condition_expression_builder_1 = require("./condition-expression-builder");
const attribute_names_function_1 = require("./functions/attribute-names.function");
const unique_attribute_value_name_function_1 = require("./functions/unique-attribute-value-name.function");
/**
* Will create a condition which can be added to a request using the param object.
* It will create the expression statement and the attribute names and values.
*
* @param {string} attributePath
* @param {ConditionOperator} operation
* @param {any[]} values Depending on the operation the amount of values differs
* @param {string[]} existingValueNames If provided the existing names are used to make sure we have a unique name for the current attributePath
* @param {Metadata<any>} metadata If provided we use the metadata to define the attribute name and use it to map the given value(s) to attributeValue(s)
* @returns {Expression}
* @hidden
*/
function buildUpdateExpression(attributePath, operation, values, existingValueNames, metadata) {
// metadata get rid of undefined values
values = condition_expression_builder_1.deepFilter(values, value => value !== undefined) || [];
// load property metadata if model metadata was provided
let propertyMetadata;
if (metadata) {
propertyMetadata = metadata.forProperty(attributePath);
}
/*
* resolve placeholder and valuePlaceholder names (same as attributePath if it not already exists)
* myProp -> #myProp for name placeholder and :myProp for value placeholder
*
* person[0] -> #person: person
* person.list[0].age -> #person: person, #attr: attr, #age: age
* person.age
*/
const resolvedAttributeNames = attribute_names_function_1.resolveAttributeNames(attributePath, metadata);
const valuePlaceholder = unique_attribute_value_name_function_1.uniqueAttributeValueName(attributePath, existingValueNames);
/*
* build the statement
*/
return buildDefaultExpression(attributePath, resolvedAttributeNames.placeholder, valuePlaceholder, resolvedAttributeNames.attributeNames, values, existingValueNames, propertyMetadata, operation);
}
exports.buildUpdateExpression = buildUpdateExpression;
/**
* @hidden
*/
function buildDefaultExpression(attributePath, namePlaceholder, valuePlaceholder, attributeNames, values, existingValueNames, propertyMetadata, operator) {
const attributeValues = {};
let attribute = null;
if (!isNoAttributeValueAction(operator.action)) {
// special cases: appendToList, add, removeFromSet
// we allow to provide arrays or sets for those methods.
// so it's necessary to make sure appendToList receives Arrays, add & removeFromSet receive Sets
if (['removeFromSet', 'add'].includes(operator.action) && Array.isArray(values[0])) {
values[0] = new Set(values[0]);
}
else if (['appendToList'].includes(operator.action) && util_1.isSet(values[0])) {
values[0] = [...values[0]];
}
// special case: [same as in buildDefaultConditionExpression]
// we have the metadata for an Array/Set of an Object,
// but only get a single item when using list indexes in attributePath
// e.g. `attribute('myCollectionProp[0]').set(...)`
// (not exclusive to `.set(...)` but all updateActions)
if (/\[\d+\]$/.test(attributePath)) {
attribute = mapper_1.toDbOne(values[0], util_1.getPropertyPath(propertyMetadata, attributePath), property_metadata_model_1.alterCollectionPropertyMetadataForSingleItem(propertyMetadata));
}
else {
attribute = mapper_1.toDbOne(values[0], propertyMetadata);
}
if (attribute) {
attributeValues[valuePlaceholder] = attribute;
}
}
// see update-expression-definition-chain.ts for action definitions
let statement;
switch (operator.action) {
case 'incrementBy':
validateAttributeType(operator.action, attribute, 'N');
statement = `${namePlaceholder} = ${namePlaceholder} + ${valuePlaceholder}`;
break;
case 'decrementBy':
validateAttributeType(operator.action, attribute, 'N');
statement = `${namePlaceholder} = ${namePlaceholder} - ${valuePlaceholder}`;
break;
case 'set':
if (values.length > 1 && !!values[values.length - 1] === true) {
statement = `${namePlaceholder} = if_not_exists(${namePlaceholder}, ${valuePlaceholder})`;
}
else {
statement = `${namePlaceholder} = ${valuePlaceholder}`;
}
break;
case 'appendToList':
if (values.length > 1 && values[values.length - 1] === 'START') {
statement = `${namePlaceholder} = list_append(${valuePlaceholder}, ${namePlaceholder})`;
}
else {
statement = `${namePlaceholder} = list_append(${namePlaceholder}, ${valuePlaceholder})`;
}
break;
case 'remove':
statement = `${namePlaceholder}`;
break;
case 'removeFromListAt':
statement = values.map(pos => `${namePlaceholder}[${pos}]`).join(', ');
break;
case 'add':
validateAttributeType(operator.action, attribute, 'N', 'SS', 'NS', 'BS');
statement = `${namePlaceholder} ${valuePlaceholder}`;
break;
case 'removeFromSet':
validateAttributeType(operator.action, attribute, 'SS', 'NS', 'BS');
statement = `${namePlaceholder} ${valuePlaceholder}`;
break;
default:
throw new Error(`no implementation for action ${operator.action}`);
}
return {
type: operator.actionKeyword,
statement,
attributeNames,
attributeValues,
};
}
/**
* @hidden
*/
function isNoAttributeValueAction(action) {
return (action === 'remove' ||
// special cases: values are used in statement instead of expressionValues
action === 'removeFromListAt');
}
/**
* @hidden
*/
function validateAttributeType(name, attribute, ...allowedTypes) {
if (attribute === null || attribute === undefined) {
throw new Error(`${name} requires an attributeValue of ${allowedTypes.join(', ')} but non was given`);
}
const key = Object.keys(attribute)[0];
if (!allowedTypes.includes(key)) {
throw new Error(`Type ${key} of ${JSON.stringify(attribute)} is not allowed for ${name}. Valid types are: ${allowedTypes.join('. ')}`);
}
}
exports.validateAttributeType = validateAttributeType;
//# sourceMappingURL=update-expression-builder.js.map