@trpc/server 11.14.0 → 11.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/bin/intent.js +20 -0
- package/dist/adapters/next-app-dir.cjs +76 -76
- package/dist/adapters/next-app-dir.mjs +76 -76
- package/dist/adapters/next-app-dir.mjs.map +1 -1
- package/package.json +13 -3
- package/skills/adapter-aws-lambda/SKILL.md +188 -0
- package/skills/adapter-express/SKILL.md +152 -0
- package/skills/adapter-fastify/SKILL.md +206 -0
- package/skills/adapter-fetch/SKILL.md +177 -0
- package/skills/adapter-standalone/SKILL.md +184 -0
- package/skills/auth/SKILL.md +342 -0
- package/skills/caching/SKILL.md +205 -0
- package/skills/error-handling/SKILL.md +253 -0
- package/skills/middlewares/SKILL.md +242 -0
- package/skills/non-json-content-types/SKILL.md +265 -0
- package/skills/server-setup/SKILL.md +378 -0
- package/skills/server-side-calls/SKILL.md +249 -0
- package/skills/service-oriented-architecture/SKILL.md +247 -0
- package/skills/subscriptions/SKILL.md +406 -0
- package/skills/trpc-router/SKILL.md +151 -0
- package/skills/validators/SKILL.md +228 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-side-calls
|
|
3
|
+
description: >
|
|
4
|
+
Call tRPC procedures directly from server code using t.createCallerFactory()
|
|
5
|
+
and router.createCaller(context) for integration testing, internal server logic,
|
|
6
|
+
and custom API endpoints. Catch TRPCError and extract HTTP status with
|
|
7
|
+
getHTTPStatusCodeFromError(). Error handling via onError option.
|
|
8
|
+
type: core
|
|
9
|
+
library: trpc
|
|
10
|
+
library_version: '11.14.0'
|
|
11
|
+
requires:
|
|
12
|
+
- server-setup
|
|
13
|
+
sources:
|
|
14
|
+
- 'trpc/trpc:www/docs/server/server-side-calls.md'
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# tRPC -- Server-Side Calls
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// server/trpc.ts
|
|
23
|
+
import { initTRPC } from '@trpc/server';
|
|
24
|
+
|
|
25
|
+
type Context = { user?: { id: string } };
|
|
26
|
+
|
|
27
|
+
const t = initTRPC.context<Context>().create();
|
|
28
|
+
|
|
29
|
+
export const router = t.router;
|
|
30
|
+
export const publicProcedure = t.procedure;
|
|
31
|
+
export const createCallerFactory = t.createCallerFactory;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// server/appRouter.ts
|
|
36
|
+
import { TRPCError } from '@trpc/server';
|
|
37
|
+
import { z } from 'zod';
|
|
38
|
+
import { createCallerFactory, publicProcedure, router } from './trpc';
|
|
39
|
+
|
|
40
|
+
interface Post {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const posts: Post[] = [{ id: '1', title: 'Hello world' }];
|
|
46
|
+
|
|
47
|
+
export const appRouter = router({
|
|
48
|
+
post: router({
|
|
49
|
+
add: publicProcedure
|
|
50
|
+
.input(z.object({ title: z.string().min(2) }))
|
|
51
|
+
.mutation(({ input }) => {
|
|
52
|
+
const post: Post = { ...input, id: `${Math.random()}` };
|
|
53
|
+
posts.push(post);
|
|
54
|
+
return post;
|
|
55
|
+
}),
|
|
56
|
+
byId: publicProcedure
|
|
57
|
+
.input(z.object({ id: z.string() }))
|
|
58
|
+
.query(({ input }) => {
|
|
59
|
+
const post = posts.find((p) => p.id === input.id);
|
|
60
|
+
if (!post) throw new TRPCError({ code: 'NOT_FOUND' });
|
|
61
|
+
return post;
|
|
62
|
+
}),
|
|
63
|
+
list: publicProcedure.query(() => posts),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export type AppRouter = typeof appRouter;
|
|
68
|
+
export const createCaller = createCallerFactory(appRouter);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// usage
|
|
73
|
+
import { createCaller } from './appRouter';
|
|
74
|
+
|
|
75
|
+
const caller = createCaller({ user: { id: '1' } });
|
|
76
|
+
const postList = await caller.post.list();
|
|
77
|
+
const newPost = await caller.post.add({ title: 'New post' });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Core Patterns
|
|
81
|
+
|
|
82
|
+
### Integration test with createCallerFactory
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// server/appRouter.test.ts
|
|
86
|
+
import type { inferProcedureInput } from '@trpc/server';
|
|
87
|
+
import { createCaller } from './appRouter';
|
|
88
|
+
import type { AppRouter } from './appRouter';
|
|
89
|
+
import { createContextInner } from './context';
|
|
90
|
+
|
|
91
|
+
async function testAddAndGetPost() {
|
|
92
|
+
const ctx = await createContextInner({ user: undefined });
|
|
93
|
+
const caller = createCaller(ctx);
|
|
94
|
+
|
|
95
|
+
const input: inferProcedureInput<AppRouter['post']['add']> = {
|
|
96
|
+
title: 'Test post',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const post = await caller.post.add(input);
|
|
100
|
+
const allPosts = await caller.post.list();
|
|
101
|
+
|
|
102
|
+
console.assert(allPosts.some((p) => p.id === post.id));
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Using router.createCaller() directly
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { initTRPC } from '@trpc/server';
|
|
110
|
+
import { z } from 'zod';
|
|
111
|
+
|
|
112
|
+
const t = initTRPC.create();
|
|
113
|
+
|
|
114
|
+
const appRouter = t.router({
|
|
115
|
+
greeting: t.procedure
|
|
116
|
+
.input(z.object({ name: z.string() }))
|
|
117
|
+
.query(({ input }) => `Hello ${input.name}`),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const caller = appRouter.createCaller({});
|
|
121
|
+
const result = await caller.greeting({ name: 'tRPC' });
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Error handling in a custom API endpoint
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { TRPCError } from '@trpc/server';
|
|
128
|
+
import { getHTTPStatusCodeFromError } from '@trpc/server/http';
|
|
129
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
130
|
+
import { appRouter } from './appRouter';
|
|
131
|
+
|
|
132
|
+
export default async function handler(
|
|
133
|
+
req: NextApiRequest,
|
|
134
|
+
res: NextApiResponse,
|
|
135
|
+
) {
|
|
136
|
+
const caller = appRouter.createCaller({});
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const post = await caller.post.byId({ id: req.query.id as string });
|
|
140
|
+
res.status(200).json({ data: { postTitle: post.title } });
|
|
141
|
+
} catch (cause) {
|
|
142
|
+
if (cause instanceof TRPCError) {
|
|
143
|
+
const httpStatusCode = getHTTPStatusCodeFromError(cause);
|
|
144
|
+
res.status(httpStatusCode).json({ error: { message: cause.message } });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
res.status(500).json({ error: { message: 'Internal server error' } });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Caller with onError callback
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { initTRPC } from '@trpc/server';
|
|
156
|
+
import { z } from 'zod';
|
|
157
|
+
|
|
158
|
+
const t = initTRPC.create();
|
|
159
|
+
|
|
160
|
+
const appRouter = t.router({
|
|
161
|
+
greeting: t.procedure
|
|
162
|
+
.input(z.object({ name: z.string() }))
|
|
163
|
+
.query(({ input }) => {
|
|
164
|
+
if (input.name === 'invalid') {
|
|
165
|
+
throw new Error('Invalid name');
|
|
166
|
+
}
|
|
167
|
+
return `Hello ${input.name}`;
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const caller = appRouter.createCaller(
|
|
172
|
+
{},
|
|
173
|
+
{
|
|
174
|
+
onError: (opts) => {
|
|
175
|
+
console.error('An error occurred:', opts.error);
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Common Mistakes
|
|
182
|
+
|
|
183
|
+
### [HIGH] Using createCaller inside another procedure
|
|
184
|
+
|
|
185
|
+
Wrong:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { appRouter } from './appRouter';
|
|
189
|
+
import { createCallerFactory, publicProcedure } from './trpc';
|
|
190
|
+
|
|
191
|
+
const createCaller = createCallerFactory(appRouter);
|
|
192
|
+
|
|
193
|
+
const proc = publicProcedure.query(async () => {
|
|
194
|
+
const caller = createCaller({});
|
|
195
|
+
return caller.post.list();
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Correct:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import type { Context } from './context';
|
|
203
|
+
import { publicProcedure } from './trpc';
|
|
204
|
+
|
|
205
|
+
async function listPosts(ctx: Context) {
|
|
206
|
+
return ctx.db.post.findMany();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const proc = publicProcedure.query(async ({ ctx }) => {
|
|
210
|
+
return listPosts(ctx);
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Calling createCaller from within a procedure re-creates context, re-runs all middleware, and re-validates input; extract shared logic into a plain function instead.
|
|
215
|
+
|
|
216
|
+
Source: www/docs/server/server-side-calls.md
|
|
217
|
+
|
|
218
|
+
### [MEDIUM] Not providing context to createCaller
|
|
219
|
+
|
|
220
|
+
Wrong:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { appRouter } from './appRouter';
|
|
224
|
+
|
|
225
|
+
const caller = appRouter.createCaller({});
|
|
226
|
+
await caller.protectedRoute();
|
|
227
|
+
// middleware throws UNAUTHORIZED because ctx.user is undefined
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Correct:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { appRouter } from './appRouter';
|
|
234
|
+
import { createContextInner } from './context';
|
|
235
|
+
|
|
236
|
+
const ctx = await createContextInner({ user: { id: '1' } });
|
|
237
|
+
const caller = appRouter.createCaller(ctx);
|
|
238
|
+
await caller.protectedRoute();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`createCaller` requires a context object matching what procedures and middleware expect; passing an empty object when procedures require auth context causes runtime errors.
|
|
242
|
+
|
|
243
|
+
Source: www/docs/server/server-side-calls.md
|
|
244
|
+
|
|
245
|
+
## See Also
|
|
246
|
+
|
|
247
|
+
- `server-setup` -- initTRPC, routers, context configuration
|
|
248
|
+
- `middlewares` -- auth middleware that callers must satisfy
|
|
249
|
+
- `error-handling` -- TRPCError and getHTTPStatusCodeFromError
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-oriented-architecture
|
|
3
|
+
description: >
|
|
4
|
+
Break a tRPC backend into multiple services with custom routing links that
|
|
5
|
+
split on the first path segment (op.path.split('.')) to route to different
|
|
6
|
+
backend service URLs. Define a faux gateway router that merges service routers
|
|
7
|
+
for the AppRouter type without running them in the same process. Share
|
|
8
|
+
procedure and router definitions via a server-lib package with a single
|
|
9
|
+
initTRPC instance. Each service runs its own standalone/Express/Fastify server.
|
|
10
|
+
type: composition
|
|
11
|
+
library: trpc
|
|
12
|
+
library_version: '11.14.0'
|
|
13
|
+
requires:
|
|
14
|
+
- server-setup
|
|
15
|
+
- client-setup
|
|
16
|
+
- links
|
|
17
|
+
sources:
|
|
18
|
+
- examples/soa/
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# tRPC — Service-Oriented Architecture
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
### Shared library (single initTRPC instance)
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// packages/server-lib/index.ts
|
|
29
|
+
import { initTRPC } from '@trpc/server';
|
|
30
|
+
|
|
31
|
+
type Context = {
|
|
32
|
+
requestId?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const t = initTRPC.context<Context>().create();
|
|
36
|
+
|
|
37
|
+
export const router = t.router;
|
|
38
|
+
export const publicProcedure = t.procedure;
|
|
39
|
+
export const mergeRouters = t.mergeRouters;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Service A (own server)
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// services/service-a/router.ts
|
|
46
|
+
import { publicProcedure, router } from '@myorg/server-lib';
|
|
47
|
+
import { z } from 'zod';
|
|
48
|
+
|
|
49
|
+
export const serviceARouter = router({
|
|
50
|
+
greet: publicProcedure
|
|
51
|
+
.input(z.object({ name: z.string() }))
|
|
52
|
+
.query(({ input }) => ({ greeting: `Hello, ${input.name}!` })),
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// services/service-a/index.ts
|
|
58
|
+
import { createHTTPServer } from '@trpc/server/adapters/standalone';
|
|
59
|
+
import { serviceARouter } from './router';
|
|
60
|
+
|
|
61
|
+
createHTTPServer({
|
|
62
|
+
router: serviceARouter,
|
|
63
|
+
createContext() {
|
|
64
|
+
return {};
|
|
65
|
+
},
|
|
66
|
+
}).listen(2021);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Service B (own server)
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// services/service-b/router.ts
|
|
73
|
+
import { publicProcedure, router } from '@myorg/server-lib';
|
|
74
|
+
|
|
75
|
+
export const serviceBRouter = router({
|
|
76
|
+
status: publicProcedure.query(() => ({ status: 'ok' })),
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// services/service-b/index.ts
|
|
82
|
+
import { createHTTPServer } from '@trpc/server/adapters/standalone';
|
|
83
|
+
import { serviceBRouter } from './router';
|
|
84
|
+
|
|
85
|
+
createHTTPServer({
|
|
86
|
+
router: serviceBRouter,
|
|
87
|
+
createContext() {
|
|
88
|
+
return {};
|
|
89
|
+
},
|
|
90
|
+
}).listen(2022);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Gateway (type-only, not a running server)
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// gateway/index.ts
|
|
97
|
+
import { router } from '@myorg/server-lib';
|
|
98
|
+
import { serviceARouter } from '../services/service-a/router';
|
|
99
|
+
import { serviceBRouter } from '../services/service-b/router';
|
|
100
|
+
|
|
101
|
+
const appRouter = router({
|
|
102
|
+
serviceA: serviceARouter,
|
|
103
|
+
serviceB: serviceBRouter,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export type AppRouter = typeof appRouter;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The gateway merges routers only for type inference. It does not run as a server process. The client uses the `AppRouter` type for full type safety.
|
|
110
|
+
|
|
111
|
+
### Client with custom routing link
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// client/client.ts
|
|
115
|
+
import { createTRPCClient, httpBatchLink } from '@trpc/client';
|
|
116
|
+
import type { AppRouter } from '../gateway';
|
|
117
|
+
|
|
118
|
+
export const client = createTRPCClient<AppRouter>({
|
|
119
|
+
links: [
|
|
120
|
+
(runtime) => {
|
|
121
|
+
const servers = {
|
|
122
|
+
serviceA: httpBatchLink({ url: 'http://localhost:2021' })(runtime),
|
|
123
|
+
serviceB: httpBatchLink({ url: 'http://localhost:2022' })(runtime),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return (ctx) => {
|
|
127
|
+
const { op } = ctx;
|
|
128
|
+
const pathParts = op.path.split('.');
|
|
129
|
+
const serverName = pathParts.shift() as keyof typeof servers;
|
|
130
|
+
const path = pathParts.join('.');
|
|
131
|
+
|
|
132
|
+
const link = servers[serverName];
|
|
133
|
+
if (!link) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Unknown service: ${String(serverName)}. Known: ${Object.keys(servers).join(', ')}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return link({
|
|
139
|
+
...ctx,
|
|
140
|
+
op: { ...op, path },
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// Usage
|
|
150
|
+
const greeting = await client.serviceA.greet.query({ name: 'World' });
|
|
151
|
+
const status = await client.serviceB.status.query();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Core Patterns
|
|
155
|
+
|
|
156
|
+
### Path-based routing convention
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
(runtime) => {
|
|
160
|
+
const servers = {
|
|
161
|
+
users: httpBatchLink({ url: 'http://users-service:3000' })(runtime),
|
|
162
|
+
billing: httpBatchLink({ url: 'http://billing-service:3000' })(runtime),
|
|
163
|
+
notifications: httpBatchLink({ url: 'http://notifications-service:3000' })(
|
|
164
|
+
runtime,
|
|
165
|
+
),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return (ctx) => {
|
|
169
|
+
const { op } = ctx;
|
|
170
|
+
const [serverName, ...rest] = op.path.split('.');
|
|
171
|
+
const link = servers[serverName as keyof typeof servers];
|
|
172
|
+
|
|
173
|
+
if (!link) {
|
|
174
|
+
throw new Error(`Unknown service: ${serverName}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return link({
|
|
178
|
+
...ctx,
|
|
179
|
+
op: { ...op, path: rest.join('.') },
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The first segment of the procedure path (before the first `.`) maps to a service name. The remaining path is forwarded to the target service.
|
|
186
|
+
|
|
187
|
+
### Adding shared headers across services
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
(runtime) => {
|
|
191
|
+
const servers = {
|
|
192
|
+
serviceA: httpBatchLink({
|
|
193
|
+
url: 'http://localhost:2021',
|
|
194
|
+
headers() {
|
|
195
|
+
return { 'x-request-id': crypto.randomUUID() };
|
|
196
|
+
},
|
|
197
|
+
})(runtime),
|
|
198
|
+
serviceB: httpBatchLink({
|
|
199
|
+
url: 'http://localhost:2022',
|
|
200
|
+
headers() {
|
|
201
|
+
return { 'x-request-id': crypto.randomUUID() };
|
|
202
|
+
},
|
|
203
|
+
})(runtime),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return (ctx) => {
|
|
207
|
+
const [serverName, ...rest] = ctx.op.path.split('.');
|
|
208
|
+
return servers[serverName as keyof typeof servers]({
|
|
209
|
+
...ctx,
|
|
210
|
+
op: { ...ctx.op, path: rest.join('.') },
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Common Mistakes
|
|
217
|
+
|
|
218
|
+
### MEDIUM Path routing assumes first segment is server name
|
|
219
|
+
|
|
220
|
+
Wrong:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
const serverName = op.path.split('.').shift();
|
|
224
|
+
// Breaks if router structure changes or has nested namespaces
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Correct:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
const [serverName, ...rest] = op.path.split('.');
|
|
231
|
+
const link = servers[serverName as keyof typeof servers];
|
|
232
|
+
if (!link) {
|
|
233
|
+
throw new Error(`Unknown service: ${serverName}. Known: ${Object.keys(servers).join(', ')}`);
|
|
234
|
+
}
|
|
235
|
+
return link({ ...ctx, op: { ...op, path: rest.join('.') } });
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Custom routing links that split on the first path segment break silently if the router structure changes. Add validation and clear error messages when the server name is unrecognized. The path convention must be documented and enforced across teams.
|
|
239
|
+
|
|
240
|
+
Source: examples/soa/client/client.ts
|
|
241
|
+
|
|
242
|
+
## See Also
|
|
243
|
+
|
|
244
|
+
- **server-setup** -- single `initTRPC.create()` instance shared across services
|
|
245
|
+
- **links** -- `httpBatchLink`, custom link authoring
|
|
246
|
+
- **client-setup** -- `createTRPCClient`, type-safe client with `AppRouter`
|
|
247
|
+
- **adapter-standalone** -- running individual service servers
|