diff --git a/.jshintrc b/.jshintrc index f9d9a89..5778044 100644 --- a/.jshintrc +++ b/.jshintrc @@ -2,6 +2,7 @@ "node": false, "browser": true, "esnext": true, + "asi": true, "bitwise": true, "camelcase": true, "curly": true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 48803a2..a28d29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +##### 0.11.0 - 08 October 2015 + +- #18, #21 - Support filtering across relations (apply joins / fix column name) by @techniq +- #19, #20 - Use co-mocha for tests to simplify async flow. by @techniq +- Project now enforces JS Standard Style + ##### 0.10.0 - 19 September 2015 - #15, #16 - Removed the various database libs from peerDependencies. diff --git a/Gruntfile.js b/Gruntfile.js index b0a0355..869d60c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -71,10 +71,27 @@ module.exports = function (grunt) { } }); + grunt.registerTask('standard', function () { + var child_process = require('child_process'); + var done = this.async(); + grunt.log.writeln('Linting for correcting formatting...'); + child_process.exec('node node_modules/standard/bin/cmd.js --parser babel-eslint src/index.js', function (err, stdout) { + console.log(stdout); + if (err) { + grunt.log.writeln('Failed due to ' + (stdout.split('\n').length - 2) + ' lint errors!'); + done(err); + } else { + grunt.log.writeln('Done linting.'); + done(); + } + }); + }); + grunt.registerTask('n', ['mochaTest']); grunt.registerTask('test', ['build', 'n']); grunt.registerTask('build', [ + 'standard', 'webpack' ]); grunt.registerTask('go', ['build', 'watch:dist']); diff --git a/dist/js-data-sql.js b/dist/js-data-sql.js index dda4228..9fa399c 100644 --- a/dist/js-data-sql.js +++ b/dist/js-data-sql.js @@ -49,12 +49,14 @@ module.exports = value: true }); - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + 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 _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + var knex = __webpack_require__(1); var JSData = __webpack_require__(2); var map = __webpack_require__(3); @@ -65,13 +67,19 @@ module.exports = var reserved = ['orderBy', 'sort', 'limit', 'offset', 'skip', 'where']; + function getTable(resourceConfig) { + return resourceConfig.table || underscore(resourceConfig.name); + } + function filterQuery(resourceConfig, params) { - var query = this.query.select('*').from(resourceConfig.table || underscore(resourceConfig.name)); + var query = this.query.select('*').from(getTable(resourceConfig)); params = params || {}; params.where = params.where || {}; params.orderBy = params.orderBy || params.sort; params.skip = params.skip || params.offset; + var joinedTables = []; + DSUtils.forEach(DSUtils.keys(params), function (k) { var v = params[k]; if (!DSUtils.contains(reserved, k)) { @@ -93,7 +101,51 @@ module.exports = '==': criteria }; } + DSUtils.forOwn(criteria, function (v, op) { + if (DSUtils.contains(field, '.')) { + (function () { + var parts = field.split('.'); + var localResourceConfig = resourceConfig; + + var relationPath = []; + + var _loop = function () { + var relationName = parts.shift(); + var relationResourceConfig = resourceConfig.getResource(relationName); + relationPath.push(relationName); + + if (!joinedTables.some(function (t) { + return t === relationPath.join('.'); + })) { + var _localResourceConfig$relationList$filter = localResourceConfig.relationList.filter(function (r) { + return r.relation === relationName; + }); + + var _localResourceConfig$relationList$filter2 = _slicedToArray(_localResourceConfig$relationList$filter, 1); + + var relation = _localResourceConfig$relationList$filter2[0]; + + var table = getTable(localResourceConfig); + var localId = table + '.' + relation.localKey; + + var relationTable = getTable(relationResourceConfig); + var foreignId = relationTable + '.' + relationResourceConfig.idAttribute; + + query = query.join(relationTable, localId, foreignId); + joinedTables.push(relationPath.join('.')); + } + localResourceConfig = relationResourceConfig; + }; + + while (parts.length >= 2) { + _loop(); + } + + field = getTable(localResourceConfig) + '.' + parts[0]; + })(); + } + if (op === '==' || op === '===') { query = query.where(field, v); } else if (op === '!=' || op === '!==') { @@ -106,10 +158,10 @@ module.exports = query = query.where(field, '<', v); } else if (op === '<=') { query = query.where(field, '<=', v); - //} else if (op === 'isectEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0); - //} else if (op === 'isectNotEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0); + // } else if (op === 'isectEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === 'isectNotEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) } else if (op === 'in') { query = query.where(field, 'in', v); } else if (op === 'notIn') { @@ -128,10 +180,10 @@ module.exports = query = query.orWhere(field, '<', v); } else if (op === '|<=') { query = query.orWhere(field, '<=', v); - //} else if (op === '|isectEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0); - //} else if (op === '|isectNotEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0); + // } else if (op === '|isectEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === '|isectNotEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) } else if (op === '|in') { query = query.orWhere(field, 'in', v); } else if (op === '|notIn') { @@ -166,6 +218,143 @@ module.exports = return query; } + function loadWithRelations(items, resourceConfig, options) { + var _this = this; + + var tasks = []; + var instance = Array.isArray(items) ? null : items; + + DSUtils.forEach(resourceConfig.relationList, function (def) { + var relationName = def.relation; + var relationDef = resourceConfig.getResource(relationName); + + var containedName = null; + if (DSUtils.contains(options['with'], relationName)) { + containedName = relationName; + } else if (DSUtils.contains(options['with'], def.localField)) { + containedName = def.localField; + } else { + return; + } + + var __options = DSUtils.deepMixIn({}, options.orig ? options.orig() : options); + + // Filter to only properties under current relation + __options['with'] = options['with'].filter(function (relation) { + return relation !== containedName && relation.indexOf(containedName) === 0 && relation.length >= containedName.length && relation[containedName.length] === '.'; + }).map(function (relation) { + return relation.substr(containedName.length + 1); + }); + + var task = undefined; + + if ((def.type === 'hasOne' || def.type === 'hasMany') && def.foreignKey) { + var foreignKeyFilter = undefined; + if (instance) { + foreignKeyFilter = { '==': instance[resourceConfig.idAttribute] }; + } else { + foreignKeyFilter = { 'in': map(items, function (item) { + return item[resourceConfig.idAttribute]; + }) }; + } + task = _this.findAll(resourceConfig.getResource(relationName), { + where: _defineProperty({}, def.foreignKey, foreignKeyFilter) + }, __options).then(function (relatedItems) { + if (instance) { + if (def.type === 'hasOne' && relatedItems.length) { + instance[def.localField] = relatedItems[0]; + } else { + instance[def.localField] = relatedItems; + } + } else { + DSUtils.forEach(items, function (item) { + var attached = relatedItems.filter(function (ri) { + return ri[def.foreignKey] === item[resourceConfig.idAttribute]; + }); + if (def.type === 'hasOne' && attached.length) { + item[def.localField] = attached[0]; + } else { + item[def.localField] = attached; + } + }); + } + + return relatedItems; + }); + } else if (def.type === 'hasMany' && def.localKeys) { + (function () { + // TODO: Write test for with: hasMany property with localKeys + var localKeys = []; + + if (instance) { + var itemKeys = instance[def.localKeys] || []; + itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys); + localKeys = localKeys.concat(itemKeys || []); + } else { + DSUtils.forEach(items, function (item) { + var itemKeys = item[def.localKeys] || []; + itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys); + localKeys = localKeys.concat(itemKeys || []); + }); + } + + task = _this.findAll(resourceConfig.getResource(relationName), { + where: _defineProperty({}, relationDef.idAttribute, { + 'in': DSUtils.filter(unique(localKeys), function (x) { + return x; + }) + }) + }, __options).then(function (relatedItems) { + if (instance) { + instance[def.localField] = relatedItems; + } else { + DSUtils.forEach(items, function (item) { + var itemKeys = item[def.localKeys] || []; + var attached = relatedItems.filter(function (ri) { + return itemKeys && DSUtils.contains(itemKeys, ri[relationDef.idAttribute]); + }); + item[def.localField] = attached; + }); + } + + return relatedItems; + }); + })(); + } else if (def.type === 'belongsTo' || def.type === 'hasOne' && def.localKey) { + if (instance) { + task = _this.find(resourceConfig.getResource(relationName), DSUtils.get(instance, def.localKey), __options).then(function (relatedItem) { + instance[def.localField] = relatedItem; + return relatedItem; + }); + } else { + task = _this.findAll(resourceConfig.getResource(relationName), { + where: _defineProperty({}, relationDef.idAttribute, { + 'in': DSUtils.filter(map(items, function (item) { + return DSUtils.get(item, def.localKey); + }), function (x) { + return x; + }) + }) + }, __options).then(function (relatedItems) { + DSUtils.forEach(items, function (item) { + DSUtils.forEach(relatedItems, function (relatedItem) { + if (relatedItem[relationDef.idAttribute] === item[def.localKey]) { + item[def.localField] = relatedItem; + } + }); + }); + return relatedItems; + }); + } + } + + if (task) { + tasks.push(task); + } + }); + return DSUtils.Promise.all(tasks); + } + var DSSqlAdapter = (function () { function DSSqlAdapter(options) { _classCallCheck(this, DSSqlAdapter); @@ -183,92 +372,17 @@ module.exports = _createClass(DSSqlAdapter, [{ key: 'find', value: function find(resourceConfig, id, options) { - var _this = this; + var _this2 = this; var instance = undefined; options = options || {}; options['with'] = options['with'] || []; - return this.query.select('*').from(resourceConfig.table || underscore(resourceConfig.name)).where(resourceConfig.idAttribute, toString(id)).then(function (rows) { + return this.query.select('*').from(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).then(function (rows) { if (!rows.length) { return DSUtils.Promise.reject(new Error('Not Found!')); } else { - var _ret = (function () { - instance = rows[0]; - var tasks = []; - - DSUtils.forEach(resourceConfig.relationList, function (def) { - var relationName = def.relation; - var relationDef = resourceConfig.getResource(relationName); - var containedName = null; - if (DSUtils.contains(options['with'], relationName)) { - containedName = relationName; - } else if (DSUtils.contains(options['with'], def.localField)) { - containedName = def.localField; - } - if (containedName) { - (function () { - var __options = DSUtils.deepMixIn({}, options.orig ? options.orig() : options); - __options['with'] = options['with'].slice(); - __options = DSUtils._(relationDef, __options); - DSUtils.remove(__options['with'], containedName); - DSUtils.forEach(__options['with'], function (relation, i) { - if (relation && relation.indexOf(containedName) === 0 && relation.length >= containedName.length && relation[containedName.length] === '.') { - __options['with'][i] = relation.substr(containedName.length + 1); - } else { - __options['with'][i] = ''; - } - }); - - var task = undefined; - - if ((def.type === 'hasOne' || def.type === 'hasMany') && def.foreignKey) { - task = _this.findAll(resourceConfig.getResource(relationName), { - where: _defineProperty({}, def.foreignKey, { - '==': instance[resourceConfig.idAttribute] - }) - }, __options).then(function (relatedItems) { - if (def.type === 'hasOne' && relatedItems.length) { - DSUtils.set(instance, def.localField, relatedItems[0]); - } else { - DSUtils.set(instance, def.localField, relatedItems); - } - return relatedItems; - }); - } else if (def.type === 'hasMany' && def.localKeys) { - var localKeys = []; - var itemKeys = instance[def.localKeys] || []; - itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys); - localKeys = localKeys.concat(itemKeys || []); - task = _this.findAll(resourceConfig.getResource(relationName), { - where: _defineProperty({}, relationDef.idAttribute, { - 'in': DSUtils.filter(unique(localKeys), function (x) { - return x; - }) - }) - }, __options).then(function (relatedItems) { - DSUtils.set(instance, def.localField, relatedItems); - return relatedItems; - }); - } else if (def.type === 'belongsTo' || def.type === 'hasOne' && def.localKey) { - task = _this.find(resourceConfig.getResource(relationName), DSUtils.get(instance, def.localKey), __options).then(function (relatedItem) { - DSUtils.set(instance, def.localField, relatedItem); - return relatedItem; - }); - } - - if (task) { - tasks.push(task); - } - })(); - } - }); - - return { - v: DSUtils.Promise.all(tasks) - }; - })(); - - if (typeof _ret === 'object') return _ret.v; + instance = rows[0]; + return loadWithRelations.call(_this2, instance, resourceConfig, options); } }).then(function () { return instance; @@ -277,121 +391,14 @@ module.exports = }, { key: 'findAll', value: function findAll(resourceConfig, params, options) { - var _this2 = this; + var _this3 = this; var items = null; options = options || {}; options['with'] = options['with'] || []; return filterQuery.call(this, resourceConfig, params, options).then(function (_items) { items = _items; - var tasks = []; - DSUtils.forEach(resourceConfig.relationList, function (def) { - var relationName = def.relation; - var relationDef = resourceConfig.getResource(relationName); - var containedName = null; - if (DSUtils.contains(options['with'], relationName)) { - containedName = relationName; - } else if (DSUtils.contains(options['with'], def.localField)) { - containedName = def.localField; - } - if (containedName) { - (function () { - var __options = DSUtils.deepMixIn({}, options.orig ? options.orig() : options); - __options['with'] = options['with'].slice(); - __options = DSUtils._(relationDef, __options); - DSUtils.remove(__options['with'], containedName); - DSUtils.forEach(__options['with'], function (relation, i) { - if (relation && relation.indexOf(containedName) === 0 && relation.length >= containedName.length && relation[containedName.length] === '.') { - __options['with'][i] = relation.substr(containedName.length + 1); - } else { - __options['with'][i] = ''; - } - }); - - var task = undefined; - - if ((def.type === 'hasOne' || def.type === 'hasMany') && def.foreignKey) { - task = _this2.findAll(resourceConfig.getResource(relationName), { - where: _defineProperty({}, def.foreignKey, { - 'in': DSUtils.filter(map(items, function (item) { - return DSUtils.get(item, resourceConfig.idAttribute); - }), function (x) { - return x; - }) - }) - }, __options).then(function (relatedItems) { - DSUtils.forEach(items, function (item) { - var attached = []; - DSUtils.forEach(relatedItems, function (relatedItem) { - if (DSUtils.get(relatedItem, def.foreignKey) === item[resourceConfig.idAttribute]) { - attached.push(relatedItem); - } - }); - if (def.type === 'hasOne' && attached.length) { - DSUtils.set(item, def.localField, attached[0]); - } else { - DSUtils.set(item, def.localField, attached); - } - }); - return relatedItems; - }); - } else if (def.type === 'hasMany' && def.localKeys) { - (function () { - var localKeys = []; - DSUtils.forEach(items, function (item) { - var itemKeys = item[def.localKeys] || []; - itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys); - localKeys = localKeys.concat(itemKeys || []); - }); - task = _this2.findAll(resourceConfig.getResource(relationName), { - where: _defineProperty({}, relationDef.idAttribute, { - 'in': DSUtils.filter(unique(localKeys), function (x) { - return x; - }) - }) - }, __options).then(function (relatedItems) { - DSUtils.forEach(items, function (item) { - var attached = []; - var itemKeys = item[def.localKeys] || []; - itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys); - DSUtils.forEach(relatedItems, function (relatedItem) { - if (itemKeys && DSUtils.contains(itemKeys, relatedItem[relationDef.idAttribute])) { - attached.push(relatedItem); - } - }); - DSUtils.set(item, def.localField, attached); - }); - return relatedItems; - }); - })(); - } else if (def.type === 'belongsTo' || def.type === 'hasOne' && def.localKey) { - task = _this2.findAll(resourceConfig.getResource(relationName), { - where: _defineProperty({}, relationDef.idAttribute, { - 'in': DSUtils.filter(map(items, function (item) { - return DSUtils.get(item, def.localKey); - }), function (x) { - return x; - }) - }) - }, __options).then(function (relatedItems) { - DSUtils.forEach(items, function (item) { - DSUtils.forEach(relatedItems, function (relatedItem) { - if (relatedItem[relationDef.idAttribute] === item[def.localKey]) { - DSUtils.set(item, def.localField, relatedItem); - } - }); - }); - return relatedItems; - }); - } - - if (task) { - tasks.push(task); - } - })(); - } - }); - return DSUtils.Promise.all(tasks); + return loadWithRelations.call(_this3, _items, resourceConfig, options); }).then(function () { return items; }); @@ -399,14 +406,14 @@ module.exports = }, { key: 'create', value: function create(resourceConfig, attrs, options) { - var _this3 = this; + var _this4 = this; attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); - return this.query(resourceConfig.table || underscore(resourceConfig.name)).insert(attrs, resourceConfig.idAttribute).then(function (ids) { + return this.query(getTable(resourceConfig)).insert(attrs, resourceConfig.idAttribute).then(function (ids) { if (attrs[resourceConfig.idAttribute]) { - return _this3.find(resourceConfig, attrs[resourceConfig.idAttribute], options); + return _this4.find(resourceConfig, attrs[resourceConfig.idAttribute], options); } else if (ids.length) { - return _this3.find(resourceConfig, ids[0], options); + return _this4.find(resourceConfig, ids[0], options); } else { throw new Error('Failed to create!'); } @@ -415,17 +422,17 @@ module.exports = }, { key: 'update', value: function update(resourceConfig, id, attrs, options) { - var _this4 = this; + var _this5 = this; attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); - return this.query(resourceConfig.table || underscore(resourceConfig.name)).where(resourceConfig.idAttribute, toString(id)).update(attrs).then(function () { - return _this4.find(resourceConfig, id, options); + return this.query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).update(attrs).then(function () { + return _this5.find(resourceConfig, id, options); }); } }, { key: 'updateAll', value: function updateAll(resourceConfig, attrs, params, options) { - var _this5 = this; + var _this6 = this; attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); return filterQuery.call(this, resourceConfig, params, options).then(function (items) { @@ -433,19 +440,19 @@ module.exports = return item[resourceConfig.idAttribute]; }); }).then(function (ids) { - return filterQuery.call(_this5, resourceConfig, params, options).update(attrs).then(function () { + return filterQuery.call(_this6, resourceConfig, params, options).update(attrs).then(function () { var _params = { where: {} }; _params.where[resourceConfig.idAttribute] = { 'in': ids }; - return filterQuery.call(_this5, resourceConfig, _params, options); + return filterQuery.call(_this6, resourceConfig, _params, options); }); }); } }, { key: 'destroy', value: function destroy(resourceConfig, id) { - return this.query(resourceConfig.table || underscore(resourceConfig.name)).where(resourceConfig.idAttribute, toString(id)).del().then(function () { + return this.query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).del().then(function () { return undefined; }); } diff --git a/package.json b/package.json index e080b8b..b5c0632 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "js-data-sql", "description": "Postgres/MySQL/MariaDB/SQLite3 adapter for js-data.", - "version": "0.10.0", + "version": "0.11.0", "homepage": "http://www.js-data.io/docs/dssqladapter", "repository": { "type": "git", @@ -27,10 +27,11 @@ ], "devDependencies": { "babel-core": "5.8.25", + "babel-eslint": "4.1.3", "babel-loader": "5.3.2", - "bluebird": "2.10.0", - "chai": "3.2.0", - "co-mocha": "^1.1.2", + "bluebird": "2.10.2", + "chai": "3.3.0", + "co-mocha": "1.1.2", "grunt": "0.4.5", "grunt-contrib-watch": "0.6.1", "grunt-karma-coveralls": "2.5.4", @@ -39,13 +40,17 @@ "jit-grunt": "0.9.1", "jshint": "2.8.0", "jshint-loader": "0.8.3", - "sinon": "1.16.1", + "sinon": "1.17.1", + "standard": "5.3.1", "time-grunt": "1.2.1", - "webpack-dev-server": "1.11.0" + "webpack-dev-server": "1.12.0" }, "scripts": { "test": "grunt test" }, + "standard": { + "parser": "babel-eslint" + }, "dependencies": { "mout": "0.11.0" }, diff --git a/src/index.js b/src/index.js index 73720c6..e597e98 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,10 @@ -let knex = require('knex'); -let JSData = require('js-data'); -let map = require('mout/array/map'); -let underscore = require('mout/string/underscore'); -let unique = require('mout/array/unique'); -let toString = require('mout/lang/toString'); -let { DSUtils } = JSData; +let knex = require('knex') +let JSData = require('js-data') +let map = require('mout/array/map') +let underscore = require('mout/string/underscore') +let unique = require('mout/array/unique') +let toString = require('mout/lang/toString') +let { DSUtils } = JSData let reserved = [ 'orderBy', @@ -13,178 +13,181 @@ let reserved = [ 'offset', 'skip', 'where' -]; +] -function getTable(resourceConfig) { - return resourceConfig.table || underscore(resourceConfig.name); +function getTable (resourceConfig) { + return resourceConfig.table || underscore(resourceConfig.name) } -function filterQuery(resourceConfig, params) { - let query = this.query.select('*').from(getTable(resourceConfig)); - params = params || {}; - params.where = params.where || {}; - params.orderBy = params.orderBy || params.sort; - params.skip = params.skip || params.offset; +function filterQuery (resourceConfig, params) { + let query = this.query.select('*').from(getTable(resourceConfig)) + params = params || {} + params.where = params.where || {} + params.orderBy = params.orderBy || params.sort + params.skip = params.skip || params.offset - let joinedTables = []; + let joinedTables = [] DSUtils.forEach(DSUtils.keys(params), k => { - let v = params[k]; + let v = params[k] if (!DSUtils.contains(reserved, k)) { if (DSUtils.isObject(v)) { - params.where[k] = v; + params.where[k] = v } else { params.where[k] = { '==': v - }; + } } - delete params[k]; + delete params[k] } - }); + }) if (!DSUtils.isEmpty(params.where)) { DSUtils.forOwn(params.where, (criteria, field) => { if (!DSUtils.isObject(criteria)) { params.where[field] = { '==': criteria - }; + } } DSUtils.forOwn(criteria, (v, op) => { if (DSUtils.contains(field, '.')) { - let parts = field.split('.'); - let localResourceConfig = resourceConfig; + let parts = field.split('.') + let localResourceConfig = resourceConfig - let relationPath = []; + let relationPath = [] while (parts.length >= 2) { - let relationName = parts.shift(); - let relationResourceConfig = resourceConfig.getResource(relationName); - relationPath.push(relationName); + let relationName = parts.shift() + let relationResourceConfig = resourceConfig.getResource(relationName) + relationPath.push(relationName) if (!joinedTables.some(t => t === relationPath.join('.'))) { - let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName); - let table = getTable(localResourceConfig); - let localId = `${table}.${relation.localKey}`; + let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName) + let table = getTable(localResourceConfig) + let localId = `${table}.${relation.localKey}` - let relationTable = getTable(relationResourceConfig); - let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}`; + let relationTable = getTable(relationResourceConfig) + let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}` - query = query.join(relationTable, localId, foreignId); - joinedTables.push(relationPath.join('.')); + query = query.join(relationTable, localId, foreignId) + joinedTables.push(relationPath.join('.')) } - localResourceConfig = relationResourceConfig; + localResourceConfig = relationResourceConfig } - field = `${getTable(localResourceConfig)}.${parts[0]}`; + field = `${getTable(localResourceConfig)}.${parts[0]}` } if (op === '==' || op === '===') { - query = query.where(field, v); + query = query.where(field, v) } else if (op === '!=' || op === '!==') { - query = query.where(field, '!=', v); + query = query.where(field, '!=', v) } else if (op === '>') { - query = query.where(field, '>', v); + query = query.where(field, '>', v) } else if (op === '>=') { - query = query.where(field, '>=', v); + query = query.where(field, '>=', v) } else if (op === '<') { - query = query.where(field, '<', v); + query = query.where(field, '<', v) } else if (op === '<=') { - query = query.where(field, '<=', v); - //} else if (op === 'isectEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0); - //} else if (op === 'isectNotEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0); + query = query.where(field, '<=', v) + // } else if (op === 'isectEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === 'isectNotEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) } else if (op === 'in') { - query = query.where(field, 'in', v); + query = query.where(field, 'in', v) } else if (op === 'notIn') { - query = query.whereNotIn(field, v); + query = query.whereNotIn(field, v) } else if (op === 'like') { - query = query.where(field, 'like', v); + query = query.where(field, 'like', v) } else if (op === '|==' || op === '|===') { - query = query.orWhere(field, v); + query = query.orWhere(field, v) } else if (op === '|!=' || op === '|!==') { - query = query.orWhere(field, '!=', v); + query = query.orWhere(field, '!=', v) } else if (op === '|>') { - query = query.orWhere(field, '>', v); + query = query.orWhere(field, '>', v) } else if (op === '|>=') { - query = query.orWhere(field, '>=', v); + query = query.orWhere(field, '>=', v) } else if (op === '|<') { - query = query.orWhere(field, '<', v); + query = query.orWhere(field, '<', v) } else if (op === '|<=') { - query = query.orWhere(field, '<=', v); - //} else if (op === '|isectEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0); - //} else if (op === '|isectNotEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0); + query = query.orWhere(field, '<=', v) + // } else if (op === '|isectEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === '|isectNotEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) } else if (op === '|in') { - query = query.orWhere(field, 'in', v); + query = query.orWhere(field, 'in', v) } else if (op === '|notIn') { - query = query.orWhereNotIn(field, v); + query = query.orWhereNotIn(field, v) } else { - throw new Error('Operator not found'); + throw new Error('Operator not found') } - }); - }); + }) + }) } if (params.orderBy) { if (DSUtils.isString(params.orderBy)) { params.orderBy = [ [params.orderBy, 'asc'] - ]; + ] } for (var i = 0; i < params.orderBy.length; i++) { if (DSUtils.isString(params.orderBy[i])) { - params.orderBy[i] = [params.orderBy[i], 'asc']; + params.orderBy[i] = [params.orderBy[i], 'asc'] } - query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc'); + query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc') } } if (params.skip) { - query = query.offset(params.offset); + query = query.offset(params.offset) } if (params.limit) { - query = query.limit(params.limit); + query = query.limit(params.limit) } - return query; + return query } -function loadWithRelations(items, resourceConfig, options) { - let tasks = []; - let instance = Array.isArray(items) ? null : items; +function loadWithRelations (items, resourceConfig, options) { + let tasks = [] + let instance = Array.isArray(items) ? null : items DSUtils.forEach(resourceConfig.relationList, def => { - let relationName = def.relation; - let relationDef = resourceConfig.getResource(relationName); + let relationName = def.relation + let relationDef = resourceConfig.getResource(relationName) - let containedName = null; + let containedName = null if (DSUtils.contains(options.with, relationName)) { - containedName = relationName; + containedName = relationName } else if (DSUtils.contains(options.with, def.localField)) { - containedName = def.localField; + containedName = def.localField } else { - return; + return } - let __options = DSUtils.deepMixIn({}, options.orig ? options.orig() : options); + let __options = DSUtils.deepMixIn({}, options.orig ? options.orig() : options) // Filter to only properties under current relation __options.with = options.with.filter(relation => { return relation !== containedName && - relation.indexOf(containedName) === 0 && - relation.length >= containedName.length && - relation[containedName.length] === '.'; - }).map(relation => relation.substr(containedName.length + 1)); + relation.indexOf(containedName) === 0 && + relation.length >= containedName.length && + relation[containedName.length] === '.' + }).map(relation => relation.substr(containedName.length + 1)) - let task; + let task if ((def.type === 'hasOne' || def.type === 'hasMany') && def.foreignKey) { - let foreignKeyFilter = instance ? - { '==': instance[resourceConfig.idAttribute] } : - { 'in': map(items, item => item[resourceConfig.idAttribute]) } ; + let foreignKeyFilter + if (instance) { + foreignKeyFilter = { '==': instance[resourceConfig.idAttribute] } + } else { + foreignKeyFilter = { 'in': map(items, item => item[resourceConfig.idAttribute]) } + } task = this.findAll(resourceConfig.getResource(relationName), { where: { [def.foreignKey]: foreignKeyFilter @@ -192,37 +195,37 @@ function loadWithRelations(items, resourceConfig, options) { }, __options).then(relatedItems => { if (instance) { if (def.type === 'hasOne' && relatedItems.length) { - instance[def.localField] = relatedItems[0]; + instance[def.localField] = relatedItems[0] } else { - instance[def.localField] = relatedItems; + instance[def.localField] = relatedItems } } else { DSUtils.forEach(items, item => { - let attached = relatedItems.filter(ri => ri[def.foreignKey] === item[resourceConfig.idAttribute]); + let attached = relatedItems.filter(ri => ri[def.foreignKey] === item[resourceConfig.idAttribute]) if (def.type === 'hasOne' && attached.length) { - item[def.localField] = attached[0]; + item[def.localField] = attached[0] } else { - item[def.localField] = attached; + item[def.localField] = attached } - }); + }) } - return relatedItems; - }); + return relatedItems + }) } else if (def.type === 'hasMany' && def.localKeys) { // TODO: Write test for with: hasMany property with localKeys - let localKeys = []; + let localKeys = [] if (instance) { - let itemKeys = instance[def.localKeys] || []; - itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys); - localKeys = localKeys.concat(itemKeys || []); + let itemKeys = instance[def.localKeys] || [] + itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys) + localKeys = localKeys.concat(itemKeys || []) } else { DSUtils.forEach(items, item => { - let itemKeys = item[def.localKeys] || []; - itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys); - localKeys = localKeys.concat(itemKeys || []); - }); + let itemKeys = item[def.localKeys] || [] + itemKeys = Array.isArray(itemKeys) ? itemKeys : DSUtils.keys(itemKeys) + localKeys = localKeys.concat(itemKeys || []) + }) } task = this.findAll(resourceConfig.getResource(relationName), { @@ -233,23 +236,23 @@ function loadWithRelations(items, resourceConfig, options) { } }, __options).then(relatedItems => { if (instance) { - instance[def.localField] = relatedItems; + instance[def.localField] = relatedItems } else { DSUtils.forEach(items, item => { - let itemKeys = item[def.localKeys] || []; - let attached = relatedItems.filter(ri => itemKeys && DSUtils.contains(itemKeys, ri[relationDef.idAttribute])); - item[def.localField] = attached; - }); + let itemKeys = item[def.localKeys] || [] + let attached = relatedItems.filter(ri => itemKeys && DSUtils.contains(itemKeys, ri[relationDef.idAttribute])) + item[def.localField] = attached + }) } - return relatedItems; - }); + return relatedItems + }) } else if (def.type === 'belongsTo' || (def.type === 'hasOne' && def.localKey)) { if (instance) { task = this.find(resourceConfig.getResource(relationName), DSUtils.get(instance, def.localKey), __options).then(relatedItem => { - instance[def.localField] = relatedItem; - return relatedItem; - }); + instance[def.localField] = relatedItem + return relatedItem + }) } else { task = this.findAll(resourceConfig.getResource(relationName), { where: { @@ -261,110 +264,110 @@ function loadWithRelations(items, resourceConfig, options) { DSUtils.forEach(items, item => { DSUtils.forEach(relatedItems, relatedItem => { if (relatedItem[relationDef.idAttribute] === item[def.localKey]) { - item[def.localField] = relatedItem; + item[def.localField] = relatedItem } - }); - }); - return relatedItems; - }); + }) + }) + return relatedItems + }) } } if (task) { - tasks.push(task); + tasks.push(task) } - }); - return DSUtils.Promise.all(tasks); + }) + return DSUtils.Promise.all(tasks) } class DSSqlAdapter { - constructor(options) { - this.defaults = {}; - options = options || {}; + constructor (options) { + this.defaults = {} + options = options || {} if (options.__knex__) { - this.query = options; + this.query = options } else { - this.query = knex(options); + this.query = knex(options) } - DSUtils.deepMixIn(this.defaults, options); + DSUtils.deepMixIn(this.defaults, options) } - find(resourceConfig, id, options) { - let instance; - options = options || {}; - options.with = options.with || []; + find (resourceConfig, id, options) { + let instance + options = options || {} + options.with = options.with || [] return this.query .select('*') .from(getTable(resourceConfig)) .where(resourceConfig.idAttribute, toString(id)) .then(rows => { if (!rows.length) { - return DSUtils.Promise.reject(new Error('Not Found!')); + return DSUtils.Promise.reject(new Error('Not Found!')) } else { - instance = rows[0]; - return loadWithRelations.call(this, instance, resourceConfig, options); + instance = rows[0] + return loadWithRelations.call(this, instance, resourceConfig, options) } }) - .then(() => instance); + .then(() => instance) } - findAll(resourceConfig, params, options) { - let items = null; - options = options || {}; - options.with = options.with || []; + findAll (resourceConfig, params, options) { + let items = null + options = options || {} + options.with = options.with || [] return filterQuery.call(this, resourceConfig, params, options).then(_items => { - items = _items; - return loadWithRelations.call(this, _items, resourceConfig, options); - }).then(() => items); + items = _items + return loadWithRelations.call(this, _items, resourceConfig, options) + }).then(() => items) } - create(resourceConfig, attrs, options) { - attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); + create (resourceConfig, attrs, options) { + attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])) return this.query(getTable(resourceConfig)) .insert(attrs, resourceConfig.idAttribute) .then(ids => { if (attrs[resourceConfig.idAttribute]) { - return this.find(resourceConfig, attrs[resourceConfig.idAttribute], options); + return this.find(resourceConfig, attrs[resourceConfig.idAttribute], options) } else if (ids.length) { - return this.find(resourceConfig, ids[0], options); + return this.find(resourceConfig, ids[0], options) } else { - throw new Error('Failed to create!'); + throw new Error('Failed to create!') } - }); + }) } - update(resourceConfig, id, attrs, options) { - attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); + update (resourceConfig, id, attrs, options) { + attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])) return this.query(getTable(resourceConfig)) .where(resourceConfig.idAttribute, toString(id)) .update(attrs) - .then(() => this.find(resourceConfig, id, options)); + .then(() => this.find(resourceConfig, id, options)) } - updateAll(resourceConfig, attrs, params, options) { - attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); + updateAll (resourceConfig, attrs, params, options) { + attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])) return filterQuery.call(this, resourceConfig, params, options).then(items => { - return map(items, item => item[resourceConfig.idAttribute]); + return map(items, item => item[resourceConfig.idAttribute]) }).then(ids => { return filterQuery.call(this, resourceConfig, params, options).update(attrs).then(() => { - let _params = {where: {}}; + let _params = {where: {}} _params.where[resourceConfig.idAttribute] = { 'in': ids - }; - return filterQuery.call(this, resourceConfig, _params, options); - }); - }); + } + return filterQuery.call(this, resourceConfig, _params, options) + }) + }) } - destroy(resourceConfig, id) { + destroy (resourceConfig, id) { return this.query(getTable(resourceConfig)) .where(resourceConfig.idAttribute, toString(id)) - .del().then(() => undefined); + .del().then(() => undefined) } - destroyAll(resourceConfig, params, options) { - return filterQuery.call(this, resourceConfig, params, options).del().then(() => undefined); + destroyAll (resourceConfig, params, options) { + return filterQuery.call(this, resourceConfig, params, options).del().then(() => undefined) } } -export default DSSqlAdapter; +export default DSSqlAdapter