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 +16 -1
- package/docs/readme.md +1 -1
- package/lib/index.js +36 -34
- package/lib/model-crud.js +70 -18
- package/lib/model-validate.js +26 -20
- package/lib/model.js +2 -3
- package/lib/monk-monkey-patches.js +73 -0
- package/lib/util.js +20 -18
- package/package.json +1 -1
- package/test/crud.js +59 -1
- package/test/monk.js +1 -1
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
|
|
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
|
|
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
|
|
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.
|
|
42
|
-
manager.
|
|
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.
|
|
51
|
-
manager.
|
|
44
|
+
manager.model = require('./model')
|
|
45
|
+
manager.models = models
|
|
52
46
|
manager.parseData = util.parseData.bind(util)
|
|
53
|
-
manager.
|
|
54
|
-
manager.
|
|
55
|
-
manager.
|
|
56
|
-
|
|
57
|
-
|
|
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('
|
|
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
|
|
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
|
-
|
|
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}
|
|
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,
|
|
227
|
+
opts = await this._queryObject(opts, type)
|
|
182
228
|
let data = opts.data
|
|
183
229
|
let response = null
|
|
184
|
-
let operators = util.
|
|
185
|
-
let custom = [
|
|
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}
|
|
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}
|
|
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}
|
|
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
|
|
205
|
-
if (
|
|
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 (
|
|
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 -
|
|
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
|
},
|
package/lib/model-validate.js
CHANGED
|
@@ -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)) {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
}
|
|
54
|
-
if (cb) cb(
|
|
55
|
-
else throw
|
|
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
|
|
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,
|
|
144
|
-
|
|
145
|
-
for (let i
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
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.
|
|
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('
|
|
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([
|