feat(generators): capture core-trpc as verbatim template files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 14:06:14 +02:00
parent 3d653666c2
commit 0a3a2c3845
12 changed files with 231 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
# AGENTS.md — core-trpc
**Tag:** core-composition
Frontend tRPC platform providing the client and framework-specific providers (Next.js, TanStack). Bridges typed backend (`@repo/core-api`) to frontend applications. Tagged `core-composition` (not `core`) because it transitively reaches features through `core-api`'s `AppRouter` type.
## Responsibilities
- **Create tRPC React client** — `createTRPCReact<AppRouter>()` for use in React apps
- **Query client factory** — TanStack React Query setup per app
- **Framework-specific providers** — Next.js App Router provider + TanStack Start provider
- **No business logic** — purely framework integration
## Must NOT import
- Any feature package (`@repo/auth`, `@repo/blog`, etc.)
- Any app package
- `@repo/core-api`, `@repo/core-cms`, `@repo/core-ui`
## Public exports
From `package.json`:
- `.` — client + query client factory
- `./next` — Next.js App Router provider
- `./tanstack` — TanStack Start provider
Example usage:
```typescript
// In Next.js app
import { TrpcProvider } from "@repo/core-trpc/next";
// In TanStack Start app
import { TrpcProvider } from "@repo/core-trpc/tanstack";
// In client components
import { useTRPC } from "@repo/core-trpc";
const trpc = useTRPC();
```
## Test conventions
- No unit tests (provider layer)
- Verify at app boot: `pnpm dev --filter @repo/web-next` or `pnpm dev --filter @repo/web-tanstack` succeeds
- Verify client type safety: `pnpm typecheck` confirms `AppRouter` is imported and typed correctly
## Structure
```
src/
client.ts # createTRPCReact<AppRouter>()
query-client.ts # makeQueryClient()
providers/
next-provider.tsx # 'use client' provider for Next.js
tanstack-provider.tsx # Provider for TanStack Start
index.ts # re-exports client + factories
```

View File

@@ -0,0 +1,3 @@
import baseConfig from "@repo/core-eslint/base";
export default baseConfig;

View File

@@ -0,0 +1,37 @@
{
"name": "@repo/core-trpc",
"private": true,
"version": "0.0.0",
"type": "module",
"exports": {
".": "./src/index.ts",
"./next": "./src/providers/next-provider.tsx",
"./tanstack": "./src/providers/tanstack-provider.tsx"
},
"scripts": {
"build": "tsc --noEmit",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests"
},
"dependencies": {
"@repo/core-api": "workspace:*",
"@trpc/client": "^11.0.0",
"@trpc/react-query": "^11.0.0",
"@trpc/server": "^11.0.0",
"@trpc/tanstack-react-query": "^11.1.0",
"@tanstack/react-query": "^5.66.0",
"react": "^19.0.0",
"superjson": "^2.2.1"
},
"devDependencies": {
"@repo/core-eslint": "workspace:*",
"@repo/core-testing": "workspace:*",
"@repo/core-typescript": "workspace:*",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.0",
"@types/react": "^19.0.0",
"jsdom": "^25.0.0",
"vitest": "^3.0.0"
}
}

View File

@@ -0,0 +1,12 @@
import { describe, it, expect } from "vitest";
import { useTRPC, TRPCProvider } from "./client";
describe("core-trpc client exports", () => {
it("exports useTRPC hook", () => {
expect(useTRPC).toBeTypeOf("function");
});
it("exports TRPCProvider component", () => {
expect(TRPCProvider).toBeTypeOf("function");
});
});

View File

@@ -0,0 +1,6 @@
"use client";
import { createTRPCContext } from "@trpc/tanstack-react-query";
import type { AppRouter } from "@repo/core-api";
export const { TRPCProvider, useTRPC } = createTRPCContext<AppRouter>();

View File

@@ -0,0 +1,3 @@
export { useTRPC, TRPCProvider } from "./client";
export { getQueryClient } from "./query-client";
export type { AppRouter } from "@repo/core-api";

View File

@@ -0,0 +1,32 @@
"use client";
import { useState } from "react";
import { QueryClientProvider } from "@tanstack/react-query";
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import superjson from "superjson";
import type { AppRouter } from "@repo/core-api";
import { TRPCProvider } from "../client";
import { getQueryClient } from "../query-client";
export function NextTrpcProvider({
children,
trpcUrl = "/api/trpc",
}: {
children: React.ReactNode;
trpcUrl?: string;
}) {
const [queryClient] = useState(() => getQueryClient());
const [trpcClient] = useState(() =>
createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: trpcUrl, transformer: superjson })],
}),
);
return (
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</TRPCProvider>
);
}

View File

@@ -0,0 +1,32 @@
"use client";
import { useState } from "react";
import { QueryClientProvider } from "@tanstack/react-query";
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import superjson from "superjson";
import type { AppRouter } from "@repo/core-api";
import { TRPCProvider } from "../client";
import { getQueryClient } from "../query-client";
export function TanstackTrpcProvider({
children,
trpcUrl,
}: {
children: React.ReactNode;
trpcUrl: string;
}) {
const [queryClient] = useState(() => getQueryClient());
const [trpcClient] = useState(() =>
createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: trpcUrl, transformer: superjson })],
}),
);
return (
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</TRPCProvider>
);
}

View File

@@ -0,0 +1,22 @@
import { QueryClient } from "@tanstack/react-query";
let clientQueryClient: QueryClient | undefined;
const defaultOptions = {
queries: {
staleTime: 30 * 1000,
refetchOnWindowFocus: false,
},
};
export function getQueryClient(): QueryClient {
if (typeof window === "undefined") {
// Server: always create a new instance per request
return new QueryClient({ defaultOptions });
}
// Browser: singleton
if (!clientQueryClient) {
clientQueryClient = new QueryClient({ defaultOptions });
}
return clientQueryClient;
}

View File

@@ -0,0 +1,17 @@
{
"extends": "@repo/core-typescript/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"lib": ["ES2022", "DOM"],
"jsx": "preserve",
"declaration": false,
"declarationMap": false,
"types": ["vitest/globals", "@testing-library/jest-dom"],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,4 @@
{
"extends": ["//"],
"tags": ["core-composition"]
}

View File

@@ -0,0 +1,7 @@
import path from "node:path";
import { mergeConfig } from "vitest/config";
import { jsdomVitestConfig } from "@repo/core-typescript/vitest.base.jsdom";
export default mergeConfig(jsdomVitestConfig, {
resolve: { alias: { "@": path.resolve(__dirname, "./src") } },
});