UNPKG

feathers-sequelize

Version:
347 lines 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SequelizeAdapter = void 0; const errors_1 = require("@feathersjs/errors"); const commons_1 = require("@feathersjs/commons"); const adapter_commons_1 = require("@feathersjs/adapter-commons"); const utils_1 = require("./utils"); const sequelize_1 = require("sequelize"); const defaultOpMap = () => { return { $eq: sequelize_1.Op.eq, $ne: sequelize_1.Op.ne, $gte: sequelize_1.Op.gte, $gt: sequelize_1.Op.gt, $lte: sequelize_1.Op.lte, $lt: sequelize_1.Op.lt, $in: sequelize_1.Op.in, $nin: sequelize_1.Op.notIn, $like: sequelize_1.Op.like, $notLike: sequelize_1.Op.notLike, $iLike: sequelize_1.Op.iLike, $notILike: sequelize_1.Op.notILike, $or: sequelize_1.Op.or, $and: sequelize_1.Op.and }; }; const defaultFilters = () => { return { $returning: (value) => { if (value === true || value === false || value === undefined) { return value; } throw new errors_1.BadRequest('Invalid $returning filter value'); }, $and: true }; }; class SequelizeAdapter extends adapter_commons_1.AdapterBase { constructor(options) { if (!options.Model) { throw new Error('You must provide a Sequelize Model'); } if (options.operators && !Array.isArray(options.operators)) { throw new Error('The \'operators\' option must be an array. For migration from feathers.js v4 see: https://github.com/feathersjs-ecosystem/feathers-sequelize/tree/dove#migrate-to-feathers-v5-dove'); } const operatorMap = Object.assign(defaultOpMap(), options.operatorMap); const operators = Object.keys(operatorMap); if (options.operators) { options.operators.forEach(op => { if (!operators.includes(op)) { operators.push(op); } }); } const { primaryKeyAttributes } = options.Model; const id = typeof primaryKeyAttributes === 'object' && primaryKeyAttributes[0] !== undefined ? primaryKeyAttributes[0] : 'id'; const filters = Object.assign(defaultFilters(), options.filters); super(Object.assign({ id }, options, { operatorMap, filters, operators })); } get raw() { return this.options.raw !== false; } get Op() { // @ts-ignore return this.options.Model.sequelize.Sequelize.Op; } get Model() { if (!this.options.Model) { throw new Error('The Model getter was called with no Model provided in options!'); } return this.options.Model; } // eslint-disable-next-line @typescript-eslint/no-unused-vars getModel(_params) { if (!this.options.Model) { throw new Error('getModel was called without a Model present in the constructor options and without overriding getModel! Perhaps you intended to override getModel in a child class?'); } return this.options.Model; } /** * @deprecated use 'service.ModelWithScope' instead. 'applyScope' will be removed in a future release. */ applyScope(params) { var _a; const Model = this.getModel(params); if ((_a = params === null || params === void 0 ? void 0 : params.sequelize) === null || _a === void 0 ? void 0 : _a.scope) { return Model.scope(params.sequelize.scope); } return Model; } ModelWithScope(params) { return this.applyScope(params); } convertOperators(q) { if (Array.isArray(q)) { return q.map(subQuery => this.convertOperators(subQuery)); } if (!(0, utils_1.isPlainObject)(q)) { return q; } const { operatorMap } = this.options; const converted = Object.keys(q).reduce((result, prop) => { const value = q[prop]; const key = (operatorMap[prop] ? operatorMap[prop] : prop); result[key] = this.convertOperators(value); return result; }, {}); Object.getOwnPropertySymbols(q).forEach(symbol => { converted[symbol] = q[symbol]; }); return converted; } filterQuery(params) { const options = this.getOptions(params); const { filters, query: _query } = (0, adapter_commons_1.filterQuery)(params.query || {}, options); const query = this.convertOperators({ ..._query, ...commons_1._.omit(filters, '$select', '$skip', '$limit', '$sort') }); return { filters, query, paginate: options.paginate }; } paramsToAdapter(id, _params) { const params = _params || {}; if (id) { const { query: where } = this.filterQuery(params); const { and } = this.Op; // Attach 'where' constraints, if any were used. const q = Object.assign({ raw: this.raw, where: Object.assign(where, { [and]: where[and] ? [...where[and], { [this.id]: id }] : { [this.id]: id } }) }, params.sequelize); return q; } else { const { filters, query: where } = this.filterQuery(params); const order = (0, utils_1.getOrder)(filters.$sort); const q = Object.assign({ where, order, limit: filters.$limit, offset: filters.$skip, raw: this.raw, distinct: true }, params.sequelize); if (filters.$select) { // Add the id to the select if it is not already there if (!filters.$select.includes(this.id)) { filters.$select.push(this.id); } q.attributes = filters.$select.map((select) => `${select}`); } // Until Sequelize fix all the findAndCount issues, a few 'hacks' are needed to get the total count correct // Adding an empty include changes the way the count is done // See: https://github.com/sequelize/sequelize/blob/7e441a6a5ca44749acd3567b59b1d6ceb06ae64b/lib/model.js#L1780-L1782 q.include = q.include || []; return q; } } async _getOrFind(id, _params) { const params = _params || {}; if (id === null) { return await this._find({ ...params, paginate: false }); } return await this._get(id, params); } async _find(params = {}) { const { paginate } = this.filterQuery(params); const Model = this.ModelWithScope(params); const q = this.paramsToAdapter(null, params); try { if (paginate && paginate.default) { const result = await Model.findAndCountAll(q); return { total: result.count, limit: q.limit, skip: q.offset || 0, data: result.rows }; } return await Model.findAll(q); } catch (err) { return (0, utils_1.errorHandler)(err); } } async _get(id, params = {}) { const Model = this.ModelWithScope(params); const q = this.paramsToAdapter(id, params); // findById calls findAll under the hood. We use findAll so that // eager loading can be used without a separate code path. try { const result = await Model.findAll(q); if (result.length === 0) { throw new errors_1.NotFound(`No record found for id '${id}'`); } const item = result[0]; return (0, adapter_commons_1.select)(params, this.id)(item); } catch (err) { return (0, utils_1.errorHandler)(err); } } async _create(data, params = {}) { if (Array.isArray(data) && !this.allowsMulti('create', params)) { throw new errors_1.MethodNotAllowed('Can not create multiple entries'); } const options = Object.assign({ raw: this.raw }, params.sequelize); // Model.create's `raw` option is different from other methods. // In order to use `raw` consistently to serialize the result, // we need to shadow the Model.create use of raw, which we provide // access to by specifying `ignoreSetters`. const ignoreSetters = !!options.ignoreSetters; const createOptions = Object.assign({ returning: true }, options, { raw: ignoreSetters }); const isArray = Array.isArray(data); const Model = this.ModelWithScope(params); try { const result = isArray ? await Model.bulkCreate(data, createOptions) : await Model.create(data, createOptions); const sel = (0, adapter_commons_1.select)(params, this.id); if (options.raw === false) { return result; } if (isArray) { return result.map(item => sel(item.toJSON())); } return sel(result.toJSON()); } catch (err) { return (0, utils_1.errorHandler)(err); } } async _patch(id, data, params = {}) { var _a, _b; if (id === null && !this.allowsMulti('patch', params)) { throw new errors_1.MethodNotAllowed('Can not patch multiple entries'); } const Model = this.ModelWithScope(params); // Get a list of ids that match the id/query. Overwrite the // $select because only the id is needed for this idList const itemOrItems = await this._getOrFind(id, { ...params, query: { ...params === null || params === void 0 ? void 0 : params.query, $select: [this.id] } }); const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; const ids = items.map(item => item[this.id]); try { const seqOptions = Object.assign({ raw: this.raw }, params.sequelize, { where: { [this.id]: { [this.Op.in]: ids } } }); await Model.update(commons_1._.omit(data, this.id), seqOptions); if (params.$returning === false) { return Promise.resolve([]); } // Create a new query that re-queries all ids that // were originally changed const findParams = { ...params, query: { [this.id]: { $in: ids }, ...(((_a = params === null || params === void 0 ? void 0 : params.query) === null || _a === void 0 ? void 0 : _a.$select) ? { $select: (_b = params === null || params === void 0 ? void 0 : params.query) === null || _b === void 0 ? void 0 : _b.$select } : {}) } }; const result = await this._getOrFind(id, findParams); return (0, adapter_commons_1.select)(params, this.id)(result); } catch (err) { return (0, utils_1.errorHandler)(err); } } async _update(id, data, params = {}) { var _a; const query = Object.assign({}, this.filterQuery(params).query); // Force the {raw: false} option as the instance is needed to properly update const seqOptions = Object.assign({}, params.sequelize, { raw: false }); const instance = await this._get(id, { sequelize: seqOptions, query }); const itemToUpdate = Object.keys(instance.toJSON()).reduce((result, key) => { // @ts-ignore result[key] = key in data ? data[key] : null; return result; }, {}); try { await instance.update(itemToUpdate, seqOptions); const item = await this._get(id, { sequelize: Object.assign({}, seqOptions, { raw: typeof ((_a = params === null || params === void 0 ? void 0 : params.sequelize) === null || _a === void 0 ? void 0 : _a.raw) === 'boolean' ? params.sequelize.raw : this.raw }) }); return (0, adapter_commons_1.select)(params, this.id)(item); } catch (err) { return (0, utils_1.errorHandler)(err); } } async _remove(id, params = {}) { var _a; if (id === null && !this.allowsMulti('remove', params)) { throw new errors_1.MethodNotAllowed('Can not remove multiple entries'); } const Model = this.ModelWithScope(params); const findParams = { ...params }; if (params.$returning === false) { findParams.query = { ...findParams.query, $select: [this.id] }; } else if ((_a = params.query) === null || _a === void 0 ? void 0 : _a.$select) { findParams.query = { ...findParams.query, $select: [...params.query.$select, this.id] }; } const itemOrItems = await this._getOrFind(id, findParams); const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; const ids = items.map(item => item[this.id]); const seqOptions = Object.assign({ raw: this.raw }, params.sequelize, { where: { [this.id]: { [this.Op.in]: ids } } }); try { await Model.destroy(seqOptions); if (params.$returning === false) { return []; } return (0, adapter_commons_1.select)(params, this.id)(itemOrItems); } catch (err) { return (0, utils_1.errorHandler)(err); } } } exports.SequelizeAdapter = SequelizeAdapter; //# sourceMappingURL=adapter.js.map