monastery 1.35.0 → 1.36.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/changelog.md CHANGED
@@ -2,12 +2,27 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [1.36.0](https://github.com/boycce/monastery/compare/1.35.0...1.36.0) (2022-04-15)
6
+
7
+
8
+ ### Features
9
+
10
+ * added findOneAndUpdate ([60b518a](https://github.com/boycce/monastery/commit/60b518a1d09002a794e72fe3476c39c43c0c3b5e))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * removed depreciation warnings ([6db73bb](https://github.com/boycce/monastery/commit/6db73bba7916b4c5c98a24fd03b34c0f776abb5c))
16
+
5
17
  ## [1.35.0](https://github.com/boycce/monastery/compare/1.34.0...1.35.0) (2022-04-08)
6
18
 
7
19
 
8
20
  ### Features
9
21
 
10
- * `false` removes blacklist, added tests for blacklisting/project stirng ([5999859](https://github.com/boycce/monastery/commit/599985972cc14b980148db26c03108feabf23756))
22
+ * Blacklist `false` removes all blacklisting, added tests for blacklisting/project stirng ([5999859](https://github.com/boycce/monastery/commit/599985972cc14b980148db26c03108feabf23756))
23
+ * Add project to insert/update/validate ([1b1eb12](https://github.com/boycce/monastery/commit/1b1eb12bc476ff82445a46489db64b37c13f9513))
24
+ * Whitelisting a parent will remove any previously blacklisted children ([1b1eb12](https://github.com/boycce/monastery/commit/1b1eb12bc476ff82445a46489db64b37c13f9513))
25
+ * Blacklist/project works the same across find/insert/update/validate ([1b1eb12](https://github.com/boycce/monastery/commit/1b1eb12bc476ff82445a46489db64b37c13f9513))
11
26
 
12
27
  ## [1.34.0](https://github.com/boycce/monastery/compare/1.33.0...1.34.0) (2022-04-05)
13
28
 
package/docs/readme.md CHANGED
@@ -87,7 +87,7 @@ Coming soon...
87
87
  - Add before/afterInsertUpdate
88
88
  - Bug: Setting an object literal on an ID field ('model') saves successfully
89
89
  - Population within array items
90
- - ~~Blacklist `false` removes all blacklisting~~
90
+ - ~~Blacklist false removes all blacklisting~~
91
91
  - ~~Add project to insert/update/validate~~
92
92
  - ~~Whitelisting a parent will remove any previously blacklisted children~~
93
93
  - ~~Blacklist/project works the same across find/insert/update/validate~~
package/lib/index.js CHANGED
@@ -1,6 +1,10 @@
1
- let util = require('./util')
2
- let monk = require('monk')
3
1
  let debug = require('debug')
2
+ let monk = require('monk')
3
+ let util = require('./util')
4
+
5
+ // Apply monk monkey patches
6
+ monk.manager.prototype.open = require('./monk-monkey-patches').open
7
+ monk.Collection.prototype.findOneAndUpdate = require('./monk-monkey-patches').findOneAndUpdate
4
8
 
5
9
  module.exports = function(uri, opts, fn) {
6
10
  /**
@@ -12,59 +16,51 @@ module.exports = function(uri, opts, fn) {
12
16
  * @param {object} opts
13
17
  * @return monk manager
14
18
  */
19
+ let monasteryOpts = [
20
+ 'defaultObjects', 'imagePlugin', 'limit', 'nullObjects', 'timestamps', 'useMilliseconds'
21
+ ]
22
+
15
23
  if (!opts) opts = {}
16
24
  if (util.isDefined(opts.defaultFields)) {
17
25
  var depreciationWarningDefaultField = true
18
26
  opts.timestamps = opts.defaultFields
27
+ delete opts.defaultFields
28
+ }
29
+ if (!util.isDefined(opts.timestamps)) {
30
+ opts.timestamps = true
19
31
  }
20
- let defaultObjects = opts.defaultObjects
21
- let imagePlugin = opts.imagePlugin
22
- let limit = opts.limit
23
- let nullObjects = opts.nullObjects
24
- let timestamps = util.isDefined(opts.timestamps)? opts.timestamps : true
25
- let useMilliseconds = opts.useMilliseconds
26
- delete opts.defaultFields
27
- delete opts.defaultObjects
28
- delete opts.imagePlugin
29
- delete opts.limit
30
- delete opts.nullObjects
31
- delete opts.timestamps
32
- delete opts.useMilliseconds
33
32
 
34
- // Monk Manager instance or manager mock
33
+ // Monk manager instance or manager mock
35
34
  // Monk manager instances have manager._db defined which is the raw mongodb connection
36
35
  if (typeof uri === 'object') var manager = uri
37
- else if (uri) manager = monk(uri, { useUnifiedTopology: true, ...opts }, fn)
36
+ else if (uri) manager = monk(uri, { useUnifiedTopology: true, ...util.omit(opts, monasteryOpts) }, fn)
38
37
  else manager = { id: monk.id }
39
38
 
40
39
  // Add monastery properties
41
- manager.error = debug('monastery:error*')
42
- manager.warn = debug('monastery:warn')
43
- manager.info = debug('monastery:info')
44
- manager.model = require('./model')
45
- manager.models = models
46
- manager.defaultObjects = defaultObjects
47
- manager.imagePlugin = imagePlugin
40
+ manager.arrayWithSchema = arrayWithSchema
41
+ manager.beforeModel = []
48
42
  manager.imagePluginFile = require('../plugins/images')
49
43
  manager.isId = util.isId.bind(util)
50
- manager.limit = limit
51
- manager.nullObjects = nullObjects
44
+ manager.model = require('./model')
45
+ manager.models = models
52
46
  manager.parseData = util.parseData.bind(util)
53
- manager.timestamps = timestamps
54
- manager.useMilliseconds = useMilliseconds
55
- manager.beforeModel = []
56
- manager.arrayWithSchema = manager.arraySchema = (array, schema) => {
57
- array.schema = schema; return array
47
+ manager.warn = debug('monastery:warn')
48
+ manager.error = debug('monastery:error*')
49
+ manager.info = debug('monastery:info')
50
+
51
+ // Add opts onto manager
52
+ for (let key of monasteryOpts) {
53
+ manager[key] = opts[key]
58
54
  }
59
55
 
60
56
  // Depreciation warnings
61
57
  if (depreciationWarningDefaultField) {
62
- manager.error('manager.defaultFields has been depreciated in favour of manager.timestamps')
58
+ manager.error('opts.defaultFields has been depreciated in favour of opts.timestamps')
63
59
  }
64
60
 
65
61
  // Initiate any plugins
66
62
  if (manager.imagePlugin) {
67
- manager.imagePluginFile.setup(manager, util.isObject(imagePlugin)? imagePlugin : {})
63
+ manager.imagePluginFile.setup(manager, util.isObject(manager.imagePlugin)? manager.imagePlugin : {})
68
64
  }
69
65
 
70
66
  // Catch mongodb connectivity errors
@@ -72,11 +68,17 @@ module.exports = function(uri, opts, fn) {
72
68
  return manager
73
69
  }
74
70
 
75
- function models(path) {
71
+ let arrayWithSchema = function(array, schema) {
72
+ array.schema = schema
73
+ return array
74
+ }
75
+
76
+ let models = function(path) {
76
77
  /**
77
78
  * Setup model definitions from a folder location
78
79
  * @param {string} pathname
79
80
  * @return {object} - e.g. { user: , article: , .. }
81
+ * @this Manager
80
82
  */
81
83
  let models = {}
82
84
  if (!path || typeof path !== 'string') {
package/lib/model-crud.js CHANGED
@@ -65,7 +65,7 @@ module.exports = {
65
65
  }
66
66
  try {
67
67
  opts = await this._queryObject(opts, 'find', one)
68
- let custom = ['blacklist', 'one', 'populate', 'project', 'query', 'respond']
68
+ let custom = ['blacklist', 'model', 'one', 'populate', 'project', 'query', 'respond']
69
69
  let lookups = []
70
70
 
71
71
  // Get projection
@@ -152,11 +152,56 @@ module.exports = {
152
152
  return this.find(opts, cb, true)
153
153
  },
154
154
 
155
- findOneAndUpdate: function(opts, cb) {
156
- return this._findOneAndUpdate(opts, cb)
155
+ findOneAndUpdate: async function(opts, cb) {
156
+ /**
157
+ * Find and update document(s) with monk, also auto populates
158
+ * @param {object} opts
159
+ * @param {array|string|false} <opts.blacklist> - augment findBL/updateBL, `false` will remove all blacklisting
160
+ * @param {array|string|false} <opts.blacklistFind> - augment findBL, `false` will remove all blacklisting
161
+ * @param {array} <opts.populate> - find population, see docs
162
+ * @param {array|string} <opts.project> - return only these fields, ignores blacklisting
163
+ * @param {array|string} <opts.projectFind> - return only these fields, ignores blacklisting
164
+ * @param {object} <opts.query> - mongodb query object
165
+ * @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
166
+ * @param {any} <any mongodb option>
167
+ *
168
+ * Update options:
169
+ * @param {object|array} opts.data - mongodb document update object(s)
170
+ * @param {array|string|false} <opts.blacklistUpdate> - augment updateBL, `false` will remove all blacklisting
171
+ * @param {array|string} <opts.projectUpdate> - return only these fields, ignores blacklisting
172
+ * @param {array|string|true} <opts.skipValidation> - skip validation for this field name(s)
173
+ * @param {boolean} <opts.timestamps> - whether `updatedAt` is automatically updated
174
+ * @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
175
+ * default, but false on update
176
+ * @param {function} <cb> - execute cb(err, data) instead of responding
177
+ * @return promise
178
+ * @this model
179
+ */
180
+
181
+ if (opts.populate) {
182
+ try {
183
+ // todo: add transaction flag
184
+ delete opts.multi
185
+ let update = await this.update(opts, null, 'findOneAndUpdate')
186
+ if (update) var response = await this.findOne(opts)
187
+ else response = update
188
+
189
+ // Success
190
+ if (cb) cb(null, response)
191
+ else if (opts.req && opts.respond) opts.req.res.json(response)
192
+ else return Promise.resolve(response)
193
+
194
+ } catch (e) {
195
+ if (cb) cb(e)
196
+ else if (opts && opts.req && opts.respond) opts.req.res.error(e)
197
+ else throw e
198
+ }
199
+ } else {
200
+ return this.update(opts, cb, 'findOneAndUpdate')
201
+ }
157
202
  },
158
203
 
159
- update: async function(opts, cb) {
204
+ update: async function(opts, cb, type='update') {
160
205
  /**
161
206
  * Updates document(s) with monk after validating data & before hooks.
162
207
  * @param {object} opts
@@ -171,41 +216,48 @@ module.exports = {
171
216
  * default, but false on update
172
217
  * @param {any} <any mongodb option>
173
218
  * @param {function} <cb> - execute cb(err, data) instead of responding
219
+ * @param {function} <type> - 'update', or 'findOneAndUpdate'
174
220
  * @return promise(data)
175
221
  * @this model
176
222
  */
177
223
  if (cb && !util.isFunction(cb)) {
178
- throw new Error(`The callback passed to ${this.name}.update() is not a function`)
224
+ throw new Error(`The callback passed to ${this.name}.${type}() is not a function`)
179
225
  }
180
226
  try {
181
- opts = await this._queryObject(opts, 'update')
227
+ opts = await this._queryObject(opts, type)
182
228
  let data = opts.data
183
229
  let response = null
184
- let operators = util.pluck(opts, [/^\$/])
185
- let custom = ['blacklist', 'data', 'query', 'respond', 'skipValidation', 'validateUndefined']
230
+ let operators = util.pick(opts, [/^\$/])
231
+ let custom = [
232
+ 'blacklist', 'data', 'model', 'one', 'populate', 'project', 'query', 'respond', 'skipValidation',
233
+ 'validateUndefined'
234
+ ]
186
235
 
187
236
  // Validate
188
237
  if (util.isDefined(data)) data = await this.validate(opts.data, { ...opts })
189
238
  if (!util.isDefined(data) && util.isEmpty(operators)) {
190
- throw new Error(`Please pass an update operator to ${this.name}.update(), e.g. data, $unset, etc`)
239
+ throw new Error(`Please pass an update operator to ${this.name}.${type}(), e.g. data, $unset, etc`)
191
240
  }
192
241
  if (util.isDefined(data) && (!data || util.isEmpty(data))) {
193
- throw new Error(`No valid data passed to ${this.name}.update({ data: .. })`)
242
+ throw new Error(`No valid data passed to ${this.name}.${type}({ data: .. })`)
194
243
  }
195
244
  // Hook: beforeUpdate (has access to original, non-validated opts.data)
196
245
  await util.runSeries(this.beforeUpdate.map(f => f.bind(opts, data||{})))
197
246
  if (data && operators['$set']) {
198
- this.warn(`'$set' fields take precedence over the data fields for \`${this.name}.update()\``)
247
+ this.warn(`'$set' fields take precedence over the data fields for \`${this.name}.${type}()\``)
199
248
  }
200
249
  if (data || operators['$set']) {
201
250
  operators['$set'] = { ...data, ...(operators['$set'] || {}) }
202
251
  }
252
+ // Just peform a normal update if we need to populate a findOneAndUpdate
253
+ if (opts.populate && type == 'findOneAndUpdate') type ='update'
203
254
  // Update
204
- let update = await this._update(opts.query, operators, util.omit(opts, custom))
205
- if (update.n) response = Object.assign(Object.create({ _output: update }), operators['$set']||{})
255
+ let update = await this['_' + type](opts.query, operators, util.omit(opts, custom))
256
+ if (type == 'findOneAndUpdate') response = update
257
+ else if (update.n) response = Object.assign(Object.create({ _output: update }), operators['$set']||{})
206
258
 
207
259
  // Hook: afterUpdate (doesn't have access to validated data)
208
- if (update.n) await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)))
260
+ if (response) await util.runSeries(this.afterUpdate.map(f => f.bind(opts, response)))
209
261
 
210
262
  // Success
211
263
  if (cb) cb(null, response)
@@ -236,7 +288,7 @@ module.exports = {
236
288
  }
237
289
  try {
238
290
  opts = await this._queryObject(opts, 'remove')
239
- let custom = ['query', 'respond']
291
+ let custom = ['model', 'query', 'respond']
240
292
  if (util.isEmpty(opts.query)) throw new Error('Please specify opts.query')
241
293
 
242
294
  // Remove
@@ -368,7 +420,7 @@ module.exports = {
368
420
  /**
369
421
  * Normalise options
370
422
  * @param {MongoId|string|object} opts
371
- * @param {string} type - operation type
423
+ * @param {string} type - insert, update, find, remove, findOneAndUpdate
372
424
  * @param {boolean} one - return one document
373
425
  * @return {Promise} opts
374
426
  * @this model
@@ -397,7 +449,7 @@ module.exports = {
397
449
  throw new Error('Please pass an object or MongoId to options.query')
398
450
  }
399
451
  if (util.isId(opts.query._id)) opts.query._id = this.manager.id(opts.query._id)
400
- if (isIdType(opts.query._id) || one) opts.one = true
452
+ if (isIdType(opts.query._id) || one || type == 'findOneAndUpdate') opts.one = true
401
453
  opts.query = util.removeUndefined(opts.query)
402
454
 
403
455
  // Query options
@@ -417,7 +469,7 @@ module.exports = {
417
469
  if (util.isDefined(opts.data)) opts.data = await util.parseData(opts.data)
418
470
 
419
471
  opts.type = type
420
- opts[type] = true
472
+ opts[type] = true // still being included in the operation options..
421
473
  opts.model = this
422
474
  return opts
423
475
  },
@@ -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.projection = this._getProjectionFromProject(opts.project)
40
+ else opts.projection = 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) {
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.0",
6
6
  "license": "MIT",
7
7
  "repository": "github:boycce/monastery",
8
8
  "homepage": "https://boycce.github.io/monastery/",
package/test/crud.js CHANGED
@@ -423,14 +423,72 @@ module.exports = function(monastery, opendb) {
423
423
  db.close()
424
424
  })
425
425
 
426
- test('remove basics', async () => {
426
+ test('findOneAndUpdate basics', async () => {
427
+ // todo: test all findOneAndUpdate options
428
+
427
429
  let db = (await opendb(null)).db
430
+ let dog = db.model('dog', {
431
+ fields: {
432
+ name: { type: 'string', default: 'Scruff' },
433
+ }
434
+ })
428
435
  let user = db.model('user', {
436
+ fields: {
437
+ name: { type: 'string', default: 'Martin' },
438
+ dog: { model: 'dog' },
439
+ }
440
+ })
441
+
442
+ // Returns omitted field after update, i.e. dog
443
+ let dog1 = await dog.insert({ data: {} })
444
+ let user1 = await user.insert({ data: { dog: dog1._id }})
445
+ expect(await user.findOneAndUpdate({ query: { _id: user1._id }, data: { name: 'Martin2' }})).toEqual({
446
+ _id: user1._id,
447
+ name: 'Martin2',
448
+ dog: dog1._id,
449
+ })
450
+
451
+ // Returns omitted field requiring population after update, i.e. dog
452
+ expect(await user.findOneAndUpdate({
453
+ query: { _id: user1._id },
454
+ data: { name: 'Martin2' },
455
+ populate: ['dog'],
456
+ })).toEqual({
457
+ _id: user1._id,
458
+ name: 'Martin2',
459
+ dog: {
460
+ _id: dog1._id,
461
+ name: 'Scruff'
462
+ },
463
+ })
464
+
465
+ // Error finding document to update
466
+ expect(await user.findOneAndUpdate({
467
+ query: { _id: db.id() },
468
+ data: { name: 'Martin2' },
469
+ })).toEqual(null)
470
+
471
+ // Error finding document to update (populate)
472
+ expect(await user.findOneAndUpdate({
473
+ query: { _id: db.id() },
474
+ data: { name: 'Martin2' },
475
+ populate: ['dog'],
476
+ })).toEqual(null)
477
+
478
+ db.close()
479
+ })
480
+
481
+ test('remove basics', async () => {
482
+ let db = (await opendb(null)).db
483
+ let user = db.model('userRemove', {
429
484
  fields: {
430
485
  name: { type: 'string' },
431
486
  },
432
487
  })
433
488
 
489
+ // Clear (incase of failed a test)
490
+ user._remove({}, { multi: true })
491
+
434
492
  // Insert multiple
435
493
  let inserted2 = await user.insert({ data: [{ name: 'Martin' }, { name: 'Martin' }, { name: 'Martin' }]})
436
494
  expect(inserted2).toEqual([
package/test/monk.js CHANGED
@@ -1,6 +1,6 @@
1
1
  module.exports = function(monastery, opendb) {
2
2
 
3
- test('Monk confilicts', async () => {
3
+ test('Monk conflicts', async () => {
4
4
  // Setup
5
5
  let db = (await opendb(false)).db
6
6
  let monkdb = require('monk')(':badconnection', () => {})