monastery 1.28.0 → 1.28.1
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/docs/readme.md +6 -0
- package/docs/schema.md +4 -4
- package/lib/model-validate.js +2 -1
- package/lib/model.js +17 -14
- package/lib/rules.js +17 -12
- package/package.json +1 -1
- package/test/model.js +62 -12
package/docs/readme.md
CHANGED
package/docs/schema.md
CHANGED
|
@@ -173,13 +173,13 @@ You are able to define custom validation rules to use. (`this` will refer to the
|
|
|
173
173
|
```js
|
|
174
174
|
schema.rules = {
|
|
175
175
|
// Basic definition
|
|
176
|
-
isGrandMaster: function(value, ruleArgument,
|
|
176
|
+
isGrandMaster: function(value, ruleArgument, path, model) {
|
|
177
177
|
return (value == 'Martin Luther')? true : false
|
|
178
178
|
},
|
|
179
179
|
// Full definition
|
|
180
180
|
isGrandMaster: {
|
|
181
|
-
message: (value, ruleArgument,
|
|
182
|
-
fn: function(value, ruleArgument,
|
|
181
|
+
message: (value, ruleArgument, path, model) => 'Only grand masters are permitted'
|
|
182
|
+
fn: function(value, ruleArgument, path, model) {
|
|
183
183
|
return (value == 'Martin Luther' || this.age > 100)? true : false
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -214,7 +214,7 @@ schema.messages = {
|
|
|
214
214
|
type: 'Sorry, your name needs to be a string'
|
|
215
215
|
},
|
|
216
216
|
'address.city': {
|
|
217
|
-
minLength: (value, ruleArgument,
|
|
217
|
+
minLength: (value, ruleArgument, path, model) => {
|
|
218
218
|
return `Is your city of residence really only ${ruleArgument} characters long?`
|
|
219
219
|
}
|
|
220
220
|
},
|
package/lib/model-validate.js
CHANGED
|
@@ -259,9 +259,10 @@ module.exports = {
|
|
|
259
259
|
let ruleMessage = ruleMessageKey && this.messages[ruleMessageKey][ruleName]
|
|
260
260
|
if (!ruleMessage) ruleMessage = rule.message
|
|
261
261
|
|
|
262
|
+
|
|
262
263
|
if (ruleName !== 'required') {
|
|
263
264
|
// Ignore undefined when not testing 'required'
|
|
264
|
-
if (typeof value === 'undefined') return
|
|
265
|
+
if (typeof value === 'undefined') return ////////////////////////////////////////
|
|
265
266
|
|
|
266
267
|
// Ignore null if not testing required
|
|
267
268
|
if (value === null && !field.isObject && !field.isArray) return
|
package/lib/model.js
CHANGED
|
@@ -6,10 +6,11 @@ let validate = require('./model-validate')
|
|
|
6
6
|
let Model = module.exports = function(name, opts, manager) {
|
|
7
7
|
/**
|
|
8
8
|
* Setup a model (aka monk collection)
|
|
9
|
-
* Todo: convert into a promise
|
|
10
9
|
* @param {string} name
|
|
11
10
|
* @param {object} opts - see mongodb colleciton documentation
|
|
12
|
-
* @
|
|
11
|
+
* @param {boolean} opts.waitForIndexes
|
|
12
|
+
* @this model
|
|
13
|
+
* @return Promise(model) | this
|
|
13
14
|
*/
|
|
14
15
|
if (!(this instanceof Model)) {
|
|
15
16
|
return new Model(name, opts, this)
|
|
@@ -25,10 +26,6 @@ let Model = module.exports = function(name, opts, manager) {
|
|
|
25
26
|
opts = opts || {}
|
|
26
27
|
Object.assign(this, {
|
|
27
28
|
...(opts.methods || {}),
|
|
28
|
-
name: name,
|
|
29
|
-
manager: manager,
|
|
30
|
-
error: manager.error,
|
|
31
|
-
info: manager.info,
|
|
32
29
|
afterFind: opts.afterFind || [],
|
|
33
30
|
afterInsert: (opts.afterInsert || []).concat(opts.afterInsertUpdate || []),
|
|
34
31
|
afterUpdate: (opts.afterUpdate || []).concat(opts.afterInsertUpdate || []),
|
|
@@ -37,12 +34,16 @@ let Model = module.exports = function(name, opts, manager) {
|
|
|
37
34
|
beforeUpdate: (opts.beforeUpdate || []).concat(opts.beforeInsertUpdate || []),
|
|
38
35
|
beforeRemove: opts.beforeRemove || [],
|
|
39
36
|
beforeValidate: opts.beforeValidate || [],
|
|
40
|
-
|
|
37
|
+
error: manager.error,
|
|
38
|
+
info: manager.info,
|
|
41
39
|
insertBL: opts.insertBL || [],
|
|
42
|
-
updateBL: opts.updateBL || [],
|
|
43
|
-
messages: opts.messages || {},
|
|
44
40
|
fields: { ...(util.deepCopy(opts.fields) || {}) },
|
|
45
|
-
|
|
41
|
+
findBL: opts.findBL || ['password'],
|
|
42
|
+
manager: manager,
|
|
43
|
+
messages: opts.messages || {},
|
|
44
|
+
name: name,
|
|
45
|
+
rules: { ...(opts.rules || {}) },
|
|
46
|
+
updateBL: opts.updateBL || [],
|
|
46
47
|
})
|
|
47
48
|
|
|
48
49
|
// Run before model hooks
|
|
@@ -94,13 +95,13 @@ let Model = module.exports = function(name, opts, manager) {
|
|
|
94
95
|
// Add model to manager.model
|
|
95
96
|
this.manager.model[name] = this
|
|
96
97
|
|
|
97
|
-
// Ensure field indexes exist in
|
|
98
|
+
// Setup/Ensure field indexes exist in MongoDB
|
|
98
99
|
let errHandler = err => {
|
|
99
100
|
if (err.type == 'info') this.info(err.detail)
|
|
100
101
|
else this.error(err)
|
|
101
102
|
}
|
|
102
|
-
if (opts.
|
|
103
|
-
else this._setupIndexes().catch(errHandler)
|
|
103
|
+
if (opts.waitForIndexes) return this._setupIndexes().catch(errHandler).then(() => this)
|
|
104
|
+
else this._setupIndexes().catch(errHandler) // returns this
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
Model.prototype._getFieldlist = function(fields, path) {
|
|
@@ -243,7 +244,7 @@ Model.prototype._setupIndexes = function(fields) {
|
|
|
243
244
|
* @link https://docs.mongodb.com/manual/reference/command/createIndexes/
|
|
244
245
|
* @link https://mongodb.github.io/node-mongodb-native/2.1/api/Collection.html#createIndexes
|
|
245
246
|
* @param {object} <fields>
|
|
246
|
-
* @return Promise( {array} indexes | {string} error )
|
|
247
|
+
* @return Promise( {array} indexes ensured | {string} error )
|
|
247
248
|
*
|
|
248
249
|
* MongoDB index structures = [
|
|
249
250
|
* true = { name: 'name_1', key: { name: 1 } },
|
|
@@ -267,6 +268,7 @@ Model.prototype._setupIndexes = function(fields) {
|
|
|
267
268
|
|
|
268
269
|
// Find all indexes
|
|
269
270
|
recurseFields(fields || model.fields, '')
|
|
271
|
+
// console.log(2, indexes, fields)
|
|
270
272
|
if (hasTextIndex) indexes.push(textIndex)
|
|
271
273
|
if (!indexes.length) return Promise.resolve([]) // No indexes defined
|
|
272
274
|
|
|
@@ -313,6 +315,7 @@ Model.prototype._setupIndexes = function(fields) {
|
|
|
313
315
|
})
|
|
314
316
|
.then(response => {
|
|
315
317
|
model.info('db index(s) created for ' + model.name)
|
|
318
|
+
return indexes
|
|
316
319
|
})
|
|
317
320
|
|
|
318
321
|
function recurseFields(fields, parentPath) {
|
package/lib/rules.js
CHANGED
|
@@ -5,6 +5,7 @@ let validator = require('validator')
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
|
|
7
7
|
required: {
|
|
8
|
+
runOnUndefined: true, // integrate
|
|
8
9
|
message: 'This field is required.',
|
|
9
10
|
fn: function(x) {
|
|
10
11
|
if (util.isArray(x) && !x.length) return false
|
|
@@ -12,7 +13,7 @@ module.exports = {
|
|
|
12
13
|
}
|
|
13
14
|
},
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
+
// Type rules below ignore undefined
|
|
16
17
|
|
|
17
18
|
'isBoolean': {
|
|
18
19
|
message: 'Value was not a boolean.',
|
|
@@ -25,12 +26,6 @@ module.exports = {
|
|
|
25
26
|
return typeof x === 'boolean'
|
|
26
27
|
}
|
|
27
28
|
},
|
|
28
|
-
'isNotEmptyString': {
|
|
29
|
-
message: 'Value was an empty string.',
|
|
30
|
-
fn: function(x) {
|
|
31
|
-
return x !== ''
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
29
|
'isArray': {
|
|
35
30
|
message: 'Value was not an array.',
|
|
36
31
|
tryParse: function(x) {
|
|
@@ -132,6 +127,8 @@ module.exports = {
|
|
|
132
127
|
return util.isObject(x) && ObjectId.isValid(x)/*x.get_inc*/? true : false
|
|
133
128
|
}
|
|
134
129
|
},
|
|
130
|
+
|
|
131
|
+
|
|
135
132
|
'max': {
|
|
136
133
|
message: (x, arg) => 'Value was greater than the configured maximum (' + arg + ')',
|
|
137
134
|
fn: function(x, arg) {
|
|
@@ -146,8 +143,16 @@ module.exports = {
|
|
|
146
143
|
return x >= arg
|
|
147
144
|
}
|
|
148
145
|
},
|
|
146
|
+
'isNotEmptyString': {
|
|
147
|
+
message: 'Value was an empty string.',
|
|
148
|
+
fn: function(x) {
|
|
149
|
+
return x !== ''
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Rules below ignore undefined & empty strings
|
|
154
|
+
// (e.g. an empty email field can be saved that isn't required)
|
|
149
155
|
|
|
150
|
-
// Rules below ignore null & empty strings
|
|
151
156
|
'enum': {
|
|
152
157
|
ignoreEmptyString: true,
|
|
153
158
|
message: (x, arg) => 'Invalid enum value',
|
|
@@ -157,10 +162,10 @@ module.exports = {
|
|
|
157
162
|
}
|
|
158
163
|
}
|
|
159
164
|
},
|
|
160
|
-
'hasAgreed': {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
},
|
|
165
|
+
// 'hasAgreed': {
|
|
166
|
+
// message: (x, arg) => 'Please agree to the terms and conditions.',
|
|
167
|
+
// fn: function(x, arg) { return !x }
|
|
168
|
+
// },
|
|
164
169
|
'isAfter': {
|
|
165
170
|
ignoreEmptyString: true,
|
|
166
171
|
message: (x, arg) => 'Value was before the configured time (' + arg + ')',
|
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.28.
|
|
5
|
+
"version": "1.28.1",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
package/test/model.js
CHANGED
|
@@ -116,27 +116,77 @@ module.exports = function(monastery, opendb) {
|
|
|
116
116
|
})
|
|
117
117
|
|
|
118
118
|
test('Model indexes', async (done) => {
|
|
119
|
-
// Setup
|
|
120
119
|
// Need to test different types of indexes
|
|
121
120
|
let db = (await opendb(null)).db
|
|
122
|
-
let user = db.model('user', {})
|
|
123
|
-
let user2 = db.model('user2', {})
|
|
124
121
|
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
// Drop previously tested collections
|
|
123
|
+
if ((await db._db.listCollections().toArray()).find(o => o.name == 'userIndexRaw')) {
|
|
124
|
+
await db._db.collection('userIndexRaw').drop()
|
|
125
|
+
}
|
|
126
|
+
if ((await db._db.listCollections().toArray()).find(o => o.name == 'userIndex')) {
|
|
127
|
+
await db._db.collection('userIndex').drop()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Unique & text index (after model initialisation, in serial)
|
|
131
|
+
let userIndexRawModel = db.model('userIndexRaw', {})
|
|
132
|
+
let setupIndex1 = await userIndexRawModel._setupIndexes({
|
|
133
|
+
email: { type: 'string', index: 'unique' },
|
|
134
|
+
})
|
|
135
|
+
let setupIndex2 = await userIndexRawModel._setupIndexes({
|
|
136
|
+
name: { type: 'string', index: 'text' },
|
|
137
|
+
})
|
|
138
|
+
await expect(db._db.collection('userIndexRaw').indexes()).resolves.toEqual([
|
|
139
|
+
{ v: 2, key: { _id: 1 }, name: '_id_' },
|
|
140
|
+
{ v: 2, unique: true, key: { email: 1 }, name: 'email_1' },
|
|
141
|
+
{
|
|
142
|
+
v: 2,
|
|
143
|
+
key: { _fts: 'text', _ftsx: 1 },
|
|
144
|
+
name: 'text',
|
|
145
|
+
weights: { name: 1 },
|
|
146
|
+
default_language: 'english',
|
|
147
|
+
language_override: 'language',
|
|
148
|
+
textIndexVersion: 3
|
|
149
|
+
}
|
|
150
|
+
])
|
|
151
|
+
|
|
152
|
+
// Unique & text index
|
|
153
|
+
let userIndexModel = await db.model('userIndex', {
|
|
154
|
+
waitForIndexes: true,
|
|
155
|
+
fields: {
|
|
156
|
+
email: { type: 'string', index: 'unique' },
|
|
157
|
+
name: { type: 'string', index: 'text' },
|
|
158
|
+
}
|
|
128
159
|
})
|
|
160
|
+
await expect(db._db.collection('userIndex').indexes()).resolves.toEqual([
|
|
161
|
+
{ v: 2, key: { _id: 1 }, name: '_id_' },
|
|
162
|
+
{ v: 2, unique: true, key: { email: 1 }, name: 'email_1' },
|
|
163
|
+
{
|
|
164
|
+
v: 2,
|
|
165
|
+
key: { _fts: 'text', _ftsx: 1 },
|
|
166
|
+
name: 'text',
|
|
167
|
+
weights: { name: 1 },
|
|
168
|
+
default_language: 'english',
|
|
169
|
+
language_override: 'language',
|
|
170
|
+
textIndexVersion: 3
|
|
171
|
+
}
|
|
172
|
+
])
|
|
129
173
|
|
|
130
174
|
// No text index change error, i.e. new Error("Index with name: text already exists with different options")
|
|
131
|
-
await expect(
|
|
175
|
+
await expect(userIndexModel._setupIndexes({
|
|
132
176
|
name: { type: 'string', index: 'text' },
|
|
133
177
|
name2: { type: 'string', index: 'text' }
|
|
134
|
-
})).resolves.toEqual(
|
|
178
|
+
})).resolves.toEqual([{
|
|
179
|
+
"key": { "name": "text", "name2": "text" },
|
|
180
|
+
"name": "text",
|
|
181
|
+
}])
|
|
135
182
|
|
|
136
183
|
// Text index on a different model
|
|
137
|
-
await expect(
|
|
138
|
-
|
|
139
|
-
})).resolves.toEqual(
|
|
184
|
+
await expect(userIndexRawModel._setupIndexes({
|
|
185
|
+
name2: { type: 'string', index: 'text' }
|
|
186
|
+
})).resolves.toEqual([{
|
|
187
|
+
"key": {"name2": "text"},
|
|
188
|
+
"name": "text",
|
|
189
|
+
}])
|
|
140
190
|
|
|
141
191
|
db.close()
|
|
142
192
|
done()
|
|
@@ -147,7 +197,7 @@ module.exports = function(monastery, opendb) {
|
|
|
147
197
|
// with text indexes are setup at the same time
|
|
148
198
|
let db = (await opendb(null)).db
|
|
149
199
|
await db.model('user3', {
|
|
150
|
-
|
|
200
|
+
waitForIndexes: true,
|
|
151
201
|
fields: {
|
|
152
202
|
location: {
|
|
153
203
|
index: '2dsphere',
|