mongoose 8.9.7 → 8.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.umd.js +1 -1
- package/lib/aggregate.js +21 -4
- package/lib/collection.js +2 -8
- package/lib/connection.js +72 -3
- package/lib/cursor/aggregationCursor.js +7 -1
- package/lib/document.js +14 -16
- package/lib/drivers/node-mongodb-native/connection.js +19 -0
- package/lib/helpers/createJSONSchemaTypeDefinition.js +24 -0
- package/lib/helpers/query/castUpdate.js +10 -0
- package/lib/model.js +159 -36
- package/lib/query.js +41 -23
- package/lib/schema/array.js +21 -0
- package/lib/schema/bigint.js +14 -0
- package/lib/schema/boolean.js +14 -0
- package/lib/schema/buffer.js +16 -2
- package/lib/schema/date.js +14 -0
- package/lib/schema/decimal128.js +16 -2
- package/lib/schema/documentArray.js +18 -0
- package/lib/schema/double.js +13 -0
- package/lib/schema/int32.js +14 -0
- package/lib/schema/map.js +28 -0
- package/lib/schema/number.js +16 -2
- package/lib/schema/objectId.js +16 -2
- package/lib/schema/string.js +16 -2
- package/lib/schema/subdocument.js +17 -0
- package/lib/schema/uuid.js +16 -2
- package/lib/schema.js +100 -7
- package/lib/schemaType.js +14 -2
- package/package.json +6 -2
- package/types/connection.d.ts +2 -0
- package/types/document.d.ts +13 -13
- package/types/index.d.ts +20 -18
- package/types/models.d.ts +21 -4
- package/types/query.d.ts +10 -4
- package/types/schematypes.d.ts +14 -12
- package/types/validation.d.ts +6 -1
package/lib/aggregate.js
CHANGED
|
@@ -13,6 +13,7 @@ const getConstructorName = require('./helpers/getConstructorName');
|
|
|
13
13
|
const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
|
|
14
14
|
const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
|
|
15
15
|
const utils = require('./utils');
|
|
16
|
+
const { modelSymbol } = require('./helpers/symbols');
|
|
16
17
|
const read = Query.prototype.read;
|
|
17
18
|
const readConcern = Query.prototype.readConcern;
|
|
18
19
|
|
|
@@ -46,13 +47,17 @@ const validRedactStringValues = new Set(['$$DESCEND', '$$PRUNE', '$$KEEP']);
|
|
|
46
47
|
* @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
|
|
47
48
|
* @see driver https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#aggregate
|
|
48
49
|
* @param {Array} [pipeline] aggregation pipeline as an array of objects
|
|
49
|
-
* @param {Model} [
|
|
50
|
+
* @param {Model|Connection} [modelOrConn] the model or connection to use with this aggregate.
|
|
50
51
|
* @api public
|
|
51
52
|
*/
|
|
52
53
|
|
|
53
|
-
function Aggregate(pipeline,
|
|
54
|
+
function Aggregate(pipeline, modelOrConn) {
|
|
54
55
|
this._pipeline = [];
|
|
55
|
-
|
|
56
|
+
if (modelOrConn == null || modelOrConn[modelSymbol]) {
|
|
57
|
+
this._model = modelOrConn;
|
|
58
|
+
} else {
|
|
59
|
+
this._connection = modelOrConn;
|
|
60
|
+
}
|
|
56
61
|
this.options = {};
|
|
57
62
|
|
|
58
63
|
if (arguments.length === 1 && Array.isArray(pipeline)) {
|
|
@@ -1041,12 +1046,24 @@ Aggregate.prototype.pipeline = function() {
|
|
|
1041
1046
|
*/
|
|
1042
1047
|
|
|
1043
1048
|
Aggregate.prototype.exec = async function exec() {
|
|
1044
|
-
if (!this._model) {
|
|
1049
|
+
if (!this._model && !this._connection) {
|
|
1045
1050
|
throw new Error('Aggregate not bound to any Model');
|
|
1046
1051
|
}
|
|
1047
1052
|
if (typeof arguments[0] === 'function') {
|
|
1048
1053
|
throw new MongooseError('Aggregate.prototype.exec() no longer accepts a callback');
|
|
1049
1054
|
}
|
|
1055
|
+
|
|
1056
|
+
if (this._connection) {
|
|
1057
|
+
if (!this._pipeline.length) {
|
|
1058
|
+
throw new MongooseError('Aggregate has empty pipeline');
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
this._optionsForExec();
|
|
1062
|
+
|
|
1063
|
+
const cursor = await this._connection.client.db().aggregate(this._pipeline, this.options);
|
|
1064
|
+
return await cursor.toArray();
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1050
1067
|
const model = this._model;
|
|
1051
1068
|
const collection = this._model.collection;
|
|
1052
1069
|
|
package/lib/collection.js
CHANGED
|
@@ -29,7 +29,7 @@ function Collection(name, conn, opts) {
|
|
|
29
29
|
this.collectionName = name;
|
|
30
30
|
this.conn = conn;
|
|
31
31
|
this.queue = [];
|
|
32
|
-
this.buffer =
|
|
32
|
+
this.buffer = !conn?._hasOpened;
|
|
33
33
|
this.emitter = new EventEmitter();
|
|
34
34
|
|
|
35
35
|
if (STATES.connected === this.conn.readyState) {
|
|
@@ -311,13 +311,7 @@ Collection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
|
|
|
311
311
|
if (opts && opts.schemaUserProvidedOptions != null && opts.schemaUserProvidedOptions.bufferTimeoutMS != null) {
|
|
312
312
|
return opts.schemaUserProvidedOptions.bufferTimeoutMS;
|
|
313
313
|
}
|
|
314
|
-
|
|
315
|
-
return conn.config.bufferTimeoutMS;
|
|
316
|
-
}
|
|
317
|
-
if (conn.base != null && conn.base.get('bufferTimeoutMS') != null) {
|
|
318
|
-
return conn.base.get('bufferTimeoutMS');
|
|
319
|
-
}
|
|
320
|
-
return 10000;
|
|
314
|
+
return conn._getBufferTimeoutMS();
|
|
321
315
|
};
|
|
322
316
|
|
|
323
317
|
/*!
|
package/lib/connection.js
CHANGED
|
@@ -818,18 +818,71 @@ Connection.prototype.dropCollection = async function dropCollection(collection)
|
|
|
818
818
|
/**
|
|
819
819
|
* Waits for connection to be established, so the connection has a `client`
|
|
820
820
|
*
|
|
821
|
+
* @param {Boolean} [noTimeout=false] if set, don't put a timeout on the operation. Used internally so `mongoose.model()` doesn't leave open handles.
|
|
821
822
|
* @return Promise
|
|
822
823
|
* @api private
|
|
823
824
|
*/
|
|
824
825
|
|
|
825
|
-
Connection.prototype._waitForConnect = async function _waitForConnect() {
|
|
826
|
+
Connection.prototype._waitForConnect = async function _waitForConnect(noTimeout) {
|
|
826
827
|
if ((this.readyState === STATES.connecting || this.readyState === STATES.disconnected) && this._shouldBufferCommands()) {
|
|
827
|
-
|
|
828
|
-
|
|
828
|
+
const bufferTimeoutMS = this._getBufferTimeoutMS();
|
|
829
|
+
let timeout = null;
|
|
830
|
+
let timedOut = false;
|
|
831
|
+
// The element that this function pushes onto `_queue`, stored to make it easy to remove later
|
|
832
|
+
const queueElement = {};
|
|
833
|
+
|
|
834
|
+
// Mongoose executes all elements in `_queue` when initial connection succeeds in `onOpen()`.
|
|
835
|
+
const waitForConnectPromise = new Promise(resolve => {
|
|
836
|
+
queueElement.fn = resolve;
|
|
837
|
+
this._queue.push(queueElement);
|
|
829
838
|
});
|
|
839
|
+
|
|
840
|
+
if (noTimeout) {
|
|
841
|
+
await waitForConnectPromise;
|
|
842
|
+
} else {
|
|
843
|
+
await Promise.race([
|
|
844
|
+
waitForConnectPromise,
|
|
845
|
+
new Promise(resolve => {
|
|
846
|
+
timeout = setTimeout(
|
|
847
|
+
() => {
|
|
848
|
+
timedOut = true;
|
|
849
|
+
resolve();
|
|
850
|
+
},
|
|
851
|
+
bufferTimeoutMS
|
|
852
|
+
);
|
|
853
|
+
})
|
|
854
|
+
]);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (timedOut) {
|
|
858
|
+
const index = this._queue.indexOf(queueElement);
|
|
859
|
+
if (index !== -1) {
|
|
860
|
+
this._queue.splice(index, 1);
|
|
861
|
+
}
|
|
862
|
+
const message = 'Connection operation buffering timed out after ' + bufferTimeoutMS + 'ms';
|
|
863
|
+
throw new MongooseError(message);
|
|
864
|
+
} else if (timeout != null) {
|
|
865
|
+
// Not strictly necessary, but avoid the extra overhead of creating a new MongooseError
|
|
866
|
+
// in case of success
|
|
867
|
+
clearTimeout(timeout);
|
|
868
|
+
}
|
|
830
869
|
}
|
|
831
870
|
};
|
|
832
871
|
|
|
872
|
+
/*!
|
|
873
|
+
* Get the default buffer timeout for this connection
|
|
874
|
+
*/
|
|
875
|
+
|
|
876
|
+
Connection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
|
|
877
|
+
if (this.config.bufferTimeoutMS != null) {
|
|
878
|
+
return this.config.bufferTimeoutMS;
|
|
879
|
+
}
|
|
880
|
+
if (this.base != null && this.base.get('bufferTimeoutMS') != null) {
|
|
881
|
+
return this.base.get('bufferTimeoutMS');
|
|
882
|
+
}
|
|
883
|
+
return 10000;
|
|
884
|
+
};
|
|
885
|
+
|
|
833
886
|
/**
|
|
834
887
|
* Helper for MongoDB Node driver's `listCollections()`.
|
|
835
888
|
* Returns an array of collection objects.
|
|
@@ -1156,6 +1209,10 @@ Connection.prototype.close = async function close(force) {
|
|
|
1156
1209
|
this.$wasForceClosed = !!force;
|
|
1157
1210
|
}
|
|
1158
1211
|
|
|
1212
|
+
if (this._lastHeartbeatAt != null) {
|
|
1213
|
+
this._lastHeartbeatAt = null;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1159
1216
|
for (const model of Object.values(this.models)) {
|
|
1160
1217
|
// If manually disconnecting, make sure to clear each model's `$init`
|
|
1161
1218
|
// promise, so Mongoose knows to re-run `init()` in case the
|
|
@@ -1742,6 +1799,18 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
|
|
|
1742
1799
|
* @api public
|
|
1743
1800
|
*/
|
|
1744
1801
|
|
|
1802
|
+
/**
|
|
1803
|
+
* Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
|
|
1804
|
+
*
|
|
1805
|
+
* @method aggregate
|
|
1806
|
+
* @memberOf Connection
|
|
1807
|
+
* @param {Array} pipeline
|
|
1808
|
+
* @param {Object} [options]
|
|
1809
|
+
* @param {Boolean} [options.cursor=false] If true, make the Aggregate resolve to a Mongoose AggregationCursor rather than an array
|
|
1810
|
+
* @return {Aggregate} Aggregation wrapper
|
|
1811
|
+
* @api public
|
|
1812
|
+
*/
|
|
1813
|
+
|
|
1745
1814
|
/**
|
|
1746
1815
|
* Removes the database connection with the given name created with with `useDb()`.
|
|
1747
1816
|
*
|
|
@@ -41,11 +41,17 @@ function AggregationCursor(agg) {
|
|
|
41
41
|
this.cursor = null;
|
|
42
42
|
this.agg = agg;
|
|
43
43
|
this._transforms = [];
|
|
44
|
+
const connection = agg._connection;
|
|
44
45
|
const model = agg._model;
|
|
45
46
|
delete agg.options.cursor.useMongooseAggCursor;
|
|
46
47
|
this._mongooseOptions = {};
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
if (connection) {
|
|
50
|
+
this.cursor = connection.db.aggregate(agg._pipeline, agg.options || {});
|
|
51
|
+
setImmediate(() => this.emit('cursor', this.cursor));
|
|
52
|
+
} else {
|
|
53
|
+
_init(model, this, agg);
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
util.inherits(AggregationCursor, Readable);
|
package/lib/document.js
CHANGED
|
@@ -741,15 +741,10 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
741
741
|
let schemaType;
|
|
742
742
|
let path;
|
|
743
743
|
let i;
|
|
744
|
-
let index = 0;
|
|
745
744
|
const strict = self.$__.strictMode;
|
|
746
745
|
const docSchema = self.$__schema;
|
|
747
746
|
|
|
748
|
-
|
|
749
|
-
_init(index++);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function _init(index) {
|
|
747
|
+
for (let index = 0; index < len; ++index) {
|
|
753
748
|
i = keys[index];
|
|
754
749
|
// avoid prototype pollution
|
|
755
750
|
if (i === '__proto__' || i === 'constructor') {
|
|
@@ -807,8 +802,8 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
807
802
|
reason: e
|
|
808
803
|
}));
|
|
809
804
|
}
|
|
810
|
-
} else if (opts.hydratedPopulatedDocs) {
|
|
811
|
-
doc[i] = schemaType.cast(value, self, true);
|
|
805
|
+
} else if (schemaType && opts.hydratedPopulatedDocs) {
|
|
806
|
+
doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
|
|
812
807
|
|
|
813
808
|
if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
|
|
814
809
|
self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
|
|
@@ -3558,8 +3553,10 @@ Document.prototype.$__undoReset = function $__undoReset() {
|
|
|
3558
3553
|
}
|
|
3559
3554
|
}
|
|
3560
3555
|
|
|
3561
|
-
|
|
3562
|
-
subdoc.$
|
|
3556
|
+
if (!this.$isSubdocument) {
|
|
3557
|
+
for (const subdoc of this.$getAllSubdocs()) {
|
|
3558
|
+
subdoc.$__undoReset();
|
|
3559
|
+
}
|
|
3563
3560
|
}
|
|
3564
3561
|
};
|
|
3565
3562
|
|
|
@@ -4256,24 +4253,25 @@ function applySchemaTypeTransforms(self, json) {
|
|
|
4256
4253
|
|
|
4257
4254
|
for (const path of paths) {
|
|
4258
4255
|
const schematype = schema.paths[path];
|
|
4259
|
-
|
|
4256
|
+
const topLevelTransformFunction = schematype.options.transform ?? schematype.constructor?.defaultOptions?.transform;
|
|
4257
|
+
const embeddedSchemaTypeTransformFunction = schematype.$embeddedSchemaType?.options?.transform
|
|
4258
|
+
?? schematype.$embeddedSchemaType?.constructor?.defaultOptions?.transform;
|
|
4259
|
+
if (typeof topLevelTransformFunction === 'function') {
|
|
4260
4260
|
const val = self.$get(path);
|
|
4261
4261
|
if (val === undefined) {
|
|
4262
4262
|
continue;
|
|
4263
4263
|
}
|
|
4264
|
-
const transformedValue =
|
|
4264
|
+
const transformedValue = topLevelTransformFunction.call(self, val);
|
|
4265
4265
|
throwErrorIfPromise(path, transformedValue);
|
|
4266
4266
|
utils.setValue(path, transformedValue, json);
|
|
4267
|
-
} else if (
|
|
4268
|
-
typeof schematype.$embeddedSchemaType.options.transform === 'function') {
|
|
4267
|
+
} else if (typeof embeddedSchemaTypeTransformFunction === 'function') {
|
|
4269
4268
|
const val = self.$get(path);
|
|
4270
4269
|
if (val === undefined) {
|
|
4271
4270
|
continue;
|
|
4272
4271
|
}
|
|
4273
4272
|
const vals = [].concat(val);
|
|
4274
|
-
const transform = schematype.$embeddedSchemaType.options.transform;
|
|
4275
4273
|
for (let i = 0; i < vals.length; ++i) {
|
|
4276
|
-
const transformedValue =
|
|
4274
|
+
const transformedValue = embeddedSchemaTypeTransformFunction.call(self, vals[i]);
|
|
4277
4275
|
vals[i] = transformedValue;
|
|
4278
4276
|
throwErrorIfPromise(path, transformedValue);
|
|
4279
4277
|
}
|
|
@@ -132,6 +132,17 @@ NativeConnection.prototype.useDb = function(name, options) {
|
|
|
132
132
|
return newConn;
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
|
|
137
|
+
*
|
|
138
|
+
* @param {Array} pipeline
|
|
139
|
+
* @param {Object} [options]
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
NativeConnection.prototype.aggregate = function aggregate(pipeline, options) {
|
|
143
|
+
return new this.base.Aggregate(null, this).append(pipeline).option(options ?? {});
|
|
144
|
+
};
|
|
145
|
+
|
|
135
146
|
/**
|
|
136
147
|
* Removes the database connection with the given name created with `useDb()`.
|
|
137
148
|
*
|
|
@@ -270,6 +281,11 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio
|
|
|
270
281
|
delete options.autoSearchIndex;
|
|
271
282
|
}
|
|
272
283
|
|
|
284
|
+
if ('bufferTimeoutMS' in options) {
|
|
285
|
+
this.config.bufferTimeoutMS = options.bufferTimeoutMS;
|
|
286
|
+
delete options.bufferTimeoutMS;
|
|
287
|
+
}
|
|
288
|
+
|
|
273
289
|
// Backwards compat
|
|
274
290
|
if (options.user || options.pass) {
|
|
275
291
|
options.auth = options.auth || {};
|
|
@@ -415,6 +431,9 @@ function _setClient(conn, client, options, dbName) {
|
|
|
415
431
|
}
|
|
416
432
|
});
|
|
417
433
|
}
|
|
434
|
+
|
|
435
|
+
conn._lastHeartbeatAt = null;
|
|
436
|
+
|
|
418
437
|
client.on('serverHeartbeatSucceeded', () => {
|
|
419
438
|
conn._lastHeartbeatAt = Date.now();
|
|
420
439
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handles creating `{ type: 'object' }` vs `{ bsonType: 'object' }` vs `{ bsonType: ['object', 'null'] }`
|
|
5
|
+
*
|
|
6
|
+
* @param {String} type
|
|
7
|
+
* @param {String} bsonType
|
|
8
|
+
* @param {Boolean} useBsonType
|
|
9
|
+
* @param {Boolean} isRequired
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
module.exports = function createJSONSchemaTypeArray(type, bsonType, useBsonType, isRequired) {
|
|
13
|
+
if (useBsonType) {
|
|
14
|
+
if (isRequired) {
|
|
15
|
+
return { bsonType };
|
|
16
|
+
}
|
|
17
|
+
return { bsonType: [bsonType, 'null'] };
|
|
18
|
+
} else {
|
|
19
|
+
if (isRequired) {
|
|
20
|
+
return { type };
|
|
21
|
+
}
|
|
22
|
+
return { type: [type, 'null'] };
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -82,6 +82,16 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
|
|
|
82
82
|
schema = schema.discriminators[discriminatorValue] ||
|
|
83
83
|
(byValue && byValue.schema) ||
|
|
84
84
|
schema;
|
|
85
|
+
} else if (schema != null &&
|
|
86
|
+
options.overwriteDiscriminatorKey &&
|
|
87
|
+
obj.$set != null &&
|
|
88
|
+
utils.hasUserDefinedProperty(obj.$set, schema.options.discriminatorKey) &&
|
|
89
|
+
schema.discriminators != null) {
|
|
90
|
+
const discriminatorValue = obj.$set[schema.options.discriminatorKey];
|
|
91
|
+
const byValue = getDiscriminatorByValue(context.model.discriminators, discriminatorValue);
|
|
92
|
+
schema = schema.discriminators[discriminatorValue] ||
|
|
93
|
+
(byValue && byValue.schema) ||
|
|
94
|
+
schema;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
if (options.upsert) {
|
package/lib/model.js
CHANGED
|
@@ -64,7 +64,6 @@ const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscrim
|
|
|
64
64
|
const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
|
|
65
65
|
const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
|
|
66
66
|
const setDottedPath = require('./helpers/path/setDottedPath');
|
|
67
|
-
const STATES = require('./connectionState');
|
|
68
67
|
const util = require('util');
|
|
69
68
|
const utils = require('./utils');
|
|
70
69
|
const minimize = require('./helpers/minimize');
|
|
@@ -151,6 +150,62 @@ Model.prototype.$isMongooseModelPrototype = true;
|
|
|
151
150
|
|
|
152
151
|
Model.prototype.db;
|
|
153
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Changes the Connection instance this model uses to make requests to MongoDB.
|
|
155
|
+
* This function is most useful for changing the Connection that a Model defined using `mongoose.model()` uses
|
|
156
|
+
* after initialization.
|
|
157
|
+
*
|
|
158
|
+
* #### Example:
|
|
159
|
+
*
|
|
160
|
+
* await mongoose.connect('mongodb://127.0.0.1:27017/db1');
|
|
161
|
+
* const UserModel = mongoose.model('User', mongoose.Schema({ name: String }));
|
|
162
|
+
* UserModel.connection === mongoose.connection; // true
|
|
163
|
+
*
|
|
164
|
+
* const conn2 = await mongoose.createConnection('mongodb://127.0.0.1:27017/db2').asPromise();
|
|
165
|
+
* UserModel.useConnection(conn2); // `UserModel` now stores documents in `db2`, not `db1`
|
|
166
|
+
*
|
|
167
|
+
* UserModel.connection === mongoose.connection; // false
|
|
168
|
+
* UserModel.connection === conn2; // true
|
|
169
|
+
*
|
|
170
|
+
* conn2.model('User') === UserModel; // true
|
|
171
|
+
* mongoose.model('User'); // Throws 'MissingSchemaError'
|
|
172
|
+
*
|
|
173
|
+
* Note: `useConnection()` does **not** apply any [connection-level plugins](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.plugin()) from the new connection.
|
|
174
|
+
* If you use `useConnection()` to switch a model's connection, the model will still have the old connection's plugins.
|
|
175
|
+
*
|
|
176
|
+
* @function useConnection
|
|
177
|
+
* @param [Connection] connection The new connection to use
|
|
178
|
+
* @return [Model] this
|
|
179
|
+
* @api public
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
Model.useConnection = function useConnection(connection) {
|
|
183
|
+
if (!connection) {
|
|
184
|
+
throw new Error('Please provide a connection.');
|
|
185
|
+
}
|
|
186
|
+
if (this.db) {
|
|
187
|
+
delete this.db.models[this.modelName];
|
|
188
|
+
delete this.prototype.db;
|
|
189
|
+
delete this.prototype[modelDbSymbol];
|
|
190
|
+
delete this.prototype.collection;
|
|
191
|
+
delete this.prototype.$collection;
|
|
192
|
+
delete this.prototype[modelCollectionSymbol];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.db = connection;
|
|
196
|
+
const collection = connection.collection(this.modelName, connection.options);
|
|
197
|
+
this.prototype.collection = collection;
|
|
198
|
+
this.prototype.$collection = collection;
|
|
199
|
+
this.prototype[modelCollectionSymbol] = collection;
|
|
200
|
+
this.prototype.db = connection;
|
|
201
|
+
this.prototype[modelDbSymbol] = connection;
|
|
202
|
+
this.collection = collection;
|
|
203
|
+
this.$__collection = collection;
|
|
204
|
+
connection.models[this.modelName] = this;
|
|
205
|
+
|
|
206
|
+
return this;
|
|
207
|
+
};
|
|
208
|
+
|
|
154
209
|
/**
|
|
155
210
|
* The collection instance this model uses.
|
|
156
211
|
* A Mongoose collection is a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)).
|
|
@@ -1048,20 +1103,28 @@ Model.init = function init() {
|
|
|
1048
1103
|
return results;
|
|
1049
1104
|
};
|
|
1050
1105
|
const _createCollection = async() => {
|
|
1051
|
-
|
|
1052
|
-
await new Promise(resolve => {
|
|
1053
|
-
conn._queue.push({ fn: resolve });
|
|
1054
|
-
});
|
|
1055
|
-
}
|
|
1056
|
-
const autoCreate = utils.getOption(
|
|
1106
|
+
let autoCreate = utils.getOption(
|
|
1057
1107
|
'autoCreate',
|
|
1058
1108
|
this.schema.options,
|
|
1059
|
-
conn.config
|
|
1060
|
-
|
|
1109
|
+
conn.config
|
|
1110
|
+
// No base.options here because we don't want to take the base value if the connection hasn't
|
|
1111
|
+
// set it yet
|
|
1061
1112
|
);
|
|
1113
|
+
if (autoCreate == null) {
|
|
1114
|
+
// `autoCreate` may later be set when the connection is opened, so wait for connect before checking
|
|
1115
|
+
await conn._waitForConnect(true);
|
|
1116
|
+
autoCreate = utils.getOption(
|
|
1117
|
+
'autoCreate',
|
|
1118
|
+
this.schema.options,
|
|
1119
|
+
conn.config,
|
|
1120
|
+
conn.base.options
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1062
1124
|
if (!autoCreate) {
|
|
1063
1125
|
return;
|
|
1064
1126
|
}
|
|
1127
|
+
|
|
1065
1128
|
return await this.createCollection();
|
|
1066
1129
|
};
|
|
1067
1130
|
|
|
@@ -1246,19 +1309,21 @@ Model.syncIndexes = async function syncIndexes(options) {
|
|
|
1246
1309
|
throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
|
|
1247
1310
|
}
|
|
1248
1311
|
|
|
1249
|
-
const
|
|
1312
|
+
const autoCreate = options?.autoCreate ?? this.schema.options?.autoCreate ?? this.db.config.autoCreate ?? true;
|
|
1250
1313
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1314
|
+
if (autoCreate) {
|
|
1315
|
+
try {
|
|
1316
|
+
await this.createCollection();
|
|
1317
|
+
} catch (err) {
|
|
1318
|
+
if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
|
|
1319
|
+
throw err;
|
|
1320
|
+
}
|
|
1256
1321
|
}
|
|
1257
1322
|
}
|
|
1258
1323
|
|
|
1259
|
-
const diffIndexesResult = await
|
|
1260
|
-
const dropped = await
|
|
1261
|
-
await
|
|
1324
|
+
const diffIndexesResult = await this.diffIndexes({ indexOptionsToCreate: true });
|
|
1325
|
+
const dropped = await this.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
|
|
1326
|
+
await this.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
|
|
1262
1327
|
|
|
1263
1328
|
return dropped;
|
|
1264
1329
|
};
|
|
@@ -1361,13 +1426,14 @@ Model.listSearchIndexes = async function listSearchIndexes(options) {
|
|
|
1361
1426
|
*
|
|
1362
1427
|
* const { toDrop, toCreate } = await Model.diffIndexes();
|
|
1363
1428
|
* toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
|
|
1364
|
-
* toCreate; // Array of
|
|
1429
|
+
* toCreate; // Array of index specs containing the keys of indexes that `syncIndexes()` will create
|
|
1365
1430
|
*
|
|
1366
1431
|
* @param {Object} [options]
|
|
1432
|
+
* @param {Boolean} [options.indexOptionsToCreate=false] If true, `toCreate` will include both the index spec and the index options, not just the index spec
|
|
1367
1433
|
* @return {Promise<Object>} contains the indexes that would be dropped in MongoDB and indexes that would be created in MongoDB as `{ toDrop: string[], toCreate: string[] }`.
|
|
1368
1434
|
*/
|
|
1369
1435
|
|
|
1370
|
-
Model.diffIndexes = async function diffIndexes() {
|
|
1436
|
+
Model.diffIndexes = async function diffIndexes(options) {
|
|
1371
1437
|
if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
|
1372
1438
|
throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
|
|
1373
1439
|
}
|
|
@@ -1389,13 +1455,14 @@ Model.diffIndexes = async function diffIndexes() {
|
|
|
1389
1455
|
const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
|
|
1390
1456
|
|
|
1391
1457
|
const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
|
|
1392
|
-
const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop);
|
|
1458
|
+
const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options);
|
|
1393
1459
|
|
|
1394
1460
|
return { toDrop, toCreate };
|
|
1395
1461
|
};
|
|
1396
1462
|
|
|
1397
|
-
function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
|
|
1463
|
+
function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options) {
|
|
1398
1464
|
const toCreate = [];
|
|
1465
|
+
const indexOptionsToCreate = options?.indexOptionsToCreate ?? false;
|
|
1399
1466
|
|
|
1400
1467
|
for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
|
|
1401
1468
|
let found = false;
|
|
@@ -1416,7 +1483,11 @@ function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
|
|
|
1416
1483
|
}
|
|
1417
1484
|
|
|
1418
1485
|
if (!found) {
|
|
1419
|
-
|
|
1486
|
+
if (indexOptionsToCreate) {
|
|
1487
|
+
toCreate.push([schemaIndexKeysObject, schemaIndexOptions]);
|
|
1488
|
+
} else {
|
|
1489
|
+
toCreate.push(schemaIndexKeysObject);
|
|
1490
|
+
}
|
|
1420
1491
|
}
|
|
1421
1492
|
}
|
|
1422
1493
|
|
|
@@ -1465,7 +1536,7 @@ function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
|
|
|
1465
1536
|
* @param {Object} [options]
|
|
1466
1537
|
* @param {Array<String>} [options.toDrop] if specified, contains a list of index names to drop
|
|
1467
1538
|
* @param {Boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher
|
|
1468
|
-
* @return {Promise<String
|
|
1539
|
+
* @return {Promise<Array<String>>} list of dropped or hidden index names
|
|
1469
1540
|
* @api public
|
|
1470
1541
|
*/
|
|
1471
1542
|
|
|
@@ -1597,7 +1668,7 @@ Model.createIndexes = async function createIndexes(options) {
|
|
|
1597
1668
|
*/
|
|
1598
1669
|
|
|
1599
1670
|
function _ensureIndexes(model, options, callback) {
|
|
1600
|
-
const indexes = model.schema.indexes();
|
|
1671
|
+
const indexes = Array.isArray(options?.toCreate) ? options.toCreate : model.schema.indexes();
|
|
1601
1672
|
let indexError;
|
|
1602
1673
|
|
|
1603
1674
|
options = options || {};
|
|
@@ -1681,12 +1752,6 @@ function _ensureIndexes(model, options, callback) {
|
|
|
1681
1752
|
indexOptions.background = options.background;
|
|
1682
1753
|
}
|
|
1683
1754
|
|
|
1684
|
-
if ('toCreate' in options) {
|
|
1685
|
-
if (options.toCreate.length === 0) {
|
|
1686
|
-
return done();
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
1755
|
// Just in case `createIndex()` throws a sync error
|
|
1691
1756
|
let promise = null;
|
|
1692
1757
|
try {
|
|
@@ -2115,9 +2180,8 @@ Model.estimatedDocumentCount = function estimatedDocumentCount(options) {
|
|
|
2115
2180
|
*
|
|
2116
2181
|
* #### Example:
|
|
2117
2182
|
*
|
|
2118
|
-
* Adventure.countDocuments({ type: 'jungle' }
|
|
2119
|
-
*
|
|
2120
|
-
* });
|
|
2183
|
+
* const count = await Adventure.countDocuments({ type: 'jungle' });
|
|
2184
|
+
* console.log('there are %d jungle adventures', count);
|
|
2121
2185
|
*
|
|
2122
2186
|
* If you want to count all documents in a large collection,
|
|
2123
2187
|
* use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount())
|
|
@@ -2627,6 +2691,10 @@ Model.create = async function create(doc, options) {
|
|
|
2627
2691
|
|
|
2628
2692
|
delete options.aggregateErrors; // dont pass on the option to "$save"
|
|
2629
2693
|
|
|
2694
|
+
if (options.session && !options.ordered && args.length > 1) {
|
|
2695
|
+
throw new MongooseError('Cannot call `create()` with a session and multiple documents unless `ordered: true` is set');
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2630
2698
|
if (options.ordered) {
|
|
2631
2699
|
for (let i = 0; i < args.length; i++) {
|
|
2632
2700
|
try {
|
|
@@ -2713,6 +2781,49 @@ Model.create = async function create(doc, options) {
|
|
|
2713
2781
|
return res;
|
|
2714
2782
|
};
|
|
2715
2783
|
|
|
2784
|
+
/**
|
|
2785
|
+
* Shortcut for saving one document to the database.
|
|
2786
|
+
* `MyModel.insertOne(obj, options)` is almost equivalent to `new MyModel(obj).save(options)`.
|
|
2787
|
+
* The difference is that `insertOne()` checks if `obj` is already a document, and checks for discriminators.
|
|
2788
|
+
*
|
|
2789
|
+
* This function triggers the following middleware.
|
|
2790
|
+
*
|
|
2791
|
+
* - `save()`
|
|
2792
|
+
*
|
|
2793
|
+
* #### Example:
|
|
2794
|
+
*
|
|
2795
|
+
* // Insert one new `Character` document
|
|
2796
|
+
* const character = await Character.insertOne({ name: 'Jean-Luc Picard' });
|
|
2797
|
+
* character.name; // 'Jean-Luc Picard'
|
|
2798
|
+
*
|
|
2799
|
+
* // Create a new character within a transaction.
|
|
2800
|
+
* await Character.insertOne({ name: 'Jean-Luc Picard' }, { session });
|
|
2801
|
+
*
|
|
2802
|
+
* @param {Object|Document} doc Document to insert, as a POJO or Mongoose document
|
|
2803
|
+
* @param {Object} [options] Options passed down to `save()`.
|
|
2804
|
+
* @return {Promise<Document>} resolves to the saved document
|
|
2805
|
+
* @api public
|
|
2806
|
+
*/
|
|
2807
|
+
|
|
2808
|
+
Model.insertOne = async function insertOne(doc, options) {
|
|
2809
|
+
_checkContext(this, 'insertOne');
|
|
2810
|
+
|
|
2811
|
+
const discriminatorKey = this.schema.options.discriminatorKey;
|
|
2812
|
+
const Model = this.discriminators && doc[discriminatorKey] != null ?
|
|
2813
|
+
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
|
|
2814
|
+
this;
|
|
2815
|
+
if (Model == null) {
|
|
2816
|
+
throw new MongooseError(
|
|
2817
|
+
`Discriminator "${doc[discriminatorKey]}" not found for model "${this.modelName}"`
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
if (!(doc instanceof Model)) {
|
|
2821
|
+
doc = new Model(doc);
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
return await doc.$save(options);
|
|
2825
|
+
};
|
|
2826
|
+
|
|
2716
2827
|
/**
|
|
2717
2828
|
* _Requires a replica set running MongoDB >= 3.6.0._ Watches the
|
|
2718
2829
|
* underlying collection for changes using
|
|
@@ -3878,6 +3989,10 @@ Model.hydrate = function(obj, projection, options) {
|
|
|
3878
3989
|
* res.upsertedId; // null or an id containing a document that had to be upserted.
|
|
3879
3990
|
* res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
|
|
3880
3991
|
*
|
|
3992
|
+
* // Other supported syntaxes
|
|
3993
|
+
* await Person.find({ name: /Stark$/ }).updateMany({ isDeleted: true }); // Using chaining syntax
|
|
3994
|
+
* await Person.find().updateMany({ isDeleted: true }); // Set `isDeleted` on _all_ Person documents
|
|
3995
|
+
*
|
|
3881
3996
|
* This function triggers the following middleware.
|
|
3882
3997
|
*
|
|
3883
3998
|
* - `updateMany()`
|
|
@@ -3898,10 +4013,14 @@ Model.hydrate = function(obj, projection, options) {
|
|
|
3898
4013
|
* @api public
|
|
3899
4014
|
*/
|
|
3900
4015
|
|
|
3901
|
-
Model.updateMany = function updateMany(conditions,
|
|
4016
|
+
Model.updateMany = function updateMany(conditions, update, options) {
|
|
3902
4017
|
_checkContext(this, 'updateMany');
|
|
3903
4018
|
|
|
3904
|
-
|
|
4019
|
+
if (update == null) {
|
|
4020
|
+
throw new MongooseError('updateMany `update` parameter cannot be nullish');
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
return _update(this, 'updateMany', conditions, update, options);
|
|
3905
4024
|
};
|
|
3906
4025
|
|
|
3907
4026
|
/**
|
|
@@ -3918,6 +4037,10 @@ Model.updateMany = function updateMany(conditions, doc, options) {
|
|
|
3918
4037
|
* res.upsertedId; // null or an id containing a document that had to be upserted.
|
|
3919
4038
|
* res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
|
|
3920
4039
|
*
|
|
4040
|
+
* // Other supported syntaxes
|
|
4041
|
+
* await Person.findOne({ name: 'Jean-Luc Picard' }).updateOne({ ship: 'USS Enterprise' }); // Using chaining syntax
|
|
4042
|
+
* await Person.updateOne({ ship: 'USS Enterprise' }); // Updates first doc's `ship` property
|
|
4043
|
+
*
|
|
3921
4044
|
* This function triggers the following middleware.
|
|
3922
4045
|
*
|
|
3923
4046
|
* - `updateOne()`
|