UNPKG

14.8 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11Object.defineProperty(exports, "__esModule", { value: true });
12exports.Query = exports.Scan = void 0;
13const ddb = require("./aws/ddb/internal");
14const CustomError = require("./Error");
15const utils = require("./utils");
16const Condition_1 = require("./Condition");
17const Document_1 = require("./Document");
18const General_1 = require("./General");
19const Populate_1 = require("./Populate");
20var DocumentRetrieverTypes;
21(function (DocumentRetrieverTypes) {
22 DocumentRetrieverTypes["scan"] = "scan";
23 DocumentRetrieverTypes["query"] = "query";
24})(DocumentRetrieverTypes || (DocumentRetrieverTypes = {}));
25class DocumentRetriever {
26 constructor(model, typeInformation, object) {
27 this.internalSettings = { model, typeInformation };
28 let condition;
29 try {
30 condition = new Condition_1.Condition(object);
31 }
32 catch (e) {
33 e.message = `${e.message.replace(" is invalid.", "")} is invalid for the ${this.internalSettings.typeInformation.type} operation.`;
34 throw e;
35 }
36 this.settings = {
37 "condition": condition
38 };
39 }
40 exec(callback) {
41 let timesRequested = 0;
42 const prepareForReturn = (result) => __awaiter(this, void 0, void 0, function* () {
43 if (Array.isArray(result)) {
44 result = utils.merge_objects(...result);
45 }
46 if (this.settings.count) {
47 return {
48 "count": result.Count,
49 [`${this.internalSettings.typeInformation.pastTense}Count`]: result[`${utils.capitalize_first_letter(this.internalSettings.typeInformation.pastTense)}Count`]
50 };
51 }
52 const array = (yield Promise.all(result.Items.map((item) => __awaiter(this, void 0, void 0, function* () { return yield new this.internalSettings.model.Document(item, { "type": "fromDynamo" }).conformToSchema({ "customTypesDynamo": true, "checkExpiredItem": true, "saveUnknown": true, "modifiers": ["get"], "type": "fromDynamo" }); })))).filter((a) => Boolean(a));
53 array.lastKey = result.LastEvaluatedKey ? Array.isArray(result.LastEvaluatedKey) ? result.LastEvaluatedKey.map((key) => this.internalSettings.model.Document.fromDynamo(key)) : this.internalSettings.model.Document.fromDynamo(result.LastEvaluatedKey) : undefined;
54 array.count = result.Count;
55 array[`${this.internalSettings.typeInformation.pastTense}Count`] = result[`${utils.capitalize_first_letter(this.internalSettings.typeInformation.pastTense)}Count`];
56 array[`times${utils.capitalize_first_letter(this.internalSettings.typeInformation.pastTense)}`] = timesRequested;
57 array["populate"] = Populate_1.PopulateDocuments;
58 array["toJSON"] = utils.dynamoose.documentToJSON;
59 return array;
60 });
61 const promise = this.internalSettings.model.pendingTaskPromise().then(() => this.getRequest()).then((request) => {
62 const allRequest = (extraParameters = {}) => {
63 let promise = ddb(this.internalSettings.typeInformation.type, Object.assign(Object.assign({}, request), extraParameters));
64 timesRequested++;
65 if (this.settings.all) {
66 promise = promise.then((result) => __awaiter(this, void 0, void 0, function* () {
67 if (this.settings.all.delay && this.settings.all.delay > 0) {
68 yield utils.timeout(this.settings.all.delay);
69 }
70 let lastKey = result.LastEvaluatedKey;
71 let requestedTimes = 1;
72 while (lastKey && (this.settings.all.max === 0 || requestedTimes < this.settings.all.max)) {
73 if (this.settings.all.delay && this.settings.all.delay > 0) {
74 yield utils.timeout(this.settings.all.delay);
75 }
76 const nextRequest = yield ddb(this.internalSettings.typeInformation.type, Object.assign(Object.assign(Object.assign({}, request), extraParameters), { "ExclusiveStartKey": lastKey }));
77 timesRequested++;
78 result = utils.merge_objects(result, nextRequest);
79 // 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.
80 result.LastEvaluatedKey = nextRequest.LastEvaluatedKey; // eslint-disable-line require-atomic-updates
81 lastKey = nextRequest.LastEvaluatedKey;
82 requestedTimes++;
83 }
84 return result;
85 }));
86 }
87 return promise;
88 };
89 if (this.settings.parallel) {
90 return Promise.all(new Array(this.settings.parallel).fill(0).map((a, index) => allRequest({ "Segment": index })));
91 }
92 else {
93 return allRequest();
94 }
95 });
96 // TODO: we do something similar to do this below in other functions as well (ex. get, save), where we allow a callback or a promise, we should figure out a way to make this code more DRY and have a standard way of doing this throughout Dynamoose
97 if (callback) {
98 promise.then((result) => prepareForReturn(result)).then((result) => callback(null, result)).catch((error) => callback(error));
99 }
100 else {
101 return (() => __awaiter(this, void 0, void 0, function* () {
102 const result = yield promise;
103 const finalResult = yield prepareForReturn(result);
104 return finalResult;
105 }))();
106 }
107 }
108}
109Object.entries(Condition_1.Condition.prototype).forEach((prototype) => {
110 const [key, func] = prototype;
111 if (key !== "requestObject") {
112 DocumentRetriever.prototype[key] = function (...args) {
113 func.bind(this.settings.condition)(...args);
114 return this;
115 };
116 }
117});
118DocumentRetriever.prototype.getRequest = function () {
119 return __awaiter(this, void 0, void 0, function* () {
120 const object = Object.assign(Object.assign({}, this.settings.condition.requestObject({ "conditionString": "FilterExpression", "conditionStringType": "array" })), { "TableName": this.internalSettings.model.name });
121 if (this.settings.limit) {
122 object.Limit = this.settings.limit;
123 }
124 if (this.settings.startAt) {
125 object.ExclusiveStartKey = Document_1.Document.isDynamoObject(this.settings.startAt) ? this.settings.startAt : this.internalSettings.model.Document.objectToDynamo(this.settings.startAt);
126 }
127 const indexes = yield this.internalSettings.model.getIndexes();
128 if (this.settings.index) {
129 object.IndexName = this.settings.index;
130 }
131 else if (this.internalSettings.typeInformation.type === "query") {
132 const comparisonChart = this.settings.condition.settings.conditions.reduce((res, item) => {
133 const myItem = Object.entries(item)[0];
134 res[myItem[0]] = { "type": myItem[1].type };
135 return res;
136 }, {});
137 const index = utils.array_flatten(Object.values(indexes)).find((index) => {
138 const { hash /*, range*/ } = index.KeySchema.reduce((res, item) => {
139 res[item.KeyType.toLowerCase()] = item.AttributeName;
140 return res;
141 }, {});
142 // TODO: we need to write logic here to prioritize indexes with a range key that is being queried.
143 return (comparisonChart[hash] || {}).type === "EQ" /* && (!range || comparisonChart[range])*/;
144 });
145 if (!index) {
146 if ((comparisonChart[this.internalSettings.model.getHashKey()] || {}).type !== "EQ") {
147 throw new CustomError.InvalidParameter("Index can't be found for query.");
148 }
149 }
150 else {
151 object.IndexName = index.IndexName;
152 }
153 }
154 function moveParameterNames(val, prefix) {
155 const entry = Object.entries(object.ExpressionAttributeNames).find((entry) => entry[1] === val);
156 if (!entry) {
157 return;
158 }
159 const [key, value] = entry;
160 const filterExpressionIndex = object.FilterExpression.findIndex((item) => item.includes(key));
161 const filterExpression = object.FilterExpression[filterExpressionIndex];
162 if (filterExpression.includes("attribute_exists") || filterExpression.includes("contains")) {
163 return;
164 }
165 object.ExpressionAttributeNames[`#${prefix}a`] = value;
166 delete object.ExpressionAttributeNames[key];
167 const valueKey = key.replace("#a", ":v");
168 Object.keys(object.ExpressionAttributeValues).filter((key) => key.startsWith(valueKey)).forEach((key) => {
169 object.ExpressionAttributeValues[key.replace(new RegExp(":v\\d"), `:${prefix}v`)] = object.ExpressionAttributeValues[key];
170 delete object.ExpressionAttributeValues[key];
171 });
172 const newExpression = filterExpression.replace(key, `#${prefix}a`).replace(new RegExp(valueKey, "g"), `:${prefix}v`);
173 object.KeyConditionExpression = `${object.KeyConditionExpression || ""}${object.KeyConditionExpression ? " AND " : ""}${newExpression}`;
174 utils.object.delete(object.FilterExpression, filterExpressionIndex);
175 const previousElementIndex = filterExpressionIndex === 0 ? 0 : filterExpressionIndex - 1;
176 if (object.FilterExpression[previousElementIndex] === "AND") {
177 utils.object.delete(object.FilterExpression, previousElementIndex);
178 }
179 }
180 if (this.internalSettings.typeInformation.type === "query") {
181 const index = utils.array_flatten(Object.values(indexes)).find((index) => index.IndexName === object.IndexName);
182 if (index) {
183 const { hash, range } = index.KeySchema.reduce((res, item) => {
184 res[item.KeyType.toLowerCase()] = item.AttributeName;
185 return res;
186 }, {});
187 moveParameterNames(hash, "qh");
188 if (range) {
189 moveParameterNames(range, "qr");
190 }
191 }
192 else {
193 moveParameterNames(this.internalSettings.model.getHashKey(), "qh");
194 if (this.internalSettings.model.getRangeKey()) {
195 moveParameterNames(this.internalSettings.model.getRangeKey(), "qr");
196 }
197 }
198 }
199 if (this.settings.consistent) {
200 object.ConsistentRead = this.settings.consistent;
201 }
202 if (this.settings.count) {
203 object.Select = "COUNT";
204 }
205 if (this.settings.parallel) {
206 object.TotalSegments = this.settings.parallel;
207 }
208 if (this.settings.sort === General_1.SortOrder.descending) {
209 object.ScanIndexForward = false;
210 }
211 if (this.settings.attributes) {
212 if (!object.ExpressionAttributeNames) {
213 object.ExpressionAttributeNames = {};
214 }
215 object.ProjectionExpression = this.settings.attributes.map((attribute) => {
216 let expressionAttributeName = "";
217 expressionAttributeName = (Object.entries(object.ExpressionAttributeNames).find((entry) => entry[1] === attribute) || [])[0];
218 if (!expressionAttributeName) {
219 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;
220 expressionAttributeName = `#a${nextIndex}`;
221 object.ExpressionAttributeNames[expressionAttributeName] = attribute;
222 }
223 return expressionAttributeName;
224 }).sort().join(", ");
225 }
226 if (object.FilterExpression) {
227 object.FilterExpression = utils.dynamoose.convertConditionArrayRequestObjectToString(object.FilterExpression);
228 }
229 if (object.FilterExpression === "") {
230 delete object.FilterExpression;
231 }
232 return object;
233 });
234};
235const settings = [
236 "limit",
237 "startAt",
238 "attributes",
239 { "name": "count", "boolean": true },
240 { "name": "consistent", "boolean": true },
241 { "name": "using", "settingsName": "index" }
242];
243settings.forEach((item) => {
244 DocumentRetriever.prototype[item.name || item] = function (value) {
245 const key = item.settingsName || item.name || item;
246 this.settings[key] = item.boolean ? !this.settings[key] : value;
247 return this;
248 };
249});
250DocumentRetriever.prototype.all = function (delay = 0, max = 0) {
251 this.settings.all = { delay, max };
252 return this;
253};
254class Scan extends DocumentRetriever {
255 exec(callback) {
256 return super.exec(callback);
257 }
258 parallel(value) {
259 this.settings.parallel = value;
260 return this;
261 }
262 constructor(model, object) {
263 super(model, { "type": DocumentRetrieverTypes.scan, "pastTense": "scanned" }, object);
264 }
265}
266exports.Scan = Scan;
267class Query extends DocumentRetriever {
268 exec(callback) {
269 return super.exec(callback);
270 }
271 sort(order) {
272 this.settings.sort = order;
273 return this;
274 }
275 constructor(model, object) {
276 super(model, { "type": DocumentRetrieverTypes.query, "pastTense": "queried" }, object);
277 }
278}
279exports.Query = Query;
280//# sourceMappingURL=DocumentRetriever.js.map
\No newline at end of file