@qazuor/claude-code-config 0.5.0 → 0.6.0
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 +106 -41
- package/dist/bin.cjs +963 -84
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +963 -84
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +73 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +73 -56
- package/dist/index.js.map +1 -1
- package/package.json +23 -24
- package/templates/CLAUDE.md.template +60 -5
- package/templates/agents/README.md +58 -39
- package/templates/agents/_registry.json +43 -202
- package/templates/agents/engineering/{hono-engineer.md → api-engineer.md} +61 -70
- package/templates/agents/engineering/database-engineer.md +253 -0
- package/templates/agents/engineering/frontend-engineer.md +302 -0
- package/templates/hooks/on-notification.sh +0 -0
- package/templates/scripts/add-changelogs.sh +0 -0
- package/templates/scripts/generate-code-registry.ts +0 -0
- package/templates/scripts/health-check.sh +0 -0
- package/templates/scripts/sync-registry.sh +0 -0
- package/templates/scripts/telemetry-report.ts +0 -0
- package/templates/scripts/validate-docs.sh +0 -0
- package/templates/scripts/validate-registry.sh +0 -0
- package/templates/scripts/validate-structure.sh +0 -0
- package/templates/scripts/worktree-cleanup.sh +0 -0
- package/templates/scripts/worktree-create.sh +0 -0
- package/templates/skills/README.md +99 -90
- package/templates/skills/_registry.json +323 -16
- package/templates/skills/api-frameworks/express-patterns.md +411 -0
- package/templates/skills/api-frameworks/fastify-patterns.md +419 -0
- package/templates/skills/api-frameworks/hono-patterns.md +388 -0
- package/templates/skills/api-frameworks/nestjs-patterns.md +497 -0
- package/templates/skills/database/drizzle-patterns.md +449 -0
- package/templates/skills/database/mongoose-patterns.md +503 -0
- package/templates/skills/database/prisma-patterns.md +487 -0
- package/templates/skills/frontend-frameworks/astro-patterns.md +415 -0
- package/templates/skills/frontend-frameworks/nextjs-patterns.md +470 -0
- package/templates/skills/frontend-frameworks/react-patterns.md +516 -0
- package/templates/skills/frontend-frameworks/tanstack-start-patterns.md +469 -0
- package/templates/skills/patterns/atdd-methodology.md +364 -0
- package/templates/skills/patterns/bdd-methodology.md +281 -0
- package/templates/skills/patterns/clean-architecture.md +444 -0
- package/templates/skills/patterns/hexagonal-architecture.md +567 -0
- package/templates/skills/patterns/vertical-slice-architecture.md +502 -0
- package/templates/agents/engineering/astro-engineer.md +0 -293
- package/templates/agents/engineering/db-drizzle-engineer.md +0 -360
- package/templates/agents/engineering/express-engineer.md +0 -316
- package/templates/agents/engineering/fastify-engineer.md +0 -399
- package/templates/agents/engineering/mongoose-engineer.md +0 -473
- package/templates/agents/engineering/nestjs-engineer.md +0 -429
- package/templates/agents/engineering/nextjs-engineer.md +0 -451
- package/templates/agents/engineering/prisma-engineer.md +0 -432
- package/templates/agents/engineering/react-senior-dev.md +0 -394
- package/templates/agents/engineering/tanstack-start-engineer.md +0 -447
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# TanStack Start Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
TanStack Start is a full-stack React framework with file-based routing and server functions. This skill provides patterns for building TanStack Start applications.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Route Definition
|
|
10
|
+
|
|
11
|
+
### Basic Route
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// app/routes/index.tsx
|
|
15
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
16
|
+
|
|
17
|
+
export const Route = createFileRoute('/')({
|
|
18
|
+
component: HomePage,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function HomePage() {
|
|
22
|
+
return (
|
|
23
|
+
<main className="container py-8">
|
|
24
|
+
<h1>Welcome</h1>
|
|
25
|
+
<p>This is the home page.</p>
|
|
26
|
+
</main>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Route with Loader
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// app/routes/items/index.tsx
|
|
35
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
36
|
+
import { getItems } from '@/lib/api';
|
|
37
|
+
|
|
38
|
+
export const Route = createFileRoute('/items/')({
|
|
39
|
+
loader: async () => {
|
|
40
|
+
const items = await getItems();
|
|
41
|
+
return { items };
|
|
42
|
+
},
|
|
43
|
+
component: ItemsPage,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function ItemsPage() {
|
|
47
|
+
const { items } = Route.useLoaderData();
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<main>
|
|
51
|
+
<h1>Items</h1>
|
|
52
|
+
<ul>
|
|
53
|
+
{items.map((item) => (
|
|
54
|
+
<li key={item.id}>{item.title}</li>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
</main>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Dynamic Route
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// app/routes/items/$itemId.tsx
|
|
66
|
+
import { createFileRoute, notFound } from '@tanstack/react-router';
|
|
67
|
+
import { getItem } from '@/lib/api';
|
|
68
|
+
|
|
69
|
+
export const Route = createFileRoute('/items/$itemId')({
|
|
70
|
+
loader: async ({ params }) => {
|
|
71
|
+
const item = await getItem(params.itemId);
|
|
72
|
+
|
|
73
|
+
if (!item) {
|
|
74
|
+
throw notFound();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { item };
|
|
78
|
+
},
|
|
79
|
+
component: ItemPage,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function ItemPage() {
|
|
83
|
+
const { item } = Route.useLoaderData();
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<article>
|
|
87
|
+
<h1>{item.title}</h1>
|
|
88
|
+
<p>{item.description}</p>
|
|
89
|
+
<p>Price: ${item.price}</p>
|
|
90
|
+
</article>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Server Functions
|
|
98
|
+
|
|
99
|
+
### Basic Server Function
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// app/lib/server-fns.ts
|
|
103
|
+
import { createServerFn } from '@tanstack/start';
|
|
104
|
+
import { db } from './db';
|
|
105
|
+
|
|
106
|
+
export const getItems = createServerFn('GET', async () => {
|
|
107
|
+
const items = await db.item.findMany({
|
|
108
|
+
where: { status: 'active' },
|
|
109
|
+
orderBy: { createdAt: 'desc' },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return items;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export const getItem = createServerFn('GET', async (id: string) => {
|
|
116
|
+
const item = await db.item.findUnique({
|
|
117
|
+
where: { id },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return item;
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Mutation Server Function
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// app/lib/server-fns.ts
|
|
128
|
+
import { createServerFn } from '@tanstack/start';
|
|
129
|
+
import { z } from 'zod';
|
|
130
|
+
import { db } from './db';
|
|
131
|
+
import { getSession } from './auth';
|
|
132
|
+
|
|
133
|
+
const createItemSchema = z.object({
|
|
134
|
+
title: z.string().min(1),
|
|
135
|
+
description: z.string().optional(),
|
|
136
|
+
price: z.number().positive(),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export const createItem = createServerFn('POST', async (input: unknown) => {
|
|
140
|
+
const session = await getSession();
|
|
141
|
+
if (!session?.user) {
|
|
142
|
+
throw new Error('Unauthorized');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const data = createItemSchema.parse(input);
|
|
146
|
+
|
|
147
|
+
const item = await db.item.create({
|
|
148
|
+
data: {
|
|
149
|
+
...data,
|
|
150
|
+
authorId: session.user.id,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return item;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export const updateItem = createServerFn(
|
|
158
|
+
'POST',
|
|
159
|
+
async ({ id, data }: { id: string; data: unknown }) => {
|
|
160
|
+
const session = await getSession();
|
|
161
|
+
if (!session?.user) {
|
|
162
|
+
throw new Error('Unauthorized');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const parsed = createItemSchema.partial().parse(data);
|
|
166
|
+
|
|
167
|
+
const item = await db.item.update({
|
|
168
|
+
where: { id, authorId: session.user.id },
|
|
169
|
+
data: parsed,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return item;
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
export const deleteItem = createServerFn('POST', async (id: string) => {
|
|
177
|
+
const session = await getSession();
|
|
178
|
+
if (!session?.user) {
|
|
179
|
+
throw new Error('Unauthorized');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await db.item.delete({
|
|
183
|
+
where: { id, authorId: session.user.id },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return { success: true };
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Using Server Functions in Components
|
|
193
|
+
|
|
194
|
+
### With TanStack Query
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// app/routes/items/index.tsx
|
|
198
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
199
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
200
|
+
import { getItems, createItem, deleteItem } from '@/lib/server-fns';
|
|
201
|
+
|
|
202
|
+
export const Route = createFileRoute('/items/')({
|
|
203
|
+
loader: () => getItems(),
|
|
204
|
+
component: ItemsPage,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
function ItemsPage() {
|
|
208
|
+
const items = Route.useLoaderData();
|
|
209
|
+
const queryClient = useQueryClient();
|
|
210
|
+
|
|
211
|
+
const createMutation = useMutation({
|
|
212
|
+
mutationFn: createItem,
|
|
213
|
+
onSuccess: () => {
|
|
214
|
+
queryClient.invalidateQueries({ queryKey: ['items'] });
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const deleteMutation = useMutation({
|
|
219
|
+
mutationFn: deleteItem,
|
|
220
|
+
onSuccess: () => {
|
|
221
|
+
queryClient.invalidateQueries({ queryKey: ['items'] });
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<main>
|
|
227
|
+
<h1>Items</h1>
|
|
228
|
+
|
|
229
|
+
<form
|
|
230
|
+
onSubmit={(e) => {
|
|
231
|
+
e.preventDefault();
|
|
232
|
+
const formData = new FormData(e.currentTarget);
|
|
233
|
+
createMutation.mutate({
|
|
234
|
+
title: formData.get('title') as string,
|
|
235
|
+
price: Number(formData.get('price')),
|
|
236
|
+
});
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
<input name="title" placeholder="Title" required />
|
|
240
|
+
<input name="price" type="number" placeholder="Price" required />
|
|
241
|
+
<button type="submit" disabled={createMutation.isPending}>
|
|
242
|
+
Create
|
|
243
|
+
</button>
|
|
244
|
+
</form>
|
|
245
|
+
|
|
246
|
+
<ul>
|
|
247
|
+
{items.map((item) => (
|
|
248
|
+
<li key={item.id}>
|
|
249
|
+
{item.title} - ${item.price}
|
|
250
|
+
<button
|
|
251
|
+
onClick={() => deleteMutation.mutate(item.id)}
|
|
252
|
+
disabled={deleteMutation.isPending}
|
|
253
|
+
>
|
|
254
|
+
Delete
|
|
255
|
+
</button>
|
|
256
|
+
</li>
|
|
257
|
+
))}
|
|
258
|
+
</ul>
|
|
259
|
+
</main>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Layouts
|
|
267
|
+
|
|
268
|
+
### Root Layout
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// app/routes/__root.tsx
|
|
272
|
+
import { createRootRoute, Outlet } from '@tanstack/react-router';
|
|
273
|
+
import { Header } from '@/components/Header';
|
|
274
|
+
import { Footer } from '@/components/Footer';
|
|
275
|
+
|
|
276
|
+
export const Route = createRootRoute({
|
|
277
|
+
component: RootLayout,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
function RootLayout() {
|
|
281
|
+
return (
|
|
282
|
+
<>
|
|
283
|
+
<Header />
|
|
284
|
+
<Outlet />
|
|
285
|
+
<Footer />
|
|
286
|
+
</>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Nested Layout
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// app/routes/dashboard/_layout.tsx
|
|
295
|
+
import { createFileRoute, Outlet } from '@tanstack/react-router';
|
|
296
|
+
import { Sidebar } from '@/components/Sidebar';
|
|
297
|
+
|
|
298
|
+
export const Route = createFileRoute('/dashboard/_layout')({
|
|
299
|
+
component: DashboardLayout,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
function DashboardLayout() {
|
|
303
|
+
return (
|
|
304
|
+
<div className="flex">
|
|
305
|
+
<Sidebar />
|
|
306
|
+
<main className="flex-1">
|
|
307
|
+
<Outlet />
|
|
308
|
+
</main>
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Error Handling
|
|
317
|
+
|
|
318
|
+
### Route Error Boundary
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// app/routes/items/$itemId.tsx
|
|
322
|
+
import { createFileRoute, notFound, ErrorComponent } from '@tanstack/react-router';
|
|
323
|
+
|
|
324
|
+
export const Route = createFileRoute('/items/$itemId')({
|
|
325
|
+
loader: async ({ params }) => {
|
|
326
|
+
const item = await getItem(params.itemId);
|
|
327
|
+
|
|
328
|
+
if (!item) {
|
|
329
|
+
throw notFound();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return { item };
|
|
333
|
+
},
|
|
334
|
+
errorComponent: ItemErrorBoundary,
|
|
335
|
+
notFoundComponent: ItemNotFound,
|
|
336
|
+
component: ItemPage,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
function ItemErrorBoundary({ error }: { error: Error }) {
|
|
340
|
+
return (
|
|
341
|
+
<div className="p-4 border border-red-500 rounded">
|
|
342
|
+
<h2>Error loading item</h2>
|
|
343
|
+
<p>{error.message}</p>
|
|
344
|
+
</div>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function ItemNotFound() {
|
|
349
|
+
return (
|
|
350
|
+
<div className="p-4">
|
|
351
|
+
<h2>Item not found</h2>
|
|
352
|
+
<p>The item you're looking for doesn't exist.</p>
|
|
353
|
+
</div>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Search Params
|
|
361
|
+
|
|
362
|
+
### Route with Search Params
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// app/routes/items/index.tsx
|
|
366
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
367
|
+
import { z } from 'zod';
|
|
368
|
+
|
|
369
|
+
const itemsSearchSchema = z.object({
|
|
370
|
+
q: z.string().optional(),
|
|
371
|
+
page: z.number().default(1),
|
|
372
|
+
status: z.enum(['active', 'archived']).optional(),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
export const Route = createFileRoute('/items/')({
|
|
376
|
+
validateSearch: itemsSearchSchema,
|
|
377
|
+
loaderDeps: ({ search }) => ({ search }),
|
|
378
|
+
loader: async ({ deps }) => {
|
|
379
|
+
const items = await getItems(deps.search);
|
|
380
|
+
return { items };
|
|
381
|
+
},
|
|
382
|
+
component: ItemsPage,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
function ItemsPage() {
|
|
386
|
+
const { items } = Route.useLoaderData();
|
|
387
|
+
const search = Route.useSearch();
|
|
388
|
+
const navigate = Route.useNavigate();
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
<main>
|
|
392
|
+
<input
|
|
393
|
+
value={search.q || ''}
|
|
394
|
+
onChange={(e) =>
|
|
395
|
+
navigate({ search: { ...search, q: e.target.value } })
|
|
396
|
+
}
|
|
397
|
+
placeholder="Search..."
|
|
398
|
+
/>
|
|
399
|
+
|
|
400
|
+
<select
|
|
401
|
+
value={search.status || ''}
|
|
402
|
+
onChange={(e) =>
|
|
403
|
+
navigate({
|
|
404
|
+
search: { ...search, status: e.target.value || undefined },
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
>
|
|
408
|
+
<option value="">All</option>
|
|
409
|
+
<option value="active">Active</option>
|
|
410
|
+
<option value="archived">Archived</option>
|
|
411
|
+
</select>
|
|
412
|
+
|
|
413
|
+
<ul>
|
|
414
|
+
{items.map((item) => (
|
|
415
|
+
<li key={item.id}>{item.title}</li>
|
|
416
|
+
))}
|
|
417
|
+
</ul>
|
|
418
|
+
</main>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Project Structure
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
app/
|
|
429
|
+
├── routes/
|
|
430
|
+
│ ├── __root.tsx # Root layout
|
|
431
|
+
│ ├── index.tsx # Home page
|
|
432
|
+
│ ├── about.tsx
|
|
433
|
+
│ ├── items/
|
|
434
|
+
│ │ ├── index.tsx # /items
|
|
435
|
+
│ │ └── $itemId.tsx # /items/:itemId
|
|
436
|
+
│ └── dashboard/
|
|
437
|
+
│ ├── _layout.tsx # Dashboard layout
|
|
438
|
+
│ └── index.tsx # /dashboard
|
|
439
|
+
├── components/
|
|
440
|
+
│ ├── Header.tsx
|
|
441
|
+
│ ├── Footer.tsx
|
|
442
|
+
│ └── Sidebar.tsx
|
|
443
|
+
├── lib/
|
|
444
|
+
│ ├── server-fns.ts # Server functions
|
|
445
|
+
│ ├── db.ts # Database client
|
|
446
|
+
│ └── auth.ts # Authentication
|
|
447
|
+
└── styles/
|
|
448
|
+
└── global.css
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Best Practices
|
|
454
|
+
|
|
455
|
+
### Good
|
|
456
|
+
|
|
457
|
+
- Use loaders for data fetching
|
|
458
|
+
- Use server functions for mutations
|
|
459
|
+
- Use search params validation with Zod
|
|
460
|
+
- Use TanStack Query for client-side caching
|
|
461
|
+
- Use error and notFound components
|
|
462
|
+
|
|
463
|
+
### Bad
|
|
464
|
+
|
|
465
|
+
- Client-side fetching when loader works
|
|
466
|
+
- Not validating search params
|
|
467
|
+
- Ignoring error boundaries
|
|
468
|
+
- Not using server functions for sensitive operations
|
|
469
|
+
- Putting business logic in components
|