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.Document = void 0;
|
13 | const aws = require("./aws");
|
14 | const ddb = require("./aws/ddb/internal");
|
15 | const utils = require("./utils");
|
16 | const Error = require("./Error");
|
17 | const Internal = require("./Internal");
|
18 | const { internalProperties } = Internal.General;
|
19 | const dynamooseUndefined = Internal.Public.undefined;
|
20 | const Populate_1 = require("./Populate");
|
21 |
|
22 | class Document {
|
23 | constructor(model, object, settings) {
|
24 | const documentObject = Document.isDynamoObject(object) ? aws.converter().unmarshall(object) : object;
|
25 | Object.keys(documentObject).forEach((key) => this[key] = documentObject[key]);
|
26 | Object.defineProperty(this, internalProperties, {
|
27 | "configurable": false,
|
28 | "value": {}
|
29 | });
|
30 | this[internalProperties].originalObject = JSON.parse(JSON.stringify(documentObject));
|
31 | this[internalProperties].originalSettings = Object.assign({}, settings);
|
32 | Object.defineProperty(this, "model", {
|
33 | "configurable": false,
|
34 | "value": model
|
35 | });
|
36 | if (settings.type === "fromDynamo") {
|
37 | this[internalProperties].storedInDynamo = true;
|
38 | }
|
39 | }
|
40 | static objectToDynamo(object, settings = { "type": "object" }) {
|
41 | return (settings.type === "value" ? aws.converter().input : aws.converter().marshall)(object);
|
42 | }
|
43 | static fromDynamo(object) {
|
44 | return aws.converter().unmarshall(object);
|
45 | }
|
46 |
|
47 | static isDynamoObject(object, recurrsive) {
|
48 | function isValid(value) {
|
49 | if (typeof value === "undefined" || value === null) {
|
50 | return false;
|
51 | }
|
52 | const keys = Object.keys(value);
|
53 | const key = keys[0];
|
54 | const nestedResult = typeof value[key] === "object" && !(value[key] instanceof Buffer) ? Array.isArray(value[key]) ? value[key].every((value) => Document.isDynamoObject(value, true)) : Document.isDynamoObject(value[key]) : true;
|
55 | const { Schema } = require("./Schema");
|
56 | const attributeType = Schema.attributeTypes.findDynamoDBType(key);
|
57 | return typeof value === "object" && keys.length === 1 && attributeType && (nestedResult || Object.keys(value[key]).length === 0 || attributeType.isSet);
|
58 | }
|
59 | const keys = Object.keys(object);
|
60 | const values = Object.values(object);
|
61 | if (keys.length === 0) {
|
62 | return null;
|
63 | }
|
64 | else {
|
65 | return recurrsive ? isValid(object) : values.every((value) => isValid(value));
|
66 | }
|
67 | }
|
68 |
|
69 | prepareForResponse() {
|
70 | return __awaiter(this, void 0, void 0, function* () {
|
71 | if (this.model.options.populate) {
|
72 | return this.populate({ "properties": this.model.options.populate });
|
73 | }
|
74 | return this;
|
75 | });
|
76 | }
|
77 |
|
78 | original() {
|
79 | return this[internalProperties].originalSettings.type === "fromDynamo" ? this[internalProperties].originalObject : null;
|
80 |
|
81 | }
|
82 | toJSON() {
|
83 | return utils.dynamoose.documentToJSON.bind(this)();
|
84 | }
|
85 |
|
86 | serialize(nameOrOptions) {
|
87 | return this.model.serializer._serialize(this, nameOrOptions);
|
88 | }
|
89 | delete(callback) {
|
90 | return this.model.delete({
|
91 | [this.model.getHashKey()]: this[this.model.getHashKey()]
|
92 | }, callback);
|
93 | }
|
94 | save(settings, callback) {
|
95 | if (typeof settings !== "object" && typeof settings !== "undefined") {
|
96 | callback = settings;
|
97 | settings = {};
|
98 | }
|
99 | if (typeof settings === "undefined") {
|
100 | settings = {};
|
101 | }
|
102 | const localSettings = settings;
|
103 | const paramsPromise = this.toDynamo({ "defaults": true, "validate": true, "required": true, "enum": true, "forceDefault": true, "combine": true, "saveUnknown": true, "customTypesDynamo": true, "updateTimestamps": true, "modifiers": ["set"] }).then((item) => {
|
104 | const putItemObj = {
|
105 | "Item": item,
|
106 | "TableName": this.model.name
|
107 | };
|
108 | if (localSettings.overwrite === false) {
|
109 | putItemObj.ConditionExpression = "attribute_not_exists(#__hash_key)";
|
110 | putItemObj.ExpressionAttributeNames = { "#__hash_key": this.model.getHashKey() };
|
111 | }
|
112 | return putItemObj;
|
113 | });
|
114 | if (settings.return === "request") {
|
115 | if (callback) {
|
116 | const localCallback = callback;
|
117 | paramsPromise.then((result) => localCallback(null, result));
|
118 | return;
|
119 | }
|
120 | else {
|
121 | return paramsPromise;
|
122 | }
|
123 | }
|
124 | const promise = Promise.all([paramsPromise, this.model.pendingTaskPromise()]).then((promises) => {
|
125 | const [putItemObj] = promises;
|
126 | return ddb("putItem", putItemObj);
|
127 | });
|
128 | if (callback) {
|
129 | const localCallback = callback;
|
130 | promise.then(() => {
|
131 | this[internalProperties].storedInDynamo = true;
|
132 | localCallback(null, this);
|
133 | }).catch((error) => callback(error));
|
134 | }
|
135 | else {
|
136 | return (() => __awaiter(this, void 0, void 0, function* () {
|
137 | yield promise;
|
138 | this[internalProperties].storedInDynamo = true;
|
139 | return this;
|
140 | }))();
|
141 | }
|
142 | }
|
143 | populate(...args) {
|
144 | return Populate_1.PopulateDocument.bind(this)(...args);
|
145 | }
|
146 | }
|
147 | exports.Document = Document;
|
148 |
|
149 | Document.prepareForObjectFromSchema = function (object, model, settings) {
|
150 | return __awaiter(this, void 0, void 0, function* () {
|
151 | if (settings.updateTimestamps) {
|
152 | const schema = yield model.schemaForObject(object);
|
153 | if (schema.settings.timestamps && settings.type === "toDynamo") {
|
154 | const date = new Date();
|
155 | const createdAtProperties = (Array.isArray(schema.settings.timestamps.createdAt) ? schema.settings.timestamps.createdAt : [schema.settings.timestamps.createdAt]).filter((a) => Boolean(a));
|
156 | const updatedAtProperties = (Array.isArray(schema.settings.timestamps.updatedAt) ? schema.settings.timestamps.updatedAt : [schema.settings.timestamps.updatedAt]).filter((a) => Boolean(a));
|
157 | if (object[internalProperties] && !object[internalProperties].storedInDynamo && (typeof settings.updateTimestamps === "boolean" || settings.updateTimestamps.createdAt)) {
|
158 | createdAtProperties.forEach((prop) => {
|
159 | utils.object.set(object, prop, date);
|
160 | });
|
161 | }
|
162 | if (typeof settings.updateTimestamps === "boolean" || settings.updateTimestamps.updatedAt) {
|
163 | updatedAtProperties.forEach((prop) => {
|
164 | utils.object.set(object, prop, date);
|
165 | });
|
166 | }
|
167 | }
|
168 | }
|
169 | return object;
|
170 | });
|
171 | };
|
172 |
|
173 |
|
174 | const attributesWithSchemaCache = {};
|
175 | Document.attributesWithSchema = function (document, model) {
|
176 | return __awaiter(this, void 0, void 0, function* () {
|
177 | const schema = yield model.schemaForObject(document);
|
178 | const attributes = schema.attributes();
|
179 | const documentID = utils.object.keys(document).join("");
|
180 | if (attributesWithSchemaCache[documentID] && attributesWithSchemaCache[documentID][attributes.join()]) {
|
181 | return attributesWithSchemaCache[documentID][attributes.join()];
|
182 | }
|
183 |
|
184 | const root = {};
|
185 | attributes.forEach((attribute) => {
|
186 | let node = root;
|
187 | attribute.split(".").forEach((part) => {
|
188 | node[part] = node[part] || {};
|
189 | node = node[part];
|
190 | });
|
191 | });
|
192 |
|
193 | function traverse(node, treeNode, outPath, callback) {
|
194 | callback(outPath);
|
195 | if (Object.keys(treeNode).length === 0) {
|
196 | return;
|
197 | }
|
198 | Object.keys(treeNode).forEach((attr) => {
|
199 | if (attr === "0") {
|
200 | if (!node || node.length == 0) {
|
201 | node = [{}];
|
202 | }
|
203 | node.forEach((a, index) => {
|
204 | outPath.push(index);
|
205 | traverse(node[index], treeNode[attr], outPath, callback);
|
206 | outPath.pop();
|
207 | });
|
208 | }
|
209 | else {
|
210 | if (!node) {
|
211 | node = {};
|
212 | }
|
213 | outPath.push(attr);
|
214 | traverse(node[attr], treeNode[attr], outPath, callback);
|
215 | outPath.pop();
|
216 | }
|
217 | });
|
218 | }
|
219 | const out = [];
|
220 | traverse(document, root, [], (val) => out.push(val.join(".")));
|
221 | const result = out.slice(1);
|
222 | attributesWithSchemaCache[documentID] = { [attributes.join()]: result };
|
223 | return result;
|
224 | });
|
225 | };
|
226 |
|
227 | Document.objectFromSchema = function (object, model, settings = { "type": "toDynamo" }) {
|
228 | return __awaiter(this, void 0, void 0, function* () {
|
229 | if (settings.checkExpiredItem && model.options.expires && (model.options.expires.items || {}).returnExpired === false && object[model.options.expires.attribute] && object[model.options.expires.attribute] * 1000 < Date.now()) {
|
230 | return undefined;
|
231 | }
|
232 | const returnObject = Object.assign({}, object);
|
233 | const schema = settings.schema || (yield model.schemaForObject(returnObject));
|
234 | const schemaAttributes = schema.attributes(returnObject);
|
235 |
|
236 | const validParents = [];
|
237 | const keysToDelete = [];
|
238 | const typeIndexOptionMap = schema.getTypePaths(returnObject, settings);
|
239 | const checkTypeFunction = (item) => {
|
240 | const [key, value] = item;
|
241 | if (validParents.find((parent) => key.startsWith(parent.key) && (parent.infinite || key.split(".").length === parent.key.split(".").length + 1))) {
|
242 | return;
|
243 | }
|
244 | const genericKey = key.replace(/\.\d+/gu, ".0");
|
245 | const existsInSchema = schemaAttributes.includes(genericKey);
|
246 | if (existsInSchema) {
|
247 | const { isValidType, matchedTypeDetails, typeDetailsArray } = utils.dynamoose.getValueTypeCheckResult(schema, value, genericKey, settings, { "standardKey": true, typeIndexOptionMap });
|
248 | if (!isValidType) {
|
249 | throw new Error.TypeMismatch(`Expected ${key} to be of type ${typeDetailsArray.map((detail) => detail.dynamicName ? detail.dynamicName() : detail.name.toLowerCase()).join(", ")}, instead found type ${typeof value}.`);
|
250 | }
|
251 | else if (matchedTypeDetails.isSet || matchedTypeDetails.name.toLowerCase() === "model") {
|
252 | validParents.push({ key, "infinite": true });
|
253 | }
|
254 | else if ( matchedTypeDetails.dynamodbType === "L") {
|
255 |
|
256 | value.forEach((subValue, index, array) => {
|
257 | if (index === 0 || typeof subValue !== typeof array[0]) {
|
258 | checkTypeFunction([`${key}.${index}`, subValue]);
|
259 | }
|
260 | else if (keysToDelete.includes(`${key}.0`) && typeof subValue === typeof array[0]) {
|
261 | keysToDelete.push(`${key}.${index}`);
|
262 | }
|
263 | });
|
264 | validParents.push({ key });
|
265 | }
|
266 | }
|
267 | else {
|
268 |
|
269 | if (!settings.saveUnknown || !utils.dynamoose.wildcard_allowed_check(schema.getSettingValue("saveUnknown"), key)) {
|
270 | keysToDelete.push(key);
|
271 | }
|
272 | }
|
273 | };
|
274 | utils.object.entries(returnObject).filter((item) => item[1] !== undefined && item[1] !== dynamooseUndefined).map(checkTypeFunction);
|
275 | keysToDelete.reverse().forEach((key) => utils.object.delete(returnObject, key));
|
276 | if (settings.defaults || settings.forceDefault) {
|
277 | yield Promise.all((yield Document.attributesWithSchema(returnObject, model)).map((key) => __awaiter(this, void 0, void 0, function* () {
|
278 | const value = utils.object.get(returnObject, key);
|
279 | if (value === dynamooseUndefined) {
|
280 | utils.object.set(returnObject, key, undefined);
|
281 | }
|
282 | else {
|
283 | const defaultValue = yield schema.defaultCheck(key, value, settings);
|
284 | const isDefaultValueUndefined = Array.isArray(defaultValue) ? defaultValue.some((defaultValue) => typeof defaultValue === "undefined" || defaultValue === null) : typeof defaultValue === "undefined" || defaultValue === null;
|
285 | if (!isDefaultValueUndefined) {
|
286 | const { isValidType, typeDetailsArray } = utils.dynamoose.getValueTypeCheckResult(schema, defaultValue, key, settings, { typeIndexOptionMap });
|
287 | if (!isValidType) {
|
288 | throw new Error.TypeMismatch(`Expected ${key} to be of type ${typeDetailsArray.map((detail) => detail.dynamicName ? detail.dynamicName() : detail.name.toLowerCase()).join(", ")}, instead found type ${typeof defaultValue}.`);
|
289 | }
|
290 | else {
|
291 | utils.object.set(returnObject, key, defaultValue);
|
292 | }
|
293 | }
|
294 | }
|
295 | })));
|
296 | }
|
297 |
|
298 | if (settings.customTypesDynamo) {
|
299 | (yield Document.attributesWithSchema(returnObject, model)).map((key) => {
|
300 | const value = utils.object.get(returnObject, key);
|
301 | const isValueUndefined = typeof value === "undefined" || value === null;
|
302 | if (!isValueUndefined) {
|
303 | const typeDetails = utils.dynamoose.getValueTypeCheckResult(schema, value, key, settings, { typeIndexOptionMap }).matchedTypeDetails;
|
304 | const { customType } = typeDetails;
|
305 | const { "type": typeInfo } = typeDetails.isOfType(value);
|
306 | const isCorrectTypeAlready = typeInfo === (settings.type === "toDynamo" ? "underlying" : "main");
|
307 | if (customType && !isCorrectTypeAlready) {
|
308 | const customValue = customType.functions[settings.type](value);
|
309 | utils.object.set(returnObject, key, customValue);
|
310 | }
|
311 | }
|
312 | });
|
313 | }
|
314 |
|
315 | utils.object.entries(returnObject).filter((item) => typeof item[1] === "object").forEach((item) => {
|
316 | const [key, value] = item;
|
317 | let typeDetails;
|
318 | try {
|
319 | typeDetails = utils.dynamoose.getValueTypeCheckResult(schema, value, key, settings, { typeIndexOptionMap }).matchedTypeDetails;
|
320 | }
|
321 | catch (e) {
|
322 | const { Schema } = require("./Schema");
|
323 | typeDetails = Schema.attributeTypes.findTypeForValue(value, settings.type, settings);
|
324 | }
|
325 | if (typeDetails && typeDetails[settings.type]) {
|
326 | utils.object.set(returnObject, key, typeDetails[settings.type](value));
|
327 | }
|
328 | });
|
329 | if (settings.combine) {
|
330 | schemaAttributes.map((key) => {
|
331 | try {
|
332 | const typeDetails = schema.getAttributeTypeDetails(key);
|
333 | return {
|
334 | key,
|
335 | "type": typeDetails
|
336 | };
|
337 | }
|
338 | catch (e) { }
|
339 | }).map((obj) => {
|
340 | if (obj && Array.isArray(obj.type)) {
|
341 | throw new Error.InvalidParameter("Combine type is not allowed to be used with multiple types.");
|
342 | }
|
343 | return obj;
|
344 | }).filter((item) => item.type.name === "Combine").forEach((item) => {
|
345 | const { key, type } = item;
|
346 | const value = type.typeSettings.attributes.map((attribute) => utils.object.get(returnObject, attribute)).filter((value) => typeof value !== "undefined" && value !== null).join(type.typeSettings.seperator);
|
347 | utils.object.set(returnObject, key, value);
|
348 | });
|
349 | }
|
350 | if (settings.modifiers) {
|
351 | yield Promise.all(settings.modifiers.map((modifier) => __awaiter(this, void 0, void 0, function* () {
|
352 | return Promise.all((yield Document.attributesWithSchema(returnObject, model)).map((key) => __awaiter(this, void 0, void 0, function* () {
|
353 | const value = utils.object.get(returnObject, key);
|
354 | const modifierFunction = yield schema.getAttributeSettingValue(modifier, key, { "returnFunction": true });
|
355 | const modifierFunctionExists = Array.isArray(modifierFunction) ? modifierFunction.some((val) => Boolean(val)) : Boolean(modifierFunction);
|
356 | const isValueUndefined = typeof value === "undefined" || value === null;
|
357 | if (modifierFunctionExists && !isValueUndefined) {
|
358 | const oldValue = object.original ? utils.object.get(object.original(), key) : undefined;
|
359 | utils.object.set(returnObject, key, yield modifierFunction(value, oldValue));
|
360 | }
|
361 | })));
|
362 | })));
|
363 | }
|
364 | if (settings.validate) {
|
365 | yield Promise.all((yield Document.attributesWithSchema(returnObject, model)).map((key) => __awaiter(this, void 0, void 0, function* () {
|
366 | const value = utils.object.get(returnObject, key);
|
367 | const isValueUndefined = typeof value === "undefined" || value === null;
|
368 | if (!isValueUndefined) {
|
369 | const validator = yield schema.getAttributeSettingValue("validate", key, { "returnFunction": true });
|
370 | if (validator) {
|
371 | let result;
|
372 | if (validator instanceof RegExp) {
|
373 |
|
374 | result = validator.test(value);
|
375 | }
|
376 | else {
|
377 | result = typeof validator === "function" ? yield validator(value) : validator === value;
|
378 | }
|
379 | if (!result) {
|
380 | throw new Error.ValidationError(`${key} with a value of ${value} had a validation error when trying to save the document`);
|
381 | }
|
382 | }
|
383 | }
|
384 | })));
|
385 | }
|
386 | if (settings.required) {
|
387 | let attributesToCheck = yield Document.attributesWithSchema(returnObject, model);
|
388 | if (settings.required === "nested") {
|
389 | attributesToCheck = attributesToCheck.filter((attribute) => utils.object.keys(returnObject).find((key) => attribute.startsWith(key)));
|
390 | }
|
391 | yield Promise.all(attributesToCheck.map((key) => __awaiter(this, void 0, void 0, function* () {
|
392 | const check = () => __awaiter(this, void 0, void 0, function* () {
|
393 | const value = utils.object.get(returnObject, key);
|
394 | yield schema.requiredCheck(key, value);
|
395 | });
|
396 | const keyParts = key.split(".");
|
397 | const parentKey = keyParts.slice(0, -1).join(".");
|
398 | if (parentKey) {
|
399 | const parentValue = utils.object.get(returnObject, parentKey);
|
400 | const isParentValueUndefined = typeof parentValue === "undefined" || parentValue === null;
|
401 | if (!isParentValueUndefined) {
|
402 | yield check();
|
403 | }
|
404 | }
|
405 | else {
|
406 | yield check();
|
407 | }
|
408 | })));
|
409 | }
|
410 | if (settings.enum) {
|
411 | yield Promise.all((yield Document.attributesWithSchema(returnObject, model)).map((key) => __awaiter(this, void 0, void 0, function* () {
|
412 | const value = utils.object.get(returnObject, key);
|
413 | const isValueUndefined = typeof value === "undefined" || value === null;
|
414 | if (!isValueUndefined) {
|
415 | const enumArray = yield schema.getAttributeSettingValue("enum", key);
|
416 | if (enumArray && !enumArray.includes(value)) {
|
417 | throw new Error.ValidationError(`${key} must equal ${JSON.stringify(enumArray)}, but is set to ${value}`);
|
418 | }
|
419 | }
|
420 | })));
|
421 | }
|
422 | return returnObject;
|
423 | });
|
424 | };
|
425 | Document.prototype.toDynamo = function (settings = {}) {
|
426 | return __awaiter(this, void 0, void 0, function* () {
|
427 | const newSettings = Object.assign(Object.assign({}, settings), { "type": "toDynamo" });
|
428 | yield Document.prepareForObjectFromSchema(this, this.model, newSettings);
|
429 | const object = yield Document.objectFromSchema(this, this.model, newSettings);
|
430 | return Document.objectToDynamo(object);
|
431 | });
|
432 | };
|
433 |
|
434 | Document.prototype.conformToSchema = function (settings = { "type": "fromDynamo" }) {
|
435 | return __awaiter(this, void 0, void 0, function* () {
|
436 | let document = this;
|
437 | if (settings.type === "fromDynamo") {
|
438 | document = yield this.prepareForResponse();
|
439 | }
|
440 | yield Document.prepareForObjectFromSchema(document, document.model, settings);
|
441 | const expectedObject = yield Document.objectFromSchema(document, document.model, settings);
|
442 | if (!expectedObject) {
|
443 | return expectedObject;
|
444 | }
|
445 | const expectedKeys = Object.keys(expectedObject);
|
446 | Object.keys(document).forEach((key) => {
|
447 | if (!expectedKeys.includes(key)) {
|
448 | delete this[key];
|
449 | }
|
450 | else if (this[key] !== expectedObject[key]) {
|
451 | this[key] = expectedObject[key];
|
452 | }
|
453 | });
|
454 | return this;
|
455 | });
|
456 | };
|
457 |
|
\ | No newline at end of file |