monastery 3.2.0 → 3.3.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 +4 -0
- package/docs/manager/index.md +5 -4
- package/docs/model/update.md +11 -1
- package/lib/index.js +1 -1
- package/lib/model-crud.js +3 -0
- package/lib/model-validate.js +7 -3
- package/package.json +1 -1
- package/test/crud.js +112 -43
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.3.0](https://github.com/boycce/monastery/compare/3.2.1...3.3.0) (2024-08-07)
|
|
6
|
+
|
|
7
|
+
### [3.2.1](https://github.com/boycce/monastery/compare/3.2.0...3.2.1) (2024-08-05)
|
|
8
|
+
|
|
5
9
|
## [3.2.0](https://github.com/boycce/monastery/compare/3.1.0...3.2.0) (2024-07-17)
|
|
6
10
|
|
|
7
11
|
### [3.1.1](https://github.com/boycce/monastery/compare/3.1.0...3.1.1) (2024-05-27)
|
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/model/update.md
CHANGED
|
@@ -22,7 +22,17 @@ Update document(s) in a collection and calls model hooks: `beforeUpdate`, `afte
|
|
|
22
22
|
|
|
23
23
|
### Returns
|
|
24
24
|
|
|
25
|
-
A promise
|
|
25
|
+
`{Promise<Object>}` A promise that resolves to an object with the updated fields.
|
|
26
|
+
|
|
27
|
+
You can also access the native MongoDB output via `result._output`, a prototype property:
|
|
28
|
+
```js
|
|
29
|
+
{
|
|
30
|
+
acknowledged: true,
|
|
31
|
+
modifiedCount: 1,
|
|
32
|
+
matchedCount: 1,
|
|
33
|
+
...
|
|
34
|
+
}
|
|
35
|
+
```
|
|
26
36
|
|
|
27
37
|
### Example
|
|
28
38
|
|
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]
|
package/lib/model-crud.js
CHANGED
|
@@ -531,6 +531,9 @@ Model.prototype._queryObject = async function (opts, type, _one) {
|
|
|
531
531
|
let order = (opts.sort.match(/:(-?[0-9])/) || [])[1]
|
|
532
532
|
opts.sort = { [name]: parseInt(order || 1) }
|
|
533
533
|
}
|
|
534
|
+
if (typeof opts.noDefaults == 'undefined' && typeof this.manager.opts.noDefaults != 'undefined') {
|
|
535
|
+
opts.noDefaults = this.manager.opts.noDefaults
|
|
536
|
+
}
|
|
534
537
|
if (util.isString(opts.noDefaults)) {
|
|
535
538
|
opts.noDefaults = [opts.noDefaults]
|
|
536
539
|
}
|
package/lib/model-validate.js
CHANGED
|
@@ -140,6 +140,7 @@ Model.prototype._validateFields = function (dataRoot, fields, data, opts, parent
|
|
|
140
140
|
// Field is a subdocument
|
|
141
141
|
if (schema.isObject) {
|
|
142
142
|
// Object schema errors
|
|
143
|
+
let res
|
|
143
144
|
const verrors = this._validateRules(dataRoot, schema, value, opts, path)
|
|
144
145
|
if (verrors.length) errors.push(...verrors)
|
|
145
146
|
// Recurse if inserting, value is a subdocument, or a deep property (todo: not dot-notation)
|
|
@@ -148,21 +149,22 @@ Model.prototype._validateFields = function (dataRoot, fields, data, opts, parent
|
|
|
148
149
|
util.isObject(value) ||
|
|
149
150
|
(util.isDefined(opts.validateUndefined) ? opts.validateUndefined : (path||'').indexOf('.') !== -1)
|
|
150
151
|
) {
|
|
151
|
-
|
|
152
|
+
res = this._validateFields(dataRoot, field, value, opts, path, path2)
|
|
152
153
|
if (res[0].length) errors.push(...res[0])
|
|
153
154
|
}
|
|
154
155
|
if (util.isDefined(value) && !verrors.length) {
|
|
155
|
-
data2[indexOrFieldName] = res? res[1] : value
|
|
156
|
+
data2[indexOrFieldName] = res ? res[1] : value
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
// Field is an array
|
|
159
160
|
} else if (schema.isArray) {
|
|
160
161
|
// Array schema errors
|
|
162
|
+
let res2
|
|
161
163
|
const verrors = this._validateRules(dataRoot, schema, value, opts, path)
|
|
162
164
|
if (verrors.length) errors.push(...verrors)
|
|
163
165
|
// Data value is array too
|
|
164
166
|
if (util.isArray(value)) {
|
|
165
|
-
|
|
167
|
+
res2 = this._validateFields(dataRoot, field, value, opts, path, path2)
|
|
166
168
|
if (res2[0].length) errors.push(...res2[0])
|
|
167
169
|
}
|
|
168
170
|
if (util.isDefined(value) && !verrors.length) {
|
|
@@ -209,6 +211,8 @@ Model.prototype._validateRules = function (dataRoot, fieldSchema, value, opts, p
|
|
|
209
211
|
if (opts.skipValidation === true) return []
|
|
210
212
|
|
|
211
213
|
// Skip validation for a field, takes in to account if a parent has been skipped.
|
|
214
|
+
// Todo: Maybe we can use model-crud:blacklisted logic? But just allow it to skip all [0-9] paths via '$'
|
|
215
|
+
// if (!parentPath && fieldName == 'categories') console.timeEnd(i + ' - ' + m + ' - ' + fieldName + ' - 1')/////
|
|
212
216
|
if (opts.skipValidation.length) {
|
|
213
217
|
//console.log(path, field, opts)
|
|
214
218
|
let pathChunks = path.split('.')
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "monastery",
|
|
3
3
|
"description": "⛪ A simple, straightforward MongoDB ODM",
|
|
4
4
|
"author": "Ricky Boyce",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.3.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
package/test/crud.js
CHANGED
|
@@ -337,41 +337,112 @@ test('find default field blacklisted', async () => {
|
|
|
337
337
|
})
|
|
338
338
|
})
|
|
339
339
|
|
|
340
|
-
test('find default field population with noDefaults', async () => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
340
|
+
test('find default field population with option noDefaults', async () => {
|
|
341
|
+
async function setup(noDefaults) {
|
|
342
|
+
/**
|
|
343
|
+
* Setup
|
|
344
|
+
* @returns {Object} {dog, user, dog1Doc, dog2Doc, user1Doc}
|
|
345
|
+
*/
|
|
346
|
+
// similar to "find default field population"
|
|
347
|
+
const db = monastery('127.0.0.1/monastery', { noDefaults: noDefaults, timestamps: false })
|
|
348
|
+
const userDefinition = {
|
|
349
|
+
fields: {
|
|
350
|
+
name: { type: 'string', default: 'Martin Luther' },
|
|
351
|
+
addresses: [{ city: { type: 'string' }, country: { type: 'string', default: 'Germany' } }],
|
|
352
|
+
address: { country: { type: 'string', default: 'Germany' }},
|
|
353
|
+
pet: { dog: { model: 'dog' }},
|
|
354
|
+
pets: { dog: [{ model: 'dog' }]},
|
|
355
|
+
dogs: [{ model: 'dog' }], // virtual association
|
|
356
|
+
},
|
|
357
|
+
}
|
|
358
|
+
const dogDefinition = {
|
|
359
|
+
fields: {
|
|
360
|
+
name: { type: 'string', default: 'Scruff' },
|
|
361
|
+
user: { model: 'user' },
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
const user = db.model('user', userDefinition)
|
|
365
|
+
const dog = db.model('dog', dogDefinition)
|
|
366
|
+
|
|
367
|
+
// Default field population test
|
|
368
|
+
// Insert documents (without defaults)
|
|
369
|
+
let dog1Doc = await dog._insert({})
|
|
370
|
+
let dog2Doc = await dog._insert({})
|
|
371
|
+
let user1Doc = await user._insert({
|
|
372
|
+
addresses: [
|
|
373
|
+
{ city: 'Frankfurt' },
|
|
374
|
+
{ city: 'Christchurch', country: 'New Zealand' },
|
|
375
|
+
],
|
|
376
|
+
pet: { dog: dog1Doc._id },
|
|
377
|
+
pets: { dog: [dog1Doc._id, dog2Doc._id]},
|
|
378
|
+
})
|
|
379
|
+
await dog._update(dog1Doc._id, { $set: { user: user1Doc._id }})
|
|
380
|
+
return { db, dog, user, dog1Doc, dog2Doc, user1Doc }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const s1 = await setup()
|
|
384
|
+
|
|
385
|
+
// Test noDefaults = true
|
|
386
|
+
const find1 = await s1.db.user.findOne({
|
|
387
|
+
query: s1.user1Doc._id,
|
|
388
|
+
populate: ['pet.dog', 'pets.dog', {
|
|
389
|
+
from: 'dog',
|
|
390
|
+
localField: '_id',
|
|
391
|
+
foreignField: 'user',
|
|
392
|
+
as: 'dogs',
|
|
393
|
+
}],
|
|
394
|
+
noDefaults: true,
|
|
351
395
|
})
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
396
|
+
expect(find1).toEqual({
|
|
397
|
+
_id: s1.user1Doc._id,
|
|
398
|
+
addresses: [
|
|
399
|
+
{ city: 'Frankfurt' },
|
|
400
|
+
{ city: 'Christchurch', country: 'New Zealand' },
|
|
401
|
+
],
|
|
402
|
+
pet: { dog: { _id: s1.dog1Doc._id, user: s1.user1Doc._id }},
|
|
403
|
+
pets: {
|
|
404
|
+
dog: [
|
|
405
|
+
{ _id: s1.dog1Doc._id, user: s1.user1Doc._id },
|
|
406
|
+
{ _id: s1.dog2Doc._id },
|
|
407
|
+
],
|
|
356
408
|
},
|
|
409
|
+
dogs: [{ _id: s1.dog1Doc._id, user: s1.user1Doc._id }],
|
|
357
410
|
})
|
|
358
411
|
|
|
359
|
-
//
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
412
|
+
// Test noDefaults = ['dogs', 'pet.dog']
|
|
413
|
+
const find2 = await s1.db.user.findOne({
|
|
414
|
+
query: s1.user1Doc._id,
|
|
415
|
+
populate: ['pet.dog', 'pets.dog', {
|
|
416
|
+
from: 'dog',
|
|
417
|
+
localField: '_id',
|
|
418
|
+
foreignField: 'user',
|
|
419
|
+
as: 'dogs',
|
|
420
|
+
}],
|
|
421
|
+
noDefaults: ['dogs', 'pet.dog'],
|
|
422
|
+
})
|
|
423
|
+
expect(find2).toEqual({
|
|
424
|
+
_id: s1.user1Doc._id,
|
|
425
|
+
name: 'Martin Luther',
|
|
364
426
|
addresses: [
|
|
365
|
-
{ city: 'Frankfurt' },
|
|
427
|
+
{ city: 'Frankfurt', country: 'Germany' },
|
|
366
428
|
{ city: 'Christchurch', country: 'New Zealand' },
|
|
367
429
|
],
|
|
368
|
-
|
|
369
|
-
|
|
430
|
+
address: { country: 'Germany' },
|
|
431
|
+
pet: { dog: { _id: s1.dog1Doc._id, user: s1.user1Doc._id }}, // should not have a default name
|
|
432
|
+
pets: {
|
|
433
|
+
dog: [
|
|
434
|
+
{ _id: s1.dog1Doc._id, name: 'Scruff', user: s1.user1Doc._id },
|
|
435
|
+
{ _id: s1.dog2Doc._id, name: 'Scruff' },
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
dogs: [{ _id: s1.dog1Doc._id, user: s1.user1Doc._id }], // should not have a default name
|
|
370
439
|
})
|
|
371
|
-
await db.dog._update(dog1._id, { $set: { user: user1._id }})
|
|
372
440
|
|
|
373
|
-
|
|
374
|
-
|
|
441
|
+
const s2 = await setup(['dogs'])
|
|
442
|
+
|
|
443
|
+
// Test noDefaults = true overrides manager option noDefaults = ['dogs']
|
|
444
|
+
const find3 = await s2.db.user.findOne({
|
|
445
|
+
query: s2.user1Doc._id,
|
|
375
446
|
populate: ['pet.dog', 'pets.dog', {
|
|
376
447
|
from: 'dog',
|
|
377
448
|
localField: '_id',
|
|
@@ -380,50 +451,48 @@ test('find default field population with noDefaults', async () => {
|
|
|
380
451
|
}],
|
|
381
452
|
noDefaults: true,
|
|
382
453
|
})
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
_id: user1._id,
|
|
454
|
+
expect(find3).toEqual({
|
|
455
|
+
_id: s2.user1Doc._id,
|
|
386
456
|
addresses: [
|
|
387
457
|
{ city: 'Frankfurt' },
|
|
388
458
|
{ city: 'Christchurch', country: 'New Zealand' },
|
|
389
459
|
],
|
|
390
|
-
pet: { dog: { _id:
|
|
460
|
+
pet: { dog: { _id: s2.dog1Doc._id, user: s2.user1Doc._id }},
|
|
391
461
|
pets: {
|
|
392
462
|
dog: [
|
|
393
|
-
{ _id:
|
|
394
|
-
{ _id:
|
|
463
|
+
{ _id: s2.dog1Doc._id, user: s2.user1Doc._id },
|
|
464
|
+
{ _id: s2.dog2Doc._id },
|
|
395
465
|
],
|
|
396
466
|
},
|
|
397
|
-
dogs: [{ _id:
|
|
467
|
+
dogs: [{ _id: s2.dog1Doc._id, user: s2.user1Doc._id }],
|
|
398
468
|
})
|
|
399
469
|
|
|
400
|
-
|
|
401
|
-
|
|
470
|
+
// Test manager option noDefaults = ['dogs']
|
|
471
|
+
const find4 = await s2.db.user.findOne({
|
|
472
|
+
query: s2.user1Doc._id,
|
|
402
473
|
populate: ['pet.dog', 'pets.dog', {
|
|
403
474
|
from: 'dog',
|
|
404
475
|
localField: '_id',
|
|
405
476
|
foreignField: 'user',
|
|
406
477
|
as: 'dogs',
|
|
407
478
|
}],
|
|
408
|
-
noDefaults: ['dogs', 'pet.dog'],
|
|
409
479
|
})
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
_id: user1._id,
|
|
480
|
+
expect(find4).toEqual({
|
|
481
|
+
_id: s2.user1Doc._id,
|
|
413
482
|
name: 'Martin Luther',
|
|
414
483
|
addresses: [
|
|
415
484
|
{ city: 'Frankfurt', country: 'Germany' },
|
|
416
485
|
{ city: 'Christchurch', country: 'New Zealand' },
|
|
417
486
|
],
|
|
418
487
|
address: { country: 'Germany' },
|
|
419
|
-
pet: { dog: { _id:
|
|
488
|
+
pet: { dog: { _id: s2.dog1Doc._id, user: s2.user1Doc._id, name: 'Scruff' }},
|
|
420
489
|
pets: {
|
|
421
490
|
dog: [
|
|
422
|
-
{ _id:
|
|
423
|
-
{ _id:
|
|
491
|
+
{ _id: s2.dog1Doc._id, name: 'Scruff', user: s2.user1Doc._id },
|
|
492
|
+
{ _id: s2.dog2Doc._id, name: 'Scruff' },
|
|
424
493
|
],
|
|
425
494
|
},
|
|
426
|
-
dogs: [{ _id:
|
|
495
|
+
dogs: [{ _id: s2.dog1Doc._id, user: s2.user1Doc._id }], // should not have a default name
|
|
427
496
|
})
|
|
428
497
|
})
|
|
429
498
|
|