@contentstack/datasync-mongodb-sdk 1.0.9-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.github/workflows/codeql-analysis.yml +68 -0
  2. package/.github/workflows/jira.yml +33 -0
  3. package/.github/workflows/release.yml +77 -0
  4. package/.github/workflows/sast-scan.yml +11 -0
  5. package/.github/workflows/sca-scan.yml +15 -0
  6. package/.talismanrc +4 -0
  7. package/CODEOWNERS +1 -0
  8. package/LICENCE +21 -0
  9. package/README.md +191 -0
  10. package/SECURITY.md +27 -0
  11. package/contentstack-datasync-mongodb-sdk-1.0.9-beta.1.tgz +0 -0
  12. package/dist/config.js +47 -0
  13. package/dist/index.js +42 -0
  14. package/dist/stack.js +2272 -0
  15. package/dist/util.js +86 -0
  16. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  17. package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
  18. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  19. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  20. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  21. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  22. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  23. package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
  24. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  25. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  26. package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
  27. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  28. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  29. package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  30. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  31. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  32. package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
  33. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  34. package/docs/global.html +7520 -0
  35. package/docs/global.html#Stack +1070 -0
  36. package/docs/index.html +291 -0
  37. package/docs/index.js.html +92 -0
  38. package/docs/scripts/linenumber.js +25 -0
  39. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  40. package/docs/scripts/prettify/lang-css.js +2 -0
  41. package/docs/stack.js.html +2244 -0
  42. package/docs/styles/jsdoc-default.css +358 -0
  43. package/docs/styles/prettify-jsdoc.css +111 -0
  44. package/docs/styles/prettify-tomorrow.css +132 -0
  45. package/example/index.js +56 -0
  46. package/package.json +59 -0
  47. package/test/comparison-operators.ts +257 -0
  48. package/test/conditional-operators.ts +106 -0
  49. package/test/config.ts +12 -0
  50. package/test/core.ts +333 -0
  51. package/test/count.ts +98 -0
  52. package/test/data/assets.ts +35 -0
  53. package/test/data/author.ts +168 -0
  54. package/test/data/blog.ts +138 -0
  55. package/test/data/category.ts +20 -0
  56. package/test/data/content_types.ts +164 -0
  57. package/test/data/products.ts +64 -0
  58. package/test/expressions.ts +108 -0
  59. package/test/include-exclude.ts +176 -0
  60. package/test/logical-operators.ts +140 -0
  61. package/test/projections.ts +109 -0
  62. package/test/queries.ts +143 -0
  63. package/test/references.ts +162 -0
  64. package/test/skip-limit.ts +150 -0
  65. package/test/sorting.ts +177 -0
  66. package/tslint.json +45 -0
  67. package/typings/config.d.ts +42 -0
  68. package/typings/index.d.ts +36 -0
  69. package/typings/stack.d.ts +1097 -0
  70. package/typings/util.d.ts +28 -0
package/dist/stack.js ADDED
@@ -0,0 +1,2272 @@
1
+ "use strict";
2
+ /*!
3
+ * Contentstack DataSync Mongodb SDK
4
+ * Copyright (c) 2019 Contentstack LLC
5
+ * MIT Licensed
6
+ */
7
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
8
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
11
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
12
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
13
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
14
+ });
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.Stack = void 0;
21
+ const lodash_1 = require("lodash");
22
+ const mongodb_1 = require("mongodb");
23
+ const sift_1 = __importDefault(require("sift"));
24
+ const config_1 = require("./config");
25
+ const util_1 = require("./util");
26
+ /**
27
+ * @class Stack
28
+ * @descriptionExpose SDK query methods on Stack
29
+ * @constructor
30
+ * @descriptionProvides a range of connection/disconnect, filters and projections on mongodb
31
+ * @returns {Stack} Returns an instance of `Stack`
32
+ */
33
+ class Stack {
34
+ constructor(stackConfig, existingDB) {
35
+ this.config = (0, lodash_1.merge)(config_1.config, stackConfig);
36
+ // validates config.locales property
37
+ (0, util_1.validateConfig)(this.config);
38
+ this.contentStore = this.config.contentStore;
39
+ this.collectionNames = this.contentStore.collection;
40
+ this.types = this.contentStore.internal.types;
41
+ this.q = {};
42
+ this.internal = {};
43
+ this.db = existingDB;
44
+ }
45
+ /**
46
+ * @public
47
+ * @method ascending
48
+ * @summary Sorts the documents based on the 'sort' key
49
+ * @description
50
+ * The sort function requires that the entire sort be able to complete within 32 megabytes.
51
+ * When the sort option consumes more than 32 megabytes, MongoDB will return an error.
52
+ * @param {string} field The field to sort in ascending order
53
+ * @example
54
+ * Stack
55
+ * .contentType('')
56
+ * .entries()
57
+ * .ascending()
58
+ * .find()
59
+ * .then((result) => {
60
+ * // result sorted in ascending manner with respect to 'published_at' field (by default)
61
+ * })
62
+ * .catch((error) => {
63
+ * // handle query errors
64
+ * })
65
+ *
66
+ * @returns {Stack} Returns an instance of 'stack'
67
+ */
68
+ ascending(field) {
69
+ if (typeof this.q.content_type_uid !== 'string' || typeof field !== 'string' || field.length === 0) {
70
+ throw new Error('Kindly provide valid parameters for .ascending!');
71
+ }
72
+ else if (this.internal.sort && typeof this.internal.sort === 'object') {
73
+ this.internal.sort[field] = 1;
74
+ }
75
+ else {
76
+ this.internal.sort = {
77
+ [field]: 1,
78
+ };
79
+ }
80
+ return this;
81
+ }
82
+ /**
83
+ * @public
84
+ * @method descending
85
+ * @summary Sorts the documents based on the 'sort' key
86
+ * @description
87
+ * The sort function requires that the entire sort be able to complete within 32 megabytes.
88
+ * When the sort option consumes more than 32 megabytes, MongoDB will return an error.
89
+ *
90
+ * @param {string} field The field to sort in descending order
91
+ * @example
92
+ * Stack
93
+ * .contentType('')
94
+ * .entries()
95
+ * .descending('title')
96
+ * .find()
97
+ * .then((result) => {
98
+ * // result sorted in descending manner with respect to 'title' field
99
+ * })
100
+ * .catch((error) => {
101
+ * // handle query errors
102
+ * })
103
+ *
104
+ * @returns {Stack} Returns an instance of 'stack'
105
+ */
106
+ descending(field) {
107
+ if (typeof this.q.content_type_uid !== 'string' || typeof field !== 'string' || field.length === 0) {
108
+ throw new Error('Kindly provide valid parameters for .descending()!');
109
+ }
110
+ else if (this.internal.sort && typeof this.internal.sort === 'object') {
111
+ this.internal.sort[field] = -1;
112
+ }
113
+ else {
114
+ this.internal.sort = {
115
+ [field]: -1,
116
+ };
117
+ }
118
+ return this;
119
+ }
120
+ /**
121
+ * @public
122
+ * @method connect
123
+ * @summary
124
+ * Establish connection to mongodb
125
+ *
126
+ * @param {object} overrides Config overrides/mongodb specific config
127
+ * @example
128
+ * Stack
129
+ * .connect({overrides})
130
+ * .then((result) => {
131
+ * // mongodb connection object
132
+ * // indexes will be created on the collection in the background if provided in config
133
+ * })
134
+ * .catch((error) => {
135
+ * // handle query errors
136
+ * })
137
+ *
138
+ * @returns {object} Mongodb 'db' instance
139
+ */
140
+ connect(overrides = {}) {
141
+ return __awaiter(this, void 0, void 0, function* () {
142
+ const dbConfig = (0, lodash_1.merge)({}, this.config, overrides).contentStore;
143
+ const url = (0, util_1.validateURI)(dbConfig.url);
144
+ const options = dbConfig.options;
145
+ const dbName = dbConfig.dbName;
146
+ const client = new mongodb_1.MongoClient(url, options);
147
+ this.client = client;
148
+ yield client.connect();
149
+ this.db = client.db(dbName);
150
+ return this.db;
151
+ });
152
+ }
153
+ /**
154
+ * @public
155
+ * @method close
156
+ * @summary Closes connection with mongodb
157
+ */
158
+ close() {
159
+ this.client.close();
160
+ }
161
+ /**
162
+ * @method language
163
+ * @description
164
+ * Locale to query on
165
+ *
166
+ * @param {string} code Query locale's code
167
+ * @example
168
+ * Stack
169
+ * .contentType('')
170
+ * .entries()
171
+ * .language('es-es')
172
+ * .find()
173
+ * .then((result) => {
174
+ * // results in entries fetched from 'es-es' locale
175
+ * // if not provided, defaults to the 1st locale provided in the 'locales' key, provided in config
176
+ * })
177
+ * .catch((error) => {
178
+ * // handle query errors
179
+ * })
180
+ *
181
+ * @returns {Stack} Returns an instance of 'stack'
182
+ */
183
+ language(code) {
184
+ if (typeof code !== 'string' || code.length === 0) {
185
+ throw new Error('Kindly pass valid parameters for .language()!');
186
+ }
187
+ this.q.locale = code;
188
+ return this;
189
+ }
190
+ /**
191
+ * @public
192
+ * @method and
193
+ * @summary Logical AND query wrapper
194
+ * @descriptionAccepts 2 queries and returns only those documents, that satisfy both the query conditions
195
+ * @param {object} queries Query filter
196
+ * @example
197
+ * Stack
198
+ * .contentType('')
199
+ * .entries()
200
+ * .and([
201
+ * {
202
+ * title: 'John'
203
+ * },
204
+ * {
205
+ * age: 30
206
+ * }
207
+ * ])
208
+ * .find()
209
+ * .then((result) => {
210
+ * // filtered entries, where { title: 'John', age: 30 }
211
+ * })
212
+ * .catch((error) => {
213
+ * // handle query errors
214
+ * })
215
+ *
216
+ * @returns {Stack} Returns an instance of 'stack'
217
+ */
218
+ and(queries) {
219
+ if (typeof queries !== 'object' || !Array.isArray(queries)) {
220
+ throw new Error('Kindly provide valid parameters for .and()!');
221
+ }
222
+ else if (this.q.query && typeof this.q.query === 'object') {
223
+ this.q.query = (0, lodash_1.merge)(this.q.query, {
224
+ $and: queries,
225
+ });
226
+ }
227
+ else {
228
+ this.q.query = {
229
+ $and: queries,
230
+ };
231
+ }
232
+ return this;
233
+ }
234
+ /**
235
+ * @public
236
+ * @method or
237
+ * @summary Logical OR query wrapper
238
+ * @descriptionAccepts 2 queries and returns only those documents, that satisfy either of the query conditions
239
+ * @param {object} queries Query filter
240
+ * @example
241
+ * Stack
242
+ * .contentType('')
243
+ * .entries()
244
+ * .or([
245
+ * {
246
+ * title: 'John'
247
+ * },
248
+ * {
249
+ * title: 'Jane'
250
+ * }
251
+ * ])
252
+ * .find()
253
+ * .then((result) => {
254
+ * // filtered entries, where { title: 'John' } OR { title: 'Jane' }
255
+ * })
256
+ * .catch((error) => {
257
+ * // handle query errors
258
+ * })
259
+ *
260
+ * @returns {Stack} Returns an instance of 'stack'
261
+ */
262
+ or(queries) {
263
+ if (typeof queries !== 'object' || !Array.isArray(queries)) {
264
+ throw new Error('Kindly provide valid parameters for .or()!');
265
+ }
266
+ else if (this.q.query && typeof this.q.query === 'object') {
267
+ this.q.query = (0, lodash_1.merge)(this.q.query, {
268
+ $or: queries,
269
+ });
270
+ }
271
+ else {
272
+ this.q.query = {
273
+ $or: queries,
274
+ };
275
+ }
276
+ return this;
277
+ }
278
+ /**
279
+ * @public
280
+ * @method lessThan
281
+ * @summary Comparison $lt query wrapper
282
+ * @description
283
+ * Compares the field/key provided against the provided value.
284
+ * Only documents that have lower value than the one provided are returned.
285
+ * Check https://docs.mongodb.com/manual/reference/operator/query/lt/
286
+ * and https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing for more info
287
+ * @param {string} key Field to compare against
288
+ * @param {*} value Value to compare with
289
+ * @example
290
+ * Stack
291
+ * .contentType('')
292
+ * .entries()
293
+ * .lessThan('age', 18)
294
+ * .find()
295
+ * .then((result) => {
296
+ * // filtered entries, where { age < 18 }
297
+ * })
298
+ * .catch((error) => {
299
+ * // handle query errors
300
+ * })
301
+ *
302
+ * @returns {Stack} Returns an instance of 'stack'
303
+ */
304
+ lessThan(key, value) {
305
+ if (typeof key !== 'string' || typeof value === 'undefined') {
306
+ throw new Error('Kindly pass valid key and value parameters for \'.lessThan()\'');
307
+ }
308
+ else if (this.q.query && typeof this.q.query === 'object') {
309
+ this.q.query[key] = {
310
+ $lt: value,
311
+ };
312
+ }
313
+ else {
314
+ this.q.query = {
315
+ [key]: {
316
+ $lt: value,
317
+ },
318
+ };
319
+ }
320
+ return this;
321
+ }
322
+ /**
323
+ * @public
324
+ * @method lessThanOrEqualTo
325
+ * @summary Comparison $lte query wrapper
326
+ * @description
327
+ * Compares the field/key provided against the provided value.
328
+ * Only documents that have lower or equal value than the one provided are returned.
329
+ * Check https://docs.mongodb.com/manual/reference/operator/query/lte/
330
+ * and https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing for more info
331
+ * @param {string} key Field to compare against
332
+ * @param {*} value Value to compare with
333
+ * @example
334
+ * Stack
335
+ * .contentType('')
336
+ * .entries()
337
+ * .lessThanOrEqualTo('age', 18)
338
+ * .find()
339
+ * .then((result) => {
340
+ * // filtered entries, where { age <= 18 }
341
+ * })
342
+ * .catch((error) => {
343
+ * // handle query errors
344
+ * })
345
+ *
346
+ * @returns {Stack} Returns an instance of 'stack'
347
+ */
348
+ lessThanOrEqualTo(key, value) {
349
+ if (typeof key !== 'string' || typeof value === 'undefined') {
350
+ throw new Error('Kindly pass valid key and value parameters for \'.lessThanOrEqualTo()\'');
351
+ }
352
+ else if (this.q.query && typeof this.q.query === 'object') {
353
+ this.q.query[key] = {
354
+ $lte: value,
355
+ };
356
+ }
357
+ else {
358
+ this.q.query = {
359
+ [key]: {
360
+ $lte: value,
361
+ },
362
+ };
363
+ }
364
+ return this;
365
+ }
366
+ /**
367
+ * @public
368
+ * @method greaterThan
369
+ * @summary Comparison $gt query wrapper
370
+ * @description
371
+ * Compares the field/key provided against the provided value.
372
+ * Only documents that have greater value than the one provided are returned.
373
+ * Check {@link https://docs.mongodb.com/manual/reference/operator/query/gt/ }
374
+ * and https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing for more info
375
+ * @param {string} key Field to compare against
376
+ * @param {*} value Value to compare with
377
+ * @example
378
+ * Stack
379
+ * .contentType('')
380
+ * .entries()
381
+ * .greaterThan('age', 60)
382
+ * .find()
383
+ * .then((result) => {
384
+ * // filtered entries, where { age > 60 }
385
+ * })
386
+ * .catch((error) => {
387
+ * // handle query errors
388
+ * })
389
+ *
390
+ * @returns {Stack} Returns an instance of 'stack'
391
+ */
392
+ greaterThan(key, value) {
393
+ if (typeof key !== 'string' || typeof value === 'undefined') {
394
+ throw new Error('Kindly pass valid key and value parameters for \'.greaterThan()\'');
395
+ }
396
+ else if (this.q.query && typeof this.q.query === 'object') {
397
+ this.q.query[key] = {
398
+ $gt: value,
399
+ };
400
+ }
401
+ else {
402
+ this.q.query = {
403
+ [key]: {
404
+ $gt: value,
405
+ },
406
+ };
407
+ }
408
+ return this;
409
+ }
410
+ /**
411
+ * @public
412
+ * @method greaterThanOrEqualTo
413
+ * @summary Comparison $gte query wrapper
414
+ * @description
415
+ * Compares the field/key provided against the provided value.
416
+ * Only documents that have greater than or equal value than the one provided are returned.
417
+ * Check https://docs.mongodb.com/manual/reference/operator/query/gte/ and
418
+ * https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing for more info
419
+ * @param {string} key - Field to compare against
420
+ * @param {*} value - Value to compare with
421
+ * @example
422
+ * Stack
423
+ * .contentType('')
424
+ * .entries()
425
+ * .greaterThanOrEqualTo('age', 60)
426
+ * .find()
427
+ * .then((result) => {
428
+ * // filtered entries, where { age >= 60 }
429
+ * })
430
+ * .catch((error) => {
431
+ * // handle query errors
432
+ * })
433
+ *
434
+ * @returns {Stack} Returns an instance of 'stack'
435
+ */
436
+ greaterThanOrEqualTo(key, value) {
437
+ if (typeof key !== 'string' || typeof value === 'undefined') {
438
+ throw new Error('Kindly pass valid key and value parameters for \'.greaterThanOrEqualTo()\'');
439
+ }
440
+ else if (this.q.query && typeof this.q.query === 'object') {
441
+ this.q.query[key] = {
442
+ $gte: value,
443
+ };
444
+ }
445
+ else {
446
+ this.q.query = {
447
+ [key]: {
448
+ $gte: value,
449
+ },
450
+ };
451
+ }
452
+ return this;
453
+ }
454
+ /**
455
+ * @public
456
+ * @method notEqualTo
457
+ * @summary Comparison $ne query wrapper
458
+ * @description
459
+ * Compares the field/key provided against the provided value.
460
+ * Only documents that have value not equals than the one provided are returned.
461
+ *
462
+ * Check mongodb query here: {@link https://docs.mongodb.com/manual/reference/operator/query/ne/}.
463
+ *
464
+ * Res: {@link https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing}.
465
+ *
466
+ * Comparison ordering
467
+ * {@link https://docs.mongodb.com/manual/reference/bson-type-comparison-order/#bson-types-comparison-order}
468
+ * @param {string} key Field to compare against
469
+ * @param {*} value Value to compare with
470
+ * @example
471
+ * Stack
472
+ * .contentType('')
473
+ * .entries()
474
+ * .notEqualTo('age', 25)
475
+ * .find()
476
+ * .then((result) => {
477
+ * // filtered entries, where { age != 25 }
478
+ * })
479
+ * .catch((error) => {
480
+ * // handle query errors
481
+ * })
482
+ *
483
+ * @returns {Stack} Returns an instance of 'stack'
484
+ */
485
+ notEqualTo(key, value) {
486
+ if (typeof key !== 'string' || typeof value === 'undefined') {
487
+ throw new Error('Kindly pass valid key and value parameters for \'.notEqualTo()\'');
488
+ }
489
+ else if (this.q.query && typeof this.q.query === 'object') {
490
+ this.q.query[key] = {
491
+ $ne: value,
492
+ };
493
+ }
494
+ else {
495
+ this.q.query = {
496
+ [key]: {
497
+ $ne: value,
498
+ },
499
+ };
500
+ }
501
+ return this;
502
+ }
503
+ /**
504
+ * @public
505
+ * @method containedIn
506
+ * @summary Comparison $in query wrapper
507
+ * @description
508
+ * Compares the field/key provided against the provided value.
509
+ * Only documents that have value contained in the field/key provided are returned.
510
+ *
511
+ * Check mongodb query here: {@link https://docs.mongodb.com/manual/reference/operator/query/in/}.
512
+ *
513
+ * Res: {@link https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing}.
514
+ *
515
+ * Comparison ordering
516
+ * {@link https://docs.mongodb.com/manual/reference/bson-type-comparison-order/#bson-types-comparison-order}
517
+ * @param {string} key Field to compare against
518
+ * @param {*} value Value to compare with
519
+ *
520
+ * @example
521
+ * Stack
522
+ * .contentType('')
523
+ * .entries()
524
+ * .containedIn('emails', 'john.doe@some.com')
525
+ * .find()
526
+ * .then((result) => {
527
+ * // filtered entries, where 'john.doe@some.com' exists in 'emails' field (array)
528
+ * })
529
+ * .catch((error) => {
530
+ * // handle query errors
531
+ * })
532
+ *
533
+ * @returns {Stack} Returns an instance of 'stack'
534
+ */
535
+ containedIn(key, value) {
536
+ if (typeof key !== 'string' || typeof value !== 'object' || !(value instanceof Array)) {
537
+ throw new Error('Kindly pass valid key and value parameters for \'.containedIn()\'');
538
+ }
539
+ else if (this.q.query && typeof this.q.query === 'object') {
540
+ this.q.query[key] = {
541
+ $in: value,
542
+ };
543
+ }
544
+ else {
545
+ this.q.query = {
546
+ [key]: {
547
+ $in: value,
548
+ },
549
+ };
550
+ }
551
+ return this;
552
+ }
553
+ /**
554
+ * @public
555
+ * @method notContainedIn
556
+ * @summary Comparison $nin query wrapper
557
+ * @description
558
+ * Compares the field/key provided against the provided value.
559
+ * Only documents that have value not contained in the field/key provided are returned.
560
+ *
561
+ * Check mongodb query here: {@link https://docs.mongodb.com/manual/reference/operator/query/nin/}.
562
+ *
563
+ * Res: {@link https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing}.
564
+ *
565
+ * Comparison ordering
566
+ * {@link https://docs.mongodb.com/manual/reference/bson-type-comparison-order/#bson-types-comparison-order}
567
+ * @param {string} key Field to compare against
568
+ * @param {*} value Value to compare with
569
+ *
570
+ * @example
571
+ * Stack
572
+ * .contentType('')
573
+ * .entries()
574
+ * .notContainedIn('emails', 'john.doe@some.com')
575
+ * .find()
576
+ * .then((result) => {
577
+ * // filtered entries, where 'john.doe@some.com' does not exist in 'emails' field (array)
578
+ * })
579
+ * .catch((error) => {
580
+ * // handle query errors
581
+ * })
582
+ *
583
+ * @returns {Stack} Returns an instance of 'stack'
584
+ */
585
+ notContainedIn(key, value) {
586
+ if (typeof key !== 'string' || typeof value !== 'object' || !(value instanceof Array)) {
587
+ throw new Error('Kindly pass valid key and value parameters for \'.notContainedIn()\'');
588
+ }
589
+ else if (this.q.query && typeof this.q.query === 'object') {
590
+ this.q.query[key] = {
591
+ $nin: value,
592
+ };
593
+ }
594
+ else {
595
+ this.q.query = {
596
+ [key]: {
597
+ $nin: value,
598
+ },
599
+ };
600
+ }
601
+ return this;
602
+ }
603
+ /**
604
+ * @public
605
+ * @method exists
606
+ * @summary Element $exists query wrapper, checks if a field exists
607
+ * @description
608
+ * Compares the field / key provided against the provided value.Only documents that have the field /
609
+ * key specified are returned.
610
+ *
611
+ * Check mongodb query here: {@link https://docs.mongodb.com/manual/reference/operator/query/exists/}.
612
+ *
613
+ * Res: {@link https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing}.
614
+ *
615
+ * Comparison ordering{
616
+ * @link https: //docs.mongodb.com/manual/reference/bson-type-comparison-order/#bson-types-comparison-order}
617
+ * @param {string} key Field to compare against
618
+ * @param {*} value Value to compare with
619
+ *
620
+ * @example
621
+ * Stack
622
+ * .contentType('')
623
+ * .entries()
624
+ * .exists('emails')
625
+ * .find()
626
+ * .then((result) => {
627
+ * // filtered entries, where 'emails' property exists
628
+ * })
629
+ * .catch((error) => {
630
+ * // handle query errors
631
+ * })
632
+ *
633
+ * @returns {Stack} Returns an instance of 'stack'
634
+ */
635
+ exists(key) {
636
+ if (typeof key !== 'string') {
637
+ throw new Error('Kindly pass valid key for \'.exists()\'');
638
+ }
639
+ else if (this.q.query && typeof this.q.query === 'object') {
640
+ this.q.query[key] = {
641
+ $exists: true,
642
+ };
643
+ }
644
+ else {
645
+ this.q.query = {
646
+ [key]: {
647
+ $exists: true,
648
+ },
649
+ };
650
+ }
651
+ return this;
652
+ }
653
+ /**
654
+ * @public
655
+ * @method notExists
656
+ * @summary
657
+ * Property $exists query wrapper, checks if a field does not exists
658
+ * @description
659
+ * Compares the field/key provided against the provided value. Only documents that do not have the key are returned.
660
+ *
661
+ * Check mongodb query here: {@link https://docs.mongodb.com/manual/reference/operator/query/exists/}.
662
+ *
663
+ * Res: {@link https://docs.mongodb.com/manual/reference/method/db.collection.find/#type-bracketing}.
664
+ *
665
+ * Comparison ordering{
666
+ * @link https: //docs.mongodb.com/manual/reference/bson-type-comparison-order/#bson-types-comparison-order}
667
+ * @param {string} key Field to compare against
668
+ * @param {*} value Value to compare with
669
+ * @example
670
+ * Stack
671
+ * .contentType('')
672
+ * .entries()
673
+ * .notExists('emails')
674
+ * .find()
675
+ * .then((result) => {
676
+ * // filtered entries, where 'emails' property does not exist
677
+ * })
678
+ * .catch((error) => {
679
+ * // handle query errors
680
+ * })
681
+ *
682
+ * @returns {Stack} Returns an instance of 'stack'
683
+ */
684
+ notExists(key) {
685
+ if (typeof key !== 'string') {
686
+ throw new Error('Kindly pass valid key for \'.notExists()\'');
687
+ }
688
+ else if (this.q.query && typeof this.q.query === 'object') {
689
+ this.q.query[key] = {
690
+ $exists: false,
691
+ };
692
+ }
693
+ else {
694
+ this.q.query = {
695
+ [key]: {
696
+ $exists: false,
697
+ },
698
+ };
699
+ }
700
+ return this;
701
+ }
702
+ /**
703
+ * @public
704
+ * @method contentType
705
+ * @summary Content type to query on
706
+ * @param {string} uid Content type uid
707
+ * @example
708
+ * Stack
709
+ * .contentType('blog')
710
+ * .entries()
711
+ * .find()
712
+ * .then((result) => {
713
+ * // returns entries filtered based on 'blog' content type
714
+ * })
715
+ * .catch((error) => {
716
+ * // handle query errors
717
+ * })
718
+ *
719
+ * @returns {Stack} Returns an instance of 'stack'
720
+ */
721
+ contentType(uid) {
722
+ // create new instances, instead of re-using the old one
723
+ const stack = new Stack(this.config, this.db);
724
+ if (uid && typeof uid === 'string') {
725
+ stack.q.content_type_uid = uid;
726
+ return stack;
727
+ }
728
+ throw new Error('Kindly pass the content type\'s uid');
729
+ }
730
+ /**
731
+ * @public
732
+ * @method entry
733
+ * @summary Query for a single entry
734
+ * @param {string} uid Entry uid to be found, if not provided,
735
+ * by default returns the 1st element in the content type.
736
+ * Useful for `singleton` content types
737
+ * @example
738
+ * Stack
739
+ * .contentType('blog')
740
+ * .entry()
741
+ * .find()
742
+ * .then((result) => {
743
+ * // returns the entry based on its 'uid',
744
+ * // if not provided, it would return the 1st entry found in 'blog' content type
745
+ * })
746
+ * .catch((error) => {
747
+ * // handle query errors
748
+ * })
749
+ *
750
+ * @returns {Stack} Returns an instance of 'stack'
751
+ */
752
+ entry(uid) {
753
+ if (!(this.q.content_type_uid)) {
754
+ throw new Error('Kindly call \'contentType()\' before \'entry()\'!');
755
+ }
756
+ if (uid && typeof uid === 'string') {
757
+ this.q.query = this.q.query || {};
758
+ this.q.query.uid = uid;
759
+ }
760
+ this.internal.limit = 1;
761
+ this.internal.single = true;
762
+ return this;
763
+ }
764
+ /**
765
+ * @public
766
+ * @method entries
767
+ * @description
768
+ * Query for a set of entries on a content type
769
+ *
770
+ * @example
771
+ * Stack
772
+ * .contentType('blog')
773
+ * .entries()
774
+ * .find()
775
+ * .then((result) => {
776
+ * // returns entries filtered based on 'blog' content type
777
+ * })
778
+ * .catch((error) => {
779
+ * // handle query errors
780
+ * })
781
+ *
782
+ * @returns {Stack} Returns an instance of 'stack'
783
+ */
784
+ entries() {
785
+ if (this.q.content_type_uid && typeof this.q.content_type_uid === 'string') {
786
+ return this;
787
+ }
788
+ throw new Error('Kindly call \'contentType()\' before \'entries()\'!');
789
+ }
790
+ /**
791
+ * @public
792
+ * @method asset
793
+ * @description
794
+ * Query for a single asset
795
+ *
796
+ * @param {string} uid Asset uid to be found, if not provided,
797
+ * by default returns the 1st element from assets.
798
+ * @example
799
+ * Stack
800
+ * .asset()
801
+ * .find()
802
+ * .then((result) => {
803
+ * // returns the asset based on its 'uid', if not provided, it would return the 1st asset found
804
+ * })
805
+ * .catch((error) => {
806
+ * // handle query errors
807
+ * })
808
+ *
809
+ * @returns {Stack} Returns an instance of 'stack'
810
+ */
811
+ asset(uid) {
812
+ const stack = new Stack(this.config, this.db);
813
+ if (uid && typeof uid === 'string') {
814
+ stack.q.query = stack.q.query || {};
815
+ stack.q.query.uid = uid;
816
+ }
817
+ stack.q.content_type_uid = this.types.assets;
818
+ // stack.collection = stack.db.collection(stack.contentStore.collectionName)
819
+ stack.internal.limit = 1;
820
+ stack.internal.single = true;
821
+ return stack;
822
+ }
823
+ /**
824
+ * @public
825
+ * @method assets
826
+ * @description
827
+ * Query for a set of assets
828
+ *
829
+ * @example
830
+ * Stack
831
+ * .assets()
832
+ * .find()
833
+ * .then((result) => {
834
+ * // returns assets filtered based on 'blog' content type
835
+ * })
836
+ * .catch((error) => {
837
+ * // handle query errors
838
+ * })
839
+ *
840
+ * @returns {Stack} Returns an instance of 'stack'
841
+ */
842
+ assets() {
843
+ const stack = new Stack(this.config, this.db);
844
+ stack.q.content_type_uid = this.types.assets;
845
+ // stack.collection = stack.db.collection(stack.contentStore.collectionName)
846
+ return stack;
847
+ }
848
+ /**
849
+ * @public
850
+ * @method schema
851
+ * @description
852
+ * Query for a single content type's schema
853
+ *
854
+ * @param {string} uid Content type uid to be found, if not provided,
855
+ * by default returns the 1st element from content types
856
+ *
857
+ * @example
858
+ * Stack
859
+ * .schema('blog')
860
+ * .find()
861
+ * .then((result) => {
862
+ * // returns content 'blog' content type's schema
863
+ * })
864
+ * .catch((error) => {
865
+ * // handle query errors
866
+ * })
867
+ *
868
+ * @returns {Stack} Returns an instance of 'stack'
869
+ */
870
+ schema(uid) {
871
+ const stack = new Stack(this.config, this.db);
872
+ if (uid && typeof uid === 'string') {
873
+ stack.q.query = stack.q.query || {};
874
+ stack.q.query.uid = uid;
875
+ }
876
+ stack.q.content_type_uid = this.types.content_types;
877
+ // stack.collection = stack.db.collection(stack.contentStore.collectionName)
878
+ stack.internal.limit = 1;
879
+ stack.internal.single = true;
880
+ return stack;
881
+ }
882
+ /**
883
+ * @public
884
+ * @method schemas
885
+ * @description
886
+ * Query for a set of content type schemas
887
+ * @example
888
+ * Stack
889
+ * .schemas()
890
+ * .find()
891
+ * .then((result) => {
892
+ * // returns a set of content type schemas
893
+ * })
894
+ * .catch((error) => {
895
+ * // handle query errors
896
+ * })
897
+ *
898
+ * @returns {Stack} Returns an instance of 'stack'
899
+ */
900
+ schemas() {
901
+ const stack = new Stack(this.config, this.db);
902
+ stack.q.content_type_uid = this.types.content_types;
903
+ // stack.collection = stack.db.collection(stack.contentStore.collectionName)
904
+ return stack;
905
+ }
906
+ /**
907
+ * @public
908
+ * @method contentTypes
909
+ * @description
910
+ * Query for a set of content type schemas
911
+ * @example
912
+ * Stack
913
+ * .contentTypes()
914
+ * .find()
915
+ * .then((result) => {
916
+ * // returns a set of content type schemas
917
+ * })
918
+ * .catch((error) => {
919
+ * // handle query errors
920
+ * })
921
+ *
922
+ * @returns {Stack} Returns an instance of 'stack'
923
+ */
924
+ contentTypes() {
925
+ const stack = new Stack(this.config, this.db);
926
+ stack.q.content_type_uid = this.types.content_types;
927
+ // stack.collection = stack.db.collection(stack.contentStore.collectionName)
928
+ return stack;
929
+ }
930
+ /**
931
+ * @public
932
+ * @method limit
933
+ * @description
934
+ * Parameter - used to limit the total no of items returned/scanned
935
+ * Defaults to 100 (internally, which is overridden)
936
+ * @param {number} no Max count of the 'items' returned
937
+ *
938
+ * @example
939
+ * Stack
940
+ * .contentType('blog')
941
+ * .entries()
942
+ * .limit(20)
943
+ * .find()
944
+ * .then((result) => {
945
+ * // returns a maximum of 20 entries
946
+ * // if not provided, by default - the limit specified in config is returned
947
+ * })
948
+ * .catch((error) => {
949
+ * // handle query errors
950
+ * })
951
+ *
952
+ * @returns {Stack} Returns an instance of 'stack'
953
+ */
954
+ limit(no) {
955
+ if (typeof no === 'number' && (no >= 0) && typeof this.q.content_type_uid === 'string') {
956
+ this.internal.limit = no;
957
+ return this;
958
+ }
959
+ throw new Error('Kindly provide a valid \'numeric\' value for \'limit()\'');
960
+ }
961
+ /**
962
+ * @public
963
+ * @method skip
964
+ * @description
965
+ * Parameter - used to skip initial no of items scanned
966
+ * Defaults to 0 (internally, which is overridden)
967
+ * @param {number} no Min count of the 'items' to be scanned
968
+ *
969
+ * @example
970
+ * Stack
971
+ * .contentType('blog')
972
+ * .entries()
973
+ * .skip(10)
974
+ * .find()
975
+ * .then((result) => {
976
+ * // returnes entries, after first skipping 20 entries of 'blog' content type
977
+ * // if not provided, by default - the skip value provided in config is considered
978
+ * })
979
+ * .catch((error) => {
980
+ * // handle query errors
981
+ * })
982
+ *
983
+ * @returns {Stack} Returns an instance of 'stack'
984
+ */
985
+ skip(no) {
986
+ if (typeof no === 'number' && (no >= 0) && typeof this.q.content_type_uid === 'string') {
987
+ this.internal.skip = no;
988
+ return this;
989
+ }
990
+ throw new Error('Kindly provide a valid \'numeric\' value for \'skip()\'');
991
+ }
992
+ /**
993
+ * @public
994
+ * @method query
995
+ * @description
996
+ * Wrapper around a raw query wrapper
997
+ * @param {object} queryObject Query filter
998
+ *
999
+ * @example
1000
+ * Stack
1001
+ * .contentType('blog')
1002
+ * .entries()
1003
+ * .query({"group.heading": "Tab 1"})
1004
+ * .find()
1005
+ * .then((result) => {
1006
+ * // returns entries that have - {"group.heading": "Tab 1"}
1007
+ * })
1008
+ * .catch((error) => {
1009
+ * // handle query errors
1010
+ * })
1011
+ *
1012
+ * @returns {Stack} Returns an instance of 'stack'
1013
+ */
1014
+ query(queryObject = {}) {
1015
+ if (this.q.query && typeof this.q.query === 'object') {
1016
+ this.q.query = (0, lodash_1.merge)(this.q.query, queryObject);
1017
+ }
1018
+ else {
1019
+ this.q.query = queryObject;
1020
+ }
1021
+ return this;
1022
+ }
1023
+ /**
1024
+ * @public
1025
+ * @method only
1026
+ * @description
1027
+ * Projections - returns only the fields passed here
1028
+ *
1029
+ * @param {array} fields Array of 'fields', separated by dot ('.') notation for embedded document query
1030
+ *
1031
+ * @example
1032
+ * Stack
1033
+ * .contentType('blog')
1034
+ * .entries()
1035
+ * .only(["title", "url", "links"])
1036
+ * .find()
1037
+ * .then((result) => {
1038
+ * // returns entries and projects only their - ["title", "url", "links"] properties
1039
+ * })
1040
+ * .catch((error) => {
1041
+ * // handle query errors
1042
+ * })
1043
+ *
1044
+ * @returns {Stack} Returns an instance of 'stack'
1045
+ */
1046
+ only(fields) {
1047
+ if (!fields || typeof fields !== 'object' || !(fields instanceof Array) || fields.length === 0) {
1048
+ throw new Error('Kindly provide valid \'field\' values for \'only()\'');
1049
+ }
1050
+ this.internal.only = this.internal.only || {};
1051
+ this.internal.only._id = 0;
1052
+ fields.forEach((field) => {
1053
+ if (typeof field === 'string') {
1054
+ this.internal.only[field] = 1;
1055
+ }
1056
+ });
1057
+ return this;
1058
+ }
1059
+ /**
1060
+ * @public
1061
+ * @method except
1062
+ * @description
1063
+ * Projections - returns fields except the ones passed here
1064
+ *
1065
+ * @param {array} fields Array of 'fields', separated by dot ('.') notation for embedded document query
1066
+ * @example
1067
+ * Stack
1068
+ * .contentType('blog')
1069
+ * .entries()
1070
+ * .except(["title", "url", "links"])
1071
+ * .find()
1072
+ * .then((result) => {
1073
+ * // returns entries and projects all of their properties, except - ["title", "url", "links"]
1074
+ * })
1075
+ * .catch((error) => {
1076
+ * // handle query errors
1077
+ * })
1078
+ *
1079
+ * @returns {Stack} Returns an instance of 'stack'
1080
+ */
1081
+ except(fields) {
1082
+ if (!fields || typeof fields !== 'object' || !(fields instanceof Array) || fields.length === 0) {
1083
+ throw new Error('Kindly provide valid \'field\' values for \'except()\'');
1084
+ }
1085
+ this.internal.except = this.internal.except || {};
1086
+ fields.forEach((field) => {
1087
+ if (typeof field === 'string') {
1088
+ this.internal.except[field] = 0;
1089
+ }
1090
+ });
1091
+ this.internal.except = (0, lodash_1.merge)(this.contentStore.projections, this.internal.except);
1092
+ return this;
1093
+ }
1094
+ /**
1095
+ * @public
1096
+ * @method regex
1097
+ * @description
1098
+ * Raw regex to be applied on a field - wrapper
1099
+ *
1100
+ * @param {string} field Field on which the regex is to be applied on
1101
+ * @param {pattern} pattern Regex pattern
1102
+ * @param {options} options Options to be applied while evaluating the regex
1103
+ * @example
1104
+ * Stack
1105
+ * .contentType('blog')
1106
+ * .entries()
1107
+ * .regex("name", "^J")
1108
+ * .find()
1109
+ * .then((result) => {
1110
+ * // returns entries who's name properties start with "J"
1111
+ * })
1112
+ * .catch((error) => {
1113
+ * // handle query errors
1114
+ * })
1115
+ *
1116
+ * @returns {Stack} Returns an instance of 'stack'
1117
+ */
1118
+ regex(field, pattern, options = 'i') {
1119
+ if (!(field) || !(pattern) || typeof field !== 'string' || typeof pattern !== 'string') {
1120
+ throw new Error('Kindly provide a valid field and pattern value for \'.regex()\'');
1121
+ }
1122
+ else if (this.q.query && typeof this.q.query === 'object') {
1123
+ this.q.query = (0, lodash_1.merge)(this.q.query, {
1124
+ [field]: {
1125
+ $options: options,
1126
+ $regex: pattern,
1127
+ },
1128
+ });
1129
+ }
1130
+ else {
1131
+ this.q.query = {
1132
+ [field]: {
1133
+ $options: options,
1134
+ $regex: pattern,
1135
+ },
1136
+ };
1137
+ }
1138
+ return this;
1139
+ }
1140
+ /**
1141
+ * @public
1142
+ * @method tags
1143
+ * @summary Match entries that match a specific tags
1144
+ *
1145
+ * @param {array} values Array of tag values
1146
+ * @example
1147
+ * Stack
1148
+ * .contentType('blog')
1149
+ * .entries()
1150
+ * .tags(["new", "fresh"])
1151
+ * .find()
1152
+ * .then((result) => {
1153
+ * // returns entries filtered based on their tag fields
1154
+ * })
1155
+ * .catch((error) => {
1156
+ * // handle query errors
1157
+ * })
1158
+ *
1159
+ * @returns {Stack} Returns an instance of 'stack'
1160
+ */
1161
+ tags(values) {
1162
+ if (!values || typeof values !== 'object' || !(values instanceof Array)) {
1163
+ throw new Error('Kindly provide valid \'field\' values for \'tags()\'');
1164
+ }
1165
+ // filter non-string keys
1166
+ (0, lodash_1.remove)(values, (value) => {
1167
+ return typeof value !== 'string';
1168
+ });
1169
+ this.q.query = this.q.query || {};
1170
+ if (values.length === 0) {
1171
+ this.q.query.tags = {
1172
+ $size: 0,
1173
+ };
1174
+ }
1175
+ else {
1176
+ this.q.query.tags = {
1177
+ $in: values,
1178
+ };
1179
+ }
1180
+ return this;
1181
+ }
1182
+ /**
1183
+ * @public
1184
+ * @method where
1185
+ * @summary Pass JS expression or a full function to the query system
1186
+ * @description
1187
+ * Use the $where operator to pass either a string containing a JavaScript expression or a full JavaScript
1188
+ * function to the query system.
1189
+ * The $where provides greater flexibility, but requires that the database processes the JavaScript expression or
1190
+ * function for each document in the collection.
1191
+ * Reference the document in the JavaScript expression or function using either this or obj.
1192
+ * Only apply the $where query operator to top-level documents.
1193
+ * The $where query operator will not work inside a nested document, for instance, in an $elemMatch query.
1194
+ * Ref. - https://docs.mongodb.com/manual/reference/operator/query/where/index.html
1195
+ * @param { * } expr Pass either a string containing a JavaScript expression or a full JavaScript
1196
+ * function to the query system.
1197
+ * @example
1198
+ * Stack
1199
+ * .contentType('blog')
1200
+ * .entries()
1201
+ * .where(function() {
1202
+ * return (hex_md5(this.name) === "9b53e667f30cd329dca1ec9e6a83e994")
1203
+ * })
1204
+ * .find()
1205
+ * .then((result) => {
1206
+ * // returns entries filtered based on the $where condition provided
1207
+ * })
1208
+ * .catch((error) => {
1209
+ * // handle query errors
1210
+ * })
1211
+ *
1212
+ * @returns {Stack} Returns an instance of 'stack'
1213
+ */
1214
+ where(expr) {
1215
+ if (!(expr)) {
1216
+ throw new Error('Kindly provide a valid field and expr/fn value for \'.where()\'');
1217
+ }
1218
+ else if (this.q.query && typeof this.q.query === 'object') {
1219
+ if (typeof expr === 'function') {
1220
+ expr = expr.toString();
1221
+ }
1222
+ this.q.query = (0, lodash_1.merge)(this.q.query, {
1223
+ $where: expr,
1224
+ });
1225
+ }
1226
+ else {
1227
+ if (typeof expr === 'function') {
1228
+ expr = expr.toString();
1229
+ }
1230
+ this.q.query = {
1231
+ $where: expr,
1232
+ };
1233
+ }
1234
+ return this;
1235
+ }
1236
+ /**
1237
+ * @public
1238
+ * @method includeCount
1239
+ * @description
1240
+ * Includes 'count' key in response, which is the total count of the items being returned
1241
+ *
1242
+ * @example
1243
+ * Stack
1244
+ * .contentType('blog')
1245
+ * .entries()
1246
+ * .includeCount()
1247
+ * .find()
1248
+ * .then((result) => {
1249
+ * // returns entries, along with a 'count' property, with the total count of entries being returned
1250
+ * })
1251
+ * .catch((error) => {
1252
+ * // handle query errors
1253
+ * })
1254
+ *
1255
+ * @returns {Stack} Returns an instance of 'stack'
1256
+ */
1257
+ includeCount() {
1258
+ this.internal.includeCount = true;
1259
+ return this;
1260
+ }
1261
+ /**
1262
+ * @description
1263
+ * Includes 'content_type' key in response, which is the content type schema of the entries filtered/scanned
1264
+ * @example
1265
+ * Stack
1266
+ * .contentType('blog')
1267
+ * .entries()
1268
+ * .includeSchema()
1269
+ * .find()
1270
+ * .then((result) => {
1271
+ * // returns entries, along with a 'content_type' property, which is 'blog' content type's schema
1272
+ * })
1273
+ * .catch((error) => {
1274
+ * // handle query errors
1275
+ * })
1276
+ *
1277
+ * @returns {Stack} Returns an instance of 'stack'
1278
+ */
1279
+ includeSchema() {
1280
+ this.internal.includeSchema = true;
1281
+ return this;
1282
+ }
1283
+ /**
1284
+ * @public
1285
+ * @method includeContentType
1286
+ * @description
1287
+ * Includes 'content_type' key in response, which is the content type schema of the entries filtered/scanned
1288
+ * @example
1289
+ * Stack
1290
+ * .contentType('blog')
1291
+ * .entries()
1292
+ * .includeContentType()
1293
+ * .find()
1294
+ * .then((result) => {
1295
+ * // returns entries, along with a 'content_type' property, which is 'blog' content type's schema
1296
+ * })
1297
+ * .catch((error) => {
1298
+ * // handle query errors
1299
+ * })
1300
+ *
1301
+ * @returns {Stack} Returns an instance of 'stack'
1302
+ */
1303
+ includeContentType() {
1304
+ this.internal.includeSchema = true;
1305
+ return this;
1306
+ }
1307
+ /**
1308
+ * @public
1309
+ * @method excludeReferences
1310
+ * @description
1311
+ * Excludes all references of the entries being scanned.
1312
+ * Note: On calling this, assets will not be binded in the result being returned.
1313
+ *
1314
+ * @example
1315
+ * Stack
1316
+ * .contentType('blog')
1317
+ * .entries()
1318
+ * .excludeReferences()
1319
+ * .find()
1320
+ * .then((result) => {
1321
+ * // returns entries, without any of its assets Or references
1322
+ * })
1323
+ * .catch((error) => {
1324
+ * // handle query errors
1325
+ * })
1326
+ *
1327
+ * @returns {Stack} Returns an instance of 'stack'
1328
+ */
1329
+ excludeReferences() {
1330
+ this.internal.excludeReferences = true;
1331
+ return this;
1332
+ }
1333
+ /**
1334
+ * @public
1335
+ * @method queryReferences
1336
+ * @description
1337
+ * Wrapper, that allows querying on the entry's references.
1338
+ * Note: This is a slow method, since it scans all documents and fires the `reference`
1339
+ * query on them.Once the references are binded, the query object passed is used
1340
+ * for filtering
1341
+ * Use `.query()` filters to reduce the total no of documents being scanned
1342
+ *
1343
+ * @example
1344
+ * Stack
1345
+ * .contentType('blog')
1346
+ * .entries()
1347
+ * .queryReferences({"authors.name": "John Doe"})
1348
+ * .find()
1349
+ * .then((result) => {
1350
+ * // returns entries, who's reference author's name equals "John Doe"
1351
+ * })
1352
+ * .catch((error) => {
1353
+ * // handle query errors
1354
+ * })
1355
+ *
1356
+ * @returns {Stack} Returns an instance of 'stack'
1357
+ */
1358
+ queryReferences(query) {
1359
+ if (query && typeof query === 'object') {
1360
+ this.internal.queryReferences = query;
1361
+ return this;
1362
+ }
1363
+ throw new Error('Kindly pass a query object for \'.queryReferences()\'');
1364
+ }
1365
+ /**
1366
+ * @public
1367
+ * @method getQuery
1368
+ * @description
1369
+ * Returns the query build thusfar
1370
+ * @example
1371
+ * const query = Stack
1372
+ * .contentType('blog')
1373
+ * .entries()
1374
+ * .getQuery()
1375
+ * // exposes details of the queries formed inside the SDK
1376
+ *
1377
+ * @returns {Stack} Returns an instance of 'stack'
1378
+ */
1379
+ getQuery() {
1380
+ return Object.assign({}, this.q);
1381
+ }
1382
+ /**
1383
+ * @public
1384
+ * @method includeReferences
1385
+ * @description
1386
+ * This method would return all the references of your queried entries (until depth 2)
1387
+ * Note: If you wish to increase the depth of the references fetched, call pass a numeric parameter
1388
+ * @example
1389
+ * Stack
1390
+ * .contentType('blog')
1391
+ * .entries()
1392
+ * .includeReferences(3)
1393
+ * @returns {Stack} Returns 'this' instance (of Stack)
1394
+ */
1395
+ includeReferences(depth) {
1396
+ console.warn('.includeReferences() is a relatively slow query..!');
1397
+ if (typeof depth === 'number') {
1398
+ this.q.referenceDepth = depth;
1399
+ }
1400
+ this.internal.includeAllReferences = true;
1401
+ return this;
1402
+ }
1403
+ /**
1404
+ * @public
1405
+ * @method include
1406
+ * @description
1407
+ * Pass in reference field uids, that you want included in your result.
1408
+ * If you want all the references, use .includeReferences()
1409
+ * @example
1410
+ * Stack.contentType('blog')
1411
+ * .entries()
1412
+ * .include(['related_blogs', 'authors.blogs']) // here related_blogs and authors.blogs are reference field uids
1413
+ * @param {object} fields An array of reference field uids
1414
+ * @returns {Stack} Returns 'this' instance (of Stack)
1415
+ */
1416
+ include(fields) {
1417
+ if (fields.length === 0) {
1418
+ throw new Error('Kindly pass a valid reference field path to \'.include()\' ');
1419
+ }
1420
+ else if (typeof fields === 'string') {
1421
+ this.internal.includeSpecificReferences = [fields];
1422
+ }
1423
+ else {
1424
+ this.internal.includeSpecificReferences = fields;
1425
+ }
1426
+ return this;
1427
+ }
1428
+ /**
1429
+ * @public
1430
+ * @method find
1431
+ * @description
1432
+ * Queries the db using the query built/passed
1433
+ * Does all the processing, filtering, referencing after querying the DB
1434
+ * @param {object} query Optional query object, that overrides all the
1435
+ * previously build queries
1436
+ * @public
1437
+ * @example
1438
+ * Stack
1439
+ * .contentType('blog')
1440
+ * .entries()
1441
+ * .find()
1442
+ * .then((result) => {
1443
+ * // returns blog content type's entries
1444
+ * })
1445
+ * .catch((error) => {
1446
+ * // handle query errors
1447
+ * })
1448
+ *
1449
+ * @returns {object} - Returns a objects, that have been processed, filtered and referenced
1450
+ */
1451
+ find(query = {}) {
1452
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
1453
+ const queryFilters = this.preProcess(query);
1454
+ if (this.internal.sort) {
1455
+ this.collection = this.collection
1456
+ .find(queryFilters)
1457
+ .sort(this.internal.sort);
1458
+ }
1459
+ else {
1460
+ this.collection = this.collection
1461
+ .find(queryFilters);
1462
+ }
1463
+ if (this.internal.queryReferences) {
1464
+ this.collection = this.collection
1465
+ .project(this.internal.projections)
1466
+ .toArray();
1467
+ }
1468
+ else {
1469
+ this.collection = this.collection
1470
+ .project(this.internal.projections)
1471
+ .limit(this.internal.limit)
1472
+ .skip(this.internal.skip)
1473
+ .toArray();
1474
+ }
1475
+ return this.collection
1476
+ .then((result) => __awaiter(this, void 0, void 0, function* () {
1477
+ // Ignore references include, for empty list, exclude call, content type & assets
1478
+ if (result.length === 0 || this.internal.excludeReferences || this.q.content_type_uid === this
1479
+ .types.content_types || this.q.content_type_uid
1480
+ === this.types.assets || (this.internal.onlyCount && !this.internal.queryReferences)) {
1481
+ // Do nothing
1482
+ }
1483
+ else if (this.internal.includeSpecificReferences) {
1484
+ yield this.includeSpecificReferences(result, this.q.content_type_uid, this.q.locale, this
1485
+ .internal.includeSpecificReferences);
1486
+ }
1487
+ else if (this.internal.includeAllReferences) {
1488
+ yield this.bindReferences(result, this.q.content_type_uid, this.q.locale);
1489
+ }
1490
+ else {
1491
+ yield this.includeAssetsOnly(result, this.q.content_type_uid, this.q.locale);
1492
+ }
1493
+ if (this.internal.queryReferences) {
1494
+ result = result.filter((0, sift_1.default)(this.internal.queryReferences));
1495
+ if (this.internal.skip) {
1496
+ result = result.splice(this.internal.skip, this.internal.limit);
1497
+ }
1498
+ else if (this.internal.limit) {
1499
+ result = result.splice(0, this.internal.limit);
1500
+ }
1501
+ }
1502
+ result = yield this.postProcess(result);
1503
+ return resolve(result);
1504
+ }))
1505
+ .catch((error) => {
1506
+ this.cleanup();
1507
+ return reject(error);
1508
+ });
1509
+ }));
1510
+ }
1511
+ /**
1512
+ * @public
1513
+ * @method count
1514
+ * @descriptionReturns the count of the entries/assets that match the filter
1515
+ * @param {object} query Optional query filter object
1516
+ * @public
1517
+ * @example
1518
+ * Stack
1519
+ * .contentType('blog')
1520
+ * .entries()
1521
+ * .count()
1522
+ * .then((result) => {
1523
+ * // returns entries, without any of its assets Or references
1524
+ * })
1525
+ * .catch((error) => {
1526
+ * // handle query errors
1527
+ * })
1528
+ *
1529
+ * @returns {object} Returns count of the entries/asset's matched
1530
+ */
1531
+ count(query) {
1532
+ return __awaiter(this, void 0, void 0, function* () {
1533
+ this.internal.onlyCount = true;
1534
+ return this.find(query);
1535
+ });
1536
+ }
1537
+ /**
1538
+ * @public
1539
+ * @method findOne
1540
+ * @deprecated - Use .fetch() instead
1541
+ * @description
1542
+ * Queries the db using the query built/passed. Returns a single entry/asset/content type object
1543
+ * Does all the processing, filtering, referencing after querying the DB
1544
+ * @param {object} query Optional query object, that overrides all the previously build queries
1545
+ *
1546
+ * @example
1547
+ * Stack
1548
+ * .contentType('blog')
1549
+ * .entries()
1550
+ * .findOne()
1551
+ *
1552
+ * @returns {object} - Returns an object, that has been processed, filtered and referenced
1553
+ */
1554
+ findOne(query = {}) {
1555
+ this.internal.single = true;
1556
+ return this.find(query);
1557
+ }
1558
+ /**
1559
+ * @public
1560
+ * @method fetch
1561
+ * @description
1562
+ * Queries the db using the query built/passed. Returns a single entry/asset/content type object
1563
+ * Does all the processing, filtering, referencing after querying the DB
1564
+ * @param {object} query Optional query object, that overrides all the previously build queries
1565
+ *
1566
+ * @example
1567
+ * Stack
1568
+ * .contentType('blog')
1569
+ * .entries()
1570
+ * .fetch()
1571
+ *
1572
+ * @returns {object} - Returns an object, that has been processed, filtered and referenced
1573
+ */
1574
+ fetch(query = {}) {
1575
+ this.internal.single = true;
1576
+ return this.find(query);
1577
+ }
1578
+ /**
1579
+ * @private
1580
+ * @method preProcess
1581
+ * @summary Internal method, that executes and formats the queries built/passed
1582
+ * @param {object} query Query filter/process object
1583
+ * @returns {object} Returns a query object, that has been processed to be queried in mongodb
1584
+ */
1585
+ preProcess(query) {
1586
+ let queryFilters;
1587
+ if (this.q.query && typeof this.q.query === 'object') {
1588
+ this.q.query = (0, lodash_1.merge)(this.q.query, query);
1589
+ }
1590
+ else {
1591
+ this.q.query = {};
1592
+ }
1593
+ // tslint:disable-next-line: max-line-length
1594
+ this.q.referenceDepth = (typeof this.q.referenceDepth === 'number') ? this.q.referenceDepth : this.contentStore.referenceDepth;
1595
+ if (this.internal.only) {
1596
+ this.internal.projections = this.internal.only;
1597
+ }
1598
+ else {
1599
+ this.internal.projections = (0, lodash_1.merge)(this.contentStore.projections, this.internal.except);
1600
+ }
1601
+ // set default limit, if .limit() hasn't been called
1602
+ if (!(this.internal.limit)) {
1603
+ this.internal.limit = this.contentStore.limit;
1604
+ }
1605
+ // set default skip, if .skip() hasn't been called
1606
+ if (!(this.internal.skip)) {
1607
+ this.internal.skip = this.contentStore.skip;
1608
+ }
1609
+ // set default locale, if no locale has been passed
1610
+ if (!(this.q.locale)) {
1611
+ this.q.locale = this.contentStore.locale;
1612
+ }
1613
+ // by default, sort by latest content
1614
+ if (!this.internal.sort) {
1615
+ this.internal.sort = {
1616
+ updated_at: -1,
1617
+ };
1618
+ }
1619
+ const filters = Object.assign({ _content_type_uid: this.q.content_type_uid, locale: this.q.locale }, this.q.query);
1620
+ if (this.q.content_type_uid === this.types.assets) {
1621
+ // allow querying only on published assets..!
1622
+ queryFilters = {
1623
+ $and: [
1624
+ filters,
1625
+ {
1626
+ _version: {
1627
+ $exists: true,
1628
+ },
1629
+ },
1630
+ ],
1631
+ };
1632
+ }
1633
+ else {
1634
+ queryFilters = filters;
1635
+ }
1636
+ this.collection = this.db.collection((0, util_1.getCollectionName)({
1637
+ content_type_uid: this.q.content_type_uid,
1638
+ locale: this.q.locale,
1639
+ }, this.collectionNames));
1640
+ return queryFilters;
1641
+ }
1642
+ /**
1643
+ * @private
1644
+ * @method cleanup
1645
+ * @summary Does GC, so memory doesn't stackup
1646
+ */
1647
+ cleanup() {
1648
+ this.collection = null;
1649
+ this.internal = null;
1650
+ this.q = null;
1651
+ }
1652
+ /**
1653
+ * @private
1654
+ * @method postProcess
1655
+ * @summary Internal method, that executes and formats the result, which the user and use
1656
+ * @param {object} result Result, which's to be manipulated
1657
+ * @returns {object} Returns the formatted version of the `result` object
1658
+ */
1659
+ postProcess(result) {
1660
+ return __awaiter(this, void 0, void 0, function* () {
1661
+ const count = (result === null) ? 0 : result.length;
1662
+ const output = {
1663
+ locale: this.q.locale,
1664
+ };
1665
+ if (this.internal.onlyCount) {
1666
+ output.content_type_uid = (this.q.content_type_uid === this.types.assets) ? 'assets' : ((this.q.content_type_uid
1667
+ === this.types.content_types) ? 'content_types' : this.q.content_type_uid);
1668
+ output.count = count;
1669
+ return output;
1670
+ }
1671
+ switch (this.q.content_type_uid) {
1672
+ case this.types.assets:
1673
+ if (this.internal.single) {
1674
+ output.asset = (result === null) ? result : result[0];
1675
+ }
1676
+ else {
1677
+ output.assets = result;
1678
+ }
1679
+ output.content_type_uid = 'assets';
1680
+ break;
1681
+ case this.types.content_types:
1682
+ if (this.internal.single) {
1683
+ output.content_type = (result === null) ? result : result[0];
1684
+ }
1685
+ else {
1686
+ output.content_types = result;
1687
+ }
1688
+ output.content_type_uid = 'content_types';
1689
+ break;
1690
+ default:
1691
+ if (this.internal.single) {
1692
+ output.entry = (result === null) ? result : result[0];
1693
+ }
1694
+ else {
1695
+ output.entries = result;
1696
+ }
1697
+ output.content_type_uid = this.q.content_type_uid;
1698
+ break;
1699
+ }
1700
+ if (this.internal.includeCount) {
1701
+ output.count = yield this.db.collection((0, util_1.getCollectionName)({
1702
+ content_type_uid: this.q.content_type_uid,
1703
+ locale: this.q.locale,
1704
+ }, this.collectionNames))
1705
+ .count({
1706
+ _content_type_uid: this.q.content_type_uid,
1707
+ });
1708
+ }
1709
+ if (this.internal.includeSchema) {
1710
+ output.content_type = yield this.db.collection((0, util_1.getCollectionName)({
1711
+ content_type_uid: this.types.content_types,
1712
+ locale: this.q.locale,
1713
+ }, this.collectionNames))
1714
+ .findOne({
1715
+ uid: this.q.content_type_uid,
1716
+ }, {
1717
+ projection: {
1718
+ _assets: 0,
1719
+ _content_type_uid: 0,
1720
+ _id: 0,
1721
+ _references: 0,
1722
+ }
1723
+ });
1724
+ }
1725
+ this.cleanup();
1726
+ return output;
1727
+ });
1728
+ }
1729
+ includeAssetsOnly(entries, contentTypeUid, locale) {
1730
+ return __awaiter(this, void 0, void 0, function* () {
1731
+ const schema = yield this.db
1732
+ .collection((0, util_1.getCollectionName)({
1733
+ content_type_uid: this.types.content_types,
1734
+ locale,
1735
+ }, this.collectionNames))
1736
+ .findOne({
1737
+ _content_type_uid: this.types.content_types,
1738
+ uid: contentTypeUid,
1739
+ }, {
1740
+ projection: {
1741
+ _assets: 1,
1742
+ _id: 0,
1743
+ }
1744
+ });
1745
+ if (schema === null || schema[this.types.assets] !== 'object') {
1746
+ return;
1747
+ }
1748
+ const paths = Object.keys(schema[this.types.assets]);
1749
+ const shelf = [];
1750
+ const queryBucket = {
1751
+ $or: [],
1752
+ };
1753
+ for (let i = 0, j = paths.length; i < j; i++) {
1754
+ this.fetchPathDetails(entries, locale, paths[i].split('.'), queryBucket, shelf, true, entries, 0);
1755
+ }
1756
+ if (shelf.length === 0) {
1757
+ return;
1758
+ }
1759
+ const assets = yield this.db.collection((0, util_1.getCollectionName)({
1760
+ content_type_uid: this.types.assets,
1761
+ locale,
1762
+ }, this.collectionNames))
1763
+ .find(queryBucket)
1764
+ .project({
1765
+ _content_type_uid: 0,
1766
+ _id: 0,
1767
+ })
1768
+ .toArray();
1769
+ for (let l = 0, m = shelf.length; l < m; l++) {
1770
+ for (let n = 0, o = assets.length; n < o; n++) {
1771
+ if (shelf[l].uid === assets[n].uid) {
1772
+ shelf[l].path[shelf[l].position] = assets[n];
1773
+ break;
1774
+ }
1775
+ }
1776
+ }
1777
+ return;
1778
+ });
1779
+ }
1780
+ /**
1781
+ * @summary
1782
+ * Internal method, that iteratively calls itself and binds entries reference
1783
+ * @param {Object} entry - An entry or a collection of entries, who's references are to be found
1784
+ * @param {String} contentTypeUid - Content type uid
1785
+ * @param {String} locale - Locale, in which the reference is to be found
1786
+ * @param {Object} include - Array of field paths, to be included
1787
+ * @returns {Object} - Returns `entries`, that has all of its reference binded
1788
+ */
1789
+ includeSpecificReferences(entries, contentTypeUid, locale, include) {
1790
+ return __awaiter(this, void 0, void 0, function* () {
1791
+ const ctQuery = {
1792
+ _content_type_uid: this.types.content_types,
1793
+ uid: contentTypeUid,
1794
+ };
1795
+ const { paths, // ref. fields in the current content types
1796
+ pendingPath, // left over of *paths*
1797
+ schemaList, // list of content type uids, the current content types refer to
1798
+ } = yield this.getReferencePath(ctQuery, locale, include);
1799
+ const queries = {
1800
+ $or: [],
1801
+ }; // reference field paths
1802
+ const shelf = []; // a mapper object, that holds pointer to the original element
1803
+ // iterate over each path in the entries and fetch the references
1804
+ // while fetching, keep track of their location
1805
+ for (let i = 0, j = paths.length; i < j; i++) {
1806
+ this.fetchPathDetails(entries, locale, paths[i].split('.'), queries, shelf, true, entries, 0);
1807
+ }
1808
+ // even after traversing, if no references were found, simply return the entries found thusfar
1809
+ if (shelf.length === 0) {
1810
+ return entries;
1811
+ }
1812
+ // else, self-recursively iterate and fetch references
1813
+ // Note: Shelf is the one holding `pointers` to the actual entry
1814
+ // Once the pointer has been used, for GC, point the object to null
1815
+ return this.includeReferenceIteration(queries, schemaList, locale, pendingPath, shelf);
1816
+ });
1817
+ }
1818
+ fetchPathDetails(data, locale, pathArr, queryBucket, shelf, assetsOnly = false, parent, pos, counter = 0) {
1819
+ if (counter === (pathArr.length)) {
1820
+ if (data && typeof data === 'object') {
1821
+ if (data instanceof Array && data.length) {
1822
+ data.forEach((elem, idx) => {
1823
+ if (typeof elem === 'string') {
1824
+ queryBucket.$or.push({
1825
+ _content_type_uid: this.types.assets,
1826
+ _version: { $exists: true },
1827
+ locale,
1828
+ uid: elem,
1829
+ });
1830
+ shelf.push({
1831
+ path: data,
1832
+ position: idx,
1833
+ uid: elem,
1834
+ });
1835
+ }
1836
+ else if (elem && typeof elem === 'object' && elem.hasOwnProperty('_content_type_uid')) {
1837
+ queryBucket.$or.push({
1838
+ _content_type_uid: elem._content_type_uid,
1839
+ locale,
1840
+ uid: elem.uid,
1841
+ });
1842
+ shelf.push({
1843
+ path: data,
1844
+ position: idx,
1845
+ uid: elem.uid,
1846
+ });
1847
+ }
1848
+ });
1849
+ }
1850
+ else if (typeof data === 'object') {
1851
+ if (data.hasOwnProperty('_content_type_uid')) {
1852
+ queryBucket.$or.push({
1853
+ _content_type_uid: data._content_type_uid,
1854
+ locale,
1855
+ uid: data.uid,
1856
+ });
1857
+ shelf.push({
1858
+ path: parent,
1859
+ position: pos,
1860
+ uid: data.uid,
1861
+ });
1862
+ }
1863
+ }
1864
+ }
1865
+ else if (typeof data === 'string') {
1866
+ queryBucket.$or.push({
1867
+ _content_type_uid: this.types.assets,
1868
+ _version: { $exists: true },
1869
+ locale,
1870
+ uid: data,
1871
+ });
1872
+ shelf.push({
1873
+ path: parent,
1874
+ position: pos,
1875
+ uid: data,
1876
+ });
1877
+ }
1878
+ }
1879
+ else {
1880
+ const currentField = pathArr[counter];
1881
+ counter++;
1882
+ if (data instanceof Array) {
1883
+ // tslint:disable-next-line: prefer-for-of
1884
+ for (let i = 0; i < data.length; i++) {
1885
+ if (data[i][currentField]) {
1886
+ this.fetchPathDetails(data[i][currentField], locale, pathArr, queryBucket, shelf, assetsOnly, data[i], currentField, counter);
1887
+ }
1888
+ }
1889
+ }
1890
+ else {
1891
+ if (data[currentField]) {
1892
+ this.fetchPathDetails(data[currentField], locale, pathArr, queryBucket, shelf, assetsOnly, data, currentField, counter);
1893
+ }
1894
+ }
1895
+ }
1896
+ // since we've reached last of the paths, return!
1897
+ return;
1898
+ }
1899
+ bindLeftoverAssets(queries, locale, pointerList) {
1900
+ return __awaiter(this, void 0, void 0, function* () {
1901
+ // const contents = await readFile(getAssetsPath(locale) + '.json')
1902
+ if (!this.sanitizeIQuery(queries)) {
1903
+ throw new Error('Invalid queries provided');
1904
+ }
1905
+ const filteredAssets = yield this.db.collection((0, util_1.getCollectionName)({
1906
+ content_type_uid: this.types.assets,
1907
+ locale,
1908
+ }, this.collectionNames))
1909
+ .find(queries)
1910
+ .project({
1911
+ _content_type_uid: 0,
1912
+ _id: 0,
1913
+ })
1914
+ .toArray();
1915
+ for (let l = 0, m = pointerList.length; l < m; l++) {
1916
+ for (let n = 0, o = filteredAssets.length; n < o; n++) {
1917
+ if (pointerList[l].uid === filteredAssets[n].uid) {
1918
+ pointerList[l].path[pointerList[l].position] = filteredAssets[n];
1919
+ break;
1920
+ }
1921
+ }
1922
+ }
1923
+ return;
1924
+ });
1925
+ }
1926
+ includeReferenceIteration(eQuery, ctQuery, locale, include, oldShelf) {
1927
+ return __awaiter(this, void 0, void 0, function* () {
1928
+ if (oldShelf.length === 0) {
1929
+ return;
1930
+ }
1931
+ else if (ctQuery.$or.length === 0 && eQuery.$or.length > 0) {
1932
+ yield this.bindLeftoverAssets(eQuery, locale, oldShelf);
1933
+ return;
1934
+ }
1935
+ const { paths, pendingPath, schemaList, } = yield this.getReferencePath(ctQuery, locale, include);
1936
+ const { result, queries, shelf, } = yield this.fetchEntries(eQuery, locale, paths, include);
1937
+ // GC to avoid mem leaks!
1938
+ eQuery = null;
1939
+ for (let i = 0, j = oldShelf.length; i < j; i++) {
1940
+ const element = oldShelf[i];
1941
+ let flag = true;
1942
+ for (let k = 0, l = result.length; k < l; k++) {
1943
+ if (result[k].uid === element.uid) {
1944
+ element.path[element.position] = result[k];
1945
+ flag = false;
1946
+ break;
1947
+ }
1948
+ }
1949
+ if (flag) {
1950
+ for (let e = 0, f = oldShelf[i].path.length; e < f; e++) {
1951
+ // tslint:disable-next-line: max-line-length
1952
+ if (oldShelf[i].path[e].hasOwnProperty('_content_type_uid') && Object.keys(oldShelf[i].path[e]).length === 2) {
1953
+ oldShelf[i].path.splice(e, 1);
1954
+ break;
1955
+ }
1956
+ }
1957
+ }
1958
+ }
1959
+ // GC to avoid mem leaks!
1960
+ oldShelf = null;
1961
+ // Iterative loops, that traverses paths and binds them onto entries
1962
+ yield this.includeReferenceIteration(queries, schemaList, locale, pendingPath, shelf);
1963
+ return;
1964
+ });
1965
+ }
1966
+ getReferencePath(query, locale, currentInclude) {
1967
+ return __awaiter(this, void 0, void 0, function* () {
1968
+ if (!this.sanityQueryAny(query)) {
1969
+ throw new Error('Invalid query provided');
1970
+ }
1971
+ const schemas = yield this.db.collection((0, util_1.getCollectionName)({
1972
+ content_type_uid: this.types.content_types,
1973
+ locale,
1974
+ }, this.collectionNames))
1975
+ .find(query)
1976
+ .project({
1977
+ _assets: 1,
1978
+ _id: 0,
1979
+ _references: 1,
1980
+ })
1981
+ .toArray();
1982
+ const pendingPath = [];
1983
+ const schemasReferred = [];
1984
+ const paths = [];
1985
+ const schemaList = {
1986
+ $or: [],
1987
+ };
1988
+ if (schemas.length === 0) {
1989
+ return {
1990
+ paths,
1991
+ pendingPath,
1992
+ schemaList,
1993
+ };
1994
+ }
1995
+ let entryReferences = {};
1996
+ schemas.forEach((schema) => {
1997
+ // Entry references
1998
+ entryReferences = (0, lodash_1.mergeWith)(entryReferences, schema[this.types.references], (existingReferences, newReferences) => {
1999
+ if ((0, lodash_1.isArray)(existingReferences)) {
2000
+ return Array.from(new Set(existingReferences.concat(newReferences)));
2001
+ }
2002
+ return existingReferences;
2003
+ });
2004
+ for (const path in schema[this.types.assets]) {
2005
+ paths.push(path);
2006
+ }
2007
+ });
2008
+ for (let i = 0, j = currentInclude.length; i < j; i++) {
2009
+ const includePath = currentInclude[i];
2010
+ // tslint:disable-next-line: forin
2011
+ for (const path in entryReferences) {
2012
+ const subStr = includePath.slice(0, path.length);
2013
+ if (subStr === path) {
2014
+ let subPath;
2015
+ // Its the complete path!! Hurrah!
2016
+ if (path.length !== includePath.length) {
2017
+ subPath = subStr;
2018
+ pendingPath.push(includePath.slice(path.length + 1));
2019
+ }
2020
+ else {
2021
+ subPath = includePath;
2022
+ }
2023
+ if (typeof entryReferences[path] === 'string') {
2024
+ schemasReferred.push({
2025
+ _content_type_uid: this.types.content_types,
2026
+ uid: entryReferences[path],
2027
+ });
2028
+ }
2029
+ else if (entryReferences[path].length) {
2030
+ entryReferences[path].forEach((contentTypeUid) => {
2031
+ schemasReferred.push({
2032
+ _content_type_uid: this.types.content_types,
2033
+ uid: contentTypeUid,
2034
+ });
2035
+ });
2036
+ }
2037
+ paths.push(subPath);
2038
+ break;
2039
+ }
2040
+ }
2041
+ }
2042
+ schemaList.$or = schemasReferred;
2043
+ return {
2044
+ // path, that's possible in the current schema
2045
+ paths,
2046
+ // paths, that's yet to be traversed
2047
+ pendingPath,
2048
+ // schemas, to be loaded!
2049
+ schemaList,
2050
+ };
2051
+ });
2052
+ }
2053
+ fetchEntries(query, locale, paths, include, includeAll = false) {
2054
+ return __awaiter(this, void 0, void 0, function* () {
2055
+ if (!this.sanitizeIQuery(query)) {
2056
+ throw new Error('Invalid queries provided');
2057
+ }
2058
+ const result = yield this.db.collection((0, util_1.getCollectionName)({
2059
+ content_type_uid: 'entries',
2060
+ locale,
2061
+ }, this.collectionNames))
2062
+ .find(query)
2063
+ .project({
2064
+ _content_type_uid: 0,
2065
+ _id: 0,
2066
+ _synced_at: 0,
2067
+ event_at: 0,
2068
+ })
2069
+ .toArray();
2070
+ const queries = {
2071
+ $or: [],
2072
+ };
2073
+ const shelf = [];
2074
+ if (result.length === 0) {
2075
+ return {
2076
+ queries,
2077
+ result,
2078
+ shelf,
2079
+ };
2080
+ }
2081
+ if (include.length || includeAll) {
2082
+ paths.forEach((path) => {
2083
+ this.fetchPathDetails(result, locale, path.split('.'), queries, shelf, false, result, 0);
2084
+ });
2085
+ }
2086
+ else {
2087
+ // if there are no includes, only fetch assets)
2088
+ paths.forEach((path) => {
2089
+ this.fetchPathDetails(result, locale, path.split('.'), queries, shelf, true, result, 0);
2090
+ });
2091
+ }
2092
+ return {
2093
+ queries,
2094
+ result,
2095
+ shelf,
2096
+ };
2097
+ });
2098
+ }
2099
+ bindReferences(entries, contentTypeUid, locale) {
2100
+ return __awaiter(this, void 0, void 0, function* () {
2101
+ const ctQuery = {
2102
+ $or: [{
2103
+ _content_type_uid: this.types.content_types,
2104
+ uid: contentTypeUid,
2105
+ }],
2106
+ };
2107
+ const { paths, // ref. fields in the current content types
2108
+ ctQueries, // list of content type uids, the current content types refer to
2109
+ } = yield this.getAllReferencePaths(ctQuery, locale);
2110
+ const queries = {
2111
+ $or: [],
2112
+ }; // reference field paths
2113
+ const objectPointerList = []; // a mapper object, that holds pointer to the original element
2114
+ // iterate over each path in the entries and fetch the references
2115
+ // while fetching, keep track of their location
2116
+ for (let i = 0, j = paths.length; i < j; i++) {
2117
+ this.fetchPathDetails(entries, locale, paths[i].split('.'), queries, objectPointerList, true, entries, 0);
2118
+ }
2119
+ // even after traversing, if no references were found, simply return the entries found thusfar
2120
+ if (objectPointerList.length === 0) {
2121
+ return entries;
2122
+ }
2123
+ // else, self-recursively iterate and fetch references
2124
+ // Note: Shelf is the one holding `pointers` to the actual entry
2125
+ // Once the pointer has been used, for GC, point the object to null
2126
+ return this.includeAllReferencesIteration(queries, ctQueries, locale, objectPointerList);
2127
+ });
2128
+ }
2129
+ includeAllReferencesIteration(oldEntryQueries, oldCtQueries, locale, oldObjectPointerList, depth = 0) {
2130
+ return __awaiter(this, void 0, void 0, function* () {
2131
+ if (depth > this.q.referenceDepth || oldObjectPointerList.length === 0) {
2132
+ return;
2133
+ }
2134
+ else if (oldCtQueries.$or.length === 0 && oldObjectPointerList.length > 0 && oldEntryQueries.$or.length > 0) {
2135
+ yield this.bindLeftoverAssets(oldEntryQueries, locale, oldObjectPointerList);
2136
+ return;
2137
+ }
2138
+ const { ctQueries, paths, } = yield this.getAllReferencePaths(oldCtQueries, locale);
2139
+ // GC to aviod mem leaks
2140
+ oldCtQueries = null;
2141
+ const { result, queries, shelf, } = yield this.fetchEntries(oldEntryQueries, locale, paths, [], true);
2142
+ // GC to avoid mem leaks!
2143
+ oldEntryQueries = null;
2144
+ for (let i = 0, j = oldObjectPointerList.length; i < j; i++) {
2145
+ const element = oldObjectPointerList[i];
2146
+ let flag = true;
2147
+ for (let k = 0, l = result.length; k < l; k++) {
2148
+ if (result[k].uid === element.uid) {
2149
+ element.path[element.position] = result[k];
2150
+ flag = false;
2151
+ break;
2152
+ }
2153
+ }
2154
+ if (flag) {
2155
+ for (let e = 0, f = oldObjectPointerList[i].path.length; e < f; e++) {
2156
+ // tslint:disable-next-line: max-line-length
2157
+ if (oldObjectPointerList[i].path[e].hasOwnProperty('_content_type_uid') && Object.keys(oldObjectPointerList[i].path[e]).length === 2) {
2158
+ oldObjectPointerList[i].path.splice(e, 1);
2159
+ break;
2160
+ }
2161
+ }
2162
+ }
2163
+ }
2164
+ // GC to avoid mem leaks!
2165
+ oldObjectPointerList = null;
2166
+ ++depth;
2167
+ // Iterative loops, that traverses paths and binds them onto entries
2168
+ yield this.includeAllReferencesIteration(queries, ctQueries, locale, shelf, depth);
2169
+ return;
2170
+ });
2171
+ }
2172
+ getAllReferencePaths(contentTypeQueries, locale) {
2173
+ return __awaiter(this, void 0, void 0, function* () {
2174
+ const contents = yield this.db
2175
+ .collection((0, util_1.getCollectionName)({
2176
+ content_type_uid: this.types.content_types,
2177
+ locale,
2178
+ }, this.collectionNames))
2179
+ .find(contentTypeQueries)
2180
+ .project({
2181
+ _assets: 1,
2182
+ _references: 1,
2183
+ })
2184
+ .toArray();
2185
+ const ctQueries = {
2186
+ $or: [],
2187
+ };
2188
+ let paths = [];
2189
+ for (let i = 0, j = contents.length; i < j; i++) {
2190
+ let assetFieldPaths;
2191
+ let entryReferencePaths;
2192
+ if (contents[i].hasOwnProperty(this.types.assets)) {
2193
+ assetFieldPaths = Object.keys(contents[i][this.types.assets]);
2194
+ paths = paths.concat(assetFieldPaths);
2195
+ }
2196
+ if (contents[i].hasOwnProperty('_references')) {
2197
+ entryReferencePaths = Object.keys(contents[i][this.types.references]);
2198
+ paths = paths.concat(entryReferencePaths);
2199
+ for (let k = 0, l = entryReferencePaths.length; k < l; k++) {
2200
+ if (typeof contents[i][this.types.references][entryReferencePaths[k]] === 'string') {
2201
+ ctQueries.$or.push({
2202
+ _content_type_uid: this.types.content_types,
2203
+ // this would probably make it slow in FS, avoid this there?
2204
+ // locale,
2205
+ uid: contents[i][this.types.references][entryReferencePaths[k]],
2206
+ });
2207
+ }
2208
+ else if (contents[i][this.types.references][entryReferencePaths[k]].length) {
2209
+ contents[i][this.types.references][entryReferencePaths[k]].forEach((uid) => {
2210
+ ctQueries.$or.push({
2211
+ _content_type_uid: this.types.content_types,
2212
+ // avoiding locale here, not sure if its required
2213
+ // locale,
2214
+ uid,
2215
+ });
2216
+ });
2217
+ }
2218
+ }
2219
+ }
2220
+ }
2221
+ return {
2222
+ ctQueries,
2223
+ paths,
2224
+ };
2225
+ });
2226
+ }
2227
+ sanitizeIQuery(query) {
2228
+ const allowedKeys = {
2229
+ _content_type_uid: 'string',
2230
+ uid: 'string',
2231
+ _version: {
2232
+ $exists: 'boolean'
2233
+ },
2234
+ locale: 'string'
2235
+ };
2236
+ const validateObject = (obj, schema) => {
2237
+ for (const key in obj) {
2238
+ if (!schema.hasOwnProperty(key)) {
2239
+ return false;
2240
+ }
2241
+ if (typeof schema[key] === 'object') {
2242
+ if (!validateObject(obj[key], schema[key])) {
2243
+ return false;
2244
+ }
2245
+ }
2246
+ else if (typeof obj[key] !== schema[key]) {
2247
+ return false;
2248
+ }
2249
+ }
2250
+ return true;
2251
+ };
2252
+ if (!query || typeof query !== 'object' || Array.isArray(query)) {
2253
+ return false;
2254
+ }
2255
+ if (!query.$or || !Array.isArray(query.$or)) {
2256
+ return false;
2257
+ }
2258
+ for (const item of query.$or) {
2259
+ if (!validateObject(item, allowedKeys)) {
2260
+ return false;
2261
+ }
2262
+ }
2263
+ return true;
2264
+ }
2265
+ sanityQueryAny(query) {
2266
+ if (!query || typeof query !== 'object' || Array.isArray(query)) {
2267
+ return false;
2268
+ }
2269
+ return true;
2270
+ }
2271
+ }
2272
+ exports.Stack = Stack;