refactor(core-trpc): break the core-api package cycle

core-trpc imported AppRouter (type-only) from core-api, which depends
on the features, which depend on core-trpc — a cycle that killed every
turbo graph walk (lint/build/CI) and an illegal core -> core-composition
boundary edge. The tRPC context is now router-agnostic (AnyTRPCRouter);
useTRPC takes the router type as a generic and each feature exports a
type-only app-slice (e.g. BlogAppSlice) mirroring its mount key, so UI
hooks keep full procedure typing without touching the composition layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 17:38:12 +02:00
parent 5047953c9a
commit c551b33bfb
14 changed files with 70 additions and 24 deletions

View File

@@ -43,3 +43,13 @@ export const blogRouter = router({
});
export type BlogRouter = typeof blogRouter;
/**
* This feature's slice as it is mounted in the app router (the `blog` key
* in @repo/core-api). UI hooks pass it to `useTRPC<BlogAppSlice>()` so
* they stay fully typed without core-trpc (or this feature's client code)
* depending on the composition layer. Type-only: erased at compile time.
*/
export type BlogAppSlice = ReturnType<
typeof router<{ blog: typeof blogRouter }>
>;

View File

@@ -2,10 +2,11 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { useTRPC } from "@repo/core-trpc";
import type { BlogAppSlice } from "../../integrations/api/router";
import type { Article } from "../../entities/models/article";
export function useArticleBySlug(slug: string) {
const trpc = useTRPC();
const trpc = useTRPC<BlogAppSlice>();
return useSuspenseQuery(trpc.blog.articleBySlug.queryOptions({ slug })) as {
data: Article | null;
};

View File

@@ -2,13 +2,14 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { useTRPC } from "@repo/core-trpc";
import type { BlogAppSlice } from "../../integrations/api/router";
import type { Article } from "../../entities/models/article";
export function useArticleList(options?: {
status?: "draft" | "published";
limit?: number;
}) {
const trpc = useTRPC();
const trpc = useTRPC<BlogAppSlice>();
return useSuspenseQuery(
trpc.blog.listArticles.queryOptions({
status: options?.status ?? "published",