From 746dc1537d0c059be41317847f329e4404131811 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Tue, 17 May 2022 22:49:42 +0000 Subject: [PATCH 1/4] Dependency injection without ts metadata --- .vscode/settings.json | 3 +- api/index.ts | 5 +- api/tsyringe.config.ts | 10 ++- api/tsyringe.ts | 106 +++++++++++++++++++++++++++++++ api/user/passport-initializer.ts | 2 +- 5 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 api/tsyringe.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index c3e724b10..474996aeb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,13 @@ { "cSpell.words": [ "Biblatex", - "Nuxt", "booktitle", "codegen", "datetime", + "esbuild", "jiti", "journaltitle", + "Nuxt", "nuxtjs", "transpiled", "tsyringe", diff --git a/api/index.ts b/api/index.ts index 93c4d24ea..a94613e0b 100644 --- a/api/index.ts +++ b/api/index.ts @@ -2,16 +2,15 @@ import http from 'http' import express from 'express' import { ApolloServer } from 'apollo-server-express' import 'reflect-metadata' // Needed for tsyringe -import { container } from 'tsyringe' import { ApolloServerPluginDrainHttpServer, ApolloServerPluginLandingPageLocalDefault, } from 'apollo-server-core' import { Environment } from '../config' +import { resolve } from './tsyringe' import { configure as configureTsyringe } from './tsyringe.config' import { buildContext } from './context' import { loadSchema } from './schema' -import PassportInitializer from './user/passport-initializer' import config from '#config' // Create express instance @@ -25,7 +24,7 @@ const httpServer = http.createServer(app) // TODO: Replace this with await, once esbuild supports top-level await void configureTsyringe().then(() => { - const passportInitializer = container.resolve(PassportInitializer) + const passportInitializer = resolve('PassportInitializer') passportInitializer.initialize() passportInitializer.install(app) diff --git a/api/tsyringe.config.ts b/api/tsyringe.config.ts index 82624e6fe..c90b6db3a 100644 --- a/api/tsyringe.config.ts +++ b/api/tsyringe.config.ts @@ -1,15 +1,21 @@ import prisma from '@prisma/client' import { container, instanceCachingFactory } from 'tsyringe' +import { InjectionSymbols } from './tsyringe' +import PassportInitializer from './user/passport-initializer' import { createRedisClient } from './utils/services.factory' const { PrismaClient } = prisma export async function configure(): Promise { - container.register('PrismaClient', { + container.register(InjectionSymbols.PrismaClient.sym, { useFactory: instanceCachingFactory(() => new PrismaClient()), }) - container.register('RedisClient', { + container.register(InjectionSymbols.RedisClient.sym, { useValue: await createRedisClient(), }) + + container.register(InjectionSymbols.PassportInitializer.sym, { + useClass: PassportInitializer, + }) } diff --git a/api/tsyringe.ts b/api/tsyringe.ts new file mode 100644 index 000000000..5226ab8fc --- /dev/null +++ b/api/tsyringe.ts @@ -0,0 +1,106 @@ +import type { PrismaClient } from '@prisma/client' +import type { RedisClientType } from 'redis' +import { container, inject as tsyringeInject } from 'tsyringe' +import type { constructor } from 'tsyringe/dist/typings/types' +import type PassportInitializer from './user/passport-initializer' + +export { injectable } from 'tsyringe' + +type InjectionSymbol = { sym: symbol; value: T | undefined } + +/** + * Define a new injection token. + * @param name The name of the token. + * + * @note We use currying here as a workaround for the fact that the typescript compiler does not support partial inference: + * https://github.com/microsoft/TypeScript/issues/26242 + */ +function injectSymbol( + name: S +): () => Record> { + return () => { + const res = {} as Record> + res[name] = { sym: Symbol(name), value: undefined } + return res + } +} + +export const InjectionSymbols = { + ...injectSymbol('PrismaClient')(), + ...injectSymbol('RedisClient')>(), + ...injectSymbol('PassportInitializer')(), +} + +type Token = keyof typeof InjectionSymbols + +/** + * Parameter decorator factory that allows for interface information to be stored in the constructor's metadata. + * + * We use this approach using an explicit token instead of tsyringe's automatic detection of the type to not rely + * on typescript's metadata via `emitDecoratorMetadata` which is not supported by esbuild. + * + * @param token The token to inject. + * @return The parameter decorator. + * + * TODO: Restrict targets to be constructors whose corresponding arg has the correct type. + * Depends on https://github.com/Microsoft/TypeScript/issues/30102 and/or https://github.com/microsoft/TypeScript/issues/43132. + */ +export function inject( + token: Token +): (target: any, propertyKey: string | symbol, parameterIndex: number) => void { + return tsyringeInject(InjectionSymbols[token].sym) +} + +type TypeOfSymbol = T extends InjectionSymbol ? X : never +type InstanceTypeOrPlain = T extends constructor ? InstanceType : T +type ValueOfSymbol = InstanceTypeOrPlain> + +const injectionSymbolToConstructor = new Map>() +/** + * Resolve a token into an instance. + * + * @param token The dependency token. + * @return An instance of the dependency. + */ +export function resolve( + token: T +): ValueOfSymbol { + const symb = InjectionSymbols[token].sym + // If explicitly registered, use that + if (container.isRegistered(symb)) { + return container.resolve(symb) + } + + const constr = injectionSymbolToConstructor.get(symb) + if (constr !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return container.resolve(constr) + } else { + throw new Error(`No constructor registered for ${symb.toString()}`) + } +} +// https://stackoverflow.com/questions/51419176/how-to-get-a-subset-of-keyof-t-whose-value-tk-are-callable-functions-in-typ +type FilterByType = { + [P in keyof T as T[P] extends V ? P : never]: T[P] +} +type InjectionSymbolsWithValue = { + [K in keyof typeof InjectionSymbols]: TypeOfSymbol +} + +type ConstructableSymbols = { + [P in keyof FilterByType< + InjectionSymbolsWithValue, + constructor + >]: typeof InjectionSymbols[P] +} + +/** + * Register a constructor as fallback if no explicit value has been provided for the given token. + * TODO: Rename to register, provide all overloads, remove symbol -> constructor map + */ +export function fallback( + token: T, + target: TypeOfSymbol +): void { + injectionSymbolToConstructor.set(InjectionSymbols[token].sym, target) +} diff --git a/api/user/passport-initializer.ts b/api/user/passport-initializer.ts index 12ba41eb5..ad5a04ec2 100644 --- a/api/user/passport-initializer.ts +++ b/api/user/passport-initializer.ts @@ -3,7 +3,7 @@ import { Express } from 'express-serve-static-core' import session from 'express-session' import passport from 'passport' import { RedisClientType } from 'redis' -import { inject, injectable } from 'tsyringe' +import { inject, injectable } from './../tsyringe' import { AuthService } from './auth.service' import EmailStrategy from './auth.email.strategy' import config from '#config' From c65049f01ce7197ac005b1992f0b22cb8fa9dec0 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 18 May 2022 11:26:03 +0000 Subject: [PATCH 2/4] Finish it --- api/database/disconnect.ts | 5 +- api/documents/resolvers.spec.ts | 10 +- api/documents/resolvers.ts | 26 +++-- api/documents/user.document.service.spec.ts | 8 +- api/documents/user.document.service.ts | 2 +- api/groups/resolvers.ts | 14 +-- api/groups/service.ts | 2 +- api/tsyringe.config.ts | 48 +++++++-- api/tsyringe.ts | 103 +++++++++++++++++++- api/user/auth.service.ts | 2 +- api/user/passport-initializer.ts | 2 +- api/user/resolvers.ts | 27 ++--- nuxt.config.ts | 7 -- test/apollo.server.ts | 5 +- test/global.setup.ts | 9 +- 15 files changed, 198 insertions(+), 72 deletions(-) diff --git a/api/database/disconnect.ts b/api/database/disconnect.ts index e37e79385..a91bbbc06 100644 --- a/api/database/disconnect.ts +++ b/api/database/disconnect.ts @@ -1,6 +1,5 @@ -import { container } from 'tsyringe' -import type { PrismaClient } from '@prisma/client' +import { resolve } from './../tsyringe' export async function disconnect(): Promise { - await container.resolve('PrismaClient').$disconnect() + await resolve('PrismaClient').$disconnect() } diff --git a/api/documents/resolvers.spec.ts b/api/documents/resolvers.spec.ts index e0edafee7..a42b66648 100644 --- a/api/documents/resolvers.spec.ts +++ b/api/documents/resolvers.spec.ts @@ -1,14 +1,14 @@ -import { container } from 'tsyringe' import mocking from 'jest-mock-extended' import { UserDocument } from '@prisma/client' +import { register, resolve } from '../tsyringe' import { UserDocumentService } from './user.document.service' -import { Query, Mutation, DocumentResolver } from './resolvers' +import { DocumentResolver } from './resolvers' import { createUnauthenticatedContext } from '~/test/context.helper' const userDocumentService = mocking.mock() -container.registerInstance(UserDocumentService, userDocumentService) -const query = container.resolve(Query) -const mutation = container.resolve(Mutation) +register('UserDocumentService', { useValue: userDocumentService }) +const query = resolve('DocumentQuery') +const mutation = resolve('DocumentMutation') const context = createUnauthenticatedContext() diff --git a/api/documents/resolvers.ts b/api/documents/resolvers.ts index a7896dec8..3bf0d15dc 100644 --- a/api/documents/resolvers.ts +++ b/api/documents/resolvers.ts @@ -1,5 +1,4 @@ import { DocumentType, Prisma } from '@prisma/client' -import { container, injectable } from 'tsyringe' import { Context } from '../context' import { AddJournalArticleInput, @@ -17,6 +16,7 @@ import { UpdateUserDocumentInput, } from '../graphql' import { ResolveType } from '../utils/extractResolveType' +import { resolve, injectable, inject } from './../tsyringe' import { UserDocumentService, UserDocument } from './user.document.service' // Fields that are stored as separate columns in the database @@ -157,7 +157,10 @@ function convertDocumentInput( @injectable() export class Query { - constructor(private userDocumentService: UserDocumentService) {} + constructor( + @inject('UserDocumentService') + private userDocumentService: UserDocumentService + ) {} async userDocument( _root: Record, @@ -170,7 +173,10 @@ export class Query { @injectable() export class Mutation { - constructor(private userDocumentService: UserDocumentService) {} + constructor( + @inject('UserDocumentService') + private userDocumentService: UserDocumentService + ) {} async addUserDocument( _root: Record, @@ -308,12 +314,12 @@ export class OtherResolver extends DocumentResolver {} export function resolvers(): Resolvers { return { - Query: container.resolve(Query), - Mutation: container.resolve(Mutation), - Document: container.resolve(DocumentResolver), - JournalArticle: container.resolve(JournalArticleResolver), - ProceedingsArticle: container.resolve(ProceedingsArticleResolver), - Thesis: container.resolve(ThesisResolver), - Other: container.resolve(OtherResolver), + Query: resolve('DocumentQuery'), + Mutation: resolve('DocumentMutation'), + Document: resolve('DocumentResolver'), + JournalArticle: resolve('JournalArticleResolver'), + ProceedingsArticle: resolve('ProceedingsArticleResolver'), + Thesis: resolve('ThesisResolver'), + Other: resolve('OtherResolver'), } } diff --git a/api/documents/user.document.service.spec.ts b/api/documents/user.document.service.spec.ts index 62cfdeca4..0dc726fae 100644 --- a/api/documents/user.document.service.spec.ts +++ b/api/documents/user.document.service.spec.ts @@ -1,11 +1,11 @@ -import { container } from 'tsyringe' import { mockDeep, mockReset } from 'jest-mock-extended' import type { PrismaClient } from '@prisma/client' -import { UserDocument, UserDocumentService } from './user.document.service' +import { register, resolve } from '../tsyringe' +import { UserDocument } from './user.document.service' const prisma = mockDeep() -container.registerInstance('PrismaClient', prisma) -const userDocumentService = container.resolve(UserDocumentService) +register('PrismaClient', { useValue: prisma }) +const userDocumentService = resolve('UserDocumentService') const testDocument: UserDocument = { id: 'test', diff --git a/api/documents/user.document.service.ts b/api/documents/user.document.service.ts index 540788fde..5519f257c 100644 --- a/api/documents/user.document.service.ts +++ b/api/documents/user.document.service.ts @@ -7,8 +7,8 @@ import type { JournalIssue, Journal, } from '@prisma/client' -import { inject, injectable } from 'tsyringe' import { DocumentFilters, UserDocumentsConnection } from '../graphql' +import { inject, injectable } from './../tsyringe' export type UserDocument = PlainUserDocument & { other?: UserDocumentOtherField[] diff --git a/api/groups/resolvers.ts b/api/groups/resolvers.ts index 9ff737885..5367465b9 100644 --- a/api/groups/resolvers.ts +++ b/api/groups/resolvers.ts @@ -1,5 +1,4 @@ import { UserInputError } from 'apollo-server-errors' -import { container, injectable } from 'tsyringe' import prisma from '@prisma/client' import type { Group, GroupType as GroupTypeT } from '@prisma/client' import { Context } from '../context' @@ -9,6 +8,7 @@ import { MutationUpdateGroupArgs, MutationCreateGroupArgs, } from '../graphql' +import { resolve, injectable, inject } from './../tsyringe' import { GroupService } from './service' const { GroupType, GroupHierarchyType } = prisma @@ -21,7 +21,7 @@ export type GroupMaybeResolved = Group | GroupResolved @injectable() export class Query { - constructor(private groupService: GroupService) {} + constructor(@inject('GroupService') private groupService: GroupService) {} async group( _root: Record, @@ -34,7 +34,7 @@ export class Query { @injectable() export class Mutation { - constructor(private groupService: GroupService) {} + constructor(@inject('GroupService') private groupService: GroupService) {} async createGroup( _root: Record, @@ -153,7 +153,7 @@ export class Mutation { @injectable() export class GroupResolver { - constructor(private groupService: GroupService) {} + constructor(@inject('GroupService') private groupService: GroupService) {} __resolveType(group: GroupMaybeResolved): GroupTypeT { return group.type @@ -178,8 +178,8 @@ export class GroupResolver { export function resolvers(): Resolvers { return { - Query: container.resolve(Query), - Mutation: container.resolve(Mutation), - Group: container.resolve(GroupResolver), + Query: resolve('GroupQuery'), + Mutation: resolve('GroupMutation'), + Group: resolve('GroupResolver'), } } diff --git a/api/groups/service.ts b/api/groups/service.ts index c15204030..9265dd77a 100644 --- a/api/groups/service.ts +++ b/api/groups/service.ts @@ -1,5 +1,5 @@ import type { Group, Prisma, PrismaClient, User } from '@prisma/client' -import { inject, injectable } from 'tsyringe' +import { inject, injectable } from './../tsyringe' @injectable() export class GroupService { diff --git a/api/tsyringe.config.ts b/api/tsyringe.config.ts index c90b6db3a..e1f47981b 100644 --- a/api/tsyringe.config.ts +++ b/api/tsyringe.config.ts @@ -1,21 +1,53 @@ import prisma from '@prisma/client' -import { container, instanceCachingFactory } from 'tsyringe' -import { InjectionSymbols } from './tsyringe' +import * as DocumentResolvers from './documents/resolvers' +import { UserDocumentService } from './documents/user.document.service' +import * as GroupResolvers from './groups/resolvers' +import { GroupService } from './groups/service' +import { instanceCachingFactory, register } from './tsyringe' +import { AuthService } from './user/auth.service' import PassportInitializer from './user/passport-initializer' +import * as UserResolvers from './user/resolvers' import { createRedisClient } from './utils/services.factory' const { PrismaClient } = prisma export async function configure(): Promise { - container.register(InjectionSymbols.PrismaClient.sym, { + // Tools + register('PrismaClient', { useFactory: instanceCachingFactory(() => new PrismaClient()), }) - - container.register(InjectionSymbols.RedisClient.sym, { + register('RedisClient', { useValue: await createRedisClient(), }) + register('PassportInitializer', PassportInitializer) - container.register(InjectionSymbols.PassportInitializer.sym, { - useClass: PassportInitializer, - }) + // Services + register('UserDocumentService', UserDocumentService) + register('AuthService', AuthService) + register('GroupService', GroupService) + // Resolvers + register('DocumentQuery', DocumentResolvers.Query) + register('DocumentMutation', DocumentResolvers.Mutation) + register('DocumentResolver', DocumentResolvers.DocumentResolver) + register('JournalArticleResolver', DocumentResolvers.JournalArticleResolver) + register( + 'ProceedingsArticleResolver', + DocumentResolvers.ProceedingsArticleResolver + ) + register('ThesisResolver', DocumentResolvers.ThesisResolver) + register('OtherResolver', DocumentResolvers.OtherResolver) + + register('GroupQuery', GroupResolvers.Query) + register('GroupMutation', GroupResolvers.Mutation) + register('GroupResolver', GroupResolvers.GroupResolver) + + register('UserQuery', UserResolvers.Query) + register('UserMutation', UserResolvers.Mutation) + register('UserResolver', UserResolvers.UserResolver) + register('LoginPayloadResolver', UserResolvers.LoginPayloadResolver) + register('SignupPayloadResolver', UserResolvers.SignupPayloadResolver) + register( + 'ChangePasswordPayloadResolver', + UserResolvers.ChangePasswordPayloadResolver + ) } diff --git a/api/tsyringe.ts b/api/tsyringe.ts index 5226ab8fc..02be91275 100644 --- a/api/tsyringe.ts +++ b/api/tsyringe.ts @@ -1,10 +1,27 @@ import type { PrismaClient } from '@prisma/client' import type { RedisClientType } from 'redis' -import { container, inject as tsyringeInject } from 'tsyringe' -import type { constructor } from 'tsyringe/dist/typings/types' +import { + ClassProvider, + container, + DependencyContainer, + FactoryProvider, + inject as tsyringeInject, + Lifecycle, + Provider, + RegistrationOptions, + TokenProvider, + ValueProvider, +} from 'tsyringe' +import { constructor } from 'tsyringe/dist/typings/types' +import type * as DocumentResolvers from './documents/resolvers' +import type { UserDocumentService } from './documents/user.document.service' +import type * as GroupResolvers from './groups/resolvers' +import type { GroupService } from './groups/service' +import type { AuthService } from './user/auth.service' import type PassportInitializer from './user/passport-initializer' +import type * as UserResolvers from './user/resolvers' -export { injectable } from 'tsyringe' +export { injectable, instanceCachingFactory } from 'tsyringe' type InjectionSymbol = { sym: symbol; value: T | undefined } @@ -26,9 +43,45 @@ function injectSymbol( } export const InjectionSymbols = { + // Tools ...injectSymbol('PrismaClient')(), ...injectSymbol('RedisClient')>(), ...injectSymbol('PassportInitializer')(), + // Services + ...injectSymbol('UserDocumentService')(), + ...injectSymbol('AuthService')(), + ...injectSymbol('GroupService')(), + // Resolvers + ...injectSymbol('DocumentQuery')(), + ...injectSymbol('DocumentMutation')(), + ...injectSymbol('DocumentResolver')< + typeof DocumentResolvers.DocumentResolver + >(), + ...injectSymbol('JournalArticleResolver')< + typeof DocumentResolvers.JournalArticleResolver + >(), + ...injectSymbol('ProceedingsArticleResolver')< + typeof DocumentResolvers.ProceedingsArticleResolver + >(), + ...injectSymbol('ThesisResolver')(), + ...injectSymbol('OtherResolver')(), + + ...injectSymbol('GroupQuery')(), + ...injectSymbol('GroupMutation')(), + ...injectSymbol('GroupResolver')(), + + ...injectSymbol('UserQuery')(), + ...injectSymbol('UserMutation')(), + ...injectSymbol('UserResolver')(), + ...injectSymbol('LoginPayloadResolver')< + typeof UserResolvers.LoginPayloadResolver + >(), + ...injectSymbol('SignupPayloadResolver')< + typeof UserResolvers.SignupPayloadResolver + >(), + ...injectSymbol('ChangePasswordPayloadResolver')< + typeof UserResolvers.ChangePasswordPayloadResolver + >(), } type Token = keyof typeof InjectionSymbols @@ -54,6 +107,7 @@ export function inject( type TypeOfSymbol = T extends InjectionSymbol ? X : never type InstanceTypeOrPlain = T extends constructor ? InstanceType : T type ValueOfSymbol = InstanceTypeOrPlain> +type ValueOfToken = ValueOfSymbol const injectionSymbolToConstructor = new Map>() /** @@ -104,3 +158,46 @@ export function fallback( ): void { injectionSymbolToConstructor.set(InjectionSymbols[token].sym, target) } + +/** + * Register a dependency provider. + * + * @param provider {Provider} The dependency provider + */ +export function register( + token: T, + provider: ValueProvider> +): DependencyContainer +export function register( + token: T, + provider: FactoryProvider> +): DependencyContainer +export function register( + token: T, + provider: TokenProvider>, + options?: RegistrationOptions +): DependencyContainer +export function register( + token: T, + provider: ClassProvider>, + options?: RegistrationOptions +): DependencyContainer +export function register( + token: T, + provider: constructor>, + options?: RegistrationOptions +): DependencyContainer +export function register( + token: T, + providerOrConstructor: + | Provider> + | constructor>, + options: RegistrationOptions = { lifecycle: Lifecycle.Transient } +): DependencyContainer { + return container.register( + InjectionSymbols[token].sym, + // @ts-ignore: There is a problem with the overloads, don't know why + providerOrConstructor, + options + ) +} diff --git a/api/user/auth.service.ts b/api/user/auth.service.ts index 13b6c9f41..4dfd527ed 100644 --- a/api/user/auth.service.ts +++ b/api/user/auth.service.ts @@ -1,11 +1,11 @@ import type { PrismaClient, User } from '@prisma/client' -import { inject, injectable } from 'tsyringe' import uuid from 'uuid' // TODO: Change to { v4 as generateToken } as soon as uuid is a proper esm module / jest supports it (https://github.com/uuidjs/uuid/issues/451) import { RedisClientType } from 'redis' import { hash, verifyHash } from '../utils/crypto' import { sendEmail } from '../utils/sendEmail' import { resetPasswordTemplate } from '../utils/resetPasswordTemplate' import { ResolversTypes } from '../graphql' +import { inject, injectable } from './../tsyringe' export { InfoArgument as AuthenticationMessage } from 'graphql-passport' diff --git a/api/user/passport-initializer.ts b/api/user/passport-initializer.ts index ad5a04ec2..5c445155f 100644 --- a/api/user/passport-initializer.ts +++ b/api/user/passport-initializer.ts @@ -12,7 +12,7 @@ import { Environment } from '~/config' @injectable() export default class PassportInitializer { constructor( - private accountService: AuthService, + @inject('AuthService') private accountService: AuthService, @inject('RedisClient') private redisClient: RedisClientType ) {} diff --git a/api/user/resolvers.ts b/api/user/resolvers.ts index 593698a5d..8858bc810 100644 --- a/api/user/resolvers.ts +++ b/api/user/resolvers.ts @@ -1,5 +1,4 @@ import { User } from '@prisma/client' -import { container, injectable } from 'tsyringe' import { Context } from '../context' import { MutationLoginArgs, @@ -17,6 +16,7 @@ import { UserDocumentsResult, } from '../documents/user.document.service' import { GroupService } from '../groups/service' +import { resolve, injectable, inject } from './../tsyringe' import { AuthService, ChangePasswordPayload, @@ -27,7 +27,7 @@ import { @injectable() export class Query { - constructor(private authService: AuthService) {} + constructor(@inject('AuthService') private authService: AuthService) {} async user( _root: Record, @@ -48,7 +48,7 @@ export class Query { @injectable() export class Mutation { - constructor(private authService: AuthService) {} + constructor(@inject('AuthService') private authService: AuthService) {} async signup( _root: Record, @@ -118,7 +118,7 @@ export class Mutation { } @injectable() -class SignupPayloadResolver { +export class SignupPayloadResolver { __resolveType( signup: SignupPayload ): 'UserReturned' | 'InputValidationProblem' { @@ -130,7 +130,7 @@ class SignupPayloadResolver { } @injectable() -class ChangePasswordPayloadResolver { +export class ChangePasswordPayloadResolver { __resolveType( changePassword: ChangePasswordPayload ): 'UserReturned' | 'TokenProblem' | 'InputValidationProblem' { @@ -141,7 +141,7 @@ class ChangePasswordPayloadResolver { } @injectable() -class LoginPayloadResolver { +export class LoginPayloadResolver { __resolveType( login: LoginPayload ): 'UserReturned' | 'InputValidationProblem' { @@ -155,8 +155,9 @@ class LoginPayloadResolver { @injectable() export class UserResolver { constructor( + @inject('UserDocumentService') private userDocumentService: UserDocumentService, - private groupService: GroupService + @inject('GroupService') private groupService: GroupService ) {} async documents( @@ -208,11 +209,11 @@ export class UserResolver { export function resolvers(): Resolvers { return { - Query: container.resolve(Query), - Mutation: container.resolve(Mutation), - User: container.resolve(UserResolver), - LoginPayload: container.resolve(LoginPayloadResolver), - SignupPayload: container.resolve(SignupPayloadResolver), - ChangePasswordPayload: container.resolve(ChangePasswordPayloadResolver), + Query: resolve('UserQuery'), + Mutation: resolve('UserMutation'), + User: resolve('UserResolver'), + LoginPayload: resolve('LoginPayloadResolver'), + SignupPayload: resolve('SignupPayloadResolver'), + ChangePasswordPayload: resolve('ChangePasswordPayloadResolver'), } } diff --git a/nuxt.config.ts b/nuxt.config.ts index 5b620f48a..575bd44c3 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,5 +1,4 @@ import { defineNuxtConfig } from '@nuxt/bridge' -import typescript from 'rollup-plugin-typescript2' import { constructPrivateConfig, constructPublicConfig } from './config' export default defineNuxtConfig({ @@ -32,12 +31,6 @@ export default defineNuxtConfig({ }, nitro: { - hooks: { - 'nitro:rollup:before'(ctx) { - // Needed for emitting decorator metadata (which is not supported by esbuild) - ctx.rollupConfig?.plugins?.unshift(typescript()) - }, - }, // Prevent 'reflect-metadata' from being treeshaked (since we don't explicitly use the import it would otherwise be removed) moduleSideEffects: ['reflect-metadata'], }, diff --git a/test/apollo.server.ts b/test/apollo.server.ts index ca0cbbd8b..47f66cb3b 100644 --- a/test/apollo.server.ts +++ b/test/apollo.server.ts @@ -1,6 +1,5 @@ import { ApolloServer } from 'apollo-server-express' -import { container } from 'tsyringe' -import { AuthService } from '../api/user/auth.service' +import { resolve } from './../api/tsyringe' import { loadSchema } from '~/api/schema' export function createAuthenticatedClient(): ApolloServer { @@ -8,7 +7,7 @@ export function createAuthenticatedClient(): ApolloServer { schema: loadSchema(), context: () => ({ getUser: () => - container.resolve(AuthService).getUserById('ckn4oul7100004cv7y3t94n8j'), + resolve('AuthService').getUserById('ckn4oul7100004cv7y3t94n8j'), }), }) } diff --git a/test/global.setup.ts b/test/global.setup.ts index 20f90ff45..745ce50a9 100644 --- a/test/global.setup.ts +++ b/test/global.setup.ts @@ -1,21 +1,20 @@ -import { RedisClientType } from 'redis' import prisma from '@prisma/client' -import { container, instanceCachingFactory } from 'tsyringe' import dotenv from 'dotenv' +import { register, resolve, instanceCachingFactory } from '~/api/tsyringe' import { createRedisClient } from '~/api/utils/services.factory' // Register services for all tests -container.register('RedisClient', { +register('RedisClient', { useValue: await createRedisClient(), }) afterAll(async () => { - await container.resolve('RedisClient').quit() + await resolve('RedisClient').quit() }) // Setup services for integration tests // @ts-ignore: Jest doesn't allow an easy way to add typescript info if (global.isIntegrationTest) { - container.register('PrismaClient', { + register('PrismaClient', { useFactory: instanceCachingFactory(() => new prisma.PrismaClient()), }) } From 7fb43a047e540d511ce1aaec9d26b675550d07fe Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 18 May 2022 11:47:20 +0000 Subject: [PATCH 3/4] Remove rollup-plugin-typescript2 --- package.json | 1 - yarn.lock | 87 +++++----------------------------------------------- 2 files changed, 8 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index 7801d0e88..04864c4ea 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,6 @@ "prettier": "^2.6.2", "prisma": "3.14.0", "redis-mock": "^0.56.3", - "rollup-plugin-typescript2": "^0.31.2", "ts-jest": "^28.0.2", "vue-tsc": "^0.34.13" }, diff --git a/yarn.lock b/yarn.lock index 0033dbdec..3e3c8cb5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5301,14 +5301,6 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@ts-type/package-dts@^1.0.58": - version "1.0.58" - resolved "https://registry.yarnpkg.com/@ts-type/package-dts/-/package-dts-1.0.58.tgz#75f6fdf5f1e8f262a5081b90346439b4c4bc8d01" - integrity sha512-Ry5RPZDAnSz/gyLtjd2a2yNC07CZ/PCOsuDzYj3phOolIgEH68HXRw6SbsDlavnVUEenDYj5GUM10gQ5iVEbVQ== - dependencies: - "@types/semver" "^7.3.9" - ts-type "^2.1.4" - "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -5859,11 +5851,6 @@ dependencies: "@types/node" "*" -"@types/semver@^7.3.9": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" - integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== - "@types/serve-static@*", "@types/serve-static@1.13.9": version "1.13.9" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" @@ -6664,16 +6651,6 @@ resolved "https://registry.yarnpkg.com/@yaireo/tagify/-/tagify-4.12.0.tgz#4e5cd6d37fa80cb70f029dce879843b047844e3d" integrity sha512-3PErDt0dWGsrpxFcfoCXiCKJDil4J8hdts2GApS7z6KAKkkBMYxD9pSyF28TdkdehcuC/E64TMVwU9e2TdjTxw== -"@yarn-tool/resolve-package@^1.0.40": - version "1.0.42" - resolved "https://registry.yarnpkg.com/@yarn-tool/resolve-package/-/resolve-package-1.0.42.tgz#4a72c1a77b7035dc86250744d2cdbc16292bc4f8" - integrity sha512-1BAsoiD6jGAaPc7mRH0UxIVXgRSTv7fnhwfKkaFUYpqsU4ZR7KIigZTMcb2bujtlzKQbNneMPQGjiqe3F8cmlw== - dependencies: - "@ts-type/package-dts" "^1.0.58" - pkg-dir "< 6 >= 5" - tslib "^2.3.1" - upath2 "^3.1.12" - "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -11292,7 +11269,7 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.0.0, find-cache-dir@^3.3.1, find-cache-dir@^3.3.2: +find-cache-dir@^3.0.0, find-cache-dir@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== @@ -16724,13 +16701,6 @@ path-is-absolute@1.0.1, path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-network-drive@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/path-is-network-drive/-/path-is-network-drive-1.0.13.tgz#c9aa0183eb72c328aa83f43def93ddcb9d7ec4d4" - integrity sha512-Hg74mRN6mmXV+gTm3INjFK40ncAmC/Lo4qoQaSZ+GT3hZzlKdWQSqAjqyPeW0SvObP2W073WyYEBWY9d3wOm3A== - dependencies: - tslib "^2.3.1" - path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -16758,13 +16728,6 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-strip-sep@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/path-strip-sep/-/path-strip-sep-1.0.10.tgz#2be4e789406b298af8709ff79af716134b733b98" - integrity sha512-JpCy+8LAJQQTO1bQsb/84s1g+/Stm3h39aOpPRBQ/paMUGVPPZChLTOTKHoaCkc/6sKuF7yVsnq5Pe1S6xQGcA== - dependencies: - tslib "^2.3.1" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -16868,13 +16831,6 @@ pirates@^4.0.4: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw== -"pkg-dir@< 6 >= 5", pkg-dir@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" - integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== - dependencies: - find-up "^5.0.0" - pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -16896,6 +16852,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + pkg-types@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-0.3.2.tgz#1b3244b561745591035517475bc8af9c5e089e47" @@ -19180,18 +19143,6 @@ rollup-plugin-terser@^7.0.2: serialize-javascript "^4.0.0" terser "^5.0.0" -rollup-plugin-typescript2@^0.31.2: - version "0.31.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.2.tgz#463aa713a7e2bf85b92860094b9f7fb274c5a4d8" - integrity sha512-hRwEYR1C8xDGVVMFJQdEVnNAeWRvpaY97g5mp3IeLnzhNXzSVq78Ye/BJ9PAaUfN4DXa/uDnqerifMOaMFY54Q== - dependencies: - "@rollup/pluginutils" "^4.1.2" - "@yarn-tool/resolve-package" "^1.0.40" - find-cache-dir "^3.3.2" - fs-extra "^10.0.0" - resolve "^1.20.0" - tslib "^2.3.1" - rollup-plugin-visualizer@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.6.0.tgz#06aa7cf3fd504a29d404335700f2a3f28ebb33f3" @@ -20821,14 +20772,6 @@ ts-pnp@^1.1.6: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== -ts-type@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/ts-type/-/ts-type-2.1.4.tgz#d268d52ac054ef3076bf1c3b2fde0d4d5496e6a3" - integrity sha512-wnajiiIMhn/RHJ1oPld95siKmMJrOgaT6+rMmC8vO1LORgDFEzKP2nBmEFM5b4XVe7Q0J5KcU9oRJFzju7UzrA== - dependencies: - tslib "^2.3.1" - typedarray-dts "^1.0.0" - tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -20935,11 +20878,6 @@ type-is@^1.6.16, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typedarray-dts@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typedarray-dts/-/typedarray-dts-1.0.0.tgz#9dec9811386dbfba964c295c2606cf9a6b982d06" - integrity sha512-Ka0DBegjuV9IPYFT1h0Qqk5U4pccebNIJCGl8C5uU7xtOs+jpJvKGAY4fHGK25hTmXZOEUl9Cnsg5cS6K/b5DA== - typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -21340,15 +21278,6 @@ untyped@^0.4.4: "@babel/types" "^7.17.0" scule "^0.2.1" -upath2@^3.1.12: - version "3.1.12" - resolved "https://registry.yarnpkg.com/upath2/-/upath2-3.1.12.tgz#441b3dfbadde21731017bd1b7beb169498efd0a9" - integrity sha512-yC3eZeCyCXFWjy7Nu4pgjLhXNYjuzuUmJiRgSSw6TJp8Emc+E4951HGPJf+bldFC5SL7oBLeNbtm1fGzXn2gxw== - dependencies: - path-is-network-drive "^1.0.13" - path-strip-sep "^1.0.10" - tslib "^2.3.1" - upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" From 080d66db1e75469d16b48fe567178c2898e388bd Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 18 May 2022 11:51:43 +0000 Subject: [PATCH 4/4] Fix tests --- api/tsyringe.config.ts | 5 +++++ test/global.setup.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/api/tsyringe.config.ts b/api/tsyringe.config.ts index e1f47981b..04562d1e8 100644 --- a/api/tsyringe.config.ts +++ b/api/tsyringe.config.ts @@ -19,6 +19,11 @@ export async function configure(): Promise { register('RedisClient', { useValue: await createRedisClient(), }) + registerClasses() +} + +export function registerClasses(): void { + // Tools register('PassportInitializer', PassportInitializer) // Services diff --git a/test/global.setup.ts b/test/global.setup.ts index 745ce50a9..d458cf625 100644 --- a/test/global.setup.ts +++ b/test/global.setup.ts @@ -1,9 +1,11 @@ import prisma from '@prisma/client' import dotenv from 'dotenv' -import { register, resolve, instanceCachingFactory } from '~/api/tsyringe' +import { instanceCachingFactory, register, resolve } from '~/api/tsyringe' +import { registerClasses } from '~/api/tsyringe.config' import { createRedisClient } from '~/api/utils/services.factory' // Register services for all tests +registerClasses() register('RedisClient', { useValue: await createRedisClient(), })