Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: testing a composite setup #5459

Draft
wants to merge 11 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/next-big-router-composite/.gitignore
@@ -0,0 +1,5 @@
.next
src/server/routers
src/module
node_modules
dist
5 changes: 5 additions & 0 deletions examples/next-big-router-composite/.prettierrc
@@ -0,0 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true
}
4 changes: 4 additions & 0 deletions examples/next-big-router-composite/.vscode/settings.json
@@ -0,0 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
9 changes: 9 additions & 0 deletions examples/next-big-router-composite/README.md
@@ -0,0 +1,9 @@
This is an example to preview how the DX is when making a big router with tRPC v10.

It has a `postinstall`-script that generates 700 procedures (which you can modify in `scripts/codegen.ts`).

```bash
git clone git@github.com:trpc/examples-v10-next-big-router.git
cd examples-v10-next-big-router
yarn && code . && yarn dev
```
2 changes: 2 additions & 0 deletions examples/next-big-router-composite/module/.gitignore
@@ -0,0 +1,2 @@
# ignore generated routers
router*
@@ -0,0 +1,25 @@
import type * as trpcNext from "@trpc/server/adapters/next";

interface CreateContextOptions {
// session: Session | null
}

/**
* Inner function for `createContext` where we create the context.
* This is useful for testing when we don't want to mock Next.js' request/response
*/
export async function createContextInner(_opts: CreateContextOptions) {
return {};
}

export type Context = Awaited<ReturnType<typeof createContextInner>>;

/**
* Creates context for an incoming request
* @link https://trpc.io/docs/v11/context
*/
export async function createContext(
opts: trpcNext.CreateNextContextOptions
): Promise<Context> {
return await createContextInner({});
}
@@ -0,0 +1,8 @@
import { initTRPC } from "@trpc/server";
import type { Context } from "./context";

export const t = initTRPC.context<Context>().create();

export const router = t.router;

export const publicProcedure = t.procedure;
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig-base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist"
},
"references": []
}
@@ -0,0 +1,31 @@
import { httpBatchLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
import type { AppRouter } from "../../router-app/server/_app";

function getBaseUrl() {
if (typeof window !== "undefined") {
// In the browser, we return a relative URL
return "";
}
// When rendering on the server, we return an absolute URL

// reference for vercel.com
if (process.env.VERCEL_URL) {
return `https://${process.env.VERCEL_URL}`;
}

// assume localhost
return `http://localhost:${process.env.PORT ?? 3000}`;
}

export const trpc = createTRPCNext<AppRouter>({
config() {
return {
links: [
httpBatchLink({
url: getBaseUrl() + "/api/trpc",
}),
],
};
},
});
5 changes: 5 additions & 0 deletions examples/next-big-router-composite/next-env.d.ts
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
5 changes: 5 additions & 0 deletions examples/next-big-router-composite/next.config.js
@@ -0,0 +1,5 @@
/** @type {import("next").NextConfig} */
module.exports = {
/** We run eslint as a separate task in CI */
eslint: { ignoreDuringBuilds: !!process.env.CI },
};
32 changes: 32 additions & 0 deletions examples/next-big-router-composite/package.json
@@ -0,0 +1,32 @@
{
"name": "@examples/next-big-router-composite",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"lint": "eslint --cache --ext \".js,.ts,.tsx\" src",
"start": "next start",
"postinstall": "tsx scripts/codegen",
"tsc": "tsc"
},
"dependencies": {
"@tanstack/react-query": "^5.0.0",
"@trpc/client": "npm:@trpc/client@next",
"@trpc/next": "npm:@trpc/next@next",
"@trpc/react-query": "npm:@trpc/react-query@next",
"@trpc/server": "npm:@trpc/server@next",
"next": "^14.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.0.0"
},
"devDependencies": {
"@types/node": "^20.10.0",
"@types/react": "^18.2.33",
"@types/react-dom": "^18.2.14",
"eslint": "^8.40.0",
"prettier": "^2.8.8",
"tsx": "^4.0.0",
"typescript": "^5.3.3"
}
}
108 changes: 108 additions & 0 deletions examples/next-big-router-composite/scripts/codegen.ts
@@ -0,0 +1,108 @@
import fs from "fs";

// Modify this is if you want to try bigger routers
// Each router will have 5 procedures + a small sub-router with 2 procedures
const NUM_ROUTERS = 100;

const ROOT_DIR = __dirname + "../";
const MODULES_DIR = __dirname + "/../module";

// delete all folders in MODULES_DIR that start with 'router'
const folders = fs.readdirSync(MODULES_DIR);
for (const folder of folders) {
if (folder.startsWith("router")) {
fs.rmSync(MODULES_DIR + "/" + folder, { recursive: true });
}
}

// read file codege-base.ts in the same dir as this script
const routerBase = fs.readFileSync(
__dirname + "/router-x/server/codegen-router.ts",
"utf-8"
);

function createRouter(routerName: string) {
return routerBase.replace("__ROUTER__NAME__", routerName);
}

const indexBuf: string[] = [];
for (let i = 0; i < NUM_ROUTERS; i++) {
const routerName = `router${i}`;
indexBuf.push(routerName);
const moduleDir = `${MODULES_DIR}/${routerName}`;
const serverDir = `${moduleDir}/server`;
fs.mkdirSync(serverDir, {
recursive: true,
});
fs.writeFileSync(`${serverDir}/${routerName}.ts`, createRouter(routerName));

const tsconfig = {
extends: "../../tsconfig-base.json",
compilerOptions: {
rootDir: ".",
outDir: "./dist",
},
includes: ["./**/*.ts"],
references: [
{
path: "../trpc-base",
},
],
};
fs.writeFileSync(
`${moduleDir}/tsconfig.json`,
JSON.stringify(tsconfig, null, 4)
);
}

{
// router-app

const moduleDir = `${MODULES_DIR}/router-app`;
const serverDir = `${moduleDir}/server`;

{
// create index file
const indexFile = `
import { router } from '../../trpc-base/server/trpc';

${indexBuf
.map((name) => `import { ${name} } from '../../${name}/server/${name}';`)
.join("\n")}

export const appRouter = router({
${indexBuf.join(",\n ")}
})

// export only the type definition of the API
// None of the actual implementation is exposed to the client
export type AppRouter = typeof appRouter;
`.trim();

fs.mkdirSync(serverDir, {
recursive: true,
});
fs.writeFileSync(`${serverDir}/_app.ts`, indexFile);
}

{
// create composite tsconfig for the generated router-app

const tsconfig = {
extends: "../../tsconfig-base.json",
compilerOptions: {
rootDir: ".",
outDir: "./dist",
},
includes: ["./**/*.ts"],
references: ["trpc-base", ...indexBuf].map((name) => ({
path: `../${name}`,
})),
};

fs.writeFileSync(
`${moduleDir}/tsconfig.json`,
JSON.stringify(tsconfig, null, 4)
);
}
}
@@ -0,0 +1,45 @@
import type { TRPCRouterRecord } from "@trpc/server";
import { z } from "zod";
import { publicProcedure } from "../../../module/trpc-base/server/trpc";

export const __ROUTER__NAME__ = {
greeting: publicProcedure
.input(
z.object({
who: z.string(),
})
)
.query(({ input }) => `hello ${input.who}`),
greeting2: publicProcedure
.input(
z.object({
who: z.string(),
})
)
.query(({ input }) => `hello ${input.who}`),
greeting3: publicProcedure
.input(
z.object({
who: z.string(),
})
)
.query(({ input }) => `hello ${input.who}`),
greeting4: publicProcedure
.input(
z.object({
who: z.string(),
})
)
.query(({ input }) => `hello ${input.who}`),
greeting5: publicProcedure
.input(
z.object({
who: z.string(),
})
)
.query(({ input }) => `hello ${input.who}`),
childRouter: {
hello: publicProcedure.query(() => "there"),
doSomething: publicProcedure.mutation(() => "okay"),
},
} satisfies TRPCRouterRecord;
8 changes: 8 additions & 0 deletions examples/next-big-router-composite/src/pages/_app.tsx
@@ -0,0 +1,8 @@
import type { AppType } from "next/app";
import { trpc } from "../../module/trpc/shared/nextClient";

const MyApp: AppType = ({ Component, pageProps }) => {
return <Component {...pageProps} />;
};

export default trpc.withTRPC(MyApp);
13 changes: 13 additions & 0 deletions examples/next-big-router-composite/src/pages/api/trpc/[trpc].ts
@@ -0,0 +1,13 @@
/**
* This is the API-handler of your app that contains all your API routes.
* On a bigger app, you will probably want to split this file up into multiple files.
*/
import * as trpcNext from "@trpc/server/adapters/next";
import { createContext } from "~/server/context";
import { appRouter } from "~/server/routers/_app";

// export API handler
export default trpcNext.createNextApiHandler({
router: appRouter,
createContext,
});
32 changes: 32 additions & 0 deletions examples/next-big-router-composite/src/pages/index.tsx
@@ -0,0 +1,32 @@
/**
* This is a Next.js page.
*/

import { trpc } from "../../module/trpc/shared/nextClient";

export default function IndexPage() {
// 💡 Tip: CMD+Click (or CTRL+Click) on `greeting` to go to the server definition
const result = trpc.router99.greeting.useQuery({ who: "KATT" });

if (!result.data) {
return (
<div style={styles}>
<h1>Loading...</h1>
</div>
);
}
return (
<div style={styles}>
<h1>{result.data}</h1>
{/* ^? */}
</div>
);
}

const styles = {
width: "100vw",
height: "100vh",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
16 changes: 16 additions & 0 deletions examples/next-big-router-composite/src/tsconfig.json
@@ -0,0 +1,16 @@
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist"
},
"include": ["./**/*.ts"],
"references": [
{
"path": "../module/trpc-base"
},
{
"path": "../module/router-app"
}
]
}