monastery 3.3.0 → 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 +2 -0
- package/docs/readme.md +4 -2
- package/lib/index.js +14 -2
- package/lib/model-crud.js +125 -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 +128 -1
- 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,8 @@
|
|
|
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
|
+
|
|
5
7
|
## [3.3.0](https://github.com/boycce/monastery/compare/3.2.1...3.3.0) (2024-08-07)
|
|
6
8
|
|
|
7
9
|
### [3.2.1](https://github.com/boycce/monastery/compare/3.2.0...3.2.1) (2024-08-05)
|
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
|
@@ -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
|
*
|
|
@@ -542,7 +565,7 @@ Model.prototype._queryObject = async function (opts, type, _one) {
|
|
|
542
565
|
// Data
|
|
543
566
|
if (!opts) opts = {}
|
|
544
567
|
if (!util.isDefined(opts.data) && util.isDefined((opts.req||{}).body)) opts.data = opts.req.body
|
|
545
|
-
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)/////
|
|
546
569
|
|
|
547
570
|
opts.type = type
|
|
548
571
|
opts[type] = true // still being included in the operation options..
|
|
@@ -554,11 +577,13 @@ Model.prototype._queryObject = async function (opts, type, _one) {
|
|
|
554
577
|
Model.prototype._pathBlacklisted = function (path, projectionInclusion, projectionKeys, matchDeepWhitelistedKeys=true) {
|
|
555
578
|
/**
|
|
556
579
|
* Checks if the path is blacklisted within a inclusion/exclusion projection
|
|
557
|
-
*
|
|
580
|
+
*
|
|
581
|
+
* @param {string} path - path without array brackets e.g. '.[]'
|
|
558
582
|
* @param {boolean} projectionInclusion - is a inclusion or exclusion projection (default is exclusion)
|
|
559
|
-
* @param {array}
|
|
583
|
+
* @param {array} projectionKeys - inclusion/exclusion projection keys, not mixed
|
|
560
584
|
* @param {boolean} matchDeepWhitelistedKeys - match deep whitelisted keys containing path
|
|
561
585
|
* E.g. pets.color == pets.color.age
|
|
586
|
+
*
|
|
562
587
|
* @return {boolean}
|
|
563
588
|
*/
|
|
564
589
|
if (projectionInclusion) {
|
|
@@ -592,8 +617,9 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
592
617
|
* e.g. "nurses": [{ model: 'user' }]
|
|
593
618
|
*
|
|
594
619
|
* @param {object|array|null} data
|
|
595
|
-
* @param {object}
|
|
596
|
-
* @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
|
+
*
|
|
597
623
|
* @return Promise(data)
|
|
598
624
|
* @this model
|
|
599
625
|
*/
|
|
@@ -606,6 +632,7 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
606
632
|
const projectionKeys = Object.keys(projection)
|
|
607
633
|
const projectionInclusion = projection[projectionKeys[0]] ? true : false // default false
|
|
608
634
|
if (!isArray) data = [data]
|
|
635
|
+
|
|
609
636
|
let modelFields = this
|
|
610
637
|
._recurseAndFindModels(data)
|
|
611
638
|
.concat(data.map((o, i) => ({
|
|
@@ -652,7 +679,7 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
652
679
|
const _opts = { ...afterFindContext, afterFindName: _modelName }
|
|
653
680
|
const _dataRef = _item.dataRefParent[_item.dataRefKey]
|
|
654
681
|
_item.dataRefParent[_item.dataRefKey] = (
|
|
655
|
-
await
|
|
682
|
+
await _model._callHooks('afterFind', _dataRef, _opts)
|
|
656
683
|
)
|
|
657
684
|
}).bind(null, item)
|
|
658
685
|
)
|
|
@@ -665,8 +692,10 @@ Model.prototype._processAfterFind = async function (data, projection={}, afterFi
|
|
|
665
692
|
Model.prototype._recurseAndFindModels = function (dataArr, dataParentPath='') {
|
|
666
693
|
/**
|
|
667
694
|
* Returns a flattened list of models fields, sorted by depth
|
|
695
|
+
*
|
|
668
696
|
* @param {object|array} dataArr
|
|
669
|
-
* @param {string}
|
|
697
|
+
* @param {string} <dataParentPath>
|
|
698
|
+
*
|
|
670
699
|
* @this Model
|
|
671
700
|
* @return [{
|
|
672
701
|
* dataRefParent: { *fields here* },
|
|
@@ -736,6 +765,14 @@ Model.prototype._recurseAndFindModels = function (dataArr, dataParentPath='') {
|
|
|
736
765
|
return out
|
|
737
766
|
}
|
|
738
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
|
+
|
|
739
776
|
Model.prototype._queryOptions = [
|
|
740
777
|
// todo: remove type properties
|
|
741
778
|
'blacklist', 'data', 'find', 'findOneAndUpdate', 'insert', 'model', '_one', 'populate', 'project',
|