diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7237ddce4..feefb597c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,13 @@ /* eslint-disable import/export */ export * from './interfaces'; export * from './common'; -export { QueryService, AssemblerQueryService, RelationQueryService, QueryServiceRelation } from './services'; +export { + QueryService, + AssemblerQueryService, + RelationQueryService, + QueryServiceRelation, + getQueryServiceToken, +} from './services'; export { transformFilter, transformQuery, transformSort, QueryFieldMap } from './helpers'; export { ClassTransformerAssembler, diff --git a/packages/core/src/services/index.ts b/packages/core/src/services/index.ts index b448b8a2b..44461680b 100644 --- a/packages/core/src/services/index.ts +++ b/packages/core/src/services/index.ts @@ -1,3 +1,3 @@ -export { QueryService } from './query.service'; +export { QueryService, getQueryServiceToken } from './query.service'; export { AssemblerQueryService } from './assembler-query.service'; export { RelationQueryService, QueryServiceRelation } from './relation-query.service'; diff --git a/packages/core/src/services/query.service.ts b/packages/core/src/services/query.service.ts index e6b3e01d3..a90f91b0a 100644 --- a/packages/core/src/services/query.service.ts +++ b/packages/core/src/services/query.service.ts @@ -167,3 +167,7 @@ export function QueryService(DTOClass: Class) { return Injectable()(cls); }; } + +export function getQueryServiceToken(DTOClass: Class): string { + return `${DTOClass.name}QueryService`; +} diff --git a/packages/query-graphql/__tests__/module.spec.ts b/packages/query-graphql/__tests__/module.spec.ts new file mode 100644 index 000000000..190a80ef6 --- /dev/null +++ b/packages/query-graphql/__tests__/module.spec.ts @@ -0,0 +1,27 @@ +import { ObjectType } from '@nestjs/graphql'; +import { NestjsQueryGraphQLModule } from '../src'; +import { FilterableField } from '../src/decorators/filterable-field.decorator'; + +describe('NestjsQueryTypeOrmModule', () => { + @ObjectType() + class TestDTO { + @FilterableField() + name!: string; + } + + it('should create a module', () => { + const typeOrmModule = NestjsQueryGraphQLModule.forFeature({ + imports: [], + resolvers: [ + { + DTOClass: TestDTO, + EntityClass: TestDTO, + }, + ], + }); + expect(typeOrmModule.imports).toHaveLength(0); + expect(typeOrmModule.module).toBe(NestjsQueryGraphQLModule); + expect(typeOrmModule.providers).toHaveLength(1); + expect(typeOrmModule.exports).toHaveLength(1); + }); +}); diff --git a/packages/query-graphql/__tests__/providers.spec.ts b/packages/query-graphql/__tests__/providers.spec.ts new file mode 100644 index 000000000..c0ae29c16 --- /dev/null +++ b/packages/query-graphql/__tests__/providers.spec.ts @@ -0,0 +1,22 @@ +import { Class } from '@nestjs-query/core'; +import { NoOpQueryService } from '@nestjs-query/core/src/services/noop-query.service'; +import { ObjectType } from '@nestjs/graphql'; +import { FilterableField } from '../src/decorators'; +import { createResolvers } from '../src/providers'; +import { CRUDResolver } from '../src/resolvers'; + +describe('createTypeOrmQueryServiceProviders', () => { + @ObjectType() + class TestDTO { + @FilterableField() + name!: string; + } + + it('should create a provider for the entity', () => { + const providers = createResolvers([{ DTOClass: TestDTO, EntityClass: TestDTO }]); + expect(providers).toHaveLength(1); + const Provider = providers[0] as Class>; + expect(Provider.name).toBe('TestDTOAutoResolver'); + expect(new Provider(NoOpQueryService.getInstance())).toBeInstanceOf(Provider); + }); +}); diff --git a/packages/query-graphql/src/index.ts b/packages/query-graphql/src/index.ts index 669e50d10..2ecb62065 100644 --- a/packages/query-graphql/src/index.ts +++ b/packages/query-graphql/src/index.ts @@ -3,3 +3,5 @@ export { FilterableField, ResolverMethodOpts } from './decorators'; export * from './resolvers'; export * from './federation'; export { DTONamesOpts } from './common'; +export { NestjsQueryGraphQLModule } from './module'; +export { AutoResolverOpts } from './providers'; diff --git a/packages/query-graphql/src/module.ts b/packages/query-graphql/src/module.ts new file mode 100644 index 000000000..4b1c45287 --- /dev/null +++ b/packages/query-graphql/src/module.ts @@ -0,0 +1,19 @@ +import { DynamicModule } from '@nestjs/common'; +import { AutoResolverOpts, createResolvers } from './providers'; + +export interface NestjsQueryGraphqlModuleOpts { + imports: DynamicModule[]; + resolvers: AutoResolverOpts[]; +} + +export class NestjsQueryGraphQLModule { + static forFeature(opts: NestjsQueryGraphqlModuleOpts): DynamicModule { + const resolverProviders = createResolvers(opts.resolvers); + return { + module: NestjsQueryGraphQLModule, + imports: [...opts.imports], + providers: [...resolverProviders], + exports: [...resolverProviders, ...opts.imports], + }; + } +} diff --git a/packages/query-graphql/src/providers.ts b/packages/query-graphql/src/providers.ts new file mode 100644 index 000000000..04464b808 --- /dev/null +++ b/packages/query-graphql/src/providers.ts @@ -0,0 +1,37 @@ +import { Class, QueryService, getQueryServiceToken, AssemblerQueryService, AssemblerFactory } from '@nestjs-query/core'; +import { Provider, Inject } from '@nestjs/common'; +import { Resolver } from '@nestjs/graphql'; +import { CRUDResolver, CRUDResolverOpts } from './resolvers'; + +export interface AutoResolverOpts extends CRUDResolverOpts { + DTOClass: Class; + EntityClass: Class; +} + +const getResolverToken = (DTOClass: Class): string => `${DTOClass.name}AutoResolver`; + +function createResolver(resolverOpts: AutoResolverOpts): Provider { + const { DTOClass, EntityClass } = resolverOpts; + + class Service extends AssemblerQueryService { + constructor(service: QueryService) { + const assembler = AssemblerFactory.getAssembler(DTOClass, EntityClass); + super(assembler, service); + } + } + + @Resolver(() => DTOClass) + class AutoResolver extends CRUDResolver(DTOClass, resolverOpts) { + constructor(@Inject(getQueryServiceToken(resolverOpts.EntityClass)) service: QueryService) { + super(new Service(service)); + } + } + // need to set class name so DI works properly + Object.defineProperty(AutoResolver, 'name', { value: getResolverToken(DTOClass), writable: false }); + + return AutoResolver; +} + +export const createResolvers = (opts: AutoResolverOpts[]): Provider[] => { + return opts.map((opt) => createResolver(opt)); +}; diff --git a/packages/query-sequelize/__tests__/decorators/inject-sequelize-query-service.decorator.spec.ts b/packages/query-sequelize/__tests__/decorators/inject-sequelize-query-service.decorator.spec.ts index e9ef478b4..3aac9bafa 100644 --- a/packages/query-sequelize/__tests__/decorators/inject-sequelize-query-service.decorator.spec.ts +++ b/packages/query-sequelize/__tests__/decorators/inject-sequelize-query-service.decorator.spec.ts @@ -1,7 +1,7 @@ -import { QueryService } from '@nestjs-query/core'; +import { getQueryServiceToken, QueryService } from '@nestjs-query/core'; import * as nestjsCommon from '@nestjs/common'; import { Model } from 'sequelize-typescript'; -import { getSequelizeQueryServiceKey, InjectSequelizeQueryService } from '../../src/decorators'; +import { InjectSequelizeQueryService } from '../../src'; describe('@InjectSequelizeQueryService', () => { const injectSpy = jest.spyOn(nestjsCommon, 'Inject'); @@ -15,6 +15,6 @@ describe('@InjectSequelizeQueryService', () => { constructor(@InjectSequelizeQueryService(TestEntity) readonly service: QueryService) {} } expect(injectSpy).toBeCalledTimes(1); - expect(injectSpy).toBeCalledWith(getSequelizeQueryServiceKey(TestEntity)); + expect(injectSpy).toBeCalledWith(getQueryServiceToken(TestEntity)); }); }); diff --git a/packages/query-sequelize/__tests__/providers.spec.ts b/packages/query-sequelize/__tests__/providers.spec.ts index 55aff4d48..3ec2cce62 100644 --- a/packages/query-sequelize/__tests__/providers.spec.ts +++ b/packages/query-sequelize/__tests__/providers.spec.ts @@ -1,7 +1,7 @@ +import { getQueryServiceToken } from '@nestjs-query/core'; import { getModelToken } from '@nestjs/sequelize'; import { Model, Sequelize, Table, Column } from 'sequelize-typescript'; import * as core from '@nestjs-query/core'; -import { getSequelizeQueryServiceKey } from '../src/decorators'; import { createSequelizeQueryServiceProviders } from '../src/providers'; import { SequelizeQueryService } from '../src/services'; @@ -22,7 +22,7 @@ describe('createSequelizeQueryServiceProviders', () => { }); const providers = createSequelizeQueryServiceProviders([TestEntity]); expect(providers).toHaveLength(1); - expect(providers[0].provide).toBe(getSequelizeQueryServiceKey(TestEntity)); + expect(providers[0].provide).toBe(getQueryServiceToken(TestEntity)); expect(providers[0].inject).toEqual([getModelToken(TestEntity)]); expect(providers[0].useFactory(TestEntity)).toBeInstanceOf(SequelizeQueryService); expect(assemblerDeserializerSpy).toBeCalledTimes(1); diff --git a/packages/query-sequelize/src/decorators/decorators.utils.ts b/packages/query-sequelize/src/decorators/decorators.utils.ts deleted file mode 100644 index d97208dcd..000000000 --- a/packages/query-sequelize/src/decorators/decorators.utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Class } from '@nestjs-query/core'; - -export function getSequelizeQueryServiceKey(entity: Class): string { - return `${entity.name}SequelizeQueryService`; -} diff --git a/packages/query-sequelize/src/decorators/index.ts b/packages/query-sequelize/src/decorators/index.ts index efca4fdb5..98df7a2ea 100644 --- a/packages/query-sequelize/src/decorators/index.ts +++ b/packages/query-sequelize/src/decorators/index.ts @@ -1,2 +1 @@ export { InjectSequelizeQueryService } from './inject-sequelize-query-service.decorator'; -export { getSequelizeQueryServiceKey } from './decorators.utils'; diff --git a/packages/query-sequelize/src/decorators/inject-sequelize-query-service.decorator.ts b/packages/query-sequelize/src/decorators/inject-sequelize-query-service.decorator.ts index 0372b2a1c..cfc7e942f 100644 --- a/packages/query-sequelize/src/decorators/inject-sequelize-query-service.decorator.ts +++ b/packages/query-sequelize/src/decorators/inject-sequelize-query-service.decorator.ts @@ -1,7 +1,7 @@ +import { getQueryServiceToken } from '@nestjs-query/core'; import { Inject } from '@nestjs/common'; import { Model, ModelCtor } from 'sequelize-typescript'; -import { getSequelizeQueryServiceKey } from './decorators.utils'; export const InjectSequelizeQueryService = >( entity: ModelCtor, -): ParameterDecorator => Inject(getSequelizeQueryServiceKey(entity)); +): ParameterDecorator => Inject(getQueryServiceToken(entity)); diff --git a/packages/query-sequelize/src/index.ts b/packages/query-sequelize/src/index.ts index cf706d464..9ddcc0a65 100644 --- a/packages/query-sequelize/src/index.ts +++ b/packages/query-sequelize/src/index.ts @@ -1,3 +1,3 @@ export { SequelizeQueryService } from './services'; -export { InjectSequelizeQueryService, getSequelizeQueryServiceKey } from './decorators'; +export { InjectSequelizeQueryService } from './decorators'; export { NestjsQuerySequelizeModule } from './module'; diff --git a/packages/query-sequelize/src/providers.ts b/packages/query-sequelize/src/providers.ts index 7833e0800..e3fab874b 100644 --- a/packages/query-sequelize/src/providers.ts +++ b/packages/query-sequelize/src/providers.ts @@ -1,8 +1,7 @@ -import { AssemblerSerializer, AssemblerDeserializer } from '@nestjs-query/core'; +import { AssemblerSerializer, AssemblerDeserializer, getQueryServiceToken } from '@nestjs-query/core'; import { FactoryProvider } from '@nestjs/common'; import { ModelCtor, Model, SequelizeOptions } from 'sequelize-typescript'; import { getModelToken } from '@nestjs/sequelize'; -import { getSequelizeQueryServiceKey } from './decorators'; import { SequelizeQueryService } from './services'; function createSequelizeQueryServiceProvider( @@ -10,7 +9,7 @@ function createSequelizeQueryServiceProvider( connection?: SequelizeOptions | string, ): FactoryProvider { return { - provide: getSequelizeQueryServiceKey(EntityClass), + provide: getQueryServiceToken(EntityClass), useFactory(entity: ModelCtor) { AssemblerSerializer((instance) => instance.get({ plain: true }))(entity); AssemblerDeserializer((obj: object) => entity.build(obj) as Entity)(entity); diff --git a/packages/query-typeorm/__tests__/decorators/inject-typeorm-query-service.decorator.spec.ts b/packages/query-typeorm/__tests__/decorators/inject-typeorm-query-service.decorator.spec.ts index fc1f6cddd..16b136839 100644 --- a/packages/query-typeorm/__tests__/decorators/inject-typeorm-query-service.decorator.spec.ts +++ b/packages/query-typeorm/__tests__/decorators/inject-typeorm-query-service.decorator.spec.ts @@ -1,6 +1,7 @@ import { QueryService } from '@nestjs-query/core'; +import { getQueryServiceToken } from '@nestjs-query/core/src'; import * as nestjsCommon from '@nestjs/common'; -import { getTypeOrmQueryServiceKey, InjectTypeOrmQueryService } from '../../src/decorators'; +import { InjectTypeOrmQueryService } from '../../src/decorators'; describe('@InjectTypeOrmQueryService', () => { const injectSpy = jest.spyOn(nestjsCommon, 'Inject'); @@ -14,6 +15,6 @@ describe('@InjectTypeOrmQueryService', () => { constructor(@InjectTypeOrmQueryService(TestEntity) readonly service: QueryService) {} } expect(injectSpy).toBeCalledTimes(1); - expect(injectSpy).toBeCalledWith(getTypeOrmQueryServiceKey(TestEntity)); + expect(injectSpy).toBeCalledWith(getQueryServiceToken(TestEntity)); }); }); diff --git a/packages/query-typeorm/__tests__/providers.spec.ts b/packages/query-typeorm/__tests__/providers.spec.ts index 54c152117..a82289a5c 100644 --- a/packages/query-typeorm/__tests__/providers.spec.ts +++ b/packages/query-typeorm/__tests__/providers.spec.ts @@ -1,8 +1,8 @@ +import { getQueryServiceToken } from '@nestjs-query/core'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { mock, instance } from 'ts-mockito'; import { createTypeOrmQueryServiceProviders } from '../src/providers'; -import { getTypeOrmQueryServiceKey } from '../src/decorators'; import { TypeOrmQueryService } from '../src/services'; describe('createTypeOrmQueryServiceProviders', () => { @@ -11,7 +11,7 @@ describe('createTypeOrmQueryServiceProviders', () => { const mockRepo = mock>(Repository); const providers = createTypeOrmQueryServiceProviders([TestEntity]); expect(providers).toHaveLength(1); - expect(providers[0].provide).toBe(getTypeOrmQueryServiceKey(TestEntity)); + expect(providers[0].provide).toBe(getQueryServiceToken(TestEntity)); expect(providers[0].inject).toEqual([getRepositoryToken(TestEntity)]); expect(providers[0].useFactory(instance(mockRepo))).toBeInstanceOf(TypeOrmQueryService); }); diff --git a/packages/query-typeorm/src/decorators/decorators.utils.ts b/packages/query-typeorm/src/decorators/decorators.utils.ts deleted file mode 100644 index c9c64607b..000000000 --- a/packages/query-typeorm/src/decorators/decorators.utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Class } from '@nestjs-query/core'; - -export function getTypeOrmQueryServiceKey(entity: Class): string { - return `${entity.name}TypeOrmQueryService`; -} diff --git a/packages/query-typeorm/src/decorators/index.ts b/packages/query-typeorm/src/decorators/index.ts index 1d34aa3d2..e9a8fddc7 100644 --- a/packages/query-typeorm/src/decorators/index.ts +++ b/packages/query-typeorm/src/decorators/index.ts @@ -1,2 +1 @@ export { InjectTypeOrmQueryService } from './inject-typeorm-query-service.decorator'; -export { getTypeOrmQueryServiceKey } from './decorators.utils'; diff --git a/packages/query-typeorm/src/decorators/inject-typeorm-query-service.decorator.ts b/packages/query-typeorm/src/decorators/inject-typeorm-query-service.decorator.ts index ef748069b..fe6a06f0f 100644 --- a/packages/query-typeorm/src/decorators/inject-typeorm-query-service.decorator.ts +++ b/packages/query-typeorm/src/decorators/inject-typeorm-query-service.decorator.ts @@ -1,6 +1,5 @@ -import { Class } from '@nestjs-query/core'; +import { Class, getQueryServiceToken } from '@nestjs-query/core'; import { Inject } from '@nestjs/common'; -import { getTypeOrmQueryServiceKey } from './decorators.utils'; export const InjectTypeOrmQueryService = (entity: Class): ParameterDecorator => - Inject(getTypeOrmQueryServiceKey(entity)); + Inject(getQueryServiceToken(entity)); diff --git a/packages/query-typeorm/src/index.ts b/packages/query-typeorm/src/index.ts index 66d0e04fd..bdc22e3de 100644 --- a/packages/query-typeorm/src/index.ts +++ b/packages/query-typeorm/src/index.ts @@ -1,3 +1,3 @@ export { TypeOrmQueryService, TypeOrmQueryServiceOpts } from './services'; -export { InjectTypeOrmQueryService, getTypeOrmQueryServiceKey } from './decorators'; +export { InjectTypeOrmQueryService } from './decorators'; export { NestjsQueryTypeOrmModule } from './module'; diff --git a/packages/query-typeorm/src/providers.ts b/packages/query-typeorm/src/providers.ts index 27b62fb92..d00542d7b 100644 --- a/packages/query-typeorm/src/providers.ts +++ b/packages/query-typeorm/src/providers.ts @@ -1,8 +1,7 @@ -import { Class } from '@nestjs-query/core'; +import { Class, getQueryServiceToken } from '@nestjs-query/core'; import { FactoryProvider } from '@nestjs/common'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository, Connection, ConnectionOptions } from 'typeorm'; -import { getTypeOrmQueryServiceKey } from './decorators'; import { TypeOrmQueryService } from './services'; function createTypeOrmQueryServiceProvider( @@ -10,7 +9,7 @@ function createTypeOrmQueryServiceProvider( connection?: Connection | ConnectionOptions | string, ): FactoryProvider { return { - provide: getTypeOrmQueryServiceKey(EntityClass), + provide: getQueryServiceToken(EntityClass), useFactory(repo: Repository) { return new TypeOrmQueryService(repo); },