From b18e50a71a8ba4e51d3769861de3f8382493eead Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 11:50:58 +0200 Subject: [PATCH 1/8] fix: make trpc raw input generation v11 compatible --- packages/core/src/trpc.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index 1320f0ff15bc..48b5c9b0d09d 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -15,6 +15,7 @@ export interface SentryTrpcMiddlewareArguments { type?: unknown; next: () => T; rawInput?: unknown; + getRawInput?: () => Promise; } const trpcCaptureContext = { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } }; @@ -24,7 +25,8 @@ const trpcCaptureContext = { mechanism: { handled: false, data: { function: 'trp */ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { return function (opts: SentryTrpcMiddlewareArguments): T { - const { path, type, next, rawInput } = opts; + const { path, type, next, rawInput, getRawInput } = opts; + const client = getClient(); const clientOptions = client && client.getOptions(); @@ -33,10 +35,23 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { }; if (options.attachRpcInput !== undefined ? options.attachRpcInput : clientOptions && clientOptions.sendDefaultPii) { - trpcContext.input = normalize(rawInput); - } + if (rawInput !== undefined) { + trpcContext.input = normalize(rawInput); + setContext('trpc', trpcContext); + } - setContext('trpc', trpcContext); + if (getRawInput !== undefined && typeof getRawInput === 'function') { + getRawInput().then( + rawRes => { + trpcContext.input = normalize(rawRes); + setContext('trpc', trpcContext); + }, + _e => { + // noop + }, + ); + } + } function captureIfError(nextResult: unknown): void { // TODO: Set span status based on what TRPCError was encountered From cb8b0d382d789945d8e37b01da5bbb036303edaf Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 11:52:20 +0200 Subject: [PATCH 2/8] test: add t3 e2e app --- .github/workflows/build.yml | 1 + .../test-applications/nextjs-t3/.gitignore | 45 +++++++ .../test-applications/nextjs-t3/.npmrc | 2 + .../test-applications/nextjs-t3/README.md | 29 +++++ .../test-applications/nextjs-t3/next-env.d.ts | 5 + .../nextjs-t3/next.config.js | 11 ++ .../test-applications/nextjs-t3/package.json | 52 ++++++++ .../nextjs-t3/playwright.config.mjs | 19 +++ .../nextjs-t3/postcss.config.cjs | 7 ++ .../nextjs-t3/public/favicon.ico | Bin 0 -> 15406 bytes .../nextjs-t3/sentry.client.config.ts | 8 ++ .../nextjs-t3/sentry.edge.config.ts | 13 ++ .../nextjs-t3/sentry.server.config.ts | 8 ++ .../nextjs-t3/src/app/_components/post.tsx | 75 ++++++++++++ .../src/app/api/trpc/[trpc]/route.ts | 34 ++++++ .../nextjs-t3/src/app/global-error.tsx | 27 +++++ .../nextjs-t3/src/app/layout.tsx | 24 ++++ .../nextjs-t3/src/app/page.tsx | 53 +++++++++ .../test-applications/nextjs-t3/src/env.js | 40 +++++++ .../nextjs-t3/src/instrumentation.ts | 13 ++ .../nextjs-t3/src/server/api/root.ts | 23 ++++ .../nextjs-t3/src/server/api/routers/post.ts | 39 ++++++ .../nextjs-t3/src/server/api/trpc.ts | 112 ++++++++++++++++++ .../nextjs-t3/src/styles/globals.css | 3 + .../nextjs-t3/src/trpc/query-client.ts | 25 ++++ .../nextjs-t3/src/trpc/react.tsx | 76 ++++++++++++ .../nextjs-t3/src/trpc/server.ts | 30 +++++ .../nextjs-t3/start-event-proxy.mjs | 6 + .../nextjs-t3/tailwind.config.ts | 14 +++ .../nextjs-t3/tests/trpc-error.test.ts | 34 ++++++ .../nextjs-t3/tests/trpc-mutation.test.ts | 19 +++ .../test-applications/nextjs-t3/tsconfig.json | 42 +++++++ 32 files changed, 889 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/README.md create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/next-env.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/next.config.js create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/package.json create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/postcss.config.cjs create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/public/favicon.ico create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.edge.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.server.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/_components/post.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/routers/post.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/styles/globals.css create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67d956b620be..0a9587bada7d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -902,6 +902,7 @@ jobs: 'nextjs-13', 'nextjs-14', 'nextjs-15', + 'nextjs-t3', 'react-17', 'react-19', 'react-create-hash-router', diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-t3/.gitignore new file mode 100644 index 000000000000..e799cc33c4e7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/.gitignore @@ -0,0 +1,45 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +!*.d.ts + +# Sentry +.sentryclirc + +.vscode + +test-results diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/.npmrc b/dev-packages/e2e-tests/test-applications/nextjs-t3/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/README.md b/dev-packages/e2e-tests/test-applications/nextjs-t3/README.md new file mode 100644 index 000000000000..67943c7fb44c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/README.md @@ -0,0 +1,29 @@ +# 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) +- [Drizzle](https://orm.drizzle.team) +- [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/dev-packages/e2e-tests/test-applications/nextjs-t3/next-env.d.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/next-env.d.ts new file mode 100644 index 000000000000..40c3d68096c2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/next.config.js b/dev-packages/e2e-tests/test-applications/nextjs-t3/next.config.js new file mode 100644 index 000000000000..b22141b67893 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/next.config.js @@ -0,0 +1,11 @@ +await import('./src/env.js'); + +/** @type {import("next").NextConfig} */ +const config = {}; + +import { withSentryConfig } from '@sentry/nextjs'; + +export default withSentryConfig(config, { + disableLogger: true, + silent: true, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json new file mode 100644 index 000000000000..4a2378a5483b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json @@ -0,0 +1,52 @@ +{ + "name": "t3", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:prod": "TEST_ENV=production playwright test", + "test:dev": "TEST_ENV=development playwright test", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", + "test:build-latest": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", + "test:assert": "pnpm test:prod && pnpm test:dev" + }, + "dependencies": { + "@sentry/nextjs": "latest || *", + "@t3-oss/env-nextjs": "^0.10.1", + "@tanstack/react-query": "^5.50.0", + "@trpc/client": "^11.0.0-rc.446", + "@trpc/react-query": "^11.0.0-rc.446", + "@trpc/server": "^11.0.0-rc.446", + "geist": "^1.3.0", + "next": "^14.2.4", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "server-only": "^0.0.1", + "superjson": "^2.2.1", + "zod": "^3.23.3" + }, + "devDependencies": { + "@types/eslint": "^8.56.10", + "@types/node": "^20.14.10", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^8.1.0", + "@typescript-eslint/parser": "^8.1.0", + "eslint": "^8.57.0", + "eslint-config-next": "^14.2.4", + "postcss": "^8.4.39", + "prettier": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.5", + "tailwindcss": "^3.4.3", + "typescript": "^5.5.3" + }, + "ct3aMetadata": { + "initVersion": "7.37.0" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-t3/playwright.config.mjs new file mode 100644 index 000000000000..8448829443d6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/playwright.config.mjs @@ -0,0 +1,19 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; +const testEnv = process.env.TEST_ENV; + +if (!testEnv) { + throw new Error('No test env defined'); +} + +const config = getPlaywrightConfig( + { + startCommand: testEnv === 'development' ? 'pnpm next dev -p 3030' : 'pnpm next start -p 3030', + port: 3030, + }, + { + // This comes with the risk of tests leaking into each other but the tests run quite slow so we should parallelize + workers: '100%', + }, +); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/postcss.config.cjs b/dev-packages/e2e-tests/test-applications/nextjs-t3/postcss.config.cjs new file mode 100644 index 000000000000..4cdb2f430f8e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/postcss.config.cjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +module.exports = config; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/public/favicon.ico b/dev-packages/e2e-tests/test-applications/nextjs-t3/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/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts new file mode 100644 index 000000000000..0e3121a8f01b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, + debug: false, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.edge.config.ts new file mode 100644 index 000000000000..4f1cb3e93e9c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.edge.config.ts @@ -0,0 +1,13 @@ +// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on). +// The config you add here will be used whenever one of the edge features is loaded. +// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.server.config.ts new file mode 100644 index 000000000000..ad780407a5b7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.server.config.ts @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/_components/post.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/_components/post.tsx new file mode 100644 index 000000000000..0b1c6dcf367b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/_components/post.tsx @@ -0,0 +1,75 @@ +'use client'; + +import { useState } from 'react'; + +import { api } from '~/trpc/react'; + +export function LatestPost() { + const [latestPost] = api.post.getLatest.useSuspenseQuery(); + + const utils = api.useUtils(); + const [name, setName] = useState(''); + const createPost = api.post.create.useMutation({ + onSuccess: async () => { + await utils.post.invalidate(); + setName(''); + }, + }); + + const throwingMutation = api.post.throwError.useMutation({ + onSuccess: async () => { + await utils.post.invalidate(); + setName(''); + }, + }); + + return ( +
+ {latestPost ? ( +

Your most recent post: {latestPost.name}

+ ) : ( +

You have no posts yet.

+ )} +
{ + e.preventDefault(); + createPost.mutate({ name }); + }} + className="flex flex-col gap-2" + > + setName(e.target.value)} + id="createInput" + className="w-full rounded-full px-4 py-2 text-black" + /> + +
+
{ + e.preventDefault(); + throwingMutation.mutate({ name: 'I love dogs' }); + }} + className="flex flex-col gap-2" + > + +
+
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts new file mode 100644 index 000000000000..f41a82e0a2b8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts @@ -0,0 +1,34 @@ +import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; +import { type NextRequest } from "next/server"; + +import { env } from "~/env"; +import { appRouter } from "~/server/api/root"; +import { createTRPCContext } from "~/server/api/trpc"; + +/** + * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when + * handling a HTTP request (e.g. when you make requests from Client Components). + */ +const createContext = async (req: NextRequest) => { + return createTRPCContext({ + headers: req.headers, + }); +}; + +const handler = (req: NextRequest) => + fetchRequestHandler({ + endpoint: "/api/trpc", + req, + router: appRouter, + createContext: () => createContext(req), + onError: + env.NODE_ENV === "development" + ? ({ path, error }) => { + console.error( + `❌ tRPC failed on ${path ?? ""}: ${error.message}`, + ); + } + : undefined, + }); + +export { handler as GET, handler as POST }; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx new file mode 100644 index 000000000000..9388e06e02bd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx @@ -0,0 +1,27 @@ +"use client"; + +import * as Sentry from "@sentry/nextjs"; +import NextError from "next/error"; +import { useEffect } from "react"; + +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string }; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + {/* `NextError` is the default Next.js error page component. Its type + definition requires a `statusCode` prop. However, since the App Router + does not expose status codes for errors, we simply pass 0 to render a + generic error message. */} + + + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx new file mode 100644 index 000000000000..c2215fff12e4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx @@ -0,0 +1,24 @@ +import "~/styles/globals.css"; + +import { GeistSans } from "geist/font/sans"; +import { type Metadata } from "next"; + +import { TRPCReactProvider } from "~/trpc/react"; + +export const metadata: Metadata = { + title: "Create T3 App", + description: "Generated by create-t3-app", + icons: [{ rel: "icon", url: "/favicon.ico" }], +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + + {children} + + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx new file mode 100644 index 000000000000..d7121d83d699 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx @@ -0,0 +1,53 @@ +import Link from "next/link"; + +import { LatestPost } from "~/app/_components/post"; +import { api, HydrateClient } from "~/trpc/server"; + +export default async function Home() { + const hello = await api.post.hello({ text: "from tRPC" }); + + void api.post.getLatest.prefetch(); + + return ( + +
+
+

+ Create T3 App +

+
+ +

First Steps →

+
+ Just the basics - Everything you need to know to set up your + database and authentication. +
+ + +

Documentation →

+
+ Learn more about Create T3 App, the libraries it uses, and how + to deploy it. +
+ +
+
+

+ {hello ? hello.greeting : "Loading tRPC query..."} +

+
+ + +
+
+
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js new file mode 100644 index 000000000000..5c2f937caccc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js @@ -0,0 +1,40 @@ +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: { + NODE_ENV: z.enum(["development", "test", "production"]), + }, + + /** + * 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: { + 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/dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts new file mode 100644 index 000000000000..ecb65282ba23 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts @@ -0,0 +1,13 @@ +import * as Sentry from "@sentry/nextjs"; + +export async function register() { + if (process.env.NEXT_RUNTIME === "nodejs") { + await import("../sentry.server.config"); + } + + if (process.env.NEXT_RUNTIME === "edge") { + await import("../sentry.edge.config"); + } +} + +export const onRequestError = Sentry.captureRequestError; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts new file mode 100644 index 000000000000..b341fc4d6492 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts @@ -0,0 +1,23 @@ +import { postRouter } from "~/server/api/routers/post"; +import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc"; + +/** + * This is the primary router for your server. + * + * All routers added in /api/routers should be manually added here. + */ +export const appRouter = createTRPCRouter({ + post: postRouter, +}); + +// export type definition of API +export type AppRouter = typeof appRouter; + +/** + * Create a server-side caller for the tRPC API. + * @example + * const trpc = createCaller(createContext); + * const res = await trpc.post.all(); + * ^? Post[] + */ +export const createCaller = createCallerFactory(appRouter); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/routers/post.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/routers/post.ts new file mode 100644 index 000000000000..042ebe69e9bb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/routers/post.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; + +import { createTRPCRouter, publicProcedure } from '~/server/api/trpc'; + +// Mocked DB +interface Post { + id: number; + name: string; +} +const posts: Post[] = [ + { + id: 1, + name: 'Hello World', + }, +]; + +export const postRouter = createTRPCRouter({ + hello: publicProcedure.input(z.object({ text: z.string() })).query(({ input }) => { + return { + greeting: `Hello ${input.text}`, + }; + }), + + create: publicProcedure.input(z.object({ name: z.string().min(1) })).mutation(async ({ input }) => { + const post: Post = { + id: posts.length + 1, + name: input.name, + }; + posts.push(post); + return post; + }), + + getLatest: publicProcedure.query(() => { + return posts.at(-1) ?? null; + }), + throwError: publicProcedure.input(z.object({ name: z.string().min(1) })).mutation(async () => { + throw new Error('Error thrown in trpc router'); + }), +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts new file mode 100644 index 000000000000..12db7ababfe1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts @@ -0,0 +1,112 @@ +/** + * 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 superjson from "superjson"; +import { ZodError } from "zod"; +import * as Sentry from "@sentry/nextjs"; + +/** + * 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. + * + * This helper generates the "internals" for a tRPC context. The API handler and RSC clients each + * wrap this and provides the required context. + * + * @see https://trpc.io/docs/server/context + */ +export const createTRPCContext = async (opts: { headers: Headers }) => { + return { + ...opts, + }; +}; + +/** + * 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, + }, + }; + }, +}); + +/** + * Create a server-side caller. + * + * @see https://trpc.io/docs/server/server-side-calls + */ +export const createCallerFactory = t.createCallerFactory; + +/** + * 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; + +/** + * Middleware for timing procedure execution and adding an artificial delay in development. + * + * You can remove this if you don't like it, but it can help catch unwanted waterfalls by simulating + * network latency that would occur in production but not in local development. + */ +const timingMiddleware = t.middleware(async ({ next, path }) => { + const start = Date.now(); + + if (t._config.isDev) { + // artificial delay in dev + const waitMs = Math.floor(Math.random() * 400) + 100; + await new Promise((resolve) => setTimeout(resolve, waitMs)); + } + + const result = await next(); + + const end = Date.now(); + console.log(`[TRPC] ${path} took ${end - start}ms to execute`); + + return result; +}); + +const sentryMiddleware = t.middleware( + Sentry.trpcMiddleware({ + attachRpcInput: true, + }), +); + +/** + * 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 + .use(sentryMiddleware) + .use(timingMiddleware); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/styles/globals.css b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/styles/globals.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/styles/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts new file mode 100644 index 000000000000..bda64397cad9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts @@ -0,0 +1,25 @@ +import { + defaultShouldDehydrateQuery, + QueryClient, +} from "@tanstack/react-query"; +import SuperJSON from "superjson"; + +export const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + // With SSR, we usually want to set some default staleTime + // above 0 to avoid refetching immediately on the client + staleTime: 30 * 1000, + }, + dehydrate: { + serializeData: SuperJSON.serialize, + shouldDehydrateQuery: (query) => + defaultShouldDehydrateQuery(query) || + query.state.status === "pending", + }, + hydrate: { + deserializeData: SuperJSON.deserialize, + }, + }, + }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx new file mode 100644 index 000000000000..89c0a547e482 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { QueryClientProvider, type QueryClient } from "@tanstack/react-query"; +import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client"; +import { createTRPCReact } from "@trpc/react-query"; +import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; +import { useState } from "react"; +import SuperJSON from "superjson"; + +import { type AppRouter } from "~/server/api/root"; +import { createQueryClient } from "./query-client"; + +let clientQueryClientSingleton: QueryClient | undefined = undefined; +const getQueryClient = () => { + if (typeof window === "undefined") { + // Server: always make a new query client + return createQueryClient(); + } + // Browser: use singleton pattern to keep the same query client + return (clientQueryClientSingleton ??= createQueryClient()); +}; + +export const api = createTRPCReact(); + +/** + * 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; + +export function TRPCReactProvider(props: { children: React.ReactNode }) { + const queryClient = getQueryClient(); + + const [trpcClient] = useState(() => + api.createClient({ + links: [ + loggerLink({ + enabled: (op) => + process.env.NODE_ENV === "development" || + (op.direction === "down" && op.result instanceof Error), + }), + unstable_httpBatchStreamLink({ + transformer: SuperJSON, + url: getBaseUrl() + "/api/trpc", + headers: () => { + const headers = new Headers(); + headers.set("x-trpc-source", "nextjs-react"); + return headers; + }, + }), + ], + }), + ); + + return ( + + + {props.children} + + + ); +} + +function getBaseUrl() { + if (typeof window !== "undefined") return window.location.origin; + if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; + return `http://localhost:${process.env.PORT ?? 3000}`; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts new file mode 100644 index 000000000000..a43b165ad96c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts @@ -0,0 +1,30 @@ +import "server-only"; + +import { createHydrationHelpers } from "@trpc/react-query/rsc"; +import { headers } from "next/headers"; +import { cache } from "react"; + +import { createCaller, type AppRouter } from "~/server/api/root"; +import { createTRPCContext } from "~/server/api/trpc"; +import { createQueryClient } from "./query-client"; + +/** + * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when + * handling a tRPC call from a React Server Component. + */ +const createContext = cache(() => { + const heads = new Headers(headers()); + heads.set("x-trpc-source", "rsc"); + + return createTRPCContext({ + headers: heads, + }); +}); + +const getQueryClient = cache(createQueryClient); +const caller = createCaller(createContext); + +export const { trpc: api, HydrateClient } = createHydrationHelpers( + caller, + getQueryClient, +); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-t3/start-event-proxy.mjs new file mode 100644 index 000000000000..afc5d2e465e7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nextjs-t3', +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts new file mode 100644 index 000000000000..5fd44e8f234a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts @@ -0,0 +1,14 @@ +import { type Config } from "tailwindcss"; +import { fontFamily } from "tailwindcss/defaultTheme"; + +export default { + content: ["./src/**/*.tsx"], + theme: { + extend: { + fontFamily: { + sans: ["var(--font-geist-sans)", ...fontFamily.sans], + }, + }, + }, + plugins: [], +} satisfies Config; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts new file mode 100644 index 000000000000..0245b641db5c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('should capture error with trpc context', async ({ page }) => { + const errorEventPromise = waitForError('nextjs-t3', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Error thrown in trpc router'; + }); + + await page.goto('/'); + await page.click('#error-button'); + + const trpcError = await errorEventPromise; + + expect(trpcError).toBeDefined(); + expect(trpcError.contexts.trpc).toBeDefined(); + expect(trpcError.contexts.trpc.procedure_type).toEqual('mutation'); + expect(trpcError.contexts.trpc.input).toEqual({ name: 'I love dogs' }); +}); + +test('should create transaction with trpc input for error', async ({ page }) => { + const trpcTransactionPromise = waitForTransaction('nextjs-t3', async transactionEvent => { + return transactionEvent?.transaction === 'POST /api/trpc/[trpc]'; + }); + + await page.goto('/'); + await page.click('#error-button'); + + const trpcTransaction = await trpcTransactionPromise; + + expect(trpcTransaction).toBeDefined(); + expect(trpcTransaction.contexts.trpc).toBeDefined(); + expect(trpcTransaction.contexts.trpc.procedure_type).toEqual('mutation'); + expect(trpcTransaction.contexts.trpc.input).toEqual({ name: 'I love dogs' }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts new file mode 100644 index 000000000000..47d6a52f8a19 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('should create transaction with trpc input for mutation', async ({ page }) => { + const trpcTransactionPromise = waitForTransaction('nextjs-t3', async transactionEvent => { + return transactionEvent?.transaction === 'POST /api/trpc/[trpc]'; + }); + + await page.goto('/'); + await page.locator('#createInput').fill('I love dogs'); + await page.click('#createButton'); + + const trpcTransaction = await trpcTransactionPromise; + + expect(trpcTransaction).toBeDefined(); + expect(trpcTransaction.contexts.trpc).toBeDefined(); + expect(trpcTransaction.contexts.trpc.procedure_type).toEqual('mutation'); + expect(trpcTransaction.contexts.trpc.input).toEqual({ name: 'I love dogs' }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-t3/tsconfig.json new file mode 100644 index 000000000000..905062ded60c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/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"] +} From b5f59befc057a85f3931fa3ca2b9becd97af60f2 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 14:11:34 +0200 Subject: [PATCH 3/8] refactor: make trpc middleware async --- packages/core/src/trpc.ts | 68 ++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index 48b5c9b0d09d..d410f729b0d1 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -20,11 +20,24 @@ export interface SentryTrpcMiddlewareArguments { const trpcCaptureContext = { mechanism: { handled: false, data: { function: 'trpcMiddleware' } } }; +function captureIfError(nextResult: unknown): void { + // TODO: Set span status based on what TRPCError was encountered + if ( + typeof nextResult === 'object' && + nextResult !== null && + 'ok' in nextResult && + !nextResult.ok && + 'error' in nextResult + ) { + captureException(nextResult.error, trpcCaptureContext); + } +} + /** * Sentry tRPC middleware that captures errors and creates spans for tRPC procedures. */ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { - return function (opts: SentryTrpcMiddlewareArguments): T { + return async function (opts: SentryTrpcMiddlewareArguments): Promise { const { path, type, next, rawInput, getRawInput } = opts; const client = getClient(); @@ -37,34 +50,19 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { if (options.attachRpcInput !== undefined ? options.attachRpcInput : clientOptions && clientOptions.sendDefaultPii) { if (rawInput !== undefined) { trpcContext.input = normalize(rawInput); - setContext('trpc', trpcContext); } if (getRawInput !== undefined && typeof getRawInput === 'function') { - getRawInput().then( - rawRes => { - trpcContext.input = normalize(rawRes); - setContext('trpc', trpcContext); - }, - _e => { - // noop - }, - ); - } - } + try { + const rawRes = await getRawInput(); - function captureIfError(nextResult: unknown): void { - // TODO: Set span status based on what TRPCError was encountered - if ( - typeof nextResult === 'object' && - nextResult !== null && - 'ok' in nextResult && - !nextResult.ok && - 'error' in nextResult - ) { - captureException(nextResult.error, trpcCaptureContext); + trpcContext.input = normalize(rawRes); + } catch (err) { + // noop + } } } + setContext('trpc', trpcContext); return startSpanManual( { @@ -75,7 +73,7 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.rpc.trpc', }, }, - span => { + async span => { let maybePromiseResult; try { maybePromiseResult = next(); @@ -86,18 +84,16 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { } if (isThenable(maybePromiseResult)) { - return maybePromiseResult.then( - nextResult => { - captureIfError(nextResult); - span.end(); - return nextResult; - }, - e => { - captureException(e, trpcCaptureContext); - span.end(); - throw e; - }, - ) as T; + try { + const nextResult = await maybePromiseResult; + captureIfError(nextResult); + span.end(); + return nextResult; + } catch (e) { + captureException(e, trpcCaptureContext); + span.end(); + throw e; + } } else { captureIfError(maybePromiseResult); span.end(); From 2ef4215b67c3d72523916f1f5d6d1489c0e0b1bc Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 14:36:21 +0200 Subject: [PATCH 4/8] lint e2e test --- .../test-applications/nextjs-t3/README.md | 29 ----------------- .../src/app/api/trpc/[trpc]/route.ts | 18 +++++------ .../nextjs-t3/src/app/global-error.tsx | 8 ++--- .../nextjs-t3/src/app/layout.tsx | 18 +++++------ .../nextjs-t3/src/app/page.tsx | 18 +++++------ .../test-applications/nextjs-t3/src/env.js | 6 ++-- .../nextjs-t3/src/instrumentation.ts | 10 +++--- .../nextjs-t3/src/server/api/root.ts | 4 +-- .../nextjs-t3/src/server/api/trpc.ts | 17 +++++----- .../nextjs-t3/src/trpc/query-client.ts | 11 ++----- .../nextjs-t3/src/trpc/react.tsx | 31 +++++++++---------- .../nextjs-t3/src/trpc/server.ts | 21 ++++++------- .../nextjs-t3/tailwind.config.ts | 12 +++---- 13 files changed, 77 insertions(+), 126 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/nextjs-t3/README.md diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/README.md b/dev-packages/e2e-tests/test-applications/nextjs-t3/README.md deleted file mode 100644 index 67943c7fb44c..000000000000 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# 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) -- [Drizzle](https://orm.drizzle.team) -- [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/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts index f41a82e0a2b8..5756411c583e 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/api/trpc/[trpc]/route.ts @@ -1,9 +1,9 @@ -import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; -import { type NextRequest } from "next/server"; +import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; +import { type NextRequest } from 'next/server'; -import { env } from "~/env"; -import { appRouter } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/trpc"; +import { env } from '~/env'; +import { appRouter } from '~/server/api/root'; +import { createTRPCContext } from '~/server/api/trpc'; /** * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when @@ -17,16 +17,14 @@ const createContext = async (req: NextRequest) => { const handler = (req: NextRequest) => fetchRequestHandler({ - endpoint: "/api/trpc", + endpoint: '/api/trpc', req, router: appRouter, createContext: () => createContext(req), onError: - env.NODE_ENV === "development" + env.NODE_ENV === 'development' ? ({ path, error }) => { - console.error( - `❌ tRPC failed on ${path ?? ""}: ${error.message}`, - ); + console.error(`❌ tRPC failed on ${path ?? ''}: ${error.message}`); } : undefined, }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx index 9388e06e02bd..912ad3606a61 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/global-error.tsx @@ -1,8 +1,8 @@ -"use client"; +'use client'; -import * as Sentry from "@sentry/nextjs"; -import NextError from "next/error"; -import { useEffect } from "react"; +import * as Sentry from '@sentry/nextjs'; +import NextError from 'next/error'; +import { useEffect } from 'react'; export default function GlobalError({ error, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx index c2215fff12e4..e703260be1a3 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/layout.tsx @@ -1,19 +1,17 @@ -import "~/styles/globals.css"; +import '~/styles/globals.css'; -import { GeistSans } from "geist/font/sans"; -import { type Metadata } from "next"; +import { GeistSans } from 'geist/font/sans'; +import { type Metadata } from 'next'; -import { TRPCReactProvider } from "~/trpc/react"; +import { TRPCReactProvider } from '~/trpc/react'; export const metadata: Metadata = { - title: "Create T3 App", - description: "Generated by create-t3-app", - icons: [{ rel: "icon", url: "/favicon.ico" }], + title: 'Create T3 App', + description: 'Generated by create-t3-app', + icons: [{ rel: 'icon', url: '/favicon.ico' }], }; -export default function RootLayout({ - children, -}: Readonly<{ children: React.ReactNode }>) { +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx index d7121d83d699..f8e261c98c34 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/app/page.tsx @@ -1,10 +1,10 @@ -import Link from "next/link"; +import Link from 'next/link'; -import { LatestPost } from "~/app/_components/post"; -import { api, HydrateClient } from "~/trpc/server"; +import { LatestPost } from '~/app/_components/post'; +import { HydrateClient, api } from '~/trpc/server'; export default async function Home() { - const hello = await api.post.hello({ text: "from tRPC" }); + const hello = await api.post.hello({ text: 'from tRPC' }); void api.post.getLatest.prefetch(); @@ -23,8 +23,7 @@ export default async function Home() { >

First Steps →

- Just the basics - Everything you need to know to set up your - database and authentication. + Just the basics - Everything you need to know to set up your database and authentication.

Documentation →

- Learn more about Create T3 App, the libraries it uses, and how - to deploy it. + Learn more about Create T3 App, the libraries it uses, and how to deploy it.
-

- {hello ? hello.greeting : "Loading tRPC query..."} -

+

{hello ? hello.greeting : 'Loading tRPC query...'}

diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js index 5c2f937caccc..8c66c421c7ec 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/env.js @@ -1,5 +1,5 @@ -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({ /** @@ -7,7 +7,7 @@ export const env = createEnv({ * isn't built with invalid env vars. */ server: { - NODE_ENV: z.enum(["development", "test", "production"]), + NODE_ENV: z.enum(['development', 'test', 'production']), }, /** diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts index ecb65282ba23..8aff09f087d0 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/instrumentation.ts @@ -1,12 +1,12 @@ -import * as Sentry from "@sentry/nextjs"; +import * as Sentry from '@sentry/nextjs'; export async function register() { - if (process.env.NEXT_RUNTIME === "nodejs") { - await import("../sentry.server.config"); + if (process.env.NEXT_RUNTIME === 'nodejs') { + await import('../sentry.server.config'); } - if (process.env.NEXT_RUNTIME === "edge") { - await import("../sentry.edge.config"); + if (process.env.NEXT_RUNTIME === 'edge') { + await import('../sentry.edge.config'); } } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts index b341fc4d6492..4a6e7dc0f6bc 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/root.ts @@ -1,5 +1,5 @@ -import { postRouter } from "~/server/api/routers/post"; -import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc"; +import { postRouter } from '~/server/api/routers/post'; +import { createCallerFactory, createTRPCRouter } from '~/server/api/trpc'; /** * This is the primary router for your server. diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts index 12db7ababfe1..e803dd17b43d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts @@ -1,3 +1,4 @@ +import * as Sentry from '@sentry/nextjs'; /** * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS: * 1. You want to modify request context (see Part 1). @@ -6,10 +7,9 @@ * 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 superjson from "superjson"; -import { ZodError } from "zod"; -import * as Sentry from "@sentry/nextjs"; +import { initTRPC } from '@trpc/server'; +import superjson from 'superjson'; +import { ZodError } from 'zod'; /** * 1. CONTEXT @@ -43,8 +43,7 @@ const t = initTRPC.context().create({ ...shape, data: { ...shape.data, - zodError: - error.cause instanceof ZodError ? error.cause.flatten() : null, + zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, @@ -83,7 +82,7 @@ const timingMiddleware = t.middleware(async ({ next, path }) => { if (t._config.isDev) { // artificial delay in dev const waitMs = Math.floor(Math.random() * 400) + 100; - await new Promise((resolve) => setTimeout(resolve, waitMs)); + await new Promise(resolve => setTimeout(resolve, waitMs)); } const result = await next(); @@ -107,6 +106,4 @@ const sentryMiddleware = t.middleware( * 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 - .use(sentryMiddleware) - .use(timingMiddleware); +export const publicProcedure = t.procedure.use(sentryMiddleware).use(timingMiddleware); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts index bda64397cad9..22319e7c0a5a 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/query-client.ts @@ -1,8 +1,5 @@ -import { - defaultShouldDehydrateQuery, - QueryClient, -} from "@tanstack/react-query"; -import SuperJSON from "superjson"; +import { QueryClient, defaultShouldDehydrateQuery } from '@tanstack/react-query'; +import SuperJSON from 'superjson'; export const createQueryClient = () => new QueryClient({ @@ -14,9 +11,7 @@ export const createQueryClient = () => }, dehydrate: { serializeData: SuperJSON.serialize, - shouldDehydrateQuery: (query) => - defaultShouldDehydrateQuery(query) || - query.state.status === "pending", + shouldDehydrateQuery: query => defaultShouldDehydrateQuery(query) || query.state.status === 'pending', }, hydrate: { deserializeData: SuperJSON.deserialize, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx index 89c0a547e482..12459d66eee6 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/react.tsx @@ -1,18 +1,18 @@ -"use client"; +'use client'; -import { QueryClientProvider, type QueryClient } from "@tanstack/react-query"; -import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client"; -import { createTRPCReact } from "@trpc/react-query"; -import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; -import { useState } from "react"; -import SuperJSON from "superjson"; +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { loggerLink, unstable_httpBatchStreamLink } from '@trpc/client'; +import { createTRPCReact } from '@trpc/react-query'; +import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server'; +import { useState } from 'react'; +import SuperJSON from 'superjson'; -import { type AppRouter } from "~/server/api/root"; -import { createQueryClient } from "./query-client"; +import { type AppRouter } from '~/server/api/root'; +import { createQueryClient } from './query-client'; let clientQueryClientSingleton: QueryClient | undefined = undefined; const getQueryClient = () => { - if (typeof window === "undefined") { + if (typeof window === 'undefined') { // Server: always make a new query client return createQueryClient(); } @@ -43,16 +43,15 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) { api.createClient({ links: [ loggerLink({ - enabled: (op) => - process.env.NODE_ENV === "development" || - (op.direction === "down" && op.result instanceof Error), + enabled: op => + process.env.NODE_ENV === 'development' || (op.direction === 'down' && op.result instanceof Error), }), unstable_httpBatchStreamLink({ transformer: SuperJSON, - url: getBaseUrl() + "/api/trpc", + url: getBaseUrl() + '/api/trpc', headers: () => { const headers = new Headers(); - headers.set("x-trpc-source", "nextjs-react"); + headers.set('x-trpc-source', 'nextjs-react'); return headers; }, }), @@ -70,7 +69,7 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) { } function getBaseUrl() { - if (typeof window !== "undefined") return window.location.origin; + if (typeof window !== 'undefined') return window.location.origin; if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; return `http://localhost:${process.env.PORT ?? 3000}`; } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts index a43b165ad96c..b6cb13a70781 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/trpc/server.ts @@ -1,12 +1,12 @@ -import "server-only"; +import 'server-only'; -import { createHydrationHelpers } from "@trpc/react-query/rsc"; -import { headers } from "next/headers"; -import { cache } from "react"; +import { createHydrationHelpers } from '@trpc/react-query/rsc'; +import { headers } from 'next/headers'; +import { cache } from 'react'; -import { createCaller, type AppRouter } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/trpc"; -import { createQueryClient } from "./query-client"; +import { type AppRouter, createCaller } from '~/server/api/root'; +import { createTRPCContext } from '~/server/api/trpc'; +import { createQueryClient } from './query-client'; /** * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when @@ -14,7 +14,7 @@ import { createQueryClient } from "./query-client"; */ const createContext = cache(() => { const heads = new Headers(headers()); - heads.set("x-trpc-source", "rsc"); + heads.set('x-trpc-source', 'rsc'); return createTRPCContext({ headers: heads, @@ -24,7 +24,4 @@ const createContext = cache(() => { const getQueryClient = cache(createQueryClient); const caller = createCaller(createContext); -export const { trpc: api, HydrateClient } = createHydrationHelpers( - caller, - getQueryClient, -); +export const { trpc: api, HydrateClient } = createHydrationHelpers(caller, getQueryClient); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts index 5fd44e8f234a..bdd1ea1f6102 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tailwind.config.ts @@ -1,14 +1,14 @@ -import { type Config } from "tailwindcss"; -import { fontFamily } from "tailwindcss/defaultTheme"; +import { type Config } from 'tailwindcss'; +import { fontFamily } from 'tailwindcss/defaultTheme'; -export default { - content: ["./src/**/*.tsx"], +export default ({ + content: ['./src/**/*.tsx'], theme: { extend: { fontFamily: { - sans: ["var(--font-geist-sans)", ...fontFamily.sans], + sans: ['var(--font-geist-sans)', ...fontFamily.sans], }, }, }, plugins: [], -} satisfies Config; +} satisfies Config); From 8dd468cc04749aa367acc37b1a94bf359541a4ff Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 16:55:35 +0200 Subject: [PATCH 5/8] refactor async code --- packages/core/src/trpc.ts | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index d410f729b0d1..a3101d793a31 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -1,4 +1,4 @@ -import { isThenable, normalize } from '@sentry/utils'; +import { normalize } from '@sentry/utils'; import { getClient } from './currentScopes'; import { captureException, setContext } from './exports'; @@ -74,31 +74,16 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { }, }, async span => { - let maybePromiseResult; try { - maybePromiseResult = next(); + const nextResult = await next(); + captureIfError(nextResult); + span.end(); + return nextResult; } catch (e) { captureException(e, trpcCaptureContext); span.end(); throw e; } - - if (isThenable(maybePromiseResult)) { - try { - const nextResult = await maybePromiseResult; - captureIfError(nextResult); - span.end(); - return nextResult; - } catch (e) { - captureException(e, trpcCaptureContext); - span.end(); - throw e; - } - } else { - captureIfError(maybePromiseResult); - span.end(); - return maybePromiseResult; - } }, ); }; From 7baa30ff8082f10d92bd34f9dcdd878da01de1e9 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 17:32:11 +0200 Subject: [PATCH 6/8] make ts happy --- .../e2e-tests/test-applications/node-express/src/app.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts index de240b761df0..4fa07d82ff6d 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/src/app.ts @@ -105,7 +105,9 @@ Sentry.addEventProcessor(event => { export const t = initTRPC.context().create(); -const procedure = t.procedure.use(Sentry.trpcMiddleware({ attachRpcInput: true })); +const sentryMiddleware = Sentry.trpcMiddleware({ attachRpcInput: true }); + +const procedure = t.procedure.use(async opts => sentryMiddleware(opts)); export const appRouter = t.router({ getSomething: procedure.input(z.string()).query(opts => { From d6cf71cf2c2a3cd7d91d68e6828a54e607cd4ee2 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 17:45:32 +0200 Subject: [PATCH 7/8] remove weird timing middleware + ts fix --- .../nextjs-t3/src/server/api/trpc.ts | 38 ++----------------- 1 file changed, 3 insertions(+), 35 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts index e803dd17b43d..0bc74b51243e 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/src/server/api/trpc.ts @@ -70,40 +70,8 @@ export const createCallerFactory = t.createCallerFactory; */ export const createTRPCRouter = t.router; -/** - * Middleware for timing procedure execution and adding an artificial delay in development. - * - * You can remove this if you don't like it, but it can help catch unwanted waterfalls by simulating - * network latency that would occur in production but not in local development. - */ -const timingMiddleware = t.middleware(async ({ next, path }) => { - const start = Date.now(); - - if (t._config.isDev) { - // artificial delay in dev - const waitMs = Math.floor(Math.random() * 400) + 100; - await new Promise(resolve => setTimeout(resolve, waitMs)); - } - - const result = await next(); - - const end = Date.now(); - console.log(`[TRPC] ${path} took ${end - start}ms to execute`); - - return result; +const sentryMiddleware = Sentry.trpcMiddleware({ + attachRpcInput: true, }); -const sentryMiddleware = t.middleware( - Sentry.trpcMiddleware({ - attachRpcInput: true, - }), -); - -/** - * 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.use(sentryMiddleware).use(timingMiddleware); +export const publicProcedure = t.procedure.use(async opts => sentryMiddleware(opts)); From 579a65dc976bbfd2b9bc97fce555facd6adb364f Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 30 Sep 2024 18:00:45 +0200 Subject: [PATCH 8/8] test: add missing dependencies --- dev-packages/e2e-tests/test-applications/nextjs-t3/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json index 4a2378a5483b..d5c3a9d20f0d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/package.json @@ -29,6 +29,8 @@ "zod": "^3.23.3" }, "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", "@types/eslint": "^8.56.10", "@types/node": "^20.14.10", "@types/react": "^18.3.3",