UNPKG

forest-express-sequelize

Version:

Official Express/Sequelize Liana for Forest

152 lines (146 loc) 7.21 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); require("core-js/modules/es.array.sort.js"); require("core-js/modules/es.array.iterator.js"); var _objectTools = _interopRequireDefault(require("./object-tools")); var _operators = _interopRequireDefault(require("./operators")); var _query = _interopRequireDefault(require("./query")); function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } /** * Extract all where conditions along the include tree, and bubbles them up to the top in-place. * This allows to work around a sequelize quirk that cause nested 'where' to fail when they * refer to relation fields from an intermediary include (ie '$book.id$'). * * This happens when forest admin filters on relations are used. * * @see https://sequelize.org/master/manual/eager-loading.html#complex-where-clauses-at-the-top-level * @see https://github.com/ForestAdmin/forest-express-sequelize/blob/7d7ad0/src/services/filters-parser.js#L104 */ function bubbleWheresInPlace(operators, options) { var _options$include; const parentIncludeList = (_options$include = options.include) !== null && _options$include !== void 0 ? _options$include : []; parentIncludeList.forEach(function (include) { bubbleWheresInPlace(operators, include); if (include.where) { const newWhere = _objectTools.default.mapKeysDeep(include.where, function (key) { var _include$model$rawAtt, _include$model, _include$model$rawAtt2, _include$model$rawAtt3; // Targeting a nested field, simply nest it deeper. if (key[0] === '$' && key[key.length - 1] === '$') { return `$${include.as}.${key.substring(1)}`; } // Targeting a simple field. // Try to resolve the column name, as sequelize does not allow using model aliases here. return `$${include.as}.${(_include$model$rawAtt = (_include$model = include.model) === null || _include$model === void 0 ? void 0 : (_include$model$rawAtt2 = _include$model.rawAttributes) === null || _include$model$rawAtt2 === void 0 ? void 0 : (_include$model$rawAtt3 = _include$model$rawAtt2[key]) === null || _include$model$rawAtt3 === void 0 ? void 0 : _include$model$rawAtt3.field) !== null && _include$model$rawAtt !== void 0 ? _include$model$rawAtt : key}$`; }); options.where = _query.default.mergeWhere(operators, options.where, newWhere); delete include.where; } }); } /** * Includes can be expressed in different ways in sequelize, which is inconvenient to * remove duplicate associations. * This convert all valid ways to perform eager loading into [{model: X, as: 'x'}]. * * This is necessary as we have no control over which way customer use when writing SmartFields * search handlers. * * Among those: * - { include: [Book] } * - { include: [{ association: 'book' }] } * - { include: ['book'] } * - { include: [[{ as: 'book' }]] } * - { include: [[{ model: Book }]] } */ function normalizeInclude(model, include) { if (include.sequelize) { return { model: include, as: Object.keys(model.associations).find(function (association) { return model.associations[association].target.name === include.name; }) }; } if (typeof include === 'string' && model.associations[include]) { return { as: include, model: model.associations[include].target }; } if (typeof include === 'object') { if (typeof include.association === 'string' && model.associations[include.association]) { include.as = include.association; delete include.association; } if (typeof include.as === 'string' && !include.model && model.associations[include.as]) { const includeModel = model.associations[include.as].target; include.model = includeModel; } if (include.model && !include.as) { include.as = Object.keys(model.associations).find(function (association) { return model.associations[association].target.name === include.model.name; }); } } // Recurse if (include.include) { if (Array.isArray(include.include)) { include.include = include.include.map(function (childInclude) { return normalizeInclude(include.model, childInclude); }); } else { include.include = [normalizeInclude(include.model, include.include)]; } } return include; } /** * Remove duplications in a queryOption.include array in-place. * Using multiple times the same association yields invalid SQL when using sequelize <= 4.x */ function removeDuplicateAssociations(model, includeList) { // Remove duplicates includeList.sort(function (include1, include2) { return include1.as < include2.as ? -1 : 1; }); for (let i = 1; i < includeList.length; i += 1) { if (includeList[i - 1].as === includeList[i].as) { const newInclude = _objectSpread(_objectSpread({}, includeList[i - 1]), includeList[i]); if (includeList[i - 1].attributes && includeList[i].attributes) { // Keep 'attributes' only when defined on both sides. newInclude.attributes = [...new Set([...includeList[i - 1].attributes, ...includeList[i].attributes])].sort(); } else { delete newInclude.attributes; } if (includeList[i - 1].include || includeList[i].include) { var _includeList$include, _includeList$i$includ; newInclude.include = [...((_includeList$include = includeList[i - 1].include) !== null && _includeList$include !== void 0 ? _includeList$include : []), ...((_includeList$i$includ = includeList[i].include) !== null && _includeList$i$includ !== void 0 ? _includeList$i$includ : [])]; } includeList[i - 1] = newInclude; includeList.splice(i, 1); i -= 1; } } // Recurse includeList.forEach(function (include) { const association = model.associations[include.as]; if (include.include && association) { removeDuplicateAssociations(association.target, include.include); } }); } exports.postProcess = function (model, rawOptions) { if (!rawOptions.include) return rawOptions; const options = rawOptions; const operators = _operators.default.getInstance({ Sequelize: model.sequelize.constructor }); options.include = options.include.map(function (include) { return normalizeInclude(model, include); }); bubbleWheresInPlace(operators, options); removeDuplicateAssociations(model, options.include); return options; };