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

@@ -9,14 +9,22 @@ import type { IGetHeaderController } from "../../interface-adapters/controllers/
import { navigationProcedure } from "./procedures";
export const navigationRouter = router({
header: navigationProcedure
.input(getHeaderInputSchema)
.query(({ input }) => {
const ctrl = navigationContainer.get<IGetHeaderController>(
NAVIGATION_SYMBOLS.IGetHeaderController,
);
return ctrl(input);
}),
header: navigationProcedure.input(getHeaderInputSchema).query(({ input }) => {
const ctrl = navigationContainer.get<IGetHeaderController>(
NAVIGATION_SYMBOLS.IGetHeaderController,
);
return ctrl(input);
}),
});
export type NavigationRouter = typeof navigationRouter;
/**
* This feature's slice as it is mounted in the app router (the `navigation` key
* in @repo/core-api). UI hooks pass it to `useTRPC<NavigationAppSlice>()` 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 NavigationAppSlice = ReturnType<
typeof router<{ navigation: typeof navigationRouter }>
>;

View File

@@ -2,10 +2,11 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { useTRPC } from "@repo/core-trpc";
import type { NavigationAppSlice } from "../../integrations/api/router";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function useHeader() {
const trpc = useTRPC();
const trpc = useTRPC<NavigationAppSlice>();
return useSuspenseQuery(trpc.navigation.header.queryOptions({})) as {
data: {
items: { label: string; href: string; external: boolean }[];