monastery-js
Advanced tools
Comparing version 1.10.7 to 1.10.8
@@ -12,4 +12,3 @@ --- | ||
- | - | ||
required | boolean | ||
type | string - 'string', 'boolean', 'number', 'integer', 'date', 'id', 'any', '[image](#image-type)' | ||
enum | array of strings (for the 'string' type only) | ||
isAfter | see validator.isAfter | ||
@@ -30,2 +29,4 @@ isBefore | see validator.isBefore | ||
regex | see validator.matches | ||
required | boolean | ||
type | string - 'string', 'boolean', 'number', 'integer', 'date', 'id', 'any', '[image](#image-type)' | ||
@@ -32,0 +33,0 @@ *See [validator](https://github.com/validatorjs/validator.js#validators) for their validator logic* |
@@ -112,3 +112,3 @@ --- | ||
// below for more information | ||
index: true|1|-1|'2dsphere'|'text'|'unique'|Object | ||
index: true|1|-1|'text'|'unique'|Object | ||
} | ||
@@ -130,6 +130,2 @@ ``` | ||
// This will create an ascending 2dsphere index which translates: | ||
// { key: { [fieldName]: '2dsphere' }} | ||
index: '2dsphere', | ||
// Text indexes are handled a little differently in which all the fields on the model | ||
@@ -147,2 +143,26 @@ // schema that have a `index: 'text` set are collated into one index, e.g. | ||
And here's how you would use a 2dsphere index, e.g. | ||
```js | ||
schema.fields = { | ||
name: { | ||
type: 'string', | ||
required: true | ||
}, | ||
location: { | ||
index: '2dsphere', | ||
type: { type: 'string', default: 'Point' }, | ||
coordinates: [{ type: 'number' }] // lng, lat | ||
} | ||
} | ||
// Inserting a 2dsphere point | ||
await db.user.insert({ | ||
data: { | ||
location: { coordinates: [170.2628528648167, -43.59467883784971] } | ||
} | ||
} | ||
``` | ||
### Custom validation rules | ||
@@ -149,0 +169,0 @@ |
@@ -9,2 +9,3 @@ let crud = require('./model-crud') | ||
* Setup a model (aka monk collection) | ||
* Todo: convert into a promise | ||
* @param {string} name | ||
@@ -98,6 +99,8 @@ * @param {object} opts - see mongodb colleciton documentation | ||
// Ensure field indexes exist in mongodb | ||
this._setupIndexes().catch(err => { | ||
let errHandler = err => { | ||
if (err.type == 'info') this.info(err.detail) | ||
else this.error(err) | ||
}) | ||
} | ||
if (opts.promise) return this._setupIndexes().catch(errHandler) | ||
else this._setupIndexes().catch(errHandler) | ||
} | ||
@@ -137,3 +140,2 @@ | ||
let isType = 'is' + util.ucFirst(field.type) | ||
//if (field.index == '2dspehere') return | ||
@@ -179,3 +181,9 @@ // No type defined | ||
let nullObject = this.manager.nullObjects | ||
field.schema = { type: 'object', isObject: true, default: objectDefault, nullObject: nullObject, ...(field.schema || {}) } | ||
let index2dsphere = util.isSubdocument2dsphere(field) | ||
field.schema = field.schema || {} | ||
if (index2dsphere) { | ||
field.schema.index = index2dsphere | ||
delete field.index | ||
} | ||
field.schema = { type: 'object', isObject: true, default: objectDefault, nullObject: nullObject, ...field.schema } | ||
this._setupFields(field) | ||
@@ -233,3 +241,2 @@ } | ||
// Create indexes | ||
// console.log(indexes) | ||
return (model.manager._state == 'open'? Promise.resolve() : model.manager) | ||
@@ -247,3 +254,3 @@ .then(data => { | ||
.then(indexes1 => { | ||
// Remove existing text index that has different options otherwise createIndexes will throw an error | ||
// Remove any existing text index that has different options as createIndexes will throws error about this | ||
let indexNames = [] | ||
@@ -279,18 +286,21 @@ if (!indexes1.length) return Promise.resolve() | ||
util.forEach(fields, (field, name) => { | ||
if (util.isSubdocument(field)) recurseFields(field, parentPath + name + '.') | ||
if (!field.index/* || util.isObject(field.index)*/) return // commented out for future options | ||
let index = field.index | ||
if (index) { | ||
let path = name == 'schema'? parentPath.slice(0, -1) : parentPath + name | ||
let options = util.isObject(index)? util.omit(index, ['type']) : {} | ||
let type = util.isObject(index)? index.type : index | ||
if (type === true) type = 1 | ||
let path = parentPath + name | ||
let options = util.isObject(field.index)? field.index.filter((o, k) => k !== 'type') : {} | ||
let type = util.isObject(field.index)? field.index.type : field.index | ||
if (type === true) type = 1 | ||
if (type == 'text') { | ||
hasTextIndex = textIndex.key[path] = 'text' | ||
Object.assign(textIndex, options) | ||
} else if (type == '1' || type == '-1' || type == '2dsphere') { | ||
indexes.push({ name: `${path}_${type}`, key: { [path]: type }, ...options }) | ||
} else if (type == 'unique') { | ||
indexes.push({ name: `${path}_1`, key: { [path]: 1 }, unique: true, ...options }) | ||
if (type == 'text') { | ||
hasTextIndex = textIndex.key[path] = 'text' | ||
Object.assign(textIndex, options) | ||
} else if (type == '1' || type == '-1' || type == '2dsphere') { | ||
indexes.push({ name: `${path}_${type}`, key: { [path]: type }, ...options }) | ||
} else if (type == 'unique') { | ||
indexes.push({ name: `${path}_1`, key: { [path]: 1 }, unique: true, ...options }) | ||
} | ||
} | ||
if (util.isSubdocument(field)) { | ||
recurseFields(field, parentPath + name + '.') | ||
} | ||
}) | ||
@@ -297,0 +307,0 @@ } |
@@ -135,2 +135,10 @@ let id = require('monk').id | ||
}, | ||
'enum': { | ||
message: (x, arg) => 'Invalid enum value', | ||
fn: function(x, arg) { | ||
for (let item of arg) { | ||
if (x === item + '') return true | ||
} | ||
} | ||
}, | ||
@@ -137,0 +145,0 @@ // Rules below ignore null & empty strings |
@@ -98,3 +98,3 @@ module.exports = { | ||
isSchema: function(value) { | ||
return !this.isSubdocument(value) && !this.isArray(value) | ||
return !this.isSubdocument(value) && !this.isArray(value) && typeof value !== 'string' | ||
}, | ||
@@ -109,3 +109,2 @@ | ||
* Is the value a subdocument which contains more fields? Or a just a field definition? | ||
* Note: Passing { index: '2dsphere' } as a subdocument as this is used to denote a 2dsphere object index | ||
* @param {object} value - object to check | ||
@@ -119,4 +118,5 @@ * E.g. isSubdocument = { | ||
for (let key in value) { | ||
if (key == 'index' && value[key] == '2dsphere') continue | ||
if (this.isObject(value[key]) || this.isArray(value[key])) continue | ||
let v = value[key] | ||
if (key == 'index' && this.isSubdocument2dsphere({ index: v })) continue | ||
if (this.isObject(v) || this.isArray(v)) continue | ||
return false | ||
@@ -127,2 +127,12 @@ } | ||
isSubdocument2dsphere: function(value) { | ||
/** | ||
* Index 2dsphere implies that the parent object is a subdocument | ||
* @return index value | ||
*/ | ||
if (value.index == '2dsphere') return '2dsphere' | ||
else if (this.isObject(value.index) && value.index.type == '2dsphere') return value.index | ||
else return false | ||
}, | ||
isUndefined: function(value) { | ||
@@ -129,0 +139,0 @@ return typeof value === 'undefined' |
@@ -5,3 +5,3 @@ { | ||
"author": "Ricky Boyce", | ||
"version": "1.10.7", | ||
"version": "1.10.8", | ||
"license": "MIT", | ||
@@ -8,0 +8,0 @@ "repository": "github:boycce/monastery", |
@@ -106,3 +106,3 @@ module.exports = function(monastery, db) { | ||
let user = db.model('user', {}) | ||
let zuser = db.model('zuser', {}) | ||
let user2 = db.model('user2', {}) | ||
@@ -121,3 +121,3 @@ // Text index setup | ||
// Text index on a different model | ||
await expect(zuser._setupIndexes({ | ||
await expect(user2._setupIndexes({ | ||
name: { type: 'string', index: 'text' } | ||
@@ -130,2 +130,66 @@ })).resolves.toEqual(undefined) | ||
test('Model 2dsphere indexes', async (done) => { | ||
// Setup. The tested model needs to be unique as race condition issue arises when the same model | ||
// with text indexes are setup at the same time | ||
let db = monastery('localhost/monastery', { serverSelectionTimeoutMS: 2000 }) | ||
await db.model('user3', { | ||
promise: true, | ||
fields: { | ||
location: { | ||
index: '2dsphere', | ||
type: { type: 'string', default: 'Point' }, | ||
coordinates: [{ type: 'number' }] // lat, lng | ||
}, | ||
location2: { | ||
index: { type: '2dsphere' }, // opts... | ||
type: { type: 'string', default: 'Point' }, | ||
coordinates: [{ type: 'number' }] // lat, lng | ||
} | ||
} | ||
}) | ||
// Schema check | ||
expect(db.user3.fields.location).toEqual({ | ||
type: { type: 'string', default: 'Point', isString: true }, | ||
coordinates: expect.any(Array), | ||
schema: { | ||
default: undefined, | ||
index: "2dsphere", | ||
isObject: true, | ||
nullObject: undefined, | ||
type: "object" | ||
} | ||
}) | ||
expect(db.user3.fields.location2).toEqual({ | ||
type: { type: 'string', default: 'Point', isString: true }, | ||
coordinates: expect.any(Array), | ||
schema: { | ||
default: undefined, | ||
index: { type: "2dsphere" }, | ||
isObject: true, | ||
nullObject: undefined, | ||
type: "object" | ||
} | ||
}) | ||
// Insert 2dsphere point data | ||
await expect(db.user3.insert({ | ||
data: { | ||
location: { coordinates: [172.5880385, -43.3311608] } | ||
} | ||
})).resolves.toEqual({ | ||
_id: expect.any(Object), | ||
createdAt: expect.any(Number), | ||
location: { | ||
coordinates: [172.5880385, -43.3311608], | ||
type: "Point" | ||
}, | ||
updatedAt: expect.any(Number), | ||
}) | ||
db.close() | ||
done() | ||
}) | ||
test('Model findWL, findBLProject', async (done) => { | ||
@@ -132,0 +196,0 @@ let db = monastery('localhost/monastery', { |
@@ -320,3 +320,4 @@ module.exports = function(monastery, db) { | ||
name: { type: 'string', minLength: 7 }, | ||
email: { type: 'string', isEmail: true } | ||
email: { type: 'string', isEmail: true }, | ||
names: { type: 'string', enum: ['Martin', 'Luther'] } | ||
}}) | ||
@@ -349,2 +350,15 @@ | ||
}) | ||
// Enum | ||
await expect(user.validate({ names: 'Martin' })).resolves.toEqual({ "names": "Martin" }) | ||
await expect(user.validate({ names: 'bad name' })).rejects.toContainEqual({ | ||
detail: "Invalid enum value", | ||
status: "400", | ||
title: "names", | ||
meta: { | ||
model: "user", | ||
field: "names", | ||
rule: "enum" | ||
} | ||
}) | ||
}) | ||
@@ -351,0 +365,0 @@ |
864438
3648