1 | "use strict";
|
2 | var __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 | };
|
11 | Object.defineProperty(exports, "__esModule", { value: true });
|
12 | exports.Query = exports.Scan = void 0;
|
13 | const ddb = require("./aws/ddb/internal");
|
14 | const CustomError = require("./Error");
|
15 | const utils = require("./utils");
|
16 | const Condition_1 = require("./Condition");
|
17 | const Document_1 = require("./Document");
|
18 | const General_1 = require("./General");
|
19 | const Populate_1 = require("./Populate");
|
20 | var DocumentRetrieverTypes;
|
21 | (function (DocumentRetrieverTypes) {
|
22 | DocumentRetrieverTypes["scan"] = "scan";
|
23 | DocumentRetrieverTypes["query"] = "query";
|
24 | })(DocumentRetrieverTypes || (DocumentRetrieverTypes = {}));
|
25 | class 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 |
|
80 | result.LastEvaluatedKey = nextRequest.LastEvaluatedKey;
|
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 |
|
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 | }
|
109 | Object.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 | });
|
118 | DocumentRetriever.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 } = index.KeySchema.reduce((res, item) => {
|
139 | res[item.KeyType.toLowerCase()] = item.AttributeName;
|
140 | return res;
|
141 | }, {});
|
142 |
|
143 | return (comparisonChart[hash] || {}).type === "EQ" ;
|
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 | };
|
235 | const settings = [
|
236 | "limit",
|
237 | "startAt",
|
238 | "attributes",
|
239 | { "name": "count", "boolean": true },
|
240 | { "name": "consistent", "boolean": true },
|
241 | { "name": "using", "settingsName": "index" }
|
242 | ];
|
243 | settings.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 | });
|
250 | DocumentRetriever.prototype.all = function (delay = 0, max = 0) {
|
251 | this.settings.all = { delay, max };
|
252 | return this;
|
253 | };
|
254 | class 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 | }
|
266 | exports.Scan = Scan;
|
267 | class 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 | }
|
279 | exports.Query = Query;
|
280 |
|
\ | No newline at end of file |