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 CHANGED
@@ -71,6 +71,12 @@ $ DEBUG=monastery:info
71
71
  $ DEBUG=monastery:*
72
72
  ```
73
73
 
74
+ To run isolated tests with Jest:
75
+
76
+ ```bash
77
+ npm run dev -- -t 'Model indexes'
78
+ ```
79
+
74
80
  ## Contributing
75
81
 
76
82
  Coming soon...
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, fieldName, model) {
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, fieldName, model) => 'Only grand masters are permitted'
182
- fn: function(value, ruleArgument, fieldName, model) {
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, fieldName, model) => {
217
+ minLength: (value, ruleArgument, path, model) => {
218
218
  return `Is your city of residence really only ${ruleArgument} characters long?`
219
219
  }
220
220
  },
@@ -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
- * @this Model
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
- findBL: opts.findBL || ['password'],
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
- rules: { ...(opts.rules || {}) }
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 mongodb
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.promise) return this._setupIndexes().catch(errHandler)
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
- // Rules below ignore null
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
- message: (x, arg) => 'Please agree to the terms and conditions.',
162
- fn: function(x, arg) { return !x }
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.0",
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
- // Text index setup
126
- let setupIndex1 = await user._setupIndexes({
127
- name: { type: 'string', index: 'text' }
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(user._setupIndexes({
175
+ await expect(userIndexModel._setupIndexes({
132
176
  name: { type: 'string', index: 'text' },
133
177
  name2: { type: 'string', index: 'text' }
134
- })).resolves.toEqual(undefined)
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(user2._setupIndexes({
138
- name: { type: 'string', index: 'text' }
139
- })).resolves.toEqual(undefined)
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
- promise: true,
200
+ waitForIndexes: true,
151
201
  fields: {
152
202
  location: {
153
203
  index: '2dsphere',