From trpc-knowledge-patch
Provides tRPC v11 knowledge including SSE subscriptions with tracked() reconnection, streaming queries/mutations, TanStack React Query options API, Next.js server actions, lazy routers, OpenAPI support. Load before writing tRPC v11 code.
npx claudepluginhub nevaberry/nevaberry-plugins --plugin trpc-knowledge-patchThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Reviews prose for communication issues impeding comprehension, outputs minimal fixes in a three-column table per Microsoft Writing Style Guide. Useful for 'review prose' or 'improve prose' requests.
Claude's baseline knowledge covers tRPC through v10. This skill provides features from v11 (December 2024) onwards.
createTRPCProxyClient renamed, TypeScript 5.7.2+ / Node 18+ requirementstracked() reconnection, httpSubscriptionLink, useSubscription status union, streaming queries/mutations, embedded promises with httpBatchStreamLink@trpc/tanstack-react-query package, createTRPCContext, createTRPCOptionsProxy for RSC/singletons, .queryOptions()/.mutationOptions() API, migration codemodexperimental_caller with experimental_nextAppDirCaller, turning procedures into plain async functions, auth guards, progressive enhancementlocalLink, shorthand router definitions, non-JSON content types (FormData/File/Blob), @trpc/openapi (alpha), HTTP/2 standalone adapter| Change | v10 | v11 |
|---|---|---|
| Transformer location | createTRPCClient({ transformer }) | httpBatchLink({ transformer }) |
| React Query version | v4 | v5 required (isPending replaces isLoading) |
| Client constructor | createTRPCProxyClient | createTRPCClient (same API) |
| Subscriptions | Observable + WebSocket | Async generator + SSE |
| Min TypeScript | — | >=5.7.2 |
| Min Node.js | — | 18+ |
import { httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
httpBatchLink({
url: '/api/trpc',
transformer: superjson, // moved here from createTRPCClient
});
const appRouter = router({
onEvent: publicProcedure.subscription(async function* (opts) {
for await (const data of on(ee, 'event', { signal: opts.signal })) {
yield data[0];
}
}),
});
SSE config in initTRPC.create():
const t = initTRPC.create({
sse: {
ping: { enabled: true, intervalMs: 15_000 },
client: { reconnectAfterInactivityMs: 20_000 },
},
});
import { tracked } from '@trpc/server';
t.procedure
.input(z.object({ lastEventId: z.string().nullish() }).optional())
.subscription(async function* (opts) {
if (opts.input?.lastEventId) { /* fetch missed events */ }
for await (const [data] of on(ee, 'add', { signal: opts.signal })) {
yield tracked(data.id, data); // client tracks this id
}
});
import { splitLink, httpBatchLink, httpSubscriptionLink } from '@trpc/client';
const client = createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: '/api/trpc',
eventSourceOptions: async ({ op }) => ({
headers: { authorization: `Bearer ${token}` },
}),
}),
false: httpBatchLink({ url: '/api/trpc' }),
}),
],
});
Returns discriminated union on status: 'idle' | 'connecting' | 'pending' | 'error'.
const sub = trpc.onEvent.useSubscription(undefined, {
onData: (data) => {},
onError: (err) => {},
});
// sub.status, sub.data, sub.error, sub.reset()
const appRouter = router({
stream: publicProcedure.query(async function* () {
for (let i = 0; i < 10; i++) {
yield i;
await new Promise((r) => setTimeout(r, 500));
}
}),
});
// Client: for await (const v of await trpc.stream.query()) { ... }
publicProcedure.query(() => ({
instant: 'ready',
slow: slowAsyncFn(), // streams when resolved
}));
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTRPC } from './trpc';
function MyComponent() {
const trpc = useTRPC();
const greeting = useQuery(trpc.greeting.queryOptions({ name: 'Jerry' }));
const create = useMutation(trpc.createUser.mutationOptions());
// Invalidation
const qc = useQueryClient();
await qc.invalidateQueries(trpc.greeting.queryFilter({ name: 'Jerry' }));
}
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
// RSC: direct router call, no HTTP
const trpc = createTRPCOptionsProxy({ ctx: createTRPCContext, router: appRouter, queryClient: getQueryClient });
// SPA singleton:
const trpc = createTRPCOptionsProxy<AppRouter>({ client: trpcClient, queryClient });
void queryClient.prefetchQuery(trpc.hello.queryOptions({ text: 'world' }));
Migration codemod: npx @trpc/upgrade (select "Migrate Hooks to xxxOptions API").
'use server';
import { protectedAction } from '../server/trpc';
import { z } from 'zod';
export const createPost = protectedAction
.meta({ span: 'create-post' })
.input(z.object({ title: z.string() }))
.mutation(async (opts) => { /* opts.ctx.user, opts.input.title */ });
// createPost is now: (input: { title: string }) => Promise<void>
import { lazy } from '@trpc/server';
const appRouter = router({
greeting: lazy(() => import('./greeting.js')),
user: lazy(() => import('./user.js').then((m) => m.userRouter)),
});
import { splitLink, httpBatchLink, httpLink, isNonJsonSerializable } from '@trpc/client';
createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({ url }),
false: httpBatchLink({ url }),
}),
],
});
pnpm add @trpc/openapi
pnpm exec trpc-openapi ./src/server/router.ts -o api.json --title "My API" --version 1.0.0