mongoose 8.8.3 → 8.9.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/dist/browser.umd.js +1 -1
- package/index.js +1 -0
- package/lib/cast/double.js +50 -0
- package/lib/cast/int32.js +36 -0
- package/lib/connection.js +180 -0
- package/lib/document.js +12 -3
- package/lib/drivers/node-mongodb-native/connection.js +0 -4
- package/lib/helpers/clone.js +5 -0
- package/lib/helpers/model/castBulkWrite.js +218 -205
- package/lib/helpers/populate/getModelsMapForPopulate.js +7 -0
- package/lib/helpers/query/getEmbeddedDiscriminatorPath.js +7 -1
- package/lib/helpers/schema/getIndexes.js +5 -0
- package/lib/model.js +64 -99
- package/lib/mongoose.js +2 -0
- package/lib/plugins/saveSubdocs.js +4 -2
- package/lib/query.js +6 -2
- package/lib/queryHelpers.js +13 -32
- package/lib/schema/bigint.js +2 -2
- package/lib/schema/double.js +212 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/int32.js +249 -0
- package/lib/schema.js +35 -0
- package/lib/schemaType.js +36 -8
- package/lib/validOptions.js +1 -0
- package/package.json +7 -7
- package/types/expressions.d.ts +102 -0
- package/types/index.d.ts +1 -1
- package/types/indexes.d.ts +4 -2
- package/types/inferschematype.d.ts +29 -27
- package/types/populate.d.ts +2 -0
- package/types/schematypes.d.ts +34 -1
- package/types/types.d.ts +5 -3
package/index.js
CHANGED
|
@@ -46,6 +46,7 @@ module.exports.Decimal128 = mongoose.Decimal128;
|
|
|
46
46
|
module.exports.Mixed = mongoose.Mixed;
|
|
47
47
|
module.exports.Date = mongoose.Date;
|
|
48
48
|
module.exports.Number = mongoose.Number;
|
|
49
|
+
module.exports.Double = mongoose.Double;
|
|
49
50
|
module.exports.Error = mongoose.Error;
|
|
50
51
|
module.exports.MongooseError = mongoose.MongooseError;
|
|
51
52
|
module.exports.now = mongoose.now;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const BSON = require('bson');
|
|
5
|
+
const isBsonType = require('../helpers/isBsonType');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Given a value, cast it to a IEEE 754-2008 floating point, or throw an `Error` if the value
|
|
9
|
+
* cannot be casted. `null`, `undefined`, and `NaN` are considered valid inputs.
|
|
10
|
+
*
|
|
11
|
+
* @param {Any} value
|
|
12
|
+
* @return {Number}
|
|
13
|
+
* @throws {Error} if `value` does not represent a IEEE 754-2008 floating point. If casting from a string, see [BSON Double.fromString API documentation](https://mongodb.github.io/node-mongodb-native/Next/classes/BSON.Double.html#fromString)
|
|
14
|
+
* @api private
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
module.exports = function castDouble(val) {
|
|
18
|
+
if (val == null || val === '') {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let coercedVal;
|
|
23
|
+
if (isBsonType(val, 'Long')) {
|
|
24
|
+
coercedVal = val.toNumber();
|
|
25
|
+
} else if (typeof val === 'string') {
|
|
26
|
+
try {
|
|
27
|
+
coercedVal = BSON.Double.fromString(val);
|
|
28
|
+
return coercedVal;
|
|
29
|
+
} catch {
|
|
30
|
+
assert.ok(false);
|
|
31
|
+
}
|
|
32
|
+
} else if (typeof val === 'object') {
|
|
33
|
+
const tempVal = val.valueOf() ?? val.toString();
|
|
34
|
+
// ex: { a: 'im an object, valueOf: () => 'helloworld' } // throw an error
|
|
35
|
+
if (typeof tempVal === 'string') {
|
|
36
|
+
try {
|
|
37
|
+
coercedVal = BSON.Double.fromString(val);
|
|
38
|
+
return coercedVal;
|
|
39
|
+
} catch {
|
|
40
|
+
assert.ok(false);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
coercedVal = Number(tempVal);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
coercedVal = Number(val);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new BSON.Double(coercedVal);
|
|
50
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const isBsonType = require('../helpers/isBsonType');
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Given a value, cast it to a Int32, or throw an `Error` if the value
|
|
8
|
+
* cannot be casted. `null` and `undefined` are considered valid.
|
|
9
|
+
*
|
|
10
|
+
* @param {Any} value
|
|
11
|
+
* @return {Number}
|
|
12
|
+
* @throws {Error} if `value` does not represent an integer, or is outside the bounds of an 32-bit integer.
|
|
13
|
+
* @api private
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
module.exports = function castInt32(val) {
|
|
17
|
+
if (val == null) {
|
|
18
|
+
return val;
|
|
19
|
+
}
|
|
20
|
+
if (val === '') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const coercedVal = isBsonType(val, 'Long') ? val.toNumber() : Number(val);
|
|
25
|
+
|
|
26
|
+
const INT32_MAX = 0x7FFFFFFF;
|
|
27
|
+
const INT32_MIN = -0x80000000;
|
|
28
|
+
|
|
29
|
+
if (coercedVal === (coercedVal | 0) &&
|
|
30
|
+
coercedVal >= INT32_MIN &&
|
|
31
|
+
coercedVal <= INT32_MAX
|
|
32
|
+
) {
|
|
33
|
+
return coercedVal;
|
|
34
|
+
}
|
|
35
|
+
assert.ok(false);
|
|
36
|
+
};
|
package/lib/connection.js
CHANGED
|
@@ -8,6 +8,7 @@ const ChangeStream = require('./cursor/changeStream');
|
|
|
8
8
|
const EventEmitter = require('events').EventEmitter;
|
|
9
9
|
const Schema = require('./schema');
|
|
10
10
|
const STATES = require('./connectionState');
|
|
11
|
+
const MongooseBulkWriteError = require('./error/bulkWriteError');
|
|
11
12
|
const MongooseError = require('./error/index');
|
|
12
13
|
const ServerSelectionError = require('./error/serverSelection');
|
|
13
14
|
const SyncIndexesError = require('./error/syncIndexes');
|
|
@@ -15,9 +16,13 @@ const applyPlugins = require('./helpers/schema/applyPlugins');
|
|
|
15
16
|
const clone = require('./helpers/clone');
|
|
16
17
|
const driver = require('./driver');
|
|
17
18
|
const get = require('./helpers/get');
|
|
19
|
+
const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
|
|
18
20
|
const immediate = require('./helpers/immediate');
|
|
19
21
|
const utils = require('./utils');
|
|
20
22
|
const CreateCollectionsError = require('./error/createCollectionsError');
|
|
23
|
+
const castBulkWrite = require('./helpers/model/castBulkWrite');
|
|
24
|
+
const { modelSymbol } = require('./helpers/symbols');
|
|
25
|
+
const isPromise = require('./helpers/isPromise');
|
|
21
26
|
|
|
22
27
|
const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
|
|
23
28
|
const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
|
|
@@ -111,6 +116,9 @@ Object.defineProperty(Connection.prototype, 'readyState', {
|
|
|
111
116
|
if (
|
|
112
117
|
this._readyState === STATES.connected &&
|
|
113
118
|
this._lastHeartbeatAt != null &&
|
|
119
|
+
// LoadBalanced topology (behind haproxy, including Atlas serverless instances) don't use heartbeats,
|
|
120
|
+
// so we can't use this check in that case.
|
|
121
|
+
this.client?.topology?.s?.description?.type !== 'LoadBalanced' &&
|
|
114
122
|
typeof this.client?.topology?.s?.description?.heartbeatFrequencyMS === 'number' &&
|
|
115
123
|
Date.now() - this._lastHeartbeatAt >= this.client.topology.s.description.heartbeatFrequencyMS * 2) {
|
|
116
124
|
return STATES.disconnected;
|
|
@@ -416,6 +424,178 @@ Connection.prototype.createCollection = async function createCollection(collecti
|
|
|
416
424
|
return this.db.createCollection(collection, options);
|
|
417
425
|
};
|
|
418
426
|
|
|
427
|
+
/**
|
|
428
|
+
* _Requires MongoDB Server 8.0 or greater_. Executes bulk write operations across multiple models in a single operation.
|
|
429
|
+
* You must specify the `model` for each operation: Mongoose will use `model` for casting and validation, as well as
|
|
430
|
+
* determining which collection to apply the operation to.
|
|
431
|
+
*
|
|
432
|
+
* #### Example:
|
|
433
|
+
* const Test = mongoose.model('Test', new Schema({ name: String }));
|
|
434
|
+
*
|
|
435
|
+
* await db.bulkWrite([
|
|
436
|
+
* { model: Test, name: 'insertOne', document: { name: 'test1' } }, // Can specify model as a Model class...
|
|
437
|
+
* { model: 'Test', name: 'insertOne', document: { name: 'test2' } } // or as a model name
|
|
438
|
+
* ], { ordered: false });
|
|
439
|
+
*
|
|
440
|
+
* @method bulkWrite
|
|
441
|
+
* @param {Array} ops
|
|
442
|
+
* @param {Object} [options]
|
|
443
|
+
* @param {Boolean} [options.ordered] If false, perform unordered operations. If true, perform ordered operations.
|
|
444
|
+
* @param {Session} [options.session] The session to use for the operation.
|
|
445
|
+
* @return {Promise}
|
|
446
|
+
* @see MongoDB https://www.mongodb.com/docs/manual/reference/command/bulkWrite/#mongodb-dbcommand-dbcmd.bulkWrite
|
|
447
|
+
* @api public
|
|
448
|
+
*/
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
Connection.prototype.bulkWrite = async function bulkWrite(ops, options) {
|
|
452
|
+
await this._waitForConnect();
|
|
453
|
+
options = options || {};
|
|
454
|
+
|
|
455
|
+
const ordered = options.ordered == null ? true : options.ordered;
|
|
456
|
+
const asyncLocalStorage = this.base.transactionAsyncLocalStorage?.getStore();
|
|
457
|
+
if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) {
|
|
458
|
+
options = { ...options, session: asyncLocalStorage.session };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const now = this.base.now();
|
|
462
|
+
|
|
463
|
+
let res = null;
|
|
464
|
+
if (ordered) {
|
|
465
|
+
const opsToSend = [];
|
|
466
|
+
for (const op of ops) {
|
|
467
|
+
if (typeof op.model !== 'string' && !op.model?.[modelSymbol]) {
|
|
468
|
+
throw new MongooseError('Must specify model in Connection.prototype.bulkWrite() operations');
|
|
469
|
+
}
|
|
470
|
+
const Model = op.model[modelSymbol] ? op.model : this.model(op.model);
|
|
471
|
+
|
|
472
|
+
if (op.name == null) {
|
|
473
|
+
throw new MongooseError('Must specify operation name in Connection.prototype.bulkWrite()');
|
|
474
|
+
}
|
|
475
|
+
if (!castBulkWrite.cast.hasOwnProperty(op.name)) {
|
|
476
|
+
throw new MongooseError(`Unrecognized bulkWrite() operation name ${op.name}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
await castBulkWrite.cast[op.name](Model, op, options, now);
|
|
480
|
+
opsToSend.push({ ...op, namespace: Model.namespace() });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
res = await this.client.bulkWrite(opsToSend, options);
|
|
484
|
+
} else {
|
|
485
|
+
const validOps = [];
|
|
486
|
+
const validOpIndexes = [];
|
|
487
|
+
let validationErrors = [];
|
|
488
|
+
const asyncValidations = [];
|
|
489
|
+
const results = [];
|
|
490
|
+
for (let i = 0; i < ops.length; ++i) {
|
|
491
|
+
const op = ops[i];
|
|
492
|
+
if (typeof op.model !== 'string' && !op.model?.[modelSymbol]) {
|
|
493
|
+
const error = new MongooseError('Must specify model in Connection.prototype.bulkWrite() operations');
|
|
494
|
+
validationErrors.push({ index: i, error: error });
|
|
495
|
+
results[i] = error;
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
let Model;
|
|
499
|
+
try {
|
|
500
|
+
Model = op.model[modelSymbol] ? op.model : this.model(op.model);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
validationErrors.push({ index: i, error: error });
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (op.name == null) {
|
|
506
|
+
const error = new MongooseError('Must specify operation name in Connection.prototype.bulkWrite()');
|
|
507
|
+
validationErrors.push({ index: i, error: error });
|
|
508
|
+
results[i] = error;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
if (!castBulkWrite.cast.hasOwnProperty(op.name)) {
|
|
512
|
+
const error = new MongooseError(`Unrecognized bulkWrite() operation name ${op.name}`);
|
|
513
|
+
validationErrors.push({ index: i, error: error });
|
|
514
|
+
results[i] = error;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
let maybePromise = null;
|
|
519
|
+
try {
|
|
520
|
+
maybePromise = castBulkWrite.cast[op.name](Model, op, options, now);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
validationErrors.push({ index: i, error: error });
|
|
523
|
+
results[i] = error;
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
if (isPromise(maybePromise)) {
|
|
527
|
+
asyncValidations.push(
|
|
528
|
+
maybePromise.then(
|
|
529
|
+
() => {
|
|
530
|
+
validOps.push({ ...op, namespace: Model.namespace() });
|
|
531
|
+
validOpIndexes.push(i);
|
|
532
|
+
},
|
|
533
|
+
error => {
|
|
534
|
+
validationErrors.push({ index: i, error: error });
|
|
535
|
+
results[i] = error;
|
|
536
|
+
}
|
|
537
|
+
)
|
|
538
|
+
);
|
|
539
|
+
} else {
|
|
540
|
+
validOps.push({ ...op, namespace: Model.namespace() });
|
|
541
|
+
validOpIndexes.push(i);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (asyncValidations.length > 0) {
|
|
546
|
+
await Promise.all(asyncValidations);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
validationErrors = validationErrors.
|
|
550
|
+
sort((v1, v2) => v1.index - v2.index).
|
|
551
|
+
map(v => v.error);
|
|
552
|
+
|
|
553
|
+
if (validOps.length === 0) {
|
|
554
|
+
if (options.throwOnValidationError && validationErrors.length) {
|
|
555
|
+
throw new MongooseBulkWriteError(
|
|
556
|
+
validationErrors,
|
|
557
|
+
results,
|
|
558
|
+
res,
|
|
559
|
+
'bulkWrite'
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
return getDefaultBulkwriteResult();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let error;
|
|
566
|
+
[res, error] = await this.client.bulkWrite(validOps, options).
|
|
567
|
+
then(res => ([res, null])).
|
|
568
|
+
catch(err => ([null, err]));
|
|
569
|
+
|
|
570
|
+
if (error) {
|
|
571
|
+
if (validationErrors.length > 0) {
|
|
572
|
+
error.mongoose = error.mongoose || {};
|
|
573
|
+
error.mongoose.validationErrors = validationErrors;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
for (let i = 0; i < validOpIndexes.length; ++i) {
|
|
578
|
+
results[validOpIndexes[i]] = null;
|
|
579
|
+
}
|
|
580
|
+
if (validationErrors.length > 0) {
|
|
581
|
+
if (options.throwOnValidationError) {
|
|
582
|
+
throw new MongooseBulkWriteError(
|
|
583
|
+
validationErrors,
|
|
584
|
+
results,
|
|
585
|
+
res,
|
|
586
|
+
'bulkWrite'
|
|
587
|
+
);
|
|
588
|
+
} else {
|
|
589
|
+
res.mongoose = res.mongoose || {};
|
|
590
|
+
res.mongoose.validationErrors = validationErrors;
|
|
591
|
+
res.mongoose.results = results;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return res;
|
|
597
|
+
};
|
|
598
|
+
|
|
419
599
|
/**
|
|
420
600
|
* Calls `createCollection()` on a models in a series.
|
|
421
601
|
*
|
package/lib/document.js
CHANGED
|
@@ -624,16 +624,17 @@ Document.prototype.toBSON = function() {
|
|
|
624
624
|
};
|
|
625
625
|
|
|
626
626
|
/**
|
|
627
|
-
*
|
|
627
|
+
* Hydrates this document with the data in `doc`. Does not run setters or mark any paths modified.
|
|
628
628
|
*
|
|
629
|
-
* Called internally after a document is returned from
|
|
629
|
+
* Called internally after a document is returned from MongoDB. Normally,
|
|
630
630
|
* you do **not** need to call this function on your own.
|
|
631
631
|
*
|
|
632
632
|
* This function triggers `init` [middleware](https://mongoosejs.com/docs/middleware.html).
|
|
633
633
|
* Note that `init` hooks are [synchronous](https://mongoosejs.com/docs/middleware.html#synchronous).
|
|
634
634
|
*
|
|
635
|
-
* @param {Object} doc document returned by mongo
|
|
635
|
+
* @param {Object} doc raw document returned by mongo
|
|
636
636
|
* @param {Object} [opts]
|
|
637
|
+
* @param {Boolean} [opts.hydratedPopulatedDocs=false] If true, hydrate and mark as populated any paths that are populated in the raw document
|
|
637
638
|
* @param {Function} [fn]
|
|
638
639
|
* @api public
|
|
639
640
|
* @memberOf Document
|
|
@@ -805,6 +806,14 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
805
806
|
reason: e
|
|
806
807
|
}));
|
|
807
808
|
}
|
|
809
|
+
} else if (opts.hydratedPopulatedDocs) {
|
|
810
|
+
doc[i] = schemaType.cast(value, self, true);
|
|
811
|
+
|
|
812
|
+
if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
|
|
813
|
+
self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
|
|
814
|
+
} else if (Array.isArray(doc[i]) && doc[i].length && doc[i][0]?.$__?.wasPopulated) {
|
|
815
|
+
self.$populated(path, doc[i].map(populatedDoc => populatedDoc?.$__?.wasPopulated?.value).filter(val => val != null), doc[i][0].$__.wasPopulated.options);
|
|
816
|
+
}
|
|
808
817
|
} else {
|
|
809
818
|
doc[i] = value;
|
|
810
819
|
}
|
|
@@ -426,9 +426,6 @@ function _setClient(conn, client, options, dbName) {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
conn.onOpen();
|
|
429
|
-
if (client.topology?.s?.state === 'connected') {
|
|
430
|
-
conn._lastHeartbeatAt = Date.now();
|
|
431
|
-
}
|
|
432
429
|
|
|
433
430
|
for (const i in conn.collections) {
|
|
434
431
|
if (utils.object.hasOwnProperty(conn.collections, i)) {
|
|
@@ -437,7 +434,6 @@ function _setClient(conn, client, options, dbName) {
|
|
|
437
434
|
}
|
|
438
435
|
}
|
|
439
436
|
|
|
440
|
-
|
|
441
437
|
/*!
|
|
442
438
|
* Module exports.
|
|
443
439
|
*/
|
package/lib/helpers/clone.js
CHANGED
|
@@ -11,6 +11,7 @@ const isObject = require('./isObject');
|
|
|
11
11
|
const isPOJO = require('./isPOJO');
|
|
12
12
|
const symbols = require('./symbols');
|
|
13
13
|
const trustedSymbol = require('./query/trusted').trustedSymbol;
|
|
14
|
+
const BSON = require('bson');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Object clone with Mongoose natives support.
|
|
@@ -30,6 +31,10 @@ function clone(obj, options, isArrayChild) {
|
|
|
30
31
|
if (obj == null) {
|
|
31
32
|
return obj;
|
|
32
33
|
}
|
|
34
|
+
|
|
35
|
+
if (isBsonType(obj, 'Double')) {
|
|
36
|
+
return new BSON.Double(obj.value);
|
|
37
|
+
}
|
|
33
38
|
if (typeof obj === 'number' || typeof obj === 'string' || typeof obj === 'boolean' || typeof obj === 'bigint') {
|
|
34
39
|
return obj;
|
|
35
40
|
}
|