monastery 3.2.1 → 3.4.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/.eslintrc.json +1 -1
- package/changelog.md +4 -0
- package/docs/manager/index.md +5 -4
- package/docs/readme.md +4 -2
- package/lib/index.js +15 -3
- package/lib/model-crud.js +128 -88
- package/lib/model-validate.js +131 -97
- package/lib/model.js +15 -0
- package/lib/util.js +101 -72
- package/package.json +1 -1
- package/test/crud.js +240 -44
- package/test/util.js +237 -22
- package/test/validate.js +87 -3
package/.eslintrc.json
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"exports": "always-multiline",
|
|
33
33
|
"functions": "never"
|
|
34
34
|
}],
|
|
35
|
-
"max-len": ["error", { "code":
|
|
35
|
+
"max-len": ["error", { "code": 130, "ignorePattern": "^\\s*<(rect|path|line)\\s" }],
|
|
36
36
|
"no-prototype-builtins": "off",
|
|
37
37
|
"no-unused-vars": ["error", { "args": "none" }],
|
|
38
38
|
"object-shorthand": ["error", "consistent"],
|
package/changelog.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
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
|
+
## [3.4.0](https://github.com/boycce/monastery/compare/3.3.0...3.4.0) (2024-08-09)
|
|
6
|
+
|
|
7
|
+
## [3.3.0](https://github.com/boycce/monastery/compare/3.2.1...3.3.0) (2024-08-07)
|
|
8
|
+
|
|
5
9
|
### [3.2.1](https://github.com/boycce/monastery/compare/3.2.0...3.2.1) (2024-08-05)
|
|
6
10
|
|
|
7
11
|
## [3.2.0](https://github.com/boycce/monastery/compare/3.1.0...3.2.0) (2024-07-17)
|
package/docs/manager/index.md
CHANGED
|
@@ -13,11 +13,12 @@ Monastery manager constructor.
|
|
|
13
13
|
`uri` *(string\|array)*: A [mongo connection string URI](https://www.mongodb.com/docs/v5.0/reference/connection-string/). Replica sets can be an array or comma separated.
|
|
14
14
|
|
|
15
15
|
[`options`] *(object)*:
|
|
16
|
-
- [`defaultObjects=false`] *(boolean)*: when [inserting](../model/insert.html#defaults-example), undefined embedded documents and arrays are defined
|
|
17
|
-
- [`logLevel=2`] *(number)*: 1=errors, 2=warnings, 3=info. You can also use the debug environment variable `DEBUG=monastery:info
|
|
16
|
+
- [`defaultObjects=false`] *(boolean)*: when [inserting](../model/insert.html#defaults-example), undefined embedded documents and arrays are defined.
|
|
17
|
+
- [`logLevel=2`] *(number)*: 1=errors, 2=warnings, 3=info. You can also use the debug environment variable `DEBUG=monastery:info`.
|
|
18
|
+
- [`noDefaults`] *(boolean\|string\|array)*: after find operations, don't add defaults for any matching paths, e.g. ['pet.name']. You can override this per operation.
|
|
18
19
|
- [`nullObjects=false`] *(boolean)*: embedded documents and arrays can be set to null or an empty string (which gets converted to null). You can override this per field via `nullObject: true`.
|
|
19
|
-
- [`promise=false`] *(boolean)*: return a promise instead of the manager instance
|
|
20
|
-
- [`timestamps=true`] *(boolean)*: whether to use [`createdAt` and `updatedAt`](../definition), this can be overridden per operation
|
|
20
|
+
- [`promise=false`] *(boolean)*: return a promise instead of the manager instance.
|
|
21
|
+
- [`timestamps=true`] *(boolean)*: whether to use [`createdAt` and `updatedAt`](../definition), this can be overridden per operation.
|
|
21
22
|
- [`useMilliseconds=false`] *(boolean)*: by default the `createdAt` and `updatedAt` fields that get created automatically use unix timestamps in seconds, set this to true to use milliseconds instead.
|
|
22
23
|
- [`mongo options`](https://mongodb.github.io/node-mongodb-native/5.9/interfaces/MongoClientOptions.html)...
|
|
23
24
|
|
package/docs/readme.md
CHANGED
|
@@ -86,7 +86,7 @@ You can view MongoDB's [compatibility table here](https://www.mongodb.com/docs/d
|
|
|
86
86
|
## v3 Breaking Changes
|
|
87
87
|
|
|
88
88
|
- Removed callback functions on all model methods, you can use the returned promise instead
|
|
89
|
-
- model.update() now returns the following
|
|
89
|
+
- model.update() now returns the following res._output property: `{ acknowledged: true, matchedCount: 1, modifiedCount: 1, upsertedCount: 0, upsertedId: null }` instead of `{ n: 1, nModified: 1, ok: 1 }`
|
|
90
90
|
- model.remove() now returns `{ acknowledged: true, deletedCount: 1 }`, instead of `{ results: {n:1, ok:1} }`
|
|
91
91
|
- model._indexes() now returns collection._indexes() not collection._indexInformation()
|
|
92
92
|
- db.model.* moved to db.models.*
|
|
@@ -95,7 +95,7 @@ You can view MongoDB's [compatibility table here](https://www.mongodb.com/docs/d
|
|
|
95
95
|
- db._db moved to db.db
|
|
96
96
|
- db.catch/then() moved to db.onError/db.onOpen()
|
|
97
97
|
- next() is now redundant when returning promises from hooks, e.g. `afterFind: [async (data) => {...}]`
|
|
98
|
-
-
|
|
98
|
+
- deep paths in data, e.g. `books[].title` are now validated, and don't replace the whole object, e.g. `books`
|
|
99
99
|
|
|
100
100
|
## v2 Breaking Changes
|
|
101
101
|
|
|
@@ -134,6 +134,8 @@ You can view MongoDB's [compatibility table here](https://www.mongodb.com/docs/d
|
|
|
134
134
|
- ~~added `model.count()`~~
|
|
135
135
|
- Typescript support
|
|
136
136
|
- Add soft remove plugin
|
|
137
|
+
- ~~Added deep path validation support for updates~~
|
|
138
|
+
- ~~Added option skipHooks~~
|
|
137
139
|
|
|
138
140
|
## Debugging
|
|
139
141
|
|
package/lib/index.js
CHANGED
|
@@ -38,7 +38,7 @@ function Manager(uri, opts) {
|
|
|
38
38
|
const mongoOpts = Object.keys(opts||{}).reduce((acc, key) => {
|
|
39
39
|
if (
|
|
40
40
|
![
|
|
41
|
-
'databaseName', 'defaultObjects', 'logLevel', 'imagePlugin', 'limit', 'nullObjects',
|
|
41
|
+
'databaseName', 'defaultObjects', 'logLevel', 'imagePlugin', 'limit', 'noDefaults', 'nullObjects',
|
|
42
42
|
'promise', 'timestamps', 'useMilliseconds',
|
|
43
43
|
].includes(key)) {
|
|
44
44
|
acc[key] = opts[key]
|
|
@@ -277,8 +277,20 @@ Manager.prototype.open = async function() {
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
Manager.prototype.parseData = function(obj) {
|
|
281
|
-
return util.parseData(obj)
|
|
280
|
+
Manager.prototype.parseData = function(obj, parseBracketToDotNotation, parseDotNotation) {
|
|
281
|
+
return util.parseData(obj, parseBracketToDotNotation, parseDotNotation)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
Manager.prototype.parseBracketNotation = function(obj) {
|
|
285
|
+
return util.parseBracketNotation(obj)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Manager.prototype.parseBracketToDotNotation = function(obj) {
|
|
289
|
+
return util.parseBracketToDotNotation(obj)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
Manager.prototype.parseDotNotation = function(obj) {
|
|
293
|
+
return util.parseDotNotation(obj)
|
|
282
294
|
}
|
|
283
295
|
|
|
284
296
|
Manager.prototype.model = Model
|
package/lib/model-crud.js
CHANGED
|
@@ -5,9 +5,9 @@ Model.prototype.count = async function (opts) {
|
|
|
5
5
|
/**
|
|
6
6
|
* Count document(s)
|
|
7
7
|
* @param {object} opts
|
|
8
|
-
* @param {object}
|
|
8
|
+
* @param {object} <opts.query> - mongodb query object
|
|
9
9
|
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
10
|
-
* @param {any}
|
|
10
|
+
* @param {any} <any mongodb option>
|
|
11
11
|
* @return promise
|
|
12
12
|
* @this model
|
|
13
13
|
*/
|
|
@@ -28,30 +28,38 @@ Model.prototype.count = async function (opts) {
|
|
|
28
28
|
Model.prototype.insert = async function (opts) {
|
|
29
29
|
/**
|
|
30
30
|
* Inserts document(s) after validating data & before hooks.
|
|
31
|
+
*
|
|
31
32
|
* @param {object} opts
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
33
|
+
* @param {object|array} opts.data - documents to insert
|
|
34
|
+
* @param {array|string|false} <opts.blacklist> - augment schema.insertBL, `false` will remove blacklisting
|
|
35
|
+
* @param {boolean} <opts.bracketToDotNotation> - covert fields in bracket notation (form data) to
|
|
36
|
+
* paths in dot notation instead of objects, e.g. { 'user[names][0]': 'John' } => { user.names.0 }
|
|
37
|
+
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
|
|
38
|
+
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
39
|
+
* @param {boolean} <opts.skipHooks> - skip insert and validate before/after hooks
|
|
40
|
+
* @param {array|string|boolean} <opts.skipValidation> - skip validation for these fields, or pass `true` for all fields.
|
|
41
|
+
* $set and $unset objects are skipped by default, but can be enabled via opts.skipValidation=false
|
|
42
|
+
* @param {boolean} <opts.timestamps> - whether `createdAt` and `updatedAt` are automatically inserted
|
|
43
|
+
* @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
|
|
44
|
+
* default, but false on update
|
|
45
|
+
* @param {any} <any mongodb option>
|
|
46
|
+
*
|
|
42
47
|
* @return promise
|
|
43
48
|
* @this model
|
|
44
49
|
*/
|
|
45
50
|
try {
|
|
46
51
|
opts = await this._queryObject(opts, 'insert')
|
|
52
|
+
let data = opts.data
|
|
47
53
|
|
|
48
54
|
// Validate
|
|
49
|
-
|
|
55
|
+
if (this._shouldValidate(opts, 'data')) {
|
|
56
|
+
data = await this.validate(data || {}, opts) // was { ...opts }
|
|
57
|
+
}
|
|
50
58
|
|
|
51
59
|
// Insert
|
|
52
|
-
data = await
|
|
60
|
+
data = await this._callHooks('beforeInsert', data, opts)
|
|
53
61
|
let response = await this._insert(data, util.omit(opts, this._queryOptions))
|
|
54
|
-
response = await
|
|
62
|
+
response = await this._callHooks('afterInsert', response, opts)
|
|
55
63
|
|
|
56
64
|
// Success/error
|
|
57
65
|
if (opts.req && opts.respond) opts.req.res.json(response)
|
|
@@ -66,15 +74,18 @@ Model.prototype.insert = async function (opts) {
|
|
|
66
74
|
Model.prototype.find = async function (opts, _one) {
|
|
67
75
|
/**
|
|
68
76
|
* Finds document(s), with auto population
|
|
77
|
+
*
|
|
69
78
|
* @param {object} opts (todo doc getSignedUrls like in the doc)
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
79
|
+
* @param {object} <opts.query> - mongodb query object
|
|
80
|
+
* @param {array|string|false} <opts.blacklist> - augment schema.findBL, `false` will remove all blacklisting
|
|
81
|
+
* @param {boolean|string|array} <opts.noDefaults> - dont add defaults for any matching paths, e.g. ['pet.name']
|
|
82
|
+
* @param {array} <opts.populate> - population, see docs
|
|
83
|
+
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
|
|
84
|
+
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
85
|
+
* @param {boolean} <opts.skipHooks> - skip after find hooks
|
|
86
|
+
* @param {any} <any mongodb option>
|
|
77
87
|
* @param {boolean} <_one> - return one document
|
|
88
|
+
*
|
|
78
89
|
* @return promise
|
|
79
90
|
* @this model
|
|
80
91
|
*/
|
|
@@ -215,22 +226,20 @@ Model.prototype.findOne = async function (opts) {
|
|
|
215
226
|
Model.prototype.findOneAndUpdate = async function (opts) {
|
|
216
227
|
/**
|
|
217
228
|
* Find and update document(s) with auto population
|
|
229
|
+
*
|
|
218
230
|
* @param {object} opts
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* @param {boolean} <opts.timestamps> - whether `updatedAt` is automatically updated
|
|
232
|
-
* @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
|
|
233
|
-
* default, but false on update
|
|
231
|
+
* Find options:
|
|
232
|
+
* @param {object} <opts.query> - mongodb query object
|
|
233
|
+
* @param {array} <opts.populate> - find population, see docs
|
|
234
|
+
* @param {any} <any model.find option>
|
|
235
|
+
*
|
|
236
|
+
* Update options:
|
|
237
|
+
* @param {object|array} opts.data - mongodb document update object(s)
|
|
238
|
+
* @param {any} <any model.update option>
|
|
239
|
+
*
|
|
240
|
+
* Mongo options:
|
|
241
|
+
* @param {any} <any mongodb option>
|
|
242
|
+
*
|
|
234
243
|
* @return promise
|
|
235
244
|
* @this model
|
|
236
245
|
*/
|
|
@@ -259,50 +268,59 @@ Model.prototype.findOneAndUpdate = async function (opts) {
|
|
|
259
268
|
Model.prototype.update = async function (opts, type='update') {
|
|
260
269
|
/**
|
|
261
270
|
* Updates document(s) after validating data & before hooks.
|
|
271
|
+
*
|
|
262
272
|
* @param {object} opts
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
273
|
+
* @param {object} opts.query - mongodb query object
|
|
274
|
+
* @param {object|array} opts.data - mongodb document update object(s)
|
|
275
|
+
* @param {array|string|false} <opts.blacklist> - augment schema.updateBL, `false` will remove blacklisting
|
|
276
|
+
* @param {boolean} <opts.bracketToDotNotation> - covert fields in bracket notation (form data) to
|
|
277
|
+
* paths in dot notation instead of objects, e.g. { 'user[names][0]': 'John' } => { user.names.0 }
|
|
278
|
+
* @param {array|string} <opts.project> - return only these fields, ignores blacklisting
|
|
279
|
+
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
280
|
+
* @param {boolean} <opts.skipHooks> - validate and update before/after hooks
|
|
281
|
+
* @param {array|string|boolean} <opts.skipValidation> - skip validation for these fields, or pass `true` for all fields.
|
|
282
|
+
* $set and $unset objects are skipped by default, but can be enabled via opts.skipValidation=false
|
|
283
|
+
* @param {boolean} <opts.timestamps> - whether `updatedAt` is automatically updated
|
|
284
|
+
* @param {array|string|false} <opts.validateUndefined> - validates all 'required' undefined fields, true by
|
|
285
|
+
* default, but false on update
|
|
286
|
+
* @param {any} <any mongodb option or operation>
|
|
274
287
|
* @param {function} <type> - 'update', or 'findOneAndUpdate'
|
|
288
|
+
*
|
|
275
289
|
* @return promise(data)
|
|
276
290
|
* @this model
|
|
277
291
|
*/
|
|
278
292
|
try {
|
|
279
293
|
opts = await this._queryObject(opts, type)
|
|
280
|
-
let data = opts.data
|
|
281
294
|
let response = null
|
|
282
|
-
let operators = util.pick(opts, [
|
|
295
|
+
let operators = util.removeUndefined(util.pick(opts, [/^\$/, 'data']))
|
|
283
296
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
if (!util.isDefined(data) && util.isEmpty(operators)) {
|
|
297
|
+
if (operators.data && operators.$set) {
|
|
298
|
+
this.info(`'$set' fields take precedence over the data fields for \`${this.name}.${type}()\``)
|
|
299
|
+
} else if (!Object.keys(operators).length) {
|
|
289
300
|
throw new Error(`Please pass an update operator to ${this.name}.${type}(), e.g. data, $unset, etc`)
|
|
290
301
|
}
|
|
291
|
-
if (util.isDefined(data) && (!data || util.isEmpty(data))) {
|
|
292
|
-
throw new Error(`No valid data passed to ${this.name}.${type}({ data: .. })`)
|
|
293
|
-
}
|
|
294
302
|
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
303
|
+
// Validate data (doesn't mutate opts)
|
|
304
|
+
for (let key in operators) {
|
|
305
|
+
if (this._shouldValidate(opts, key)) {
|
|
306
|
+
operators[key] = await this.validate(operators[key], opts)
|
|
307
|
+
}
|
|
308
|
+
if (util.isEmpty(operators[key] || {})) {
|
|
309
|
+
throw new Error(`No valid data passed to ${this.name}.${type}({ ${key}: .. })`)
|
|
310
|
+
}
|
|
301
311
|
}
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
|
|
313
|
+
// Merge data into $set
|
|
314
|
+
if (operators.data || operators.$set) {
|
|
315
|
+
operators.$set = { ...(operators.data||{}), ...(operators['$set'] || {}) }
|
|
316
|
+
delete operators.data
|
|
304
317
|
}
|
|
305
318
|
|
|
319
|
+
// Hook: beforeUpdate (has access to original, non-validated data via opts.data)
|
|
320
|
+
const onlySet = Object.keys(operators).length == 1 && operators.$set
|
|
321
|
+
if (onlySet) operators.$set = await this._callHooks('beforeUpdate', operators.$set, opts)
|
|
322
|
+
else operators = await this._callHooks('beforeUpdate', operators, opts)
|
|
323
|
+
|
|
306
324
|
// findOneAndUpdate, get 'find' projection
|
|
307
325
|
if (type == 'findOneAndUpdate') {
|
|
308
326
|
if (opts.project) opts.projection = this._getProjectionFromProject(opts.project)
|
|
@@ -323,10 +341,8 @@ Model.prototype.update = async function (opts, type='update') {
|
|
|
323
341
|
)
|
|
324
342
|
}
|
|
325
343
|
|
|
326
|
-
// Hook: afterUpdate (doesn't have access to validated data)
|
|
327
|
-
if (response)
|
|
328
|
-
response = await util.runSeries.call(this, this.afterUpdate.map(f => f.bind(opts)), 'afterUpdate', response)
|
|
329
|
-
}
|
|
344
|
+
// Hook: afterUpdate (doesn't have access to validated data, just the response)
|
|
345
|
+
if (response) response = await this._callHooks('afterUpdate', response, opts)
|
|
330
346
|
|
|
331
347
|
// Hook: afterFind if findOneAndUpdate
|
|
332
348
|
if (response && type == 'findOneAndUpdate') {
|
|
@@ -346,11 +362,14 @@ Model.prototype.update = async function (opts, type='update') {
|
|
|
346
362
|
Model.prototype.remove = async function (opts) {
|
|
347
363
|
/**
|
|
348
364
|
* Remove document(s) with before and after hooks.
|
|
365
|
+
*
|
|
349
366
|
* @param {object} opts
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
367
|
+
* @param {object} <opts.query> - mongodb query object
|
|
368
|
+
* @param {boolean=true} <opts.multi> - set to false to limit the deletion to just one document
|
|
369
|
+
* @param {boolean} <opts.respond> - automatically call res.json/error (requires opts.req)
|
|
370
|
+
* @param {boolean} <opts.skipHooks> - remove before/after hooks
|
|
371
|
+
* @param {any} <any mongodb option>
|
|
372
|
+
*
|
|
354
373
|
* @return promise
|
|
355
374
|
* @this model
|
|
356
375
|
*/
|
|
@@ -358,9 +377,9 @@ Model.prototype.remove = async function (opts) {
|
|
|
358
377
|
opts = await this._queryObject(opts, 'remove')
|
|
359
378
|
|
|
360
379
|
// Remove
|
|
361
|
-
await
|
|
380
|
+
await this._callHooks('beforeRemove', null, opts)
|
|
362
381
|
let response = await this._remove(opts.query, util.omit(opts, this._queryOptions))
|
|
363
|
-
await
|
|
382
|
+
await this._callHooks('afterRemove', response, opts)
|
|
364
383
|
|
|
365
384
|
// Success
|
|
366
385
|
if (opts.req && opts.respond) opts.req.res.json(response)
|
|
@@ -379,9 +398,10 @@ Model.prototype._getProjectionFromBlacklist = function (type, customBlacklist) {
|
|
|
379
398
|
* Path collisions are removed
|
|
380
399
|
* E.g. ['pets.dogs', 'pets.dogs.name', '-cat', 'pets.dogs.age'] = { 'pets.dog': 0 }
|
|
381
400
|
*
|
|
382
|
-
* @param {string}
|
|
401
|
+
* @param {string} type - find, insert, or update
|
|
383
402
|
* @param {array|string|false} customBlacklist - normally passed through options
|
|
384
|
-
*
|
|
403
|
+
*
|
|
404
|
+
* @return {array|undefined} exclusion $project {'pets.name': 0}
|
|
385
405
|
* @this model
|
|
386
406
|
*
|
|
387
407
|
* 1. collate deep-blacklists
|
|
@@ -467,7 +487,8 @@ Model.prototype._getProjectionFromProject = function (customProject) {
|
|
|
467
487
|
* todo: tests
|
|
468
488
|
*
|
|
469
489
|
* @param {object|array|string} customProject - normally passed through options
|
|
470
|
-
*
|
|
490
|
+
*
|
|
491
|
+
* @return {array|undefined} in/exclusion projection {'pets.name': 0}
|
|
471
492
|
* @this model
|
|
472
493
|
*/
|
|
473
494
|
let projection
|
|
@@ -488,9 +509,11 @@ Model.prototype._getProjectionFromProject = function (customProject) {
|
|
|
488
509
|
Model.prototype._queryObject = async function (opts, type, _one) {
|
|
489
510
|
/**
|
|
490
511
|
* Normalize options
|
|
512
|
+
*
|
|
491
513
|
* @param {MongoId|string|object} opts
|
|
492
|
-
* @param {string}
|
|
493
|
-
* @param {boolean}
|
|
514
|
+
* @param {string} type - insert, update, find, remove, findOneAndUpdate
|
|
515
|
+
* @param {boolean} _one - return one document
|
|
516
|
+
*
|
|
494
517
|
* @return {Promise} opts
|
|
495
518
|
* @this model
|
|
496
519
|
*
|
|
@@ -531,6 +554,9 @@ Model.prototype._queryObject = async function (opts, type, _one) {
|
|
|
531
554
|
let order = (opts.sort.match(/:(-?[0-9])/) || [])[1]
|
|
532
555
|
opts.sort = { [name]: parseInt(order || 1) }
|
|
533
556
|
}
|
|
557
|
+
if (typeof opts.noDefaults == 'undefined' && typeof this.manager.opts.noDefaults != 'undefined') {
|
|
558
|
+
opts.noDefaults = this.manager.opts.noDefaults
|
|
559
|
+
}
|
|
534
560
|
if (util.isString(opts.noDefaults)) {
|
|
535
561
|
opts.noDefaults = [opts.noDefaults]
|
|
536
562
|
}
|
|
@@ -539,7 +565,7 @@ Model.prototype._queryObject = async function (opts, type, _one) {
|
|
|
539
565
|
// Data
|
|
540
566
|
if (!opts) opts = {}
|
|
541
567
|
if (!util.isDefined(opts.data) && util.isDefined((opts.req||{}).body)) opts.data = opts.req.body
|
|
542
|
-
if (util.isDefined(opts.data)) opts.data = await util.parseData(opts.data)
|
|
568
|
+
if (util.isDefined(opts.data)) opts.data = await util.parseData(opts.data, opts.bracketToDotNotation)/////
|
|
543
569
|
|
|
544
570
|
opts.type = type
|
|
545
571
|
opts[type] = true // still being included in the operation options..
|
|
@@ -551,11 +577,13 @@ Model.prototype._queryObject = async function (opts, type, _one) {
|
|
|
551
577
|
Model.prototype._pathBlacklisted = function (path, projectionInclusion, projectionKeys, matchDeepWhitelistedKeys=true) {
|
|
552
578
|
/**
|
|
553
579
|
* Checks if the path is blacklisted within a inclusion/exclusion projection
|
|
554
|
-
*
|
|
580
|
+
*
|
|
581
|
+
* @param {string} path - path without array brackets e.g. '.[]'
|
|
555
582
|
* @param {boolean} projectionInclusion - is a inclusion or exclusion projection (default is exclusion)
|
|
556
|
-
* @param {array}
|
|
583
|
+
* @param {array} projectionKeys - inclusion/exclusion projection keys, not mixed
|
|
557
584
|
* @param {boolean} matchDeepWhitelistedKeys - match deep whitelisted keys containing path
|
|
558
585
|
* E.g. pets.color == pets.color.age
|
|
586
|
+
*
|
|
559
587
|
* @return {boolean}
|
|
560
588
|
*/
|
|
561
589
|
if (projectionInclusion) {
|
|
@@ -589,8 +617,9 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
589
617
|
* e.g. "nurses": [{ model: 'user' }]
|
|
590
618
|
*
|
|
591
619
|
* @param {object|array|null} data
|
|
592
|
-
* @param {object}
|
|
593
|
-
* @param {object}
|
|
620
|
+
* @param {object} projection - opts.projection (== opts.blacklist is merged with all found deep model blacklists)
|
|
621
|
+
* @param {object} afterFindContext - handy context object given to schema.afterFind
|
|
622
|
+
*
|
|
594
623
|
* @return Promise(data)
|
|
595
624
|
* @this model
|
|
596
625
|
*/
|
|
@@ -603,6 +632,7 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
603
632
|
const projectionKeys = Object.keys(projection)
|
|
604
633
|
const projectionInclusion = projection[projectionKeys[0]] ? true : false // default false
|
|
605
634
|
if (!isArray) data = [data]
|
|
635
|
+
|
|
606
636
|
let modelFields = this
|
|
607
637
|
._recurseAndFindModels(data)
|
|
608
638
|
.concat(data.map((o, i) => ({
|
|
@@ -649,7 +679,7 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
649
679
|
const _opts = { ...afterFindContext, afterFindName: _modelName }
|
|
650
680
|
const _dataRef = _item.dataRefParent[_item.dataRefKey]
|
|
651
681
|
_item.dataRefParent[_item.dataRefKey] = (
|
|
652
|
-
await
|
|
682
|
+
await _model._callHooks('afterFind', _dataRef, _opts)
|
|
653
683
|
)
|
|
654
684
|
}).bind(null, item)
|
|
655
685
|
)
|
|
@@ -662,8 +692,10 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
662
692
|
Model.prototype._recurseAndFindModels = function (dataArr, dataParentPath='') {
|
|
663
693
|
/**
|
|
664
694
|
* Returns a flattened list of models fields, sorted by depth
|
|
695
|
+
*
|
|
665
696
|
* @param {object|array} dataArr
|
|
666
|
-
* @param {string}
|
|
697
|
+
* @param {string} <dataParentPath>
|
|
698
|
+
*
|
|
667
699
|
* @this Model
|
|
668
700
|
* @return [{
|
|
669
701
|
* dataRefParent: { *fields here* },
|
|
@@ -733,6 +765,14 @@ Model.prototype._recurseAndFindModels = function (dataArr, dataParentPath='') {
|
|
|
733
765
|
return out
|
|
734
766
|
}
|
|
735
767
|
|
|
768
|
+
Model.prototype._shouldValidate = function (opts, operatorName) {
|
|
769
|
+
return ['data', '$set', '$unset'].includes(operatorName) && (
|
|
770
|
+
opts.skipValidation === false ? true
|
|
771
|
+
: opts.skipValidation && opts.skipValidation !== true ? true
|
|
772
|
+
: operatorName == 'data' // enabled by default
|
|
773
|
+
)
|
|
774
|
+
}
|
|
775
|
+
|
|
736
776
|
Model.prototype._queryOptions = [
|
|
737
777
|
// todo: remove type properties
|
|
738
778
|
'blacklist', 'data', 'find', 'findOneAndUpdate', 'insert', 'model', '_one', 'populate', 'project',
|