mongoose 5.4.23 → 5.5.3
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/History.md +50 -0
- package/lib/aggregate.js +16 -4
- package/lib/connection.js +58 -1
- package/lib/document.js +195 -36
- package/lib/drivers/node-mongodb-native/collection.js +16 -4
- package/lib/drivers/node-mongodb-native/index.js +1 -1
- package/lib/helpers/document/cleanModifiedSubpaths.js +3 -0
- package/lib/helpers/document/compile.js +16 -1
- package/lib/helpers/document/getEmbeddedDiscriminatorPath.js +5 -5
- package/lib/helpers/model/applyHooks.js +4 -1
- package/lib/helpers/model/applyStaticHooks.js +61 -0
- package/lib/helpers/populate/assignVals.js +11 -1
- package/lib/helpers/query/applyQueryMiddleware.js +6 -2
- package/lib/helpers/query/castUpdate.js +0 -2
- package/lib/helpers/schema/applyPlugins.js +43 -0
- package/lib/helpers/symbols.js +4 -8
- package/lib/index.js +22 -36
- package/lib/internal.js +1 -0
- package/lib/model.js +155 -33
- package/lib/plugins/validateBeforeSave.js +7 -1
- package/lib/query.js +135 -29
- package/lib/schema/array.js +50 -0
- package/lib/schema/documentarray.js +39 -25
- package/lib/schema.js +6 -5
- package/lib/schematype.js +51 -6
- package/lib/types/array.js +20 -21
- package/lib/types/buffer.js +8 -34
- package/lib/types/documentarray.js +4 -3
- package/lib/types/embedded.js +1 -1
- package/lib/types/subdocument.js +27 -1
- package/package.json +6 -5
package/History.md
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
5.5.3 / 2019-04-22
|
|
2
|
+
==================
|
|
3
|
+
* fix: add findAndModify deprecation warning that references the useFindAndModify option #7644
|
|
4
|
+
* fix(document): handle pushing a doc onto a discriminator that contains a doc array #7704
|
|
5
|
+
* fix(update): run setters on array elements when doing $set #7679
|
|
6
|
+
* fix: correct usage of arguments while buffering commands #7718 [rzymek](https://github.com/rzymek)
|
|
7
|
+
* fix(document): avoid error clearing modified subpaths if doc not defined #7715 [bitflower](https://github.com/bitflower)
|
|
8
|
+
* refactor(array): move `_parent` property behind a symbol #7726 #7700
|
|
9
|
+
* docs(model): list out all operations and options for `bulkWrite()` #7055
|
|
10
|
+
* docs(aggregate): use `eachAsync()` instead of nonexistent `each()` #7699
|
|
11
|
+
* docs(validation): add CastError validation example #7514
|
|
12
|
+
* docs(query+model): list out all options and callback details for Model.updateX() and Query#updateX() #7646
|
|
13
|
+
|
|
14
|
+
5.5.2 / 2019-04-16
|
|
15
|
+
==================
|
|
16
|
+
* fix(document): support setting nested path to non-POJO object #7639
|
|
17
|
+
* perf(connection): remove leaked event handler in `Model.init()` so `deleteModel()` frees all memory #7682
|
|
18
|
+
* fix(timestamps): handle custom statics that conflict with built-in functions (like mongoose-delete plugin) #7698
|
|
19
|
+
* fix(populate): make `Document#populated()` work for populated subdocs #7685
|
|
20
|
+
* fix(document): support `.set()` on document array underneath embedded discriminator path #7656
|
|
21
|
+
|
|
22
|
+
5.5.1 / 2019-04-11
|
|
23
|
+
==================
|
|
24
|
+
* fix(document): correctly overwrite all properties when setting a single nested subdoc #7660 #7681
|
|
25
|
+
* fix(array): allow customization of array required validator #7696 [freewil](https://github.com/freewil)
|
|
26
|
+
* fix(discriminator): handle embedded discriminators when casting array defaults #7687
|
|
27
|
+
* fix(collection): ensure collection functions return a promise even if disconnected #7676
|
|
28
|
+
* fix(schematype): avoid indexing properties with `{ unique: false, index: false }` #7620
|
|
29
|
+
* fix(aggregate): make `Aggregate#model()` with no arguments return the aggregation's model #7608
|
|
30
|
+
|
|
31
|
+
5.5.0 / 2019-04-08
|
|
32
|
+
==================
|
|
33
|
+
* feat(model): support applying hooks to custom static functions #5982
|
|
34
|
+
* feat(populate): support specifying a function as `match` #7397
|
|
35
|
+
* perf(buffer): avoid calling `defineProperties()` in Buffer constructor #7331
|
|
36
|
+
* feat(connection): add `plugin()` for connection-scoped plugins #7378
|
|
37
|
+
* feat(model): add Model#deleteOne() and corresponding hooks #7538
|
|
38
|
+
* feat(query): support hooks for `Query#distinct()` #5938
|
|
39
|
+
* feat(model): print warning when calling create() incorrectly with a session #7535
|
|
40
|
+
* feat(document): add Document#isEmpty() and corresponding helpers for nested paths #5369
|
|
41
|
+
* feat(document): add `getters` option to Document#get() #7233
|
|
42
|
+
* feat(query): add Query#projection() to get or overwrite the current projection #7384
|
|
43
|
+
* fix(document): set full validator path on validatorProperties if `propsParameter` set on validator #7447
|
|
44
|
+
* feat(document): add Document#directModifiedPaths() #7373
|
|
45
|
+
* feat(document): add $locals property #7691
|
|
46
|
+
* feat(document): add validateUpdatedOnly option that only validates modified paths in `save()` #7492 [captaincaius](https://github.com/captaincaius)
|
|
47
|
+
* chore: upgrade MongoDB driver to v3.2.0 #7641
|
|
48
|
+
* fix(schematype): deprecate `isAsync` option for custom validators #6700
|
|
49
|
+
* chore(mongoose): deprecate global.MONGOOSE_DRIVER_PATH so we can be webpack-warning-free in 6.0 #7501
|
|
50
|
+
|
|
1
51
|
5.4.23 / 2019-04-08
|
|
2
52
|
===================
|
|
3
53
|
* fix(document): report cast error when string path in schema is an array in MongoDB #7619
|
package/lib/aggregate.js
CHANGED
|
@@ -80,14 +80,26 @@ function Aggregate(pipeline) {
|
|
|
80
80
|
Aggregate.prototype.options;
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
83
|
+
* Get/set the model that this aggregation will execute on.
|
|
84
84
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
85
|
+
* ####Example:
|
|
86
|
+
* const aggregate = MyModel.aggregate([{ $match: { answer: 42 } }]);
|
|
87
|
+
* aggregate.model() === MyModel; // true
|
|
88
|
+
*
|
|
89
|
+
* // Change the model. There's rarely any reason to do this.
|
|
90
|
+
* aggregate.model(SomeOtherModel);
|
|
91
|
+
* aggregate.model() === SomeOtherModel; // true
|
|
92
|
+
*
|
|
93
|
+
* @param {Model} [model] the model to which the aggregate is to be bound
|
|
94
|
+
* @return {Aggregate|Model} if model is passed, will return `this`, otherwise will return the model
|
|
87
95
|
* @api public
|
|
88
96
|
*/
|
|
89
97
|
|
|
90
98
|
Aggregate.prototype.model = function(model) {
|
|
99
|
+
if (arguments.length === 0) {
|
|
100
|
+
return this._model;
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
this._model = model;
|
|
92
104
|
if (model.schema != null) {
|
|
93
105
|
if (this.options.readPreference == null &&
|
|
@@ -790,7 +802,7 @@ Aggregate.prototype.option = function(value) {
|
|
|
790
802
|
* ####Example:
|
|
791
803
|
*
|
|
792
804
|
* var cursor = Model.aggregate(..).cursor({ batchSize: 1000 }).exec();
|
|
793
|
-
* cursor.
|
|
805
|
+
* cursor.eachAsync(function(error, doc) {
|
|
794
806
|
* // use doc
|
|
795
807
|
* });
|
|
796
808
|
*
|
package/lib/connection.js
CHANGED
|
@@ -10,6 +10,7 @@ const Collection = require('./driver').get().Collection;
|
|
|
10
10
|
const STATES = require('./connectionstate');
|
|
11
11
|
const MongooseError = require('./error');
|
|
12
12
|
const PromiseProvider = require('./promise_provider');
|
|
13
|
+
const applyPlugins = require('./helpers/schema/applyPlugins');
|
|
13
14
|
const get = require('./helpers/get');
|
|
14
15
|
const mongodb = require('mongodb');
|
|
15
16
|
const utils = require('./utils');
|
|
@@ -59,6 +60,7 @@ function Connection(base) {
|
|
|
59
60
|
this._readyState = STATES.disconnected;
|
|
60
61
|
this._closeCalled = false;
|
|
61
62
|
this._hasOpened = false;
|
|
63
|
+
this.plugins = [];
|
|
62
64
|
|
|
63
65
|
this.$internalEmitter = new EventEmitter();
|
|
64
66
|
this.$internalEmitter.setMaxListeners(0);
|
|
@@ -147,6 +149,29 @@ Connection.prototype.collections;
|
|
|
147
149
|
|
|
148
150
|
Connection.prototype.name;
|
|
149
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The plugins that will be applied to all models created on this connection.
|
|
154
|
+
*
|
|
155
|
+
* ####Example:
|
|
156
|
+
*
|
|
157
|
+
* const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
|
|
158
|
+
* db.plugin(() => console.log('Applied'));
|
|
159
|
+
* db.plugins.length; // 1
|
|
160
|
+
*
|
|
161
|
+
* db.model('Test', new Schema({})); // Prints "Applied"
|
|
162
|
+
*
|
|
163
|
+
* @property plugins
|
|
164
|
+
* @memberOf Connection
|
|
165
|
+
* @instance
|
|
166
|
+
* @api public
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
Object.defineProperty(Connection.prototype, 'plugins', {
|
|
170
|
+
configurable: false,
|
|
171
|
+
enumerable: true,
|
|
172
|
+
writable: true
|
|
173
|
+
});
|
|
174
|
+
|
|
150
175
|
/**
|
|
151
176
|
* The host name portion of the URI. If multiple hosts, such as a replica set,
|
|
152
177
|
* this will contain the first host name in the URI
|
|
@@ -336,7 +361,13 @@ Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(co
|
|
|
336
361
|
*/
|
|
337
362
|
|
|
338
363
|
Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) {
|
|
339
|
-
this
|
|
364
|
+
// If `dropDatabase()` is called, this model's collection will not be
|
|
365
|
+
// init-ed. It is sufficiently common to call `dropDatabase()` after
|
|
366
|
+
// `mongoose.connect()` but before creating models that we want to
|
|
367
|
+
// support this. See gh-6967
|
|
368
|
+
for (const name of Object.keys(this.models)) {
|
|
369
|
+
delete this.models[name].$init;
|
|
370
|
+
}
|
|
340
371
|
this.db.dropDatabase(cb);
|
|
341
372
|
});
|
|
342
373
|
|
|
@@ -729,6 +760,30 @@ Connection.prototype.collection = function(name, options) {
|
|
|
729
760
|
return this.collections[name];
|
|
730
761
|
};
|
|
731
762
|
|
|
763
|
+
/**
|
|
764
|
+
* Declares a plugin executed on all schemas you pass to `conn.model()`
|
|
765
|
+
*
|
|
766
|
+
* Equivalent to calling `.plugin(fn)` on each schema you create.
|
|
767
|
+
*
|
|
768
|
+
* ####Example:
|
|
769
|
+
* const db = mongoose.createConnection('mongodb://localhost:27017/mydb');
|
|
770
|
+
* db.plugin(() => console.log('Applied'));
|
|
771
|
+
* db.plugins.length; // 1
|
|
772
|
+
*
|
|
773
|
+
* db.model('Test', new Schema({})); // Prints "Applied"
|
|
774
|
+
*
|
|
775
|
+
* @param {Function} fn plugin callback
|
|
776
|
+
* @param {Object} [opts] optional options
|
|
777
|
+
* @return {Connection} this
|
|
778
|
+
* @see plugins ./plugins.html
|
|
779
|
+
* @api public
|
|
780
|
+
*/
|
|
781
|
+
|
|
782
|
+
Connection.prototype.plugin = function(fn, opts) {
|
|
783
|
+
this.plugins.push([fn, opts]);
|
|
784
|
+
return this;
|
|
785
|
+
};
|
|
786
|
+
|
|
732
787
|
/**
|
|
733
788
|
* Defines or retrieves a model.
|
|
734
789
|
*
|
|
@@ -800,6 +855,8 @@ Connection.prototype.model = function(name, schema, collection) {
|
|
|
800
855
|
let model;
|
|
801
856
|
|
|
802
857
|
if (schema && schema.instanceOfSchema) {
|
|
858
|
+
applyPlugins(schema, this.plugins, null, '$connectionPluginsApplied');
|
|
859
|
+
|
|
803
860
|
// compile a model
|
|
804
861
|
model = this.base.model(fn || name, schema, collection, opts);
|
|
805
862
|
|
package/lib/document.js
CHANGED
|
@@ -100,13 +100,15 @@ function Document(obj, fields, skipId, options) {
|
|
|
100
100
|
$__hasIncludedChildren(fields) :
|
|
101
101
|
{};
|
|
102
102
|
|
|
103
|
-
this
|
|
103
|
+
if (this._doc == null) {
|
|
104
|
+
this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
// By default, defaults get applied **before** setting initial values
|
|
107
|
+
// Re: gh-6155
|
|
108
|
+
$__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
|
|
109
|
+
isNew: this.isNew
|
|
110
|
+
});
|
|
111
|
+
}
|
|
110
112
|
|
|
111
113
|
if (obj) {
|
|
112
114
|
if (obj instanceof Document) {
|
|
@@ -136,6 +138,7 @@ function Document(obj, fields, skipId, options) {
|
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
this.$__._id = this._id;
|
|
141
|
+
this.$locals = {};
|
|
139
142
|
|
|
140
143
|
if (!schema.options.strict && obj) {
|
|
141
144
|
const _this = this;
|
|
@@ -177,6 +180,35 @@ Document.prototype.constructor = Document;
|
|
|
177
180
|
|
|
178
181
|
Document.prototype.schema;
|
|
179
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Empty object that you can use for storing properties on the document. This
|
|
185
|
+
* is handy for passing data to middleware without conflicting with Mongoose
|
|
186
|
+
* internals.
|
|
187
|
+
*
|
|
188
|
+
* ####Example:
|
|
189
|
+
*
|
|
190
|
+
* schema.pre('save', function() {
|
|
191
|
+
* // Mongoose will set `isNew` to `false` if `save()` succeeds
|
|
192
|
+
* this.$locals.wasNew = this.isNew;
|
|
193
|
+
* });
|
|
194
|
+
*
|
|
195
|
+
* schema.post('save', function() {
|
|
196
|
+
* // Prints true if `isNew` was set before `save()`
|
|
197
|
+
* console.log(this.$locals.wasNew);
|
|
198
|
+
* });
|
|
199
|
+
*
|
|
200
|
+
* @api public
|
|
201
|
+
* @property $locals
|
|
202
|
+
* @memberOf Document
|
|
203
|
+
* @instance
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
Object.defineProperty(Document.prototype, '$locals', {
|
|
207
|
+
configurable: false,
|
|
208
|
+
enumerable: false,
|
|
209
|
+
writable: true
|
|
210
|
+
});
|
|
211
|
+
|
|
180
212
|
/**
|
|
181
213
|
* Boolean flag specifying if the document is new.
|
|
182
214
|
*
|
|
@@ -758,6 +790,8 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
758
790
|
|
|
759
791
|
return this;
|
|
760
792
|
}
|
|
793
|
+
} else {
|
|
794
|
+
this.$__.$setCalled.add(path);
|
|
761
795
|
}
|
|
762
796
|
|
|
763
797
|
function _handleIndex(i) {
|
|
@@ -776,7 +810,8 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
776
810
|
delete this._doc[key];
|
|
777
811
|
}
|
|
778
812
|
|
|
779
|
-
if (
|
|
813
|
+
if (typeof path[key] === 'object' &&
|
|
814
|
+
path[key] != null &&
|
|
780
815
|
pathtype !== 'virtual' &&
|
|
781
816
|
pathtype !== 'real' &&
|
|
782
817
|
!(this.$__path(pathName) instanceof MixedSchema) &&
|
|
@@ -819,7 +854,10 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
819
854
|
}
|
|
820
855
|
}
|
|
821
856
|
|
|
822
|
-
|
|
857
|
+
let pathType = this.schema.pathType(path);
|
|
858
|
+
if (pathType === 'adhocOrUndefined') {
|
|
859
|
+
pathType = getEmbeddedDiscriminatorPath(this, path, { typeOnly: true });
|
|
860
|
+
}
|
|
823
861
|
|
|
824
862
|
// Assume this is a Mongoose document that was copied into a POJO using
|
|
825
863
|
// `Object.assign()` or `{...doc}`
|
|
@@ -828,7 +866,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
828
866
|
}
|
|
829
867
|
|
|
830
868
|
if (pathType === 'nested' && val) {
|
|
831
|
-
if (
|
|
869
|
+
if (typeof val === 'object' && val != null) {
|
|
832
870
|
if (!merge) {
|
|
833
871
|
this.setValue(path, null);
|
|
834
872
|
cleanModifiedSubpaths(this, path);
|
|
@@ -1026,25 +1064,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
1026
1064
|
// a single nested doc. That's to make sure we get the correct context.
|
|
1027
1065
|
// Otherwise we would double-call the setter, see gh-7196.
|
|
1028
1066
|
if (this.schema.singleNestedPaths[path] == null) {
|
|
1029
|
-
// Init the new subdoc to the previous values of the doc, so
|
|
1030
|
-
// getters/setters see the correct current state. We pass the new subdoc
|
|
1031
|
-
// instead of the old subdoc because otherwise side effects in setters
|
|
1032
|
-
// wouldn't work, see gh-7585
|
|
1033
|
-
if (constructing && this.$__.$options.priorDoc) {
|
|
1034
|
-
const priorVal = Object.assign({}, this.$__.$options.priorDoc._doc);
|
|
1035
|
-
delete priorVal[this.schema.options.discriminatorKey];
|
|
1036
|
-
init(this, priorVal, this._doc);
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
1067
|
val = schema.applySetters(val, this, false, priorVal);
|
|
1040
|
-
|
|
1041
|
-
if (constructing && this.$__.$options.priorDoc) {
|
|
1042
|
-
// Clear init-ed paths afterwards, because those should be paths that
|
|
1043
|
-
// were in the previous doc and should not be in the new one.
|
|
1044
|
-
for (const path of Object.keys(this.$__.activePaths.states.init)) {
|
|
1045
|
-
delete this._doc[path];
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
1068
|
}
|
|
1049
1069
|
|
|
1050
1070
|
if (!didPopulate && this.$__.populated) {
|
|
@@ -1277,6 +1297,9 @@ Document.prototype.setValue = function(path, val) {
|
|
|
1277
1297
|
*
|
|
1278
1298
|
* @param {String} path
|
|
1279
1299
|
* @param {Schema|String|Number|Buffer|*} [type] optionally specify a type for on-the-fly attributes
|
|
1300
|
+
* @param {Object} [options]
|
|
1301
|
+
* @param {Boolean} [options.virtuals=false] Apply virtuals before getting this path
|
|
1302
|
+
* @param {Boolean} [options.getters=true] If false, skip applying getters and just get the raw value
|
|
1280
1303
|
* @api public
|
|
1281
1304
|
*/
|
|
1282
1305
|
|
|
@@ -1323,7 +1346,7 @@ Document.prototype.get = function(path, type, options) {
|
|
|
1323
1346
|
obj = adhoc.cast(obj);
|
|
1324
1347
|
}
|
|
1325
1348
|
|
|
1326
|
-
if (schema != null) {
|
|
1349
|
+
if (schema != null && options.getters !== false) {
|
|
1327
1350
|
obj = schema.applyGetters(obj, this);
|
|
1328
1351
|
} else if (this.schema.nested[path] && options.virtuals) {
|
|
1329
1352
|
// Might need to apply virtuals if this is a nested path
|
|
@@ -1420,6 +1443,95 @@ Document.prototype.$ignore = function(path) {
|
|
|
1420
1443
|
this.$__.activePaths.ignore(path);
|
|
1421
1444
|
};
|
|
1422
1445
|
|
|
1446
|
+
/**
|
|
1447
|
+
* Returns the list of paths that have been directly modified. A direct
|
|
1448
|
+
* modified path is a path that you explicitly set, whether via `doc.foo = 'bar'`,
|
|
1449
|
+
* `Object.assign(doc, { foo: 'bar' })`, or `doc.set('foo', 'bar')`.
|
|
1450
|
+
*
|
|
1451
|
+
* A path `a` may be in `modifiedPaths()` but not in `directModifiedPaths()`
|
|
1452
|
+
* because a child of `a` was directly modified.
|
|
1453
|
+
*
|
|
1454
|
+
* ####Example
|
|
1455
|
+
* const schema = new Schema({ foo: String, nested: { bar: String } });
|
|
1456
|
+
* const Model = mongoose.model('Test', schema);
|
|
1457
|
+
* await Model.create({ foo: 'original', nested: { bar: 'original' } });
|
|
1458
|
+
*
|
|
1459
|
+
* const doc = await Model.findOne();
|
|
1460
|
+
* doc.nested.bar = 'modified';
|
|
1461
|
+
* doc.directModifiedPaths(); // ['nested.bar']
|
|
1462
|
+
* doc.modifiedPaths(); // ['nested', 'nested.bar']
|
|
1463
|
+
*
|
|
1464
|
+
* @return {Array}
|
|
1465
|
+
* @api public
|
|
1466
|
+
*/
|
|
1467
|
+
|
|
1468
|
+
Document.prototype.directModifiedPaths = function() {
|
|
1469
|
+
return Object.keys(this.$__.activePaths.states.modify);
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* Returns true if the given path is nullish or only contains empty objects.
|
|
1474
|
+
* Useful for determining whether this subdoc will get stripped out by the
|
|
1475
|
+
* [minimize option](/docs/guide.html#minimize).
|
|
1476
|
+
*
|
|
1477
|
+
* ####Example:
|
|
1478
|
+
* const schema = new Schema({ nested: { foo: String } });
|
|
1479
|
+
* const Model = mongoose.model('Test', schema);
|
|
1480
|
+
* const doc = new Model({});
|
|
1481
|
+
* doc.$isEmpty('nested'); // true
|
|
1482
|
+
* doc.nested.$isEmpty(); // true
|
|
1483
|
+
*
|
|
1484
|
+
* doc.nested.foo = 'bar';
|
|
1485
|
+
* doc.$isEmpty('nested'); // false
|
|
1486
|
+
* doc.nested.$isEmpty(); // false
|
|
1487
|
+
*
|
|
1488
|
+
* @memberOf Document
|
|
1489
|
+
* @instance
|
|
1490
|
+
* @api public
|
|
1491
|
+
* @method $isEmpty
|
|
1492
|
+
* @return {Boolean}
|
|
1493
|
+
*/
|
|
1494
|
+
|
|
1495
|
+
Document.prototype.$isEmpty = function(path) {
|
|
1496
|
+
const isEmptyOptions = {
|
|
1497
|
+
minimize: true,
|
|
1498
|
+
virtuals: false,
|
|
1499
|
+
getters: false,
|
|
1500
|
+
transform: false
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
if (arguments.length > 0) {
|
|
1504
|
+
const v = this.get(path);
|
|
1505
|
+
if (v == null) {
|
|
1506
|
+
return true;
|
|
1507
|
+
}
|
|
1508
|
+
if (typeof v !== 'object') {
|
|
1509
|
+
return false;
|
|
1510
|
+
}
|
|
1511
|
+
if (utils.isPOJO(v)) {
|
|
1512
|
+
return _isEmpty(v);
|
|
1513
|
+
}
|
|
1514
|
+
return Object.keys(v.toObject(isEmptyOptions)).length === 0;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
return Object.keys(this.toObject(isEmptyOptions)).length === 0;
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
function _isEmpty(v) {
|
|
1521
|
+
if (v == null) {
|
|
1522
|
+
return true;
|
|
1523
|
+
}
|
|
1524
|
+
if (typeof v !== 'object' || Array.isArray(v)) {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
for (const key of Object.keys(v)) {
|
|
1528
|
+
if (!_isEmpty(v[key])) {
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return true;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1423
1535
|
/**
|
|
1424
1536
|
* Returns the list of paths that have been modified.
|
|
1425
1537
|
*
|
|
@@ -1757,7 +1869,7 @@ Document.prototype.validate = function(options, callback) {
|
|
|
1757
1869
|
options = null;
|
|
1758
1870
|
}
|
|
1759
1871
|
|
|
1760
|
-
return utils.promiseOrCallback(callback, cb => this.$__validate(function(error) {
|
|
1872
|
+
return utils.promiseOrCallback(callback, cb => this.$__validate(options, function(error) {
|
|
1761
1873
|
cb(error);
|
|
1762
1874
|
}), this.constructor.events);
|
|
1763
1875
|
};
|
|
@@ -1889,7 +2001,23 @@ function _getPathsToValidate(doc) {
|
|
|
1889
2001
|
* ignore
|
|
1890
2002
|
*/
|
|
1891
2003
|
|
|
1892
|
-
Document.prototype.$__validate = function(callback) {
|
|
2004
|
+
Document.prototype.$__validate = function(options, callback) {
|
|
2005
|
+
if (typeof options === 'function') {
|
|
2006
|
+
callback = options;
|
|
2007
|
+
options = null;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
const hasValidateModifiedOnlyOption = options &&
|
|
2011
|
+
(typeof options === 'object') &&
|
|
2012
|
+
('validateModifiedOnly' in options);
|
|
2013
|
+
|
|
2014
|
+
let shouldValidateModifiedOnly;
|
|
2015
|
+
if (hasValidateModifiedOnlyOption) {
|
|
2016
|
+
shouldValidateModifiedOnly = !!options.validateModifiedOnly;
|
|
2017
|
+
} else {
|
|
2018
|
+
shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1893
2021
|
const _this = this;
|
|
1894
2022
|
const _complete = () => {
|
|
1895
2023
|
const err = this.$__.validationError;
|
|
@@ -1911,7 +2039,9 @@ Document.prototype.$__validate = function(callback) {
|
|
|
1911
2039
|
|
|
1912
2040
|
// only validate required fields when necessary
|
|
1913
2041
|
const pathDetails = _getPathsToValidate(this);
|
|
1914
|
-
const paths =
|
|
2042
|
+
const paths = shouldValidateModifiedOnly ?
|
|
2043
|
+
pathDetails[0].filter((path) => this.isModified(path)) :
|
|
2044
|
+
pathDetails[0];
|
|
1915
2045
|
const skipSchemaValidators = pathDetails[1];
|
|
1916
2046
|
|
|
1917
2047
|
if (paths.length === 0) {
|
|
@@ -1975,7 +2105,7 @@ Document.prototype.$__validate = function(callback) {
|
|
|
1975
2105
|
_this.invalidate(path, err, undefined, true);
|
|
1976
2106
|
}
|
|
1977
2107
|
--total || complete();
|
|
1978
|
-
}, scope, { skipSchemaValidators: skipSchemaValidators[path] });
|
|
2108
|
+
}, scope, { skipSchemaValidators: skipSchemaValidators[path], path: path });
|
|
1979
2109
|
});
|
|
1980
2110
|
};
|
|
1981
2111
|
|
|
@@ -2006,16 +2136,29 @@ Document.prototype.$__validate = function(callback) {
|
|
|
2006
2136
|
* @api public
|
|
2007
2137
|
*/
|
|
2008
2138
|
|
|
2009
|
-
Document.prototype.validateSync = function(pathsToValidate) {
|
|
2139
|
+
Document.prototype.validateSync = function(pathsToValidate, options) {
|
|
2010
2140
|
const _this = this;
|
|
2011
2141
|
|
|
2142
|
+
const hasValidateModifiedOnlyOption = options &&
|
|
2143
|
+
(typeof options === 'object') &&
|
|
2144
|
+
('validateModifiedOnly' in options);
|
|
2145
|
+
|
|
2146
|
+
let shouldValidateModifiedOnly;
|
|
2147
|
+
if (hasValidateModifiedOnlyOption) {
|
|
2148
|
+
shouldValidateModifiedOnly = !!options.validateModifiedOnly;
|
|
2149
|
+
} else {
|
|
2150
|
+
shouldValidateModifiedOnly = this.schema.options.validateModifiedOnly;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2012
2153
|
if (typeof pathsToValidate === 'string') {
|
|
2013
2154
|
pathsToValidate = pathsToValidate.split(' ');
|
|
2014
2155
|
}
|
|
2015
2156
|
|
|
2016
2157
|
// only validate required fields when necessary
|
|
2017
2158
|
const pathDetails = _getPathsToValidate(this);
|
|
2018
|
-
let paths =
|
|
2159
|
+
let paths = shouldValidateModifiedOnly ?
|
|
2160
|
+
pathDetails[0].filter((path) => this.isModified(path)) :
|
|
2161
|
+
pathDetails[0];
|
|
2019
2162
|
const skipSchemaValidators = pathDetails[1];
|
|
2020
2163
|
|
|
2021
2164
|
if (pathsToValidate && pathsToValidate.length) {
|
|
@@ -2047,7 +2190,8 @@ Document.prototype.validateSync = function(pathsToValidate) {
|
|
|
2047
2190
|
|
|
2048
2191
|
const val = _this.getValue(path);
|
|
2049
2192
|
const err = p.doValidateSync(val, _this, {
|
|
2050
|
-
skipSchemaValidators: skipSchemaValidators[path]
|
|
2193
|
+
skipSchemaValidators: skipSchemaValidators[path],
|
|
2194
|
+
path: path
|
|
2051
2195
|
});
|
|
2052
2196
|
if (err && (!p.$isMongooseDocumentArray || err.$isArrayValidatorError)) {
|
|
2053
2197
|
if (p.$isSingleNested &&
|
|
@@ -3110,7 +3254,6 @@ Document.prototype.populated = function(path, val, options) {
|
|
|
3110
3254
|
}
|
|
3111
3255
|
|
|
3112
3256
|
// internal
|
|
3113
|
-
|
|
3114
3257
|
if (val === true) {
|
|
3115
3258
|
if (!this.$__.populated) {
|
|
3116
3259
|
return undefined;
|
|
@@ -3120,6 +3263,22 @@ Document.prototype.populated = function(path, val, options) {
|
|
|
3120
3263
|
|
|
3121
3264
|
this.$__.populated || (this.$__.populated = {});
|
|
3122
3265
|
this.$__.populated[path] = {value: val, options: options};
|
|
3266
|
+
|
|
3267
|
+
// If this was a nested populate, make sure each populated doc knows
|
|
3268
|
+
// about its populated children (gh-7685)
|
|
3269
|
+
const pieces = path.split('.');
|
|
3270
|
+
for (let i = 0; i < pieces.length - 1; ++i) {
|
|
3271
|
+
const subpath = pieces.slice(0, i + 1).join('.');
|
|
3272
|
+
const subdoc = this.get(subpath);
|
|
3273
|
+
if (subdoc != null && subdoc.$__ != null && this.populated(subpath)) {
|
|
3274
|
+
const rest = pieces.slice(i + 1).join('.');
|
|
3275
|
+
subdoc.populated(rest, val, options);
|
|
3276
|
+
// No need to continue because the above recursion should take care of
|
|
3277
|
+
// marking the rest of the docs as populated
|
|
3278
|
+
break;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3123
3282
|
return val;
|
|
3124
3283
|
};
|
|
3125
3284
|
|
|
@@ -20,8 +20,9 @@ const util = require('util');
|
|
|
20
20
|
* @api private
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
function NativeCollection() {
|
|
23
|
+
function NativeCollection(name, options) {
|
|
24
24
|
this.collection = null;
|
|
25
|
+
this.Promise = options.Promise || Promise;
|
|
25
26
|
MongooseCollection.apply(this, arguments);
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -115,9 +116,10 @@ const syncCollectionMethods = { watch: true };
|
|
|
115
116
|
function iter(i) {
|
|
116
117
|
NativeCollection.prototype[i] = function() {
|
|
117
118
|
const collection = this.collection;
|
|
118
|
-
const args = arguments;
|
|
119
|
+
const args = Array.from(arguments);
|
|
119
120
|
const _this = this;
|
|
120
121
|
const debug = get(_this, 'conn.base.options.debug');
|
|
122
|
+
const lastArg = arguments[arguments.length - 1];
|
|
121
123
|
|
|
122
124
|
// If user force closed, queueing will hang forever. See #5664
|
|
123
125
|
if (this.opts.$wasForceClosed) {
|
|
@@ -127,8 +129,18 @@ function iter(i) {
|
|
|
127
129
|
if (syncCollectionMethods[i]) {
|
|
128
130
|
throw new Error('Collection method ' + i + ' is synchronous');
|
|
129
131
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
if (typeof lastArg === 'function') {
|
|
133
|
+
this.addQueue(i, args);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
return new this.Promise((resolve, reject) => {
|
|
137
|
+
this.addQueue(i, [].concat(args).concat([(err, res) => {
|
|
138
|
+
if (err != null) {
|
|
139
|
+
return reject(err);
|
|
140
|
+
}
|
|
141
|
+
resolve(res);
|
|
142
|
+
}]));
|
|
143
|
+
});
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
if (debug) {
|
|
@@ -8,4 +8,4 @@ exports.Binary = require('./binary');
|
|
|
8
8
|
exports.Collection = require('./collection');
|
|
9
9
|
exports.Decimal128 = require('./decimal128');
|
|
10
10
|
exports.ObjectId = require('./objectid');
|
|
11
|
-
exports.ReadPreference = require('./ReadPreference');
|
|
11
|
+
exports.ReadPreference = require('./ReadPreference');
|
|
@@ -9,6 +9,9 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
|
|
|
9
9
|
const skipDocArrays = options.skipDocArrays;
|
|
10
10
|
|
|
11
11
|
let deleted = 0;
|
|
12
|
+
if (!doc) {
|
|
13
|
+
return deleted;
|
|
14
|
+
}
|
|
12
15
|
for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) {
|
|
13
16
|
if (skipDocArrays) {
|
|
14
17
|
const schemaType = doc.schema.path(modifiedPath);
|
|
@@ -100,6 +100,21 @@ function defineKey(prop, subprops, prototype, prefix, keys, options) {
|
|
|
100
100
|
value: true
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
const _isEmptyOptions = Object.freeze({
|
|
104
|
+
minimize: true,
|
|
105
|
+
virtuals: false,
|
|
106
|
+
getters: false,
|
|
107
|
+
transform: false
|
|
108
|
+
});
|
|
109
|
+
Object.defineProperty(nested, '$isEmpty', {
|
|
110
|
+
enumerable: false,
|
|
111
|
+
configurable: true,
|
|
112
|
+
writable: false,
|
|
113
|
+
value: function() {
|
|
114
|
+
return Object.keys(this.get(path, null, _isEmptyOptions) || {}).length === 0;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
103
118
|
compile(subprops, nested, path, options);
|
|
104
119
|
this.$__.getters[path] = nested;
|
|
105
120
|
}
|
|
@@ -140,7 +155,7 @@ function getOwnPropertyDescriptors(object) {
|
|
|
140
155
|
delete result[key];
|
|
141
156
|
return;
|
|
142
157
|
}
|
|
143
|
-
result[key].enumerable = ['isNew', '$__', 'errors', '_doc'].indexOf(key) === -1;
|
|
158
|
+
result[key].enumerable = ['isNew', '$__', 'errors', '_doc', '$locals'].indexOf(key) === -1;
|
|
144
159
|
});
|
|
145
160
|
|
|
146
161
|
return result;
|
|
@@ -18,8 +18,12 @@ module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) {
|
|
|
18
18
|
const subpath = parts.slice(0, i + 1).join('.');
|
|
19
19
|
schema = doc.schema.path(subpath);
|
|
20
20
|
if (schema == null) {
|
|
21
|
+
type = 'adhocOrUndefined';
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
24
|
+
if (schema.instance === 'Mixed') {
|
|
25
|
+
return typeOnly ? 'real' : schema;
|
|
26
|
+
}
|
|
23
27
|
type = doc.schema.pathType(subpath);
|
|
24
28
|
if ((schema.$isSingleNested || schema.$isMongooseDocumentArrayElement) &&
|
|
25
29
|
schema.schema.discriminators != null) {
|
|
@@ -30,11 +34,7 @@ module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) {
|
|
|
30
34
|
continue;
|
|
31
35
|
}
|
|
32
36
|
const rest = parts.slice(i + 1).join('.');
|
|
33
|
-
|
|
34
|
-
if (schema != null) {
|
|
35
|
-
type = discriminators[discriminatorKey].pathType(rest);
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
37
|
+
return getEmbeddedDiscriminatorPath(doc.get(subpath), rest, options);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|