The codemod
@trpc12/upgrade rewrites v11 imports, names, links and transformers to v12, keeps your formatting, and lists what is left as file:line:column steps.
On this page
@trpc12/upgrade rewrites a v11 codebase to v12 where there's one v12 form, and prints the rest as steps. It's a dry run unless you pass --write:
npx @trpc12/upgrade src # dry run: what would change, what's left
npx @trpc12/upgrade src --writeIt takes files and directories, reads .js, .jsx, .ts and .tsx files (and their .m and .c forms), and skips node_modules, dist, .git, .next and .output. The bin is trpc12-upgrade, for a global install or a package.json script.
What it rewrites
It keeps your formatting, and rewrites:
- Imports from
@trpc/server,@trpc/server/adapters/fetch,@trpc/clientand@trpc/tanstack-react-queryto their@trpc12/*entry points, with the names that changed:initTRPCtoinitTrpc,TRPCErrorandTRPCClientErrortoTrpcError,fetchRequestHandlertocreateFetchHandler,createTRPCClienttocreateTrpcClient,TRPCClienttoTrpcClient,createTRPCContexttocreateTrpcContext,createTRPCOptionsProxytocreateTrpcQueryUtils.TRPCLinkstays imported from@trpc/client, forfromV11Link. initTRPC.context<Ctx>().meta<Meta>().create()toinitTrpc<{ ctx: Ctx; meta: Meta }>(),t.router(routes)toroutes,mergeRouters(a, b)to{ ...a, ...b }, androuter.createCaller(ctx)tocreateRouterClient(router, { ctx }).httpBatchLink,httpBatchStreamLinkandhttpSubscriptionLinktohttpLink, and theirtransformertoserializer.createTRPCContext's result, destructured as a tuple, withTRPCProvider,useTRPCanduseTRPCClientrenamed wherever the project uses them.experimental_*imports of exports that became stable, to their new names (Stability).
What it leaves to you
The rest it leaves as is and prints as file:line:column steps, with the v12 way:
would upgrade src/server/trpc.ts
src/server/trpc.ts:4:3 transformer: drop it here; pass the same createSyncSerializer(transformer) to the handler and every link
src/server/index.ts:1:1 observable(): subscriptions return an async generator or an Effect Stream; a v11 link keeps it under fromV11Link
1 file(s) to upgrade, 2 step(s) by hand
The steps cover what needs a decision: an errorFormatter (declared errors, or createV11Handler for old clients), observable() subscriptions, the standalone, Express and ws adapters (Adapters), @trpc/react-query and @trpc/next, which have no v12 version, createWSClient, createCallerFactory, t.router kept as a function, a subscription's input.lastEventId (the resolver's lastEventId in v12), your own TRPCLinks (v11 links), TanStack helpers such as queryOptions(input) whose input moved into their options (queryOptions({ input })), and root options such as isDev and sse that moved to the handler. A file it can't parse is reported and left alone.
From code
upgradeSource(source, filename) rewrites one file's text and returns { code, changed, notes }; upgradeFiles(paths, { write }) is the CLI's Effect, over files and directories:
import { writeFileSync } from 'node:fs';
import { upgradeSource } from '@trpc12/upgrade';
const result = upgradeSource(source, 'src/server/trpc.ts');
if (result.changed) writeFileSync('src/server/trpc.ts', result.code);
for (const note of result.notes)
console.log(`${note.line}:${note.column} ${note.message}`);