dev-booster 1.17.2 → 1.18.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/package.json +1 -1
- package/src/index.js +62 -0
- package/template/.devbooster/MANIFEST.md +34 -1
- package/template/.devbooster/boosters/advisor.md +5 -0
- package/template/.devbooster/boosters/audit.md +20 -0
- package/template/.devbooster/boosters/auto-triage.md +5 -0
- package/template/.devbooster/boosters/backend.md +12 -0
- package/template/.devbooster/boosters/builder.md +5 -0
- package/template/.devbooster/boosters/code-audit.md +19 -0
- package/template/.devbooster/boosters/coder.md +5 -0
- package/template/.devbooster/boosters/create.md +5 -0
- package/template/.devbooster/boosters/debug.md +19 -0
- package/template/.devbooster/boosters/deploy.md +12 -0
- package/template/.devbooster/boosters/discovery.md +5 -0
- package/template/.devbooster/boosters/frontend.md +12 -0
- package/template/.devbooster/boosters/implementation.md +5 -0
- package/template/.devbooster/boosters/investigation.md +5 -0
- package/template/.devbooster/boosters/performance.md +12 -0
- package/template/.devbooster/boosters/planning.md +5 -0
- package/template/.devbooster/boosters/refactor.md +12 -0
- package/template/.devbooster/boosters/review.md +12 -0
- package/template/.devbooster/boosters/security.md +14 -0
- package/template/.devbooster/boosters/smart-task.md +27 -16
- package/template/.devbooster/boosters/stack-refresh.md +20 -0
- package/template/.devbooster/boosters/testing.md +12 -0
- package/template/.devbooster/hub/knowledge/angular-patterns.md +185 -0
- package/template/.devbooster/hub/knowledge/dependency-guide.md +175 -0
- package/template/.devbooster/hub/knowledge/eslint-migration.md +206 -0
- package/template/.devbooster/hub/knowledge/index.md +91 -0
- package/template/.devbooster/hub/knowledge/migration-guides.md +137 -0
- package/template/.devbooster/hub/knowledge/monorepo-patterns.md +121 -0
- package/template/.devbooster/hub/knowledge/nestjs-patterns.md +185 -0
- package/template/.devbooster/hub/knowledge/nextjs-pitfalls.md +226 -0
- package/template/.devbooster/hub/knowledge/nodejs-patterns.md +148 -0
- package/template/.devbooster/hub/knowledge/package-manager-patterns.md +143 -0
- package/template/.devbooster/hub/knowledge/prisma-postgresql-patterns.md +188 -0
- package/template/.devbooster/hub/knowledge/react-patterns.md +500 -0
- package/template/.devbooster/hub/knowledge/tailwind-shadcn-patterns.md +146 -0
- package/template/.devbooster/hub/knowledge/tanstack-patterns.md +278 -0
- package/template/.devbooster/hub/knowledge/testing-patterns.md +164 -0
- package/template/.devbooster/hub/knowledge/trpc-patterns.md +212 -0
- package/template/.devbooster/hub/knowledge/typescript-patterns.md +219 -0
- package/template/.devbooster/hub/knowledge/upgrade-fallout.md +154 -0
- package/template/.devbooster/hub/knowledge/vite-patterns.md +177 -0
- package/template/.devbooster/rules/GUIDE.md +24 -0
- package/template/.devbooster/rules/PROTOCOL.md +3 -2
- package/template/.devbooster/rules/TRIGGERS.md +14 -0
- package/template/DEVBOOSTER_INIT.md +22 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# TanStack Query Patterns
|
|
2
|
+
|
|
3
|
+
> **Purpose:** Practical TanStack Query v5 patterns for correct caching, mutations, cancellation, SSR hydration, and `QueryClient` lifecycle.
|
|
4
|
+
> **Primary official sources:** [Query keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) · [Query invalidation](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation) · [Caching](https://tanstack.com/query/latest/docs/framework/react/guides/caching) · [SSR and hydration](https://tanstack.com/query/latest/docs/framework/react/guides/ssr) · [Optimistic updates](https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates)
|
|
5
|
+
|
|
6
|
+
## Project Convention Decision Rule
|
|
7
|
+
|
|
8
|
+
Before applying this guidance, verify the installed versions, local rules, existing query and mutation hooks, query-key conventions, `QueryClient` lifecycle, configuration, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
1. [Use stable, complete query keys](#use-stable-complete-query-keys)
|
|
15
|
+
2. [Invalidate affected queries after mutations](#invalidate-affected-queries-after-mutations)
|
|
16
|
+
3. [Choose `staleTime` and `gcTime` for different concerns](#choose-staletime-and-gctime-for-different-concerns)
|
|
17
|
+
4. [Prefetch and hydrate SSR data deliberately](#prefetch-and-hydrate-ssr-data-deliberately)
|
|
18
|
+
5. [Write query functions that consume `AbortSignal`](#write-query-functions-that-consume-abortsignal)
|
|
19
|
+
6. [Make mutation errors and optimistic rollbacks explicit](#make-mutation-errors-and-optimistic-rollbacks-explicit)
|
|
20
|
+
7. [Create a stable `QueryClient`](#create-a-stable-queryclient)
|
|
21
|
+
8. [Prefer Existing Query Ownership Over a Parallel Effect](#prefer-existing-query-ownership-over-a-parallel-effect)
|
|
22
|
+
9. [Render Query States Deliberately](#render-query-states-deliberately)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Use stable, complete query keys
|
|
27
|
+
|
|
28
|
+
### Problem / symptom
|
|
29
|
+
Different data is served from one cache entry, or a query fails to refetch when a parameter changes. Common causes are a missing parameter in `queryKey` or inconsistent key shapes across the codebase.
|
|
30
|
+
|
|
31
|
+
### Fix
|
|
32
|
+
Use a serializable array key that uniquely describes the returned data. Include every changing variable read by the query function. Object property order does not affect hashing; array item order does.
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
const itemKeys = {
|
|
36
|
+
all: ['items'] as const,
|
|
37
|
+
list: (filters: { status?: string }) => ['items', { filters }] as const,
|
|
38
|
+
detail: (id: string) => ['items', id] as const,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
useQuery({
|
|
42
|
+
queryKey: itemKeys.detail(id),
|
|
43
|
+
queryFn: () => fetchItem(id),
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A key factory is optional; the requirement is stable, complete, consistently shaped keys.
|
|
48
|
+
|
|
49
|
+
### Verify first
|
|
50
|
+
- Compare all variables used by `queryFn` with the `queryKey`.
|
|
51
|
+
- Change each variable and confirm a distinct cache entry or expected refetch occurs.
|
|
52
|
+
- Confirm list and detail keys support the invalidation scope that mutations require.
|
|
53
|
+
|
|
54
|
+
*Source: [Query keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys)*
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Invalidate affected queries after mutations
|
|
59
|
+
|
|
60
|
+
### Problem / symptom
|
|
61
|
+
A mutation succeeds but visible data remains stale, or invalidation refetches unrelated data unnecessarily.
|
|
62
|
+
|
|
63
|
+
### Fix
|
|
64
|
+
On mutation success or settlement, invalidate the smallest key prefix that represents all data changed by the mutation. `invalidateQueries` marks matching queries stale and background-refetches matching active queries; invalidation overrides configured `staleTime`.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
const queryClient = useQueryClient()
|
|
68
|
+
|
|
69
|
+
useMutation({
|
|
70
|
+
mutationFn: updateItem,
|
|
71
|
+
onSuccess: (_item, variables) =>
|
|
72
|
+
Promise.all([
|
|
73
|
+
queryClient.invalidateQueries({ queryKey: itemKeys.detail(variables.id) }),
|
|
74
|
+
queryClient.invalidateQueries({ queryKey: itemKeys.all }),
|
|
75
|
+
]),
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use `{ exact: true }` only when the mutation genuinely affects one exact key. If the mutation response completely and correctly represents a cached resource, `setQueryData` can update that specific entry instead of a refetch.
|
|
80
|
+
|
|
81
|
+
### Verify first
|
|
82
|
+
- List every query view affected by create, update, and delete paths.
|
|
83
|
+
- Confirm the invalidation key matches each intended query and excludes unrelated ones.
|
|
84
|
+
- Confirm the mutation waits for any required invalidation promise when pending state should include the refetch.
|
|
85
|
+
|
|
86
|
+
*Source: [Query invalidation](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation) · [Invalidation from mutations](https://tanstack.com/query/latest/docs/framework/react/guides/invalidations-from-mutations)*
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Choose `staleTime` and `gcTime` for different concerns
|
|
91
|
+
|
|
92
|
+
### Problem / symptom
|
|
93
|
+
`gcTime` is increased to stop refetching, or `staleTime` is increased to retain data for remounts. The result is an incorrect freshness policy or unexpected memory use.
|
|
94
|
+
|
|
95
|
+
### Fix
|
|
96
|
+
`staleTime` controls how long fetched data is considered fresh. `gcTime` controls how long an **inactive** query remains in cache before garbage collection. They are independent:
|
|
97
|
+
|
|
98
|
+
| Setting | Controls | Default described by the docs |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `staleTime` | Freshness and eligibility for automatic refetch behavior | `0` (immediately stale) |
|
|
101
|
+
| `gcTime` | Retention after the final observer unmounts | 5 minutes in the browser |
|
|
102
|
+
|
|
103
|
+
Set `staleTime` from the acceptable age of the data, not from navigation frequency. Set `gcTime` from the value of retaining inactive data versus memory cost.
|
|
104
|
+
|
|
105
|
+
### Verify first
|
|
106
|
+
- Determine the maximum acceptable data age for each query before choosing `staleTime`.
|
|
107
|
+
- Unmount and remount a query before and after `gcTime` to distinguish retention from freshness.
|
|
108
|
+
- For SSR, note that default server `gcTime` is `Infinity`; do not set it to `0`, which can remove hydrated data before rendering completes.
|
|
109
|
+
|
|
110
|
+
*Source: [Caching examples](https://tanstack.com/query/latest/docs/framework/react/guides/caching) · [SSR memory and staleness](https://tanstack.com/query/latest/docs/framework/react/guides/ssr#tips-tricks-and-caveats)*
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Prefetch and hydrate SSR data deliberately
|
|
115
|
+
|
|
116
|
+
### Problem / symptom
|
|
117
|
+
SSR markup renders data but the browser immediately fetches it again, displays different initial output, or a server cache leaks data between requests.
|
|
118
|
+
|
|
119
|
+
### Fix
|
|
120
|
+
For data required in initial markup, create a request-local `QueryClient` during the framework's preload/loader phase, `prefetchQuery` the required queries, `dehydrate` it, then render the client tree under `HydrationBoundary` with that state. The browser provider must create one stable client instance per application lifecycle. Set a nonzero `staleTime` when immediate background refetch after hydration is not desired.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// Server preload phase
|
|
124
|
+
const queryClient = new QueryClient()
|
|
125
|
+
await queryClient.prefetchQuery({ queryKey: ['items'], queryFn: fetchItems })
|
|
126
|
+
const dehydratedState = dehydrate(queryClient)
|
|
127
|
+
|
|
128
|
+
// Rendered client tree
|
|
129
|
+
<HydrationBoundary state={dehydratedState}>
|
|
130
|
+
<Items />
|
|
131
|
+
</HydrationBoundary>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Use `fetchQuery` rather than `prefetchQuery` when server rendering must surface a failure for framework-level handling: `prefetchQuery` does not throw, and dehydration includes successful queries by default.
|
|
135
|
+
|
|
136
|
+
### Verify first
|
|
137
|
+
- Confirm the server prefetch key exactly matches the browser query key.
|
|
138
|
+
- Confirm the dehydration payload is serialized safely by the framework and contains only data safe to deliver to the browser.
|
|
139
|
+
- Test a cold request and a client-side navigation; measure whether critical data refetches according to the selected `staleTime`.
|
|
140
|
+
|
|
141
|
+
*Source: [Server rendering and hydration](https://tanstack.com/query/latest/docs/framework/react/guides/ssr)*
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Write query functions that consume `AbortSignal`
|
|
146
|
+
|
|
147
|
+
### Problem / symptom
|
|
148
|
+
Superseded or unused requests continue consuming network and server resources, or manual cancellation does not cancel the underlying request.
|
|
149
|
+
|
|
150
|
+
### Fix
|
|
151
|
+
TanStack Query passes an `AbortSignal` to each query function. Pass it to `fetch` or a supported HTTP client. When the signal is consumed and aborted, the query promise is cancelled and the query state reverts to its previous state.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
useQuery({
|
|
155
|
+
queryKey: itemKeys.detail(id),
|
|
156
|
+
queryFn: async ({ signal }) => {
|
|
157
|
+
const response = await fetch(`/api/items/${id}`, { signal })
|
|
158
|
+
if (!response.ok) throw new Error('Unable to load item')
|
|
159
|
+
return response.json()
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
By default, an unused query is not cancelled merely because it unmounts; its resolved result can still populate the cache. Consuming the signal opts the request into cancellation behavior.
|
|
165
|
+
|
|
166
|
+
### Verify first
|
|
167
|
+
- Confirm the HTTP library accepts and forwards `AbortSignal`.
|
|
168
|
+
- Start a request, unmount or call `cancelQueries`, and inspect whether the underlying request is aborted.
|
|
169
|
+
- Ensure UI and error reporting distinguish an intentional cancellation from an actionable failure when that distinction matters.
|
|
170
|
+
|
|
171
|
+
*Source: [Query cancellation](https://tanstack.com/query/latest/docs/framework/react/guides/query-cancellation)*
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Make mutation errors and optimistic rollbacks explicit
|
|
176
|
+
|
|
177
|
+
### Problem / symptom
|
|
178
|
+
An optimistic cache update is overwritten by an in-flight refetch, or a failed mutation leaves incorrect optimistic data visible.
|
|
179
|
+
|
|
180
|
+
### Fix
|
|
181
|
+
Use the simplest option first: render a temporary mutation result from `variables` when only one view needs it. For a shared optimistic cache update, cancel relevant queries, snapshot the previous value, update the cache in `onMutate`, restore the snapshot in `onError`, and invalidate in `onSettled`.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
useMutation({
|
|
185
|
+
mutationFn: updateItem,
|
|
186
|
+
onMutate: async (nextItem, context) => {
|
|
187
|
+
await context.client.cancelQueries({ queryKey: itemKeys.detail(nextItem.id) })
|
|
188
|
+
const previous = context.client.getQueryData(itemKeys.detail(nextItem.id))
|
|
189
|
+
context.client.setQueryData(itemKeys.detail(nextItem.id), nextItem)
|
|
190
|
+
return { previous }
|
|
191
|
+
},
|
|
192
|
+
onError: (_error, item, snapshot, context) => {
|
|
193
|
+
context.client.setQueryData(itemKeys.detail(item.id), snapshot?.previous)
|
|
194
|
+
},
|
|
195
|
+
onSettled: (_data, _error, item, _snapshot, context) =>
|
|
196
|
+
context.client.invalidateQueries({ queryKey: itemKeys.detail(item.id) }),
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Return the snapshot from `onMutate`; TanStack Query passes it to `onError` and `onSettled`. Retain mutation error state for a visible recovery path when appropriate.
|
|
201
|
+
|
|
202
|
+
### Verify first
|
|
203
|
+
- Force the mutation to fail after the optimistic update and confirm the precise previous value is restored.
|
|
204
|
+
- Test a refetch racing with the mutation; confirm `cancelQueries` prevents it from overwriting the optimistic state.
|
|
205
|
+
- Test concurrent mutations against the same key before assuming a single snapshot is sufficient.
|
|
206
|
+
|
|
207
|
+
*Source: [Optimistic updates](https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates)*
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Create a stable `QueryClient`
|
|
212
|
+
|
|
213
|
+
### Problem / symptom
|
|
214
|
+
Cache data disappears on every render, subscriptions reset, or all requests share cache data during SSR.
|
|
215
|
+
|
|
216
|
+
### Fix
|
|
217
|
+
Create one `QueryClient` for the application lifecycle, not during every component render. In a React client component, initialize it lazily in state. For SSR, create a request-local client in the preload phase; do not place a request-serving cache at module scope.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
function App() {
|
|
221
|
+
const [queryClient] = React.useState(() => new QueryClient())
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<QueryClientProvider client={queryClient}>
|
|
225
|
+
<Routes />
|
|
226
|
+
</QueryClientProvider>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
An async Server Component may create a new client because it runs once on the server. The official ESLint rule can detect the render-time recreation pattern.
|
|
232
|
+
|
|
233
|
+
### Verify first
|
|
234
|
+
- Re-render the provider component and confirm the `QueryClient` identity remains stable.
|
|
235
|
+
- For SSR, make two isolated requests and confirm their dehydration payloads cannot contain each other's data.
|
|
236
|
+
- Enable `@tanstack/query/stable-query-client` where the ESLint plugin is used.
|
|
237
|
+
|
|
238
|
+
*Source: [Stable Query Client ESLint rule](https://tanstack.com/query/latest/docs/eslint/stable-query-client) · [SSR setup](https://tanstack.com/query/latest/docs/framework/react/guides/ssr#initial-setup)*
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Prefer Existing Query Ownership Over a Parallel Effect
|
|
243
|
+
|
|
244
|
+
### Decision
|
|
245
|
+
When a project already uses TanStack Query for a resource, the query hook owns that server state. Do not add a parallel `useEffect` fetch, duplicate local cache, or second query-key convention for the same resource.
|
|
246
|
+
|
|
247
|
+
### Verify first
|
|
248
|
+
- Locate existing query-key factories, query hooks, API clients, and mutation invalidation rules.
|
|
249
|
+
- Reuse the closest existing hook when its contract matches the screen’s data need.
|
|
250
|
+
- Create a new hook only when the query has a distinct key, inputs, response contract, or reuse boundary.
|
|
251
|
+
- Keep local state for local UI concerns; do not mirror query data into local state without a specific interaction reason.
|
|
252
|
+
|
|
253
|
+
This preserves cache coherence, loading semantics, invalidation, and developer familiarity. A plain Effect remains appropriate only when the project does not use a query layer for that boundary or when the work is not server-state ownership.
|
|
254
|
+
|
|
255
|
+
*Sources: [TanStack Query — Important Defaults](https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults) · [React — Fetching data with Effects](https://react.dev/reference/react/useEffect#fetching-data-with-effects)*
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Render Query States Deliberately
|
|
260
|
+
|
|
261
|
+
### Decision
|
|
262
|
+
A query result needs an intentional UI for pending, error, empty, and success states. Preserve any shared loading, retry, empty-state, and error components already used by comparable screens.
|
|
263
|
+
|
|
264
|
+
### Example
|
|
265
|
+
```tsx
|
|
266
|
+
if (query.isPending) return <ItemsSkeleton />
|
|
267
|
+
if (query.isError) return <ErrorState onRetry={() => query.refetch()} />
|
|
268
|
+
if (query.data.length === 0) return <EmptyState />
|
|
269
|
+
return <ItemList items={query.data} />
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Verify first
|
|
273
|
+
- Use the status names and behavior documented for the installed TanStack Query version.
|
|
274
|
+
- Keep stale data visible during a background refetch when that matches the existing experience; do not replace it with a full-screen spinner without intent.
|
|
275
|
+
- Distinguish an empty successful response from a failed request.
|
|
276
|
+
- Make retry behavior and user-facing error treatment match project conventions and API semantics.
|
|
277
|
+
|
|
278
|
+
*Sources: [TanStack Query — Queries](https://tanstack.com/query/latest/docs/framework/react/guides/queries) · [TanStack Query — Background Fetching Indicators](https://tanstack.com/query/latest/docs/framework/react/guides/background-fetching-indicators)*
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# 🧪 Testing Patterns
|
|
2
|
+
|
|
3
|
+
> **Purpose:** Build reliable unit, integration, and browser test coverage with reproducible validation.
|
|
4
|
+
> **Primary official sources:** [Vitest Guide](https://vitest.dev/guide/) · [Jest Configuration](https://jestjs.io/docs/configuration) · [Playwright Best Practices](https://playwright.dev/docs/best-practices) · [Playwright Locators](https://playwright.dev/docs/locators) · [Testing Library Guiding Principles](https://testing-library.com/docs/guiding-principles/)
|
|
5
|
+
|
|
6
|
+
## Project Convention Decision Rule
|
|
7
|
+
|
|
8
|
+
Before applying this guidance, verify the installed versions, local rules, test runner, existing fixtures and helpers, CI configuration, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
1. [Choose the Smallest Test Scope That Proves the Behavior](#choose-the-smallest-test-scope-that-proves-the-behavior)
|
|
15
|
+
2. [Test Environment Does Not Match the Code](#test-environment-does-not-match-the-code)
|
|
16
|
+
3. [Tests Depend on Time, Randomness, or Live Network State](#tests-depend-on-time-randomness-or-live-network-state)
|
|
17
|
+
4. [Browser Tests Use Fragile Locators or Share State](#browser-tests-use-fragile-locators-or-share-state)
|
|
18
|
+
5. [CI and Local Browser or Runtime Environments Differ](#ci-and-local-browser-or-runtime-environments-differ)
|
|
19
|
+
6. [Validation Matrix](#validation-matrix)
|
|
20
|
+
7. [Test Async UI States Through Observable Behavior](#test-async-ui-states-through-observable-behavior)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Choose the Smallest Test Scope That Proves the Behavior
|
|
25
|
+
|
|
26
|
+
### Problem
|
|
27
|
+
Tests either duplicate implementation details in isolated units or use slow end-to-end flows for every behavior.
|
|
28
|
+
|
|
29
|
+
### Verify first
|
|
30
|
+
State the behavior and boundary being proved: pure logic, a component interaction, an API integration, or an essential user journey. A test’s scope should match the risk it is intended to detect.
|
|
31
|
+
|
|
32
|
+
### Fix
|
|
33
|
+
Use a practical test pyramid:
|
|
34
|
+
|
|
35
|
+
| Scope | Best for | Avoid using it for |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Unit | Pure functions, validation, reducers, formatting | Browser integration details |
|
|
38
|
+
| Integration | Component behavior, module boundaries, request handling | Full deployment and browser compatibility |
|
|
39
|
+
| End-to-end | Critical user journeys across real browser boundaries | Exhaustive branch coverage |
|
|
40
|
+
|
|
41
|
+
Prefer assertions observable to users over internal implementation state. Keep a small set of high-value browser journeys and place most coverage below that layer.
|
|
42
|
+
|
|
43
|
+
*Sources: [Testing Library — Guiding Principles](https://testing-library.com/docs/guiding-principles/), [Playwright — Best Practices](https://playwright.dev/docs/best-practices)*
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Test Environment Does Not Match the Code
|
|
48
|
+
|
|
49
|
+
### Symptom
|
|
50
|
+
Tests fail on `window`, `document`, storage, or layout APIs—or browser-oriented tests run in a slower environment without need.
|
|
51
|
+
|
|
52
|
+
### Verify first
|
|
53
|
+
Identify the APIs exercised by the test. Node is appropriate for server and pure logic. A DOM environment is required for code that accesses browser globals, but simulated DOM behavior is not equivalent to a real browser.
|
|
54
|
+
|
|
55
|
+
### Fix
|
|
56
|
+
Set the environment deliberately per project or test group. In Vitest, configure `environment` or an environment annotation; in Jest, select the appropriate `testEnvironment`. Use Playwright for behavior that depends on an actual browser engine, browser navigation, or real layout behavior.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// vitest.config.ts
|
|
60
|
+
export default {
|
|
61
|
+
test: { environment: 'jsdom' },
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Verify the selected environment's version and supported APIs before adding arbitrary global mocks.
|
|
66
|
+
|
|
67
|
+
*Sources: [Vitest — Environment](https://vitest.dev/guide/environment.html), [Jest — testEnvironment](https://jestjs.io/docs/configuration#testenvironment-string)*
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Tests Depend on Time, Randomness, or Live Network State
|
|
72
|
+
|
|
73
|
+
### Symptom
|
|
74
|
+
Tests are flaky, pass alone but fail in a suite, or fail only in CI.
|
|
75
|
+
|
|
76
|
+
### Verify first
|
|
77
|
+
Look for wall-clock time, timers, random IDs, locale/time zone, shared mutable state, and outgoing network calls. Treat each as an uncontrolled input until explicitly fixed or mocked.
|
|
78
|
+
|
|
79
|
+
### Fix
|
|
80
|
+
Control inputs at the test boundary:
|
|
81
|
+
|
|
82
|
+
- Use fake timers only when the behavior is timer-driven; advance time intentionally and restore real timers after the test.
|
|
83
|
+
- Inject or seed random values when identifiers affect assertions.
|
|
84
|
+
- Mock network requests at the client boundary and assert both success and failure behavior; do not call live external services from ordinary tests.
|
|
85
|
+
- Reset mocks, module state, storage, and environment changes between tests.
|
|
86
|
+
|
|
87
|
+
When using fake timers with asynchronous code, follow the test runner's documented async timer APIs and verify pending work has settled before asserting.
|
|
88
|
+
|
|
89
|
+
*Sources: [Vitest — Mocking Timers](https://vitest.dev/guide/mocking.html#timers), [Jest — Timer Mocks](https://jestjs.io/docs/timer-mocks), [Playwright — Network](https://playwright.dev/docs/network)*
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Browser Tests Use Fragile Locators or Share State
|
|
94
|
+
|
|
95
|
+
### Symptom
|
|
96
|
+
A Playwright test breaks after markup or styling changes, or passes/fails depending on test order.
|
|
97
|
+
|
|
98
|
+
### Verify first
|
|
99
|
+
Check whether the locator expresses user-visible meaning and whether the test starts with an independent account, storage state, data record, and browser context.
|
|
100
|
+
|
|
101
|
+
### Fix
|
|
102
|
+
Prefer Playwright’s user-facing locators: `getByRole`, `getByLabel`, `getByText`, and `getByTestId` when a stable test contract is needed. Avoid long CSS/XPath selectors tied to DOM structure. Rely on Playwright’s locator auto-waiting rather than arbitrary sleeps.
|
|
103
|
+
|
|
104
|
+
Keep tests isolated: create unique data or reset it through supported setup/teardown, and avoid state carried from another test. Reuse authenticated storage state only when it is created and managed as a deliberate fixture.
|
|
105
|
+
|
|
106
|
+
*Sources: [Playwright — Locators](https://playwright.dev/docs/locators), [Playwright — Test Isolation](https://playwright.dev/docs/browser-contexts), [Playwright — Best Practices](https://playwright.dev/docs/best-practices)*
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## CI and Local Browser or Runtime Environments Differ
|
|
111
|
+
|
|
112
|
+
### Symptom
|
|
113
|
+
The suite passes locally but fails in CI, or only a particular browser fails.
|
|
114
|
+
|
|
115
|
+
### Verify first
|
|
116
|
+
Compare Node version, package-manager lockfile mode, operating system, browser version, environment variables, time zone/locale, and test command. Confirm whether CI runs the same browser projects and build artifact as local validation.
|
|
117
|
+
|
|
118
|
+
### Fix
|
|
119
|
+
Pin and declare the runtime expected by the toolchain, install from the lockfile in CI, and install the browser binaries required by the Playwright version in use. Run the same focused command locally before changing assertions. Treat a browser-specific failure as evidence to investigate, not as a reason to remove that browser project.
|
|
120
|
+
|
|
121
|
+
*Sources: [Playwright — Continuous Integration](https://playwright.dev/docs/ci), [Node.js — Releases](https://nodejs.org/en/about/previous-releases)*
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Validation Matrix
|
|
126
|
+
|
|
127
|
+
Run the narrowest relevant checks first, then the complete required matrix before merging:
|
|
128
|
+
|
|
129
|
+
| Change type | Required validation |
|
|
130
|
+
|---|---|
|
|
131
|
+
| Pure logic or utility | Targeted unit test, typecheck |
|
|
132
|
+
| Component behavior | Targeted component/integration test, typecheck, lint |
|
|
133
|
+
| Request or state boundary | Success/failure integration tests, lint, typecheck, build |
|
|
134
|
+
| User journey or routing | Targeted Playwright test against the intended build/runtime, lint, typecheck, build |
|
|
135
|
+
| Toolchain or dependency change | Full test suite, lint, typecheck, production build, relevant browser tests |
|
|
136
|
+
|
|
137
|
+
### Verify first
|
|
138
|
+
Use the scripts and configuration that the repository actually defines. A passing test command does not imply that linting, type checking, or the production build passed, and a passing DOM simulation does not replace browser coverage.
|
|
139
|
+
|
|
140
|
+
### Minimum final check
|
|
141
|
+
1. Run affected tests.
|
|
142
|
+
2. Run lint.
|
|
143
|
+
3. Run typecheck.
|
|
144
|
+
4. Run the production build.
|
|
145
|
+
5. Run affected browser tests when user-visible flows or browser behavior changed.
|
|
146
|
+
|
|
147
|
+
*Sources: [Vitest — CLI](https://vitest.dev/guide/cli), [Jest — CLI](https://jestjs.io/docs/cli), [Playwright — Test CLI](https://playwright.dev/docs/test-cli)*
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Test Async UI States Through Observable Behavior
|
|
152
|
+
|
|
153
|
+
### Decision
|
|
154
|
+
When a feature loads, mutates, retries, or fails, test the user-observable state transitions that the project exposes: pending feedback, success data, empty results, actionable errors, and recovery. Do not assert internal hook calls or implementation-only state unless that is the public contract of a dedicated unit.
|
|
155
|
+
|
|
156
|
+
### Verify first
|
|
157
|
+
- Reuse the project’s test setup, request-mocking layer, render helpers, and query-client fixture.
|
|
158
|
+
- Cover the states the screen actually supports; do not invent loading/error behavior absent from the product requirement.
|
|
159
|
+
- Await stable UI transitions with the test runner’s documented async utilities rather than arbitrary sleeps.
|
|
160
|
+
- For mutation flows, test failure and recovery as well as success when the user can retry or when optimistic UI is used.
|
|
161
|
+
|
|
162
|
+
Use component/integration tests for most async UI behavior. Add browser coverage when routing, real browser behavior, or a critical end-to-end path is the risk being addressed.
|
|
163
|
+
|
|
164
|
+
*Sources: [Testing Library — Async Methods](https://testing-library.com/docs/dom-testing-library/api-async/) · [Testing Library — Guiding Principles](https://testing-library.com/docs/guiding-principles/) · [Playwright — Auto-waiting](https://playwright.dev/docs/actionability)*
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# tRPC Patterns
|
|
2
|
+
|
|
3
|
+
> **Purpose:** Practical tRPC v11 patterns for secure procedure boundaries, end-to-end type integrity, and observable HTTP transport.
|
|
4
|
+
> **Primary official sources:** [Context](https://trpc.io/docs/server/context) · [Procedures](https://trpc.io/docs/server/procedures) · [Error handling](https://trpc.io/docs/server/error-handling) · [HTTP batch link](https://trpc.io/docs/client/links/httpBatchLink) · [Type inference](https://trpc.io/docs/client/vanilla/infer-types)
|
|
5
|
+
|
|
6
|
+
## Project Convention Decision Rule
|
|
7
|
+
|
|
8
|
+
Before applying this guidance, verify the installed versions, local rules, existing router and procedure abstractions, transport configuration, error contracts, and tests. Preserve a valid established project convention; do not replace it only because another documented approach is also valid. Use official sources to verify API behavior, compatibility, constraints, and migrations. Recommend a change only when the developer requests it or evidence shows the current approach is incompatible, unsafe, deprecated, broken, or responsible for a verified issue.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
1. [Keep authentication and authorization at procedure boundaries](#keep-authentication-and-authorization-at-procedure-boundaries)
|
|
15
|
+
2. [Validate every procedure input on the server](#validate-every-procedure-input-on-the-server)
|
|
16
|
+
3. [Map expected failures to `TRPCError`](#map-expected-failures-to-trpcerror)
|
|
17
|
+
4. [Preserve server-to-client type inference](#preserve-server-to-client-type-inference)
|
|
18
|
+
5. [Organize routers around reusable primitives and modules](#organize-routers-around-reusable-primitives-and-modules)
|
|
19
|
+
6. [Make batching and transport observable](#make-batching-and-transport-observable)
|
|
20
|
+
7. [Keep tRPC packages version-aligned](#keep-trpc-packages-version-aligned)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Keep authentication and authorization at procedure boundaries
|
|
25
|
+
|
|
26
|
+
### Problem / symptom
|
|
27
|
+
A resolver checks authentication inconsistently, or trusts a user or permission value supplied in procedure input. This makes a protected route easy to omit during future work and leaves the authenticated value nullable throughout protected resolvers.
|
|
28
|
+
|
|
29
|
+
### Fix
|
|
30
|
+
Build request-derived context once per HTTP request. Export named base procedures: `publicProcedure`, an authenticated procedure that rejects missing identity, and narrower authorization procedures when a route needs a validated scope. Middleware can refine context, so `ctx.user` is non-null in downstream resolvers.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { initTRPC, TRPCError } from '@trpc/server'
|
|
34
|
+
|
|
35
|
+
const t = initTRPC.context<Context>().create()
|
|
36
|
+
export const router = t.router
|
|
37
|
+
export const publicProcedure = t.procedure
|
|
38
|
+
|
|
39
|
+
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
|
40
|
+
if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' })
|
|
41
|
+
return next({ ctx: { user: ctx.user } })
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use input to identify the target resource, then authorize that target using trusted context and server-side data. Do not treat an input role, account identifier, or permission flag as proof of authorization.
|
|
46
|
+
|
|
47
|
+
### Verify first
|
|
48
|
+
- Confirm `createContext` is attached to the HTTP handler; tRPC creates it once per request, including one shared context for a batched request.
|
|
49
|
+
- Confirm protected procedures cannot be called without an authenticated context.
|
|
50
|
+
- Test an authenticated identity that lacks access to the target resource and expect `FORBIDDEN`.
|
|
51
|
+
|
|
52
|
+
*Source: [Context](https://trpc.io/docs/server/context) · [Reusable base procedures](https://trpc.io/docs/server/procedures#reusable-base-procedures)*
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Validate every procedure input on the server
|
|
57
|
+
|
|
58
|
+
### Problem / symptom
|
|
59
|
+
A TypeScript type is used as if it validates network input, or a resolver assumes the caller sent a valid ID, pagination value, or object shape.
|
|
60
|
+
|
|
61
|
+
### Fix
|
|
62
|
+
Add `.input(...)` to every procedure that accepts input. The parser receives untrusted `unknown` input and must return a validated value or throw. Use a supported schema library or a custom parser; keep authorization separate from shape validation.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { z } from 'zod'
|
|
66
|
+
|
|
67
|
+
const byIdInput = z.object({ id: z.string().min(1) })
|
|
68
|
+
|
|
69
|
+
export const itemRouter = router({
|
|
70
|
+
byId: protectedProcedure
|
|
71
|
+
.input(byIdInput)
|
|
72
|
+
.query(({ input }) => findItem(input.id)),
|
|
73
|
+
})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Validation types improve developer feedback; the runtime parser is the security boundary.
|
|
77
|
+
|
|
78
|
+
### Verify first
|
|
79
|
+
- Send malformed input directly to the transport, not only through the typed client.
|
|
80
|
+
- Confirm the resolver is not entered when parsing fails.
|
|
81
|
+
- Confirm limits and domain constraints needed by downstream services are included in the schema.
|
|
82
|
+
|
|
83
|
+
*Source: [Input parsers](https://trpc.io/docs/quickstart#3-using-input-parser-to-validate-procedure-inputs)*
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Map expected failures to `TRPCError`
|
|
88
|
+
|
|
89
|
+
### Problem / symptom
|
|
90
|
+
Expected conditions are thrown as generic errors, producing misleading `INTERNAL_SERVER_ERROR` responses; or detailed internal failures and stacks reach consumers.
|
|
91
|
+
|
|
92
|
+
### Fix
|
|
93
|
+
Throw `TRPCError` for expected API outcomes and use its documented code to express the condition. Keep unexpected errors as internal failures, observe them in the adapter's `onError`, and expose a safe message. tRPC maps its error codes to HTTP statuses and omits stack traces by default in production.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
if (!item) {
|
|
97
|
+
throw new TRPCError({ code: 'NOT_FOUND', message: 'Item not found' })
|
|
98
|
+
}
|
|
99
|
+
if (!canEdit(ctx.user, item)) {
|
|
100
|
+
throw new TRPCError({ code: 'FORBIDDEN' })
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use `errorFormatter` only when the client needs a deliberate, safe extension to the error shape. Avoid returning raw database, provider, or stack details.
|
|
105
|
+
|
|
106
|
+
### Verify first
|
|
107
|
+
- Assert `UNAUTHORIZED`, `FORBIDDEN`, validation, and missing-resource paths separately.
|
|
108
|
+
- Check the production error response has no stack trace or confidential cause.
|
|
109
|
+
- Confirm `onError` records path, operation type, and a safe correlation identifier without logging secrets.
|
|
110
|
+
|
|
111
|
+
*Source: [Error codes and error handling](https://trpc.io/docs/server/error-handling)*
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Preserve server-to-client type inference
|
|
116
|
+
|
|
117
|
+
### Problem / symptom
|
|
118
|
+
The client duplicates request/response interfaces, imports server runtime code, or loses autocomplete after routers are split.
|
|
119
|
+
|
|
120
|
+
### Fix
|
|
121
|
+
Export the root router value and its type from the server boundary. Import `AppRouter` with `import type` on the client and give it to the tRPC client or integration. Derive any shared input/output type with `inferRouterInputs` and `inferRouterOutputs` rather than handwritten duplicates.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// server/app-router.ts
|
|
125
|
+
export const appRouter = router({ item: itemRouter })
|
|
126
|
+
export type AppRouter = typeof appRouter
|
|
127
|
+
|
|
128
|
+
// client/trpc.ts
|
|
129
|
+
import { createTRPCClient } from '@trpc/client'
|
|
130
|
+
import type { AppRouter } from '../server/app-router'
|
|
131
|
+
|
|
132
|
+
export const trpc = createTRPCClient<AppRouter>({ links: [] })
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Treat the router type as a compile-time contract, not runtime data. The client must still handle transport, authorization, and server validation failures.
|
|
136
|
+
|
|
137
|
+
### Verify first
|
|
138
|
+
- Type-check a valid and invalid procedure call at the client boundary.
|
|
139
|
+
- Confirm the router is exported as `typeof appRouter`, not a separately maintained interface.
|
|
140
|
+
- Confirm the cross-boundary import is type-only and does not bundle server-only runtime dependencies.
|
|
141
|
+
|
|
142
|
+
*Source: [Quickstart: client type inference](https://trpc.io/docs/quickstart#2-type-inference--autocomplete) · [Inferring types](https://trpc.io/docs/client/vanilla/infer-types)*
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Organize routers around reusable primitives and modules
|
|
147
|
+
|
|
148
|
+
### Problem / symptom
|
|
149
|
+
One large router mixes initialization, transport setup, authorization logic, and unrelated procedures. Changes create circular imports or inconsistent procedure behavior.
|
|
150
|
+
|
|
151
|
+
### Fix
|
|
152
|
+
Initialize tRPC once in a dedicated module and export small primitives such as `router`, `publicProcedure`, and `protectedProcedure`. Define feature routers in separate modules, compose them into an `appRouter`, and mount that root router in the transport adapter. This keeps common behavior in base procedures and procedure ownership in feature modules.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// trpc.ts: initialization and reusable base procedures
|
|
156
|
+
// item-router.ts: item procedures
|
|
157
|
+
// app-router.ts: router({ item: itemRouter }) and AppRouter export
|
|
158
|
+
// http.ts: adapter, appRouter, createContext, onError
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Verify first
|
|
162
|
+
- Confirm there is a single `initTRPC...create()` initialization per server application.
|
|
163
|
+
- Confirm router composition does not import the HTTP adapter back into feature routers.
|
|
164
|
+
- Confirm common authentication behavior is inherited through base procedures, not copied per resolver.
|
|
165
|
+
|
|
166
|
+
*Source: [Quickstart: recommended separation](https://trpc.io/docs/quickstart#installation) · [Reusable base procedures](https://trpc.io/docs/server/procedures#reusable-base-procedures)*
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Make batching and transport observable
|
|
171
|
+
|
|
172
|
+
### Problem / symptom
|
|
173
|
+
Requests appear slow or fail with URI/payload errors, but telemetry only shows an HTTP request and cannot identify the individual operations hidden inside a batch.
|
|
174
|
+
|
|
175
|
+
### Fix
|
|
176
|
+
`httpBatchLink` batches individual operations into one HTTP request. Instrument both layers: record operation path/type/duration/errors through tRPC error handling or links, and record HTTP status, batch size, payload/URL limits, and request timing at the transport boundary. Set compatible limits deliberately.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
httpBatchLink({
|
|
180
|
+
url: '/trpc',
|
|
181
|
+
maxItems: 10,
|
|
182
|
+
maxURLLength: 2083,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Configure the adapter with a matching or higher maxBatchSize.
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
A client `maxItems` value at or below the server `maxBatchSize` splits large batches before the server rejects them. If per-operation transport isolation is required, use `httpLink` instead of batch transport.
|
|
189
|
+
|
|
190
|
+
### Verify first
|
|
191
|
+
- Use concurrent calls and confirm the expected number of HTTP requests and operations per batch.
|
|
192
|
+
- Exercise a batch above the configured limit; verify the client splits it rather than receiving a server rejection.
|
|
193
|
+
- Inspect observability data for a single failed operation in a mixed batch.
|
|
194
|
+
|
|
195
|
+
*Source: [HTTP batch link](https://trpc.io/docs/client/links/httpBatchLink) · [Adapter `onError`](https://trpc.io/docs/server/error-handling#handling-errors)*
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Keep tRPC packages version-aligned
|
|
200
|
+
|
|
201
|
+
### Problem / symptom
|
|
202
|
+
Client and server compile against different tRPC API generations, causing missing exports, incompatible types, or transport behavior that differs from the selected documentation.
|
|
203
|
+
|
|
204
|
+
### Fix
|
|
205
|
+
Review every installed `@trpc/*` package used by the server and client together during an upgrade. Keep their versions compatible with the same tRPC documentation generation, update lockfiles in the same change, and run type-checking across both boundaries. The current official documentation cited here is v11.
|
|
206
|
+
|
|
207
|
+
### Verify first
|
|
208
|
+
- List resolved versions of `@trpc/server`, `@trpc/client`, and any installed framework integration packages.
|
|
209
|
+
- Confirm no duplicate incompatible tRPC versions are resolved where a shared type contract is expected.
|
|
210
|
+
- Type-check server and client after dependency resolution, then exercise one query and one mutation over the configured transport.
|
|
211
|
+
|
|
212
|
+
*Source: [tRPC v11 Quickstart](https://trpc.io/docs/quickstart) · [tRPC documentation](https://trpc.io/docs)*
|