Skip to content

Commit 40ea9fa

Browse files
authored
feat(trpc): trpc v11 support (#1656)
1 parent 1d546d4 commit 40ea9fa

39 files changed

+8628
-58
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* eslint-disable */
2+
3+
import type { AnyTRPCRouter as AnyRouter } from '@trpc/server';
4+
import type { NextPageContext } from 'next';
5+
import { type CreateTRPCNext, createTRPCNext as _createTRPCNext } from '@trpc/next';
6+
import type { DeepOverrideAtPath } from './utils';
7+
import type { ClientType } from '../routers';
8+
9+
export function createTRPCNext<
10+
TRouter extends AnyRouter,
11+
TPath extends string | undefined = undefined,
12+
TSSRContext extends NextPageContext = NextPageContext
13+
>(opts: Parameters<typeof _createTRPCNext>[0]) {
14+
const r: CreateTRPCNext<TRouter, TSSRContext> = _createTRPCNext<TRouter, TSSRContext>(opts);
15+
return r as DeepOverrideAtPath<CreateTRPCNext<TRouter, TSSRContext>, ClientType<TRouter>, TPath>;
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* eslint-disable */
2+
3+
import type { AnyTRPCRouter as AnyRouter } from '@trpc/server';
4+
import type { CreateTRPCReactOptions } from '@trpc/react-query/shared';
5+
import { type CreateTRPCReact, createTRPCReact as _createTRPCReact } from '@trpc/react-query';
6+
import type { DeepOverrideAtPath } from './utils';
7+
import type { ClientType } from '../routers';
8+
9+
export function createTRPCReact<
10+
TRouter extends AnyRouter,
11+
TPath extends string | undefined = undefined,
12+
TSSRContext = unknown
13+
>(opts?: CreateTRPCReactOptions<TRouter>) {
14+
const r: CreateTRPCReact<TRouter, TSSRContext> = _createTRPCReact<TRouter, TSSRContext>(opts);
15+
return r as DeepOverrideAtPath<CreateTRPCReact<TRouter, TSSRContext>, ClientType<TRouter>, TPath>;
16+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/* eslint-disable */
2+
3+
// inspired by: https://stackoverflow.com/questions/70632026/generic-to-recursively-modify-a-given-type-interface-in-typescript
4+
5+
type Primitive = string | Function | number | boolean | Symbol | undefined | null;
6+
7+
/**
8+
* Recursively merges `T` and `R`. If there's a shared key, use `R`'s field type to overwrite `T`.
9+
*/
10+
export type DeepOverride<T, R> = T extends Primitive
11+
? R
12+
: R extends Primitive
13+
? R
14+
: {
15+
[K in keyof T]: K extends keyof R ? DeepOverride<T[K], R[K]> : T[K];
16+
} & {
17+
[K in Exclude<keyof R, keyof T>]: R[K];
18+
};
19+
20+
/**
21+
* Traverse to `Path` (denoted by dot separated string literal type) in `T`, and starting from there,
22+
* recursively merge with `R`.
23+
*/
24+
export type DeepOverrideAtPath<T, R, Path extends string | undefined = undefined> = Path extends undefined
25+
? DeepOverride<T, R>
26+
: Path extends `${infer P1}.${infer P2}`
27+
? P1 extends keyof T
28+
? Omit<T, P1> & Record<P1, DeepOverride<T[P1], DeepOverrideAtPath<T[P1], R, P2>>>
29+
: never
30+
: Path extends keyof T
31+
? Omit<T, Path> & Record<Path, DeepOverride<T[Path], R>>
32+
: never;

packages/plugins/trpc/src/generator.ts

Lines changed: 139 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
5050
outDir = resolvePath(outDir, options);
5151
ensureEmptyDir(outDir);
5252

53+
const version = typeof options.version === 'string' ? options.version : 'v10';
54+
if (!['v10', 'v11'].includes(version)) {
55+
throw new PluginError(name, `Unsupported tRPC version "${version}". Use "v10" (default) or "v11".`);
56+
}
57+
58+
if (version === 'v11') {
59+
// v11 require options for importing `createTRPCRouter` and `procedure`
60+
const importCreateRouter = options.importCreateRouter as string;
61+
if (!importCreateRouter) {
62+
throw new PluginError(name, `Option "importCreateRouter" is required for tRPC v11`);
63+
}
64+
65+
const importProcedure = options.importProcedure as string;
66+
if (!importProcedure) {
67+
throw new PluginError(name, `Option "importProcedure" is required for tRPC v11`);
68+
}
69+
}
70+
5371
const prismaClientDmmf = dmmf;
5472

5573
let modelOperations = prismaClientDmmf.mappings.modelOperations;
@@ -71,8 +89,10 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.
7189
generateClientHelpers,
7290
model,
7391
zodSchemasImport,
74-
options
92+
options,
93+
version
7594
);
95+
7696
createHelper(outDir);
7797

7898
await saveProject(project);
@@ -86,7 +106,8 @@ function createAppRouter(
86106
generateClientHelpers: string[] | undefined,
87107
zmodel: Model,
88108
zodSchemasImport: string,
89-
options: PluginOptions
109+
options: PluginOptions,
110+
version: string
90111
) {
91112
const indexFile = path.resolve(outDir, 'routers', `index.ts`);
92113
const appRouter = project.createSourceFile(indexFile, undefined, {
@@ -96,31 +117,45 @@ function createAppRouter(
96117
appRouter.addStatements('/* eslint-disable */');
97118

98119
const prismaImport = getPrismaClientImportSpec(path.dirname(indexFile), options);
120+
121+
if (version === 'v10') {
122+
appRouter.addImportDeclarations([
123+
{
124+
namedImports: [
125+
'unsetMarker',
126+
'AnyRouter',
127+
'AnyRootConfig',
128+
'CreateRouterInner',
129+
'Procedure',
130+
'ProcedureBuilder',
131+
'ProcedureParams',
132+
'ProcedureRouterRecord',
133+
'ProcedureType',
134+
],
135+
isTypeOnly: true,
136+
moduleSpecifier: '@trpc/server',
137+
},
138+
]);
139+
} else {
140+
appRouter.addImportDeclarations([
141+
{
142+
namedImports: ['AnyTRPCRouter as AnyRouter'],
143+
isTypeOnly: true,
144+
moduleSpecifier: '@trpc/server',
145+
},
146+
]);
147+
}
148+
99149
appRouter.addImportDeclarations([
100-
{
101-
namedImports: [
102-
'unsetMarker',
103-
'AnyRouter',
104-
'AnyRootConfig',
105-
'CreateRouterInner',
106-
'Procedure',
107-
'ProcedureBuilder',
108-
'ProcedureParams',
109-
'ProcedureRouterRecord',
110-
'ProcedureType',
111-
],
112-
isTypeOnly: true,
113-
moduleSpecifier: '@trpc/server',
114-
},
115150
{
116151
namedImports: ['PrismaClient'],
117152
isTypeOnly: true,
118153
moduleSpecifier: prismaImport,
119154
},
120155
]);
121156

122-
appRouter.addStatements(`
123-
157+
if (version === 'v10') {
158+
appRouter.addStatements(`
124159
export type BaseConfig = AnyRootConfig;
125160
126161
export type RouterFactory<Config extends BaseConfig> = <
@@ -133,30 +168,40 @@ function createAppRouter(
133168
134169
export type ProcBuilder<Config extends BaseConfig> = ProcedureBuilder<
135170
ProcedureParams<Config, any, any, any, UnsetMarker, UnsetMarker, any>
136-
>;
171+
>;
172+
`);
173+
} else {
174+
appRouter.addImportDeclaration({
175+
namedImports: ['createTRPCRouter'],
176+
moduleSpecifier: options.importCreateRouter as string,
177+
});
178+
}
137179

180+
appRouter.addStatements(`
138181
export function db(ctx: any) {
139182
if (!ctx.prisma) {
140183
throw new Error('Missing "prisma" field in trpc context');
141184
}
142185
return ctx.prisma as PrismaClient;
143186
}
144-
145187
`);
146188

147189
const filteredModelOperations = modelOperations.filter((mo) => !hiddenModels.includes(mo.model));
148190

149191
appRouter
150192
.addFunction({
151-
name: 'createRouter<Config extends BaseConfig>',
152-
parameters: [
153-
{ name: 'router', type: 'RouterFactory<Config>' },
154-
{ name: 'procedure', type: 'ProcBuilder<Config>' },
155-
],
193+
name: version === 'v10' ? 'createRouter<Config extends BaseConfig>' : 'createRouter',
194+
parameters:
195+
version === 'v10'
196+
? [
197+
{ name: 'router', type: 'RouterFactory<Config>' },
198+
{ name: 'procedure', type: 'ProcBuilder<Config>' },
199+
]
200+
: [],
156201
isExported: true,
157202
})
158203
.setBodyText((writer) => {
159-
writer.write('return router(');
204+
writer.write(`return ${version === 'v10' ? 'router' : 'createTRPCRouter'}(`);
160205
writer.block(() => {
161206
for (const modelOperation of filteredModelOperations) {
162207
const { model, ...operations } = modelOperation;
@@ -173,15 +218,20 @@ function createAppRouter(
173218
generateClientHelpers,
174219
zodSchemasImport,
175220
options,
176-
zmodel
221+
zmodel,
222+
version
177223
);
178224

179225
appRouter.addImportDeclaration({
180226
defaultImport: `create${model}Router`,
181227
moduleSpecifier: `./${model}.router`,
182228
});
183229

184-
writer.writeLine(`${lowerCaseFirst(model)}: create${model}Router(router, procedure),`);
230+
if (version === 'v10') {
231+
writer.writeLine(`${lowerCaseFirst(model)}: create${model}Router(router, procedure),`);
232+
} else {
233+
writer.writeLine(`${lowerCaseFirst(model)}: create${model}Router(),`);
234+
}
185235
}
186236
});
187237
writer.write(');');
@@ -204,30 +254,30 @@ function createAppRouter(
204254
}),
205255
});
206256

207-
createClientHelpers(outDir, generateClientHelpers);
257+
createClientHelpers(outDir, generateClientHelpers, version);
208258
}
209259

210260
appRouter.formatText();
211261
}
212262

213-
function createClientHelpers(outputDir: string, generateClientHelpers: string[]) {
263+
function createClientHelpers(outputDir: string, generateClientHelpers: string[], version: string) {
214264
const utils = project.createSourceFile(path.resolve(outputDir, 'client', `utils.ts`), undefined, {
215265
overwrite: true,
216266
});
217-
utils.replaceWithText(fs.readFileSync(path.join(__dirname, './res/client/utils.ts'), 'utf-8'));
267+
utils.replaceWithText(fs.readFileSync(path.join(__dirname, `./res/client/${version}/utils.ts`), 'utf-8'));
218268

219269
for (const client of generateClientHelpers) {
220270
switch (client) {
221271
case 'react': {
222-
const content = fs.readFileSync(path.join(__dirname, './res/client/react.ts'), 'utf-8');
272+
const content = fs.readFileSync(path.join(__dirname, `./res/client/${version}/react.ts`), 'utf-8');
223273
project.createSourceFile(path.resolve(outputDir, 'client', 'react.ts'), content, {
224274
overwrite: true,
225275
});
226276
break;
227277
}
228278

229279
case 'next': {
230-
const content = fs.readFileSync(path.join(__dirname, './res/client/next.ts'), 'utf-8');
280+
const content = fs.readFileSync(path.join(__dirname, `./res/client/${version}/next.ts`), 'utf-8');
231281
project.createSourceFile(path.resolve(outputDir, 'client', 'next.ts'), content, { overwrite: true });
232282
break;
233283
}
@@ -244,36 +294,72 @@ function generateModelCreateRouter(
244294
generateClientHelpers: string[] | undefined,
245295
zodSchemasImport: string,
246296
options: PluginOptions,
247-
zmodel: Model
297+
zmodel: Model,
298+
version: string
248299
) {
249300
const modelRouter = project.createSourceFile(path.resolve(outputDir, 'routers', `${model}.router.ts`), undefined, {
250301
overwrite: true,
251302
});
252303

253304
modelRouter.addStatements('/* eslint-disable */');
254305

255-
modelRouter.addImportDeclarations([
256-
{
257-
namedImports: ['type RouterFactory', 'type ProcBuilder', 'type BaseConfig', 'db'],
258-
moduleSpecifier: '.',
259-
},
260-
]);
306+
if (version === 'v10') {
307+
modelRouter.addImportDeclarations([
308+
{
309+
namedImports: ['type RouterFactory', 'type ProcBuilder', 'type BaseConfig', 'db'],
310+
moduleSpecifier: '.',
311+
},
312+
]);
313+
} else {
314+
modelRouter.addImportDeclarations([
315+
{
316+
namedImports: ['db'],
317+
moduleSpecifier: '.',
318+
},
319+
]);
261320

321+
modelRouter.addImportDeclarations([
322+
{
323+
namedImports: ['createTRPCRouter'],
324+
moduleSpecifier: options.importCreateRouter as string,
325+
},
326+
]);
327+
328+
modelRouter.addImportDeclarations([
329+
{
330+
namedImports: ['procedure'],
331+
moduleSpecifier: options.importProcedure as string,
332+
},
333+
]);
334+
}
335+
336+
// zod schema import
262337
generateRouterSchemaImport(modelRouter, zodSchemasImport);
338+
339+
// runtime helpers
263340
generateHelperImport(modelRouter);
341+
342+
// client helper imports
264343
if (generateClientHelpers) {
265-
generateRouterTypingImports(modelRouter, options);
344+
generateRouterTypingImports(modelRouter, options, version);
266345
}
267346

268-
const createRouterFunc = modelRouter.addFunction({
269-
name: 'createRouter<Config extends BaseConfig>',
270-
parameters: [
271-
{ name: 'router', type: 'RouterFactory<Config>' },
272-
{ name: 'procedure', type: 'ProcBuilder<Config>' },
273-
],
274-
isExported: true,
275-
isDefaultExport: true,
276-
});
347+
const createRouterFunc =
348+
version === 'v10'
349+
? modelRouter.addFunction({
350+
name: 'createRouter<Config extends BaseConfig>',
351+
parameters: [
352+
{ name: 'router', type: 'RouterFactory<Config>' },
353+
{ name: 'procedure', type: 'ProcBuilder<Config>' },
354+
],
355+
isExported: true,
356+
isDefaultExport: true,
357+
})
358+
: modelRouter.addFunction({
359+
name: 'createRouter',
360+
isExported: true,
361+
isDefaultExport: true,
362+
});
277363

278364
let routerTypingStructure: InterfaceDeclarationStructure | undefined = undefined;
279365
if (generateClientHelpers) {
@@ -294,7 +380,7 @@ function generateModelCreateRouter(
294380
}
295381

296382
createRouterFunc.setBodyText((funcWriter) => {
297-
funcWriter.write('return router(');
383+
funcWriter.write(`return ${version === 'v10' ? 'router' : 'createTRPCRouter'}(`);
298384
funcWriter.block(() => {
299385
for (const [opType, opNameWithModel] of Object.entries(operations)) {
300386
if (isDelegateModel(dataModel) && (opType.startsWith('create') || opType.startsWith('upsert'))) {
@@ -322,7 +408,7 @@ function generateModelCreateRouter(
322408
kind: StructureKind.PropertySignature,
323409
name: generateOpName,
324410
type: (writer) => {
325-
generateRouterTyping(writer, generateOpName, model, baseOpType);
411+
generateRouterTyping(writer, generateOpName, model, baseOpType, version);
326412
},
327413
});
328414
}

0 commit comments

Comments
 (0)