@lykmapipo/mongoose-common 0.38.3 → 0.40.0

Sign up to get free protection for your applications and to get access to all the features.
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;