@lykmapipo/mongoose-common 0.39.0 → 0.40.0

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.
package/lib/index.js ADDED
@@ -0,0 +1,1290 @@
1
+ 'use strict';
2
+
3
+ const lodash = require('lodash');
4
+ const crypto = require('crypto');
5
+ const common = require('@lykmapipo/common');
6
+ const mongoose = require('mongoose-valid8');
7
+ const utils = require('mongoose/lib/utils');
8
+ const mongooseConnection = require('@lykmapipo/mongoose-connection');
9
+ const path = require('path');
10
+ const async = require('async');
11
+ const env = require('@lykmapipo/env');
12
+
13
+ /**
14
+ * @function isUniqueError
15
+ * @name isUniqueError
16
+ * @description Check if the given error is a unique mongodb error.
17
+ * @param {object} error valid error object to test.
18
+ * @returns {boolean} true if and only if it is an unique error.
19
+ * @version 0.2.0
20
+ * @since 0.1.0
21
+ * @private
22
+ */
23
+ const isUniqueError = (error) => {
24
+ return (
25
+ error &&
26
+ (error.name === 'BulkWriteError' ||
27
+ error.name === 'MongoError' ||
28
+ error.name === 'MongoServerError' ||
29
+ error.name === 'MongoBulkWriteError') &&
30
+ (error.code === 11000 || error.code === 11001) &&
31
+ !lodash.isEmpty(error.message)
32
+ );
33
+ };
34
+
35
+ /**
36
+ * @function parseErrorPaths
37
+ * @name parseErrorPaths
38
+ * @description Parse paths found in unique mongodb error.
39
+ * @param {object} schema valid mongoose schema.
40
+ * @param {Error} error valid mongodb error.
41
+ * @returns {string[]} found paths in error.
42
+ * @version 0.19.0
43
+ * @since 0.1.0
44
+ * @private
45
+ */
46
+ const parseErrorPaths = (schema, error) => {
47
+ // back off if no error message
48
+ if (!error.message) {
49
+ return [];
50
+ }
51
+
52
+ // obtain paths from error message
53
+ let paths = lodash.nth(error.message.match(/index: (.+?) dup key:/), 1) || '';
54
+ paths = paths.split('$').pop();
55
+
56
+ // handle compound unique paths index
57
+ paths = [].concat(paths.split('_'));
58
+
59
+ // in case for id ensure _id too and compact paths
60
+ paths = common.uniq([
61
+ ...paths,
62
+ ...lodash.map(paths, (pathName) => (pathName === 'id' ? '_id' : pathName)),
63
+ ]);
64
+
65
+ // ensure paths are within schema
66
+ paths = lodash.filter(paths, (pathName) => !lodash.isEmpty(schema.path(pathName)));
67
+
68
+ // return found paths
69
+ return paths;
70
+ };
71
+
72
+ /**
73
+ * @function parseErrorValues
74
+ * @name parseErrorValues
75
+ * @description Parse paths value found in unique mongodb error.
76
+ * @param {string[]} paths paths found in unique mongodb error.
77
+ * @param {Error} error valid mongodb error.
78
+ * @returns {string[]} found paths value from error.
79
+ * @version 0.19.0
80
+ * @since 0.1.0
81
+ * @private
82
+ */
83
+ const parseErrorValues = (paths, error) => {
84
+ // back off if no error message
85
+ if (!error.message) {
86
+ return [];
87
+ }
88
+
89
+ // obtain paths value
90
+ let values = lodash.nth(error.message.match(/dup key: { (.+?) }/), 1) || '';
91
+ values = common.uniq(
92
+ [].concat(values.match(/'(.+?)'/g)).concat(values.match(/"(.+?)"/g))
93
+ ); // enclosed with quotes
94
+ values = lodash.map(values, (v) => v.replace(/^"(.+?)"$/, '$1')); // double quotes
95
+ values = lodash.map(values, (v) => v.replace(/^'(.+?)'$/, '$1')); // single quotes
96
+ values = paths.length === 1 ? [values.join(' ')] : values;
97
+
98
+ // return parsed paths values
99
+ return values;
100
+ };
101
+
102
+ /**
103
+ * @function uniqueErrorPlugin
104
+ * @name uniqueErrorPlugin
105
+ * @description Plugin to handle mongodb unique error
106
+ * @param {object} schema valid mongoose schema
107
+ * @version 0.2.0
108
+ * @since 0.1.0
109
+ * @public
110
+ */
111
+ function uniqueErrorPlugin(schema) {
112
+ /**
113
+ * @function handleUniqueError
114
+ * @name handleUniqueError
115
+ * @description Handle mongodb unique error and transform to mongoose error
116
+ * @param {Error|object} error valid mongodb unique error
117
+ * @param {object} doc valid mongoose document
118
+ * @param {Function} next callback to invoke on success or error
119
+ * @returns {Error} valid mongoose error
120
+ * @version 0.2.0
121
+ * @since 0.1.0
122
+ * @private
123
+ */
124
+ function handleUniqueError(error, doc, next) {
125
+ // this: Model instance context
126
+
127
+ // obtain current instance
128
+ const instance = doc || this;
129
+
130
+ // continue if is not unique error
131
+ if (!isUniqueError(error)) {
132
+ return next(error);
133
+ }
134
+
135
+ // obtain index name
136
+ const indexName =
137
+ lodash.nth(error.message.match(/index: (.+?) dup key:/), 1) || '';
138
+
139
+ // obtain unique paths from error
140
+ const paths = parseErrorPaths(schema, error);
141
+
142
+ // obtain paths value from error
143
+ const values = parseErrorValues(paths, error);
144
+
145
+ // build mongoose validations error bag
146
+ if (!lodash.isEmpty(paths) && !lodash.isEmpty(values)) {
147
+ const errors = {};
148
+
149
+ lodash.forEach(paths, (pathName, index) => {
150
+ // construct path error properties
151
+ let pathValue = lodash.nth(values, index);
152
+ if (lodash.isFunction(instance.get)) {
153
+ pathValue = instance.get(pathName);
154
+ }
155
+ const props = {
156
+ type: 'unique',
157
+ path: pathName,
158
+ value: pathValue,
159
+ message: 'Path `{PATH}` ({VALUE}) is not unique.',
160
+ reason: error.message,
161
+ index: indexName,
162
+ };
163
+
164
+ // construct path validation error
165
+ const pathError = new mongoose.Error.ValidatorError(props);
166
+ pathError.index = indexName;
167
+ errors[pathName] = pathError;
168
+ });
169
+
170
+ // build mongoose validation error
171
+ const err = new mongoose.Error.ValidationError();
172
+ err.status = err.status || 400;
173
+ err.errors = errors;
174
+
175
+ return next(err);
176
+ }
177
+
178
+ // continue with error
179
+ return next(error);
180
+ }
181
+
182
+ // plugin unique error handler
183
+ schema.post('save', handleUniqueError);
184
+ schema.post('insertMany', handleUniqueError);
185
+ schema.post('findOneAndReplace', handleUniqueError);
186
+ schema.post('findOneAndUpdate', handleUniqueError);
187
+ schema.post('replaceOne', handleUniqueError);
188
+ schema.post('update', handleUniqueError);
189
+ schema.post('updateMany', handleUniqueError);
190
+ schema.post('updateOne', handleUniqueError);
191
+ }
192
+
193
+ /**
194
+ * @function path
195
+ * @name path
196
+ * @description obtain schema path from model
197
+ * @param {object} schema valid mongoose schema instance
198
+ * @version 0.1.0
199
+ * @since 0.1.0
200
+ * @public
201
+ */
202
+ const pathPlugin = (schema) => {
203
+ // register path
204
+ const canNotGetPath = !lodash.isFunction(schema.statics.path);
205
+
206
+ if (canNotGetPath) {
207
+ // eslint-disable-next-line no-param-reassign
208
+ schema.statics.path = function path(pathName) {
209
+ // initalize path
210
+ let $path;
211
+
212
+ // tokenize path
213
+ const paths = lodash.split(pathName, '.');
214
+
215
+ // iterate on schema recursive to get path schema
216
+ lodash.forEach(paths, function getPath(part) {
217
+ // obtain schema to resolve path
218
+ const $schema = $path ? $path.schema : schema;
219
+ $path = $schema && $schema.path ? $schema.path(part) : undefined;
220
+ });
221
+
222
+ // fall back to direct path
223
+ $path = $path || schema.path(pathName);
224
+
225
+ // return found path
226
+ return $path;
227
+ };
228
+ }
229
+ };
230
+
231
+ /**
232
+ * @name loadPathSeeds
233
+ * @description load seeds from paths
234
+ * @param {string} collectionName valid collection name
235
+ * @returns {object|object[]} given collection seed from a path
236
+ * @since 0.21.0
237
+ * @version 0.2.0
238
+ * @private
239
+ */
240
+ function loadPathSeeds(collectionName) {
241
+ // resolve seed path
242
+ const BASE_PATH = env.getString('BASE_PATH', process.cwd());
243
+ let SEED_PATH = env.getString('SEED_PATH', path.join(BASE_PATH, 'seeds'));
244
+ SEED_PATH = path.resolve(SEED_PATH, collectionName);
245
+
246
+ // try load seeds from path
247
+ try {
248
+ // eslint-disable-next-line import/no-dynamic-require, global-require
249
+ let seeds = require(SEED_PATH);
250
+ // honor es6 default exports
251
+ seeds = [].concat(lodash.isArray(seeds.default) ? seeds.default : seeds);
252
+ return seeds;
253
+ } catch (e) {
254
+ return [];
255
+ }
256
+ }
257
+
258
+ /**
259
+ * @function clearAndSeedModel
260
+ * @name clearAndSeedModel
261
+ * @description clear and seed the given model data
262
+ * @param {object|object[]|Function} data valid model data
263
+ * @param {Function} done callback to invoke on success or error
264
+ * @returns {object} seed results
265
+ * @since 0.21.0
266
+ * @version 0.2.0
267
+ * @private
268
+ */
269
+ function seedModel(data, done) {
270
+ // this: Model static context
271
+
272
+ // normalize arguments
273
+ let seeds = [];
274
+ let cb = lodash.noop;
275
+ let filterFn = (val) => val;
276
+ let transformFn = (val) => val;
277
+ if (lodash.isFunction(data)) {
278
+ cb = data;
279
+ }
280
+ if (lodash.isArray(data)) {
281
+ seeds = [].concat(data);
282
+ }
283
+ if (lodash.isPlainObject(data)) {
284
+ filterFn = data.filter || filterFn;
285
+ transformFn = data.transform || transformFn;
286
+ seeds = data.data || lodash.omit(data, 'filter', 'transform');
287
+ seeds = lodash.isArray(seeds) ? seeds : common.mergeObjects(seeds);
288
+ seeds = [].concat(seeds);
289
+ seeds = lodash.filter(seeds, (seed) => !lodash.isEmpty(seed));
290
+ }
291
+ if (lodash.isFunction(done)) {
292
+ cb = done || cb;
293
+ }
294
+ // let seeds = _.isFunction(data) ? [] : [].concat(data);
295
+ // const cb = _.isFunction(data) ? data : done;
296
+
297
+ // compact seeds
298
+ const collectionName =
299
+ lodash.get(this, 'collection.name') || lodash.get(this, 'collection.collectionName');
300
+
301
+ // ignore path seeds if seed provided
302
+ if (lodash.isEmpty(seeds)) {
303
+ const pathSeeds = loadPathSeeds(collectionName);
304
+ seeds = lodash.compact([...seeds, ...pathSeeds]);
305
+ seeds = lodash.filter(seeds, (seed) => !lodash.isEmpty(seed));
306
+ }
307
+
308
+ // filter seeds
309
+ seeds = lodash.filter(seeds, filterFn);
310
+
311
+ // transform seeds
312
+ seeds = lodash.map(seeds, transformFn);
313
+
314
+ // filter empty seeds
315
+ seeds = lodash.filter(seeds, (seed) => !lodash.isEmpty(seed));
316
+
317
+ // find existing instance fullfill seed criteria
318
+ const findExisting = (seed, afterFind) => {
319
+ // map seed to criteria
320
+ const canProvideCriteria = lodash.isFunction(this.prepareSeedCriteria);
321
+ let prepareSeedCriteria = ($seed) => $seed;
322
+ if (canProvideCriteria) {
323
+ prepareSeedCriteria = this.prepareSeedCriteria;
324
+ }
325
+ let criteria = prepareSeedCriteria(seed);
326
+ criteria = lodash.omit(criteria, 'populate');
327
+
328
+ // find existing data
329
+ return this.findOne(criteria, afterFind);
330
+ };
331
+
332
+ // fetch existing dependency
333
+ const fetchDependency = (dependency, afterDependency) => {
334
+ // obtain options
335
+ const { model, match, select, array } = dependency;
336
+
337
+ const afterFetchDependency = (error, found) => {
338
+ const result = lodash.isEmpty(found) ? undefined : found;
339
+ return afterDependency(error, result);
340
+ };
341
+
342
+ // try fetch with provide options
343
+ if (lodash.isString(model) && lodash.isPlainObject(match)) {
344
+ try {
345
+ const Model = mongoose.model(model);
346
+ if (array) {
347
+ return Model.find(
348
+ match,
349
+ common.mergeObjects(select, { _id: 1 }),
350
+ { autopopulate: false },
351
+ afterFetchDependency
352
+ );
353
+ }
354
+ return Model.findOne(
355
+ match,
356
+ common.mergeObjects(select, { _id: 1 }),
357
+ { autopopulate: false },
358
+ afterFetchDependency
359
+ );
360
+ } catch (e) {
361
+ return afterDependency(e);
362
+ }
363
+ }
364
+
365
+ // backoff: invalid options
366
+ return afterDependency(new Error('Invalid Populate Options'));
367
+ };
368
+
369
+ // fetch dependencies exclude ignored
370
+ const fetchDependencyExcludeIgnore = (dependency, afterDependency) => {
371
+ // obtain options
372
+ const { ignore = {} } = dependency;
373
+ return async.waterfall(
374
+ [
375
+ (next) => {
376
+ if (lodash.isEmpty(ignore)) {
377
+ return next(null, []);
378
+ }
379
+ const ignoreCriteria = lodash.omit(ignore, 'select');
380
+ return fetchDependency(ignoreCriteria, next);
381
+ }, // fetch ignored
382
+ (ignored, next) => {
383
+ // use ignored
384
+ const ignorePath = ignore.path || '_id';
385
+ const ignoredIds = lodash.compact(
386
+ lodash.map([].concat(ignored), (val) => common.idOf(val))
387
+ );
388
+ const { model, select, array } = common.mergeObjects(dependency);
389
+ let { match } = common.mergeObjects(dependency);
390
+ if (!lodash.isEmpty(ignoredIds)) {
391
+ match = common.mergeObjects(
392
+ {
393
+ [ignorePath]: { $nin: ignoredIds },
394
+ },
395
+ match
396
+ );
397
+ }
398
+ return fetchDependency({ model, match, select, array }, next);
399
+ }, // fetch dependencies exclude ignored
400
+ ],
401
+ afterDependency
402
+ );
403
+ };
404
+
405
+ // fetch existing seed dependencies
406
+ // TODO: optimize queries
407
+ const fetchDependencies = (seed, afterDependencies) => {
408
+ let dependencies = common.mergeObjects(seed.populate);
409
+ if (lodash.isPlainObject(dependencies) && !lodash.isEmpty(dependencies)) {
410
+ dependencies = lodash.mapValues(dependencies, (dependency) => {
411
+ return (afterDependency) => {
412
+ return fetchDependencyExcludeIgnore(dependency, afterDependency);
413
+ };
414
+ });
415
+ return async.parallel(dependencies, afterDependencies);
416
+ }
417
+ return afterDependencies(null, seed);
418
+ };
419
+
420
+ // merge existing with seed data
421
+ const mergeOne = (found, $data, afterMergeOne) => {
422
+ if (found) {
423
+ const SEED_FRESH = env.getBoolean('SEED_FRESH', false);
424
+ let updates = {};
425
+ if (SEED_FRESH) {
426
+ updates = common.mergeObjects(found.toObject(), $data);
427
+ } else {
428
+ updates = common.mergeObjects($data, found.toObject());
429
+ }
430
+ found.set(updates);
431
+ // eslint-disable-next-line no-param-reassign
432
+ found.updatedAt = new Date();
433
+ } else {
434
+ // eslint-disable-next-line no-param-reassign
435
+ found = new this($data);
436
+ }
437
+ return found.put ? found.put(afterMergeOne) : found.save(afterMergeOne);
438
+ };
439
+
440
+ // update or create seed
441
+ const upsertOne = (seed, afterUpsert) => {
442
+ return async.waterfall(
443
+ [
444
+ (next) => {
445
+ fetchDependencies(seed, (error, dependencies) => {
446
+ if (error) {
447
+ return next(error);
448
+ }
449
+ lodash.forEach(dependencies, (value, key) => {
450
+ // eslint-disable-next-line no-param-reassign
451
+ seed[key] = value;
452
+ });
453
+ return next();
454
+ });
455
+ },
456
+ (next) => findExisting(seed, next),
457
+ (found, next) => mergeOne(found, seed, next),
458
+ ],
459
+ afterUpsert
460
+ );
461
+ };
462
+
463
+ // prepare seeds
464
+ seeds = lodash.map(seeds, (seed) => {
465
+ return (next) => upsertOne(seed, next);
466
+ });
467
+
468
+ // run seeds
469
+ return async.parallel(seeds, cb);
470
+ }
471
+
472
+ /**
473
+ * @function clearAndSeedModel
474
+ * @name clearAndSeedModel
475
+ * @description clear and seed the given model data
476
+ * @param {object|object[]|Function} data valid model data
477
+ * @param {Function} done callback to invoke on success or error
478
+ * @returns {object} seed results
479
+ * @since 0.21.0
480
+ * @version 0.2.0
481
+ * @private
482
+ */
483
+ function clearAndSeedModel(data, done) {
484
+ // this: Model static context
485
+
486
+ // normalize callback
487
+ const cb = lodash.isFunction(data) ? data : done;
488
+
489
+ // clear model data
490
+ const doClear = (next) => this.deleteMany((error) => next(error));
491
+
492
+ // seed model data
493
+ const doSeed = (next) =>
494
+ lodash.isFunction(data) ? this.seed(next) : this.seed(data, next);
495
+
496
+ // run clear then seed
497
+ return async.waterfall([doClear, doSeed], cb);
498
+ }
499
+
500
+ /**
501
+ * @function seedPlugin
502
+ * @name seedPlugin
503
+ * @description Extend mongoose schema with seed capability
504
+ * @param {object} schema valid mongoose schema instance
505
+ * @since 0.21.0
506
+ * @version 0.2.0
507
+ * @public
508
+ */
509
+ function seedPlugin(schema) {
510
+ const canNotSeed = !lodash.isFunction(schema.statics.seed);
511
+ if (canNotSeed) {
512
+ // eslint-disable-next-line no-param-reassign
513
+ schema.statics.seed = seedModel;
514
+
515
+ // eslint-disable-next-line no-param-reassign
516
+ schema.statics.clearAndSeed = clearAndSeedModel;
517
+ }
518
+ }
519
+
520
+ // TODO: async prepareSeedCriteria
521
+ // TODO: prepareSeedCriteria(seed, code)
522
+ // TODO: done(error, criteria)
523
+
524
+ /**
525
+ * @module mongoose-common
526
+ * @name mongoose-common
527
+ * @description Re-usable helpers for mongoose
528
+ * @author lally elias <lallyelias87@mail.com>
529
+ * @license MIT
530
+ * @since 0.1.0
531
+ * @version 0.1.0
532
+ * @public
533
+ * @example
534
+ *
535
+ * const {
536
+ * connect,
537
+ * clear,
538
+ * drop,
539
+ * disconnect,
540
+ * } = require('@lykmapipo/mongoose-common');
541
+ *
542
+ * connect((error) => { ... });
543
+ * clear((error) => { ... });
544
+ * drop((error) => { ... });
545
+ * disconnect((error) => { ... });
546
+ */
547
+
548
+ // set global mongoose promise
549
+ mongoose.Promise = global.Promise;
550
+
551
+ /**
552
+ * @name path
553
+ * @description register path schema plugin
554
+ * @since 0.1.0
555
+ * @version 0.1.0
556
+ * @public
557
+ * @example
558
+ *
559
+ * const name = User.path('name');
560
+ * //=> SchemaString { path: 'name', instance: 'String', ... }
561
+ */
562
+ mongoose.plugin(pathPlugin); // TODO: ignore global
563
+
564
+ /**
565
+ * @name error
566
+ * @description unique error handler schema plugin
567
+ * @since 0.1.0
568
+ * @version 0.1.0
569
+ * @public
570
+ */
571
+ mongoose.plugin(uniqueErrorPlugin); // TODO: ignore global
572
+
573
+ /**
574
+ * @name seed
575
+ * @description data seed schema plugin
576
+ * @since 0.21.0
577
+ * @version 0.1.0
578
+ * @public
579
+ */
580
+ mongoose.plugin(seedPlugin); // TODO: ignore global
581
+
582
+ // expose shortcuts
583
+ const { STATES } = mongoose;
584
+ const { Aggregate } = mongoose;
585
+ const { Collection } = mongoose;
586
+ const { Connection } = mongoose;
587
+ const { Schema } = mongoose;
588
+ const { SchemaType } = mongoose;
589
+ const { SchemaTypes } = mongoose;
590
+ const { VirtualType } = mongoose;
591
+ const { Types } = mongoose;
592
+ const MongooseTypes = mongoose.Types;
593
+ const { Query } = mongoose;
594
+ const MongooseError = mongoose.Error;
595
+ const { CastError } = mongoose;
596
+ const modelNames = () => mongoose.modelNames();
597
+ const { GridFSBucket } = mongoose.mongo;
598
+
599
+ // schema types shortcuts
600
+
601
+ const SchemaString = Schema.Types.String;
602
+ const SchemaNumber = Schema.Types.Number;
603
+ const SchemaBoolean = Schema.Types.Boolean;
604
+ const { DocumentArray } = Schema.Types;
605
+ const SchemaDocumentArray = Schema.Types.DocumentArray;
606
+ const SubDocument = Schema.Types.Subdocument;
607
+ const SchemaSubDocument = Schema.Types.Subdocument;
608
+ const Embedded = SubDocument;
609
+ const SchemaEmbedded = SubDocument;
610
+ const SchemaArray = Schema.Types.Array;
611
+ const SchemaBuffer = Schema.Types.Buffer;
612
+ const SchemaDate = Schema.Types.Date;
613
+ const { ObjectId } = Schema.Types;
614
+ const SchemaObjectId = Schema.Types.ObjectId;
615
+ const { Mixed } = Schema.Types;
616
+ const SchemaMixed = Schema.Types.Mixed;
617
+ const { Decimal128 } = Schema.Types;
618
+ const SchemaDecimal = Decimal128;
619
+ const SchemaDecimal128 = Decimal128;
620
+ const SchemaMap = Schema.Types.Map;
621
+
622
+ /**
623
+ * @name LOOKUP_FIELDS
624
+ * @description Common lookup fields used in aggregation
625
+ * @author lally elias <lallyelias87@mail.com>
626
+ * @since 0.13.0
627
+ * @version 0.1.0
628
+ * @public
629
+ * @example
630
+ *
631
+ * const { LOOKUP_FIELDS } = require('@lykmapipo/mongoose-common');
632
+ * //=> ['from', 'localField', 'foreignField', 'as']
633
+ */
634
+ const LOOKUP_FIELDS = ['from', 'localField', 'foreignField', 'as'];
635
+
636
+ /**
637
+ * @function toCollectionName
638
+ * @name toCollectionName
639
+ * @description Produces a collection name of provided model name
640
+ * @param {string} modelName a model name
641
+ * @returns {string} a collection name
642
+ * @author lally elias <lallyelias87@mail.com>
643
+ * @since 0.8.0
644
+ * @version 0.1.0
645
+ * @public
646
+ * @example
647
+ *
648
+ * const collectionName = toCollectionName('User');
649
+ * //=> users
650
+ */
651
+ const toCollectionName = (modelName) => {
652
+ let collectionName = modelName;
653
+ if (!lodash.isEmpty(modelName)) {
654
+ collectionName = mongoose.pluralize()(modelName);
655
+ }
656
+ return collectionName;
657
+ };
658
+
659
+ /**
660
+ * @function isObjectId
661
+ * @name isObjectId
662
+ * @description Check if provided value is an instance of ObjectId
663
+ * @param {Mixed} val value to check if its an ObjectId
664
+ * @author lally elias <lallyelias87@mail.com>
665
+ * @returns {boolean} whether a val is ObjectId instance
666
+ * @since 0.2.0
667
+ * @version 0.1.0
668
+ * @public
669
+ * @example
670
+ *
671
+ * isObjectId(val);
672
+ * //=> true
673
+ */
674
+ const isObjectId = (val) => {
675
+ const $isObjectId = val instanceof mongoose.Types.ObjectId;
676
+ return $isObjectId;
677
+ };
678
+
679
+ /**
680
+ * @function isMap
681
+ * @name isMap
682
+ * @description Check if provided value is an instance of Map
683
+ * @param {Mixed} val value to check if its a Map
684
+ * @author lally elias <lallyelias87@mail.com>
685
+ * @returns {boolean} whether a val is Map instance
686
+ * @since 0.2.0
687
+ * @version 0.1.0
688
+ * @public
689
+ * @example
690
+ *
691
+ * isMap(val);
692
+ * //=> true
693
+ */
694
+ const isMap = (val) => {
695
+ const $isMap = val instanceof mongoose.Types.Map;
696
+ return $isMap;
697
+ };
698
+
699
+ /**
700
+ * @function isString
701
+ * @name isString
702
+ * @description Check if provided value is an instance of String schema type
703
+ * @param {Mixed} val value to check if its a String schema type
704
+ * @author lally elias <lallyelias87@mail.com>
705
+ * @returns {boolean} whether a val is String instance
706
+ * @since 0.10.0
707
+ * @version 0.1.0
708
+ * @public
709
+ * @example
710
+ *
711
+ * isString(val);
712
+ * //=> true
713
+ */
714
+ const isString = (val) => {
715
+ const $isString = val instanceof Schema.Types.String;
716
+ return $isString;
717
+ };
718
+
719
+ /**
720
+ * @function isArraySchemaType
721
+ * @name isArraySchemaType
722
+ * @description check if schema type is array
723
+ * @param {SchemaType} val valid mongoose schema type
724
+ * @returns {boolean} whether schema type is array
725
+ * @author lally elias <lallyelias87@mail.com>
726
+ * @since 0.16.0
727
+ * @version 0.1.0
728
+ * @public
729
+ * @example
730
+ *
731
+ * isArraySchemaType(val)
732
+ * //=> true
733
+ */
734
+ const isArraySchemaType = (val = {}) => {
735
+ const { $isMongooseArray = false, instance } = val;
736
+ const isArray =
737
+ val instanceof Schema.Types.Array ||
738
+ $isMongooseArray ||
739
+ instance === 'Array';
740
+ return isArray;
741
+ };
742
+
743
+ /**
744
+ * @function isStringArray
745
+ * @name isStringArray
746
+ * @description Check if provided value is an instance of StringArray
747
+ * schema type
748
+ * @param {Mixed} val value to check if its a StringArray schema type
749
+ * @author lally elias <lallyelias87@mail.com>
750
+ * @returns {boolean} whether a val is String Array instance
751
+ * @since 0.11.0
752
+ * @version 0.1.0
753
+ * @public
754
+ * @example
755
+ *
756
+ * isStringArray(val);
757
+ * //=> true
758
+ */
759
+ const isStringArray = (val) => {
760
+ const $isStringArray =
761
+ val &&
762
+ val instanceof Schema.Types.Array &&
763
+ val.caster instanceof Schema.Types.String;
764
+ return $isStringArray;
765
+ };
766
+
767
+ /**
768
+ * @function isNumber
769
+ * @name isNumber
770
+ * @description Check if provided value is an instance of Number schema type
771
+ * @param {Mixed} val value to check if its a Number schema type
772
+ * @author lally elias <lallyelias87@mail.com>
773
+ * @returns {boolean} whether a val is Number instance
774
+ * @since 0.10.0
775
+ * @version 0.1.0
776
+ * @public
777
+ * @example
778
+ *
779
+ * isNumber(<val>);
780
+ * //=> true
781
+ */
782
+ const isNumber = (val) => {
783
+ const $isNumber = val instanceof Schema.Types.Number;
784
+ return $isNumber;
785
+ };
786
+
787
+ /**
788
+ * @function isNumberArray
789
+ * @name isNumberArray
790
+ * @description Check if provided value is an instance of NumberArray
791
+ * schema type
792
+ * @param {Mixed} val value to check if its a NumberArray schema type
793
+ * @author lally elias <lallyelias87@mail.com>
794
+ * @returns {boolean} whether a val is Number Array instance
795
+ * @since 0.11.0
796
+ * @version 0.1.0
797
+ * @public
798
+ * @example
799
+ *
800
+ * isNumberArray(val);
801
+ * //=> true
802
+ */
803
+ const isNumberArray = (val) => {
804
+ const $isNumberArray =
805
+ val &&
806
+ val instanceof Schema.Types.Array &&
807
+ val.caster instanceof Schema.Types.Number;
808
+ return $isNumberArray;
809
+ };
810
+
811
+ /**
812
+ * @function isInstance
813
+ * @name isInstance
814
+ * @description check if object is valid mongoose model instance
815
+ * @param {object} value valid object
816
+ * @returns {boolean} whether object is valid model instance
817
+ * @author lally elias <lallyelias87@mail.com>
818
+ * @since 0.4.0
819
+ * @version 0.2.0
820
+ * @public
821
+ * @example
822
+ *
823
+ * isInstance(val);
824
+ * //=> true
825
+ */
826
+ const isInstance = (value) => {
827
+ if (value) {
828
+ const $isInstance =
829
+ lodash.isFunction(lodash.get(value, 'toObject', null)) &&
830
+ !lodash.isNull(lodash.get(value, '$__', null));
831
+ return $isInstance;
832
+ }
833
+ return false;
834
+ };
835
+
836
+ /**
837
+ * @name copyInstance
838
+ * @description copy and return plain object of mongoose model instance
839
+ * @param {object} value valid object
840
+ * @returns {object} plain object from mongoose model instance
841
+ * @author lally elias <lallyelias87@mail.com>
842
+ * @since 0.4.0
843
+ * @version 0.1.0
844
+ * @public
845
+ * @example
846
+ *
847
+ * const instance = copyInstance(val);
848
+ * //=> { ... }
849
+ */
850
+ const copyInstance = (value = {}) => common.mergeObjects(utils.toObject(value));
851
+
852
+ /**
853
+ * @function schemaTypeOptionOf
854
+ * @name schemaTypeOptionOf
855
+ * @description obtain schema type options
856
+ * @param {SchemaType} schemaType valid mongoose schema type
857
+ * @returns {object} schema type options
858
+ * @author lally elias <lallyelias87@mail.com>
859
+ * @since 0.14.0
860
+ * @version 0.1.0
861
+ * @private
862
+ * @example
863
+ *
864
+ * const options = schemaTypeOptionOf(schemaType)
865
+ * //=> { trim: true, ... }
866
+ */
867
+ const schemaTypeOptionOf = (schemaType = {}) => {
868
+ // grab options
869
+ const options = common.mergeObjects(
870
+ // grub schema caster options
871
+ lodash.toPlainObject(lodash.get(schemaType, 'caster.options')),
872
+ // grab direct schema options
873
+ lodash.toPlainObject(lodash.get(schemaType, 'options'))
874
+ );
875
+ // return options
876
+ return options;
877
+ };
878
+
879
+ /**
880
+ * @function eachPath
881
+ * @name eachPath
882
+ * @description iterate recursively on schema primitive paths and invoke
883
+ * provided iteratee function.
884
+ * @param {object} schema valid instance of mongoose schema
885
+ * @param {Function} iteratee callback function invoked per each path found.
886
+ * The callback is passed the pathName, parentPath and schemaType as arguments
887
+ * on each iteration.
888
+ * @see {@link https://mongoosejs.com/docs/api.html#schema_Schema-eachPath}
889
+ * @author lally elias <lallyelias87@mail.com>
890
+ * @since 0.1.0
891
+ * @version 0.1.0
892
+ * @public
893
+ * @example
894
+ *
895
+ * eachPath(schema, (path, schemaType) => { ... });
896
+ */
897
+ const eachPath = (schema, iteratee) => {
898
+ /**
899
+ * @name iterateRecursive
900
+ * @description recursivily search for a schema path
901
+ * @param {string} pathName valid schema path name
902
+ * @param {object} schemaType valid schema type
903
+ * @param {string} parentPath parent schema path
904
+ */
905
+ function iterateRecursive(pathName, schemaType, parentPath) {
906
+ // compute path name
907
+ const $path = common.compact([parentPath, pathName]).join('.');
908
+
909
+ // check if is sub schema
910
+ const $isSchema =
911
+ schemaType.schema && lodash.isFunction(schemaType.schema.eachPath);
912
+
913
+ // iterate over sub schema
914
+ if ($isSchema) {
915
+ const { schema: subSchema } = schemaType;
916
+ subSchema.eachPath(function iterateSubSchema($pathName, $schemaType) {
917
+ iterateRecursive($pathName, $schemaType, $path);
918
+ });
919
+ }
920
+
921
+ // invoke iteratee
922
+ else {
923
+ iteratee($path, schemaType);
924
+ }
925
+ }
926
+
927
+ // iterate recursive
928
+ schema.eachPath(function iterateParentSchema(pathName, schemaType) {
929
+ iterateRecursive(pathName, schemaType);
930
+ });
931
+ };
932
+
933
+ /**
934
+ * @function jsonSchema
935
+ * @name jsonSchema
936
+ * @description Produces valid json schema of all available models
937
+ * if `mongoose-schema-jsonschema` has been applied
938
+ * @author lally elias <lallyelias87@mail.com>
939
+ * @returns {object[]} models json schema
940
+ * @since 0.8.0
941
+ * @version 0.1.0
942
+ * @public
943
+ * @example
944
+ *
945
+ * const jsonSchema = jsonSchema();
946
+ * //=> {"user": {title: "User", type: "object", properties: {..} } }
947
+ */
948
+ const jsonSchema = () => {
949
+ // initialize schemas dictionary
950
+ const schemas = {};
951
+ // get model names
952
+ const $modelNames = mongoose.modelNames();
953
+ // loop model names to get schemas
954
+ lodash.forEach($modelNames, function getJsonSchema(modelName) {
955
+ // get model
956
+ const Model = mongooseConnection.model(modelName);
957
+ // collect model json schema
958
+ if (Model && lodash.isFunction(Model.jsonSchema)) {
959
+ schemas[modelName] = Model.jsonSchema();
960
+ }
961
+ });
962
+ // return available schemas
963
+ return schemas;
964
+ };
965
+
966
+ /**
967
+ * @function validationErrorFor
968
+ * @name validationErrorFor
969
+ * @description Create mongoose validation error for specified options
970
+ * @param {object} optns valid error options
971
+ * @param {number | string} [optns.status] valid error status
972
+ * @param {number | string} [optns.code] valid error code
973
+ * @param {object} [optns.paths] paths with validator error properties
974
+ * @returns {object} valid instance of mongoose validation error
975
+ * @author lally elias <lallyelias87@mail.com>
976
+ * @since 0.24.0
977
+ * @version 0.1.0
978
+ * @public
979
+ * @example
980
+ *
981
+ * const status = 400;
982
+ * const paths = {
983
+ * name: { type: 'required', path:'name', value: ..., message: ... }
984
+ * };
985
+ * const error = validationErrorFor({ status, paths });
986
+ * //=> error
987
+ */
988
+ const validationErrorFor = (optns) => {
989
+ // obtain options
990
+ const { status = 400, code = 400, paths = {} } = common.mergeObjects(optns);
991
+
992
+ // create mongoose validation error
993
+ const error = new mongoose.Error.ValidationError();
994
+ error.status = status;
995
+ error.code = code || status;
996
+ // eslint-disable-next-line no-underscore-dangle
997
+ error.message = error.message || error._message;
998
+
999
+ // attach path validator error
1000
+ if (!lodash.isEmpty(paths)) {
1001
+ const errors = {};
1002
+ lodash.forEach(paths, (props, path) => {
1003
+ let pathError = common.mergeObjects({ path }, props);
1004
+ pathError = new mongoose.Error.ValidatorError(pathError);
1005
+ errors[path] = pathError;
1006
+ });
1007
+ error.errors = errors;
1008
+ }
1009
+
1010
+ // return validation error
1011
+ return error;
1012
+ };
1013
+
1014
+ /**
1015
+ * @function areSameInstance
1016
+ * @name areSameInstance
1017
+ * @description check if given two mongoose model instances are same
1018
+ * @param {object} a valid model instance
1019
+ * @param {object} b valid model instance
1020
+ * @returns {boolean} whether model instance are same
1021
+ * @author lally elias <lallyelias87@mail.com>
1022
+ * @since 0.31.0
1023
+ * @version 0.1.0
1024
+ * @public
1025
+ * @example
1026
+ *
1027
+ * areSameInstance(a, a); //=> true
1028
+ */
1029
+ const areSameInstance = (a, b) => {
1030
+ try {
1031
+ const areSame = !!(a && b && a.equals(b));
1032
+ return areSame;
1033
+ } catch (e) {
1034
+ return false;
1035
+ }
1036
+ };
1037
+
1038
+ /**
1039
+ * @function areSameObjectId
1040
+ * @name areSameObjectId
1041
+ * @description check if given two mongoose objectid are same
1042
+ * @param {object} a valid object id
1043
+ * @param {object} b valid object
1044
+ * @returns {boolean} whether objectid's are same
1045
+ * @author lally elias <lallyelias87@mail.com>
1046
+ * @since 0.31.0
1047
+ * @version 0.1.0
1048
+ * @public
1049
+ * @example
1050
+ *
1051
+ * areSameObjectId(a, a); //=> true
1052
+ */
1053
+ const areSameObjectId = (a, b) => {
1054
+ try {
1055
+ // grab actual ids
1056
+ const idOfA = common.idOf(a) || a;
1057
+ const idOfB = common.idOf(b) || b;
1058
+
1059
+ // convert to string
1060
+ const idA = isObjectId(idOfA) ? idOfA.toString() : idOfA;
1061
+ const idB = isObjectId(idOfB) ? idOfB.toString() : idOfB;
1062
+
1063
+ // check if are equal
1064
+ const areSame = idA === idB;
1065
+ return areSame;
1066
+ } catch (e) {
1067
+ return false;
1068
+ }
1069
+ };
1070
+
1071
+ /**
1072
+ * @function toObjectIds
1073
+ * @name toObjectIds
1074
+ * @description convert given model instances into object ids
1075
+ * @param {...object} instances valid model instances
1076
+ * @returns {object[]} objectid's of model instances
1077
+ * @author lally elias <lallyelias87@mail.com>
1078
+ * @since 0.31.0
1079
+ * @version 0.1.0
1080
+ * @public
1081
+ * @example
1082
+ *
1083
+ * toObjectIds(a, b); //=> [ '5e90486301de071ca4ebc03d', ... ]
1084
+ */
1085
+ const toObjectIds = (...instances) => {
1086
+ const ids = lodash.map([...instances], (instance) => {
1087
+ const id = common.idOf(instance) || instance;
1088
+ return id;
1089
+ });
1090
+ return ids;
1091
+ };
1092
+
1093
+ /**
1094
+ * @function toObjectIdStrings
1095
+ * @name toObjectIdStrings
1096
+ * @description convert given model instances objectid's into strings
1097
+ * @param {...object} instances valid model instances
1098
+ * @returns {string[]} objectid's as strings
1099
+ * @author lally elias <lallyelias87@mail.com>
1100
+ * @since 0.31.0
1101
+ * @version 0.1.0
1102
+ * @public
1103
+ * @example
1104
+ *
1105
+ * toObjectIdStrings(a, b); //=> [ '5e90486301de071ca4ebc03d', ... ]
1106
+ */
1107
+ const toObjectIdStrings = (...instances) => {
1108
+ const ids = toObjectIds(...instances);
1109
+ const idStrings = lodash.map([...ids], (id) => {
1110
+ const idString = isObjectId(id) ? id.toString() : id;
1111
+ return idString;
1112
+ });
1113
+ return idStrings;
1114
+ };
1115
+
1116
+ /**
1117
+ * @function objectIdFor
1118
+ * @name objectIdFor
1119
+ * @description create a unique objectid of a given model values
1120
+ * @param {...string} modelName valid model name
1121
+ * @param {...string} parts values to generate object id for
1122
+ * @returns {object} valid objectid
1123
+ * @author lally elias <lallyelias87@mail.com>
1124
+ * @since 0.36.0
1125
+ * @version 0.1.0
1126
+ * @public
1127
+ * @example
1128
+ *
1129
+ * objectIdFor('Party', 'TZ-0101');
1130
+ * //=> '5e90486301de071ca4ebc03d'
1131
+ */
1132
+ const objectIdFor = (modelName, ...parts) => {
1133
+ // ensure parts
1134
+ const values = common.compact([].concat(modelName).concat(...parts));
1135
+
1136
+ // ensure secret & message
1137
+ const secret = lodash.head(values);
1138
+ const data = common.join(lodash.tail(values), ':');
1139
+
1140
+ // generate 24-byte hex hash
1141
+ const hash = crypto.createHmac('md5', secret)
1142
+ .update(data)
1143
+ .digest('hex')
1144
+ .slice(0, 24);
1145
+
1146
+ // create objectid from hash
1147
+ const objectId = mongoose.Types.ObjectId.createFromHexString(hash);
1148
+
1149
+ return objectId;
1150
+ };
1151
+
1152
+ Object.defineProperty(exports, 'SCHEMA_OPTIONS', {
1153
+ enumerable: true,
1154
+ get: function () { return mongooseConnection.SCHEMA_OPTIONS; }
1155
+ });
1156
+ Object.defineProperty(exports, 'SUB_SCHEMA_OPTIONS', {
1157
+ enumerable: true,
1158
+ get: function () { return mongooseConnection.SUB_SCHEMA_OPTIONS; }
1159
+ });
1160
+ Object.defineProperty(exports, 'clear', {
1161
+ enumerable: true,
1162
+ get: function () { return mongooseConnection.clear; }
1163
+ });
1164
+ Object.defineProperty(exports, 'collectionNameOf', {
1165
+ enumerable: true,
1166
+ get: function () { return mongooseConnection.collectionNameOf; }
1167
+ });
1168
+ Object.defineProperty(exports, 'connect', {
1169
+ enumerable: true,
1170
+ get: function () { return mongooseConnection.connect; }
1171
+ });
1172
+ Object.defineProperty(exports, 'createModel', {
1173
+ enumerable: true,
1174
+ get: function () { return mongooseConnection.createModel; }
1175
+ });
1176
+ Object.defineProperty(exports, 'createSchema', {
1177
+ enumerable: true,
1178
+ get: function () { return mongooseConnection.createSchema; }
1179
+ });
1180
+ Object.defineProperty(exports, 'createSubSchema', {
1181
+ enumerable: true,
1182
+ get: function () { return mongooseConnection.createSubSchema; }
1183
+ });
1184
+ Object.defineProperty(exports, 'createVarySubSchema', {
1185
+ enumerable: true,
1186
+ get: function () { return mongooseConnection.createVarySubSchema; }
1187
+ });
1188
+ Object.defineProperty(exports, 'disableDebug', {
1189
+ enumerable: true,
1190
+ get: function () { return mongooseConnection.disableDebug; }
1191
+ });
1192
+ Object.defineProperty(exports, 'disconnect', {
1193
+ enumerable: true,
1194
+ get: function () { return mongooseConnection.disconnect; }
1195
+ });
1196
+ Object.defineProperty(exports, 'drop', {
1197
+ enumerable: true,
1198
+ get: function () { return mongooseConnection.drop; }
1199
+ });
1200
+ Object.defineProperty(exports, 'enableDebug', {
1201
+ enumerable: true,
1202
+ get: function () { return mongooseConnection.enableDebug; }
1203
+ });
1204
+ Object.defineProperty(exports, 'isAggregate', {
1205
+ enumerable: true,
1206
+ get: function () { return mongooseConnection.isAggregate; }
1207
+ });
1208
+ Object.defineProperty(exports, 'isConnected', {
1209
+ enumerable: true,
1210
+ get: function () { return mongooseConnection.isConnected; }
1211
+ });
1212
+ Object.defineProperty(exports, 'isConnection', {
1213
+ enumerable: true,
1214
+ get: function () { return mongooseConnection.isConnection; }
1215
+ });
1216
+ Object.defineProperty(exports, 'isModel', {
1217
+ enumerable: true,
1218
+ get: function () { return mongooseConnection.isModel; }
1219
+ });
1220
+ Object.defineProperty(exports, 'isQuery', {
1221
+ enumerable: true,
1222
+ get: function () { return mongooseConnection.isQuery; }
1223
+ });
1224
+ Object.defineProperty(exports, 'isSchema', {
1225
+ enumerable: true,
1226
+ get: function () { return mongooseConnection.isSchema; }
1227
+ });
1228
+ Object.defineProperty(exports, 'model', {
1229
+ enumerable: true,
1230
+ get: function () { return mongooseConnection.model; }
1231
+ });
1232
+ Object.defineProperty(exports, 'syncIndexes', {
1233
+ enumerable: true,
1234
+ get: function () { return mongooseConnection.syncIndexes; }
1235
+ });
1236
+ exports.Aggregate = Aggregate;
1237
+ exports.CastError = CastError;
1238
+ exports.Collection = Collection;
1239
+ exports.Connection = Connection;
1240
+ exports.Decimal128 = Decimal128;
1241
+ exports.DocumentArray = DocumentArray;
1242
+ exports.Embedded = Embedded;
1243
+ exports.GridFSBucket = GridFSBucket;
1244
+ exports.LOOKUP_FIELDS = LOOKUP_FIELDS;
1245
+ exports.Mixed = Mixed;
1246
+ exports.MongooseError = MongooseError;
1247
+ exports.MongooseTypes = MongooseTypes;
1248
+ exports.ObjectId = ObjectId;
1249
+ exports.Query = Query;
1250
+ exports.STATES = STATES;
1251
+ exports.Schema = Schema;
1252
+ exports.SchemaArray = SchemaArray;
1253
+ exports.SchemaBoolean = SchemaBoolean;
1254
+ exports.SchemaBuffer = SchemaBuffer;
1255
+ exports.SchemaDate = SchemaDate;
1256
+ exports.SchemaDecimal = SchemaDecimal;
1257
+ exports.SchemaDecimal128 = SchemaDecimal128;
1258
+ exports.SchemaDocumentArray = SchemaDocumentArray;
1259
+ exports.SchemaEmbedded = SchemaEmbedded;
1260
+ exports.SchemaMap = SchemaMap;
1261
+ exports.SchemaMixed = SchemaMixed;
1262
+ exports.SchemaNumber = SchemaNumber;
1263
+ exports.SchemaObjectId = SchemaObjectId;
1264
+ exports.SchemaString = SchemaString;
1265
+ exports.SchemaSubDocument = SchemaSubDocument;
1266
+ exports.SchemaType = SchemaType;
1267
+ exports.SchemaTypes = SchemaTypes;
1268
+ exports.SubDocument = SubDocument;
1269
+ exports.Types = Types;
1270
+ exports.VirtualType = VirtualType;
1271
+ exports.areSameInstance = areSameInstance;
1272
+ exports.areSameObjectId = areSameObjectId;
1273
+ exports.copyInstance = copyInstance;
1274
+ exports.eachPath = eachPath;
1275
+ exports.isArraySchemaType = isArraySchemaType;
1276
+ exports.isInstance = isInstance;
1277
+ exports.isMap = isMap;
1278
+ exports.isNumber = isNumber;
1279
+ exports.isNumberArray = isNumberArray;
1280
+ exports.isObjectId = isObjectId;
1281
+ exports.isString = isString;
1282
+ exports.isStringArray = isStringArray;
1283
+ exports.jsonSchema = jsonSchema;
1284
+ exports.modelNames = modelNames;
1285
+ exports.objectIdFor = objectIdFor;
1286
+ exports.schemaTypeOptionOf = schemaTypeOptionOf;
1287
+ exports.toCollectionName = toCollectionName;
1288
+ exports.toObjectIdStrings = toObjectIdStrings;
1289
+ exports.toObjectIds = toObjectIds;
1290
+ exports.validationErrorFor = validationErrorFor;