xpress-mongo
Advanced tools
Comparing version 0.0.13 to 0.0.14
@@ -0,1 +1,3 @@ | ||
const {ObjectID} = require('mongodb'); | ||
class ModelDataType { | ||
@@ -13,2 +15,7 @@ constructor($default = undefined) { | ||
/** | ||
* Set Type Validator | ||
* @param {function|{or: Array}|{and: Array}} validator | ||
* @return {ModelDataType} | ||
*/ | ||
validator(validator = () => true) { | ||
@@ -18,2 +25,12 @@ this.schema.validator = validator; | ||
} | ||
validatorError(error) { | ||
this.schema.validationError = error; | ||
return this; | ||
} | ||
cast(cast) { | ||
this.schema.cast = cast; | ||
return this; | ||
} | ||
} | ||
@@ -23,3 +40,5 @@ | ||
required: false, | ||
validator: () => true | ||
validator: () => true, | ||
validationError: () => 'Validation Error', | ||
cast: null, | ||
}; | ||
@@ -32,3 +51,11 @@ | ||
const isBoolean = (v) => typeof v === 'boolean'; | ||
const isObject = (v) => (v && typeof v === 'object'); | ||
const isArray = (v) => Array.isArray(v); | ||
const isDate = (v) => v instanceof Date; | ||
const isNumber = (v) => !isNaN(v); | ||
isObject(null); | ||
/** | ||
@@ -48,7 +75,49 @@ * DataTypes | ||
ObjectId: () => { | ||
return new ModelDataType(null) | ||
.required(true); | ||
return new ModelDataType(undefined) | ||
.validator({ | ||
or: [isString, isObject] | ||
}) | ||
.cast((val, key) => { | ||
if (typeof val === "object" && ObjectID.isValid(val)) { | ||
return val | ||
} | ||
try { | ||
return new ObjectID(val); | ||
} catch (e) { | ||
throw TypeError(`(${key}) is not valid Mongodb-ObjectID`); | ||
} | ||
}) | ||
.validatorError((key) => `(${key}) is not a Mongodb-ObjectID`); | ||
}, | ||
/** | ||
* Array | ||
* @param def | ||
* @return {ModelDataType} | ||
* @constructor | ||
*/ | ||
Array: (def = () => ([])) => { | ||
return new ModelDataType(def) | ||
.validator(isArray) | ||
.validatorError((key) => `(${key}) is not an Array`); | ||
}, | ||
/** | ||
* Object | ||
* @param def | ||
* @return {ModelDataType} | ||
* @constructor | ||
*/ | ||
Object: (def = () => ({})) => { | ||
return new ModelDataType(def) | ||
.validator(isObject) | ||
.validatorError((key) => `(${key}) is not an Object`); | ||
}, | ||
/** | ||
* String | ||
@@ -61,3 +130,4 @@ * @param def | ||
return new ModelDataType(def) | ||
.validator(isString); | ||
.validator(isString) | ||
.validatorError((key) => `(${key}) is not a String`); | ||
}, | ||
@@ -73,3 +143,5 @@ | ||
return new ModelDataType(def) | ||
.validator(isBoolean); | ||
.validator(isBoolean) | ||
.validatorError((key) => `(${key}) is not a Boolean`); | ||
}, | ||
@@ -85,3 +157,4 @@ | ||
return new ModelDataType(def) | ||
.validator(isString); | ||
.validator(isDate) | ||
.validatorError((key) => `(${key}) is not a Date`); | ||
}, | ||
@@ -97,5 +170,7 @@ | ||
return new ModelDataType(def) | ||
.validator(isNaN) | ||
.validator(isNumber) | ||
.cast((v) => Number(v)) | ||
.validatorError((key) => `(${key}) is not a Number`); | ||
} | ||
} | ||
}; |
@@ -23,2 +23,39 @@ /** | ||
module.exports = {defaultValue}; | ||
/** | ||
* RunOrValidation | ||
* @description | ||
* Run validators and stop if any is true | ||
* @param {*} value | ||
* @param {[]} validators | ||
* @return {boolean} | ||
*/ | ||
function runOrValidation(value, validators = []) { | ||
for (const validator of validators) { | ||
if (validator(value) === true) return true | ||
} | ||
return false; | ||
} | ||
/** | ||
* RunAndValidation | ||
* @description | ||
* Run validators and stop if any is false | ||
* @param {*} value | ||
* @param {[]} validators | ||
* @return {boolean} | ||
*/ | ||
function runAndValidation(value, validators = []) { | ||
for (const validator of validators) { | ||
if (!validator(value)) return false | ||
} | ||
return true; | ||
} | ||
module.exports = { | ||
defaultValue, | ||
runOrValidation, | ||
runAndValidation | ||
}; |
{ | ||
"name": "xpress-mongo", | ||
"version": "0.0.13", | ||
"version": "0.0.14", | ||
"description": "Light Weight ODM for mongoDb", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
const Database = require('./connection'); | ||
const ContactSchema = is => ({ | ||
user_id: is.ObjectId(), | ||
first_name: is.String().required(), | ||
user_id: is.ObjectId().required(), | ||
first_name: is.String(), | ||
last_name: is.String().required(), | ||
phone: is.String(), | ||
created_at: is.Date() | ||
phone: is.String().required(), | ||
created_at: is.Date().required() | ||
}); | ||
@@ -15,3 +14,3 @@ | ||
super(); | ||
this.setSchema(ContactSchema); | ||
this.useSchema(ContactSchema); | ||
} | ||
@@ -21,15 +20,30 @@ } | ||
const UserSchema = is => ({ | ||
type: is.String().required(), | ||
email: is.String().required(), | ||
first_name: is.String().required(), | ||
last_name: is.String().required(), | ||
verified: is.Boolean(), | ||
updated_at: is.Date(), | ||
created_at: is.Date() | ||
verified: is.Boolean().required(), | ||
updated_at: is.Date().required(), | ||
address: is.String(), | ||
created_at: is.Date().required() | ||
}); | ||
const GuestSchema = is => ({ | ||
type: is.String().required(), | ||
first_name: is.String().required(), | ||
last_name: is.String(), | ||
guestId: is.String().required(), | ||
created_at: is.Date().required() | ||
}); | ||
class Users extends Database.model("users") { | ||
constructor() { | ||
super(); | ||
this.setSchema(UserSchema); | ||
this.addSchema('GuestSchema', GuestSchema); | ||
this.addSchema('UserSchema', UserSchema); | ||
this.useSchema('UserSchema'); | ||
} | ||
@@ -56,6 +70,6 @@ | ||
*/ | ||
module.exports.Users = Users; | ||
exports.Users = Users; | ||
/** | ||
* @type {typeof Contacts| typeof XMongoModel} | ||
*/ | ||
module.exports.Contacts = Contacts; | ||
exports.Contacts = Contacts; |
@@ -1,14 +0,38 @@ | ||
const {Client} = require('../index'); | ||
const mongodb = require('mongodb'); | ||
const {Users, Contacts} = require('./models'); | ||
const Chance = require('chance'); | ||
const chance = new Chance(); | ||
mongodb.MongoClient.connect( | ||
'mongodb://localhost/test_model', | ||
{useNewUrlParser: true, useUnifiedTopology: true} | ||
).then(async client => { | ||
const Database = Client(client).useDb('test_model'); | ||
const UserModel = Database.model('users'); | ||
async function run() { | ||
/** | ||
* Async Space | ||
*/ | ||
console.log(await UserModel.findOne()) | ||
}).catch(err => { | ||
console.log(err); | ||
// const guest = new Users().useSchema('UserS').set({ | ||
// type: 'guest', | ||
// first_name: 'Hello', | ||
// last_name: 'World', | ||
// guestId: chance.guid() | ||
// }); | ||
// const contact = await Contacts.findById('5e5b92f6d7fd524b7ce4fade'); | ||
// | ||
// contact.set('job', 39); | ||
// console.log(await contact.save()); | ||
// console.log(guest.data); | ||
// const user = new Users().useSchema('UserSchema').set({ | ||
// email: chance.email() | ||
// }); | ||
/** | ||
* End Async Space | ||
*/ | ||
} | ||
run().then(() => { | ||
process.exit(); | ||
}).catch(e => { | ||
console.log(e); | ||
process.exit(); | ||
}); |
@@ -73,3 +73,3 @@ const GenerateModel = require('./XMongoModel'); | ||
const connection = this.db.collection(collection); | ||
return GenerateModel(connection, collection) | ||
return GenerateModel(connection) | ||
} | ||
@@ -76,0 +76,0 @@ } |
const XMongoUsing = require('./XMongoUsing'); | ||
const {ObjectID} = require('mongodb'); | ||
const {is, ModelDataType} = require('./DataTypes'); | ||
const {defaultValue} = require('./fns/inbuilt'); | ||
const { | ||
defaultValue, | ||
runOrValidation, | ||
runAndValidation | ||
} = require('./fns/inbuilt'); | ||
const {diff} = require('deep-object-diff'); | ||
@@ -33,2 +39,9 @@ const ObjectCollection = require('object-collection'); | ||
write: true, | ||
enumerable: false, | ||
configurable: true, | ||
}); | ||
Object.defineProperty(this, 'schemaStore', { | ||
value: {}, | ||
write: true, | ||
enumerable: false | ||
@@ -72,2 +85,3 @@ }); | ||
/** | ||
@@ -103,3 +117,3 @@ * Defined relationships | ||
*/ | ||
XMongoModel.prototype.set = function (key, value) { | ||
XMongoModel.prototype.set = function (key, value = undefined) { | ||
if (typeof key === 'object' && value === undefined) { | ||
@@ -135,2 +149,3 @@ for (const property in key) { | ||
XMongoModel.prototype.setOriginal = function (data) { | ||
data = _.cloneDeep(data); | ||
@@ -147,3 +162,18 @@ | ||
/** | ||
* Set multiple schemas and use them at anytime using `.setSchema` | ||
* @param {string} name | ||
* @param {Object} schema | ||
* @return {XMongoModel} | ||
*/ | ||
XMongoModel.prototype.addSchema = function (name, schema) { | ||
// Save to schemaStore | ||
this.schemaStore[name] = schema; | ||
return this; | ||
}; | ||
/** | ||
* @callback schemaWithIs | ||
@@ -158,9 +188,46 @@ * @param {is|*} raw | ||
* if `schema` is undefined then `this.data` is used as schema object | ||
* @param {schemaWithIs|Object} schema | ||
* @param {schemaWithIs|Object|string} schema | ||
* @returns {XMongoModel} | ||
* | ||
* @deprecated | ||
*/ | ||
XMongoModel.prototype.setSchema = function (schema = undefined) { | ||
console.log(`.setSchema is deprecated use .useSchema`); | ||
return this.useSchema(schema); | ||
}; | ||
/** | ||
* Set Model Schema | ||
* | ||
* if `schema` is undefined then `this.data` is used as schema object | ||
* @param {schemaWithIs|Object|String} schema | ||
* @returns {XMongoModel} | ||
*/ | ||
XMongoModel.prototype.useSchema = function (schema = undefined) { | ||
// Redefine schema | ||
Object.defineProperty(this, 'schema', { | ||
value: {}, | ||
write: true, | ||
enumerable: false, | ||
configurable: true, | ||
}); | ||
// Try to find schema from .schemaStore if string | ||
if (typeof schema === "string") { | ||
if (!this.schemaStore.hasOwnProperty(schema)) { | ||
throw Error(`schemaStore does not have schema named: ${schema}`) | ||
} | ||
// Empty Data to remove any predefined schemas | ||
if (!Object.keys(this.original).length) { | ||
this.emptyData(); | ||
} | ||
schema = this.schemaStore[schema] || {} | ||
} | ||
const schemaIsData = schema === undefined; | ||
schema === undefined && (schema = this.data); | ||
const newData = {_id: null}; | ||
const newData = {_id: this.id()}; | ||
@@ -289,5 +356,13 @@ // If schema is a function then call it and pass is. | ||
if (id) { | ||
const $set = this.changes(); | ||
let $set = this.changes(); | ||
if (!Object.keys($set).length) return resolve(false); | ||
// Try to validate changes | ||
try { | ||
$set = {...$set, ...this.validate($set)}; | ||
} catch (e) { | ||
return reject(e) | ||
} | ||
return collection.updateOne( | ||
@@ -299,2 +374,9 @@ {_id: this.id()}, | ||
} else { | ||
// Try to validate new data. | ||
try { | ||
this.emptyData(this.validate()); | ||
} catch (e) { | ||
return reject(e) | ||
} | ||
return collection.insertOne( | ||
@@ -318,7 +400,142 @@ this.data, | ||
/** | ||
* Validate | ||
* @description | ||
* Runs validation on this.data if data is undefined. | ||
* @param data | ||
* @return {{}} | ||
*/ | ||
XMongoModel.prototype.validate = function (data = undefined) { | ||
/** | ||
* Checks if data was defined or not. | ||
* @type {boolean} | ||
*/ | ||
let customData = true; | ||
/** | ||
* If data is undefined, default to this.data | ||
* And set customData to false. | ||
*/ | ||
if (!data) { | ||
data = this.data; | ||
customData = false; | ||
} | ||
/** | ||
* Stores validated keys and values. | ||
* @type {{}} | ||
*/ | ||
const validated = {}; | ||
/** | ||
* Loop through all defined schemas and validate. | ||
*/ | ||
for (const schemaKey in this.schema) { | ||
/** | ||
* If data doesnt have schemaKey we skip | ||
* else throw Error if this is not a customData | ||
* | ||
* i.e else if data === this.data | ||
*/ | ||
if (data.hasOwnProperty(schemaKey)) { | ||
/** | ||
* Schema Definition of current schema | ||
* @type {*|ModelDataType} | ||
*/ | ||
const schema = this.schema[schemaKey]; | ||
/** | ||
* Current value of key being validated. | ||
* @type {*} | ||
*/ | ||
let dataValue = data[schemaKey]; | ||
/** | ||
* If schema is required and dataValue is undefined | ||
* Throw required error | ||
*/ | ||
if (dataValue === undefined && schema['required'] === true) { | ||
throw new TypeError(`(${schemaKey}) is required.`) | ||
} | ||
// validate using validator if value is not undefined | ||
if (dataValue !== undefined) { | ||
/** | ||
* Holds type of validator. | ||
* @type {"undefined"|"object"|"boolean"|"number"|"string"|"function"|"symbol"|"bigint"} | ||
*/ | ||
let validatorType = typeof schema.validator; | ||
/** | ||
* Holds validator Error for schemaKey | ||
* @type {string} | ||
*/ | ||
const validatorError = schema.validationError(schemaKey); | ||
/** | ||
* If validatorType is 'function', run validator | ||
* Throw error if validator returns false. | ||
* | ||
* Else if validatorType is 'object' | ||
* validate the object received. | ||
*/ | ||
if (validatorType === 'function' && !schema.validator(dataValue)) { | ||
throw new TypeError(validatorError); | ||
} else if (validatorType === 'object') { | ||
// Get first key of object | ||
validatorType = Object.keys(schema.validator)[0]; | ||
/** | ||
* If validatorType === 'or' run `runOrValidation`, | ||
* If validatorType === 'and' run `runAndValidation` | ||
*/ | ||
if (validatorType === 'or' && !runOrValidation(dataValue, schema.validator['or'])) { | ||
throw new TypeError(validatorError); | ||
} else if (validatorType === 'and' && !runAndValidation(dataValue, schema.validator['and'])) { | ||
throw new TypeError(validatorError); | ||
} | ||
} | ||
/** | ||
* Cast dataValue if schema.cast is defined and a function | ||
*/ | ||
if (typeof schema['cast'] === 'function') { | ||
dataValue = schema.cast(dataValue, schemaKey); | ||
} | ||
// Add to validated object | ||
validated[schemaKey] = dataValue; | ||
} | ||
} else { | ||
if (!customData) | ||
throw new TypeError(`${schemaKey} is missing in data but defined in schema`) | ||
} | ||
} | ||
// Add keys not in schema but in data and removed undefined values | ||
for (const dataKey in data) { | ||
const dataValue = data[dataKey]; | ||
if (!this.schema.hasOwnProperty(dataKey) && dataValue !== undefined) { | ||
validated[dataKey] = dataValue | ||
} | ||
if (dataValue === undefined) { | ||
delete data[dataKey]; | ||
} | ||
} | ||
// Return this way to retain original object structure | ||
return {...data, ...validated}; | ||
}; | ||
/** | ||
* Delete this | ||
* @param writeConcern | ||
* @returns {Promise} | ||
*/ | ||
XMongoModel.prototype.delete = function (writeConcern) { | ||
XMongoModel.prototype.delete = function () { | ||
const _id = this.id(); | ||
@@ -442,3 +659,3 @@ | ||
XMongoModel.prototype.emptyData = function () { | ||
XMongoModel.prototype.emptyData = function (replaceWith=undefined) { | ||
this.data = { | ||
@@ -448,2 +665,4 @@ _id: this.id() | ||
if(replaceWith && typeof replaceWith === 'object') this.data = {...this.data, ...replaceWith}; | ||
return this; | ||
@@ -450,0 +669,0 @@ }; |
45178
1359