Comparing version 0.5.1 to 0.6.0
## [Unreleased] | ||
* Wrap results docs in moltyClass but not remove the metadata provided by MongoDB | ||
* Add support to more pipeline stages in the aggregate function | ||
* Fix english misspelling in the documentation. | ||
* Populate documents with references to other documments. | ||
* Add deleteOne() document | ||
@@ -35,2 +35,13 @@ * Add embedded documents features | ||
## [0.6.0] - 2018-01-02 | ||
### Added | ||
* aggregate() function with support for $match, $project, and $lookup (without recursivity) stages pipeline | ||
* Documentation about new aggreagte() function | ||
### Fixed | ||
* Error that cause wrong merging og prehooks, posthooks and methods of discriminated models. | ||
## [0.5.1] - 2017-12-28 | ||
@@ -265,2 +276,3 @@ | ||
[0.6.0]: https://github.com/Yonirt/moltyjs/compare/v0.5.1...v0.6.0 | ||
[0.5.1]: https://github.com/Yonirt/moltyjs/compare/v0.5.0...v0.5.1 | ||
@@ -267,0 +279,0 @@ [0.5.0]: https://github.com/Yonirt/moltyjs/compare/v0.4.3...v0.5.0 |
'use strict'; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | ||
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); | ||
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } | ||
@@ -26,3 +26,21 @@ | ||
// https://docs.mongodb.com/v3.4/reference/operator/update/ | ||
const validUpdateOperators = [ | ||
// Fields | ||
'$currentDate', '$inc', '$min', '$max', '$mul', '$rename', '$set', '$setOnInsert', '$unset', | ||
// Array | ||
'$', '$addToSet', '$pop', '$pull', '$pushAll', '$push', '$pullAll', | ||
// Modifiers | ||
'$each', '$position', '$slice', '$sort', | ||
// Bitwise | ||
'$bit', '$isolated']; | ||
// https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/ | ||
const validAggregateOperators = { | ||
$match: [], | ||
$lookup: ['from', 'localField', 'foreignField', 'as'], | ||
$project: [] | ||
}; | ||
const defaultTenantsOptions = { | ||
@@ -50,2 +68,4 @@ noListener: false, | ||
const defaultAggregateOptions = {}; | ||
class MongoClient { | ||
@@ -154,22 +174,23 @@ constructor() { | ||
hooksList.forEach(key => { | ||
switch (key.hook) { | ||
case 'insertOne': | ||
insertHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
case 'insertMany': | ||
insertManyHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
case 'update': | ||
updateHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
case 'delete': | ||
deleteHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
default: | ||
throw new Error('Hook "' + key.hook + '" is not allowed.'); | ||
break; | ||
} | ||
}); | ||
if (hooksList.length > 0) { | ||
hooksList.forEach(key => { | ||
switch (key.hook) { | ||
case 'insertOne': | ||
insertHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
case 'insertMany': | ||
insertManyHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
case 'update': | ||
updateHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
case 'delete': | ||
deleteHooks.use(key.fn.bind(objectBinded, this, tenant)); | ||
break; | ||
default: | ||
throw new Error('Hook "' + key.hook + '" is not allowed.'); | ||
break; | ||
} | ||
}); | ||
} | ||
return { | ||
@@ -189,12 +210,2 @@ insertOne: insertHooks, | ||
_validateUpdateOperators(payload) { | ||
// https://docs.mongodb.com/v3.4/reference/operator/update/ | ||
const validUpdateOperators = [ | ||
// Fields | ||
'$currentDate', '$inc', '$min', '$max', '$mul', '$rename', '$set', '$setOnInsert', '$unset', | ||
// Array | ||
'$', '$addToSet', '$pop', '$pull', '$pushAll', '$push', '$pullAll', | ||
// Modifiers | ||
'$each', '$position', '$slice', '$sort', | ||
// Bitwise | ||
'$bit', '$isolated']; | ||
Object.keys(payload).forEach(operator => { | ||
@@ -204,3 +215,2 @@ if (validUpdateOperators.indexOf(operator) < 0) { | ||
} | ||
return; | ||
}); | ||
@@ -210,2 +220,59 @@ } | ||
/** | ||
* _validateAndIndexAggregateOperators(): Check if the aggregate operators | ||
* are correct and supported | ||
* | ||
* @param {Object} pipeline | ||
*/ | ||
_validateAndIndexAggregateOperators(pipeline) { | ||
let operatorIndexes = {}; | ||
let i = 0; | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
for (var _iterator = pipeline[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
let stage = _step.value; | ||
Object.keys(stage).forEach(operator => { | ||
// Save the position of all | ||
if (!operatorIndexes[operator]) operatorIndexes = _extends({}, operatorIndexes, { [operator]: [i] });else operatorIndexes = _extends({}, operatorIndexes, { | ||
[operator]: [operator].push(i) | ||
}); | ||
if (Object.keys(validAggregateOperators).indexOf(operator) < 0) { | ||
throw new Error('The aggregate operator is not allowed, got: ' + operator); | ||
} | ||
// If the aggregate operator has additional parameters let's check | ||
// which we support | ||
if (validAggregateOperators[operator].length > 0) { | ||
Object.keys(stage[operator]).forEach(suboperator => { | ||
if (validAggregateOperators[operator].indexOf(suboperator) < 0) { | ||
throw new Error('The paramater ' + suboperator + ' in ' + operator + ' aggreagate operator is not allowed'); | ||
} | ||
}); | ||
} | ||
}); | ||
i++; | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
return operatorIndexes; | ||
} | ||
/** | ||
* _validatePayload(): Check if the payload is valid based on the model schema | ||
@@ -593,8 +660,7 @@ * | ||
try { | ||
// Get the Cursor | ||
const cursor = conn.db(tenant, _this3._tenantsOptions).collection(collection).find(query); | ||
// Run the cursor | ||
var _ref8 = yield to(cursor.limit(findOptions.limit).project(findOptions.projection).toArray()), | ||
// Get and run the Cursor | ||
var _ref8 = yield to(conn.db(tenant, _this3._tenantsOptions).collection(collection).find(query, { | ||
limit: findOptions.limit, | ||
projection: findOptions.projection | ||
}).toArray()), | ||
_ref9 = _slicedToArray(_ref8, 2); | ||
@@ -605,2 +671,10 @@ | ||
// Run the cursor | ||
// http://mongodb.github.io/node-mongodb-native/3.0/api/Cursor.html | ||
/*const [error, result] = await to( | ||
cursor | ||
.limit(findOptions.limit) | ||
.project(findOptions.projection) | ||
.toArray(), | ||
);*/ | ||
@@ -659,2 +733,5 @@ if (error) { | ||
// Check update operators | ||
_this4._validateUpdateOperators(payload); | ||
// If we are updating a resources in a discriminator model | ||
@@ -682,5 +759,2 @@ // we have to set the proper filter and addres to the parent collection | ||
// Check update operators | ||
_this4._validateUpdateOperators(payload); | ||
// Validate the payload | ||
@@ -737,11 +811,12 @@ _this4._validatePayload(payload, model._schemaNormalized); | ||
/** | ||
* createIndexes(): Creates indexes on the db and collection collection | ||
* aggregate(): Execute an aggregation framework pipeline against the collection. | ||
* | ||
* @param {String} tenant | ||
* @param {String} collection | ||
* @param [{String}] fields | ||
* @param {Object[]} pipeline Array containing all the aggregation framework commands for the execution | ||
* @param {Object} options Optional settings. | ||
* | ||
* @returns {Promise} | ||
*/ | ||
createIndexes(tenant, collection, fields) { | ||
aggregate(tenant, collection, pipeline = [], options = {}) { | ||
var _this5 = this; | ||
@@ -752,7 +827,52 @@ | ||
if (!collection && typeof collection != 'string') throw new Error('Should specify the collection name (String), got: ' + collection); | ||
if (!fields && typeof fields != 'object') throw new Error('Should specify the field name (Array), got: ' + fields); | ||
// Checking if indexes are already set for this tenant and this collection | ||
if (_this5.tenants[tenant] && _this5.tenants[tenant][collection]) return; | ||
// Check aggregate operators | ||
const operatorsIndexes = _this5._validateAndIndexAggregateOperators(pipeline); | ||
// Assign default options to perform the aggregate query | ||
const aggregateOptions = Object.assign({}, defaultAggregateOptions, options); | ||
// If we are looking for resources in a discriminator model | ||
// we have to set the proper filter and address to the parent collection | ||
let model = {}; | ||
if (_this5.models[collection]) { | ||
const _discriminator = _this5.models[collection]._discriminator; | ||
if (_discriminator) { | ||
const discriminatorKey = _this5.models[collection]._schemaOptions.inheritOptions.discriminatorKey; | ||
if (pipeline[0]['$match'].discriminatorKey) throw new Error('You can not include a specific value for the "discriminatorKey" on the query.'); | ||
pipeline[0]['$match'] = _extends({}, pipeline[0]['$match'], { | ||
[discriminatorKey]: collection | ||
}); | ||
} | ||
model = _this5.models[collection]; | ||
collection = _this5.models[collection]._modelName; | ||
} else { | ||
throw new Error('The collection ' + collection + 'does not exist and is not registered.'); | ||
} | ||
// Check if there is a $lookup operator in the pipeline with | ||
// discriminated models pointing out | ||
if (operatorsIndexes['$lookup']) { | ||
for (let i = 0; i < operatorsIndexes['$lookup'].length; i++) { | ||
let lookup = pipeline[operatorsIndexes['$lookup'][i]].$lookup; | ||
if (_this5.models[lookup.from]) { | ||
const _discriminator = _this5.models[lookup.from]._discriminator; | ||
if (_discriminator) { | ||
lookup.from = _this5.models[lookup.from]._modelName; | ||
} | ||
} | ||
} | ||
} | ||
// Ensure index are created | ||
if (_this5._indexes[collection] && _this5._indexes[collection].length > 0) { | ||
yield _this5.createIndexes(tenant, collection, _this5._indexes[collection]); | ||
} | ||
// Acquiring db instance | ||
@@ -764,6 +884,5 @@ const conn = yield _this5._connectionManager.acquire(); | ||
try { | ||
// Ensure there are no indexes yet | ||
yield to(conn.db(tenant, _this5._tenantsOptions).collection(collection).dropIndexes()); | ||
var _ref14 = yield to(conn.db(tenant, _this5._tenantsOptions).collection(collection).createIndexes(fields)), | ||
// Get and run the Cursor | ||
var _ref14 = yield to(conn.db(tenant, _this5._tenantsOptions).collection(collection).aggregate(pipeline, {}) //{} = aggregateOptions | ||
.toArray()), | ||
_ref15 = _slicedToArray(_ref14, 2); | ||
@@ -774,2 +893,10 @@ | ||
// Run the cursor | ||
// http://mongodb.github.io/node-mongodb-native/3.0/api/Cursor.html | ||
/*const [error, result] = await to( | ||
cursor | ||
.limit(findOptions.limit) | ||
.project(findOptions.projection) | ||
.toArray(), | ||
);*/ | ||
@@ -779,8 +906,27 @@ if (error) { | ||
} else { | ||
// Set Indexes for this tenant and this collection are already set | ||
_this5.tenants[tenant] = _extends({}, _this5.tenants[tenant], { | ||
[collection]: true | ||
}); | ||
resolve(result); | ||
if (result) { | ||
if (aggregateOptions.moltyClass) { | ||
/* const Document = require('../document'); | ||
let docs = []; | ||
result.forEach(doc => { | ||
docs.push( | ||
new Document( | ||
doc, | ||
model._preHooks, | ||
model._postHooks, | ||
model._methods, | ||
model._schemaOptions, | ||
model._modelName, | ||
model._discriminator, | ||
), | ||
); | ||
}); | ||
resolve(docs);*/ | ||
resolve(result); | ||
} else { | ||
resolve(result); | ||
} | ||
} else { | ||
resolve(result); | ||
} | ||
} | ||
@@ -802,3 +948,3 @@ } catch (error) { | ||
/** | ||
* dropDatabase(): Drop a database, removing it permanently from the server. | ||
* createIndexes(): Creates indexes on the db and collection collection | ||
* | ||
@@ -811,3 +957,3 @@ * @param {String} tenant | ||
*/ | ||
dropDatabase(tenant) { | ||
createIndexes(tenant, collection, fields) { | ||
var _this6 = this; | ||
@@ -817,3 +963,8 @@ | ||
if (!tenant && typeof tenant != 'string') throw new Error('Should specify the tenant name (String), got: ' + tenant); | ||
if (!collection && typeof collection != 'string') throw new Error('Should specify the collection name (String), got: ' + collection); | ||
if (!fields && typeof fields != 'object') throw new Error('Should specify the field name (Array), got: ' + fields); | ||
// Checking if indexes are already set for this tenant and this collection | ||
if (_this6.tenants[tenant] && _this6.tenants[tenant][collection]) return; | ||
// Acquiring db instance | ||
@@ -825,3 +976,6 @@ const conn = yield _this6._connectionManager.acquire(); | ||
try { | ||
var _ref17 = yield to(conn.db(tenant, _this6._tenantsOptions).dropDatabase()), | ||
// Ensure there are no indexes yet | ||
yield to(conn.db(tenant, _this6._tenantsOptions).collection(collection).dropIndexes()); | ||
var _ref17 = yield to(conn.db(tenant, _this6._tenantsOptions).collection(collection).createIndexes(fields)), | ||
_ref18 = _slicedToArray(_ref17, 2); | ||
@@ -836,2 +990,7 @@ | ||
} else { | ||
// Set Indexes for this tenant and this collection are already set | ||
_this6.tenants[tenant] = _extends({}, _this6.tenants[tenant], { | ||
[collection]: true | ||
}); | ||
resolve(result); | ||
@@ -852,4 +1011,51 @@ } | ||
} | ||
/** | ||
* dropDatabase(): Drop a database, removing it permanently from the server. | ||
* | ||
* @param {String} tenant | ||
* @param {String} collection | ||
* @param [{String}] fields | ||
* | ||
* @returns {Promise} | ||
*/ | ||
dropDatabase(tenant) { | ||
var _this7 = this; | ||
return _asyncToGenerator(function* () { | ||
if (!tenant && typeof tenant != 'string') throw new Error('Should specify the tenant name (String), got: ' + tenant); | ||
// Acquiring db instance | ||
const conn = yield _this7._connectionManager.acquire(); | ||
return new Promise((() => { | ||
var _ref19 = _asyncToGenerator(function* (resolve, reject) { | ||
try { | ||
var _ref20 = yield to(conn.db(tenant, _this7._tenantsOptions).dropDatabase()), | ||
_ref21 = _slicedToArray(_ref20, 2); | ||
const error = _ref21[0], | ||
result = _ref21[1]; | ||
if (error) { | ||
reject(error); | ||
} else { | ||
resolve(result); | ||
} | ||
} catch (error) { | ||
reject(error); | ||
} finally { | ||
return yield _this7._connectionManager.release(conn); | ||
} | ||
}); | ||
return function (_x13, _x14) { | ||
return _ref19.apply(this, arguments); | ||
}; | ||
})()); | ||
})(); | ||
} | ||
} | ||
module.exports = new MongoClient(); |
@@ -156,12 +156,2 @@ 'use strict'; | ||
let schema = { | ||
methods: {}, | ||
_preHooks: {}, | ||
_postHooks: {}, | ||
_schema: {}, | ||
_options: {} | ||
}; | ||
schema._options = schemaDiscriminator._options; | ||
if (merge && merge.indexOf('methods') >= 0) { | ||
@@ -172,17 +162,18 @@ Object.keys(schemaDiscriminator.methods).forEach(key => { | ||
schema.methods = Object.assign({}, this._methods, schemaDiscriminator.methods); | ||
Object.assign(schemaDiscriminator.methods, this._methods); | ||
} | ||
if (merge && merge.indexOf('preHooks') >= 0) { | ||
schema._preHooks = this._preHooks.concat(schemaDiscriminator._preHooks); | ||
schemaDiscriminator._preHooks = this._preHooks.concat(schemaDiscriminator._preHooks); | ||
} | ||
if (merge && merge.indexOf('postHooks') >= 0) { | ||
schema._postHooks = this._postHooks.concat(schemaDiscriminator._postHooks); | ||
schemaDiscriminator._postHooks = this._postHooks.concat(schemaDiscriminator._postHooks); | ||
} | ||
schema._schema = Object.assign({}, this._schemaNormalized, schemaDiscriminator._schema); | ||
Object.assign(schemaDiscriminator._schema, this._schemaNormalized); | ||
this._validateDiscriminatorName(childDiscriminatorKey, schema._schema); | ||
this._validateDiscriminatorName(childDiscriminatorKey, schemaDiscriminator._schema); | ||
const discriminatorModel = new Model(schema, this._modelName, discriminatorModelName); | ||
const discriminatorModel = new Model(schemaDiscriminator, this._modelName, discriminatorModelName); | ||
@@ -189,0 +180,0 @@ if (this._childsModels[discriminatorModelName]) throw new Error('There is already model discriminator with the same name: ' + discriminatorModelName); |
{ | ||
"name": "moltyjs", | ||
"version": "0.5.1", | ||
"version": "0.6.0", | ||
"description": "A tiny ODM for MongoDB with multy tenancy support.", | ||
@@ -9,2 +9,3 @@ "main": "lib/index.js", | ||
"build": "babel src -d lib", | ||
"test:coverage": "\"nyc --reporter=html --reporter=text npm run test\"", | ||
"test:watch": "\"npm run test -- --watch\"", | ||
@@ -42,3 +43,3 @@ "test": "mocha \"./{,!(node_modules)/**/}*.test.js\" --require babel-core/register --require babel-polyfill --recursive --reporter spec --timeout 5000 --exit", | ||
"np": "^2.18.2", | ||
"npm": "^5.6.0", | ||
"nyc": "^11.4.1", | ||
"watch": "^1.0.2" | ||
@@ -45,0 +46,0 @@ }, |
@@ -94,2 +94,6 @@ [![npm version](https://badge.fury.io/js/moltyjs.svg)](https://badge.fury.io/js/moltyjs) | ||
}, | ||
tests: { | ||
type: [Schema.types().ObjectId], | ||
ref: 'ReferenceSchema', | ||
}, | ||
}, | ||
@@ -441,2 +445,11 @@ { | ||
* {String} `tanant` Tenant name | ||
* {String} `collection` Collection name | ||
* {Object} `query` Query object | ||
* {Object} `options` Optional settings | ||
* {Boolean} `moltyClass` (true by default) True if you want the results as MoltyJs Document class | ||
instead of MongoDB Document | ||
* {Number} `limit` (0 by default: no limit) Limit the results to the amount specified | ||
* {Object} `projection` (null by default) Create a projection of a field, the projection document limits the fields to return for all matching documents | ||
```javascript | ||
@@ -456,2 +469,45 @@ const resUpdate = await connection.updateOne( | ||
## Aggregate | ||
Aggregation operations group values from multiple documents together, and can perform a variety of operations on the grouped data to return a single result. | ||
### `aggregate(tenant, collection, pipeline = [], options = {}) {Promise}` | ||
* {String} `tanant` Tenant name | ||
* {String} `collection` Collection name | ||
* {Object[]} `pipeline` Array containing all the aggregation framework commands for the execution. | ||
* Pipeline stages supported [(use the same syntax as MongoDB Native Driver)](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/): | ||
* $match | ||
* $lookup | ||
* $project | ||
* {Object} `options` Optional settings | ||
* "There is no options supported yet" | ||
```javascript | ||
const pipeline = [ | ||
{ | ||
$match: { | ||
_id: newDoc._data._id, | ||
}, | ||
}, | ||
{ | ||
$lookup: { | ||
from: 'ReferenceSchema', | ||
localField: 'tests', | ||
foreignField: '_id', | ||
as: 'models', | ||
}, | ||
}, | ||
{ | ||
$project: { | ||
_id: 0, | ||
tests: 1, | ||
}, | ||
}, | ||
]; | ||
const aggregate = await connection.aggregate('tenant_test', 'TestModel', pipeline, {}); | ||
// {Result} || Error | ||
``` | ||
Updating a document support all the [update operators](https://docs.mongodb.com/v3.4/reference/operator/update/) from MongoDB |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
297128
23
2021
511
1