From 92a51c120aa2bf6da915037628aad041fa0fc34c Mon Sep 17 00:00:00 2001 From: doug-martin Date: Mon, 29 Jun 2020 21:23:39 -0500 Subject: [PATCH] feat(sequelize): Add support for querying for nested relations --- .../__tests__/__fixtures__/seeds.ts | 2 + .../query/filter-query.builder.spec.ts | 38 ++++---- .../__tests__/query/where.builder.spec.ts | 2 +- .../services/sequelize-query.service.spec.ts | 97 +++++++++++++++++++ .../src/query/filter-query.builder.ts | 56 +++++++++-- .../src/query/sql-comparison.builder.ts | 3 +- .../src/query/where.builder.ts | 45 +++++++-- .../src/services/relation-query.service.ts | 24 +++-- .../src/services/sequelize-query.service.ts | 3 +- 9 files changed, 221 insertions(+), 49 deletions(-) diff --git a/packages/query-sequelize/__tests__/__fixtures__/seeds.ts b/packages/query-sequelize/__tests__/__fixtures__/seeds.ts index fee207ca3..7cb7291a4 100644 --- a/packages/query-sequelize/__tests__/__fixtures__/seeds.ts +++ b/packages/query-sequelize/__tests__/__fixtures__/seeds.ts @@ -34,11 +34,13 @@ export const PLAIN_TEST_RELATIONS: Pick< testRelationPk: `test-relations-${te.testEntityPk}-2`, relationName: `${te.stringType}-test-relation`, testEntityId: te.testEntityPk, + oneTestEntityId: null, }, { testRelationPk: `test-relations-${te.testEntityPk}-3`, relationName: `${te.stringType}-test-relation`, testEntityId: te.testEntityPk, + oneTestEntityId: null, }, ]; }, [] as Pick[]); diff --git a/packages/query-sequelize/__tests__/query/filter-query.builder.spec.ts b/packages/query-sequelize/__tests__/query/filter-query.builder.spec.ts index 469bb7e81..26a42c1b2 100644 --- a/packages/query-sequelize/__tests__/query/filter-query.builder.spec.ts +++ b/packages/query-sequelize/__tests__/query/filter-query.builder.spec.ts @@ -1,12 +1,12 @@ import { FindOptions, Op, UpdateOptions, DestroyOptions } from 'sequelize'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; +import { anything, instance, mock, verify, when, deepEqual } from 'ts-mockito'; import { Query, SortDirection, SortNulls } from '@nestjs-query/core'; import { TestEntity } from '../__fixtures__/test.entity'; import { FilterQueryBuilder, WhereBuilder } from '../../src/query'; describe('FilterQueryBuilder', (): void => { const getEntityQueryBuilder = (whereBuilder: WhereBuilder): FilterQueryBuilder => - new FilterQueryBuilder(whereBuilder); + new FilterQueryBuilder(TestEntity, whereBuilder); const assertFindOptions = ( query: Query, @@ -37,13 +37,13 @@ describe('FilterQueryBuilder', (): void => { it('should not call whereBuilder#build', () => { const mockWhereBuilder: WhereBuilder = mock(WhereBuilder); assertFindOptions({}, instance(mockWhereBuilder), {}); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should call whereBuilder#build if there is a filter', () => { const mockWhereBuilder: WhereBuilder = mock(WhereBuilder); const query = { filter: { stringType: { eq: 'foo' } } }; - when(mockWhereBuilder.build(query.filter)).thenCall(() => { + when(mockWhereBuilder.build(query.filter, deepEqual(new Map()))).thenCall(() => { return { [Op.and]: { stringType: 'foo' } }; }); assertFindOptions(query, instance(mockWhereBuilder), { @@ -56,7 +56,7 @@ describe('FilterQueryBuilder', (): void => { it('should apply empty paging args', () => { const mockWhereBuilder: WhereBuilder = mock(WhereBuilder); assertFindOptions({}, instance(mockWhereBuilder), {}); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply paging args going forward', () => { @@ -71,7 +71,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { limit: 10, offset: 11 }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply paging args going backward', () => { @@ -86,7 +86,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { limit: 10, offset: 10 }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply paging with just a limit', () => { @@ -100,7 +100,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { limit: 10 }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply paging with just an offset', () => { @@ -114,7 +114,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { offset: 10 }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); }); @@ -128,7 +128,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { order: [['numberType', 'ASC']] }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply ASC NULLS_FIRST sorting', () => { @@ -140,7 +140,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { order: [['numberType', 'ASC NULLS FIRST']] }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply ASC NULLS_LAST sorting', () => { @@ -152,7 +152,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { order: [['numberType', 'ASC NULLS LAST']] }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply DESC sorting', () => { @@ -164,7 +164,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { order: [['numberType', 'DESC']] }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply DESC NULLS_FIRST sorting', () => { @@ -187,7 +187,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { order: [['numberType', 'DESC NULLS LAST']] }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); it('should apply multiple sorts', () => { @@ -211,7 +211,7 @@ describe('FilterQueryBuilder', (): void => { ], }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); }); }); @@ -221,7 +221,7 @@ describe('FilterQueryBuilder', (): void => { it('should call whereBuilder#build if there is a filter', () => { const mockWhereBuilder: WhereBuilder = mock(WhereBuilder); const query = { filter: { stringType: { eq: 'foo' } } }; - when(mockWhereBuilder.build(query.filter)).thenCall(() => { + when(mockWhereBuilder.build(query.filter, deepEqual(new Map()))).thenCall(() => { return { [Op.and]: { stringType: 'foo' } }; }); assertUpdateOptions(query, instance(mockWhereBuilder), { @@ -241,7 +241,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { where: {}, limit: 10 }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); }); }); @@ -251,7 +251,7 @@ describe('FilterQueryBuilder', (): void => { it('should call whereBuilder#build if there is a filter', () => { const mockWhereBuilder: WhereBuilder = mock(WhereBuilder); const query = { filter: { stringType: { eq: 'foo' } } }; - when(mockWhereBuilder.build(query.filter)).thenCall(() => { + when(mockWhereBuilder.build(query.filter, deepEqual(new Map()))).thenCall(() => { return { [Op.and]: { stringType: 'foo' } }; }); assertDestroyOptions(query, instance(mockWhereBuilder), { @@ -271,7 +271,7 @@ describe('FilterQueryBuilder', (): void => { instance(mockWhereBuilder), { limit: 10 }, ); - verify(mockWhereBuilder.build(anything())).never(); + verify(mockWhereBuilder.build(anything(), anything())).never(); }); }); }); diff --git a/packages/query-sequelize/__tests__/query/where.builder.spec.ts b/packages/query-sequelize/__tests__/query/where.builder.spec.ts index f8ef0937c..8beb2d005 100644 --- a/packages/query-sequelize/__tests__/query/where.builder.spec.ts +++ b/packages/query-sequelize/__tests__/query/where.builder.spec.ts @@ -7,7 +7,7 @@ describe('WhereBuilder', (): void => { const createWhereBuilder = () => new WhereBuilder(); const assertSQL = (filter: Filter, expectedWhereOpts: WhereOptions): void => { - const actual = createWhereBuilder().build(filter); + const actual = createWhereBuilder().build(filter, new Map()); expect(actual).toEqual(expectedWhereOpts); }; diff --git a/packages/query-sequelize/__tests__/services/sequelize-query.service.spec.ts b/packages/query-sequelize/__tests__/services/sequelize-query.service.spec.ts index c90ec8381..5da382db0 100644 --- a/packages/query-sequelize/__tests__/services/sequelize-query.service.spec.ts +++ b/packages/query-sequelize/__tests__/services/sequelize-query.service.spec.ts @@ -51,6 +51,57 @@ describe('SequelizeQueryService', (): void => { const queryResult = await queryService.query({ filter: { stringType: { eq: 'foo1' } } }); return expect(queryResult.map((e) => e.get({ plain: true }))).toEqual([PLAIN_TEST_ENTITIES[0]]); }); + describe('filter on relations', () => { + describe('oneToOne', () => { + it('should allow filtering on a one to one relation', async () => { + const entity = PLAIN_TEST_ENTITIES[0]; + const queryService = moduleRef.get(TestEntityService); + const queryResult = await queryService.query({ + filter: { + oneTestRelation: { + testRelationPk: { + in: [`test-relations-${entity.testEntityPk}-1`, `test-relations-${entity.testEntityPk}-3`], + }, + }, + }, + }); + expect(queryResult.map((e) => e.get({ plain: true }))).toEqual([entity]); + }); + }); + + describe('manyToOne', () => { + it('should allow filtering on a many to one relation', async () => { + const queryService = moduleRef.get(TestRelationService); + const queryResults = await queryService.query({ + filter: { + testEntity: { + testEntityPk: { + in: [PLAIN_TEST_ENTITIES[0].testEntityPk, PLAIN_TEST_ENTITIES[1].testEntityPk], + }, + }, + }, + }); + expect(queryResults.map((e) => e.get({ plain: true }))).toEqual(PLAIN_TEST_RELATIONS.slice(0, 6)); + }); + }); + + describe('oneToMany', () => { + it('should allow filtering on a many to one relation', async () => { + const entity = PLAIN_TEST_ENTITIES[0]; + const queryService = moduleRef.get(TestEntityService); + const queryResult = await queryService.query({ + filter: { + testRelations: { + relationName: { + in: [PLAIN_TEST_RELATIONS[0].relationName, PLAIN_TEST_RELATIONS[1].relationName], + }, + }, + }, + }); + expect(queryResult.map((e) => e.get({ plain: true }))).toEqual([entity]); + }); + }); + }); }); describe('#count', () => { @@ -59,6 +110,52 @@ describe('SequelizeQueryService', (): void => { const queryResult = await queryService.count({ stringType: { like: 'foo%' } }); return expect(queryResult).toBe(10); }); + + describe('with relations', () => { + describe('oneToOne', () => { + it('should properly count the number of records with the associated relations', async () => { + const entity = PLAIN_TEST_ENTITIES[0]; + const queryService = moduleRef.get(TestEntityService); + const count = await queryService.count({ + oneTestRelation: { + testRelationPk: { + in: [`test-relations-${entity.testEntityPk}-1`, `test-relations-${entity.testEntityPk}-3`], + }, + }, + }); + expect(count).toEqual(1); + }); + }); + + describe('manyToOne', () => { + it('set the relation to null', async () => { + const queryService = moduleRef.get(TestRelationService); + const count = await queryService.count({ + testEntity: { + testEntityPk: { + in: [PLAIN_TEST_ENTITIES[0].testEntityPk, PLAIN_TEST_ENTITIES[2].testEntityPk], + }, + }, + }); + expect(count).toEqual(6); + }); + }); + + describe('oneToMany', () => { + it('set the relation to null', async () => { + const relation = PLAIN_TEST_RELATIONS[0]; + const queryService = moduleRef.get(TestEntityService); + const count = await queryService.count({ + testRelations: { + testEntityId: { + in: [relation.testEntityId as string], + }, + }, + }); + expect(count).toEqual(1); + }); + }); + }); }); describe('#queryRelations', () => { diff --git a/packages/query-sequelize/src/query/filter-query.builder.ts b/packages/query-sequelize/src/query/filter-query.builder.ts index df1e64b43..d535b2ee9 100644 --- a/packages/query-sequelize/src/query/filter-query.builder.ts +++ b/packages/query-sequelize/src/query/filter-query.builder.ts @@ -1,5 +1,15 @@ -import { Filter, Paging, Query, SortField } from '@nestjs-query/core'; -import { FindOptions, Filterable, DestroyOptions, Order, OrderItem, UpdateOptions, CountOptions } from 'sequelize'; +import { Filter, getFilterFields, Paging, Query, SortField } from '@nestjs-query/core'; +import { + FindOptions, + Filterable, + DestroyOptions, + Order, + OrderItem, + UpdateOptions, + CountOptions, + Association, +} from 'sequelize'; +import { Model, ModelCtor } from 'sequelize-typescript'; import { WhereBuilder } from './where.builder'; /** @@ -26,8 +36,11 @@ interface Pageable { * * Class that will convert a Query into a `sequelize` Query Builder. */ -export class FilterQueryBuilder { - constructor(readonly whereBuilder: WhereBuilder = new WhereBuilder()) {} +export class FilterQueryBuilder> { + constructor( + readonly model: ModelCtor, + readonly whereBuilder: WhereBuilder = new WhereBuilder(), + ) {} /** * Create a `sequelize` SelectQueryBuilder with `WHERE`, `ORDER BY` and `LIMIT/OFFSET` clauses. @@ -35,7 +48,7 @@ export class FilterQueryBuilder { * @param query - the query to apply. */ findOptions(query: Query): FindOptions { - let opts: FindOptions = {}; + let opts: FindOptions = this.applyAssociationIncludes({}, query.filter); opts = this.applyFilter(opts, query.filter); opts = this.applySorting(opts, query.sorting); opts = this.applyPaging(opts, query.paging); @@ -43,7 +56,8 @@ export class FilterQueryBuilder { } countOptions(query: Query): CountOptions { - let opts: CountOptions = {}; + let opts: CountOptions = this.applyAssociationIncludes({}, query.filter); + opts.distinct = true; opts = this.applyFilter(opts, query.filter); return opts; } @@ -103,7 +117,7 @@ export class FilterQueryBuilder { return filterable; } // eslint-disable-next-line no-param-reassign - filterable.where = this.whereBuilder.build(filter); + filterable.where = this.whereBuilder.build(filter, this.getReferencedRelations(filter)); return filterable; } @@ -129,4 +143,32 @@ export class FilterQueryBuilder { ); return qb; } + + private applyAssociationIncludes( + findOpts: Opts, + filter?: Filter, + ): Opts { + if (!filter) { + return findOpts; + } + const referencedRelations = this.getReferencedRelations(filter); + return [...referencedRelations.values()].reduce((find, association) => { + // eslint-disable-next-line no-param-reassign + find.include = [...(find.include || []), { association, attributes: [] }]; + return find; + }, findOpts); + } + + private getReferencedRelations(filter: Filter): Map { + const { relationNames } = this; + const referencedFields = getFilterFields(filter); + const referencedRelations = referencedFields.filter((f) => relationNames.includes(f)); + return referencedRelations.reduce((map, r) => { + return map.set(r, this.model.associations[r]); + }, new Map()); + } + + private get relationNames(): string[] { + return Object.keys(this.model.associations || {}); + } } diff --git a/packages/query-sequelize/src/query/sql-comparison.builder.ts b/packages/query-sequelize/src/query/sql-comparison.builder.ts index 1ba021a12..0b4407c27 100644 --- a/packages/query-sequelize/src/query/sql-comparison.builder.ts +++ b/packages/query-sequelize/src/query/sql-comparison.builder.ts @@ -47,8 +47,9 @@ export class SQLComparisonBuilder { field: F, cmp: FilterComparisonOperators, val: EntityComparisonField, + alias?: string, ): WhereOptions { - const col = `${field as string}`; + const col = alias ? `$${alias}.${field as string}$` : `${field as string}`; const normalizedCmp = (cmp as string).toLowerCase(); if (this.comparisonMap[normalizedCmp]) { // comparison operator (e.b. =, !=, >, <) diff --git a/packages/query-sequelize/src/query/where.builder.ts b/packages/query-sequelize/src/query/where.builder.ts index 8d60318a7..1193413ad 100644 --- a/packages/query-sequelize/src/query/where.builder.ts +++ b/packages/query-sequelize/src/query/where.builder.ts @@ -1,4 +1,4 @@ -import { WhereOptions, Op } from 'sequelize'; +import { WhereOptions, Op, Association } from 'sequelize'; import { Filter, FilterComparisonOperators, FilterComparisons, FilterFieldComparison } from '@nestjs-query/core'; import { EntityComparisonField, SQLComparisonBuilder } from './sql-comparison.builder'; @@ -12,19 +12,20 @@ export class WhereBuilder { /** * Builds a WHERE clause from a Filter. * @param filter - the filter to build the WHERE clause from. + * @param associations - map of associations that are included in the query. */ - build(filter: Filter): WhereOptions { + build(filter: Filter, associations: Map, alias?: string): WhereOptions { const { and, or } = filter; let ands: WhereOptions[] = []; let ors: WhereOptions[] = []; let whereOpts: WhereOptions = {}; if (and && and.length) { - ands = and.map((f) => this.build(f)); + ands = and.map((f) => this.build(f, associations, alias)); } if (or && or.length) { - ors = or.map((f) => this.build(f)); + ors = or.map((f) => this.build(f, associations, alias)); } - const filterAnds = this.filterFields(filter); + const filterAnds = this.filterFields(filter, associations, alias); if (filterAnds) { ands = [...ands, filterAnds]; } @@ -41,10 +42,21 @@ export class WhereBuilder { * Creates field comparisons from a filter. This method will ignore and/or properties. * @param filter - the filter with fields to create comparisons for. */ - private filterFields(filter: Filter): WhereOptions | undefined { + private filterFields( + filter: Filter, + associations: Map, + alias?: string, + ): WhereOptions | undefined { const ands = Object.keys(filter) .filter((f) => f !== 'and' && f !== 'or') - .map((field) => this.withFilterComparison(field as keyof Entity, this.getField(filter, field as keyof Entity))); + .map((field) => + this.withFilterComparison( + field as keyof Entity, + this.getField(filter, field as keyof Entity), + associations, + alias, + ), + ); if (ands.length === 1) { return ands[0]; } @@ -61,15 +73,28 @@ export class WhereBuilder { return obj[field] as FilterFieldComparison; } - private withFilterComparison(field: T, cmp: FilterFieldComparison): WhereOptions { + private withFilterComparison( + field: T, + cmp: FilterFieldComparison, + associations: Map, + alias?: string, + ): WhereOptions { + if (associations.has(field as string)) { + const wb = new WhereBuilder(); + return wb.build((cmp as unknown) as Filter, associations, field as string); + } + let colName = field; + if (alias && associations.has(alias)) { + colName = (associations.get(alias)?.target.rawAttributes[colName as string]?.field ?? colName) as T; + } const opts = Object.keys(cmp) as FilterComparisonOperators[]; if (opts.length === 1) { const cmpType = opts[0]; - return this.sqlComparisonBuilder.build(field, cmpType, cmp[cmpType] as EntityComparisonField); + return this.sqlComparisonBuilder.build(colName, cmpType, cmp[cmpType] as EntityComparisonField, alias); } return { [Op.or]: opts.map((cmpType) => - this.sqlComparisonBuilder.build(field, cmpType, cmp[cmpType] as EntityComparisonField), + this.sqlComparisonBuilder.build(colName, cmpType, cmp[cmpType] as EntityComparisonField, alias), ), }; } diff --git a/packages/query-sequelize/src/services/relation-query.service.ts b/packages/query-sequelize/src/services/relation-query.service.ts index 199d48e70..dbcc73ef6 100644 --- a/packages/query-sequelize/src/services/relation-query.service.ts +++ b/packages/query-sequelize/src/services/relation-query.service.ts @@ -58,8 +58,9 @@ export abstract class RelationQueryService { if (Array.isArray(dto)) { return this.batchQueryRelations(RelationClass, relationName, dto, query); } - const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName)); - const relationQueryBuilder = this.getRelationQueryBuilder(); + const relationEntity = this.getRelationEntity(relationName); + const assembler = AssemblerFactory.getAssembler(RelationClass, relationEntity); + const relationQueryBuilder = this.getRelationQueryBuilder(relationEntity); const relations = await this.ensureIsEntity(dto).$get( relationName as keyof Entity, relationQueryBuilder.findOptions(assembler.convertQuery(query)), @@ -90,8 +91,9 @@ export abstract class RelationQueryService { if (Array.isArray(dto)) { return this.batchCountRelations(RelationClass, relationName, dto, filter); } - const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName)); - const relationQueryBuilder = this.getRelationQueryBuilder(); + const relationEntity = this.getRelationEntity(relationName); + const assembler = AssemblerFactory.getAssembler(RelationClass, relationEntity); + const relationQueryBuilder = this.getRelationQueryBuilder(relationEntity); return this.ensureIsEntity(dto).$count( relationName, relationQueryBuilder.countOptions(assembler.convertQuery({ filter })), @@ -207,8 +209,8 @@ export abstract class RelationQueryService { return entity; } - getRelationQueryBuilder(): FilterQueryBuilder { - return new FilterQueryBuilder(); + getRelationQueryBuilder(model: ModelCtor): FilterQueryBuilder { + return new FilterQueryBuilder(model); } /** @@ -224,8 +226,9 @@ export abstract class RelationQueryService { entities: Entity[], query: Query, ): Promise> { - const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName)); - const relationQueryBuilder = this.getRelationQueryBuilder(); + const relationEntity = this.getRelationEntity(relationName); + const assembler = AssemblerFactory.getAssembler(RelationClass, relationEntity); + const relationQueryBuilder = this.getRelationQueryBuilder(relationEntity); const findOptions = relationQueryBuilder.findOptions(assembler.convertQuery(query)); return entities.reduce(async (mapPromise, e) => { const map = await mapPromise; @@ -248,8 +251,9 @@ export abstract class RelationQueryService { entities: Entity[], filter: Filter, ): Promise> { - const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName)); - const relationQueryBuilder = this.getRelationQueryBuilder(); + const relationEntity = this.getRelationEntity(relationName); + const assembler = AssemblerFactory.getAssembler(RelationClass, relationEntity); + const relationQueryBuilder = this.getRelationQueryBuilder(relationEntity); const findOptions = relationQueryBuilder.countOptions(assembler.convertQuery({ filter })); return entities.reduce(async (mapPromise, e) => { const map = await mapPromise; diff --git a/packages/query-sequelize/src/services/sequelize-query.service.ts b/packages/query-sequelize/src/services/sequelize-query.service.ts index 40063c932..33def161c 100644 --- a/packages/query-sequelize/src/services/sequelize-query.service.ts +++ b/packages/query-sequelize/src/services/sequelize-query.service.ts @@ -24,10 +24,11 @@ import { RelationQueryService } from './relation-query.service'; */ export class SequelizeQueryService> extends RelationQueryService implements QueryService { - readonly filterQueryBuilder: FilterQueryBuilder = new FilterQueryBuilder(); + readonly filterQueryBuilder: FilterQueryBuilder; constructor(readonly model: ModelCtor) { super(); + this.filterQueryBuilder = new FilterQueryBuilder(model); } /**