mongoose 5.0.6 → 5.0.10
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 +41 -0
- package/README.md +20 -18
- package/browser.js +6 -0
- package/lib/aggregate.js +72 -0
- package/lib/cast.js +34 -6
- package/lib/collection.js +16 -0
- package/lib/connection.js +8 -2
- package/lib/cursor/AggregationCursor.js +12 -40
- package/lib/cursor/QueryCursor.js +1 -1
- package/lib/document.js +84 -18
- package/lib/drivers/node-mongodb-native/connection.js +10 -1
- package/lib/index.js +2 -1
- package/lib/model.js +5 -3
- package/lib/query.js +84 -20
- package/lib/queryhelpers.js +36 -5
- package/lib/schema/array.js +17 -6
- package/lib/schema/documentarray.js +13 -10
- package/lib/schema/embedded.js +36 -7
- package/lib/schema/objectid.js +2 -0
- package/lib/schema.js +30 -25
- package/lib/schematype.js +29 -6
- package/lib/services/model/discriminator.js +10 -5
- package/lib/services/query/castUpdate.js +28 -8
- package/lib/types/documentarray.js +25 -8
- package/lib/utils.js +1 -1
- package/migrating_to_5.md +47 -2
- package/package.json +6 -6
- package/lib/document_provider.web.js +0 -17
- package/mongoose5_transparent.png +0 -0
package/History.md
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
5.0.10 / 2018-03-12
|
|
2
|
+
===================
|
|
3
|
+
* docs(schematype): add notes re: running setters on queries #6209
|
|
4
|
+
* docs: fix typo #6208 [kamagatos](https://github.com/kamagatos)
|
|
5
|
+
* fix(query): only call setters once on query filter props for findOneAndUpdate and findOneAndRemove #6203
|
|
6
|
+
* docs: elaborate on connection string changes in migration guide #6193
|
|
7
|
+
* fix(document): skip applyDefaults if subdoc is null #6187
|
|
8
|
+
* docs: fix schematypes docs and link to them #6176
|
|
9
|
+
* docs(faq): add FAQs re: array defaults and casting aggregation pipelines #6184 #6176 #6170 [lineus](https://github.com/lineus)
|
|
10
|
+
* fix(document): ensure primitive defaults are set and built-in default functions run before setters #6155
|
|
11
|
+
* fix(query): handle single embedded embedded discriminators in castForQuery #6027
|
|
12
|
+
|
|
13
|
+
5.0.9 / 2018-03-05
|
|
14
|
+
==================
|
|
15
|
+
* perf: bump mongodb -> 3.0.4 to fix SSL perf issue #6065
|
|
16
|
+
|
|
17
|
+
5.0.8 / 2018-03-03
|
|
18
|
+
==================
|
|
19
|
+
* docs: remove obsolete references to `emitIndexErrors` #6186 [isaackwan](https://github.com/isaackwan)
|
|
20
|
+
* fix(query): don't cast findOne() until exec() so setters don't run twice #6157
|
|
21
|
+
* fix: remove document_provider.web.js file #6186
|
|
22
|
+
* fix(discriminator): support custom discriminator model names #6100 [wentout](https://github.com/wentout)
|
|
23
|
+
* fix: support caching calls to `useDb()` #6036 [rocketspacer](https://github.com/rocketspacer)
|
|
24
|
+
* fix(query): add omitUndefined option so setDefaultsOnInsert can kick in on undefined #6034
|
|
25
|
+
* fix: upgrade mongodb -> 3.0.3 for reconnectTries: 0 blocking process exit fix #6028
|
|
26
|
+
|
|
27
|
+
5.0.7 / 2018-02-23
|
|
28
|
+
==================
|
|
29
|
+
* fix: support eachAsync options with aggregation cursor #6169 #6168 [vichle](https://github.com/vichle)
|
|
30
|
+
* docs: fix link to MongoDB compound indexes docs #6162 [br0p0p](https://github.com/br0p0p)
|
|
31
|
+
* docs(aggregate): use eachAsync instead of incorrect `each()` #6160 [simllll](https://github.com/simllll)
|
|
32
|
+
* chore: fix benchmarks #6158 [pradel](https://github.com/pradel)
|
|
33
|
+
* docs: remove dead link to old blog post #6154 [markstos](https://github.com/markstos)
|
|
34
|
+
* fix: don't convert dates to numbers when updating mixed path #6146 #6145 [s4rbagamble](https://github.com/s4rbagamble)
|
|
35
|
+
* feat(aggregate): add replaceRoot, count, sortByCount helpers #6142 [jakesjews](https://github.com/jakesjews)
|
|
36
|
+
* fix(document): add includedChildren flag to modifiedPaths() #6134
|
|
37
|
+
* perf: don't create wrapper function if no hooks specified #6126
|
|
38
|
+
* fix(schema): allow indexes on single nested subdocs for geoJSON #6113
|
|
39
|
+
* fix(document): allow depopulating all fields #6073
|
|
40
|
+
* feat(mongoose): add support for `useFindAndModify` option on singleton #5616
|
|
41
|
+
|
|
1
42
|
5.0.6 / 2018-02-15
|
|
2
43
|
==================
|
|
3
44
|
* refactor(query.castUpdate): avoid creating error until necessary #6137
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Mongoose is a [MongoDB](https://www.mongodb.org/) object modeling tool designed
|
|
|
10
10
|
|
|
11
11
|
[mongoosejs.com](http://mongoosejs.com/)
|
|
12
12
|
|
|
13
|
+
[Mongoose 5.0.0](https://github.com/Automattic/mongoose/blob/master/History.md#500--2018-01-17) was released on January 17, 2018. You can find more details on backwards breaking changes in 5.0.0 on [GitHub](https://github.com/Automattic/mongoose/blob/master/migrating_to_5.md).
|
|
14
|
+
|
|
13
15
|
## Support
|
|
14
16
|
|
|
15
17
|
- [Stack Overflow](http://stackoverflow.com/questions/tagged/mongoose)
|
|
@@ -57,7 +59,7 @@ First, we need to define a connection. If your app uses only one database, you s
|
|
|
57
59
|
Both `connect` and `createConnection` take a `mongodb://` URI, or the parameters `host, database, port, options`.
|
|
58
60
|
|
|
59
61
|
```js
|
|
60
|
-
|
|
62
|
+
const mongoose = require('mongoose');
|
|
61
63
|
|
|
62
64
|
mongoose.connect('mongodb://localhost/my_database');
|
|
63
65
|
```
|
|
@@ -73,14 +75,14 @@ Once connected, the `open` event is fired on the `Connection` instance. If you'r
|
|
|
73
75
|
Models are defined through the `Schema` interface.
|
|
74
76
|
|
|
75
77
|
```js
|
|
76
|
-
|
|
78
|
+
const Schema = mongoose.Schema,
|
|
77
79
|
ObjectId = Schema.ObjectId;
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const BlogPost = new Schema({
|
|
82
|
+
author: ObjectId,
|
|
83
|
+
title: String,
|
|
84
|
+
body: String,
|
|
85
|
+
date: Date
|
|
84
86
|
});
|
|
85
87
|
```
|
|
86
88
|
|
|
@@ -100,7 +102,7 @@ Aside from defining the structure of your documents and the types of data you're
|
|
|
100
102
|
The following example shows some of these features:
|
|
101
103
|
|
|
102
104
|
```js
|
|
103
|
-
|
|
105
|
+
const Comment = new Schema({
|
|
104
106
|
name: { type: String, default: 'hahaha' },
|
|
105
107
|
age: { type: Number, min: 18, index: true },
|
|
106
108
|
bio: { type: String, match: /[a-z]/ },
|
|
@@ -127,19 +129,19 @@ Take a look at the example in `examples/schema.js` for an end-to-end example of
|
|
|
127
129
|
Once we define a model through `mongoose.model('ModelName', mySchema)`, we can access it through the same function
|
|
128
130
|
|
|
129
131
|
```js
|
|
130
|
-
|
|
132
|
+
const myModel = mongoose.model('ModelName');
|
|
131
133
|
```
|
|
132
134
|
|
|
133
135
|
Or just do it all at once
|
|
134
136
|
|
|
135
137
|
```js
|
|
136
|
-
|
|
138
|
+
const MyModel = mongoose.model('ModelName', mySchema);
|
|
137
139
|
```
|
|
138
140
|
|
|
139
141
|
The first argument is the _singular_ name of the collection your model is for. **Mongoose automatically looks for the _plural_ version of your model name.** For example, if you use
|
|
140
142
|
|
|
141
143
|
```js
|
|
142
|
-
|
|
144
|
+
const MyModel = mongoose.model('Ticket', mySchema);
|
|
143
145
|
```
|
|
144
146
|
|
|
145
147
|
Then Mongoose will create the model for your __tickets__ collection, not your __ticket__ collection.
|
|
@@ -147,7 +149,7 @@ Then Mongoose will create the model for your __tickets__ collection, not your __
|
|
|
147
149
|
Once we have our model, we can then instantiate it, and save it:
|
|
148
150
|
|
|
149
151
|
```js
|
|
150
|
-
|
|
152
|
+
const instance = new MyModel();
|
|
151
153
|
instance.my.key = 'hello';
|
|
152
154
|
instance.save(function (err) {
|
|
153
155
|
//
|
|
@@ -167,18 +169,18 @@ You can also `findOne`, `findById`, `update`, etc. For more details check out [t
|
|
|
167
169
|
**Important!** If you opened a separate connection using `mongoose.createConnection()` but attempt to access the model through `mongoose.model('ModelName')` it will not work as expected since it is not hooked up to an active db connection. In this case access your model through the connection you created:
|
|
168
170
|
|
|
169
171
|
```js
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
const conn = mongoose.createConnection('your connection string');
|
|
173
|
+
const MyModel = conn.model('ModelName', schema);
|
|
174
|
+
const m = new MyModel;
|
|
173
175
|
m.save(); // works
|
|
174
176
|
```
|
|
175
177
|
|
|
176
178
|
vs
|
|
177
179
|
|
|
178
180
|
```js
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
const conn = mongoose.createConnection('your connection string');
|
|
182
|
+
const MyModel = mongoose.model('ModelName', schema);
|
|
183
|
+
const m = new MyModel;
|
|
182
184
|
m.save(); // does not work b/c the default connection object was never connected
|
|
183
185
|
```
|
|
184
186
|
|
package/browser.js
ADDED
package/lib/aggregate.js
CHANGED
|
@@ -339,6 +339,78 @@ Aggregate.prototype.unwind = function() {
|
|
|
339
339
|
return this.append.apply(this, res);
|
|
340
340
|
};
|
|
341
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Appends a new $replaceRoot operator to this aggregate pipeline.
|
|
344
|
+
*
|
|
345
|
+
* Note that the `$replaceRoot` operator requires the new root to start with '$'.
|
|
346
|
+
* Mongoose will prepend '$' if the specified field doesn't start '$'.
|
|
347
|
+
*
|
|
348
|
+
* ####Examples:
|
|
349
|
+
*
|
|
350
|
+
* aggregate.replaceRoot("user");
|
|
351
|
+
*
|
|
352
|
+
* @see $replaceRoot https://docs.mongodb.org/manual/reference/operator/aggregation/replaceRoot
|
|
353
|
+
* @param {String} the field which will become the new root document
|
|
354
|
+
* @return {Aggregate}
|
|
355
|
+
* @api public
|
|
356
|
+
*/
|
|
357
|
+
|
|
358
|
+
Aggregate.prototype.replaceRoot = function(newRoot) {
|
|
359
|
+
return this.append({
|
|
360
|
+
$replaceRoot: {
|
|
361
|
+
newRoot: (newRoot && newRoot.charAt(0) === '$') ? newRoot : '$' + newRoot
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Appends a new $count operator to this aggregate pipeline.
|
|
368
|
+
*
|
|
369
|
+
* ####Examples:
|
|
370
|
+
*
|
|
371
|
+
* aggregate.count("userCount");
|
|
372
|
+
*
|
|
373
|
+
* @see $count https://docs.mongodb.org/manual/reference/operator/aggregation/count
|
|
374
|
+
* @param {String} the name of the count field
|
|
375
|
+
* @return {Aggregate}
|
|
376
|
+
* @api public
|
|
377
|
+
*/
|
|
378
|
+
|
|
379
|
+
Aggregate.prototype.count = function(countName) {
|
|
380
|
+
return this.append({ $count: countName });
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Appends a new $sortByCount operator to this aggregate pipeline. Accepts either a string field name
|
|
385
|
+
* or a pipeline object.
|
|
386
|
+
*
|
|
387
|
+
* Note that the `$sortByCount` operator requires the new root to start with '$'.
|
|
388
|
+
* Mongoose will prepend '$' if the specified field name doesn't start with '$'.
|
|
389
|
+
*
|
|
390
|
+
* ####Examples:
|
|
391
|
+
*
|
|
392
|
+
* aggregate.sortByCount('users');
|
|
393
|
+
* aggregate.sortByCount({ $mergeObjects: [ "$employee", "$business" ] })
|
|
394
|
+
*
|
|
395
|
+
* @see $sortByCount https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/
|
|
396
|
+
* @param {Object|String} arg
|
|
397
|
+
* @return {Aggregate} this
|
|
398
|
+
* @api public
|
|
399
|
+
*/
|
|
400
|
+
|
|
401
|
+
Aggregate.prototype.sortByCount = function(arg) {
|
|
402
|
+
if (arg && typeof arg === 'object') {
|
|
403
|
+
return this.append({ $sortByCount: arg });
|
|
404
|
+
} else if (typeof arg === 'string') {
|
|
405
|
+
return this.append({
|
|
406
|
+
$sortByCount: (arg && arg.charAt(0) === '$') ? arg : '$' + arg
|
|
407
|
+
});
|
|
408
|
+
} else {
|
|
409
|
+
throw new TypeError('Invalid arg "' + arg + '" to sortByCount(), ' +
|
|
410
|
+
'must be string or object');
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
342
414
|
/**
|
|
343
415
|
* Appends new custom $lookup operator(s) to this aggregate pipeline.
|
|
344
416
|
*
|
package/lib/cast.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/*!
|
|
2
4
|
* Module dependencies.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const StrictModeError = require('./error/strict');
|
|
8
|
+
const Types = require('./schema/index');
|
|
9
|
+
const get = require('lodash.get');
|
|
10
|
+
const util = require('util');
|
|
11
|
+
const utils = require('./utils');
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Handles internal casting for query filters.
|
|
@@ -67,6 +70,31 @@ module.exports = function cast(schema, obj, options, context) {
|
|
|
67
70
|
|
|
68
71
|
schematype = schema.path(path);
|
|
69
72
|
|
|
73
|
+
// Check for embedded discriminator paths
|
|
74
|
+
if (!schematype) {
|
|
75
|
+
let split = path.split('.');
|
|
76
|
+
let j = split.length;
|
|
77
|
+
while (j--) {
|
|
78
|
+
let pathFirstHalf = split.slice(0, j).join('.');
|
|
79
|
+
let pathLastHalf = split.slice(j).join('.');
|
|
80
|
+
let _schematype = schema.path(pathFirstHalf);
|
|
81
|
+
let discriminatorKey = get(_schematype, 'schema.options.discriminatorKey');
|
|
82
|
+
// gh-6027: if we haven't found the schematype but this path is
|
|
83
|
+
// underneath an embedded discriminator and the embedded discriminator
|
|
84
|
+
// key is in the query, use the embedded discriminator schema
|
|
85
|
+
if (_schematype != null &&
|
|
86
|
+
get(_schematype, 'schema.discriminators') != null &&
|
|
87
|
+
discriminatorKey != null &&
|
|
88
|
+
pathLastHalf !== discriminatorKey) {
|
|
89
|
+
const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey);
|
|
90
|
+
if (discriminatorVal) {
|
|
91
|
+
schematype = _schematype.schema.discriminators[discriminatorVal].
|
|
92
|
+
path(pathLastHalf);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
if (!schematype) {
|
|
71
99
|
// Handle potential embedded array queries
|
|
72
100
|
var split = path.split('.'),
|
|
@@ -206,7 +234,7 @@ module.exports = function cast(schema, obj, options, context) {
|
|
|
206
234
|
} else if (options.strictQuery) {
|
|
207
235
|
delete obj[path];
|
|
208
236
|
}
|
|
209
|
-
} else if (val
|
|
237
|
+
} else if (val == null) {
|
|
210
238
|
obj[path] = null;
|
|
211
239
|
continue;
|
|
212
240
|
} else if (val.constructor.name === 'Object') {
|
package/lib/collection.js
CHANGED
|
@@ -161,6 +161,22 @@ Collection.prototype.findAndModify = function() {
|
|
|
161
161
|
throw new Error('Collection#findAndModify unimplemented by driver');
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Abstract method that drivers must implement.
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
Collection.prototype.findOneAndUpdate = function() {
|
|
169
|
+
throw new Error('Collection#findOneAndUpdate unimplemented by driver');
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Abstract method that drivers must implement.
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
Collection.prototype.findOneAndDelete = function() {
|
|
177
|
+
throw new Error('Collection#findOneAndDelete unimplemented by driver');
|
|
178
|
+
};
|
|
179
|
+
|
|
164
180
|
/**
|
|
165
181
|
* Abstract method that drivers must implement.
|
|
166
182
|
*/
|
package/lib/connection.js
CHANGED
|
@@ -59,7 +59,8 @@ function Connection(base) {
|
|
|
59
59
|
this.pass = null;
|
|
60
60
|
this.name = null;
|
|
61
61
|
this.options = null;
|
|
62
|
-
this.otherDbs = [];
|
|
62
|
+
this.otherDbs = []; // FIXME: To be replaced with relatedDbs
|
|
63
|
+
this.relatedDbs = {}; // Hashmap of other dbs that share underlying connection
|
|
63
64
|
this.states = STATES;
|
|
64
65
|
this._readyState = STATES.disconnected;
|
|
65
66
|
this._closeCalled = false;
|
|
@@ -103,11 +104,16 @@ Object.defineProperty(Connection.prototype, 'readyState', {
|
|
|
103
104
|
|
|
104
105
|
if (this._readyState !== val) {
|
|
105
106
|
this._readyState = val;
|
|
106
|
-
// loop over the otherDbs on this connection and change their state
|
|
107
|
+
// [legacy] loop over the otherDbs on this connection and change their state
|
|
107
108
|
for (var i = 0; i < this.otherDbs.length; i++) {
|
|
108
109
|
this.otherDbs[i].readyState = val;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// loop over relatedDbs on this connection and change their state
|
|
113
|
+
for (var k in this.relatedDbs) {
|
|
114
|
+
this.relatedDbs[k].readyState = val;
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
if (STATES.connected === val) {
|
|
112
118
|
this._hasOpened = true;
|
|
113
119
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
var Readable = require('stream').Readable;
|
|
6
|
+
var eachAsync = require('../services/cursor/eachAsync');
|
|
6
7
|
var util = require('util');
|
|
7
8
|
var utils = require('../utils');
|
|
8
9
|
|
|
@@ -10,7 +11,7 @@ var utils = require('../utils');
|
|
|
10
11
|
* An AggregationCursor is a concurrency primitive for processing aggregation
|
|
11
12
|
* results one document at a time. It is analogous to QueryCursor.
|
|
12
13
|
*
|
|
13
|
-
* An AggregationCursor fulfills the
|
|
14
|
+
* An AggregationCursor fulfills the Node.js streams3 API,
|
|
14
15
|
* in addition to several other mechanisms for loading documents from MongoDB
|
|
15
16
|
* one at a time.
|
|
16
17
|
*
|
|
@@ -184,52 +185,23 @@ AggregationCursor.prototype.next = function(callback) {
|
|
|
184
185
|
* Returns a promise that resolves when done.
|
|
185
186
|
*
|
|
186
187
|
* @param {Function} fn
|
|
188
|
+
* @param {Object} [options]
|
|
189
|
+
* @param {Number} [options.parallel] the number of promises to execute in parallel. Defaults to 1.
|
|
187
190
|
* @param {Function} [callback] executed when all docs have been processed
|
|
188
191
|
* @return {Promise}
|
|
189
192
|
* @api public
|
|
190
193
|
* @method eachAsync
|
|
191
194
|
*/
|
|
192
195
|
|
|
193
|
-
AggregationCursor.prototype.eachAsync = function(fn, callback) {
|
|
194
|
-
var
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
} else {
|
|
201
|
-
callback(null);
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
var iterate = callback => {
|
|
206
|
-
return _next(this, function(error, doc) {
|
|
207
|
-
if (error) {
|
|
208
|
-
return callback(error);
|
|
209
|
-
}
|
|
210
|
-
if (!doc) {
|
|
211
|
-
return callback(null);
|
|
212
|
-
}
|
|
213
|
-
handleNextResult(doc, function(error) {
|
|
214
|
-
if (error) {
|
|
215
|
-
return callback(error);
|
|
216
|
-
}
|
|
217
|
-
// Make sure to clear the stack re: gh-4697
|
|
218
|
-
setTimeout(function() {
|
|
219
|
-
iterate(callback);
|
|
220
|
-
}, 0);
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
};
|
|
196
|
+
AggregationCursor.prototype.eachAsync = function(fn, opts, callback) {
|
|
197
|
+
var _this = this;
|
|
198
|
+
if (typeof opts === 'function') {
|
|
199
|
+
callback = opts;
|
|
200
|
+
opts = {};
|
|
201
|
+
}
|
|
202
|
+
opts = opts || {};
|
|
224
203
|
|
|
225
|
-
return
|
|
226
|
-
iterate(function(error) {
|
|
227
|
-
if (error) {
|
|
228
|
-
return cb(error);
|
|
229
|
-
}
|
|
230
|
-
cb(null);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
204
|
+
return eachAsync(function(cb) { return _next(_this, cb); }, fn, opts, callback);
|
|
233
205
|
};
|
|
234
206
|
|
|
235
207
|
/**
|
|
@@ -10,7 +10,7 @@ var utils = require('../utils');
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* A QueryCursor is a concurrency primitive for processing query results
|
|
13
|
-
* one document at a time. A QueryCursor fulfills the
|
|
13
|
+
* one document at a time. A QueryCursor fulfills the Node.js streams3 API,
|
|
14
14
|
* in addition to several other mechanisms for loading documents from MongoDB
|
|
15
15
|
* one at a time.
|
|
16
16
|
*
|
package/lib/document.js
CHANGED
|
@@ -87,6 +87,10 @@ function Document(obj, fields, skipId, options) {
|
|
|
87
87
|
|
|
88
88
|
this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren);
|
|
89
89
|
|
|
90
|
+
// By default, defaults get applied **before** setting initial values
|
|
91
|
+
// Re: gh-6155
|
|
92
|
+
$__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true);
|
|
93
|
+
|
|
90
94
|
if (obj) {
|
|
91
95
|
if (obj instanceof Document) {
|
|
92
96
|
this.isNew = obj.isNew;
|
|
@@ -99,7 +103,10 @@ function Document(obj, fields, skipId, options) {
|
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
// Function defaults get applied **after** setting initial values so they
|
|
107
|
+
// see the full doc rather than an empty one, unless they opt out.
|
|
108
|
+
// Re: gh-3781, gh-6155
|
|
109
|
+
$__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, false);
|
|
103
110
|
|
|
104
111
|
this.$__._id = this._id;
|
|
105
112
|
|
|
@@ -202,7 +209,7 @@ function $__hasIncludedChildren(fields) {
|
|
|
202
209
|
* ignore
|
|
203
210
|
*/
|
|
204
211
|
|
|
205
|
-
function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
|
|
212
|
+
function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren, isBeforeSetters) {
|
|
206
213
|
const paths = Object.keys(doc.schema.paths);
|
|
207
214
|
const plen = paths.length;
|
|
208
215
|
|
|
@@ -222,6 +229,10 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
|
|
|
222
229
|
let doc_ = doc._doc;
|
|
223
230
|
|
|
224
231
|
for (let j = 0; j < len; ++j) {
|
|
232
|
+
if (doc_ == null) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
225
236
|
let piece = path[j];
|
|
226
237
|
curPath += (!curPath.length ? '' : '.') + piece;
|
|
227
238
|
|
|
@@ -242,6 +253,18 @@ function $__applyDefaults(doc, fields, skipId, exclude, hasIncludedChildren) {
|
|
|
242
253
|
break;
|
|
243
254
|
}
|
|
244
255
|
|
|
256
|
+
if (typeof type.defaultValue === 'function') {
|
|
257
|
+
if (!type.defaultValue.$runBeforeSetters && isBeforeSetters) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
if (type.defaultValue.$runBeforeSetters && !isBeforeSetters) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
} else if (!isBeforeSetters) {
|
|
264
|
+
// Non-function defaults should always run **before** setters
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
245
268
|
if (fields && exclude !== null) {
|
|
246
269
|
if (exclude === true) {
|
|
247
270
|
// apply defaults to all non-excluded fields
|
|
@@ -1111,19 +1134,43 @@ Document.prototype.$ignore = function(path) {
|
|
|
1111
1134
|
/**
|
|
1112
1135
|
* Returns the list of paths that have been modified.
|
|
1113
1136
|
*
|
|
1137
|
+
* @param {Object} [options]
|
|
1138
|
+
* @param {Boolean} [options.includeChildren=false] if true, returns children of modified paths as well. For example, if false, the list of modified paths for `doc.colors = { primary: 'blue' };` will **not** contain `colors.primary`
|
|
1114
1139
|
* @return {Array}
|
|
1115
1140
|
* @api public
|
|
1116
1141
|
*/
|
|
1117
1142
|
|
|
1118
|
-
Document.prototype.modifiedPaths = function() {
|
|
1143
|
+
Document.prototype.modifiedPaths = function(options) {
|
|
1144
|
+
options = options || {};
|
|
1119
1145
|
var directModifiedPaths = Object.keys(this.$__.activePaths.states.modify);
|
|
1146
|
+
var _this = this;
|
|
1120
1147
|
return directModifiedPaths.reduce(function(list, path) {
|
|
1121
1148
|
var parts = path.split('.');
|
|
1122
|
-
|
|
1149
|
+
list = list.concat(parts.reduce(function(chains, part, i) {
|
|
1123
1150
|
return chains.concat(parts.slice(0, i).concat(part).join('.'));
|
|
1124
1151
|
}, []).filter(function(chain) {
|
|
1125
1152
|
return (list.indexOf(chain) === -1);
|
|
1126
1153
|
}));
|
|
1154
|
+
|
|
1155
|
+
if (!options.includeChildren) {
|
|
1156
|
+
return list;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
var cur = _this.get(path);
|
|
1160
|
+
if (cur != null && typeof cur === 'object') {
|
|
1161
|
+
if (cur._doc) {
|
|
1162
|
+
cur = cur._doc;
|
|
1163
|
+
}
|
|
1164
|
+
Object.keys(cur).
|
|
1165
|
+
filter(function(key) {
|
|
1166
|
+
return list.indexOf(path + '.' + key) === -1;
|
|
1167
|
+
}).
|
|
1168
|
+
forEach(function(key) {
|
|
1169
|
+
list.push(path + '.' + key);
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
return list;
|
|
1127
1174
|
}, []);
|
|
1128
1175
|
};
|
|
1129
1176
|
|
|
@@ -1497,8 +1544,8 @@ function _getPathsToValidate(doc) {
|
|
|
1497
1544
|
*/
|
|
1498
1545
|
|
|
1499
1546
|
Document.prototype.$__validate = function(callback) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1547
|
+
const _this = this;
|
|
1548
|
+
const _complete = function() {
|
|
1502
1549
|
var err = _this.$__.validationError;
|
|
1503
1550
|
_this.$__.validationError = undefined;
|
|
1504
1551
|
_this.emit('validate', _this);
|
|
@@ -1516,11 +1563,11 @@ Document.prototype.$__validate = function(callback) {
|
|
|
1516
1563
|
};
|
|
1517
1564
|
|
|
1518
1565
|
// only validate required fields when necessary
|
|
1519
|
-
|
|
1566
|
+
const paths = _getPathsToValidate(this);
|
|
1520
1567
|
|
|
1521
1568
|
if (paths.length === 0) {
|
|
1522
1569
|
return process.nextTick(function() {
|
|
1523
|
-
|
|
1570
|
+
const error = _complete();
|
|
1524
1571
|
if (error) {
|
|
1525
1572
|
return _this.schema.s.hooks.execPost('validate:error', _this, [ _this], { error: error }, function(error) {
|
|
1526
1573
|
callback(error);
|
|
@@ -1530,11 +1577,11 @@ Document.prototype.$__validate = function(callback) {
|
|
|
1530
1577
|
});
|
|
1531
1578
|
}
|
|
1532
1579
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1580
|
+
const validated = {};
|
|
1581
|
+
let total = 0;
|
|
1535
1582
|
|
|
1536
1583
|
var complete = function() {
|
|
1537
|
-
|
|
1584
|
+
const error = _complete();
|
|
1538
1585
|
if (error) {
|
|
1539
1586
|
return _this.schema.s.hooks.execPost('validate:error', _this, [ _this], { error: error }, function(error) {
|
|
1540
1587
|
callback(error);
|
|
@@ -1552,7 +1599,7 @@ Document.prototype.$__validate = function(callback) {
|
|
|
1552
1599
|
total++;
|
|
1553
1600
|
|
|
1554
1601
|
process.nextTick(function() {
|
|
1555
|
-
|
|
1602
|
+
const p = _this.schema.path(path);
|
|
1556
1603
|
if (!p) {
|
|
1557
1604
|
return --total || complete();
|
|
1558
1605
|
}
|
|
@@ -1563,10 +1610,11 @@ Document.prototype.$__validate = function(callback) {
|
|
|
1563
1610
|
return;
|
|
1564
1611
|
}
|
|
1565
1612
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1613
|
+
const val = _this.getValue(path);
|
|
1614
|
+
const scope = path in _this.$__.pathsToScopes ?
|
|
1568
1615
|
_this.$__.pathsToScopes[path] :
|
|
1569
1616
|
_this;
|
|
1617
|
+
|
|
1570
1618
|
p.doValidate(val, function(err) {
|
|
1571
1619
|
if (err) {
|
|
1572
1620
|
_this.invalidate(path, err, undefined, true);
|
|
@@ -1576,8 +1624,8 @@ Document.prototype.$__validate = function(callback) {
|
|
|
1576
1624
|
});
|
|
1577
1625
|
};
|
|
1578
1626
|
|
|
1579
|
-
|
|
1580
|
-
for (
|
|
1627
|
+
const numPaths = paths.length;
|
|
1628
|
+
for (let i = 0; i < numPaths; ++i) {
|
|
1581
1629
|
validatePath(paths[i]);
|
|
1582
1630
|
}
|
|
1583
1631
|
};
|
|
@@ -2647,8 +2695,26 @@ Document.prototype.depopulate = function(path) {
|
|
|
2647
2695
|
if (typeof path === 'string') {
|
|
2648
2696
|
path = path.split(' ');
|
|
2649
2697
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2698
|
+
var i;
|
|
2699
|
+
var populatedIds;
|
|
2700
|
+
|
|
2701
|
+
if (arguments.length === 0) {
|
|
2702
|
+
// Depopulate all
|
|
2703
|
+
var keys = Object.keys(this.$__.populated);
|
|
2704
|
+
|
|
2705
|
+
for (i = 0; i < keys.length; i++) {
|
|
2706
|
+
populatedIds = this.populated(keys[i]);
|
|
2707
|
+
if (!populatedIds) {
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
delete this.$__.populated[keys[i]];
|
|
2711
|
+
this.$set(keys[i], populatedIds);
|
|
2712
|
+
}
|
|
2713
|
+
return this;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
for (i = 0; i < path.length; i++) {
|
|
2717
|
+
populatedIds = this.populated(path[i]);
|
|
2652
2718
|
if (!populatedIds) {
|
|
2653
2719
|
continue;
|
|
2654
2720
|
}
|
|
@@ -40,7 +40,10 @@ NativeConnection.prototype.__proto__ = MongooseConnection.prototype;
|
|
|
40
40
|
* @api public
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
NativeConnection.prototype.useDb = function(name) {
|
|
43
|
+
NativeConnection.prototype.useDb = function(name, options) {
|
|
44
|
+
// Return immediately if cached
|
|
45
|
+
if (options && options.useCache && this.relatedDbs[name]) return this.relatedDbs[name];
|
|
46
|
+
|
|
44
47
|
// we have to manually copy all of the attributes...
|
|
45
48
|
var newConn = new this.constructor();
|
|
46
49
|
newConn.name = name;
|
|
@@ -88,6 +91,12 @@ NativeConnection.prototype.useDb = function(name) {
|
|
|
88
91
|
this.otherDbs.push(newConn);
|
|
89
92
|
newConn.otherDbs.push(this);
|
|
90
93
|
|
|
94
|
+
// push onto the relatedDbs cache, this is used when state changes
|
|
95
|
+
if (options && options.useCache) {
|
|
96
|
+
this.relatedDbs[newConn.name] = newConn;
|
|
97
|
+
newConn.relatedDbs = this.relatedDbs;
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
return newConn;
|
|
92
101
|
};
|
|
93
102
|
|