UNPKG

dynamoose

Version:

Dynamoose is a modeling tool for Amazon's DynamoDB (inspired by Mongoose)

247 lines 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Condition = void 0; const Document_1 = require("./Document"); const CustomError = require("./Error"); const utils = require("./utils"); const OR = Symbol("OR"); const isRawConditionObject = (object) => Object.keys(object).length === 3 && ["ExpressionAttributeValues", "ExpressionAttributeNames"].every((item) => Boolean(object[item]) && typeof object[item] === "object"); var ConditionComparisonComparatorName; (function (ConditionComparisonComparatorName) { ConditionComparisonComparatorName["equals"] = "eq"; ConditionComparisonComparatorName["lessThan"] = "lt"; ConditionComparisonComparatorName["lessThanEquals"] = "le"; ConditionComparisonComparatorName["greaterThan"] = "gt"; ConditionComparisonComparatorName["greaterThanEquals"] = "ge"; ConditionComparisonComparatorName["beginsWith"] = "beginsWith"; ConditionComparisonComparatorName["contains"] = "contains"; ConditionComparisonComparatorName["exists"] = "exists"; ConditionComparisonComparatorName["in"] = "in"; ConditionComparisonComparatorName["between"] = "between"; })(ConditionComparisonComparatorName || (ConditionComparisonComparatorName = {})); var ConditionComparisonComparatorDynamoName; (function (ConditionComparisonComparatorDynamoName) { ConditionComparisonComparatorDynamoName["equals"] = "EQ"; ConditionComparisonComparatorDynamoName["notEquals"] = "NE"; ConditionComparisonComparatorDynamoName["lessThan"] = "LT"; ConditionComparisonComparatorDynamoName["lessThanEquals"] = "LE"; ConditionComparisonComparatorDynamoName["greaterThan"] = "GT"; ConditionComparisonComparatorDynamoName["greaterThanEquals"] = "GE"; ConditionComparisonComparatorDynamoName["beginsWith"] = "BEGINS_WITH"; ConditionComparisonComparatorDynamoName["contains"] = "CONTAINS"; ConditionComparisonComparatorDynamoName["notContains"] = "NOT_CONTAINS"; ConditionComparisonComparatorDynamoName["exists"] = "EXISTS"; ConditionComparisonComparatorDynamoName["notExists"] = "NOT_EXISTS"; ConditionComparisonComparatorDynamoName["in"] = "IN"; ConditionComparisonComparatorDynamoName["between"] = "BETWEEN"; })(ConditionComparisonComparatorDynamoName || (ConditionComparisonComparatorDynamoName = {})); const types = [ { "name": ConditionComparisonComparatorName.equals, "typeName": ConditionComparisonComparatorDynamoName.equals, "not": ConditionComparisonComparatorDynamoName.notEquals }, { "name": ConditionComparisonComparatorName.lessThan, "typeName": ConditionComparisonComparatorDynamoName.lessThan, "not": ConditionComparisonComparatorDynamoName.greaterThanEquals }, { "name": ConditionComparisonComparatorName.lessThanEquals, "typeName": ConditionComparisonComparatorDynamoName.lessThanEquals, "not": ConditionComparisonComparatorDynamoName.greaterThan }, { "name": ConditionComparisonComparatorName.greaterThan, "typeName": ConditionComparisonComparatorDynamoName.greaterThan, "not": ConditionComparisonComparatorDynamoName.lessThanEquals }, { "name": ConditionComparisonComparatorName.greaterThanEquals, "typeName": ConditionComparisonComparatorDynamoName.greaterThanEquals, "not": ConditionComparisonComparatorDynamoName.lessThan }, { "name": ConditionComparisonComparatorName.beginsWith, "typeName": ConditionComparisonComparatorDynamoName.beginsWith }, { "name": ConditionComparisonComparatorName.contains, "typeName": ConditionComparisonComparatorDynamoName.contains, "not": ConditionComparisonComparatorDynamoName.notContains }, { "name": ConditionComparisonComparatorName.exists, "typeName": ConditionComparisonComparatorDynamoName.exists, "not": ConditionComparisonComparatorDynamoName.notExists }, { "name": ConditionComparisonComparatorName.in, "typeName": ConditionComparisonComparatorDynamoName.in }, { "name": ConditionComparisonComparatorName.between, "typeName": ConditionComparisonComparatorDynamoName.between, "multipleArguments": true } ]; class Condition { constructor(object) { if (object instanceof Condition) { Object.entries(object).forEach((entry) => { const [key, value] = entry; this[key] = value; }); } else { this.settings = { "conditions": [], "pending": {} // represents the pending chain of filter data waiting to be attached to the `conditions` parameter. For example, storing the key before we know what the comparison operator is. }; if (typeof object === "object") { if (!isRawConditionObject(object)) { Object.keys(object).forEach((key) => { const value = object[key]; const valueType = typeof value === "object" && Object.keys(value).length > 0 ? Object.keys(value)[0] : "eq"; const comparisonType = types.find((item) => item.name === valueType); if (!comparisonType) { throw new CustomError.InvalidFilterComparison(`The type: ${valueType} is invalid.`); } this.settings.conditions.push({ [key]: { "type": comparisonType.typeName, "value": typeof value[valueType] !== "undefined" && value[valueType] !== null ? value[valueType] : value } }); }); } } else if (object) { this.settings.pending.key = object; } } this.settings.raw = object; return this; } } exports.Condition = Condition; function finalizePending(instance) { const pending = instance.settings.pending; let dynamoNameType; if (pending.not === true) { if (!pending.type.not) { throw new CustomError.InvalidFilterComparison(`${pending.type.typeName} can not follow not()`); } dynamoNameType = pending.type.not; } else { dynamoNameType = pending.type.typeName; } instance.settings.conditions.push({ [pending.key]: { "type": dynamoNameType, "value": pending.value } }); instance.settings.pending = {}; } Condition.prototype.parenthesis = Condition.prototype.group = function (value) { value = typeof value === "function" ? value(new Condition()) : value; this.settings.conditions.push(value.settings.conditions); return this; }; Condition.prototype.or = function () { this.settings.conditions.push(OR); return this; }; Condition.prototype.and = function () { return this; }; Condition.prototype.not = function () { this.settings.pending.not = !this.settings.pending.not; return this; }; Condition.prototype.where = Condition.prototype.filter = Condition.prototype.attribute = function (key) { this.settings.pending = { key }; return this; }; // TODO: I don't think this prototypes are being exposed which is gonna cause a lot of problems with our type definition file. Need to figure out a better way to do this since they aren't defined and are dynamic. types.forEach((type) => { Condition.prototype[type.name] = function (...args) { this.settings.pending.value = type.multipleArguments ? args : args[0]; this.settings.pending.type = type; finalizePending(this); return this; }; }); Condition.prototype.requestObject = function (settings = { "conditionString": "ConditionExpression", "conditionStringType": "string" }) { if (this.settings.raw && utils.object.equals(Object.keys(this.settings.raw).sort(), [settings.conditionString, "ExpressionAttributeValues", "ExpressionAttributeNames"].sort())) { return Object.entries(this.settings.raw.ExpressionAttributeValues).reduce((obj, entry) => { const [key, value] = entry; // TODO: we should fix this so that we can do `isDynamoItem(value)` if (!Document_1.Document.isDynamoObject({ "key": value })) { obj.ExpressionAttributeValues[key] = Document_1.Document.objectToDynamo(value, { "type": "value" }); } return obj; }, this.settings.raw); } else if (this.settings.conditions.length === 0) { return {}; } let index = (settings.index || {}).start || 0; const setIndex = (i) => { index = i; (settings.index || { "set": utils.empty_function }).set(i); }; function main(input) { return input.reduce((object, entry, i, arr) => { let expression = ""; if (Array.isArray(entry)) { const result = main(entry); const newData = utils.merge_objects.main({ "combineMethod": "object_combine" })(Object.assign({}, result), Object.assign({}, object)); const returnObject = utils.object.pick(newData, ["ExpressionAttributeNames", "ExpressionAttributeValues"]); expression = settings.conditionStringType === "array" ? result[settings.conditionString] : `(${result[settings.conditionString]})`; object = Object.assign(Object.assign({}, object), returnObject); } else if (entry !== OR) { const [key, condition] = Object.entries(entry)[0]; const { value } = condition; const keys = { "name": `#a${index}`, "value": `:v${index}` }; setIndex(++index); const keyParts = key.split("."); if (keyParts.length === 1) { object.ExpressionAttributeNames[keys.name] = key; } else { keys.name = keyParts.reduce((finalName, part, index) => { const name = `${keys.name}_${index}`; object.ExpressionAttributeNames[name] = part; finalName.push(name); return finalName; }, []).join("."); } const toDynamo = (value) => { return Document_1.Document.objectToDynamo(value, { "type": "value" }); }; object.ExpressionAttributeValues[keys.value] = toDynamo(value); switch (condition.type) { case "EQ": case "NE": expression = `${keys.name} ${condition.type === "EQ" ? "=" : "<>"} ${keys.value}`; break; case "IN": delete object.ExpressionAttributeValues[keys.value]; expression = `${keys.name} IN (${value.map((_v, i) => `${keys.value}_${i + 1}`).join(", ")})`; value.forEach((valueItem, i) => { object.ExpressionAttributeValues[`${keys.value}_${i + 1}`] = toDynamo(valueItem); }); break; case "GT": case "GE": case "LT": case "LE": expression = `${keys.name} ${condition.type.startsWith("G") ? ">" : "<"}${condition.type.endsWith("E") ? "=" : ""} ${keys.value}`; break; case "BETWEEN": expression = `${keys.name} BETWEEN ${keys.value}_1 AND ${keys.value}_2`; object.ExpressionAttributeValues[`${keys.value}_1`] = toDynamo(value[0]); object.ExpressionAttributeValues[`${keys.value}_2`] = toDynamo(value[1]); delete object.ExpressionAttributeValues[keys.value]; break; case "CONTAINS": case "NOT_CONTAINS": expression = `${condition.type === "NOT_CONTAINS" ? "NOT " : ""}contains (${keys.name}, ${keys.value})`; break; case "EXISTS": case "NOT_EXISTS": expression = `attribute_${condition.type === "NOT_EXISTS" ? "not_" : ""}exists (${keys.name})`; delete object.ExpressionAttributeValues[keys.value]; break; case "BEGINS_WITH": expression = `begins_with (${keys.name}, ${keys.value})`; break; } } else { return object; } const conditionStringNewItems = [expression]; if (object[settings.conditionString].length > 0) { conditionStringNewItems.unshift(` ${arr[i - 1] === OR ? "OR" : "AND"} `); } conditionStringNewItems.forEach((item) => { if (typeof object[settings.conditionString] === "string") { object[settings.conditionString] = `${object[settings.conditionString]}${item}`; } else { object[settings.conditionString].push(Array.isArray(item) ? item : item.trim()); } }); return object; }, { [settings.conditionString]: settings.conditionStringType === "array" ? [] : "", "ExpressionAttributeNames": {}, "ExpressionAttributeValues": {} }); } return main(this.settings.conditions); }; //# sourceMappingURL=Condition.js.map