Skip to content

Commit e3eff0b

Browse files
Remove ModelWithScope and add handleError (#450)
* Remove ModelWithScope * Use handleError method * Fix spacing * Add semicolon * Better error handling * Update README
1 parent a4f1bcf commit e3eff0b

File tree

3 files changed

+64
-55
lines changed

3 files changed

+64
-55
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ A [Feathers](https://feathersjs.com) database adapter for [Sequelize](https://se
1616
- [`service(options)`](#serviceoptions)
1717
- [params.sequelize](#paramssequelize)
1818
- [operators](#operatormap)
19+
- [Modifying the model](#modifyModel)
1920
- [Caveats](#caveats)
2021
- [Sequelize `raw` queries](#sequelize-raw-queries)
2122
- [Working with MSSQL](#working-with-mssql)
@@ -154,6 +155,27 @@ app.service('users').find({
154155
GET /users?name[$like]=Dav%
155156
```
156157

158+
## Modifying the Model
159+
160+
Sequelize allows you to call methods like `Model.scope()`, `Model.schema()`, and others. To use these methods, extend the class to overwrite the `getModel` method.
161+
162+
```js
163+
const { SequelizeService } = require('feathers-sequelize');
164+
165+
class Service extends SequelizeService {
166+
getModel(params) {
167+
let Model = this.options.Model;
168+
if (params?.sequelize?.scope) {
169+
Model = Model.scope(params.sequelize.scope);
170+
}
171+
if (params?.sequelize?.schema) {
172+
Model = Model.schema(params.sequelize.schema);
173+
}
174+
return Model;
175+
}
176+
}
177+
```
178+
157179
## Caveats
158180
159181
### Sequelize `raw` queries

src/adapter.ts

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MethodNotAllowed, NotFound } from '@feathersjs/errors';
1+
import { MethodNotAllowed, GeneralError, NotFound } from '@feathersjs/errors';
22
import { _ } from '@feathersjs/commons';
33
import {
44
select as selector,
@@ -38,6 +38,21 @@ const defaultFilters = {
3838
$and: true as const
3939
}
4040

41+
const catchHandler = (handler: any) => {
42+
return (sequelizeError: any) => {
43+
try {
44+
errorHandler(sequelizeError);
45+
} catch (feathersError) {
46+
try {
47+
handler(feathersError, sequelizeError);
48+
} catch (error) {
49+
throw error;
50+
}
51+
}
52+
throw new GeneralError('`handleError` method must throw an error');
53+
}
54+
}
55+
4156
export class SequelizeAdapter<
4257
Result,
4358
Data = Partial<Result>,
@@ -46,11 +61,11 @@ export class SequelizeAdapter<
4661
> extends AdapterBase<Result, Data, PatchData, ServiceParams, SequelizeAdapterOptions> {
4762
constructor (options: SequelizeAdapterOptions) {
4863
if (!options.Model) {
49-
throw new Error('You must provide a Sequelize Model');
64+
throw new GeneralError('You must provide a Sequelize Model');
5065
}
5166

5267
if (options.operators && !Array.isArray(options.operators)) {
53-
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');
68+
throw new GeneralError('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');
5469
}
5570

5671
const operatorMap = {
@@ -99,7 +114,7 @@ export class SequelizeAdapter<
99114

100115
get Model () {
101116
if (!this.options.Model) {
102-
throw new Error('The Model getter was called with no Model provided in options!');
117+
throw new GeneralError('The Model getter was called with no Model provided in options!');
103118
}
104119

105120
return this.options.Model;
@@ -108,19 +123,12 @@ export class SequelizeAdapter<
108123
// eslint-disable-next-line @typescript-eslint/no-unused-vars
109124
getModel (_params?: ServiceParams) {
110125
if (!this.options.Model) {
111-
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?');
126+
throw new GeneralError('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?');
112127
}
113128

114129
return this.options.Model;
115130
}
116131

117-
ModelWithScope (params: ServiceParams) {
118-
const Model = this.getModel(params);
119-
if (params?.sequelize?.scope) {
120-
return Model.scope(params.sequelize.scope);
121-
}
122-
return Model;
123-
}
124132

125133
convertOperators (q: any): Query {
126134
if (Array.isArray(q)) {
@@ -222,23 +230,28 @@ export class SequelizeAdapter<
222230
return sequelize;
223231
}
224232

233+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
234+
handleError (feathersError: any, _sequelizeError: any) {
235+
throw feathersError;
236+
}
237+
225238
async _find (params?: ServiceParams & { paginate?: PaginationOptions }): Promise<Paginated<Result>>
226239
async _find (params?: ServiceParams & { paginate: false }): Promise<Result[]>
227240
async _find (params?: ServiceParams): Promise<Paginated<Result> | Result[]>
228241
async _find (params: ServiceParams = {} as ServiceParams): Promise<Paginated<Result> | Result[]> {
229-
const Model = this.ModelWithScope(params);
242+
const Model = this.getModel(params);
230243
const { paginate } = this.filterQuery(params);
231244
const sequelizeOptions = this.paramsToAdapter(null, params);
232245

233246
if (!paginate || !paginate.default) {
234-
const result = await Model.findAll(sequelizeOptions).catch(errorHandler);
247+
const result = await Model.findAll(sequelizeOptions).catch(catchHandler(this.handleError));
235248
return result;
236249
}
237250

238251
if (sequelizeOptions.limit === 0) {
239252
const total = (await Model
240253
.count({ ...sequelizeOptions, attributes: undefined })
241-
.catch(errorHandler)) as any as number;
254+
.catch(catchHandler(this.handleError))) as any as number;
242255

243256
return {
244257
total,
@@ -248,7 +261,7 @@ export class SequelizeAdapter<
248261
}
249262
}
250263

251-
const result = await Model.findAndCountAll(sequelizeOptions).catch(errorHandler);
264+
const result = await Model.findAndCountAll(sequelizeOptions).catch(catchHandler(this.handleError));
252265

253266
return {
254267
total: result.count,
@@ -259,9 +272,9 @@ export class SequelizeAdapter<
259272
}
260273

261274
async _get (id: Id, params: ServiceParams = {} as ServiceParams): Promise<Result> {
262-
const Model = this.ModelWithScope(params);
275+
const Model = this.getModel(params);
263276
const sequelizeOptions = this.paramsToAdapter(id, params);
264-
const result = await Model.findAll(sequelizeOptions).catch(errorHandler);
277+
const result = await Model.findAll(sequelizeOptions).catch(catchHandler(this.handleError));
265278
if (result.length === 0) {
266279
throw new NotFound(`No record found for id '${id}'`);
267280
}
@@ -283,13 +296,13 @@ export class SequelizeAdapter<
283296
return []
284297
}
285298

286-
const Model = this.ModelWithScope(params);
299+
const Model = this.getModel(params);
287300
const sequelizeOptions = this.paramsToAdapter(null, params);
288301

289302
if (isArray) {
290303
const instances = await Model
291304
.bulkCreate(data as any[], sequelizeOptions)
292-
.catch(errorHandler);
305+
.catch(catchHandler(this.handleError));
293306

294307
if (sequelizeOptions.returning === false) {
295308
return [];
@@ -318,7 +331,7 @@ export class SequelizeAdapter<
318331

319332
const result = await Model
320333
.create(data as any, sequelizeOptions as CreateOptions)
321-
.catch(errorHandler);
334+
.catch(catchHandler(this.handleError));
322335

323336
if (sequelizeOptions.raw) {
324337
return select((result as Model).toJSON())
@@ -334,7 +347,7 @@ export class SequelizeAdapter<
334347
throw new MethodNotAllowed('Can not patch multiple entries')
335348
}
336349

337-
const Model = this.ModelWithScope(params);
350+
const Model = this.getModel(params);
338351
const sequelizeOptions = this.paramsToAdapter(id, params);
339352
const select = selector(params, this.id);
340353
const values = _.omit(data, this.id);
@@ -361,7 +374,7 @@ export class SequelizeAdapter<
361374
raw: false,
362375
where: { [this.id]: ids.length === 1 ? ids[0] : { [Op.in]: ids } }
363376
} as UpdateOptions)
364-
.catch(errorHandler) as [number, Model[]?];
377+
.catch(catchHandler(this.handleError)) as [number, Model[]?];
365378

366379
if (sequelizeOptions.returning === false) {
367380
return []
@@ -436,7 +449,7 @@ export class SequelizeAdapter<
436449
await instance
437450
.set(values)
438451
.update(values, sequelizeOptions)
439-
.catch(errorHandler);
452+
.catch(catchHandler(this.handleError));
440453

441454
if (isPresent(sequelizeOptions.include)) {
442455
return this._get(id, {
@@ -462,7 +475,7 @@ export class SequelizeAdapter<
462475
}
463476

464477
async _update (id: Id, data: Data, params: ServiceParams = {} as ServiceParams): Promise<Result> {
465-
const Model = this.ModelWithScope(params);
478+
const Model = this.getModel(params);
466479
const sequelizeOptions = this.paramsToAdapter(id, params);
467480
const select = selector(params, this.id);
468481

@@ -484,7 +497,7 @@ export class SequelizeAdapter<
484497
await instance
485498
.set(values)
486499
.update(values, sequelizeOptions)
487-
.catch(errorHandler);
500+
.catch(catchHandler(this.handleError));
488501

489502
if (isPresent(sequelizeOptions.include)) {
490503
return this._get(id, {
@@ -516,7 +529,7 @@ export class SequelizeAdapter<
516529
throw new MethodNotAllowed('Can not remove multiple entries')
517530
}
518531

519-
const Model = this.ModelWithScope(params);
532+
const Model = this.getModel(params);
520533
const sequelizeOptions = this.paramsToAdapter(id, params);
521534

522535
if (id === null) {
@@ -539,7 +552,7 @@ export class SequelizeAdapter<
539552
await Model.destroy({
540553
...params.sequelize,
541554
where: { [this.id]: ids.length === 1 ? ids[0] : { [Op.in]: ids } }
542-
}).catch(errorHandler);
555+
}).catch(catchHandler(this.handleError));
543556

544557
if (sequelizeOptions.returning === false) {
545558
return [];

test/index.test.ts

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,7 @@ const Model = sequelize.define('people', {
111111
defaultValue: 'pending'
112112
}
113113
}, {
114-
freezeTableName: true,
115-
scopes: {
116-
active: {
117-
where: {
118-
status: 'active'
119-
}
120-
},
121-
pending: {
122-
where: {
123-
status: 'pending'
124-
}
125-
}
126-
}
114+
freezeTableName: true
127115
});
128116
const Order = sequelize.define('orders', {
129117
name: {
@@ -780,20 +768,6 @@ describe('Feathers Sequelize Service', () => {
780768
}
781769
});
782770
});
783-
784-
it('can set the scope of an operation #130', async () => {
785-
const people = app.service('people');
786-
const data = { name: 'Active', status: 'active' };
787-
const SCOPE_TO_ACTIVE = { sequelize: { scope: 'active' } };
788-
const SCOPE_TO_PENDING = { sequelize: { scope: 'pending' } };
789-
await people.create(data);
790-
791-
const staPeople = await people.find(SCOPE_TO_ACTIVE) as Paginated<any>;
792-
assert.strictEqual(staPeople.data.length, 1);
793-
794-
const stpPeople = await people.find(SCOPE_TO_PENDING) as Paginated<any>;
795-
assert.strictEqual(stpPeople.data.length, 0);
796-
});
797771
});
798772

799773
describe('ORM functionality', () => {

0 commit comments

Comments
 (0)