Skip to content

Commit d2cc53d

Browse files
authored
feat(aurora): add support for custom tRPC routers with type safety (#1045)
* feat(aurora): add support for custom tRPC routers with type safety - Export generic type helpers (CreateTypedTrpcReact, CreateTypedTrpcClient) for apps to extend tRPC clients with custom routers - Export AuroraRouterWithCustom type helper for merging custom routers with base router - Add trpcClient.ts to vite dts plugin include list to generate proper type definitions - Export TrpcReact type from client index This enables consuming apps (like dashboard) to: 1. Define custom tRPC routers using auroraRouter/protectedProcedure 2. Register them with createServer({ routers: [...] }) 3. Re-export Aurora's tRPC clients with extended types for full type safety * chore(aurora): remove unnedeed deps * chore(aurora): bugfix in comments * chore: exclude apps/dev from workspace and git tracking - Add apps/dev/ to .gitignore (local testing only) - Add !apps/dev to pnpm-workspace.yaml to exclude from workspace * chore(changeset): add changeset for custom routers support
1 parent 914411a commit d2cc53d

8 files changed

Lines changed: 102 additions & 11 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
"@cobaltcore-dev/aurora": minor
3+
---
4+
5+
Add support for custom tRPC routers with full type safety
6+
7+
**New exports from `@cobaltcore-dev/aurora/server`:**
8+
- `AuroraRouterWithCustom<T>` - Type helper to merge custom routers with base Aurora router
9+
10+
**New exports from `@cobaltcore-dev/aurora/client`:**
11+
- `CreateTypedTrpcReact<T>` - Generic type for typed React tRPC client
12+
- `CreateTypedTrpcClient<T>` - Generic type for typed vanilla tRPC client
13+
- `TrpcReact` - Type alias for the React tRPC client
14+
15+
**Usage:**
16+
17+
1. Define custom routers using `auroraRouter` and `protectedProcedure`:
18+
```typescript
19+
import { auroraRouter, protectedProcedure } from "@cobaltcore-dev/aurora/server"
20+
21+
export const customRouters = auroraRouter({
22+
feedback: auroraRouter({
23+
submit: protectedProcedure
24+
.input(z.object({ message: z.string() }))
25+
.mutation(async ({ input }) => ({ success: true })),
26+
}),
27+
})
28+
```
29+
30+
2. Register with `createServer`:
31+
```typescript
32+
createServer({ routers: [customRouters], ... })
33+
```
34+
35+
3. Create typed client exports:
36+
```typescript
37+
import type { AuroraRouterWithCustom } from "@cobaltcore-dev/aurora/server"
38+
import { trpcReact, CreateTypedTrpcReact } from "@cobaltcore-dev/aurora/client"
39+
40+
type AppRouter = AuroraRouterWithCustom<typeof customRouters>
41+
export const trpc = trpcReact as unknown as CreateTypedTrpcReact<AppRouter>
42+
```
43+
44+
4. Use with full type safety:
45+
```typescript
46+
const mutation = trpc.feedback.submit.useMutation() // ✅ Type-safe!
47+
```

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ test-results/
2020
playwright-results/
2121
playwright-report/
2222
playwright/.cache/
23+
24+
# Dev app (local testing only)
25+
apps/dev/

packages/aurora/src/client/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,12 @@ export {
66
type TrackEventPayload,
77
type OnTrackEventCallback,
88
} from "./AuroraApp"
9-
export { type TrpcClient, trpcClient, trpcReactClient, trpcReact } from "./trpcClient"
9+
export {
10+
type TrpcClient,
11+
type TrpcReact,
12+
type CreateTypedTrpcReact,
13+
type CreateTypedTrpcClient,
14+
trpcClient,
15+
trpcReactClient,
16+
trpcReact,
17+
} from "./trpcClient"

packages/aurora/src/client/trpcClient.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createTRPCReact } from "@trpc/react-query"
2+
import type { CreateTRPCReact } from "@trpc/react-query"
3+
import { createTRPCClient, type TRPCClient } from "@trpc/client"
24
import {
3-
createTRPCClient,
45
httpBatchLink,
56
httpBatchStreamLink,
67
httpLink,
@@ -10,6 +11,18 @@ import {
1011
} from "@trpc/client"
1112
import type { AuroraRouter } from "../server/routers"
1213

14+
/** Type alias for the React tRPC client */
15+
export type TrpcReact = ReturnType<typeof createTRPCReact<AuroraRouter>>
16+
17+
/** Type alias for the vanilla tRPC client */
18+
export type TrpcClient = ReturnType<typeof createTRPCClient<AuroraRouter>>
19+
20+
/** Generic type for creating a typed React tRPC client with a custom router */
21+
export type CreateTypedTrpcReact<TRouter extends AuroraRouter> = CreateTRPCReact<TRouter, unknown>
22+
23+
/** Generic type for creating a typed vanilla tRPC client with a custom router */
24+
export type CreateTypedTrpcClient<TRouter extends AuroraRouter> = TRPCClient<TRouter>
25+
1326
// CSRF headers factory
1427
const getCsrfHeaders = async () => {
1528
try {
@@ -91,10 +104,10 @@ const getLinks = () => [
91104
]
92105

93106
// React Query client (for hooks like useQuery/useMutation/useSubscription)
94-
export const trpcReact: ReturnType<typeof createTRPCReact<AuroraRouter>> = createTRPCReact<AuroraRouter>()
107+
export const trpcReact: TrpcReact = createTRPCReact<AuroraRouter>()
95108

96109
let _trpcReactClient: ReturnType<typeof trpcReact.createClient> | null = null
97-
let _trpcClient: ReturnType<typeof createTRPCClient<AuroraRouter>> | null = null
110+
let _trpcClient: TrpcClient | null = null
98111

99112
// Lazily initialised — created on first access after setBffEndpoint() has been called by App.
100113
export const trpcReactClient = new Proxy({} as ReturnType<typeof trpcReact.createClient>, {
@@ -106,14 +119,11 @@ export const trpcReactClient = new Proxy({} as ReturnType<typeof trpcReact.creat
106119
},
107120
})
108121

109-
export const trpcClient = new Proxy({} as ReturnType<typeof createTRPCClient<AuroraRouter>>, {
122+
export const trpcClient: TrpcClient = new Proxy({} as TrpcClient, {
110123
get(_target, prop) {
111124
if (!_trpcClient) {
112125
_trpcClient = createTRPCClient<AuroraRouter>({ links: getLinks() })
113126
}
114127
return (_trpcClient as Record<string | symbol, unknown>)[prop]
115128
},
116129
})
117-
118-
export type TrpcReact = typeof trpcReact
119-
export type TrpcClient = typeof trpcClient

packages/aurora/src/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export {
99
projectScopedInputSchema,
1010
domainScopedInputSchema,
1111
} from "./trpc"
12+
export type { AuroraRouter, AuroraRouterWithCustom } from "./routers"

packages/aurora/src/server/routers.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,28 @@ const buildBaseRouter = (policyDir: string) =>
1919

2020
export type AuroraRouter = ReturnType<typeof buildBaseRouter>
2121

22-
export const buildAppRouter = (policyDir: string, extraRouters: AnyRouter[] = []): AuroraRouter => {
22+
/**
23+
* Helper type to merge custom routers with the base Aurora router.
24+
* Use this in consuming apps to create a properly typed router that includes custom routers.
25+
*
26+
* @example
27+
* ```ts
28+
* import type { AuroraRouterWithCustom } from "@cobaltcore-dev/aurora/server"
29+
* import { customRouters } from "./server/customRouters"
30+
*
31+
* export type AppRouter = AuroraRouterWithCustom<typeof customRouters>
32+
* ```
33+
*/
34+
export type AuroraRouterWithCustom<TCustomRouters extends AnyRouter> = AuroraRouter & TCustomRouters
35+
36+
/**
37+
* Builds the complete app router by merging Aurora's base routers with optional custom routers.
38+
*
39+
* @param policyDir - Directory containing policy files
40+
* @param extraRouters - Optional array of custom tRPC routers to merge with base Aurora routers
41+
* @returns The merged router (base routers + custom routers)
42+
*/
43+
export const buildAppRouter = (policyDir: string, extraRouters: AnyRouter[] = []) => {
2344
if (extraRouters.length === 0) return buildBaseRouter(policyDir)
24-
return mergeRouters(buildBaseRouter(policyDir), ...extraRouters) as unknown as AuroraRouter
45+
return mergeRouters(buildBaseRouter(policyDir), ...extraRouters)
2546
}

packages/aurora/vite.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export default defineConfig(({ mode }) => {
6565
tsconfigPaths(),
6666
isLib &&
6767
dts({
68-
include: ["index.ts", "AuroraApp.tsx"],
68+
include: ["index.ts", "AuroraApp.tsx", "trpcClient.ts"],
6969
outDir: "../../dist/client",
7070
tsconfigPath: "../../tsconfig.json",
7171
entryRoot: ".",

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
packages:
22
- "apps/*"
3+
- "!apps/dev"
34
- "packages/*"
45

56
minimumReleaseAge: 10080

0 commit comments

Comments
 (0)