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