monastery 1.35.0 → 1.36.2
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 +2 -1
- package/.travis.yml +1 -1
- package/changelog.md +32 -1
- package/docs/{schema/rules.md → definition/field-rules.md} +6 -4
- package/docs/definition/index.md +338 -0
- package/docs/image-plugin.md +16 -16
- package/docs/manager/index.md +8 -8
- package/docs/manager/model.md +4 -3
- package/docs/manager/models.md +1 -1
- package/docs/model/find.md +31 -29
- package/docs/model/findOne.md +3 -4
- package/docs/model/findOneAndUpdate.md +42 -0
- package/docs/model/index.md +2 -2
- package/docs/model/insert.md +22 -20
- package/docs/model/remove.md +3 -3
- package/docs/model/update.md +11 -10
- package/docs/model/validate.md +22 -25
- package/docs/readme.md +8 -7
- package/lib/index.js +36 -34
- package/lib/model-crud.js +89 -25
- package/lib/model-validate.js +28 -22
- 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/blacklisting.js +132 -177
- package/test/crud.js +83 -25
- package/test/mock/blacklisting.js +122 -0
- package/test/monk.js +1 -1
- package/test/test.js +2 -0
- package/test/validate.js +33 -3
- package/docs/schema/index.md +0 -313
package/test/crud.js
CHANGED
|
@@ -145,7 +145,7 @@ module.exports = function(monastery, opendb) {
|
|
|
145
145
|
db2.close()
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
-
test('update
|
|
148
|
+
test('update general', async () => {
|
|
149
149
|
let db = (await opendb(null)).db
|
|
150
150
|
let user = db.model('user', {
|
|
151
151
|
fields: {
|
|
@@ -335,7 +335,7 @@ module.exports = function(monastery, opendb) {
|
|
|
335
335
|
|
|
336
336
|
test('find default field population', async () => {
|
|
337
337
|
let db = (await opendb(null)).db
|
|
338
|
-
|
|
338
|
+
db.model('user', {
|
|
339
339
|
fields: {
|
|
340
340
|
name: { type: 'string', default: 'Martin Luther' },
|
|
341
341
|
addresses: [{ city: { type: 'string' }, country: { type: 'string', default: 'Germany' } }],
|
|
@@ -344,7 +344,7 @@ module.exports = function(monastery, opendb) {
|
|
|
344
344
|
dogs: [{ model: 'dog' }], // virtual association
|
|
345
345
|
}
|
|
346
346
|
})
|
|
347
|
-
|
|
347
|
+
db.model('dog', {
|
|
348
348
|
fields: {
|
|
349
349
|
name: { type: 'string', default: 'Scruff' },
|
|
350
350
|
user: { model: 'user' }
|
|
@@ -352,35 +352,35 @@ module.exports = function(monastery, opendb) {
|
|
|
352
352
|
})
|
|
353
353
|
|
|
354
354
|
// Default field doesn't override null
|
|
355
|
-
let nulldoc1 = await dog.insert({ data: { name: null }})
|
|
356
|
-
let nullfind1 = await dog.findOne({ query: nulldoc1._id })
|
|
355
|
+
let nulldoc1 = await db.dog.insert({ data: { name: null }})
|
|
356
|
+
let nullfind1 = await db.dog.findOne({ query: nulldoc1._id })
|
|
357
357
|
expect(nullfind1).toEqual({ _id: nulldoc1._id, name: null })
|
|
358
358
|
|
|
359
359
|
// Default field doesn't override empty string
|
|
360
|
-
let nulldoc2 = await dog.insert({ data: { name: '' }})
|
|
361
|
-
let nullfind2 = await dog.findOne({ query: nulldoc2._id })
|
|
360
|
+
let nulldoc2 = await db.dog.insert({ data: { name: '' }})
|
|
361
|
+
let nullfind2 = await db.dog.findOne({ query: nulldoc2._id })
|
|
362
362
|
expect(nullfind2).toEqual({ _id: nulldoc2._id, name: '' })
|
|
363
363
|
|
|
364
364
|
// Default field overrides undefined
|
|
365
|
-
let nulldoc3 = await dog.insert({ data: { name: undefined }})
|
|
366
|
-
let nullfind3 = await dog.findOne({ query: nulldoc3._id })
|
|
365
|
+
let nulldoc3 = await db.dog.insert({ data: { name: undefined }})
|
|
366
|
+
let nullfind3 = await db.dog.findOne({ query: nulldoc3._id })
|
|
367
367
|
expect(nullfind3).toEqual({ _id: nullfind3._id, name: 'Scruff' })
|
|
368
368
|
|
|
369
369
|
// Default field population test
|
|
370
|
-
// Note that addresses.1.country shouldn't be
|
|
370
|
+
// Note that addresses.1.country shouldn't be overridden
|
|
371
371
|
// Insert documents (without defaults)
|
|
372
|
-
let
|
|
373
|
-
let
|
|
372
|
+
let dog1 = await db.dog._insert({})
|
|
373
|
+
let user1 = await db.user._insert({
|
|
374
374
|
addresses: [
|
|
375
375
|
{ city: 'Frankfurt' },
|
|
376
376
|
{ city: 'Christchurch', country: 'New Zealand' }
|
|
377
377
|
],
|
|
378
|
-
pet: { dog:
|
|
378
|
+
pet: { dog: dog1._id }
|
|
379
379
|
})
|
|
380
|
-
await dog._update(
|
|
380
|
+
await db.dog._update(dog1._id, { $set: { user: user1._id }})
|
|
381
381
|
|
|
382
|
-
let find1 = await user.findOne({
|
|
383
|
-
query:
|
|
382
|
+
let find1 = await db.user.findOne({
|
|
383
|
+
query: user1._id,
|
|
384
384
|
populate: ['pet.dog', {
|
|
385
385
|
from: 'dog',
|
|
386
386
|
localField: '_id',
|
|
@@ -389,20 +389,20 @@ module.exports = function(monastery, opendb) {
|
|
|
389
389
|
}]
|
|
390
390
|
})
|
|
391
391
|
expect(find1).toEqual({
|
|
392
|
-
_id:
|
|
392
|
+
_id: user1._id,
|
|
393
393
|
name: 'Martin Luther',
|
|
394
394
|
addresses: [
|
|
395
395
|
{ city: 'Frankfurt', country: 'Germany' },
|
|
396
396
|
{ city: 'Christchurch', country: 'New Zealand' }
|
|
397
397
|
],
|
|
398
398
|
address: { country: 'Germany' },
|
|
399
|
-
pet: { dog: { _id:
|
|
400
|
-
dogs: [{ _id:
|
|
399
|
+
pet: { dog: { _id: dog1._id, name: 'Scruff', user: user1._id }},
|
|
400
|
+
dogs: [{ _id: dog1._id, name: 'Scruff', user: user1._id }]
|
|
401
401
|
})
|
|
402
402
|
|
|
403
403
|
// Blacklisted default field population test
|
|
404
|
-
let find2 = await user.findOne({
|
|
405
|
-
query:
|
|
404
|
+
let find2 = await db.user.findOne({
|
|
405
|
+
query: user1._id,
|
|
406
406
|
populate: ['pet.dog', {
|
|
407
407
|
from: 'dog',
|
|
408
408
|
localField: '_id',
|
|
@@ -413,24 +413,82 @@ module.exports = function(monastery, opendb) {
|
|
|
413
413
|
// ^ great test (address should cancel addresses if not stopping at the .)
|
|
414
414
|
})
|
|
415
415
|
expect(find2).toEqual({
|
|
416
|
-
_id:
|
|
416
|
+
_id: user1._id,
|
|
417
417
|
name: 'Martin Luther',
|
|
418
418
|
addresses: [{ city: 'Frankfurt' }, { city: 'Christchurch' }],
|
|
419
|
-
pet: { dog: { _id:
|
|
420
|
-
dogs: [{ _id:
|
|
419
|
+
pet: { dog: { _id: dog1._id, name: 'Scruff', user: user1._id }},
|
|
420
|
+
dogs: [{ _id: dog1._id, user: user1._id }]
|
|
421
421
|
})
|
|
422
422
|
|
|
423
423
|
db.close()
|
|
424
424
|
})
|
|
425
425
|
|
|
426
|
-
test('
|
|
426
|
+
test('findOneAndUpdate general', async () => {
|
|
427
|
+
// todo: test all findOneAndUpdate options
|
|
428
|
+
// todo: test find & update hooks
|
|
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 general', 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([
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module.exports.bird = {
|
|
2
|
+
schema: function() {
|
|
3
|
+
return {
|
|
4
|
+
fields: {
|
|
5
|
+
color: { type: 'string', default: 'red' },
|
|
6
|
+
height: { type: 'number' },
|
|
7
|
+
name: { type: 'string' },
|
|
8
|
+
sub: {
|
|
9
|
+
color: { type: 'string', default: 'red' },
|
|
10
|
+
},
|
|
11
|
+
wing: {
|
|
12
|
+
size: { type: 'number' },
|
|
13
|
+
sizes: {
|
|
14
|
+
one: { type: 'number' },
|
|
15
|
+
two: { type: 'number' },
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
findBL: ['wing'],
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
mock: function() {
|
|
23
|
+
return {
|
|
24
|
+
height: 12,
|
|
25
|
+
name: 'Ponyo',
|
|
26
|
+
sub: {},
|
|
27
|
+
wing: { size: 1, sizes: { one: 1, two: 1 }},
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports.user = {
|
|
33
|
+
schema: function() {
|
|
34
|
+
return {
|
|
35
|
+
fields: {
|
|
36
|
+
bird: { model: 'bird' },
|
|
37
|
+
list: [{ type: 'number' }],
|
|
38
|
+
dog: { type: 'string' },
|
|
39
|
+
pet: { type: 'string' },
|
|
40
|
+
pets: [{
|
|
41
|
+
name: { type: 'string'},
|
|
42
|
+
age: { type: 'number'}
|
|
43
|
+
}],
|
|
44
|
+
animals: {
|
|
45
|
+
cat: { type: 'string' },
|
|
46
|
+
dog: { type: 'string' }
|
|
47
|
+
},
|
|
48
|
+
hiddenPets: [{
|
|
49
|
+
name: { type: 'string'}
|
|
50
|
+
}],
|
|
51
|
+
hiddenList: [{ type: 'number'}],
|
|
52
|
+
deep: {
|
|
53
|
+
deep2: {
|
|
54
|
+
deep3: {
|
|
55
|
+
deep4: { type: 'string' }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
deeper: {
|
|
60
|
+
deeper2: {
|
|
61
|
+
deeper3: {
|
|
62
|
+
deeper4: { type: 'string' }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
deepModel: {
|
|
67
|
+
myBird: { model: 'bird' }
|
|
68
|
+
},
|
|
69
|
+
hiddenDeepModel: {
|
|
70
|
+
myBird: { model: 'bird' }
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
findBL: [
|
|
74
|
+
'dog',
|
|
75
|
+
'animals.cat',
|
|
76
|
+
'pets.age',
|
|
77
|
+
'hiddenPets',
|
|
78
|
+
'hiddenList',
|
|
79
|
+
'deep.deep2.deep3',
|
|
80
|
+
'deeper',
|
|
81
|
+
'hiddenDeepModel',
|
|
82
|
+
],
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
mock: function(bird1) {
|
|
86
|
+
return {
|
|
87
|
+
bird: bird1._id,
|
|
88
|
+
list: [44, 54],
|
|
89
|
+
dog: 'Bruce',
|
|
90
|
+
pet: 'Freddy',
|
|
91
|
+
pets: [{ name: 'Pluto', age: 5 }, { name: 'Milo', age: 4 }],
|
|
92
|
+
animals: {
|
|
93
|
+
cat: 'Ginger',
|
|
94
|
+
dog: 'Max'
|
|
95
|
+
},
|
|
96
|
+
hiddenPets: [{
|
|
97
|
+
name: 'secretPet'
|
|
98
|
+
}],
|
|
99
|
+
hiddenList: [12, 23],
|
|
100
|
+
deep: {
|
|
101
|
+
deep2: {
|
|
102
|
+
deep3: {
|
|
103
|
+
deep4: 'hideme'
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
deeper: {
|
|
108
|
+
deeper2: {
|
|
109
|
+
deeper3: {
|
|
110
|
+
deeper4: 'hideme'
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
deepModel: {
|
|
115
|
+
myBird: bird1._id
|
|
116
|
+
},
|
|
117
|
+
hiddenDeepModel: {
|
|
118
|
+
myBird: bird1._id
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
}
|
package/test/monk.js
CHANGED
package/test/test.js
CHANGED
package/test/validate.js
CHANGED
|
@@ -808,8 +808,10 @@ module.exports = function(monastery, opendb) {
|
|
|
808
808
|
})
|
|
809
809
|
})
|
|
810
810
|
|
|
811
|
-
test('schema default
|
|
811
|
+
test('schema options default', async () => {
|
|
812
812
|
// Setup
|
|
813
|
+
// todo: test objects
|
|
814
|
+
// todo: change test name
|
|
813
815
|
let db = (await opendb(false)).db
|
|
814
816
|
let user = db.model('user', { fields: {
|
|
815
817
|
name: { type: 'string', minLength: 7 },
|
|
@@ -889,7 +891,35 @@ module.exports = function(monastery, opendb) {
|
|
|
889
891
|
await expect(user.validate({ amount: 'bad' })).rejects.toContainEqual(mock2)
|
|
890
892
|
})
|
|
891
893
|
|
|
892
|
-
test('schema
|
|
894
|
+
test('schema options objects', async () => {
|
|
895
|
+
let db = (await opendb(null, {
|
|
896
|
+
timestamps: false,
|
|
897
|
+
nullObjects: true,
|
|
898
|
+
serverSelectionTimeoutMS: 2000
|
|
899
|
+
})).db
|
|
900
|
+
let user = db.model('user', {
|
|
901
|
+
fields: {
|
|
902
|
+
location: {
|
|
903
|
+
lat: { type: 'number' },
|
|
904
|
+
lng: { type: 'number' },
|
|
905
|
+
schema: { required: true },
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
// Subdocument data (null/string)
|
|
911
|
+
await expect(user.validate({ location: null })).rejects.toEqual([{
|
|
912
|
+
'detail': 'This field is required.',
|
|
913
|
+
'meta': {'detailLong': undefined, 'field': 'location', 'model': 'user', 'rule': 'required'},
|
|
914
|
+
'status': '400',
|
|
915
|
+
'title': 'location'
|
|
916
|
+
}])
|
|
917
|
+
await expect(user.validate({ location: {} })).resolves.toEqual({ location: {} })
|
|
918
|
+
|
|
919
|
+
db.close()
|
|
920
|
+
})
|
|
921
|
+
|
|
922
|
+
test('validate defaultObjects', async () => {
|
|
893
923
|
let db = (await opendb(null, {
|
|
894
924
|
timestamps: false,
|
|
895
925
|
defaultObjects: true,
|
|
@@ -915,7 +945,7 @@ module.exports = function(monastery, opendb) {
|
|
|
915
945
|
db.close()
|
|
916
946
|
})
|
|
917
947
|
|
|
918
|
-
test('
|
|
948
|
+
test('validate nullObjects', async () => {
|
|
919
949
|
let db = (await opendb(null, {
|
|
920
950
|
timestamps: false,
|
|
921
951
|
nullObjects: true,
|
package/docs/schema/index.md
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Schema
|
|
3
|
-
nav_order: 4
|
|
4
|
-
has_children: true
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Schema
|
|
8
|
-
|
|
9
|
-
Model schema object.
|
|
10
|
-
|
|
11
|
-
### Table of Contents
|
|
12
|
-
|
|
13
|
-
- [Fields](#fields)
|
|
14
|
-
- [Field blacklisting](#field-blacklisting)
|
|
15
|
-
- [Field options](#field-options)
|
|
16
|
-
- [MongoDB indexes](#mongodb-indexes)
|
|
17
|
-
- [Custom validation rules](#custom-validation-rules)
|
|
18
|
-
- [Custom error messages](#custom-error-messages)
|
|
19
|
-
- [Operation hooks](#operation-hooks)
|
|
20
|
-
- [Full example](#full-example)
|
|
21
|
-
|
|
22
|
-
### Fields
|
|
23
|
-
|
|
24
|
-
1. Fields may contain subdocuments, array of values, or an array of subdocuments
|
|
25
|
-
2. Field values need to have the `type` rule defined
|
|
26
|
-
3. Field values can contain [custom](#custom-validation-rules) and [default validation rules](./rules), e.g. `{ minLength: 2 }`
|
|
27
|
-
4. Field values can contain [field options](#field-options).
|
|
28
|
-
|
|
29
|
-
```js
|
|
30
|
-
schema.fields = {
|
|
31
|
-
name: { // value
|
|
32
|
-
type: 'string',
|
|
33
|
-
required: true
|
|
34
|
-
},
|
|
35
|
-
address: { // subdocument
|
|
36
|
-
line1: { type: 'string', required: true },
|
|
37
|
-
city: { type: 'string', minLength: 2 }
|
|
38
|
-
},
|
|
39
|
-
names: [ // array of values
|
|
40
|
-
{ type: 'string' }
|
|
41
|
-
],
|
|
42
|
-
pets: [{ // array of subdocuments
|
|
43
|
-
name: { type: 'string' },
|
|
44
|
-
type: { type: 'string' }
|
|
45
|
-
}]
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
These fields automatically get assigned and take presidence over any input data when [`manager.timestamps`](./manager) is true (default). You can override the `timestamps` value per operation, e.g. `db.user.update({ ..., timestamps: false})`. These fields use unix timestamps in seconds (by default), but can be configured to use use milliseconds via the manager [`useMilliseconds` ](./manager) option.
|
|
50
|
-
|
|
51
|
-
```js
|
|
52
|
-
schema.fields = {
|
|
53
|
-
createdAt: {
|
|
54
|
-
type: 'date',
|
|
55
|
-
insertOnly: true,
|
|
56
|
-
default: function() { return Math.floor(Date.now() / 1000) }
|
|
57
|
-
},
|
|
58
|
-
updatedAt: {
|
|
59
|
-
type: 'date',
|
|
60
|
-
default: function() { return Math.floor(Date.now() / 1000) }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Field blacklisting
|
|
66
|
-
|
|
67
|
-
You are able to provide a list of fields to blacklist per model operation.
|
|
68
|
-
|
|
69
|
-
```js
|
|
70
|
-
// The 'password' field will be removed from the results returned from `model.find`
|
|
71
|
-
schema.findBL = ['password']
|
|
72
|
-
|
|
73
|
-
// The 'password' field will be removed before inserting via `model.insert`
|
|
74
|
-
schema.insertBL = ['password']
|
|
75
|
-
|
|
76
|
-
// The 'password' and 'createdAt' fields will be removed before updating via `model.update`
|
|
77
|
-
schema.updateBL = ['createdAt', 'password']
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
You are also able to blacklist nested fields within subdocuments and arrays of subdocuments.
|
|
81
|
-
|
|
82
|
-
```js
|
|
83
|
-
// Subdocument example: `address.city` will be excluded from the response
|
|
84
|
-
schema.findBL = ['address.city']
|
|
85
|
-
|
|
86
|
-
// Array of subdocuments example: `meta` will be removed from each comment in the array
|
|
87
|
-
schema.findBL = ['comments.meta']
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Field options
|
|
91
|
-
|
|
92
|
-
Here are some other special field options that can be used alongside validation rules.
|
|
93
|
-
|
|
94
|
-
```js
|
|
95
|
-
let fieldName = {
|
|
96
|
-
|
|
97
|
-
// Enables population, you would save the foreign document _id on this field.
|
|
98
|
-
model: 'pet',
|
|
99
|
-
|
|
100
|
-
// Field will only be allowed to be set on insert when calling model.insert
|
|
101
|
-
insertOnly: true,
|
|
102
|
-
|
|
103
|
-
// Default will always override any passed value (it has some use-cases)
|
|
104
|
-
defaultOverride: true,
|
|
105
|
-
|
|
106
|
-
// Default value
|
|
107
|
-
default: 12,
|
|
108
|
-
|
|
109
|
-
// Default value can be returned from a function. `this` refers to the data object, but be
|
|
110
|
-
// sure to pass any referenced default fields along with insert/update/validate, e.g. `this.age`
|
|
111
|
-
default: function(fieldName, model) { return `I'm ${this.age} years old` },
|
|
112
|
-
|
|
113
|
-
// Monastery will automatically create a mongodb index for this field, see "MongoDB indexes"
|
|
114
|
-
// below for more information
|
|
115
|
-
index: true|1|-1|'text'|'unique'|Object,
|
|
116
|
-
|
|
117
|
-
// The field won't stored, handy for fields that get populated with documents, see ./find for more details
|
|
118
|
-
virtual: true
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### MongoDB indexes
|
|
123
|
-
|
|
124
|
-
You are able to automatically setup MongoDB indexes via the `index` field option.
|
|
125
|
-
|
|
126
|
-
```js
|
|
127
|
-
let fieldName = {
|
|
128
|
-
// This will create an ascending / descending index for this field
|
|
129
|
-
index: true|1|-1,
|
|
130
|
-
|
|
131
|
-
// This will create an ascending unique index which translates:
|
|
132
|
-
// { key: { [fieldName]: 1 }, unique: true }
|
|
133
|
-
index: 'unique',
|
|
134
|
-
|
|
135
|
-
// Text indexes are handled a little differently in which all the fields on the model
|
|
136
|
-
// schema that have a `index: 'text` set are collated into one index, e.g.
|
|
137
|
-
// { key: { [fieldName1]: 'text', [fieldName2]: 'text', .. }}
|
|
138
|
-
index: 'text'
|
|
139
|
-
|
|
140
|
-
// You can also pass an object if you need to use mongodb's index options
|
|
141
|
-
// https://docs.mongodb.com/manual/reference/command/createIndexes/
|
|
142
|
-
// https://mongodb.github.io/node-mongodb-native/2.1/api/Collection.html#createIndexes
|
|
143
|
-
index: { type: 1, ...(any mongodb index option) },
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
And here's how you would use a 2dsphere index, e.g.
|
|
148
|
-
|
|
149
|
-
```js
|
|
150
|
-
schema.fields = {
|
|
151
|
-
name: {
|
|
152
|
-
type: 'string',
|
|
153
|
-
required: true
|
|
154
|
-
},
|
|
155
|
-
location: {
|
|
156
|
-
index: '2dsphere',
|
|
157
|
-
type: { type: 'string', default: 'Point' },
|
|
158
|
-
coordinates: [{ type: 'number' }] // lng, lat
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Inserting a 2dsphere point
|
|
163
|
-
await db.user.insert({
|
|
164
|
-
data: {
|
|
165
|
-
location: { coordinates: [170.2628528648167, -43.59467883784971] }
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Since unique indexes by default don't allow mutliple documents with `null`, you use a partial index (less performant), e.g.
|
|
171
|
-
|
|
172
|
-
```js
|
|
173
|
-
|
|
174
|
-
schema.fields = {
|
|
175
|
-
index: {
|
|
176
|
-
name: {
|
|
177
|
-
type: 'string',
|
|
178
|
-
index: {
|
|
179
|
-
type: 'unique',
|
|
180
|
-
partialFilterExpression: {
|
|
181
|
-
email: { $type: 'string' }
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Custom validation rules
|
|
189
|
-
|
|
190
|
-
You are able to define custom validation rules to use. (`this` will refer to the data object passed in)
|
|
191
|
-
|
|
192
|
-
```js
|
|
193
|
-
schema.rules = {
|
|
194
|
-
// Basic definition
|
|
195
|
-
isGrandMaster: function(value, ruleArgument, path, model) {
|
|
196
|
-
return (value == 'Martin Luther')? true : false
|
|
197
|
-
},
|
|
198
|
-
// Full definition
|
|
199
|
-
isGrandMaster: {
|
|
200
|
-
message: (value, ruleArgument, path, model) => 'Only grand masters are permitted'
|
|
201
|
-
fn: function(value, ruleArgument, path, model) {
|
|
202
|
-
return (value == 'Martin Luther' || this.age > 100)? true : false
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// And referencing is the same as any other builtin rule
|
|
208
|
-
schema.fields = {
|
|
209
|
-
user: {
|
|
210
|
-
name: {
|
|
211
|
-
type: 'string'
|
|
212
|
-
isGrandMaster: true // true is the ruleArgument
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Additionally, you can define custom messages here
|
|
218
|
-
schema.messages = {
|
|
219
|
-
'user.name': {
|
|
220
|
-
isGrandMaster: 'Only grand masters are permitted'
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Custom error messages
|
|
226
|
-
|
|
227
|
-
You are able to define custom error messages for each validation rule.
|
|
228
|
-
|
|
229
|
-
```js
|
|
230
|
-
schema.messages = {
|
|
231
|
-
'name': {
|
|
232
|
-
required: 'Sorry, even a monk cannot be nameless'
|
|
233
|
-
type: 'Sorry, your name needs to be a string'
|
|
234
|
-
},
|
|
235
|
-
'address.city': {
|
|
236
|
-
minLength: (value, ruleArgument, path, model) => {
|
|
237
|
-
return `Is your city of residence really only ${ruleArgument} characters long?`
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
// You can assign custom error messages for all subdocument fields in an array
|
|
241
|
-
// e.g. pets = [{ name: { type: 'string' }}]
|
|
242
|
-
'pets.name': {
|
|
243
|
-
required: `Your pet's name needs to be a string.`
|
|
244
|
-
}
|
|
245
|
-
// To target a specific array item
|
|
246
|
-
'pets.0.name': {
|
|
247
|
-
required: `You first pet needs a name`
|
|
248
|
-
}
|
|
249
|
-
// You can also target any rules set on the array or sub arrays
|
|
250
|
-
// e.g.
|
|
251
|
-
// // let arrayWithSchema = (array, schema) => { array.schema = schema; return array }, OR you can use db.arrayWithSchema
|
|
252
|
-
// petGroups = db.arrayWithSchema(
|
|
253
|
-
// [db.arrayWithSchema(
|
|
254
|
-
// [{ name: { type: 'string' }}],
|
|
255
|
-
// { minLength: 1 }
|
|
256
|
-
// )],
|
|
257
|
-
// { minLength: 1 }
|
|
258
|
-
// )
|
|
259
|
-
'petGroups': {
|
|
260
|
-
minLength: `Please add at least one pet pet group.`
|
|
261
|
-
}
|
|
262
|
-
'petGroups.$': {
|
|
263
|
-
minLength: `Please add at least one pet into your pet group.`
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### Operation hooks
|
|
269
|
-
|
|
270
|
-
You are able provide an array of callbacks to these model operation hooks. If you need to throw an error asynchronously, please pass an error as the first argument to `next()`, e.g. `next(new Error('Your error here'))`. You can also access the operation details via `this` in each callback.
|
|
271
|
-
|
|
272
|
-
```js
|
|
273
|
-
schema.afterFind = [function(data, next) {}]
|
|
274
|
-
schema.afterInsert = [function(data, next) {}]
|
|
275
|
-
schema.afterInsertUpdate = [function(data, next) {}]
|
|
276
|
-
schema.afterUpdate = [function(data, next) {}]
|
|
277
|
-
schema.afterRemove = [function(next) {}]
|
|
278
|
-
schema.beforeInsert = [function(data, next) {}]
|
|
279
|
-
schema.beforeInsertUpdate = [function(data, next) {}]
|
|
280
|
-
schema.beforeUpdate = [function(data, next) {}]
|
|
281
|
-
schema.beforeRemove = [function(next) {}]
|
|
282
|
-
schema.beforeValidate = [function(data, next) {}]
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### Full example
|
|
286
|
-
|
|
287
|
-
```js
|
|
288
|
-
let schema = {
|
|
289
|
-
fields: {
|
|
290
|
-
email: { type: 'email', required: true, index: 'unique' },
|
|
291
|
-
firstName: { type: 'string', required: true },
|
|
292
|
-
lastName: { type: 'string' }
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
messages: {
|
|
296
|
-
email: { required: 'Please enter an email.' }
|
|
297
|
-
},
|
|
298
|
-
|
|
299
|
-
updateBL: ['email'],
|
|
300
|
-
|
|
301
|
-
beforeValidate: [function (data, next) {
|
|
302
|
-
if (data.firstName) data.firstName = util.ucFirst(data.firstName)
|
|
303
|
-
if (data.lastName) data.lastName = util.ucFirst(data.lastName)
|
|
304
|
-
next()
|
|
305
|
-
}],
|
|
306
|
-
|
|
307
|
-
afterFind: [function(data) {// Synchronous
|
|
308
|
-
data = data || {}
|
|
309
|
-
data.name = data.firstName + ' ' + data.lastName
|
|
310
|
-
}]
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
```
|