From d6ab9baed73c2f46dae438d3928cffbfa2eec09b Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:54:06 +0800 Subject: [PATCH 1/4] fix: optimize generated trpc typing and fix "select" issue --- packages/plugins/swr/tests/swr.test.ts | 7 +- packages/plugins/trpc/src/generator.ts | 86 +--- packages/plugins/trpc/src/helpers.ts | 24 +- .../tests/projects/t3-trpc-v10/.eslintrc.cjs | 37 ++ .../tests/projects/t3-trpc-v10/.gitignore | 43 ++ .../trpc/tests/projects/t3-trpc-v10/README.md | 28 ++ .../tests/projects/t3-trpc-v10/next.config.js | 22 + .../tests/projects/t3-trpc-v10/package.json | 49 ++ .../projects/t3-trpc-v10/prisma/schema.prisma | 31 ++ .../projects/t3-trpc-v10/public/favicon.ico | Bin 0 -> 15406 bytes .../tests/projects/t3-trpc-v10/schema.zmodel | 35 ++ .../tests/projects/t3-trpc-v10/src/env.js | 49 ++ .../projects/t3-trpc-v10/src/pages/_app.tsx | 11 + .../t3-trpc-v10/src/pages/api/trpc/[trpc].ts | 19 + .../t3-trpc-v10/src/pages/index.module.css | 177 +++++++ .../projects/t3-trpc-v10/src/pages/index.tsx | 23 + .../t3-trpc-v10/src/server/api/root.ts | 16 + .../api/routers/generated/client/next.ts | 17 + .../api/routers/generated/client/utils.ts | 32 ++ .../server/api/routers/generated/helper.ts | 67 +++ .../routers/generated/routers/Post.router.ts | 451 ++++++++++++++++++ .../routers/generated/routers/User.router.ts | 451 ++++++++++++++++++ .../api/routers/generated/routers/index.ts | 48 ++ .../src/server/api/routers/greet.ts | 11 + .../src/server/api/routers/post.ts | 4 + .../t3-trpc-v10/src/server/api/trpc.ts | 95 ++++ .../projects/t3-trpc-v10/src/server/db.ts | 16 + .../t3-trpc-v10/src/styles/globals.css | 16 + .../projects/t3-trpc-v10/src/utils/api.ts | 68 +++ .../tests/projects/t3-trpc-v10/tsconfig.json | 42 ++ packages/plugins/trpc/tests/t3.test.ts | 30 ++ packages/plugins/trpc/tests/trpc.test.ts | 25 +- packages/testtools/src/schema.ts | 2 +- 33 files changed, 1932 insertions(+), 100 deletions(-) create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/.eslintrc.cjs create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/.gitignore create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/README.md create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/next.config.js create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/package.json create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/prisma/schema.prisma create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/public/favicon.ico create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/schema.zmodel create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/_app.tsx create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/api/trpc/[trpc].ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.module.css create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.tsx create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/root.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/next.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/utils.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/helper.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/index.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/greet.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/post.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/trpc.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/db.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/styles/globals.css create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/src/utils/api.ts create mode 100644 packages/plugins/trpc/tests/projects/t3-trpc-v10/tsconfig.json create mode 100644 packages/plugins/trpc/tests/t3.test.ts diff --git a/packages/plugins/swr/tests/swr.test.ts b/packages/plugins/swr/tests/swr.test.ts index 76db29b49..9d198269b 100644 --- a/packages/plugins/swr/tests/swr.test.ts +++ b/packages/plugins/swr/tests/swr.test.ts @@ -59,7 +59,12 @@ ${sharedModel} { provider: 'postgresql', pushDb: false, - extraDependencies: [`${origDir}/dist`, 'react@18.2.0', '@types/react@18.2.0', 'swr@^2'], + extraDependencies: [ + `${path.join(__dirname, '../dist')}`, + 'react@18.2.0', + '@types/react@18.2.0', + 'swr@^2', + ], compile: true, } ); diff --git a/packages/plugins/trpc/src/generator.ts b/packages/plugins/trpc/src/generator.ts index 0a340be1a..0d252cabc 100644 --- a/packages/plugins/trpc/src/generator.ts +++ b/packages/plugins/trpc/src/generator.ts @@ -99,6 +99,7 @@ function createAppRouter( appRouter.addImportDeclarations([ { namedImports: [ + 'unsetMarker', 'type AnyRouter', 'type AnyRootConfig', 'type CreateRouterInner', @@ -111,15 +112,12 @@ function createAppRouter( moduleSpecifier: '@trpc/server', }, { - namedImports: ['type PrismaClient', 'type Prisma'], + namedImports: ['type PrismaClient'], moduleSpecifier: prismaImport, }, - { 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; @@ -129,56 +127,11 @@ function createAppRouter( procedures: ProcRouterRecord ) => CreateRouterInner; - ${ - /** 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 type UnsetMarker = typeof unsetMarker; + + export type ProcBuilder = ProcedureBuilder< + ProcedureParams + >; export function db(ctx: any) { if (!ctx.prisma) { @@ -193,10 +146,10 @@ function createAppRouter( appRouter .addFunction({ - name: 'createRouter, Proc extends ProcBuilder>', + name: 'createRouter', parameters: [ - { name: 'router', type: 'Router' }, - { name: 'procedure', type: 'Proc' }, + { name: 'router', type: 'RouterFactory' }, + { name: 'procedure', type: 'ProcBuilder' }, ], isExported: true, }) @@ -225,9 +178,7 @@ 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(');'); @@ -299,14 +250,7 @@ function generateModelCreateRouter( modelRouter.addImportDeclarations([ { - namedImports: [ - 'type RouterFactory', - 'type ProcBuilder', - 'type BaseConfig', - 'type ProcReturns', - 'type PrismaClient', - 'db', - ], + namedImports: ['type RouterFactory', 'type ProcBuilder', 'type BaseConfig', 'db'], moduleSpecifier: '.', }, ]); @@ -318,10 +262,10 @@ function generateModelCreateRouter( } const createRouterFunc = modelRouter.addFunction({ - name: 'createRouter, Proc extends ProcBuilder>', + name: 'createRouter', parameters: [ - { name: 'router', type: 'Router' }, - { name: 'procedure', type: 'Proc' }, + { name: 'router', type: 'RouterFactory' }, + { name: 'procedure', type: 'ProcBuilder' }, ], isExported: true, isDefaultExport: true, diff --git a/packages/plugins/trpc/src/helpers.ts b/packages/plugins/trpc/src/helpers.ts index 1fbeb9efd..54aec3ecb 100644 --- a/packages/plugins/trpc/src/helpers.ts +++ b/packages/plugins/trpc/src/helpers.ts @@ -21,24 +21,14 @@ export function generateProcedure( writer.write(` ${opType}: procedure.input(${typeName}).query(({ctx, input}) => checkRead(db(ctx).${lowerCaseFirst( modelName - )}.${prismaMethod}(input as any))) as ProcReturns< - "query", - Proc, - (typeof $Schema.${upperCaseFirst(modelName)}InputSchema)["${opType.replace('OrThrow', '')}"], - ReturnType - >, + )}.${prismaMethod}(input as any))), `); } 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))) as ProcReturns< - "mutation", - Proc, - (typeof $Schema.${upperCaseFirst(modelName)}InputSchema)["${opType.replace('OrThrow', '')}"], - ReturnType - >, + )}.${prismaMethod}(input as any))), `); } } @@ -203,18 +193,18 @@ export function generateRouterTyping(writer: CodeBlockWriter, opType: string, mo writer.block(() => { if (procType === 'query') { writer.writeLine(` - useQuery: ( + useQuery: ( input: ${argsType}, - opts?: UseTRPCQueryOptions + opts?: UseTRPCQueryOptions ) => UseTRPCQueryResult< - ${resultType}, + TData, ${errorType} >; useInfiniteQuery: ( input: Omit<${argsType}, 'cursor'>, opts?: UseTRPCInfiniteQueryOptions ) => UseTRPCInfiniteQueryResult< - ${resultType}, + ${resultType}, ${errorType} >; `); @@ -223,7 +213,7 @@ export function generateRouterTyping(writer: CodeBlockWriter, opType: string, mo useMutation: (opts?: UseTRPCMutationOptions< ${genericBase}, ${errorType}, - Prisma.${upperCaseFirst(modelName)}GetPayload, + ${resultType}, Context >,) => Omit, 'mutateAsync'> & { diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/.eslintrc.cjs b/packages/plugins/trpc/tests/projects/t3-trpc-v10/.eslintrc.cjs new file mode 100644 index 000000000..c946e46e0 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/.eslintrc.cjs @@ -0,0 +1,37 @@ +/** @type {import("eslint").Linter.Config} */ +const config = { + parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + }, + plugins: ["@typescript-eslint"], + extends: [ + "next/core-web-vitals", + "plugin:@typescript-eslint/recommended-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + ], + rules: { + // These opinionated rules are enabled in stylistic-type-checked above. + // Feel free to reconfigure them to your own preference. + "@typescript-eslint/array-type": "off", + "@typescript-eslint/consistent-type-definitions": "off", + + "@typescript-eslint/consistent-type-imports": [ + "warn", + { + prefer: "type-imports", + fixStyle: "inline-type-imports", + }, + ], + "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], + "@typescript-eslint/require-await": "off", + "@typescript-eslint/no-misused-promises": [ + "error", + { + checksVoidReturn: { attributes: false }, + }, + ], + }, +}; + +module.exports = config; diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/.gitignore b/packages/plugins/trpc/tests/projects/t3-trpc-v10/.gitignore new file mode 100644 index 000000000..b9331d6ce --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/.gitignore @@ -0,0 +1,43 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# database +/prisma/db.sqlite +/prisma/db.sqlite-journal + +# next.js +/.next/ +/out/ +next-env.d.ts + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables +.env +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +package-lock.json \ No newline at end of file diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/README.md b/packages/plugins/trpc/tests/projects/t3-trpc-v10/README.md new file mode 100644 index 000000000..fba19edac --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/README.md @@ -0,0 +1,28 @@ +# Create T3 App + +This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. + +## What's next? How do I make an app with this? + +We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. + +If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. + +- [Next.js](https://nextjs.org) +- [NextAuth.js](https://next-auth.js.org) +- [Prisma](https://prisma.io) +- [Tailwind CSS](https://tailwindcss.com) +- [tRPC](https://trpc.io) + +## Learn More + +To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: + +- [Documentation](https://create.t3.gg/) +- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials + +You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! + +## How do I deploy this? + +Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/next.config.js b/packages/plugins/trpc/tests/projects/t3-trpc-v10/next.config.js new file mode 100644 index 000000000..ffbeb9fb4 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/next.config.js @@ -0,0 +1,22 @@ +/** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful + * for Docker builds. + */ +await import("./src/env.js"); + +/** @type {import("next").NextConfig} */ +const config = { + reactStrictMode: true, + + /** + * If you are using `appDir` then you must comment the below `i18n` config out. + * + * @see https://github.com/vercel/next.js/issues/41980 + */ + i18n: { + locales: ["en"], + defaultLocale: "en", + }, +}; + +export default config; diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/package.json b/packages/plugins/trpc/tests/projects/t3-trpc-v10/package.json new file mode 100644 index 000000000..3e4c35e16 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/package.json @@ -0,0 +1,49 @@ +{ + "name": "t3-trpc-v10", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "db:push": "prisma db push", + "db:studio": "prisma studio", + "dev": "next dev", + "postinstall": "prisma generate", + "lint": "next lint", + "start": "next start" + }, + "dependencies": { + "@prisma/client": "^5.6.0", + "@t3-oss/env-nextjs": "^0.7.1", + "@tanstack/react-query": "^4.36.1", + "@trpc/client": "^10.43.6", + "@trpc/next": "^10.43.6", + "@trpc/react-query": "^10.43.6", + "@trpc/server": "^10.43.6", + "@zenstackhq/language": "file:../../../../../../.build/zenstackhq-language-1.8.0.tgz", + "@zenstackhq/runtime": "file:../../../../../../.build/zenstackhq-runtime-1.8.0.tgz", + "@zenstackhq/sdk": "file:../../../../../../.build/zenstackhq-sdk-1.8.0.tgz", + "next": "^14.0.4", + "react": "18.2.0", + "react-dom": "18.2.0", + "superjson": "^2.2.1", + "zenstack": "file:../../../../../../.build/zenstack-1.8.0.tgz", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/eslint": "^8.44.7", + "@types/node": "^18.17.0", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "eslint": "^8.54.0", + "eslint-config-next": "^14.0.4", + "prisma": "^5.6.0", + "typescript": "^5.1.6" + }, + "ct3aMetadata": { + "initVersion": "7.26.0" + }, + "packageManager": "npm@10.2.3" +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/prisma/schema.prisma b/packages/plugins/trpc/tests/projects/t3-trpc-v10/prisma/schema.prisma new file mode 100644 index 000000000..2a0b2142a --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/prisma/schema.prisma @@ -0,0 +1,31 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +datasource db { + provider = "sqlite" + url = "file:./dev.db" +} + +generator client { + provider = "prisma-client-js" +} + +model User { + id Int @id() @default(autoincrement()) + email String @unique() + posts Post[] +} + +model Post { + id Int @id() @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt() + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int + + @@index([name]) +} \ No newline at end of file diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/public/favicon.ico b/packages/plugins/trpc/tests/projects/t3-trpc-v10/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..60c702aac13409c82c4040f8fee9603eb84aa10c GIT binary patch literal 15406 zcmeHOX^b4j6>hH&u3!wtgoMM(Da<7x5Rn8Tjvq)?zybua1c*c20{$U^Aj%>HD9R-z zBuWB;ARB>eYK+y}Dk#s*$8Q(p+iLA_;N7bn84x`l%#I{rywlCmbkAPayBK)27Rhm!$WXNYV+Q zK^4@P%189Q__>DsD^FL?7r_P=JJvIlKl+CJ62dyd9WqHP5Sp7xG3? zPX~|xApI@EB+@r<8Zj2@M^QA-H<*IF*CS2am*|i;*E8hDd|es0ZRN*eT}q4f={qph zUyoWUdZ+R8ip52QA+%;l|Sa2^7!PrYAH1GAw4nmfKx zy1+M;Td^MB<(cfVs$m?`u57IHH)D=|NWm@3zi7tCFgENLSn7k=Pdv~@u`r2!VJ-Hn z5cXiS66w9>LT56iNCfU;)=ma= z`c9MYdEO$lXDjiAZma0~qmy`0Ud=wxm3KG>+B=)kiuq~siOx6F`)WK*<@aK}q_nFk z=W_Xo|D8k=&&!fe^n@YJ_ToIDgFft$u(^~7d^hv_v^bCawEFQf^jDeWqrcR6S<-hm zzt3;Z#bYA(s$u7s3+5#DVP-&b)99=$<^0;j3 zcc)NTm?l#!%OgJ;80Z7t%UlN9>UibS>6}kssAr=rpgqWS-2-@jo;Z(u;uA5p57xgo zI0neFT|+sU%cxe{+k^AUuVIL^eX;Kh)-j;ZL#;@rXxqP5TdFpnH#&uyA7>w*DZVL^MIqKm_{4qbT z46X9@-3V1(K8eflW%)qJ|3tvB*;bOy=By;paJ=otl~IG8!ZC!Zx){7a=ln4@zlz(V z7_;4!AJJMDRWX`AY2VT|#s$X@PdzK-T;Cwj zne8F?PcC3MKk<6qh;i;b3A`O4yiT@P9^OMkLknOd-T*uDYt$b@NVA1^;H+n%Evu7k z>pb$3Xk0dOYE15jHpV~_t(ZqPKm3n>Lf!4L`e|*bmEqhbhbHxN@*R{YCoA0!{t9D< z0rS(%aWflbbWbUxZ)$$W8ML~>yt1+aZJ3*dF|E8+{1N%xP5HMN-`hk?7#G{oA8!yQ zItP22wv^7IzFj^8bPprM{pCAx9^42%$E4xQDr*&g=#+mBoDDzCl#kAa&+K;Sa(**; z)E3lx6Km5h?MAzv?PMIaf}i>te(+aCy+em3%;8I#;TH2vtP6w}Vahi-HQy%#tysSg z73uS&TftwgXv=7vaQstao88lj{;Ha`Y{og-R9*p(zC3v2G_ByLx>&>Sg}!UPZM37{ z>Basy&#Z6gV1VnO7GpiK)nUBcX#LkJemal)mUNRv0cJMfcj3f=Dz^j|@H+FCaH(7$6r#>64S<{vsG@JOVy1oSK4xI(+V+VV|j!MdVwyb&3DSn!Z zW3G1OR$D#3*?L50mMZRep!apV>Ydsl|2+$1T6rh6M@FW$H2ME+kz+>T7Wl^_o9vCD6fxs zw5dex9l)Jn7RI#lcJW8_p3YR>!}x930X2N`de0kKO7H%-T=dC&PcQue_TD(`)d{Ti zpVECPFYhF77eC3Qa|(3&+O+N)x;5nYT$7zD;-a%M-T>Z>_WovzZD*cO#ky(fPVm!M z4>50XE?F;*jp_Cb#^0xcejdVtG(4@Ab%LME8grb(VULnMQ(qTr?XlS4sA=Z%WpG}t z#@)bAGHE<}Ce}x+=VD)Aj=U1KY1`*%OSkaTSaNZniT#y)LG`(S^d#o(f0N$S=E0Xm z69nua-)1-RfN`**lRLwjZKf-mfScCTMmtQlmSkn&*%Qh=t8_ZBe~{3IHMCdn2^iBb zU@Z0dnsO%gtl?N6Y{&Y!ir&Ac*2mk5ac|mxL_VZh2;?)RIUwSqJpgNKaYjEF@;@WI zV;5<~G|ty}hr~8c`6=kme>Q^rmKXb<0b#1W2{PGdGuxm%Zdt`cMch0MyXbn%Lwe)X zm_Ofrn*7V_#@MFAI1Y+wEV-6^4ls%5b>Nb>VQu|ugs~#hQ+hYypVk$7do)3>4&JN5 z*Qv&IioJq8V&L9GYy;NYm7v3!Od*?f)&p$Ie z=7xjyDDv&@jzB)Sqc--SY9FM2yJ2IM#O`-=V2OZPO;(?CxHJq`3Uu%~L^f2g^2 A#Q*>R literal 0 HcmV?d00001 diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/schema.zmodel b/packages/plugins/trpc/tests/projects/t3-trpc-v10/schema.zmodel new file mode 100644 index 000000000..01c23bd80 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/schema.zmodel @@ -0,0 +1,35 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = "file:./dev.db" +} + +plugin trpc { + provider = "../../../dist" + output = "src/server/api/routers/generated" + generateClientHelpers = "next" +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + published Boolean @default(false) + author User @relation(fields: [authorId], references: [id]) + authorId Int + + @@index([name]) +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js new file mode 100644 index 000000000..e3e371948 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js @@ -0,0 +1,49 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { z } from "zod"; + +export const env = createEnv({ + /** + * Specify your server-side environment variables schema here. This way you can ensure the app + * isn't built with invalid env vars. + */ + server: { + DATABASE_URL: z + .string() + .refine( + (str) => !str.includes("YOUR_MYSQL_URL_HERE"), + "You forgot to change the default URL" + ), + NODE_ENV: z + .enum(["development", "test", "production"]) + .default("development"), + }, + + /** + * Specify your client-side environment variables schema here. This way you can ensure the app + * isn't built with invalid env vars. To expose them to the client, prefix them with + * `NEXT_PUBLIC_`. + */ + client: { + // NEXT_PUBLIC_CLIENTVAR: z.string(), + }, + + /** + * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. + * middlewares) or client-side so we need to destruct manually. + */ + runtimeEnv: { + DATABASE_URL: process.env.DATABASE_URL, + NODE_ENV: process.env.NODE_ENV, + // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, + }, + /** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially + * useful for Docker builds. + */ + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + /** + * Makes it so that empty strings are treated as undefined. + * `SOME_VAR: z.string()` and `SOME_VAR=''` will throw an error. + */ + emptyStringAsUndefined: true, +}); diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/_app.tsx b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/_app.tsx new file mode 100644 index 000000000..18319174e --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/_app.tsx @@ -0,0 +1,11 @@ +import { type AppType } from "next/app"; + +import { api } from "~/utils/api"; + +import "~/styles/globals.css"; + +const MyApp: AppType = ({ Component, pageProps }) => { + return ; +}; + +export default api.withTRPC(MyApp); diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/api/trpc/[trpc].ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/api/trpc/[trpc].ts new file mode 100644 index 000000000..587dd2bdf --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/api/trpc/[trpc].ts @@ -0,0 +1,19 @@ +import { createNextApiHandler } from "@trpc/server/adapters/next"; + +import { env } from "~/env"; +import { appRouter } from "~/server/api/root"; +import { createTRPCContext } from "~/server/api/trpc"; + +// export API handler +export default createNextApiHandler({ + router: appRouter, + createContext: createTRPCContext, + onError: + env.NODE_ENV === "development" + ? ({ path, error }) => { + console.error( + `❌ tRPC failed on ${path ?? ""}: ${error.message}` + ); + } + : undefined, +}); diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.module.css b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.module.css new file mode 100644 index 000000000..fac9982a3 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.module.css @@ -0,0 +1,177 @@ +.main { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; + background-image: linear-gradient(to bottom, #2e026d, #15162c); +} + +.container { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3rem; + padding: 4rem 1rem; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.title { + font-size: 3rem; + line-height: 1; + font-weight: 800; + letter-spacing: -0.025em; + margin: 0; + color: white; +} + +@media (min-width: 640px) { + .title { + font-size: 5rem; + } +} + +.pinkSpan { + color: hsl(280 100% 70%); +} + +.cardRow { + display: grid; + grid-template-columns: repeat(1, minmax(0, 1fr)); + gap: 1rem; +} + +@media (min-width: 640px) { + .cardRow { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (min-width: 768px) { + .cardRow { + gap: 2rem; + } +} + +.card { + max-width: 20rem; + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem; + border-radius: 0.75rem; + color: white; + background-color: rgb(255 255 255 / 0.1); +} + +.card:hover { + background-color: rgb(255 255 255 / 0.2); + transition: background-color 150ms cubic-bezier(0.5, 0, 0.2, 1); +} + +.cardTitle { + font-size: 1.5rem; + line-height: 2rem; + font-weight: 700; + margin: 0; +} + +.cardText { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.showcaseContainer { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.showcaseText { + color: white; + text-align: center; + font-size: 1.5rem; + line-height: 2rem; +} + +.authContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; +} + +.loginButton { + border-radius: 9999px; + background-color: rgb(255 255 255 / 0.1); + padding: 0.75rem 2.5rem; + font-weight: 600; + color: white; + text-decoration-line: none; + transition: background-color 150ms cubic-bezier(0.5, 0, 0.2, 1); +} + +.loginButton:hover { + background-color: rgb(255 255 255 / 0.2); +} + +.form { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.input { + width: 100%; + border-radius: 9999px; + padding: 0.5rem 1rem; + color: black; +} + +.submitButton { + all: unset; + border-radius: 9999px; + background-color: rgb(255 255 255 / 0.1); + padding: 0.75rem 2.5rem; + font-weight: 600; + color: white; + text-align: center; + transition: background-color 150ms cubic-bezier(0.5, 0, 0.2, 1); +} + +.submitButton:hover { + background-color: rgb(255 255 255 / 0.2); +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.tsx b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.tsx new file mode 100644 index 000000000..50a3d3e7d --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/pages/index.tsx @@ -0,0 +1,23 @@ +import { api } from '~/utils/api'; +import styles from './index.module.css'; + +export default function Home() { + const hello = api.greet.hello.useQuery({ text: 'from tRPC' }); + const posts = api.post.findMany.useQuery({ where: { published: true }, include: { author: true } }); + const postsTransformed = api.post.findMany.useQuery({}, { select: (data) => data.map((p) => ({ title: p.name })) }); + + return ( + <> +

+ {hello.data &&

{hello.data.greeting}

} + {posts.data && + posts.data.map((post) => ( +

+ {post.name} by {post.author.email} +

+ ))} + {postsTransformed.data && postsTransformed.data.map((post) =>

{post.title}

)} +
+ + ); +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/root.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/root.ts new file mode 100644 index 000000000..397c562bf --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/root.ts @@ -0,0 +1,16 @@ +import { greetRouter } from '~/server/api/routers/greet'; +import { createTRPCRouter } from '~/server/api/trpc'; +import { postRouter } from './routers/post'; + +/** + * This is the primary router for your server. + * + * All routers added in /api/routers should be manually added here. + */ +export const appRouter = createTRPCRouter({ + greet: greetRouter, + post: postRouter, +}); + +// export type definition of API +export type AppRouter = typeof appRouter; diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/next.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/next.ts new file mode 100644 index 000000000..982ab7980 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/next.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ + +import type { AnyRouter } from '@trpc/server'; +import type { NextPageContext } from 'next'; +import { type CreateTRPCNext, createTRPCNext as _createTRPCNext } from '@trpc/next'; +import type { DeepOverrideAtPath } from './utils'; +import type { ClientType } from '../routers'; + +export function createTRPCNext< + TRouter extends AnyRouter, + TPath extends string | undefined = undefined, + TSSRContext extends NextPageContext = NextPageContext, + TFlags = null, +>(opts: Parameters[0]) { + const r: CreateTRPCNext = _createTRPCNext(opts); + return r as DeepOverrideAtPath, ClientType, TPath>; +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/utils.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/utils.ts new file mode 100644 index 000000000..223fde54d --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/client/utils.ts @@ -0,0 +1,32 @@ +/* eslint-disable */ + +// inspired by: https://stackoverflow.com/questions/70632026/generic-to-recursively-modify-a-given-type-interface-in-typescript + +type Primitive = string | Function | number | boolean | Symbol | undefined | null; + +/** + * Recursively merges `T` and `R`. If there's a shared key, use `R`'s field type to overwrite `T`. + */ +export type DeepOverride = T extends Primitive + ? R + : R extends Primitive + ? R + : { + [K in keyof T]: K extends keyof R ? DeepOverride : T[K]; + } & { + [K in Exclude]: R[K]; + }; + +/** + * Traverse to `Path` (denoted by dot separated string literal type) in `T`, and starting from there, + * recursively merge with `R`. + */ +export type DeepOverrideAtPath = Path extends undefined + ? DeepOverride + : Path extends `${infer P1}.${infer P2}` + ? P1 extends keyof T + ? Omit & Record>> + : never + : Path extends keyof T + ? Omit & Record> + : never; diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/helper.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/helper.ts new file mode 100644 index 000000000..45e24e20e --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/helper.ts @@ -0,0 +1,67 @@ +/* eslint-disable */ +import { TRPCError } from '@trpc/server'; +import { isPrismaClientKnownRequestError } from '@zenstackhq/runtime'; + +export async function checkMutate(promise: Promise): Promise { + try { + return await promise; + } catch (err: any) { + if (isPrismaClientKnownRequestError(err)) { + if (err.code === 'P2004') { + if (err.meta?.reason === 'RESULT_NOT_READABLE') { + // unable to readback data + return undefined; + } else { + // rejected by policy + throw new TRPCError({ + code: 'FORBIDDEN', + message: err.message, + cause: err, + }); + } + } else { + // request error + throw new TRPCError({ + code: 'BAD_REQUEST', + message: err.message, + cause: err, + }); + } + } else { + throw err; + } + } +} + +export async function checkRead(promise: Promise): Promise { + try { + return await promise; + } catch (err: any) { + if (isPrismaClientKnownRequestError(err)) { + if (err.code === 'P2004') { + // rejected by policy + throw new TRPCError({ + code: 'FORBIDDEN', + message: err.message, + cause: err, + }); + } else if (err.code === 'P2025') { + // not found + throw new TRPCError({ + code: 'NOT_FOUND', + message: err.message, + cause: err, + }); + } else { + // request error + throw new TRPCError({ + code: 'BAD_REQUEST', + message: err.message, + cause: err, + }); + } + } else { + throw err; + } + } +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts new file mode 100644 index 000000000..e7fa40292 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/Post.router.ts @@ -0,0 +1,451 @@ +/* eslint-disable */ +import { type RouterFactory, type ProcBuilder, type BaseConfig, db } from '.'; +import $Schema from '@zenstackhq/runtime/zod/input'; +import { checkRead, checkMutate } from '../helper'; +import type { Prisma } from '@prisma/client'; +import type { + UseTRPCMutationOptions, + UseTRPCMutationResult, + UseTRPCQueryOptions, + UseTRPCQueryResult, + UseTRPCInfiniteQueryOptions, + UseTRPCInfiniteQueryResult, +} from '@trpc/react-query/shared'; +import type { TRPCClientErrorLike } from '@trpc/client'; +import type { AnyRouter } from '@trpc/server'; + +export default function createRouter( + router: RouterFactory, + procedure: ProcBuilder, +) { + return router({ + aggregate: procedure + .input($Schema.PostInputSchema.aggregate) + .query(({ ctx, input }) => checkRead(db(ctx).post.aggregate(input as any))), + + create: procedure + .input($Schema.PostInputSchema.create) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).post.create(input as any))), + + deleteMany: procedure + .input($Schema.PostInputSchema.deleteMany) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).post.deleteMany(input as any))), + + delete: procedure + .input($Schema.PostInputSchema.delete) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).post.delete(input as any))), + + findFirst: procedure + .input($Schema.PostInputSchema.findFirst) + .query(({ ctx, input }) => checkRead(db(ctx).post.findFirst(input as any))), + + findFirstOrThrow: procedure + .input($Schema.PostInputSchema.findFirst) + .query(({ ctx, input }) => checkRead(db(ctx).post.findFirstOrThrow(input as any))), + + findMany: procedure + .input($Schema.PostInputSchema.findMany) + .query(({ ctx, input }) => checkRead(db(ctx).post.findMany(input as any))), + + findUnique: procedure + .input($Schema.PostInputSchema.findUnique) + .query(({ ctx, input }) => checkRead(db(ctx).post.findUnique(input as any))), + + findUniqueOrThrow: procedure + .input($Schema.PostInputSchema.findUnique) + .query(({ ctx, input }) => checkRead(db(ctx).post.findUniqueOrThrow(input as any))), + + groupBy: procedure + .input($Schema.PostInputSchema.groupBy) + .query(({ ctx, input }) => checkRead(db(ctx).post.groupBy(input as any))), + + updateMany: procedure + .input($Schema.PostInputSchema.updateMany) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).post.updateMany(input as any))), + + update: procedure + .input($Schema.PostInputSchema.update) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).post.update(input as any))), + + upsert: procedure + .input($Schema.PostInputSchema.upsert) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).post.upsert(input as any))), + + count: procedure + .input($Schema.PostInputSchema.count) + .query(({ ctx, input }) => checkRead(db(ctx).post.count(input as any))), + }); +} + +export interface ClientType { + aggregate: { + useQuery: >( + input: Prisma.Subset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + create: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.PostCreateArgs, + TRPCClientErrorLike, + Prisma.PostGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.PostGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.PostGetPayload, Context>, + ) => Promise>; + }; + }; + deleteMany: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.PostDeleteManyArgs, + TRPCClientErrorLike, + Prisma.BatchPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.BatchPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.BatchPayload, Context>, + ) => Promise; + }; + }; + delete: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.PostDeleteArgs, + TRPCClientErrorLike, + Prisma.PostGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.PostGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.PostGetPayload, Context>, + ) => Promise>; + }; + }; + findFirst: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + findFirstOrThrow: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + findMany: { + useQuery: >>( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions>, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions>, Error>, + ) => UseTRPCInfiniteQueryResult>, TRPCClientErrorLike>; + }; + findUnique: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + findUniqueOrThrow: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + groupBy: { + useQuery: < + T extends Prisma.PostGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: Prisma.PostGroupByArgs['orderBy'] } + : { orderBy?: Prisma.PostGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [Error, 'Field ', P, ` in "having" needs to be provided in "by"`]; + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields], + TData = {} extends InputErrors ? Prisma.GetPostGroupByPayload : InputErrors, + >( + input: Prisma.SubsetIntersection & InputErrors, + opts?: UseTRPCQueryOptions< + string, + T, + {} extends InputErrors ? Prisma.GetPostGroupByPayload : InputErrors, + TData, + Error + >, + ) => UseTRPCQueryResult>; + useInfiniteQuery: < + T extends Prisma.PostGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: Prisma.PostGroupByArgs['orderBy'] } + : { orderBy?: Prisma.PostGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [Error, 'Field ', P, ` in "having" needs to be provided in "by"`]; + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields], + >( + input: Omit & InputErrors, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions< + string, + T, + {} extends InputErrors ? Prisma.GetPostGroupByPayload : InputErrors, + Error + >, + ) => UseTRPCInfiniteQueryResult< + {} extends InputErrors ? Prisma.GetPostGroupByPayload : InputErrors, + TRPCClientErrorLike + >; + }; + updateMany: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.PostUpdateManyArgs, + TRPCClientErrorLike, + Prisma.BatchPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.BatchPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.BatchPayload, Context>, + ) => Promise; + }; + }; + update: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.PostUpdateArgs, + TRPCClientErrorLike, + Prisma.PostGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.PostGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.PostGetPayload, Context>, + ) => Promise>; + }; + }; + upsert: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.PostUpsertArgs, + TRPCClientErrorLike, + Prisma.PostGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.PostGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.PostGetPayload, Context>, + ) => Promise>; + }; + }; + count: { + useQuery: < + T extends Prisma.PostCountArgs, + TData = 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + >( + input: Prisma.Subset, + opts?: UseTRPCQueryOptions< + string, + T, + 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + TData, + Error + >, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions< + string, + T, + 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + Error + >, + ) => UseTRPCInfiniteQueryResult< + 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + TRPCClientErrorLike + >; + }; +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts new file mode 100644 index 000000000..15bd74328 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/User.router.ts @@ -0,0 +1,451 @@ +/* eslint-disable */ +import { type RouterFactory, type ProcBuilder, type BaseConfig, db } from '.'; +import $Schema from '@zenstackhq/runtime/zod/input'; +import { checkRead, checkMutate } from '../helper'; +import type { Prisma } from '@prisma/client'; +import type { + UseTRPCMutationOptions, + UseTRPCMutationResult, + UseTRPCQueryOptions, + UseTRPCQueryResult, + UseTRPCInfiniteQueryOptions, + UseTRPCInfiniteQueryResult, +} from '@trpc/react-query/shared'; +import type { TRPCClientErrorLike } from '@trpc/client'; +import type { AnyRouter } from '@trpc/server'; + +export default function createRouter( + router: RouterFactory, + procedure: ProcBuilder, +) { + return router({ + aggregate: procedure + .input($Schema.UserInputSchema.aggregate) + .query(({ ctx, input }) => checkRead(db(ctx).user.aggregate(input as any))), + + create: procedure + .input($Schema.UserInputSchema.create) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).user.create(input as any))), + + deleteMany: procedure + .input($Schema.UserInputSchema.deleteMany) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).user.deleteMany(input as any))), + + delete: procedure + .input($Schema.UserInputSchema.delete) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).user.delete(input as any))), + + findFirst: procedure + .input($Schema.UserInputSchema.findFirst) + .query(({ ctx, input }) => checkRead(db(ctx).user.findFirst(input as any))), + + findFirstOrThrow: procedure + .input($Schema.UserInputSchema.findFirst) + .query(({ ctx, input }) => checkRead(db(ctx).user.findFirstOrThrow(input as any))), + + findMany: procedure + .input($Schema.UserInputSchema.findMany) + .query(({ ctx, input }) => checkRead(db(ctx).user.findMany(input as any))), + + findUnique: procedure + .input($Schema.UserInputSchema.findUnique) + .query(({ ctx, input }) => checkRead(db(ctx).user.findUnique(input as any))), + + findUniqueOrThrow: procedure + .input($Schema.UserInputSchema.findUnique) + .query(({ ctx, input }) => checkRead(db(ctx).user.findUniqueOrThrow(input as any))), + + groupBy: procedure + .input($Schema.UserInputSchema.groupBy) + .query(({ ctx, input }) => checkRead(db(ctx).user.groupBy(input as any))), + + updateMany: procedure + .input($Schema.UserInputSchema.updateMany) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).user.updateMany(input as any))), + + update: procedure + .input($Schema.UserInputSchema.update) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).user.update(input as any))), + + upsert: procedure + .input($Schema.UserInputSchema.upsert) + .mutation(async ({ ctx, input }) => checkMutate(db(ctx).user.upsert(input as any))), + + count: procedure + .input($Schema.UserInputSchema.count) + .query(({ ctx, input }) => checkRead(db(ctx).user.count(input as any))), + }); +} + +export interface ClientType { + aggregate: { + useQuery: >( + input: Prisma.Subset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + create: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.UserCreateArgs, + TRPCClientErrorLike, + Prisma.UserGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.UserGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.UserGetPayload, Context>, + ) => Promise>; + }; + }; + deleteMany: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.UserDeleteManyArgs, + TRPCClientErrorLike, + Prisma.BatchPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.BatchPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.BatchPayload, Context>, + ) => Promise; + }; + }; + delete: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.UserDeleteArgs, + TRPCClientErrorLike, + Prisma.UserGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.UserGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.UserGetPayload, Context>, + ) => Promise>; + }; + }; + findFirst: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + findFirstOrThrow: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + findMany: { + useQuery: >>( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions>, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions>, Error>, + ) => UseTRPCInfiniteQueryResult>, TRPCClientErrorLike>; + }; + findUnique: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + findUniqueOrThrow: { + useQuery: >( + input: Prisma.SelectSubset, + opts?: UseTRPCQueryOptions, TData, Error>, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions, Error>, + ) => UseTRPCInfiniteQueryResult, TRPCClientErrorLike>; + }; + groupBy: { + useQuery: < + T extends Prisma.UserGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: Prisma.UserGroupByArgs['orderBy'] } + : { orderBy?: Prisma.UserGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [Error, 'Field ', P, ` in "having" needs to be provided in "by"`]; + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields], + TData = {} extends InputErrors ? Prisma.GetUserGroupByPayload : InputErrors, + >( + input: Prisma.SubsetIntersection & InputErrors, + opts?: UseTRPCQueryOptions< + string, + T, + {} extends InputErrors ? Prisma.GetUserGroupByPayload : InputErrors, + TData, + Error + >, + ) => UseTRPCQueryResult>; + useInfiniteQuery: < + T extends Prisma.UserGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: Prisma.UserGroupByArgs['orderBy'] } + : { orderBy?: Prisma.UserGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [Error, 'Field ', P, ` in "having" needs to be provided in "by"`]; + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`; + }[OrderFields], + >( + input: Omit & InputErrors, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions< + string, + T, + {} extends InputErrors ? Prisma.GetUserGroupByPayload : InputErrors, + Error + >, + ) => UseTRPCInfiniteQueryResult< + {} extends InputErrors ? Prisma.GetUserGroupByPayload : InputErrors, + TRPCClientErrorLike + >; + }; + updateMany: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.UserUpdateManyArgs, + TRPCClientErrorLike, + Prisma.BatchPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.BatchPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.BatchPayload, Context>, + ) => Promise; + }; + }; + update: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.UserUpdateArgs, + TRPCClientErrorLike, + Prisma.UserGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.UserGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.UserGetPayload, Context>, + ) => Promise>; + }; + }; + upsert: { + useMutation: ( + opts?: UseTRPCMutationOptions< + Prisma.UserUpsertArgs, + TRPCClientErrorLike, + Prisma.UserGetPayload, + Context + >, + ) => Omit< + UseTRPCMutationResult< + Prisma.UserGetPayload, + TRPCClientErrorLike, + Prisma.SelectSubset, + Context + >, + 'mutateAsync' + > & { + mutateAsync: ( + variables: T, + opts?: UseTRPCMutationOptions, Prisma.UserGetPayload, Context>, + ) => Promise>; + }; + }; + count: { + useQuery: < + T extends Prisma.UserCountArgs, + TData = 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + >( + input: Prisma.Subset, + opts?: UseTRPCQueryOptions< + string, + T, + 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + TData, + Error + >, + ) => UseTRPCQueryResult>; + useInfiniteQuery: ( + input: Omit, 'cursor'>, + opts?: UseTRPCInfiniteQueryOptions< + string, + T, + 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + Error + >, + ) => UseTRPCInfiniteQueryResult< + 'select' extends keyof T + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number, + TRPCClientErrorLike + >; + }; +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/index.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/index.ts new file mode 100644 index 000000000..bcb767b6f --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/generated/routers/index.ts @@ -0,0 +1,48 @@ +/* eslint-disable */ +import { + unsetMarker, + type AnyRouter, + type AnyRootConfig, + type CreateRouterInner, + type Procedure, + type ProcedureBuilder, + type ProcedureParams, + type ProcedureRouterRecord, + type ProcedureType, +} from '@trpc/server'; +import { type PrismaClient } from '@prisma/client'; +import createUserRouter from './User.router'; +import createPostRouter from './Post.router'; +import { ClientType as UserClientType } from './User.router'; +import { ClientType as PostClientType } from './Post.router'; + +export type BaseConfig = AnyRootConfig; + +export type RouterFactory = ( + procedures: ProcRouterRecord, +) => CreateRouterInner; + +export type UnsetMarker = typeof unsetMarker; + +export type ProcBuilder = ProcedureBuilder< + ProcedureParams +>; + +export function db(ctx: any) { + if (!ctx.prisma) { + throw new Error('Missing "prisma" field in trpc context'); + } + return ctx.prisma as PrismaClient; +} + +export function createRouter(router: RouterFactory, procedure: ProcBuilder) { + return router({ + user: createUserRouter(router, procedure), + post: createPostRouter(router, procedure), + }); +} + +export interface ClientType { + user: UserClientType; + post: PostClientType; +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/greet.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/greet.ts new file mode 100644 index 000000000..2d1fbbbea --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/greet.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +import { createTRPCRouter, publicProcedure } from '~/server/api/trpc'; + +export const greetRouter = createTRPCRouter({ + hello: publicProcedure.input(z.object({ text: z.string() })).query(({ input }) => { + return { + greeting: `Hello ${input.text}`, + }; + }), +}); diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/post.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/post.ts new file mode 100644 index 000000000..04067d65c --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/routers/post.ts @@ -0,0 +1,4 @@ +import { createTRPCRouter, publicProcedure } from '../trpc'; +import { createRouter } from './generated/routers'; + +export const postRouter = createRouter(createTRPCRouter, publicProcedure); diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/trpc.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/trpc.ts new file mode 100644 index 000000000..dc3b40b24 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/api/trpc.ts @@ -0,0 +1,95 @@ +/** + * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS: + * 1. You want to modify request context (see Part 1). + * 2. You want to create a new middleware or type of procedure (see Part 3). + * + * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will + * need to use are documented accordingly near the end. + */ +import { initTRPC } from "@trpc/server"; +import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; +import superjson from "superjson"; +import { ZodError } from "zod"; + +import { db } from "~/server/db"; + +/** + * 1. CONTEXT + * + * This section defines the "contexts" that are available in the backend API. + * + * These allow you to access things when processing a request, like the database, the session, etc. + */ + +type CreateContextOptions = Record; + +/** + * This helper generates the "internals" for a tRPC context. If you need to use it, you can export + * it from here. + * + * Examples of things you may need it for: + * - testing, so we don't have to mock Next.js' req/res + * - tRPC's `createSSGHelpers`, where we don't have req/res + * + * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts + */ +const createInnerTRPCContext = (_opts: CreateContextOptions) => { + return { + db, + }; +}; + +/** + * This is the actual context you will use in your router. It will be used to process every request + * that goes through your tRPC endpoint. + * + * @see https://trpc.io/docs/context + */ +export const createTRPCContext = (_opts: CreateNextContextOptions) => { + return createInnerTRPCContext({}); +}; + +/** + * 2. INITIALIZATION + * + * This is where the tRPC API is initialized, connecting the context and transformer. We also parse + * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation + * errors on the backend. + */ + +const t = initTRPC.context().create({ + transformer: superjson, + errorFormatter({ shape, error }) { + return { + ...shape, + data: { + ...shape.data, + zodError: + error.cause instanceof ZodError ? error.cause.flatten() : null, + }, + }; + }, +}); + +/** + * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) + * + * These are the pieces you use to build your tRPC API. You should import these a lot in the + * "/src/server/api/routers" directory. + */ + +/** + * This is how you create new routers and sub-routers in your tRPC API. + * + * @see https://trpc.io/docs/router + */ +export const createTRPCRouter = t.router; + +/** + * Public (unauthenticated) procedure + * + * This is the base piece you use to build new queries and mutations on your tRPC API. It does not + * guarantee that a user querying is authorized, but you can still access user session data if they + * are logged in. + */ +export const publicProcedure = t.procedure; diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/db.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/db.ts new file mode 100644 index 000000000..02696bcc3 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/server/db.ts @@ -0,0 +1,16 @@ +import { PrismaClient } from "@prisma/client"; + +import { env } from "~/env"; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +export const db = + globalForPrisma.prisma ?? + new PrismaClient({ + log: + env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], + }); + +if (env.NODE_ENV !== "production") globalForPrisma.prisma = db; diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/styles/globals.css b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/styles/globals.css new file mode 100644 index 000000000..e5e2dcc23 --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/styles/globals.css @@ -0,0 +1,16 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/utils/api.ts b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/utils/api.ts new file mode 100644 index 000000000..abbfef8de --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/utils/api.ts @@ -0,0 +1,68 @@ +/** + * This is the client-side entrypoint for your tRPC API. It is used to create the `api` object which + * contains the Next.js App-wrapper, as well as your type-safe React Query hooks. + * + * We also create a few inference helpers for input and output types. + */ +import { httpBatchLink, loggerLink } from '@trpc/client'; +import { createTRPCNext } from '~/server/api/routers/generated/client/next'; +import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server'; +import superjson from 'superjson'; + +import { type AppRouter } from '~/server/api/root'; + +const getBaseUrl = () => { + if (typeof window !== 'undefined') return ''; // browser should use relative url + if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url + return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost +}; + +/** A set of type-safe react-query hooks for your tRPC API. */ +export const api = createTRPCNext({ + config() { + return { + /** + * Transformer used for data de-serialization from the server. + * + * @see https://trpc.io/docs/data-transformers + */ + transformer: superjson, + + /** + * Links used to determine request flow from client to server. + * + * @see https://trpc.io/docs/links + */ + links: [ + loggerLink({ + enabled: (opts) => + process.env.NODE_ENV === 'development' || + (opts.direction === 'down' && opts.result instanceof Error), + }), + httpBatchLink({ + url: `${getBaseUrl()}/api/trpc`, + }), + ], + }; + }, + /** + * Whether tRPC should await queries when server rendering pages. + * + * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false + */ + ssr: false, +}); + +/** + * Inference helper for inputs. + * + * @example type HelloInput = RouterInputs['example']['hello'] + */ +export type RouterInputs = inferRouterInputs; + +/** + * Inference helper for outputs. + * + * @example type HelloOutput = RouterOutputs['example']['hello'] + */ +export type RouterOutputs = inferRouterOutputs; diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/tsconfig.json b/packages/plugins/trpc/tests/projects/t3-trpc-v10/tsconfig.json new file mode 100644 index 000000000..905062ded --- /dev/null +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + "checkJs": true, + + /* Bundled projects */ + "lib": ["dom", "dom.iterable", "ES2022"], + "noEmit": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "plugins": [{ "name": "next" }], + "incremental": true, + + /* Path Aliases */ + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + } + }, + "include": [ + ".eslintrc.cjs", + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "**/*.cjs", + "**/*.js", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/packages/plugins/trpc/tests/t3.test.ts b/packages/plugins/trpc/tests/t3.test.ts new file mode 100644 index 000000000..1c808de23 --- /dev/null +++ b/packages/plugins/trpc/tests/t3.test.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +import { run } from '@zenstackhq/testtools'; +import path from 'path'; + +describe('tRPC plugin tests with create-t3-app', () => { + let origDir: string | undefined; + + beforeEach(() => { + origDir = process.cwd(); + }); + + afterEach(() => { + if (origDir) { + process.chdir(origDir); + } + }); + + it('project test trpc v10', () => { + const ver = require(path.join(__dirname, '../package.json')).version; + process.chdir(path.join(__dirname, './projects/t3-trpc-v10')); + + const deps = ['zenstackhq-language', 'zenstackhq-runtime', 'zenstackhq-sdk', 'zenstack']; + for (const dep of deps) { + run(`npm install ${path.join(__dirname, '../../../../.build/') + dep + '-' + ver + '.tgz'}`); + } + + run('npx zenstack generate'); + run('npm run build'); + }); +}); diff --git a/packages/plugins/trpc/tests/trpc.test.ts b/packages/plugins/trpc/tests/trpc.test.ts index cf43c9a49..ca4a9c14d 100644 --- a/packages/plugins/trpc/tests/trpc.test.ts +++ b/packages/plugins/trpc/tests/trpc.test.ts @@ -56,7 +56,7 @@ model Foo { { provider: 'postgresql', pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, fullZod: true, } @@ -98,7 +98,7 @@ model Foo { `, { pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, fullZod: true, } @@ -128,7 +128,7 @@ model Post { `, { pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, fullZod: true, customSchemaFilePath: 'zenstack/schema.zmodel', @@ -153,7 +153,7 @@ model Post { `, { pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, fullZod: true, customSchemaFilePath: 'zenstack/schema.zmodel', @@ -183,7 +183,7 @@ model Post { `, { pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, fullZod: true, customSchemaFilePath: 'zenstack/schema.zmodel', @@ -229,7 +229,12 @@ model Post { `, { pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server', '@trpc/react-query'], + extraDependencies: [ + `${path.join(__dirname, '../dist')}`, + '@trpc/client', + '@trpc/server', + '@trpc/react-query', + ], compile: true, fullZod: true, } @@ -249,7 +254,7 @@ model Post { `, { pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server', '@trpc/next'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server', '@trpc/next'], compile: true, fullZod: true, } @@ -279,7 +284,7 @@ model post_item { `, { pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, fullZod: true, } @@ -326,7 +331,7 @@ model Foo { { addPrelude: false, pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, } ); @@ -397,7 +402,7 @@ model Foo { { addPrelude: false, pushDb: false, - extraDependencies: [`${origDir}/dist`, '@trpc/client', '@trpc/server'], + extraDependencies: [`${path.join(__dirname, '../dist')}`, '@trpc/client', '@trpc/server'], compile: true, } ); diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index f69a845cc..2b0c5a380 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -37,7 +37,7 @@ export type FullDbClientContract = Record & { export function run(cmd: string, env?: Record, cwd?: string) { const start = Date.now(); execSync(cmd, { - stdio: 'pipe', + stdio: 'inherit', encoding: 'utf-8', env: { ...process.env, DO_NOT_TRACK: '1', ...env }, cwd, From 4f2cb7b994faa037dd08081daeecd13fd2e8d8a6 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:38:32 +0800 Subject: [PATCH 2/4] fix test --- .../tests/projects/t3-trpc-v10/src/env.js | 79 ++++++++----------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js index e3e371948..d0ba1eb56 100644 --- a/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js +++ b/packages/plugins/trpc/tests/projects/t3-trpc-v10/src/env.js @@ -1,49 +1,40 @@ -import { createEnv } from "@t3-oss/env-nextjs"; -import { z } from "zod"; +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; export const env = createEnv({ - /** - * Specify your server-side environment variables schema here. This way you can ensure the app - * isn't built with invalid env vars. - */ - server: { - DATABASE_URL: z - .string() - .refine( - (str) => !str.includes("YOUR_MYSQL_URL_HERE"), - "You forgot to change the default URL" - ), - NODE_ENV: z - .enum(["development", "test", "production"]) - .default("development"), - }, + /** + * Specify your server-side environment variables schema here. This way you can ensure the app + * isn't built with invalid env vars. + */ + server: { + NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), + }, - /** - * Specify your client-side environment variables schema here. This way you can ensure the app - * isn't built with invalid env vars. To expose them to the client, prefix them with - * `NEXT_PUBLIC_`. - */ - client: { - // NEXT_PUBLIC_CLIENTVAR: z.string(), - }, + /** + * Specify your client-side environment variables schema here. This way you can ensure the app + * isn't built with invalid env vars. To expose them to the client, prefix them with + * `NEXT_PUBLIC_`. + */ + client: { + // NEXT_PUBLIC_CLIENTVAR: z.string(), + }, - /** - * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. - * middlewares) or client-side so we need to destruct manually. - */ - runtimeEnv: { - DATABASE_URL: process.env.DATABASE_URL, - NODE_ENV: process.env.NODE_ENV, - // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, - }, - /** - * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially - * useful for Docker builds. - */ - skipValidation: !!process.env.SKIP_ENV_VALIDATION, - /** - * Makes it so that empty strings are treated as undefined. - * `SOME_VAR: z.string()` and `SOME_VAR=''` will throw an error. - */ - emptyStringAsUndefined: true, + /** + * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. + * middlewares) or client-side so we need to destruct manually. + */ + runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, + }, + /** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially + * useful for Docker builds. + */ + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + /** + * Makes it so that empty strings are treated as undefined. + * `SOME_VAR: z.string()` and `SOME_VAR=''` will throw an error. + */ + emptyStringAsUndefined: true, }); From 1df546f3a6fdad37b836d11c3806b329779b1c34 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:55:07 +0800 Subject: [PATCH 3/4] upgrade CI to use node 20 --- .github/workflows/build-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index aa060aa56..f92512bc8 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] prisma-version: [v4, v5] steps: From 1a1d7dbb72774b7b6aa8d9013c6def44600e41a9 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Wed, 31 Jan 2024 11:18:11 +0800 Subject: [PATCH 4/4] fix test --- packages/testtools/src/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 2b0c5a380..f69a845cc 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -37,7 +37,7 @@ export type FullDbClientContract = Record & { export function run(cmd: string, env?: Record, cwd?: string) { const start = Date.now(); execSync(cmd, { - stdio: 'inherit', + stdio: 'pipe', encoding: 'utf-8', env: { ...process.env, DO_NOT_TRACK: '1', ...env }, cwd,