monastery 3.0.23 → 3.1.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 +2 -0
- package/lib/model-validate.js +11 -1
- package/lib/model.js +41 -16
- package/package.json +1 -1
- package/test/model.js +59 -55
- package/test/validate.js +81 -0
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.1.0](https://github.com/boycce/monastery/compare/3.0.23...3.1.0) (2024-05-27)
|
|
6
|
+
|
|
5
7
|
### [3.0.23](https://github.com/boycce/monastery/compare/3.0.22...3.0.23) (2024-05-25)
|
|
6
8
|
|
|
7
9
|
### [3.0.22](https://github.com/boycce/monastery/compare/3.0.21...3.0.22) (2024-05-08)
|
package/lib/model-validate.js
CHANGED
|
@@ -91,6 +91,7 @@ Model.prototype._validateFields = function (dataRoot, fields, data, opts, parent
|
|
|
91
91
|
let timestamps = util.isDefined(opts.timestamps) ? opts.timestamps : this.manager.opts.timestamps
|
|
92
92
|
let dataArray = util.forceArray(data)
|
|
93
93
|
let data2 = fieldsIsArray ? [] : {}
|
|
94
|
+
let notStrict = fields.schema.strict === false
|
|
94
95
|
|
|
95
96
|
for (let i=0, l=dataArray.length; i<l; i++) {
|
|
96
97
|
const item = dataArray[i]
|
|
@@ -172,6 +173,15 @@ Model.prototype._validateFields = function (dataRoot, fields, data, opts, parent
|
|
|
172
173
|
}
|
|
173
174
|
// if (!parentPath && fieldName == 'categories') console.timeEnd(fieldName)
|
|
174
175
|
}
|
|
176
|
+
|
|
177
|
+
// Add any extra fields that are not in the schema. Item maybe false when inserting (from recursing above)
|
|
178
|
+
if (notStrict && !fieldsIsArray && item) {
|
|
179
|
+
const allDataKeys = Object.keys(item)
|
|
180
|
+
for (let m=0, n=allDataKeys.length; m<n; m++) {
|
|
181
|
+
const key = allDataKeys[m]
|
|
182
|
+
if (!fieldsArray.includes(key)) data2[key] = item[key]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
// Normalise array indexes and return
|
|
@@ -266,5 +276,5 @@ Model.prototype._ignoredRules = [
|
|
|
266
276
|
// todo: need to remove filesize and formats..
|
|
267
277
|
'awsAcl', 'awsBucket', 'default', 'defaultOverride', 'filename', 'filesize', 'fileSize', 'formats',
|
|
268
278
|
'image', 'index', 'insertOnly', 'model', 'nullObject', 'params', 'path', 'getSignedUrl', 'timestampField',
|
|
269
|
-
'type', 'isType', 'isSchema', 'virtual',
|
|
279
|
+
'type', 'isType', 'isSchema', 'virtual', 'strict',
|
|
270
280
|
]
|
package/lib/model.js
CHANGED
|
@@ -21,6 +21,11 @@ function Model(name, opts, manager) {
|
|
|
21
21
|
} else if (!opts.fields) {
|
|
22
22
|
throw `We couldn't find ${name}.fields in the model definition, the model maybe setup `
|
|
23
23
|
+ `or exported incorrectly:\n${JSON.stringify(opts, null, 2)}`
|
|
24
|
+
} else if (!util.isSubdocument(opts.fields) && opts.fields.type == 'any') {
|
|
25
|
+
throw `Instead of using { type: 'any' } for ${name}.fields, please use the new 'strict' definition rule` +
|
|
26
|
+
', e.g. { schema: { strict: false }}'
|
|
27
|
+
} else if (!util.isSubdocument(opts.fields) && !util.isEmpty(opts.fields)) {
|
|
28
|
+
throw `The ${name}.fields object should be a valid document, e.g. { name: { type: 'string' }}`
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
// Add schema options
|
|
@@ -107,7 +112,9 @@ function Model(name, opts, manager) {
|
|
|
107
112
|
}, this)
|
|
108
113
|
|
|
109
114
|
// Extend default fields with passed in fields and check for invalid fields
|
|
110
|
-
this._setupFields(
|
|
115
|
+
this._setupFields(
|
|
116
|
+
this.fields = util.isSchema(this.fields) ? this.fields : Object.assign({}, this._defaultFields, this.fields)
|
|
117
|
+
)
|
|
111
118
|
this.fieldsFlattened = this._getFieldsFlattened(this.fields, '') // test output?
|
|
112
119
|
this.modelFieldsArray = this._getModelFieldsArray()
|
|
113
120
|
|
|
@@ -173,11 +180,14 @@ Model.prototype._getModelFieldsArray = function() {
|
|
|
173
180
|
}, [])
|
|
174
181
|
},
|
|
175
182
|
|
|
176
|
-
Model.prototype._setupFields = function(fields) {
|
|
183
|
+
Model.prototype._setupFields = function(fields, isSub) {
|
|
177
184
|
/**
|
|
178
185
|
* Check for invalid rules on a field object, and set field.isType
|
|
179
186
|
* @param {object|array} fields - subsdocument or array
|
|
180
187
|
*/
|
|
188
|
+
// We need to allow the processing of the root schema object
|
|
189
|
+
if (!isSub) fields = { fields }
|
|
190
|
+
|
|
181
191
|
util.forEach(fields, function(field, fieldName) {
|
|
182
192
|
// Schema field
|
|
183
193
|
if (fieldName == 'schema') return
|
|
@@ -214,17 +224,8 @@ Model.prototype._setupFields = function(fields) {
|
|
|
214
224
|
isSchema: true,
|
|
215
225
|
}
|
|
216
226
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
if ((this.rules[ruleName] || rules[ruleName]) && this._ignoredRules.indexOf(ruleName) != -1) {
|
|
220
|
-
this.error(`The rule name "${ruleName}" for the model "${this.name}" is a reserved keyword, ignoring rule.`)
|
|
221
|
-
}
|
|
222
|
-
if (!this.rules[ruleName] && !rules[ruleName] && this._ignoredRules.indexOf(ruleName) == -1) {
|
|
223
|
-
// console.log(field.schema)
|
|
224
|
-
this.error(`No rule "${ruleName}" exists for model "${this.name}", ignoring rule.`)
|
|
225
|
-
delete field.schema[ruleName]
|
|
226
|
-
}
|
|
227
|
-
}
|
|
227
|
+
// Remove invalid rules
|
|
228
|
+
this._removeInvalidRules(field)
|
|
228
229
|
|
|
229
230
|
// Misused schema property
|
|
230
231
|
} else if (fieldName == 'schema' || fieldName == 'isSchema') {
|
|
@@ -233,6 +234,7 @@ Model.prototype._setupFields = function(fields) {
|
|
|
233
234
|
|
|
234
235
|
// Fields be an array
|
|
235
236
|
} else if (util.isArray(field)) {
|
|
237
|
+
this._removeInvalidRules(field)
|
|
236
238
|
field.schema = util.removeUndefined({
|
|
237
239
|
type: 'array',
|
|
238
240
|
isArray: true,
|
|
@@ -243,7 +245,7 @@ Model.prototype._setupFields = function(fields) {
|
|
|
243
245
|
virtual: field.length == 1 && (field[0] || {}).virtual ? true : undefined,
|
|
244
246
|
...(field.schema || {}),
|
|
245
247
|
})
|
|
246
|
-
this._setupFields(field)
|
|
248
|
+
this._setupFields(field, true)
|
|
247
249
|
|
|
248
250
|
// Fields can be a subdocument, e.g. user.pet = { name: {}, ..}
|
|
249
251
|
} else if (util.isSubdocument(field)) {
|
|
@@ -253,6 +255,7 @@ Model.prototype._setupFields = function(fields) {
|
|
|
253
255
|
field.schema.index = index2dsphere
|
|
254
256
|
delete field.index
|
|
255
257
|
}
|
|
258
|
+
this._removeInvalidRules(field)
|
|
256
259
|
field.schema = util.removeUndefined({
|
|
257
260
|
type: 'object',
|
|
258
261
|
isObject: true,
|
|
@@ -262,11 +265,33 @@ Model.prototype._setupFields = function(fields) {
|
|
|
262
265
|
nullObject: this.manager.opts.nullObjects,
|
|
263
266
|
...field.schema,
|
|
264
267
|
})
|
|
265
|
-
this._setupFields(field)
|
|
268
|
+
this._setupFields(field, true)
|
|
266
269
|
}
|
|
267
270
|
}, this)
|
|
268
271
|
},
|
|
269
272
|
|
|
273
|
+
Model.prototype._removeInvalidRules = function(field) {
|
|
274
|
+
/**
|
|
275
|
+
* Remove invalid rules on a field object
|
|
276
|
+
* @param {object} field
|
|
277
|
+
* @return {object} field
|
|
278
|
+
**/
|
|
279
|
+
for (let ruleName in (field||{}).schema) {
|
|
280
|
+
const ruleFn = this.rules[ruleName] || rules[ruleName]
|
|
281
|
+
// Rule doesn't exist
|
|
282
|
+
if (!ruleFn && this._ignoredRules.indexOf(ruleName) == -1) {
|
|
283
|
+
// console.log(field.schema)
|
|
284
|
+
this.error(`No rule "${ruleName}" exists for model "${this.name}", ignoring rule.`)
|
|
285
|
+
delete field.schema[ruleName]
|
|
286
|
+
}
|
|
287
|
+
// Reserved rule
|
|
288
|
+
if (this.rules[ruleName] && this._ignoredRules.indexOf(ruleName) != -1) {
|
|
289
|
+
this.error(`The rule "${ruleName}" for the model "${this.name}" is a reserved keyword, ignoring custom rule function.`)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return field
|
|
293
|
+
},
|
|
294
|
+
|
|
270
295
|
Model.prototype._setupIndexes = async function(fields, opts={}) {
|
|
271
296
|
/**
|
|
272
297
|
* Creates indexes for the model (multikey, and sub-document supported)
|
|
@@ -294,7 +319,7 @@ Model.prototype._setupIndexes = async function(fields, opts={}) {
|
|
|
294
319
|
}
|
|
295
320
|
|
|
296
321
|
// Process custom 'unprocessed' fields
|
|
297
|
-
if (fields && !fields[Object.keys(fields)[0]].schema) {
|
|
322
|
+
if (fields && !(fields[Object.keys(fields)[0]].schema||{}).isSchema) {
|
|
298
323
|
fields = util.deepCopy(fields)
|
|
299
324
|
this._setupFields(fields)
|
|
300
325
|
// console.dir(fields, { depth: null })
|
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.0
|
|
5
|
+
"version": "3.1.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": "github:boycce/monastery",
|
|
8
8
|
"homepage": "https://boycce.github.io/monastery/",
|
package/test/model.js
CHANGED
|
@@ -21,18 +21,8 @@ test('model > model on manager', async () => {
|
|
|
21
21
|
db2.close()
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
test('model setup
|
|
25
|
-
//
|
|
26
|
-
let user = db.model('user', { fields: {
|
|
27
|
-
name: { type: 'string' },
|
|
28
|
-
pets: [{ type: 'string' }],
|
|
29
|
-
colors: { red: { type: 'string' } },
|
|
30
|
-
points: [[{ type: 'number' }]],
|
|
31
|
-
points2: [[{ x: { type: 'number' } }]],
|
|
32
|
-
logo: { type: 'image' },
|
|
33
|
-
}})
|
|
34
|
-
|
|
35
|
-
// no fields defined
|
|
24
|
+
test('model setup with default fields', async () => {
|
|
25
|
+
// Default fields
|
|
36
26
|
expect(db.model('user2', { fields: {} }).fields).toEqual({
|
|
37
27
|
_id: {
|
|
38
28
|
schema: {
|
|
@@ -64,12 +54,39 @@ test('model setup basics', async () => {
|
|
|
64
54
|
type: 'integer',
|
|
65
55
|
},
|
|
66
56
|
},
|
|
57
|
+
schema: {
|
|
58
|
+
isObject: true,
|
|
59
|
+
isSchema: true,
|
|
60
|
+
isType: 'isObject',
|
|
61
|
+
type: 'object',
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('model setup basics', async () => {
|
|
67
|
+
// Setup
|
|
68
|
+
let user = db.model('user', {
|
|
69
|
+
fields: {
|
|
70
|
+
name: { type: 'string' },
|
|
71
|
+
pets: [{ type: 'string' }],
|
|
72
|
+
colors: { red: { type: 'string' } },
|
|
73
|
+
points: [[{ type: 'number' }]],
|
|
74
|
+
points2: [[{ x: { type: 'number' } }]],
|
|
75
|
+
logo: { type: 'image' },
|
|
76
|
+
},
|
|
67
77
|
})
|
|
68
78
|
|
|
69
79
|
// Has model name
|
|
70
80
|
expect(user.name)
|
|
71
81
|
.toEqual('user')
|
|
72
82
|
|
|
83
|
+
// Expect to throw an error
|
|
84
|
+
expect(() => db.model('user', { fields: { type: 'any' } }))
|
|
85
|
+
.toThrow(
|
|
86
|
+
'Instead of using { type: \'any\' } for user.fields, please use the new \'strict\' definition rule, '
|
|
87
|
+
+ 'e.g. { schema: { strict: false }}'
|
|
88
|
+
)
|
|
89
|
+
|
|
73
90
|
// Basic field
|
|
74
91
|
expect(user.fields.name.schema)
|
|
75
92
|
.toEqual({ type: 'string', isString: true, isSchema: true, isType: 'isString' })
|
|
@@ -114,42 +131,6 @@ test('model setup basics', async () => {
|
|
|
114
131
|
))
|
|
115
132
|
})
|
|
116
133
|
|
|
117
|
-
test('model setup with default fields', async () => {
|
|
118
|
-
// Default fields
|
|
119
|
-
expect(db.model('user2', { fields: {} }).fields).toEqual({
|
|
120
|
-
_id: {
|
|
121
|
-
schema: {
|
|
122
|
-
insertOnly: true,
|
|
123
|
-
isId: true,
|
|
124
|
-
isSchema: true,
|
|
125
|
-
isType: 'isId',
|
|
126
|
-
type: 'id',
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
createdAt: {
|
|
130
|
-
schema: {
|
|
131
|
-
default: expect.any(Function),
|
|
132
|
-
insertOnly: true,
|
|
133
|
-
isInteger: true,
|
|
134
|
-
isSchema: true,
|
|
135
|
-
isType: 'isInteger',
|
|
136
|
-
timestampField: true,
|
|
137
|
-
type: 'integer',
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
updatedAt: {
|
|
141
|
-
schema: {
|
|
142
|
-
default: expect.any(Function),
|
|
143
|
-
isInteger: true,
|
|
144
|
-
isSchema: true,
|
|
145
|
-
isType: 'isInteger',
|
|
146
|
-
timestampField: true,
|
|
147
|
-
type: 'integer',
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
134
|
test('model setup with default objects', async () => {
|
|
154
135
|
const db2 = monastery('127.0.0.1/monastery', { defaultObjects: true })
|
|
155
136
|
let user = db2.model('user', { fields: {
|
|
@@ -248,6 +229,24 @@ test('model setup with schema', async () => {
|
|
|
248
229
|
})
|
|
249
230
|
})
|
|
250
231
|
|
|
232
|
+
test('model setup with schema on root', async () => {
|
|
233
|
+
// Expect to throw an error
|
|
234
|
+
expect(() => db.model('user', { fields: { name: 'string' } }))
|
|
235
|
+
.toThrow('The user.fields object should be a valid document, e.g. { name: { type: \'string\' }}')
|
|
236
|
+
|
|
237
|
+
// root has schema
|
|
238
|
+
expect(db.model('user', { fields: { name: { type: 'string' } } }).fields.schema)
|
|
239
|
+
.toEqual({ type: 'object', isObject: true, isSchema: true, isType: 'isObject' })
|
|
240
|
+
|
|
241
|
+
// root has custom schema
|
|
242
|
+
expect(db.model('user', { fields: { schema: { nullObject: true } } }).fields.schema)
|
|
243
|
+
.toEqual({ type: 'object', isObject: true, isSchema: true, isType: 'isObject', nullObject: true })
|
|
244
|
+
|
|
245
|
+
// strict mode off
|
|
246
|
+
expect(db.model('user', { fields: { schema: { strict: false } } }).fields.schema)
|
|
247
|
+
.toEqual({ type: 'object', isObject: true, isSchema: true, isType: 'isObject', strict: false })
|
|
248
|
+
})
|
|
249
|
+
|
|
251
250
|
test('model setup with messages', async () => {
|
|
252
251
|
let user = db.model('user', {
|
|
253
252
|
fields: {
|
|
@@ -375,15 +374,15 @@ test('model setup with messages', async () => {
|
|
|
375
374
|
})
|
|
376
375
|
})
|
|
377
376
|
|
|
378
|
-
test('model setup reserved rules', async () => {
|
|
377
|
+
test('model setup with reserved and invalid rules', async () => {
|
|
379
378
|
// Setup
|
|
380
379
|
const db2 = monastery('127.0.0.1/monastery', { logLevel: 0 })
|
|
381
380
|
let user = db2.model('user-model', {
|
|
382
381
|
fields: {
|
|
383
382
|
name: {
|
|
384
383
|
type: 'string',
|
|
385
|
-
params: {}, // reserved keyword (image plugin)
|
|
386
|
-
|
|
384
|
+
params: {}, // reserved keyword (image plugin)
|
|
385
|
+
invalidRule: {}, // no rule function found
|
|
387
386
|
},
|
|
388
387
|
},
|
|
389
388
|
rules: {
|
|
@@ -392,10 +391,15 @@ test('model setup reserved rules', async () => {
|
|
|
392
391
|
},
|
|
393
392
|
},
|
|
394
393
|
})
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
394
|
+
expect(user.fields.name).toEqual({
|
|
395
|
+
schema: {
|
|
396
|
+
type: 'string',
|
|
397
|
+
isString: true,
|
|
398
|
+
isSchema: true,
|
|
399
|
+
isType: 'isString',
|
|
400
|
+
params: {}, // still included
|
|
401
|
+
// invalidRule: {}, should be removed
|
|
402
|
+
},
|
|
399
403
|
})
|
|
400
404
|
db2.close()
|
|
401
405
|
})
|
package/test/validate.js
CHANGED
|
@@ -154,6 +154,87 @@ test('validation basic errors', async () => {
|
|
|
154
154
|
})
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
+
test('validation type any', async () => {
|
|
158
|
+
let user1 = db.model('user', {
|
|
159
|
+
fields: {
|
|
160
|
+
name: { type: 'any' },
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
// Any type on field
|
|
164
|
+
await expect(user1.validate({ name: 'benjamin' })).resolves.toEqual({ name: 'benjamin' })
|
|
165
|
+
await expect(user1.validate({ name: 1 })).resolves.toEqual({ name: 1 })
|
|
166
|
+
await expect(user1.validate({ name: null })).resolves.toEqual({ name: null })
|
|
167
|
+
await expect(user1.validate({ name: true })).resolves.toEqual({ name: true })
|
|
168
|
+
await expect(user1.validate({ name: false })).resolves.toEqual({ name: false })
|
|
169
|
+
await expect(user1.validate({ name: [1, 2] })).resolves.toEqual({ name: [1, 2] })
|
|
170
|
+
await expect(user1.validate({ name: { first: 1 } })).resolves.toEqual({ name: { first: 1 } })
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('validation schema with reserved and invalid rules', async () => {
|
|
174
|
+
const db2 = monastery('127.0.0.1/monastery', { logLevel: 0 })
|
|
175
|
+
let user = db2.model('user-model', {
|
|
176
|
+
fields: {
|
|
177
|
+
sub: {
|
|
178
|
+
name: {
|
|
179
|
+
type: 'string',
|
|
180
|
+
default: true, // reserved keyword
|
|
181
|
+
invalidRule: {}, // no rule function found
|
|
182
|
+
validRule: true,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
rules: {
|
|
187
|
+
default: (value) => { // function shouldn't run (i.e. value still is 'Martin')
|
|
188
|
+
return false
|
|
189
|
+
},
|
|
190
|
+
validRule: (value) => {
|
|
191
|
+
if (value === 'Martin') return true
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
await expect(user.validate({ sub: { name: 'Martin' } })).resolves.toEqual({
|
|
196
|
+
createdAt: expect.any(Number),
|
|
197
|
+
updatedAt: expect.any(Number),
|
|
198
|
+
sub: {
|
|
199
|
+
name: 'Martin',
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
db2.close()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('validation strict false', async () => {
|
|
206
|
+
let user1 = db.model('user', {
|
|
207
|
+
fields: {
|
|
208
|
+
name: { type: 'string' },
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
// strict on (default)
|
|
212
|
+
let inserted1 = await user1.insert({ data: { nonDefinedField: 1 } })
|
|
213
|
+
expect(inserted1).toEqual({
|
|
214
|
+
_id: inserted1._id,
|
|
215
|
+
})
|
|
216
|
+
let user2 = db.model('user', {
|
|
217
|
+
fields: {
|
|
218
|
+
name: { type: 'string' },
|
|
219
|
+
sub: {
|
|
220
|
+
name: { type: 'string' },
|
|
221
|
+
schema: { strict: false },
|
|
222
|
+
},
|
|
223
|
+
subArray: [{
|
|
224
|
+
name: { type: 'string' },
|
|
225
|
+
schema: { strict: false },
|
|
226
|
+
}],
|
|
227
|
+
schema: { strict: false },
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
// strict off
|
|
231
|
+
let inserted2 = await user2.insert({ data: { nonDefinedField: 1 } })
|
|
232
|
+
expect(inserted2).toEqual({
|
|
233
|
+
_id: inserted2._id,
|
|
234
|
+
nonDefinedField: 1,
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
157
238
|
test('validation subdocument errors', async () => {
|
|
158
239
|
let user = db.model('user', { fields: {
|
|
159
240
|
animals: {
|