mongoose 8.9.6 → 8.10.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/lib/aggregate.js +21 -4
- package/lib/collection.js +2 -8
- package/lib/connection.js +63 -3
- package/lib/cursor/aggregationCursor.js +7 -1
- package/lib/document.js +10 -9
- package/lib/drivers/node-mongodb-native/connection.js +19 -0
- package/lib/helpers/createJSONSchemaTypeDefinition.js +24 -0
- package/lib/helpers/document/applyDefaults.js +3 -0
- package/lib/model.js +144 -33
- package/lib/query.js +8 -0
- 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 +35 -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 +97 -7
- package/lib/schemaType.js +14 -2
- package/package.json +13 -9
- package/types/connection.d.ts +2 -0
- package/types/document.d.ts +13 -13
- package/types/expressions.d.ts +15 -0
- package/types/index.d.ts +3 -1
- package/types/models.d.ts +21 -4
- package/types/query.d.ts +10 -4
- package/types/schematypes.d.ts +2 -0
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
|
@@ -824,12 +824,56 @@ Connection.prototype.dropCollection = async function dropCollection(collection)
|
|
|
824
824
|
|
|
825
825
|
Connection.prototype._waitForConnect = async function _waitForConnect() {
|
|
826
826
|
if ((this.readyState === STATES.connecting || this.readyState === STATES.disconnected) && this._shouldBufferCommands()) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
827
|
+
const bufferTimeoutMS = this._getBufferTimeoutMS();
|
|
828
|
+
let timeout = null;
|
|
829
|
+
let timedOut = false;
|
|
830
|
+
// The element that this function pushes onto `_queue`, stored to make it easy to remove later
|
|
831
|
+
const queueElement = {};
|
|
832
|
+
await Promise.race([
|
|
833
|
+
new Promise(resolve => {
|
|
834
|
+
queueElement.fn = resolve;
|
|
835
|
+
this._queue.push(queueElement);
|
|
836
|
+
}),
|
|
837
|
+
new Promise(resolve => {
|
|
838
|
+
timeout = setTimeout(
|
|
839
|
+
() => {
|
|
840
|
+
timedOut = true;
|
|
841
|
+
resolve();
|
|
842
|
+
},
|
|
843
|
+
bufferTimeoutMS
|
|
844
|
+
);
|
|
845
|
+
})
|
|
846
|
+
]);
|
|
847
|
+
|
|
848
|
+
if (timedOut) {
|
|
849
|
+
const index = this._queue.indexOf(queueElement);
|
|
850
|
+
if (index !== -1) {
|
|
851
|
+
this._queue.splice(index, 1);
|
|
852
|
+
}
|
|
853
|
+
const message = 'Connection operation buffering timed out after ' + bufferTimeoutMS + 'ms';
|
|
854
|
+
throw new MongooseError(message);
|
|
855
|
+
} else if (timeout != null) {
|
|
856
|
+
// Not strictly necessary, but avoid the extra overhead of creating a new MongooseError
|
|
857
|
+
// in case of success
|
|
858
|
+
clearTimeout(timeout);
|
|
859
|
+
}
|
|
830
860
|
}
|
|
831
861
|
};
|
|
832
862
|
|
|
863
|
+
/*!
|
|
864
|
+
* Get the default buffer timeout for this connection
|
|
865
|
+
*/
|
|
866
|
+
|
|
867
|
+
Connection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
|
|
868
|
+
if (this.config.bufferTimeoutMS != null) {
|
|
869
|
+
return this.config.bufferTimeoutMS;
|
|
870
|
+
}
|
|
871
|
+
if (this.base != null && this.base.get('bufferTimeoutMS') != null) {
|
|
872
|
+
return this.base.get('bufferTimeoutMS');
|
|
873
|
+
}
|
|
874
|
+
return 10000;
|
|
875
|
+
};
|
|
876
|
+
|
|
833
877
|
/**
|
|
834
878
|
* Helper for MongoDB Node driver's `listCollections()`.
|
|
835
879
|
* Returns an array of collection objects.
|
|
@@ -1156,6 +1200,10 @@ Connection.prototype.close = async function close(force) {
|
|
|
1156
1200
|
this.$wasForceClosed = !!force;
|
|
1157
1201
|
}
|
|
1158
1202
|
|
|
1203
|
+
if (this._lastHeartbeatAt != null) {
|
|
1204
|
+
this._lastHeartbeatAt = null;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1159
1207
|
for (const model of Object.values(this.models)) {
|
|
1160
1208
|
// If manually disconnecting, make sure to clear each model's `$init`
|
|
1161
1209
|
// promise, so Mongoose knows to re-run `init()` in case the
|
|
@@ -1742,6 +1790,18 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
|
|
|
1742
1790
|
* @api public
|
|
1743
1791
|
*/
|
|
1744
1792
|
|
|
1793
|
+
/**
|
|
1794
|
+
* Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
|
|
1795
|
+
*
|
|
1796
|
+
* @method aggregate
|
|
1797
|
+
* @memberOf Connection
|
|
1798
|
+
* @param {Array} pipeline
|
|
1799
|
+
* @param {Object} [options]
|
|
1800
|
+
* @param {Boolean} [options.cursor=false] If true, make the Aggregate resolve to a Mongoose AggregationCursor rather than an array
|
|
1801
|
+
* @return {Aggregate} Aggregation wrapper
|
|
1802
|
+
* @api public
|
|
1803
|
+
*/
|
|
1804
|
+
|
|
1745
1805
|
/**
|
|
1746
1806
|
* Removes the database connection with the given name created with with `useDb()`.
|
|
1747
1807
|
*
|
|
@@ -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
|
@@ -807,8 +807,8 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
807
807
|
reason: e
|
|
808
808
|
}));
|
|
809
809
|
}
|
|
810
|
-
} else if (opts.hydratedPopulatedDocs) {
|
|
811
|
-
doc[i] = schemaType.cast(value, self, true);
|
|
810
|
+
} else if (schemaType && opts.hydratedPopulatedDocs) {
|
|
811
|
+
doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
|
|
812
812
|
|
|
813
813
|
if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
|
|
814
814
|
self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
|
|
@@ -4069,7 +4069,7 @@ Document.prototype.$__toObjectShallow = function $__toObjectShallow() {
|
|
|
4069
4069
|
* @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
|
|
4070
4070
|
* @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
|
|
4071
4071
|
* @param {Boolean} [options.useProjection=false] - If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
|
|
4072
|
-
* @return {Object}
|
|
4072
|
+
* @return {Object} document as a plain old JavaScript object (POJO). This object may contain ObjectIds, Maps, Dates, mongodb.Binary, Buffers, and other non-POJO values.
|
|
4073
4073
|
* @see mongodb.Binary https://mongodb.github.io/node-mongodb-native/4.9/classes/Binary.html
|
|
4074
4074
|
* @api public
|
|
4075
4075
|
* @memberOf Document
|
|
@@ -4256,24 +4256,25 @@ function applySchemaTypeTransforms(self, json) {
|
|
|
4256
4256
|
|
|
4257
4257
|
for (const path of paths) {
|
|
4258
4258
|
const schematype = schema.paths[path];
|
|
4259
|
-
|
|
4259
|
+
const topLevelTransformFunction = schematype.options.transform ?? schematype.constructor?.defaultOptions?.transform;
|
|
4260
|
+
const embeddedSchemaTypeTransformFunction = schematype.$embeddedSchemaType?.options?.transform
|
|
4261
|
+
?? schematype.$embeddedSchemaType?.constructor?.defaultOptions?.transform;
|
|
4262
|
+
if (typeof topLevelTransformFunction === 'function') {
|
|
4260
4263
|
const val = self.$get(path);
|
|
4261
4264
|
if (val === undefined) {
|
|
4262
4265
|
continue;
|
|
4263
4266
|
}
|
|
4264
|
-
const transformedValue =
|
|
4267
|
+
const transformedValue = topLevelTransformFunction.call(self, val);
|
|
4265
4268
|
throwErrorIfPromise(path, transformedValue);
|
|
4266
4269
|
utils.setValue(path, transformedValue, json);
|
|
4267
|
-
} else if (
|
|
4268
|
-
typeof schematype.$embeddedSchemaType.options.transform === 'function') {
|
|
4270
|
+
} else if (typeof embeddedSchemaTypeTransformFunction === 'function') {
|
|
4269
4271
|
const val = self.$get(path);
|
|
4270
4272
|
if (val === undefined) {
|
|
4271
4273
|
continue;
|
|
4272
4274
|
}
|
|
4273
4275
|
const vals = [].concat(val);
|
|
4274
|
-
const transform = schematype.$embeddedSchemaType.options.transform;
|
|
4275
4276
|
for (let i = 0; i < vals.length; ++i) {
|
|
4276
|
-
const transformedValue =
|
|
4277
|
+
const transformedValue = embeddedSchemaTypeTransformFunction.call(self, vals[i]);
|
|
4277
4278
|
vals[i] = transformedValue;
|
|
4278
4279
|
throwErrorIfPromise(path, transformedValue);
|
|
4279
4280
|
}
|
|
@@ -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
|
+
};
|
|
@@ -19,6 +19,9 @@ module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildre
|
|
|
19
19
|
const type = doc.$__schema.paths[p];
|
|
20
20
|
const path = type.splitPath();
|
|
21
21
|
const len = path.length;
|
|
22
|
+
if (path[len - 1] === '$*') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
22
25
|
let included = false;
|
|
23
26
|
let doc_ = doc._doc;
|
|
24
27
|
for (let j = 0; j < len; ++j) {
|
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,11 +1103,7 @@ 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
|
-
}
|
|
1106
|
+
await conn._waitForConnect();
|
|
1056
1107
|
const autoCreate = utils.getOption(
|
|
1057
1108
|
'autoCreate',
|
|
1058
1109
|
this.schema.options,
|
|
@@ -1246,19 +1297,21 @@ Model.syncIndexes = async function syncIndexes(options) {
|
|
|
1246
1297
|
throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
|
|
1247
1298
|
}
|
|
1248
1299
|
|
|
1249
|
-
const
|
|
1300
|
+
const autoCreate = options?.autoCreate ?? this.schema.options?.autoCreate ?? this.db.config.autoCreate ?? true;
|
|
1250
1301
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1302
|
+
if (autoCreate) {
|
|
1303
|
+
try {
|
|
1304
|
+
await this.createCollection();
|
|
1305
|
+
} catch (err) {
|
|
1306
|
+
if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
|
|
1307
|
+
throw err;
|
|
1308
|
+
}
|
|
1256
1309
|
}
|
|
1257
1310
|
}
|
|
1258
1311
|
|
|
1259
|
-
const diffIndexesResult = await
|
|
1260
|
-
const dropped = await
|
|
1261
|
-
await
|
|
1312
|
+
const diffIndexesResult = await this.diffIndexes({ indexOptionsToCreate: true });
|
|
1313
|
+
const dropped = await this.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
|
|
1314
|
+
await this.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
|
|
1262
1315
|
|
|
1263
1316
|
return dropped;
|
|
1264
1317
|
};
|
|
@@ -1361,13 +1414,14 @@ Model.listSearchIndexes = async function listSearchIndexes(options) {
|
|
|
1361
1414
|
*
|
|
1362
1415
|
* const { toDrop, toCreate } = await Model.diffIndexes();
|
|
1363
1416
|
* toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
|
|
1364
|
-
* toCreate; // Array of
|
|
1417
|
+
* toCreate; // Array of index specs containing the keys of indexes that `syncIndexes()` will create
|
|
1365
1418
|
*
|
|
1366
1419
|
* @param {Object} [options]
|
|
1420
|
+
* @param {Boolean} [options.indexOptionsToCreate=false] If true, `toCreate` will include both the index spec and the index options, not just the index spec
|
|
1367
1421
|
* @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
1422
|
*/
|
|
1369
1423
|
|
|
1370
|
-
Model.diffIndexes = async function diffIndexes() {
|
|
1424
|
+
Model.diffIndexes = async function diffIndexes(options) {
|
|
1371
1425
|
if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
|
|
1372
1426
|
throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
|
|
1373
1427
|
}
|
|
@@ -1389,13 +1443,14 @@ Model.diffIndexes = async function diffIndexes() {
|
|
|
1389
1443
|
const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
|
|
1390
1444
|
|
|
1391
1445
|
const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
|
|
1392
|
-
const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop);
|
|
1446
|
+
const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options);
|
|
1393
1447
|
|
|
1394
1448
|
return { toDrop, toCreate };
|
|
1395
1449
|
};
|
|
1396
1450
|
|
|
1397
|
-
function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
|
|
1451
|
+
function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options) {
|
|
1398
1452
|
const toCreate = [];
|
|
1453
|
+
const indexOptionsToCreate = options?.indexOptionsToCreate ?? false;
|
|
1399
1454
|
|
|
1400
1455
|
for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
|
|
1401
1456
|
let found = false;
|
|
@@ -1416,7 +1471,11 @@ function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
|
|
|
1416
1471
|
}
|
|
1417
1472
|
|
|
1418
1473
|
if (!found) {
|
|
1419
|
-
|
|
1474
|
+
if (indexOptionsToCreate) {
|
|
1475
|
+
toCreate.push([schemaIndexKeysObject, schemaIndexOptions]);
|
|
1476
|
+
} else {
|
|
1477
|
+
toCreate.push(schemaIndexKeysObject);
|
|
1478
|
+
}
|
|
1420
1479
|
}
|
|
1421
1480
|
}
|
|
1422
1481
|
|
|
@@ -1465,7 +1524,7 @@ function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
|
|
|
1465
1524
|
* @param {Object} [options]
|
|
1466
1525
|
* @param {Array<String>} [options.toDrop] if specified, contains a list of index names to drop
|
|
1467
1526
|
* @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
|
|
1527
|
+
* @return {Promise<Array<String>>} list of dropped or hidden index names
|
|
1469
1528
|
* @api public
|
|
1470
1529
|
*/
|
|
1471
1530
|
|
|
@@ -1597,7 +1656,7 @@ Model.createIndexes = async function createIndexes(options) {
|
|
|
1597
1656
|
*/
|
|
1598
1657
|
|
|
1599
1658
|
function _ensureIndexes(model, options, callback) {
|
|
1600
|
-
const indexes = model.schema.indexes();
|
|
1659
|
+
const indexes = Array.isArray(options?.toCreate) ? options.toCreate : model.schema.indexes();
|
|
1601
1660
|
let indexError;
|
|
1602
1661
|
|
|
1603
1662
|
options = options || {};
|
|
@@ -1681,12 +1740,6 @@ function _ensureIndexes(model, options, callback) {
|
|
|
1681
1740
|
indexOptions.background = options.background;
|
|
1682
1741
|
}
|
|
1683
1742
|
|
|
1684
|
-
if ('toCreate' in options) {
|
|
1685
|
-
if (options.toCreate.length === 0) {
|
|
1686
|
-
return done();
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
1743
|
// Just in case `createIndex()` throws a sync error
|
|
1691
1744
|
let promise = null;
|
|
1692
1745
|
try {
|
|
@@ -2115,9 +2168,8 @@ Model.estimatedDocumentCount = function estimatedDocumentCount(options) {
|
|
|
2115
2168
|
*
|
|
2116
2169
|
* #### Example:
|
|
2117
2170
|
*
|
|
2118
|
-
* Adventure.countDocuments({ type: 'jungle' }
|
|
2119
|
-
*
|
|
2120
|
-
* });
|
|
2171
|
+
* const count = await Adventure.countDocuments({ type: 'jungle' });
|
|
2172
|
+
* console.log('there are %d jungle adventures', count);
|
|
2121
2173
|
*
|
|
2122
2174
|
* If you want to count all documents in a large collection,
|
|
2123
2175
|
* use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount())
|
|
@@ -2627,6 +2679,10 @@ Model.create = async function create(doc, options) {
|
|
|
2627
2679
|
|
|
2628
2680
|
delete options.aggregateErrors; // dont pass on the option to "$save"
|
|
2629
2681
|
|
|
2682
|
+
if (options.session && !options.ordered && args.length > 1) {
|
|
2683
|
+
throw new MongooseError('Cannot call `create()` with a session and multiple documents unless `ordered: true` is set');
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2630
2686
|
if (options.ordered) {
|
|
2631
2687
|
for (let i = 0; i < args.length; i++) {
|
|
2632
2688
|
try {
|
|
@@ -2713,6 +2769,49 @@ Model.create = async function create(doc, options) {
|
|
|
2713
2769
|
return res;
|
|
2714
2770
|
};
|
|
2715
2771
|
|
|
2772
|
+
/**
|
|
2773
|
+
* Shortcut for saving one document to the database.
|
|
2774
|
+
* `MyModel.insertOne(obj, options)` is almost equivalent to `new MyModel(obj).save(options)`.
|
|
2775
|
+
* The difference is that `insertOne()` checks if `obj` is already a document, and checks for discriminators.
|
|
2776
|
+
*
|
|
2777
|
+
* This function triggers the following middleware.
|
|
2778
|
+
*
|
|
2779
|
+
* - `save()`
|
|
2780
|
+
*
|
|
2781
|
+
* #### Example:
|
|
2782
|
+
*
|
|
2783
|
+
* // Insert one new `Character` document
|
|
2784
|
+
* const character = await Character.insertOne({ name: 'Jean-Luc Picard' });
|
|
2785
|
+
* character.name; // 'Jean-Luc Picard'
|
|
2786
|
+
*
|
|
2787
|
+
* // Create a new character within a transaction.
|
|
2788
|
+
* await Character.insertOne({ name: 'Jean-Luc Picard' }, { session });
|
|
2789
|
+
*
|
|
2790
|
+
* @param {Object|Document} doc Document to insert, as a POJO or Mongoose document
|
|
2791
|
+
* @param {Object} [options] Options passed down to `save()`.
|
|
2792
|
+
* @return {Promise<Document>} resolves to the saved document
|
|
2793
|
+
* @api public
|
|
2794
|
+
*/
|
|
2795
|
+
|
|
2796
|
+
Model.insertOne = async function insertOne(doc, options) {
|
|
2797
|
+
_checkContext(this, 'insertOne');
|
|
2798
|
+
|
|
2799
|
+
const discriminatorKey = this.schema.options.discriminatorKey;
|
|
2800
|
+
const Model = this.discriminators && doc[discriminatorKey] != null ?
|
|
2801
|
+
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
|
|
2802
|
+
this;
|
|
2803
|
+
if (Model == null) {
|
|
2804
|
+
throw new MongooseError(
|
|
2805
|
+
`Discriminator "${doc[discriminatorKey]}" not found for model "${this.modelName}"`
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
if (!(doc instanceof Model)) {
|
|
2809
|
+
doc = new Model(doc);
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
return await doc.$save(options);
|
|
2813
|
+
};
|
|
2814
|
+
|
|
2716
2815
|
/**
|
|
2717
2816
|
* _Requires a replica set running MongoDB >= 3.6.0._ Watches the
|
|
2718
2817
|
* underlying collection for changes using
|
|
@@ -3878,6 +3977,10 @@ Model.hydrate = function(obj, projection, options) {
|
|
|
3878
3977
|
* res.upsertedId; // null or an id containing a document that had to be upserted.
|
|
3879
3978
|
* res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
|
|
3880
3979
|
*
|
|
3980
|
+
* // Other supported syntaxes
|
|
3981
|
+
* await Person.find({ name: /Stark$/ }).updateMany({ isDeleted: true }); // Using chaining syntax
|
|
3982
|
+
* await Person.find().updateMany({ isDeleted: true }); // Set `isDeleted` on _all_ Person documents
|
|
3983
|
+
*
|
|
3881
3984
|
* This function triggers the following middleware.
|
|
3882
3985
|
*
|
|
3883
3986
|
* - `updateMany()`
|
|
@@ -3898,10 +4001,14 @@ Model.hydrate = function(obj, projection, options) {
|
|
|
3898
4001
|
* @api public
|
|
3899
4002
|
*/
|
|
3900
4003
|
|
|
3901
|
-
Model.updateMany = function updateMany(conditions,
|
|
4004
|
+
Model.updateMany = function updateMany(conditions, update, options) {
|
|
3902
4005
|
_checkContext(this, 'updateMany');
|
|
3903
4006
|
|
|
3904
|
-
|
|
4007
|
+
if (update == null) {
|
|
4008
|
+
throw new MongooseError('updateMany `update` parameter cannot be nullish');
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
return _update(this, 'updateMany', conditions, update, options);
|
|
3905
4012
|
};
|
|
3906
4013
|
|
|
3907
4014
|
/**
|
|
@@ -3918,6 +4025,10 @@ Model.updateMany = function updateMany(conditions, doc, options) {
|
|
|
3918
4025
|
* res.upsertedId; // null or an id containing a document that had to be upserted.
|
|
3919
4026
|
* res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
|
|
3920
4027
|
*
|
|
4028
|
+
* // Other supported syntaxes
|
|
4029
|
+
* await Person.findOne({ name: 'Jean-Luc Picard' }).updateOne({ ship: 'USS Enterprise' }); // Using chaining syntax
|
|
4030
|
+
* await Person.updateOne({ ship: 'USS Enterprise' }); // Updates first doc's `ship` property
|
|
4031
|
+
*
|
|
3921
4032
|
* This function triggers the following middleware.
|
|
3922
4033
|
*
|
|
3923
4034
|
* - `updateOne()`
|
package/lib/query.js
CHANGED
|
@@ -3992,6 +3992,10 @@ Query.prototype._replaceOne = async function _replaceOne() {
|
|
|
3992
3992
|
* res.n; // Number of documents matched
|
|
3993
3993
|
* res.nModified; // Number of documents modified
|
|
3994
3994
|
*
|
|
3995
|
+
* // Other supported syntaxes
|
|
3996
|
+
* await Person.find({ name: /Stark$/ }).updateMany({ isDeleted: true }); // Using chaining syntax
|
|
3997
|
+
* await Person.find().updateMany({ isDeleted: true }); // Set `isDeleted` on _all_ Person documents
|
|
3998
|
+
*
|
|
3995
3999
|
* This function triggers the following middleware.
|
|
3996
4000
|
*
|
|
3997
4001
|
* - `updateMany()`
|
|
@@ -4062,6 +4066,10 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
|
|
|
4062
4066
|
* res.upsertedCount; // Number of documents that were upserted
|
|
4063
4067
|
* res.upsertedId; // Identifier of the inserted document (if an upsert took place)
|
|
4064
4068
|
*
|
|
4069
|
+
* // Other supported syntaxes
|
|
4070
|
+
* await Person.findOne({ name: 'Jean-Luc Picard' }).updateOne({ ship: 'USS Enterprise' }); // Using chaining syntax
|
|
4071
|
+
* await Person.updateOne({ ship: 'USS Enterprise' }); // Updates first doc's `ship` property
|
|
4072
|
+
*
|
|
4065
4073
|
* This function triggers the following middleware.
|
|
4066
4074
|
*
|
|
4067
4075
|
* - `updateOne()`
|