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