Skip to content

Commit b4b08cd

Browse files
authored
fix(core): Adapt trpc middleware input attachment (#13831)
1 parent ca19f34 commit b4b08cd

33 files changed

+847
-39
lines changed

.github/workflows/build.yml

+1
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@ jobs:
902902
'nextjs-13',
903903
'nextjs-14',
904904
'nextjs-15',
905+
'nextjs-t3',
905906
'react-17',
906907
'react-19',
907908
'react-create-hash-router',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
37+
38+
!*.d.ts
39+
40+
# Sentry
41+
.sentryclirc
42+
43+
.vscode
44+
45+
test-results
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://127.0.0.1:4873
2+
@sentry-internal:registry=http://127.0.0.1:4873
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
await import('./src/env.js');
2+
3+
/** @type {import("next").NextConfig} */
4+
const config = {};
5+
6+
import { withSentryConfig } from '@sentry/nextjs';
7+
8+
export default withSentryConfig(config, {
9+
disableLogger: true,
10+
silent: true,
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"name": "t3",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"build": "next build",
8+
"clean": "npx rimraf node_modules pnpm-lock.yaml",
9+
"test:prod": "TEST_ENV=production playwright test",
10+
"test:dev": "TEST_ENV=development playwright test",
11+
"test:build": "pnpm install && npx playwright install && pnpm build",
12+
"test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build",
13+
"test:build-latest": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build",
14+
"test:assert": "pnpm test:prod && pnpm test:dev"
15+
},
16+
"dependencies": {
17+
"@sentry/nextjs": "latest || *",
18+
"@t3-oss/env-nextjs": "^0.10.1",
19+
"@tanstack/react-query": "^5.50.0",
20+
"@trpc/client": "^11.0.0-rc.446",
21+
"@trpc/react-query": "^11.0.0-rc.446",
22+
"@trpc/server": "^11.0.0-rc.446",
23+
"geist": "^1.3.0",
24+
"next": "^14.2.4",
25+
"react": "^18.3.1",
26+
"react-dom": "^18.3.1",
27+
"server-only": "^0.0.1",
28+
"superjson": "^2.2.1",
29+
"zod": "^3.23.3"
30+
},
31+
"devDependencies": {
32+
"@playwright/test": "^1.44.1",
33+
"@sentry-internal/test-utils": "link:../../../test-utils",
34+
"@types/eslint": "^8.56.10",
35+
"@types/node": "^20.14.10",
36+
"@types/react": "^18.3.3",
37+
"@types/react-dom": "^18.3.0",
38+
"@typescript-eslint/eslint-plugin": "^8.1.0",
39+
"@typescript-eslint/parser": "^8.1.0",
40+
"eslint": "^8.57.0",
41+
"eslint-config-next": "^14.2.4",
42+
"postcss": "^8.4.39",
43+
"prettier": "^3.3.2",
44+
"prettier-plugin-tailwindcss": "^0.6.5",
45+
"tailwindcss": "^3.4.3",
46+
"typescript": "^5.5.3"
47+
},
48+
"ct3aMetadata": {
49+
"initVersion": "7.37.0"
50+
},
51+
"volta": {
52+
"extends": "../../package.json"
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
2+
const testEnv = process.env.TEST_ENV;
3+
4+
if (!testEnv) {
5+
throw new Error('No test env defined');
6+
}
7+
8+
const config = getPlaywrightConfig(
9+
{
10+
startCommand: testEnv === 'development' ? 'pnpm next dev -p 3030' : 'pnpm next start -p 3030',
11+
port: 3030,
12+
},
13+
{
14+
// This comes with the risk of tests leaking into each other but the tests run quite slow so we should parallelize
15+
workers: '100%',
16+
},
17+
);
18+
19+
export default config;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const config = {
2+
plugins: {
3+
tailwindcss: {},
4+
},
5+
};
6+
7+
module.exports = config;
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
Sentry.init({
4+
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
5+
tunnel: `http://localhost:3031/`, // proxy server
6+
tracesSampleRate: 1,
7+
debug: false,
8+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
2+
// The config you add here will be used whenever one of the edge features is loaded.
3+
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
4+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
5+
6+
import * as Sentry from '@sentry/nextjs';
7+
8+
Sentry.init({
9+
environment: 'qa', // dynamic sampling bias to keep transactions
10+
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
11+
tunnel: `http://localhost:3031/`, // proxy server
12+
tracesSampleRate: 1.0,
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/nextjs';
2+
3+
Sentry.init({
4+
environment: 'qa', // dynamic sampling bias to keep transactions
5+
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
6+
tunnel: `http://localhost:3031/`, // proxy server
7+
tracesSampleRate: 1.0,
8+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
5+
import { api } from '~/trpc/react';
6+
7+
export function LatestPost() {
8+
const [latestPost] = api.post.getLatest.useSuspenseQuery();
9+
10+
const utils = api.useUtils();
11+
const [name, setName] = useState('');
12+
const createPost = api.post.create.useMutation({
13+
onSuccess: async () => {
14+
await utils.post.invalidate();
15+
setName('');
16+
},
17+
});
18+
19+
const throwingMutation = api.post.throwError.useMutation({
20+
onSuccess: async () => {
21+
await utils.post.invalidate();
22+
setName('');
23+
},
24+
});
25+
26+
return (
27+
<div className="w-full max-w-xs">
28+
{latestPost ? (
29+
<p className="truncate">Your most recent post: {latestPost.name}</p>
30+
) : (
31+
<p>You have no posts yet.</p>
32+
)}
33+
<form
34+
onSubmit={e => {
35+
e.preventDefault();
36+
createPost.mutate({ name });
37+
}}
38+
className="flex flex-col gap-2"
39+
>
40+
<input
41+
type="text"
42+
placeholder="Title"
43+
value={name}
44+
onChange={e => setName(e.target.value)}
45+
id="createInput"
46+
className="w-full rounded-full px-4 py-2 text-black"
47+
/>
48+
<button
49+
type="submit"
50+
id="createButton"
51+
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
52+
disabled={createPost.isPending}
53+
>
54+
{createPost.isPending ? 'Submitting...' : 'Submit'}
55+
</button>
56+
</form>
57+
<form
58+
onSubmit={e => {
59+
e.preventDefault();
60+
throwingMutation.mutate({ name: 'I love dogs' });
61+
}}
62+
className="flex flex-col gap-2"
63+
>
64+
<button
65+
id="error-button"
66+
type="submit"
67+
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
68+
disabled={throwingMutation.isPending}
69+
>
70+
THROW TRPC
71+
</button>
72+
</form>
73+
</div>
74+
);
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2+
import { type NextRequest } from 'next/server';
3+
4+
import { env } from '~/env';
5+
import { appRouter } from '~/server/api/root';
6+
import { createTRPCContext } from '~/server/api/trpc';
7+
8+
/**
9+
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
10+
* handling a HTTP request (e.g. when you make requests from Client Components).
11+
*/
12+
const createContext = async (req: NextRequest) => {
13+
return createTRPCContext({
14+
headers: req.headers,
15+
});
16+
};
17+
18+
const handler = (req: NextRequest) =>
19+
fetchRequestHandler({
20+
endpoint: '/api/trpc',
21+
req,
22+
router: appRouter,
23+
createContext: () => createContext(req),
24+
onError:
25+
env.NODE_ENV === 'development'
26+
? ({ path, error }) => {
27+
console.error(`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message}`);
28+
}
29+
: undefined,
30+
});
31+
32+
export { handler as GET, handler as POST };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client';
2+
3+
import * as Sentry from '@sentry/nextjs';
4+
import NextError from 'next/error';
5+
import { useEffect } from 'react';
6+
7+
export default function GlobalError({
8+
error,
9+
}: {
10+
error: Error & { digest?: string };
11+
}) {
12+
useEffect(() => {
13+
Sentry.captureException(error);
14+
}, [error]);
15+
16+
return (
17+
<html>
18+
<body>
19+
{/* `NextError` is the default Next.js error page component. Its type
20+
definition requires a `statusCode` prop. However, since the App Router
21+
does not expose status codes for errors, we simply pass 0 to render a
22+
generic error message. */}
23+
<NextError statusCode={0} />
24+
</body>
25+
</html>
26+
);
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import '~/styles/globals.css';
2+
3+
import { GeistSans } from 'geist/font/sans';
4+
import { type Metadata } from 'next';
5+
6+
import { TRPCReactProvider } from '~/trpc/react';
7+
8+
export const metadata: Metadata = {
9+
title: 'Create T3 App',
10+
description: 'Generated by create-t3-app',
11+
icons: [{ rel: 'icon', url: '/favicon.ico' }],
12+
};
13+
14+
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
15+
return (
16+
<html lang="en" className={`${GeistSans.variable}`}>
17+
<body>
18+
<TRPCReactProvider>{children}</TRPCReactProvider>
19+
</body>
20+
</html>
21+
);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Link from 'next/link';
2+
3+
import { LatestPost } from '~/app/_components/post';
4+
import { HydrateClient, api } from '~/trpc/server';
5+
6+
export default async function Home() {
7+
const hello = await api.post.hello({ text: 'from tRPC' });
8+
9+
void api.post.getLatest.prefetch();
10+
11+
return (
12+
<HydrateClient>
13+
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
14+
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16">
15+
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
16+
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
17+
</h1>
18+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
19+
<Link
20+
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
21+
href="https://create.t3.gg/en/usage/first-steps"
22+
target="_blank"
23+
>
24+
<h3 className="text-2xl font-bold">First Steps →</h3>
25+
<div className="text-lg">
26+
Just the basics - Everything you need to know to set up your database and authentication.
27+
</div>
28+
</Link>
29+
<Link
30+
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
31+
href="https://create.t3.gg/en/introduction"
32+
target="_blank"
33+
>
34+
<h3 className="text-2xl font-bold">Documentation →</h3>
35+
<div className="text-lg">
36+
Learn more about Create T3 App, the libraries it uses, and how to deploy it.
37+
</div>
38+
</Link>
39+
</div>
40+
<div className="flex flex-col items-center gap-2">
41+
<p className="text-2xl text-white">{hello ? hello.greeting : 'Loading tRPC query...'}</p>
42+
</div>
43+
44+
<LatestPost />
45+
</div>
46+
</main>
47+
</HydrateClient>
48+
);
49+
}

0 commit comments

Comments
 (0)