xpress-mongo
Advanced tools
Comparing version 0.0.3 to 0.0.4
@@ -24,3 +24,4 @@ class ModelDataType { | ||
class NotDefined {} | ||
class NotDefined { | ||
} | ||
@@ -30,2 +31,6 @@ const isString = (v) => typeof v === 'string'; | ||
/** | ||
* DataTypes | ||
* @type {{NotDefined: NotDefined, ModelDataType: ModelDataType, is: {Number: (function(*=): ModelDataType), ObjectId: (function(): ModelDataType), String: (function(*=): ModelDataType), Boolean: (function(*=): ModelDataType), Date: (function(*=): ModelDataType)}}} | ||
*/ | ||
module.exports = { | ||
@@ -73,7 +78,18 @@ NotDefined, | ||
*/ | ||
Date: (def = new Date().toISOString()) => { | ||
Date: (def = new Date()) => { | ||
return new ModelDataType(def) | ||
.validator(isString); | ||
}, | ||
/** | ||
* Number | ||
* @param def | ||
* @return {ModelDataType} | ||
* @constructor | ||
*/ | ||
Number: (def = 0) => { | ||
return new ModelDataType(def) | ||
.validator(isNaN) | ||
} | ||
} | ||
}; |
@@ -20,3 +20,3 @@ const {MongoClient} = require('mongodb'); | ||
Client, | ||
is, | ||
is | ||
}; |
{ | ||
"name": "xpress-mongo", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"description": "Light Weight ODM for mongoDb", | ||
@@ -27,3 +27,4 @@ "main": "index.js", | ||
"deep-object-diff": "^1.1.0", | ||
"mongodb": "^3.4.0" | ||
"mongodb": "^3.4.0", | ||
"object-collection": "^1.0.19" | ||
}, | ||
@@ -30,0 +31,0 @@ "devDependencies": { |
@@ -17,3 +17,3 @@ # Xpress-Mongo | ||
class User extends Collection("users") { | ||
class Users extends Collection("users") { | ||
constructor() { | ||
@@ -25,3 +25,3 @@ super(); | ||
module.exports = User; | ||
module.exports = Users; | ||
``` | ||
@@ -31,3 +31,3 @@ | ||
```javascript | ||
const user = new User().set({ | ||
const user = new Users().set({ | ||
first_name: 'John', | ||
@@ -34,0 +34,0 @@ last_name: 'Doe' |
const {Client} = require('../index'); | ||
const db = "mongodb://localhost:27017"; | ||
const db = "mongodb://127.0.0.1:27017"; | ||
const db_name = "test_model"; | ||
@@ -5,0 +5,0 @@ const db_options = { |
const Database = require('./connection'); | ||
const {is} = require('../index'); | ||
const UserSchema = { | ||
const ContactSchema = is => ({ | ||
user_id: is.ObjectId(), | ||
first_name: is.String().required(), | ||
last_name: is.String().required(), | ||
phone: is.String(), | ||
created_at: is.Date() | ||
}); | ||
class Contacts extends Database.model("contacts") { | ||
constructor() { | ||
super(); | ||
this.setSchema(ContactSchema); | ||
} | ||
} | ||
const UserSchema = is => ({ | ||
email: is.String().required(), | ||
@@ -10,8 +24,8 @@ first_name: is.String().required(), | ||
verified: is.Boolean(), | ||
updated_at: is.Date(), | ||
created_at: is.Date() | ||
}; | ||
updated_at: is.Date, | ||
created_at: is.Date | ||
}); | ||
class User extends Database.model("users") { | ||
class Users extends Database.model("users") { | ||
constructor() { | ||
@@ -21,8 +35,26 @@ super(); | ||
} | ||
static append = ['fullName']; | ||
fullName() { | ||
return `${this.data.first_name} ${this.data.last_name}` | ||
} | ||
static relationships = { | ||
contact: { | ||
type: 'hasOne', | ||
model: Contacts, | ||
where: {user_id: '_id'}, | ||
options: {projection: {_id: 1}} | ||
} | ||
}; | ||
} | ||
/** | ||
* Return User | ||
* @type {User| typeof XMongoModel} | ||
* @type {typeof Users| typeof XMongoModel} | ||
*/ | ||
module.exports = User; | ||
module.exports.Users = Users; | ||
/** | ||
* @type {typeof Contacts| typeof XMongoModel} | ||
*/ | ||
module.exports.Contacts = Contacts; |
const {ObjectID} = require('mongodb'); | ||
const {ModelDataType} = require('./DataTypes'); | ||
const {is, ModelDataType} = require('./DataTypes'); | ||
const {diff} = require('deep-object-diff'); | ||
const ObjectCollection = require('object-collection'); | ||
const _ = ObjectCollection._; | ||
/** | ||
@@ -26,3 +27,3 @@ * Returns a model class tied to the given collection. | ||
// Assume data is empty | ||
this.data = {}; | ||
this.emptyData(); | ||
Object.defineProperty(this, 'schema', { | ||
@@ -33,4 +34,9 @@ value: {}, | ||
}); | ||
Object.defineProperty(this, 'loadedRelationships', { | ||
value: [], | ||
write: true, | ||
enumerable: false | ||
}) | ||
} | ||
} | ||
@@ -44,3 +50,10 @@ | ||
/** | ||
* Model Data | ||
* @type {ObjectCollection} | ||
*/ | ||
XMongoModel.prototype.$data = new ObjectCollection; | ||
/** | ||
* Model Original Data | ||
@@ -58,3 +71,9 @@ * @type {*} | ||
/** Model Loaded Relationships | ||
* @private | ||
* @type {*[]} | ||
*/ | ||
XMongoModel.prototype.loadedRelationships = []; | ||
/** | ||
@@ -65,4 +84,4 @@ * Get Data in model | ||
*/ | ||
XMongoModel.prototype.get = function (key) { | ||
return this.data[key] || undefined; | ||
XMongoModel.prototype.get = function (key, $default) { | ||
return _.get(this.data, key, $default); | ||
}; | ||
@@ -79,14 +98,6 @@ | ||
for (const property in key) { | ||
Object.defineProperty(this.data, property, { | ||
value: key[property], | ||
writable: true, | ||
enumerable: true, | ||
}) | ||
_.set(this.data, property, key[property]) | ||
} | ||
} else if (typeof key === 'string') { | ||
Object.defineProperty(this.data, key, { | ||
value, | ||
writable: true, | ||
enumerable: true, | ||
}); | ||
_.set(this.data, key, value) | ||
} | ||
@@ -98,2 +109,14 @@ return this; | ||
/** | ||
* Insert new record and return instance. | ||
* @param data | ||
* @return {Promise<this|*>} | ||
*/ | ||
XMongoModel.new = async function (data) { | ||
const record = new this().set(data); | ||
await record.save(); | ||
return record; | ||
}; | ||
/** | ||
* Set Original result gotten from db | ||
@@ -104,2 +127,4 @@ * @param data | ||
XMongoModel.prototype.setOriginal = function (data) { | ||
data = _.cloneDeep(data); | ||
Object.defineProperty(this, 'original', { | ||
@@ -114,3 +139,8 @@ value: data, | ||
/** | ||
* @callback schemaWithIs | ||
* @param {is|*} raw | ||
*/ | ||
/** | ||
@@ -120,3 +150,3 @@ * Set Model Schema | ||
* if `schema` is undefined then `this.data` is used as schema object | ||
* @param schema | ||
* @param {schemaWithIs|Object} schema | ||
* @returns {XMongoModel} | ||
@@ -127,9 +157,15 @@ */ | ||
schema === undefined && (schema = this.data); | ||
const newData = {}; | ||
const newData = {_id: null}; | ||
// If schema is a function then call it and pass is. | ||
if (typeof schema === "function") { | ||
schema = schema(is); | ||
} | ||
for (const key in schema) { | ||
if (schema.hasOwnProperty(key)) { | ||
const val = schema[key]; | ||
let val = schema[key]; | ||
if (val instanceof ModelDataType) { | ||
@@ -168,3 +204,3 @@ this.schema[key] = val['schema']; | ||
XMongoModel.prototype.id = function () { | ||
return this.data['_id'] || null | ||
return (this.data && this.data['_id']) || null | ||
}; | ||
@@ -177,6 +213,28 @@ | ||
XMongoModel.prototype.changes = function () { | ||
return diff(this.original, this.data); | ||
const changes = diff(this.original, this.data); | ||
const data = {}; | ||
const append = this.constructor.append || []; | ||
const excluded = [...append, ...this.loadedRelationships]; | ||
for (const key in changes) { | ||
if (!excluded.includes(key)) { | ||
data[key] = this.data[key] | ||
} | ||
} | ||
return data; | ||
}; | ||
/** | ||
* Update model | ||
* @param set | ||
* @param options | ||
* @return {Promise<Collection~updateWriteOpResult|Collection~insertOneWriteOpResult>} | ||
*/ | ||
XMongoModel.prototype.update = function (set, options) { | ||
if (!this.id()) throw "UPDATE_ERROR: Model does not have an _id, so we assume it is not from the database."; | ||
return this.set(set).save(options) | ||
}; | ||
/** | ||
* Create Model if not id is missing or save document if id is found. | ||
@@ -191,5 +249,8 @@ * @param options | ||
if (id) { | ||
const $set = this.changes(); | ||
if (!Object.keys($set).length) return resolve(false); | ||
return collection.updateOne( | ||
{_id: this.id()}, | ||
{$set: this.changes()}, | ||
{$set}, | ||
options, | ||
@@ -204,3 +265,5 @@ (error, res) => error ? reject(error) : resolve(res.connection)) | ||
const {insertedId} = res; | ||
this.set('_id', insertedId); | ||
this.setOriginal(this.data); | ||
@@ -215,2 +278,128 @@ return resolve(res) | ||
/** | ||
* Delete this | ||
* @param writeConcern | ||
* @returns {Promise} | ||
*/ | ||
XMongoModel.prototype.delete = function (writeConcern) { | ||
const _id = this.id(); | ||
if (_id) { | ||
this.emptyData(); | ||
return collection.deleteOne({_id}) | ||
} else { | ||
throw "DELETE_ERROR: Model does not have an _id, so we assume it is not from the database."; | ||
} | ||
}; | ||
/** | ||
* Sets data as an instance of ObjectCollection on this.$data | ||
* @return {ObjectCollection} | ||
*/ | ||
XMongoModel.prototype.toCollection = function () { | ||
if (!this.hasOwnProperty('$data')) { | ||
Object.defineProperty(this, '$data', { | ||
value: new ObjectCollection(this.data), | ||
writable: true, | ||
enumerable: false | ||
}); | ||
return this.$data; | ||
} | ||
return this.$data; | ||
}; | ||
XMongoModel.prototype.hasOne = async function (relationship, extend = {}) { | ||
let config = this.constructor.relationships; | ||
if (config && config.hasOwnProperty(relationship)) { | ||
config = config[relationship]; | ||
if (config.type !== "hasOne") { | ||
throw Error(`Relationship: (${relationship}) is not of type "hasOne"`) | ||
} | ||
/** | ||
* Raw option check. | ||
* @type {boolean|boolean} | ||
*/ | ||
const cast = extend.hasOwnProperty('cast') && extend.cast === true; | ||
/** | ||
* Get query option | ||
* @type {*|{}} | ||
*/ | ||
let options = _.cloneDeep(config['options'] || {}); | ||
if (extend.hasOwnProperty('options')) options = _.cloneDeep(extend.options); | ||
/** | ||
* Get Relationship where query. | ||
* @type {*} | ||
*/ | ||
let where = _.clone(config.where); | ||
if (typeof where === "object" && Object.keys(where).length) { | ||
/** | ||
* Loop through all keys in where query and change the values | ||
* to matching model instance values. | ||
*/ | ||
for (const key in where) { | ||
where[key] = this.get(where[key]) | ||
} | ||
} else { | ||
where = {}; | ||
} | ||
if (typeof extend !== "object") { | ||
throw Error(`hasOne second argument must be of type "Object"`); | ||
} | ||
/** | ||
* Get hasOne Model. | ||
* @type {typeof XMongoModel} | ||
*/ | ||
let model = config.model; | ||
if (Array.isArray(model) && typeof model[0] === "function") | ||
model = model[0](); | ||
let relatedData = await model.raw.findOne(where, options); | ||
if (cast && relatedData) relatedData = model.use(relatedData); | ||
/** | ||
* Set relationship to value provided in the extend.as config. | ||
*/ | ||
if (extend['as']) relationship = extend['as']; | ||
this.set(relationship, relatedData); | ||
this.loadedRelationships.push(relationship); | ||
return relatedData; | ||
} | ||
}; | ||
/** | ||
* @private | ||
* @return {*} | ||
*/ | ||
XMongoModel.prototype.toJSON = function () { | ||
return this.data; | ||
}; | ||
XMongoModel.prototype.toJson = function (replacer = undefined, space = undefined) { | ||
return JSON.stringify(this.data, replacer, space); | ||
}; | ||
XMongoModel.prototype.emptyData = function () { | ||
this.data = { | ||
_id: this.id() | ||
}; | ||
return this; | ||
}; | ||
/** | ||
* Direct mongodb access | ||
@@ -255,7 +444,6 @@ * @type {Collection} | ||
XMongoModel.find = (query, options, raw = false) => { | ||
const result = collection.find(query, options); | ||
if (raw) return result; | ||
return new Promise((resolve, reject) => { | ||
const result = collection.find(query, options); | ||
if (raw) return resolve(result); | ||
return result.toArray((error, data) => { | ||
@@ -268,4 +456,91 @@ if (error) return reject(error); | ||
/** | ||
* Turn data provided in query function to model instances. | ||
* @param {{}} data | ||
* @return {XMongoModel} | ||
*/ | ||
XMongoModel.use = function (data) { | ||
const model = new this(); | ||
model.emptyData(); | ||
// Set Original Property | ||
model.setOriginal(data); | ||
model.set(data); | ||
if (this.append) { | ||
for (const key of this.append) { | ||
if (typeof model[key] === "function") { | ||
model.set(key, model[key]()) | ||
} | ||
} | ||
} | ||
return model; | ||
}; | ||
/** | ||
* @callback rawQueryFn | ||
* @param {Collection|*} raw | ||
*/ | ||
/** | ||
* Turn array provided to model instances. | ||
* | ||
* if function is passed instead of the array | ||
* xpress-mongo will assume you want to provide a raw query | ||
* that it will append mongodb `.toArray` function to. | ||
* | ||
* @example | ||
* E.G | ||
* contact = ContactModel.fromArray([...SomeAlreadyFetchedData]) | ||
* | ||
* OR | ||
* contact = ContactModel.fromArray(raw => raw.find().limit(10)); | ||
* | ||
* WHICH IS === | ||
* | ||
* Model.raw.find().limit(10).toArray((err, lists) => { | ||
* Model.fromArray(lists); | ||
* }) | ||
* | ||
* | ||
* @static | ||
* @method | ||
* @param {rawQueryFn|Object[]} query - Data as array or query as function. | ||
* @return {Promise<this[]>|this[]} returns - Array of model instances | ||
*/ | ||
XMongoModel.fromArray = function (query) { | ||
if (typeof query === "function") { | ||
return new Promise((resolve, reject) => { | ||
return query(this.raw).toArray((error, lists) => { | ||
if (error) return reject(error); | ||
return resolve(this.fromArray(lists)); | ||
}); | ||
}); | ||
} else { | ||
const data = []; | ||
for (const list of query) { | ||
data.push(this.use(list)) | ||
} | ||
return data; | ||
} | ||
}; | ||
/** | ||
* | ||
* @param {rawQueryFn} query | ||
* @return {Promise<this>|XMongoModel} | ||
*/ | ||
XMongoModel.from = function (query) { | ||
return new Promise((resolve, reject) => { | ||
return query(this.raw).then((error, data) => { | ||
if (error) return reject(error); | ||
return resolve(this.use(data)); | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Fetches the first document that matches the query | ||
@@ -277,5 +552,11 @@ * @param query | ||
*/ | ||
XMongoModel.findOne = (query, options, raw = false) => { | ||
XMongoModel.findOne = function (query, options, raw = false) { | ||
if (typeof options === "boolean") { | ||
raw = options; | ||
options = {}; | ||
} | ||
return new Promise((resolve, reject) => { | ||
collection.findOne(query, options, (error, data) => { | ||
return collection.findOne(query, options, (error, data) => { | ||
if (error) return reject(error); | ||
@@ -286,9 +567,3 @@ // Return new instance of Model | ||
const model = new XMongoModel(); | ||
// Set Original Property | ||
model.setOriginal(data); | ||
model.set(data); | ||
return resolve(model); | ||
return resolve(this.use(data)); | ||
}); | ||
@@ -302,8 +577,9 @@ }); | ||
* @param _id | ||
* @param options | ||
* @param isTypeObjectId | ||
* @return {Promise<XMongoModel>} | ||
*/ | ||
XMongoModel.findOneById = function (_id, isTypeObjectId = true) { | ||
XMongoModel.findById = function (_id, options = {}, isTypeObjectId = true) { | ||
let where; | ||
if (isTypeObjectId) { | ||
if (typeof _id === "string" || !isTypeObjectId) { | ||
where = XMongoModel.id(_id, true); | ||
@@ -314,3 +590,3 @@ } else { | ||
return XMongoModel.findOne(where); | ||
return this.findOne(where, options); | ||
}; | ||
@@ -325,8 +601,85 @@ | ||
XMongoModel.count = function (query, options) { | ||
return XMongoModel.raw.find(query, options).count() | ||
return this.raw.find(query, options).count() | ||
}; | ||
/** | ||
* Count Aggregations | ||
* @param query | ||
* @param options | ||
* @returns {Promise<number|*>} | ||
*/ | ||
XMongoModel.countAggregate = async function (query, options) { | ||
query = _.cloneDeep(query); | ||
query.push({$count: "count_aggregate"}); | ||
const data = await this.raw.aggregate(query, options).toArray(); | ||
if (data.length) { | ||
return data[0]['count_aggregate'] | ||
} | ||
return 0; | ||
}; | ||
/** | ||
* Paginate Find. | ||
* @param query | ||
* @param options | ||
* @param page | ||
* @param perPage | ||
* @return {Promise<{total: *, perPage: number, lastPage: number, data: [], page: number}>} | ||
*/ | ||
XMongoModel.paginate = async function (page = 1, perPage = 20, query = {}, options = {}) { | ||
page = Number(page); | ||
perPage = Number(perPage); | ||
const total = await this.count(query); | ||
const lastPage = Math.ceil(total / perPage); | ||
const skips = perPage * (page - 1); | ||
const data = await this.raw.find(query, options).skip(skips).limit(perPage).toArray(); | ||
return { | ||
total, | ||
perPage, | ||
page, | ||
lastPage, | ||
data | ||
} | ||
}; | ||
/** | ||
* Paginate Aggregation. | ||
* @param {number} page | ||
* @param {number} perPage | ||
* @param {[]} query | ||
* @param {*} options | ||
* @returns {Promise<{total: (*|number), perPage: number, lastPage: number, data: *, page: number}>} | ||
*/ | ||
XMongoModel.paginateAggregate = async function (page = 1, perPage = 20, query = [], options = {}) { | ||
query = _.cloneDeep(query); | ||
page = Number(page); | ||
perPage = Number(perPage); | ||
const total = await this.countAggregate(query); | ||
const lastPage = Math.ceil(total / perPage); | ||
const skips = perPage * (page - 1); | ||
query.push({$skip: skips}); | ||
query.push({$limit: perPage}); | ||
const data = await this.raw.aggregate(query, options).toArray(); | ||
return { | ||
total, | ||
perPage, | ||
page, | ||
lastPage, | ||
data | ||
} | ||
}; | ||
return XMongoModel; | ||
} | ||
module.exports = GenerateModel; | ||
module.exports = GenerateModel; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
26939
11
859
3
+ Addedobject-collection@^1.0.19
+ Added@types/lodash@4.17.13(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedobject-collection@1.1.0(transitive)