UNPKG

dynamoose

Version:

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

261 lines (260 loc) 15.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Query = exports.Scan = void 0; const internal_1 = require("./aws/ddb/internal"); const Error_1 = require("./Error"); const utils_1 = require("./utils"); const Condition_1 = require("./Condition"); const Item_1 = require("./Item"); const General_1 = require("./General"); const Populate_1 = require("./Populate"); const Internal_1 = require("./Internal"); const InternalPropertiesClass_1 = require("./InternalPropertiesClass"); const { internalProperties } = Internal_1.default.General; var ItemRetrieverTypes; (function (ItemRetrieverTypes) { ItemRetrieverTypes["scan"] = "scan"; ItemRetrieverTypes["query"] = "query"; })(ItemRetrieverTypes || (ItemRetrieverTypes = {})); // ItemRetriever is used for both Scan and Query since a lot of the code is shared between the two // type ItemRetriever = BasicOperators; class ItemRetriever extends InternalPropertiesClass_1.InternalPropertiesClass { exec(callback) { let timesRequested = 0; const { model } = this.getInternalProperties(internalProperties).internalSettings; const table = model.getInternalProperties(internalProperties).table(); const prepareForReturn = async (result) => { if (Array.isArray(result)) { result = utils_1.default.merge_objects(...result); } if (this.getInternalProperties(internalProperties).settings.count) { return { "count": result.Count, [`${this.getInternalProperties(internalProperties).internalSettings.typeInformation.pastTense}Count`]: result[`${utils_1.default.capitalize_first_letter(this.getInternalProperties(internalProperties).internalSettings.typeInformation.pastTense)}Count`] }; } const array = (await Promise.all(result.Items.map(async (item) => await new model.Item(item, { "type": "fromDynamo" }).conformToSchema({ "customTypesDynamo": true, "checkExpiredItem": true, "saveUnknown": true, "modifiers": ["get"], "type": "fromDynamo", "mapAttributes": true })))).filter((a) => Boolean(a)); array.lastKey = result.LastEvaluatedKey ? Array.isArray(result.LastEvaluatedKey) ? result.LastEvaluatedKey.map((key) => model.Item.fromDynamo(key)) : model.Item.fromDynamo(result.LastEvaluatedKey) : undefined; array.count = result.Count; array[`${this.getInternalProperties(internalProperties).internalSettings.typeInformation.pastTense}Count`] = result[`${utils_1.default.capitalize_first_letter(this.getInternalProperties(internalProperties).internalSettings.typeInformation.pastTense)}Count`]; array[`times${utils_1.default.capitalize_first_letter(this.getInternalProperties(internalProperties).internalSettings.typeInformation.pastTense)}`] = timesRequested; array["populate"] = Populate_1.PopulateItems; array["toJSON"] = utils_1.default.dynamoose.itemToJSON; return array; }; const promise = table.getInternalProperties(internalProperties).pendingTaskPromise().then(() => this.getRequest()).then((request) => { const allRequest = (extraParameters = {}) => { let promise = (0, internal_1.default)(table.getInternalProperties(internalProperties).instance, this.getInternalProperties(internalProperties).internalSettings.typeInformation.type, Object.assign(Object.assign({}, request), extraParameters)); timesRequested++; if (this.getInternalProperties(internalProperties).settings.all) { promise = promise.then(async (result) => { if (this.getInternalProperties(internalProperties).settings.all.delay && this.getInternalProperties(internalProperties).settings.all.delay > 0) { await utils_1.default.timeout(this.getInternalProperties(internalProperties).settings.all.delay); } let lastKey = result.LastEvaluatedKey; let requestedTimes = 1; while (lastKey && (this.getInternalProperties(internalProperties).settings.all.max === 0 || requestedTimes < this.getInternalProperties(internalProperties).settings.all.max)) { if (this.getInternalProperties(internalProperties).settings.all.delay && this.getInternalProperties(internalProperties).settings.all.delay > 0) { await utils_1.default.timeout(this.getInternalProperties(internalProperties).settings.all.delay); } const nextRequest = await (0, internal_1.default)(table.getInternalProperties(internalProperties).instance, this.getInternalProperties(internalProperties).internalSettings.typeInformation.type, Object.assign(Object.assign(Object.assign({}, request), extraParameters), { "ExclusiveStartKey": lastKey })); timesRequested++; result = utils_1.default.merge_objects(result, nextRequest); // The operation below is safe because right above we are overwriting the entire `result` variable, so there is no chance it'll be reassigned based on an outdated value since it's already been overwritten. There might be a better way to do this than ignoring the rule on the line below. result.LastEvaluatedKey = nextRequest.LastEvaluatedKey; // eslint-disable-line require-atomic-updates lastKey = nextRequest.LastEvaluatedKey; requestedTimes++; } return result; }); } return promise; }; if (this.getInternalProperties(internalProperties).settings.parallel) { return Promise.all(new Array(this.getInternalProperties(internalProperties).settings.parallel).fill(0).map((a, index) => allRequest({ "Segment": index }))); } else { return allRequest(); } }); if (callback) { promise.then((result) => prepareForReturn(result)).then((result) => callback(null, result)).catch((error) => callback(error)); } else { return (async () => { const result = await promise; const finalResult = await prepareForReturn(result); return finalResult; })(); } } constructor(model, typeInformation, object) { super(); let condition; try { condition = new Condition_1.Condition(object); } catch (e) { e.message = `${e.message.replace(" is invalid.", "")} is invalid for the ${typeInformation.type} operation.`; throw e; } this.setInternalProperties(internalProperties, { "internalSettings": { model, typeInformation }, "settings": { condition } }); } } Object.getOwnPropertyNames(Condition_1.Condition.prototype).forEach((key) => { if (!["requestObject", "constructor"].includes(key)) { ItemRetriever.prototype[key] = function (...args) { Condition_1.Condition.prototype[key].bind(this.getInternalProperties(internalProperties).settings.condition)(...args); return this; }; } }); ItemRetriever.prototype.getRequest = async function () { const { model } = this.getInternalProperties(internalProperties).internalSettings; const table = model.getInternalProperties(internalProperties).table(); const object = Object.assign(Object.assign({}, await this.getInternalProperties(internalProperties).settings.condition.getInternalProperties(internalProperties).requestObject(model, { "conditionString": "FilterExpression", "conditionStringType": "array" })), { "TableName": table.getInternalProperties(internalProperties).name }); if (this.getInternalProperties(internalProperties).settings.limit) { object.Limit = this.getInternalProperties(internalProperties).settings.limit; } if (this.getInternalProperties(internalProperties).settings.startAt) { object.ExclusiveStartKey = Item_1.Item.isDynamoObject(this.getInternalProperties(internalProperties).settings.startAt) ? this.getInternalProperties(internalProperties).settings.startAt : model.Item.objectToDynamo(this.getInternalProperties(internalProperties).settings.startAt); } const indexes = await model.getInternalProperties(internalProperties).getIndexes(); if (this.getInternalProperties(internalProperties).settings.index) { object.IndexName = this.getInternalProperties(internalProperties).settings.index; } else if (this.getInternalProperties(internalProperties).internalSettings.typeInformation.type === "query") { const comparisonChart = await this.getInternalProperties(internalProperties).settings.condition.getInternalProperties(internalProperties).comparisonChart(model); const indexSpec = utils_1.default.find_best_index(indexes, comparisonChart); if (!indexSpec.tableIndex) { if (!indexSpec.indexName) { throw new Error_1.default.InvalidParameter("Index can't be found for query."); } object.IndexName = indexSpec.indexName; } } function moveParameterNames(val, prefix) { const entry = Object.entries(object.ExpressionAttributeNames).find((entry) => entry[1] === val); if (!entry) { return; } const [key, value] = entry; const filterExpressionIndex = object.FilterExpression.findIndex((item) => item.includes(key)); const filterExpression = object.FilterExpression[filterExpressionIndex]; if (filterExpression.includes("attribute_exists") || filterExpression.includes("contains")) { return; } object.ExpressionAttributeNames[`#${prefix}a`] = value; delete object.ExpressionAttributeNames[key]; const valueKey = key.replace("#a", ":v"); Object.keys(object.ExpressionAttributeValues).filter((key) => key === valueKey || key.startsWith(`${valueKey}_`)).forEach((key) => { object.ExpressionAttributeValues[key.replace(new RegExp(":v\\d+"), `:${prefix}v`)] = object.ExpressionAttributeValues[key]; delete object.ExpressionAttributeValues[key]; }); const newExpression = filterExpression.replace(key, `#${prefix}a`).replace(new RegExp(valueKey, "g"), `:${prefix}v`); object.KeyConditionExpression = `${object.KeyConditionExpression || ""}${object.KeyConditionExpression ? " AND " : ""}${newExpression}`; utils_1.default.object.delete(object.FilterExpression, filterExpressionIndex); const previousElementIndex = filterExpressionIndex === 0 ? 0 : filterExpressionIndex - 1; if (object.FilterExpression[previousElementIndex] === "AND") { utils_1.default.object.delete(object.FilterExpression, previousElementIndex); } } if (this.getInternalProperties(internalProperties).internalSettings.typeInformation.type === "query") { const index = utils_1.default.array_flatten(Object.values(indexes)).find((index) => index.IndexName === object.IndexName) || indexes.TableIndex; const { hash, range } = index.KeySchema.reduce((res, item) => { res[item.KeyType.toLowerCase()] = item.AttributeName; return res; }, {}); moveParameterNames(hash, "qh"); if (range) { moveParameterNames(range, "qr"); } } if (this.getInternalProperties(internalProperties).settings.consistent) { object.ConsistentRead = this.getInternalProperties(internalProperties).settings.consistent; } if (this.getInternalProperties(internalProperties).settings.count) { object.Select = "COUNT"; } if (this.getInternalProperties(internalProperties).settings.parallel) { object.TotalSegments = this.getInternalProperties(internalProperties).settings.parallel; } if (this.getInternalProperties(internalProperties).settings.sort === General_1.SortOrder.descending) { object.ScanIndexForward = false; } if (this.getInternalProperties(internalProperties).settings.attributes) { if (!object.ExpressionAttributeNames) { object.ExpressionAttributeNames = {}; } object.ProjectionExpression = this.getInternalProperties(internalProperties).settings.attributes.map((attribute) => { let expressionAttributeName = ""; expressionAttributeName = (Object.entries(object.ExpressionAttributeNames).find((entry) => entry[1] === attribute) || [])[0]; if (!expressionAttributeName) { const nextIndex = (Object.keys(object.ExpressionAttributeNames).map((item) => parseInt(item.replace("#a", ""))).filter((item) => !isNaN(item)).reduce((existing, item) => Math.max(item, existing), 0) || 0) + 1; expressionAttributeName = `#a${nextIndex}`; object.ExpressionAttributeNames[expressionAttributeName] = attribute; } return expressionAttributeName; }).sort().join(", "); } if (object.FilterExpression && Array.isArray(object.FilterExpression)) { object.FilterExpression = utils_1.default.dynamoose.convertConditionArrayRequestObjectToString(object.FilterExpression); } if (object.FilterExpression === "") { delete object.FilterExpression; } return object; }; const settings = [ "limit", "startAt", "attributes", { "name": "count", "boolean": true }, { "name": "consistent", "boolean": true }, { "name": "using", "settingsName": "index" } ]; settings.forEach((item) => { ItemRetriever.prototype[item.name || item] = function (value) { const key = item.settingsName || item.name || item; this.getInternalProperties(internalProperties).settings[key] = item.boolean ? !this.getInternalProperties(internalProperties).settings[key] : value; return this; }; }); ItemRetriever.prototype.all = function (delay = 0, max = 0) { this.getInternalProperties(internalProperties).settings.all = { delay, max }; return this; }; class Scan extends ItemRetriever { exec(callback) { return super.exec(callback); } parallel(value) { this.getInternalProperties(internalProperties).settings.parallel = value; return this; } constructor(model, object) { super(model, { "type": ItemRetrieverTypes.scan, "pastTense": "scanned" }, object); } } exports.Scan = Scan; class Query extends ItemRetriever { exec(callback) { return super.exec(callback); } sort(order) { this.getInternalProperties(internalProperties).settings.sort = order; return this; } constructor(model, object) { super(model, { "type": ItemRetrieverTypes.query, "pastTense": "queried" }, object); } } exports.Query = Query;