monastery 1.35.0 → 1.36.2

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.
@@ -3,7 +3,7 @@ let rules = require('./rules')
3
3
 
4
4
  module.exports = {
5
5
 
6
- validate: function(data, opts, cb) {
6
+ validate: async function(data, opts, cb) {
7
7
  /**
8
8
  * Validates a model
9
9
  * @param {object} data
@@ -22,38 +22,44 @@ module.exports = {
22
22
  */
23
23
 
24
24
  // Optional cb and opts
25
- if (util.isFunction(opts)) { cb = opts; opts = undefined }
25
+ if (util.isFunction(opts)) {
26
+ cb = opts; opts = undefined
27
+ }
26
28
  if (cb && !util.isFunction(cb)) {
27
29
  throw new Error(`The callback passed to ${this.name}.validate() is not a function`)
28
30
  }
29
- data = util.deepCopy(data)
30
- opts = opts || {}
31
- opts.insert = !opts.update
32
- opts.action = opts.update ? 'update' : 'insert'
33
- opts.skipValidation = opts.skipValidation === true ? true : util.toArray(opts.skipValidation||[])
31
+ try {
32
+ data = util.deepCopy(data)
33
+ opts = opts || {}
34
+ opts.update = opts.update || opts.findOneAndUpdate
35
+ opts.insert = !opts.update
36
+ opts.skipValidation = opts.skipValidation === true ? true : util.toArray(opts.skipValidation||[])
37
+
38
+ // Get projection
39
+ if (opts.project) opts.projectionValidate = this._getProjectionFromProject(opts.project)
40
+ else opts.projectionValidate = this._getProjectionFromBlacklist(opts.update ? 'update' : 'insert', opts.blacklist)
34
41
 
35
- // Get projection
36
- if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
37
- else opts.projection = this._getProjectionFromBlacklist(opts.action, opts.blacklist)
42
+ // Hook: beforeValidate
43
+ await util.runSeries(this.beforeValidate.map(f => f.bind(opts, data)))
38
44
 
39
- // Run before hook, then recurse through the model's fields
40
- return util.runSeries(this.beforeValidate.map(f => f.bind(opts, data))).then(() => {
41
- return util.toArray(data).map(item => {
45
+ // Recurse and validate fields
46
+ let response = util.toArray(data).map(item => {
42
47
  let validated = this._validateFields(item, this.fields, item, opts, '')
43
48
  if (validated[0].length) throw validated[0]
44
49
  else return validated[1]
45
50
  })
46
51
 
47
- // Success/error
48
- }).then(data2 => {
49
- let response = util.isArray(data)? data2 : data2[0]
52
+ // Single document?
53
+ response = util.isArray(data)? response : response[0]
54
+
55
+ // Success/error
50
56
  if (cb) cb(null, response)
51
57
  else return Promise.resolve(response)
52
58
 
53
- }).catch(errs => {
54
- if (cb) cb(errs)
55
- else throw errs
56
- })
59
+ } catch (e) {
60
+ if (cb) cb(e)
61
+ else throw e
62
+ }
57
63
  },
58
64
 
59
65
  _getMostSpecificKeyMatchingPath: function(object, path) {
@@ -126,7 +132,7 @@ module.exports = {
126
132
  }
127
133
 
128
134
  // Ignore blacklisted
129
- if (this._pathBlacklisted(path3, opts.projection) && !schema.defaultOverride) return
135
+ if (this._pathBlacklisted(path3, opts.projectionValidate) && !schema.defaultOverride) return
130
136
  // Ignore insert only
131
137
  if (opts.update && schema.insertOnly) return
132
138
  // Ignore virtual fields
@@ -239,7 +245,7 @@ module.exports = {
239
245
  if (typeof value === 'undefined' && (!validateUndefined || !rule.validateUndefined)) return
240
246
 
241
247
  // Ignore null (if nullObject is set on objects or arrays)
242
- if (value === null && (field.isObject || field.isArray) && field.nullObject) return
248
+ if (value === null && (field.isObject || field.isArray) && field.nullObject && !rule.validateNull) return
243
249
 
244
250
  // Ignore null
245
251
  if (value === null && !(field.isObject || field.isArray) && !rule.validateNull) return
package/lib/model.js CHANGED
@@ -72,7 +72,7 @@ let Model = module.exports = function(name, opts, manager) {
72
72
  this._setupFields(this.fields = Object.assign({}, this._timestampFields, this.fields))
73
73
  this.fieldsFlattened = this._getFieldsFlattened(this.fields, '') // test output?
74
74
 
75
- // Extend model with monk collection actions
75
+ // Extend model with monk collection queries
76
76
  this._collection = manager.get? manager.get(name, { castIds: false }) : null
77
77
  if (!this._collection) {
78
78
  this.info('There is no mongodb connection, a lot of the monk/monastery methods will be unavailable')
@@ -83,8 +83,7 @@ let Model = module.exports = function(name, opts, manager) {
83
83
  }
84
84
 
85
85
  // Add model to manager
86
- if (typeof this.manager[name] === 'undefined'
87
- || typeof this.manager.model[name] !== 'undefined') {
86
+ if (typeof this.manager[name] === 'undefined' || typeof this.manager.model[name] !== 'undefined') {
88
87
  this.manager[name] = this
89
88
  } else {
90
89
  this.warn(`Your model name '${name}' is conflicting, you are only able to
@@ -0,0 +1,73 @@
1
+ let MongoClient = require('mongodb').MongoClient
2
+
3
+ module.exports.open = function(uri, opts, fn) {
4
+ /*
5
+ * Monkey patch to remove db event listener warnings
6
+ * @todo remove when monk is removed
7
+ * @see https://www.mongodb.com/community/forums/t/node-44612-deprecationwarning-listening-to-events-on-
8
+ the-db-class-has-been-deprecated-and-will-be-removed-in-the-next-major-version/15849/4
9
+ */
10
+ var STATE = {
11
+ CLOSED: 'closed',
12
+ OPENING: 'opening',
13
+ OPEN: 'open'
14
+ }
15
+ MongoClient.connect(uri, opts, function (err, client) {
16
+ // this = Manager
17
+ if (err) {
18
+ this._state = STATE.CLOSED
19
+ this.emit('error-opening', err)
20
+ } else {
21
+ this._state = STATE.OPEN
22
+
23
+ this._client = client
24
+ this._db = client.db()
25
+
26
+ // set up events
27
+ var self = this
28
+ ;['authenticated', 'close', 'error', 'parseError', 'timeout'].forEach(function (eventName) {
29
+ self._client.on(eventName, function (e) {
30
+ self.emit(eventName, e)
31
+ })
32
+ })
33
+
34
+ this.emit('open', this._db)
35
+ }
36
+ if (fn) {
37
+ fn(err, this)
38
+ }
39
+ }.bind(this))
40
+ }
41
+
42
+ module.exports.findOneAndUpdate = function(query, update, opts, fn) {
43
+ /*
44
+ * Monkey patch to use returnDocument
45
+ * @todo remove when monk is removed
46
+ * @see https://github.com/Automattic/monk/blob/master/lib/collection.js#L265
47
+ */
48
+ // this = model
49
+ if (typeof opts === 'function') {
50
+ fn = opts
51
+ opts = {}
52
+ }
53
+ return this._dispatch(function findOneAndUpdate(args) {
54
+ var method = 'findOneAndUpdate'
55
+ if (typeof (args.options || {}).returnDocument === 'undefined') {
56
+ args.options.returnDocument = 'after'
57
+ }
58
+ if (args.options.replaceOne | args.options.replace) {
59
+ method = 'findOneAndReplace'
60
+ }
61
+ return args.col[method](args.query, args.update, args.options)
62
+ .then(function (doc) {
63
+ if (doc && typeof doc.value !== 'undefined') {
64
+ return doc.value
65
+ }
66
+ if (doc.ok && doc.lastErrorObject && doc.lastErrorObject.n === 0) {
67
+ return null
68
+ }
69
+ return doc
70
+ })
71
+ })({options: opts, query: query, update: update, callback: fn}, 'findOneAndUpdate')
72
+ }
73
+
package/lib/util.js CHANGED
@@ -140,14 +140,13 @@ module.exports = {
140
140
  return typeof value === 'undefined'
141
141
  },
142
142
 
143
- omit: function(obj, keys) {
144
- let target = {}
145
- for (let i in obj) {
146
- if (keys.indexOf(i) >= 0) continue
147
- if (!Object.prototype.hasOwnProperty.call(obj, i)) continue
148
- target[i] = obj[i]
143
+ omit: function(obj, fields) {
144
+ const shallowCopy = Object.assign({}, obj)
145
+ for (let i=0; i<fields.length; i+=1) {
146
+ const key = fields[i]
147
+ delete shallowCopy[key]
149
148
  }
150
- return target
149
+ return shallowCopy
151
150
  },
152
151
 
153
152
  parseData: function(obj) {
@@ -228,21 +227,24 @@ module.exports = {
228
227
  }
229
228
  },
230
229
 
231
- pluck: function(obj, keys) {
232
- let target = {}
233
- for (let prop in obj) {
234
- let match
235
- if (!Object.prototype.hasOwnProperty.call(obj, prop)) continue
236
- for (let key of keys) {
237
- if (this.isString(key) && key == prop) match = true
238
- else if (this.isRegex(key) && prop.match(key)) match = true
230
+ pick: function(obj, keys) {
231
+ // Similiar to underscore.pick
232
+ // @param {string[] | regex[]} keys
233
+ if (!this.isObject(obj) && !this.isFunction(obj)) return {}
234
+ keys = this.toArray(keys)
235
+ let res = {}
236
+ for (let key of keys) {
237
+ if (this.isString(key) && obj.hasOwnProperty(key)) res[key] = obj[key]
238
+ if (this.isRegex(key)) {
239
+ for (let key2 in obj) {
240
+ if (obj.hasOwnProperty(key2) && key2.match(key)) res[key2] = obj[key2]
241
+ }
239
242
  }
240
- if (match) target[prop] = obj[prop]
241
243
  }
242
- return target
244
+ return res
243
245
  },
244
246
 
245
- removeUndefined: (variable) => {
247
+ removeUndefined: function(variable) {
246
248
  // takes an array or object
247
249
  if (Array.isArray(variable)) {
248
250
  for (let i=variable.length; i--;) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "monastery",
3
3
  "description": "⛪ A straight forward MongoDB ODM built around Monk",
4
4
  "author": "Ricky Boyce",
5
- "version": "1.35.0",
5
+ "version": "1.36.2",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
@@ -1,104 +1,25 @@
1
+ let bird = require('./mock/blacklisting').bird
2
+ let user = require('./mock/blacklisting').user
3
+ let util = require('../lib/util')
4
+
1
5
  module.exports = function(monastery, opendb) {
2
6
 
3
7
  test('find blacklisting basic', async () => {
4
8
  // Setup
5
9
  let db = (await opendb(null)).db
6
- let bird = db.model('bird', {
7
- fields: {
8
- name: { type: 'string' },
9
- }
10
- })
11
- let user = db.model('user', {
12
- fields: {
13
- list: [{ type: 'number' }],
14
- dog: { type: 'string' },
15
- pet: { type: 'string' },
16
- pets: [{
17
- name: { type: 'string'},
18
- age: { type: 'number'}
19
- }],
20
- animals: {
21
- cat: { type: 'string' },
22
- dog: { type: 'string' }
23
- },
24
- hiddenPets: [{
25
- name: { type: 'string'}
26
- }],
27
- hiddenList: [{ type: 'number'}],
28
- deep: {
29
- deep2: {
30
- deep3: {
31
- deep4: { type: 'string' }
32
- }
33
- }
34
- },
35
- deeper: {
36
- deeper2: {
37
- deeper3: {
38
- deeper4: { type: 'string' }
39
- }
40
- }
41
- },
42
- deepModel: {
43
- myBird: { model: 'bird' }
44
- },
45
- hiddenDeepModel: {
46
- myBird: { model: 'bird' }
47
- }
48
- },
49
- findBL: [
50
- 'dog',
51
- 'animals.cat',
52
- 'pets.age',
53
- 'hiddenPets',
54
- 'hiddenList',
55
- 'deep.deep2.deep3',
56
- 'deeper',
57
- 'hiddenDeepModel'
58
- ],
59
- })
60
- let bird1 = await bird.insert({ data: { name: 'ponyo' }})
61
- let user1 = await user.insert({ data: {
62
- list: [44, 54],
63
- dog: 'Bruce',
64
- pet: 'Freddy',
65
- pets: [{ name: 'Pluto', age: 5 }, { name: 'Milo', age: 4 }],
66
- animals: {
67
- cat: 'Ginger',
68
- dog: 'Max'
69
- },
70
- hiddenPets: [{
71
- name: 'secretPet'
72
- }],
73
- hiddenList: [12, 23],
74
- deep: {
75
- deep2: {
76
- deep3: {
77
- deep4: 'hideme'
78
- }
79
- }
80
- },
81
- deeper: {
82
- deeper2: {
83
- deeper3: {
84
- deeper4: 'hideme'
85
- }
86
- }
87
- },
88
- deepModel: {
89
- myBird: bird1._id
90
- },
91
- hiddenDeepModel: {
92
- myBird: bird1._id
93
- }
94
- }})
10
+ db.model('bird', bird.schema())
11
+ db.model('user', user.schema())
12
+
13
+ let bird1 = await db.bird.insert({ data: bird.mock() })
14
+ let user1 = await db.user.insert({ data: user.mock(bird1) })
95
15
 
96
16
  // initial blacklist
97
- let find1 = await user.findOne({
17
+ let find1 = await db.user.findOne({
98
18
  query: user1._id
99
19
  })
100
20
  expect(find1).toEqual({
101
21
  _id: user1._id,
22
+ bird: bird1._id,
102
23
  list: [44, 54],
103
24
  pet: 'Freddy',
104
25
  pets: [{ name: 'Pluto' }, { name: 'Milo' }],
@@ -108,13 +29,14 @@ module.exports = function(monastery, opendb) {
108
29
  })
109
30
 
110
31
  // augmented blacklist
111
- let find2 = await user.findOne({
32
+ let find2 = await db.user.findOne({
112
33
  query: user1._id,
113
34
  blacklist: ['pet', 'pet', 'deep', 'deepModel', '-dog', '-animals.cat']
114
35
  })
115
36
  let customBlacklist
116
37
  expect(find2).toEqual((customBlacklist = {
117
38
  _id: user1._id,
39
+ bird: bird1._id,
118
40
  dog: 'Bruce',
119
41
  list: [44, 54],
120
42
  pets: [{ name: 'Pluto' }, { name: 'Milo' }],
@@ -122,119 +44,84 @@ module.exports = function(monastery, opendb) {
122
44
  }))
123
45
 
124
46
  // blacklist string
125
- let find3 = await user.findOne({
47
+ let find3 = await db.user.findOne({
126
48
  query: user1._id,
127
49
  blacklist: 'pet pet deep deepModel -dog -animals.cat'
128
50
  })
129
51
  expect(find3).toEqual(customBlacklist)
130
52
 
131
53
  // blacklist removal
132
- let find4 = await user.findOne({ query: user1._id, blacklist: false })
54
+ let find4 = await db.user.findOne({ query: user1._id, blacklist: false })
133
55
  expect(find4).toEqual(user1)
134
56
 
135
57
  db.close()
136
58
  })
137
59
 
138
60
  test('find blacklisting population', async () => {
139
- // inprogresss
140
61
  // Setup
141
- let db = monastery('localhost/monastery', {
142
- timestamps: false,
143
- serverSelectionTimeoutMS: 2000,
144
- })
145
- let bird = db.model('bird', {
146
- fields: {
147
- color: { type: 'string', default: 'red' },
148
- height: { type: 'number' },
149
- name: { type: 'string' },
150
- sub: {
151
- color: { type: 'string', default: 'red' },
152
- },
153
- subs: [{
154
- color: { type: 'string', default: 'red'},
155
- }],
156
- wing: {
157
- size: { type: 'number' },
158
- sizes: {
159
- one: { type: 'number' },
160
- two: { type: 'number' },
161
- }
162
- },
163
- },
164
- findBL: ['wing']
165
- })
166
- let user = db.model('user', {
62
+ let db = (await opendb(null)).db
63
+ db.model('bird', bird.schema())
64
+ db.model('user', {
167
65
  fields: {
168
66
  dog: { type: 'string' },
169
- bird1: { model: 'bird' },
170
- bird2: { model: 'bird' },
171
- bird3: { model: 'bird' },
172
- bird4: { model: 'bird' },
173
- bird5: { model: 'bird' },
67
+ bird: { model: 'bird' },
174
68
  },
175
- findBL: [
176
- 'bird1.name', // bird1.name & bird1.wing blacklisted
177
- '-bird2', 'bird2.name', // bird2.name blacklisted
178
- 'bird3.name', '-bird3', 'bird3.height', // bird3.height blacklisted
179
- '-bird4.wing.sizes.one', '-bird4.wing.size', // ignored
180
- // bird4.wing.sizes.two blacklisted (expand in future verion)
181
- '-bird5.wing.sizes.one', // bird5.wing.sizes.one ignored, wing blacklisted
182
- // bird5.wing.sizes.two, wing.size blacklisted (expand in future verion)
183
- ]
184
69
  })
185
- let bird1 = await bird.insert({
186
- data: {
187
- name: 'ponyo',
188
- height: 40,
189
- sub: {},
190
- wing: { size: 1, sizes: { one: 1, two: 1 }}
191
- }
192
- })
193
- let userData = {
70
+
71
+ let bird1 = await db.bird.insert({ data: bird.mock() })
72
+ let user1 = await db.user.insert({ data: {
194
73
  dog: 'Bruce',
195
- bird1: bird1._id,
196
- bird2: bird1._id,
197
- bird3: bird1._id,
198
- bird4: bird1._id,
199
- bird5: bird1._id
74
+ bird: bird1._id,
75
+ }})
76
+
77
+ let bird1Base = {
78
+ _id: bird1._id,
79
+ color: 'red',
80
+ sub: { color: 'red' }
200
81
  }
201
- let user1 = await user.insert({ data: userData })
202
- let bird1Base = { _id: bird1._id, color: 'red', sub: { color: 'red' }}
203
82
 
204
- // Test bird1
205
- expect(await user.findOne({ query: user1._id, populate: ['bird1'] })).toEqual({
206
- ...userData,
207
- _id: user1._id,
208
- bird1: { ...bird1Base, height: 40 },
83
+ // 'bird1.name', // bird1.name & bird1.wing blacklisted
84
+ // '-bird2', 'bird2.name', // bird2.name blacklisted
85
+ // 'bird3.name', '-bird3', 'bird3.height', // bird3.height blacklisted
86
+ // '-bird4.wing.sizes.one', '-bird4.wing.size', // ignored
87
+ // bird4.wing.sizes.two blacklisted (expand in future verion)
88
+ // '-bird5.wing.sizes.one', // bird5.wing.sizes.one ignored, wing blacklisted
89
+ // bird5.wing.sizes.two, wing.size blacklisted (expand in future verion)
90
+
91
+ // test 1
92
+ db.user.findBL = ['bird.name']
93
+ expect(await db.user.findOne({ query: user1._id, populate: ['bird'] })).toEqual({
94
+ ...user1,
95
+ bird: { ...bird1Base, height: 12 },
209
96
  })
210
- // Test bird2
211
- expect(await user.findOne({ query: user1._id, populate: ['bird2'] })).toEqual({
212
- ...userData,
213
- _id: user1._id,
214
- bird2: { ...bird1Base, height: 40, wing: { size: 1, sizes: { one: 1, two: 1 }} },
97
+ // test 2
98
+ db.user.findBL = ['-bird', 'bird.name']
99
+ expect(await db.user.findOne({ query: user1._id, populate: ['bird'] })).toEqual({
100
+ ...user1,
101
+ bird: { ...bird1Base, height: 12, wing: { size: 1, sizes: { one: 1, two: 1 }} },
215
102
  })
216
- // Test bird3
217
- expect(await user.findOne({ query: user1._id, populate: ['bird3'] })).toEqual({
218
- ...userData,
219
- _id: user1._id,
220
- bird3: { ...bird1Base, name: 'ponyo', wing: { size: 1, sizes: { one: 1, two: 1 }} },
103
+ // test 3
104
+ db.user.findBL = ['bird.name', '-bird', 'bird.height']
105
+ expect(await db.user.findOne({ query: user1._id, populate: ['bird'] })).toEqual({
106
+ ...user1,
107
+ bird: { ...bird1Base, name: 'Ponyo', wing: { size: 1, sizes: { one: 1, two: 1 }} },
221
108
  })
222
- // Test bird4
223
- expect(await user.findOne({ query: user1._id, populate: ['bird4'] })).toEqual({
224
- ...userData,
225
- _id: user1._id,
226
- bird4: { ...bird1Base, name: 'ponyo', height: 40 },
109
+ // test 4
110
+ db.user.findBL = ['-bird.wing.sizes.one', '-bird.wing.size']
111
+ expect(await db.user.findOne({ query: user1._id, populate: ['bird'] })).toEqual({
112
+ ...user1,
113
+ bird: { ...bird1Base, name: 'Ponyo', height: 12 },
227
114
  })
228
- // Test bird5
229
- expect(await user.findOne({ query: user1._id, populate: ['bird5'] })).toEqual({
230
- ...userData,
231
- _id: user1._id,
232
- bird5: { ...bird1Base, name: 'ponyo', height: 40 },
115
+ // test 5
116
+ db.user.findBL = ['-bird.wing.sizes.one']
117
+ expect(await db.user.findOne({ query: user1._id, populate: ['bird'] })).toEqual({
118
+ ...user1,
119
+ bird: { ...bird1Base, name: 'Ponyo', height: 12 },
233
120
  })
234
121
  // blacklist removal
235
- expect(await user.findOne({ query: user1._id, blacklist: false, populate: ['bird1'] })).toEqual({
122
+ expect(await db.user.findOne({ query: user1._id, blacklist: false, populate: ['bird'] })).toEqual({
236
123
  ...user1,
237
- bird1: { ...bird1Base, height: 40, name: 'ponyo', wing: { size: 1, sizes: { one: 1, two: 1 }} },
124
+ bird: { ...bird1Base, height: 12, name: 'Ponyo', wing: { size: 1, sizes: { one: 1, two: 1 }} },
238
125
  })
239
126
 
240
127
  db.close()
@@ -583,4 +470,72 @@ module.exports = function(monastery, opendb) {
583
470
  db.close()
584
471
  })
585
472
 
473
+ test('findOneAndUpdate blacklisting general', async () => {
474
+ // todo: test all findOneAndUpdate options
475
+ // todo: test find & update hooks
476
+ let db = (await opendb(null)).db
477
+ db.model('bird', bird.schema())
478
+ db.model('user', user.schema())
479
+
480
+ let bird1 = await db.bird.insert({ data: bird.mock() })
481
+ let user1 = await db.user.insert({ data: user.mock(bird1) })
482
+
483
+ // augmented blacklist
484
+ let find2 = await db.user.findOneAndUpdate({
485
+ query: user1._id,
486
+ data: { dog: 'Bruce2', pet: 'Freddy2' }, // pet shouldn't update
487
+ blacklist: ['pet', 'deep', 'deepModel', '-dog', '-animals.cat'],
488
+ })
489
+ expect(find2).toEqual({
490
+ _id: user1._id,
491
+ bird: bird1._id,
492
+ dog: 'Bruce2',
493
+ list: [44, 54],
494
+ pets: [{ name: 'Pluto' }, { name: 'Milo' }],
495
+ animals: { dog: 'Max', cat: 'Ginger' },
496
+ })
497
+ expect(await db.user.findOne({ query: user1._id, project: ['pet'] })).toEqual({
498
+ _id: user1._id,
499
+ pet: 'Freddy',
500
+ })
501
+
502
+ db.close()
503
+ })
504
+
505
+ test('findOneAndUpdate blacklisting populate', async () => {
506
+ let db = (await opendb(null)).db
507
+ db.model('bird', bird.schema())
508
+ db.model('user', user.schema())
509
+
510
+ let bird1 = await db.bird.insert({ data: bird.mock() })
511
+ let user1 = await db.user.insert({ data: user.mock(bird1) })
512
+
513
+ // augmented blacklist
514
+ let find2 = await db.user.findOneAndUpdate({
515
+ query: user1._id,
516
+ data: { dog: 'Bruce2', pet: 'Freddy2' }, // pet shouldn't update
517
+ blacklist: [
518
+ 'pet', 'deep', 'deepModel', '-dog', '-animals.cat',
519
+ 'bird.name', '-bird', 'bird.height' // <- populated model
520
+ ],
521
+ populate: ['bird'],
522
+ })
523
+ expect(find2).toEqual({
524
+ _id: user1._id,
525
+ bird: {
526
+ ...util.omit(bird1, ['height']),
527
+ },
528
+ dog: 'Bruce2',
529
+ list: [44, 54],
530
+ pets: [{ name: 'Pluto' }, { name: 'Milo' }],
531
+ animals: { dog: 'Max', cat: 'Ginger' },
532
+ })
533
+ expect(await db.user.findOne({ query: user1._id, project: ['pet'] })).toEqual({
534
+ _id: user1._id,
535
+ pet: 'Freddy',
536
+ })
537
+
538
+ db.close()
539
+ })
540
+
586
541
  }