UNPKG

14.1 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.SequelizeAdapter = void 0;
4const errors_1 = require("@feathersjs/errors");
5const commons_1 = require("@feathersjs/commons");
6const adapter_commons_1 = require("@feathersjs/adapter-commons");
7const utils_1 = require("./utils");
8const sequelize_1 = require("sequelize");
9const defaultOpMap = () => {
10 return {
11 $eq: sequelize_1.Op.eq,
12 $ne: sequelize_1.Op.ne,
13 $gte: sequelize_1.Op.gte,
14 $gt: sequelize_1.Op.gt,
15 $lte: sequelize_1.Op.lte,
16 $lt: sequelize_1.Op.lt,
17 $in: sequelize_1.Op.in,
18 $nin: sequelize_1.Op.notIn,
19 $like: sequelize_1.Op.like,
20 $notLike: sequelize_1.Op.notLike,
21 $iLike: sequelize_1.Op.iLike,
22 $notILike: sequelize_1.Op.notILike,
23 $or: sequelize_1.Op.or,
24 $and: sequelize_1.Op.and
25 };
26};
27const defaultFilters = () => {
28 return {
29 $returning: (value) => {
30 if (value === true || value === false || value === undefined) {
31 return value;
32 }
33 throw new errors_1.BadRequest('Invalid $returning filter value');
34 },
35 $and: true
36 };
37};
38class SequelizeAdapter extends adapter_commons_1.AdapterBase {
39 constructor(options) {
40 if (!options.Model) {
41 throw new Error('You must provide a Sequelize Model');
42 }
43 if (options.operators && !Array.isArray(options.operators)) {
44 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');
45 }
46 const operatorMap = Object.assign(defaultOpMap(), options.operatorMap);
47 const operators = Object.keys(operatorMap);
48 if (options.operators) {
49 options.operators.forEach(op => {
50 if (!operators.includes(op)) {
51 operators.push(op);
52 }
53 });
54 }
55 const { primaryKeyAttributes } = options.Model;
56 const id = typeof primaryKeyAttributes === 'object' && primaryKeyAttributes[0] !== undefined
57 ? primaryKeyAttributes[0]
58 : 'id';
59 const filters = Object.assign(defaultFilters(), options.filters);
60 super(Object.assign({ id }, options, { operatorMap, filters, operators }));
61 }
62 get raw() {
63 return this.options.raw !== false;
64 }
65 get Op() {
66 // @ts-ignore
67 return this.options.Model.sequelize.Sequelize.Op;
68 }
69 get Model() {
70 if (!this.options.Model) {
71 throw new Error('The Model getter was called with no Model provided in options!');
72 }
73 return this.options.Model;
74 }
75 // eslint-disable-next-line @typescript-eslint/no-unused-vars
76 getModel(_params) {
77 if (!this.options.Model) {
78 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?');
79 }
80 return this.options.Model;
81 }
82 /**
83 * @deprecated use 'service.ModelWithScope' instead. 'applyScope' will be removed in a future release.
84 */
85 applyScope(params) {
86 var _a;
87 const Model = this.getModel(params);
88 if ((_a = params === null || params === void 0 ? void 0 : params.sequelize) === null || _a === void 0 ? void 0 : _a.scope) {
89 return Model.scope(params.sequelize.scope);
90 }
91 return Model;
92 }
93 ModelWithScope(params) {
94 return this.applyScope(params);
95 }
96 convertOperators(q) {
97 if (Array.isArray(q)) {
98 return q.map(subQuery => this.convertOperators(subQuery));
99 }
100 if (!(0, utils_1.isPlainObject)(q)) {
101 return q;
102 }
103 const { operatorMap } = this.options;
104 const converted = Object.keys(q).reduce((result, prop) => {
105 const value = q[prop];
106 const key = (operatorMap[prop] ? operatorMap[prop] : prop);
107 result[key] = this.convertOperators(value);
108 return result;
109 }, {});
110 Object.getOwnPropertySymbols(q).forEach(symbol => {
111 converted[symbol] = q[symbol];
112 });
113 return converted;
114 }
115 filterQuery(params) {
116 const options = this.getOptions(params);
117 const { filters, query: _query } = (0, adapter_commons_1.filterQuery)(params.query || {}, options);
118 const query = this.convertOperators({
119 ..._query,
120 ...commons_1._.omit(filters, '$select', '$skip', '$limit', '$sort')
121 });
122 return {
123 filters,
124 query,
125 paginate: options.paginate
126 };
127 }
128 paramsToAdapter(id, _params) {
129 const params = _params || {};
130 if (id) {
131 const { query: where } = this.filterQuery(params);
132 const { and } = this.Op;
133 // Attach 'where' constraints, if any were used.
134 const q = Object.assign({
135 raw: this.raw,
136 where: Object.assign(where, {
137 [and]: where[and] ? [...where[and], { [this.id]: id }] : { [this.id]: id }
138 })
139 }, params.sequelize);
140 return q;
141 }
142 else {
143 const { filters, query: where } = this.filterQuery(params);
144 const order = (0, utils_1.getOrder)(filters.$sort);
145 const q = Object.assign({
146 where,
147 order,
148 limit: filters.$limit,
149 offset: filters.$skip,
150 raw: this.raw,
151 distinct: true
152 }, params.sequelize);
153 if (filters.$select) {
154 // Add the id to the select if it is not already there
155 if (!filters.$select.includes(this.id)) {
156 filters.$select.push(this.id);
157 }
158 q.attributes = filters.$select.map((select) => `${select}`);
159 }
160 // Until Sequelize fix all the findAndCount issues, a few 'hacks' are needed to get the total count correct
161 // Adding an empty include changes the way the count is done
162 // See: https://github.com/sequelize/sequelize/blob/7e441a6a5ca44749acd3567b59b1d6ceb06ae64b/lib/model.js#L1780-L1782
163 q.include = q.include || [];
164 return q;
165 }
166 }
167 async _getOrFind(id, _params) {
168 const params = _params || {};
169 if (id === null) {
170 return await this._find({
171 ...params,
172 paginate: false
173 });
174 }
175 return await this._get(id, params);
176 }
177 async _find(params = {}) {
178 const { paginate } = this.filterQuery(params);
179 const Model = this.ModelWithScope(params);
180 const q = this.paramsToAdapter(null, params);
181 try {
182 if (paginate && paginate.default) {
183 const result = await Model.findAndCountAll(q);
184 return {
185 total: result.count,
186 limit: q.limit,
187 skip: q.offset || 0,
188 data: result.rows
189 };
190 }
191 return await Model.findAll(q);
192 }
193 catch (err) {
194 return (0, utils_1.errorHandler)(err);
195 }
196 }
197 async _get(id, params = {}) {
198 const Model = this.ModelWithScope(params);
199 const q = this.paramsToAdapter(id, params);
200 // findById calls findAll under the hood. We use findAll so that
201 // eager loading can be used without a separate code path.
202 try {
203 const result = await Model.findAll(q);
204 if (result.length === 0) {
205 throw new errors_1.NotFound(`No record found for id '${id}'`);
206 }
207 const item = result[0];
208 return (0, adapter_commons_1.select)(params, this.id)(item);
209 }
210 catch (err) {
211 return (0, utils_1.errorHandler)(err);
212 }
213 }
214 async _create(data, params = {}) {
215 if (Array.isArray(data) && !this.allowsMulti('create', params)) {
216 throw new errors_1.MethodNotAllowed('Can not create multiple entries');
217 }
218 const options = Object.assign({ raw: this.raw }, params.sequelize);
219 // Model.create's `raw` option is different from other methods.
220 // In order to use `raw` consistently to serialize the result,
221 // we need to shadow the Model.create use of raw, which we provide
222 // access to by specifying `ignoreSetters`.
223 const ignoreSetters = !!options.ignoreSetters;
224 const createOptions = Object.assign({
225 returning: true
226 }, options, { raw: ignoreSetters });
227 const isArray = Array.isArray(data);
228 const Model = this.ModelWithScope(params);
229 try {
230 const result = isArray
231 ? await Model.bulkCreate(data, createOptions)
232 : await Model.create(data, createOptions);
233 const sel = (0, adapter_commons_1.select)(params, this.id);
234 if (options.raw === false) {
235 return result;
236 }
237 if (isArray) {
238 return result.map(item => sel(item.toJSON()));
239 }
240 return sel(result.toJSON());
241 }
242 catch (err) {
243 return (0, utils_1.errorHandler)(err);
244 }
245 }
246 async _patch(id, data, params = {}) {
247 var _a, _b;
248 if (id === null && !this.allowsMulti('patch', params)) {
249 throw new errors_1.MethodNotAllowed('Can not patch multiple entries');
250 }
251 const Model = this.ModelWithScope(params);
252 // Get a list of ids that match the id/query. Overwrite the
253 // $select because only the id is needed for this idList
254 const itemOrItems = await this._getOrFind(id, {
255 ...params,
256 query: {
257 ...params === null || params === void 0 ? void 0 : params.query,
258 $select: [this.id]
259 }
260 });
261 const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
262 const ids = items.map(item => item[this.id]);
263 try {
264 const seqOptions = Object.assign({ raw: this.raw }, params.sequelize, { where: { [this.id]: { [this.Op.in]: ids } } });
265 await Model.update(commons_1._.omit(data, this.id), seqOptions);
266 if (params.$returning === false) {
267 return Promise.resolve([]);
268 }
269 // Create a new query that re-queries all ids that
270 // were originally changed
271 const findParams = {
272 ...params,
273 query: {
274 [this.id]: { $in: ids },
275 ...(((_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 } : {})
276 }
277 };
278 const result = await this._getOrFind(id, findParams);
279 return (0, adapter_commons_1.select)(params, this.id)(result);
280 }
281 catch (err) {
282 return (0, utils_1.errorHandler)(err);
283 }
284 }
285 async _update(id, data, params = {}) {
286 var _a;
287 const query = Object.assign({}, this.filterQuery(params).query);
288 // Force the {raw: false} option as the instance is needed to properly update
289 const seqOptions = Object.assign({}, params.sequelize, { raw: false });
290 const instance = await this._get(id, { sequelize: seqOptions, query });
291 const itemToUpdate = Object.keys(instance.toJSON()).reduce((result, key) => {
292 // @ts-ignore
293 result[key] = key in data ? data[key] : null;
294 return result;
295 }, {});
296 try {
297 await instance.update(itemToUpdate, seqOptions);
298 const item = await this._get(id, {
299 sequelize: Object.assign({}, seqOptions, {
300 raw: typeof ((_a = params === null || params === void 0 ? void 0 : params.sequelize) === null || _a === void 0 ? void 0 : _a.raw) === 'boolean'
301 ? params.sequelize.raw
302 : this.raw
303 })
304 });
305 return (0, adapter_commons_1.select)(params, this.id)(item);
306 }
307 catch (err) {
308 return (0, utils_1.errorHandler)(err);
309 }
310 }
311 async _remove(id, params = {}) {
312 var _a;
313 if (id === null && !this.allowsMulti('remove', params)) {
314 throw new errors_1.MethodNotAllowed('Can not remove multiple entries');
315 }
316 const Model = this.ModelWithScope(params);
317 const findParams = { ...params };
318 if (params.$returning === false) {
319 findParams.query = {
320 ...findParams.query,
321 $select: [this.id]
322 };
323 }
324 else if ((_a = params.query) === null || _a === void 0 ? void 0 : _a.$select) {
325 findParams.query = {
326 ...findParams.query,
327 $select: [...params.query.$select, this.id]
328 };
329 }
330 const itemOrItems = await this._getOrFind(id, findParams);
331 const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
332 const ids = items.map(item => item[this.id]);
333 const seqOptions = Object.assign({ raw: this.raw }, params.sequelize, { where: { [this.id]: { [this.Op.in]: ids } } });
334 try {
335 await Model.destroy(seqOptions);
336 if (params.$returning === false) {
337 return [];
338 }
339 return (0, adapter_commons_1.select)(params, this.id)(itemOrItems);
340 }
341 catch (err) {
342 return (0, utils_1.errorHandler)(err);
343 }
344 }
345}
346exports.SequelizeAdapter = SequelizeAdapter;
347//# sourceMappingURL=adapter.js.map
\No newline at end of file