feathers-sequelize
Advanced tools
Comparing version 3.1.3 to 4.0.0
# Change Log | ||
## [v3.1.3](https://github.com/feathersjs-ecosystem/feathers-sequelize/tree/v3.1.3) (2018-10-29) | ||
[Full Changelog](https://github.com/feathersjs-ecosystem/feathers-sequelize/compare/v3.1.2...v3.1.3) | ||
**Closed issues:** | ||
- Include hook doesn't work on create [\#242](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/242) | ||
- Warning messages when using "sequelize db:migrate" [\#240](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/240) | ||
- Extending service class fails when transpiling to ES5 [\#237](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/237) | ||
- Example in readme.md doesn't work [\#236](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/236) | ||
- How to use raw where clause [\#233](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/233) | ||
- Associations on Create [\#230](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/230) | ||
- Valid password characters can break the connection string [\#229](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/229) | ||
- Does Feathers-Sequalize support class and instance methods? [\#225](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/225) | ||
- Find & include data structure shape [\#224](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/224) | ||
- Fix bug Pg-Native [\#222](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/222) | ||
- Connection pool [\#221](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/221) | ||
- Question: Feathers Sequelize raw query and feathers service without using model. [\#215](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/215) | ||
- Sub include relations with Sequelize: Query erro [\#203](https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/203) | ||
**Merged pull requests:** | ||
- use transactions in `update`, related to \#188 [\#243](https://github.com/feathersjs-ecosystem/feathers-sequelize/pull/243) ([jiangts](https://github.com/jiangts)) | ||
- Update README to fix \#240 [\#241](https://github.com/feathersjs-ecosystem/feathers-sequelize/pull/241) ([leedongwei](https://github.com/leedongwei)) | ||
- minor typos [\#232](https://github.com/feathersjs-ecosystem/feathers-sequelize/pull/232) ([Strongbyte-ES](https://github.com/Strongbyte-ES)) | ||
- Update README: Latest version requires mysql2 [\#219](https://github.com/feathersjs-ecosystem/feathers-sequelize/pull/219) ([ricardopolo](https://github.com/ricardopolo)) | ||
## [v3.1.2](https://github.com/feathersjs-ecosystem/feathers-sequelize/tree/v3.1.2) (2018-06-07) | ||
@@ -4,0 +30,0 @@ [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-sequelize/compare/v3.1.1...v3.1.2) |
228
lib/index.js
@@ -1,23 +0,40 @@ | ||
const omit = require('lodash.omit'); | ||
const Proto = require('uberproto'); | ||
const errors = require('@feathersjs/errors'); | ||
const { select, filterQuery } = require('@feathersjs/commons'); | ||
const { _ } = require('@feathersjs/commons'); | ||
const { select, AdapterService } = require('@feathersjs/adapter-commons'); | ||
const utils = require('./utils'); | ||
const defaultOperators = Op => { | ||
return { | ||
$eq: Op.eq, | ||
$ne: Op.ne, | ||
$gte: Op.gte, | ||
$gt: Op.gt, | ||
$lte: Op.lte, | ||
$lt: Op.lt, | ||
$in: Op.in, | ||
$nin: Op.notIn, | ||
$like: Op.like, | ||
$notLike: Op.notLike, | ||
$iLike: Op.ilike, | ||
$notILike: Op.notILike, | ||
$or: Op.or, | ||
$and: Op.and | ||
}; | ||
}; | ||
class Service { | ||
class Service extends AdapterService { | ||
constructor (options) { | ||
if (!options) { | ||
throw new Error('Sequelize options have to be provided'); | ||
} | ||
if (!options.Model) { | ||
throw new Error('You must provide a Sequelize Model'); | ||
} | ||
this.paginate = options.paginate || false; | ||
this.options = options; | ||
this.Model = options.Model; | ||
this.id = options.id || 'id'; | ||
this.events = options.events; | ||
const defaultOps = defaultOperators(options.Model.sequelize.Op); | ||
const operators = Object.assign(defaultOps, options.operators); | ||
const whitelist = Object.keys(operators).concat(options.whitelist || []); | ||
super(Object.assign({ | ||
id: 'id', | ||
operators, | ||
whitelist | ||
}, options)); | ||
this.raw = options.raw !== false; | ||
@@ -27,3 +44,3 @@ } | ||
getModel (params) { | ||
return this.Model; | ||
return this.options.Model; | ||
} | ||
@@ -38,9 +55,43 @@ | ||
extend (obj) { | ||
return Proto.extend(obj, this); | ||
filterQuery (params) { | ||
const filtered = super.filterQuery(params); | ||
const operators = this.options.operators; | ||
const convertOperators = query => { | ||
if (Array.isArray(query)) { | ||
return query.map(convertOperators); | ||
} | ||
if (!_.isObject(query)) { | ||
return query; | ||
} | ||
return Object.keys(query).reduce((result, prop) => { | ||
const value = query[prop]; | ||
const key = operators[prop] ? operators[prop] : prop; | ||
result[key] = convertOperators(value); | ||
return result; | ||
}, {}); | ||
}; | ||
filtered.query = convertOperators(filtered.query); | ||
return filtered; | ||
} | ||
_find (params, getFilter = filterQuery, paginate) { | ||
const { filters, query } = getFilter(params.query || {}); | ||
const where = utils.getWhere(query); | ||
// returns either the model intance for an id or all unpaginated | ||
// items for `params` if id is null | ||
_getOrFind (id, params) { | ||
if (id === null) { | ||
return this._find(Object.assign(params, { | ||
paginate: false | ||
})); | ||
} | ||
return this._get(id, params); | ||
} | ||
_find (params) { | ||
const { filters, query: where, paginate } = this.filterQuery(params); | ||
const order = utils.getOrder(filters.$sort); | ||
@@ -75,3 +126,3 @@ | ||
if (paginate) { | ||
if (paginate && paginate.default) { | ||
return Model.findAndCountAll(q).then(result => { | ||
@@ -85,24 +136,9 @@ return { | ||
}).catch(utils.errorHandler); | ||
} else { | ||
return Model.findAll(q).then(result => { | ||
return { | ||
data: result | ||
}; | ||
}).catch(utils.errorHandler); | ||
} | ||
} | ||
find (params) { | ||
const paginate = (params && typeof params.paginate !== 'undefined') ? params.paginate : this.paginate; | ||
const result = this._find(params, where => filterQuery(where, paginate), paginate); | ||
if (!paginate.default) { | ||
return result.then(page => page.data); | ||
} | ||
return result; | ||
return Model.findAll(q).catch(utils.errorHandler); | ||
} | ||
_get (id, params) { | ||
const where = utils.getWhere(params.query); | ||
const { query: where } = this.filterQuery(params); | ||
@@ -112,3 +148,3 @@ // Attach 'where' constraints, if any were used. | ||
raw: this.raw, | ||
where: Object.assign({[this.id]: id}, where) | ||
where: Object.assign({ [this.id]: id }, where) | ||
}, params.sequelize); | ||
@@ -126,25 +162,7 @@ | ||
return result[0]; | ||
}) | ||
.then(select(params, this.id)) | ||
.catch(error => { | ||
throw new errors.NotFound(`No record found for id '${id}'`, error); | ||
}); | ||
}).then(select(params, this.id)).catch(utils.errorHandler); | ||
} | ||
// returns either the model intance for an id or all unpaginated | ||
// items for `params` if id is null | ||
_getOrFind (id, params) { | ||
if (id === null) { | ||
return this._find(params).then(page => page.data); | ||
} | ||
return this._get(id, params); | ||
} | ||
get (id, params) { | ||
return this._get(id, params).then(select(params, this.id)); | ||
} | ||
create (data, params) { | ||
const options = Object.assign({raw: this.raw}, params.sequelize); | ||
_create (data, params) { | ||
const options = Object.assign({ raw: this.raw }, params.sequelize); | ||
// Model.create's `raw` option is different from other methods. | ||
@@ -155,3 +173,3 @@ // In order to use `raw` consistently to serialize the result, | ||
const ignoreSetters = Boolean(options.ignoreSetters); | ||
const createOptions = Object.assign({}, options, {raw: ignoreSetters}); | ||
const createOptions = Object.assign({}, options, { raw: ignoreSetters }); | ||
const isArray = Array.isArray(data); | ||
@@ -180,5 +198,5 @@ let promise; | ||
patch (id, data, params) { | ||
const where = Object.assign({}, filterQuery(params.query || {}).query); | ||
const mapIds = page => page.data.map(current => current[this.id]); | ||
_patch (id, data, params) { | ||
const where = Object.assign({}, this.filterQuery(params).query); | ||
const mapIds = data => data.map(current => current[this.id]); | ||
@@ -189,3 +207,3 @@ if (id !== null) { | ||
const options = Object.assign({raw: this.raw}, params.sequelize, { where }); | ||
const options = Object.assign({ raw: this.raw }, params.sequelize, { where }); | ||
@@ -199,3 +217,3 @@ let Model = this.applyScope(params); | ||
return this._getOrFind(id, params) | ||
.then(results => this.getModel(params).update(omit(data, this.id), options)) | ||
.then(results => this.getModel(params).update(_.omit(data, this.id), options)) | ||
.then(results => { | ||
@@ -222,50 +240,37 @@ if (id === null) { | ||
return ids | ||
.then(idList => { | ||
// Create a new query that re-queries all ids that | ||
// were originally changed | ||
const findParams = Object.assign({}, params, { | ||
query: { [this.id]: { $in: idList } } | ||
return ids.then(idList => { | ||
// Create a new query that re-queries all ids that | ||
// were originally changed | ||
const findParams = Object.assign({}, params, Object.assign({}, { | ||
query: Object.assign({ [this.id]: { $in: idList } }, params.query) | ||
})); | ||
return Model.update(_.omit(data, this.id), options) | ||
.then(() => { | ||
if (params.$returning !== false) { | ||
return this._getOrFind(id, findParams); | ||
} else { | ||
return Promise.resolve([]); | ||
} | ||
}); | ||
return Model.update(omit(data, this.id), options) | ||
.then(() => { | ||
if (params.$returning !== false) { | ||
return this._getOrFind(id, findParams); | ||
} else { | ||
return Promise.resolve([]); | ||
} | ||
}); | ||
}) | ||
.then(select(params, this.id)) | ||
.catch(utils.errorHandler); | ||
}).then(select(params, this.id)).catch(utils.errorHandler); | ||
} | ||
update (id, data, params) { | ||
const where = Object.assign({}, filterQuery(params.query || {}).query); | ||
_update (id, data, params) { | ||
const where = Object.assign({}, this.filterQuery(params).query); | ||
const options = Object.assign({ raw: this.raw }, params.sequelize); | ||
if (Array.isArray(data)) { | ||
return Promise.reject(new errors.BadRequest('Not replacing multiple records. Did you mean `patch`?')); | ||
} | ||
// Force the {raw: false} option as the instance is needed to properly | ||
// update | ||
const updateOptions = Object.assign({ raw: false }, params.sequelize); | ||
const updateOptions = Object.assign({}, params.sequelize, { raw: false }); | ||
const getOptions = Object.assign({}, params, { query: where, sequelize: { raw: false } }); | ||
return this._get(id, { sequelize: { raw: false }, query: where }).then(instance => { | ||
if (!instance) { | ||
throw new errors.NotFound(`No record found for id '${id}'`); | ||
} | ||
return this._get(id, getOptions).then(instance => { | ||
const copy = Object.keys(instance.toJSON()).reduce((result, key) => { | ||
result[key] = typeof data[key] === 'undefined' ? null : data[key]; | ||
let copy = {}; | ||
Object.keys(instance.toJSON()).forEach(key => { | ||
if (typeof data[key] === 'undefined') { | ||
copy[key] = null; | ||
} else { | ||
copy[key] = data[key]; | ||
} | ||
}); | ||
return result; | ||
}, {}); | ||
return instance.update(copy, updateOptions).then(() => this._get(id, {sequelize: options})); | ||
return instance.update(copy, updateOptions).then(() => this._get(id, { sequelize: options })); | ||
}) | ||
@@ -276,5 +281,5 @@ .then(select(params, this.id)) | ||
remove (id, params) { | ||
_remove (id, params) { | ||
const opts = Object.assign({ raw: this.raw }, params); | ||
const where = Object.assign({}, filterQuery(params.query || {}).query); | ||
const where = Object.assign({}, this.filterQuery(params).query); | ||
@@ -285,3 +290,3 @@ if (id !== null) { | ||
const options = Object.assign({}, params.sequelize, { where }); | ||
const options = Object.assign({}, { where }, params.sequelize); | ||
@@ -304,12 +309,9 @@ let Model = this.applyScope(params); | ||
function init (options) { | ||
return new Service(options); | ||
} | ||
const init = options => new Service(options); | ||
module.exports = init; | ||
// Exposed Modules | ||
Object.assign(module.exports, { | ||
module.exports = Object.assign(init, { | ||
default: init, | ||
ERROR: utils.ERROR, | ||
Service | ||
}); |
const errors = require('@feathersjs/errors'); | ||
const ERROR = Symbol('feathers-sequelize/error'); | ||
const wrap = (error, original) => Object.assign(error, { [ERROR]: original }); | ||
exports.errorHandler = function errorHandler (error) { | ||
let feathersError = error; | ||
exports.ERROR = ERROR; | ||
if (error.name) { | ||
switch (error.name) { | ||
exports.errorHandler = error => { | ||
const { name, message } = error; | ||
if (name) { | ||
switch (name) { | ||
case 'SequelizeValidationError': | ||
@@ -13,53 +17,23 @@ case 'SequelizeUniqueConstraintError': | ||
case 'SequelizeInvalidConnectionError': | ||
feathersError = new errors.BadRequest(error); | ||
break; | ||
throw wrap(new errors.BadRequest(message, { errors: error.errors }), error); | ||
case 'SequelizeTimeoutError': | ||
case 'SequelizeConnectionTimedOutError': | ||
feathersError = new errors.Timeout(error); | ||
break; | ||
throw wrap(new errors.Timeout(message), error); | ||
case 'SequelizeConnectionRefusedError': | ||
case 'SequelizeAccessDeniedError': | ||
feathersError = new errors.Forbidden(error); | ||
break; | ||
throw wrap(new errors.Forbidden(message), error); | ||
case 'SequelizeHostNotReachableError': | ||
feathersError = new errors.Unavailable(error); | ||
break; | ||
throw wrap(new errors.Unavailable(message), error); | ||
case 'SequelizeHostNotFoundError': | ||
feathersError = new errors.NotFound(error); | ||
break; | ||
throw wrap(new errors.NotFound(message), error); | ||
} | ||
} | ||
throw feathersError; | ||
throw error; | ||
}; | ||
exports.getOrder = function getOrder (sort = {}) { | ||
let order = []; | ||
exports.getOrder = (sort = {}) => Object.keys(sort).reduce((order, name) => { | ||
order.push([ name, parseInt(sort[name], 10) === 1 ? 'ASC' : 'DESC' ]); | ||
Object.keys(sort).forEach(name => | ||
order.push([ name, parseInt(sort[name], 10) === 1 ? 'ASC' : 'DESC' ])); | ||
return order; | ||
}; | ||
exports.getWhere = function getWhere (query) { | ||
let where = Object.assign({}, query); | ||
if (where.$select) { | ||
delete where.$select; | ||
} | ||
Object.keys(where).forEach(prop => { | ||
let value = where[prop]; | ||
if (value && value.$nin) { | ||
value = Object.assign({}, value); | ||
value.$notIn = value.$nin; | ||
delete value.$nin; | ||
where[prop] = value; | ||
} | ||
}); | ||
return where; | ||
}; | ||
}, []); |
{ | ||
"name": "feathers-sequelize", | ||
"description": "A service adapter for Sequelize an SQL ORM", | ||
"version": "3.1.3", | ||
"version": "4.0.0", | ||
"homepage": "https://github.com/feathersjs-ecosystem/feathers-sequelize", | ||
@@ -60,22 +60,21 @@ "main": "lib/", | ||
"dependencies": { | ||
"@feathersjs/commons": "^1.3.0", | ||
"@feathersjs/errors": "^3.2.0", | ||
"lodash.omit": "^4.3.0", | ||
"uberproto": "^2.0.0" | ||
"@feathersjs/adapter-commons": "^1.0.5", | ||
"@feathersjs/commons": "^4.0.0", | ||
"@feathersjs/errors": "^3.3.5" | ||
}, | ||
"devDependencies": { | ||
"@feathersjs/express": "^1.1.2", | ||
"@feathersjs/feathers": "^3.0.1", | ||
"body-parser": "^1.14.1", | ||
"chai": "^4.0.0", | ||
"feathers-service-tests": "^0.10.0", | ||
"@feathersjs/express": "^1.3.0", | ||
"@feathersjs/feathers": "^3.3.0", | ||
"body-parser": "^1.18.3", | ||
"chai": "^4.2.0", | ||
"feathers-service-tests": "^0.10.2", | ||
"istanbul": "^1.1.0-alpha.1", | ||
"mocha": "^5.0.0", | ||
"mysql2": "^1.4.2", | ||
"pg": "^7.3.0", | ||
"mocha": "^5.2.0", | ||
"mysql2": "^1.6.4", | ||
"pg": "^7.7.1", | ||
"pg-hstore": "^2.3.2", | ||
"semistandard": "^12.0.0", | ||
"sequelize": "^4.36.1", | ||
"sqlite3": "^4.0.0" | ||
"semistandard": "^13.0.1", | ||
"sequelize": "^4.42.0", | ||
"sqlite3": "^4.0.4" | ||
} | ||
} |
@@ -50,2 +50,5 @@ # feathers-sequelize | ||
- `paginate` (*optional*) - A [pagination object](https://docs.feathersjs.com/api/databases/common.html#pagination) containing a `default` and `max` page size | ||
- `multi` (*optional*) - Allow `create` with arrays and `update` and `remove` with `id` `null` to change multiple items. Can be `true` for all methods or an array of allowed methods (e.g. `[ 'remove', 'create' ]`) | ||
- `operators` (*optional*) - A mapping from query syntax property names to to [Sequelize secure operators](http://docs.sequelizejs.com/manual/tutorial/querying.html) | ||
- `whitelist` (*optional*) - A list of additional query parameters to allow (e..g `[ '$regex', '$geoNear' ]`). Default is the supported `operators` | ||
@@ -74,2 +77,23 @@ ### params.sequelize | ||
### operators | ||
Sequelize deprecated string based operators a while ago for security reasons. Starting at version 4.0.0 `feathers-sequelize` converts queries securely. If you want to support additional Sequelize operators, the `operators` service option can contain a mapping from query parameter name to Sequelize operator. By default supported are: | ||
``` | ||
'$eq', | ||
'$ne', | ||
'$gte', | ||
'$gt', | ||
'$lte', | ||
'$lt', | ||
'$in', | ||
'$nin', | ||
'$like', | ||
'$notLike', | ||
'$iLike', | ||
'$notILike', | ||
'$or', | ||
'$and' | ||
``` | ||
## Sequelize `raw` queries | ||
@@ -485,6 +509,32 @@ | ||
### Migrating | ||
`feathers-sequelize` 4.0.0 comes with important security and usability updates. | ||
> __Important:__ For general migration information to the new database adapter functionality see [crow.docs.feathersjs.com/migrating.html#database-adapters](https://crow.docs.feathersjs.com/migrating.html#database-adapters). | ||
The following breaking changes have been introduced: | ||
- All methods now take `params.sequelize` into account | ||
- All methods allow additional query parameters | ||
- Multiple updates are disabled by default (see the `multi` option) | ||
- Upgraded to secure Sequelize operators (see the [operators](#operators) option) | ||
- Errors no longer contain Sequelize specific information. The original Sequelize error can be retrieved on the server via: | ||
```js | ||
const { ERROR } = require('feathers-sequelize'); | ||
try { | ||
await sequelizeService.doSomethign(); | ||
} catch(error) { | ||
// error is a FeathersError | ||
// Safely retrieve the Sequelize error | ||
const sequelizeError = error[ERROR]; | ||
} | ||
``` | ||
## License | ||
Copyright (c) 2017 | ||
Copyright (c) 2019 | ||
Licensed under the [MIT license](LICENSE). |
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
3
538
73990
10
358
+ Added@feathersjs/adapter-commons@1.0.7(transitive)
+ Added@feathersjs/commons@4.5.16(transitive)
- Removedlodash.omit@^4.3.0
- Removeduberproto@^2.0.0
- Removed@feathersjs/commons@1.4.4(transitive)
- Removedlodash.omit@4.5.0(transitive)
- Removeduberproto@2.0.6(transitive)
Updated@feathersjs/commons@^4.0.0
Updated@feathersjs/errors@^3.3.5