diff --git a/packages/plugins/trpc/src/generator.ts b/packages/plugins/trpc/src/generator.ts index 9a4f2e894..32530b836 100644 --- a/packages/plugins/trpc/src/generator.ts +++ b/packages/plugins/trpc/src/generator.ts @@ -89,35 +89,85 @@ function createAppRouter( const prismaImport = getPrismaClientImportSpec(zmodel, path.dirname(indexFile)); appRouter.addImportDeclarations([ { - namedImports: ['AnyRootConfig'], + namedImports: ['type AnyRootConfig', 'type Procedure', 'type ProcedureParams', 'type ProcedureType'], moduleSpecifier: '@trpc/server', }, { - namedImports: ['PrismaClient'], + namedImports: ['type PrismaClient', 'type Prisma'], moduleSpecifier: prismaImport, - isTypeOnly: true, }, { - namedImports: ['createRouterFactory', 'AnyRouter'], + namedImports: ['type createRouterFactory', 'AnyRouter'], moduleSpecifier: '@trpc/server/dist/core/router', }, { - namedImports: ['createBuilder'], + namedImports: ['type ProcedureBuilder'], moduleSpecifier: '@trpc/server/dist/core/internals/procedureBuilder', }, + { defaultImport: 'z', moduleSpecifier: 'zod', isTypeOnly: true }, ]); appRouter.addStatements(` + ${/** to be used by the other routers without making a bigger commit */ ''} + export { PrismaClient } from '${prismaImport}'; + export type BaseConfig = AnyRootConfig; export type RouterFactory = ReturnType< typeof createRouterFactory >; - export type ProcBuilder = ReturnType< - typeof createBuilder - >; + ${ + /** this is needed in order to prevent type errors between a procedure and a middleware-extended procedure */ '' + } + export type ProcBuilder = ProcedureBuilder<{ + _config: Config; + _ctx_out: Config['$types']['ctx']; + _input_in: any; + _input_out: any; + _output_in: any; + _output_out: any; + _meta: Config['$types']['meta']; + }>; + + type ExtractParamsFromProcBuilder> = + Builder extends ProcedureBuilder ? P : never; + type FromPromise

> = P extends Promise + ? T + : never; + + ${/** workaround to avoid creating 'typeof unsetMarker & object' on the procedure output */ ''} + type Join = A extends symbol ? B : A & B; + + ${ + /** you can name it whatever you want, but this is to make sure that + the types from the middleware and the procedure are correctly merged */ '' + } + export type ProcReturns< + PType extends ProcedureType, + PBuilder extends ProcBuilder, + ZType extends z.ZodType, + PPromise extends Prisma.PrismaPromise + > = Procedure< + PType, + ProcedureParams< + ExtractParamsFromProcBuilder["_config"], + ExtractParamsFromProcBuilder["_ctx_out"], + Join["_input_in"], z.infer>, + Join["_input_out"], z.infer>, + Join< + ExtractParamsFromProcBuilder["_output_in"], + FromPromise + >, + Join< + ExtractParamsFromProcBuilder["_output_out"], + FromPromise + >, + ExtractParamsFromProcBuilder["_meta"] + > + >; + export function db(ctx: any) { if (!ctx.prisma) { throw new Error('Missing "prisma" field in trpc context'); @@ -131,10 +181,10 @@ function createAppRouter( appRouter .addFunction({ - name: 'createRouter', + name: 'createRouter, Proc extends ProcBuilder>', parameters: [ - { name: 'router', type: 'RouterFactory' }, - { name: 'procedure', type: 'ProcBuilder' }, + { name: 'router', type: 'Router' }, + { name: 'procedure', type: 'Proc' }, ], isExported: true, }) @@ -159,7 +209,9 @@ function createAppRouter( moduleSpecifier: `./${model}.router`, }); - writer.writeLine(`${lowerCaseFirst(model)}: create${model}Router(router, procedure),`); + writer.writeLine( + `${lowerCaseFirst(model)}: create${model}Router(router, procedure),` + ); } }); writer.write(');'); @@ -231,7 +283,14 @@ function generateModelCreateRouter( modelRouter.addImportDeclarations([ { - namedImports: ['type RouterFactory', 'type ProcBuilder', 'type BaseConfig', 'db'], + namedImports: [ + 'type RouterFactory', + 'type ProcBuilder', + 'type BaseConfig', + 'type ProcReturns', + 'type PrismaClient', + 'db', + ], moduleSpecifier: '.', }, ]); @@ -243,10 +302,10 @@ function generateModelCreateRouter( } const createRouterFunc = modelRouter.addFunction({ - name: 'createRouter', + name: 'createRouter, Proc extends ProcBuilder>', parameters: [ - { name: 'router', type: 'RouterFactory' }, - { name: 'procedure', type: 'ProcBuilder' }, + { name: 'router', type: 'Router' }, + { name: 'procedure', type: 'Proc' }, ], isExported: true, isDefaultExport: true, diff --git a/packages/plugins/trpc/src/helpers.ts b/packages/plugins/trpc/src/helpers.ts index d56e42c9b..4bbe18460 100644 --- a/packages/plugins/trpc/src/helpers.ts +++ b/packages/plugins/trpc/src/helpers.ts @@ -21,14 +21,24 @@ export function generateProcedure( writer.write(` ${opType}: procedure.input(${typeName}).query(({ctx, input}) => checkRead(db(ctx).${lowerCaseFirst( modelName - )}.${prismaMethod}(input as any))), + )}.${prismaMethod}(input as any))) as ProcReturns< + "query", + Proc, + (typeof ${upperCaseFirst(modelName)}InputSchema)["${opType.replace('OrThrow', '')}"], + ReturnType + >, `); } else if (procType === 'mutation') { // the cast "as any" is to circumvent a TS compiler misfired error in certain cases writer.write(` ${opType}: procedure.input(${typeName}).mutation(async ({ctx, input}) => checkMutate(db(ctx).${lowerCaseFirst( modelName - )}.${prismaMethod}(input as any))), + )}.${prismaMethod}(input as any))) as ProcReturns< + "mutation", + Proc, + (typeof ${upperCaseFirst(modelName)}InputSchema)["${opType.replace('OrThrow', '')}"], + ReturnType + >, `); } }