@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,419 @@
|
|
|
1
|
+
# Fastify Framework Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Fastify is a high-performance, low-overhead web framework for Node.js. This skill provides patterns for implementing APIs with Fastify.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## App Setup
|
|
10
|
+
|
|
11
|
+
**Pattern**: Plugin-based with type provider
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import Fastify from 'fastify';
|
|
15
|
+
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
|
|
16
|
+
import cors from '@fastify/cors';
|
|
17
|
+
import helmet from '@fastify/helmet';
|
|
18
|
+
import { databasePlugin } from './plugins/database';
|
|
19
|
+
import { authPlugin } from './plugins/auth';
|
|
20
|
+
import { itemRoutes } from './routes/items';
|
|
21
|
+
import { errorHandler } from './handlers/error';
|
|
22
|
+
|
|
23
|
+
export async function buildApp() {
|
|
24
|
+
const app = Fastify({
|
|
25
|
+
logger: true,
|
|
26
|
+
}).withTypeProvider<TypeBoxTypeProvider>();
|
|
27
|
+
|
|
28
|
+
// Register plugins
|
|
29
|
+
await app.register(helmet);
|
|
30
|
+
await app.register(cors);
|
|
31
|
+
await app.register(databasePlugin);
|
|
32
|
+
await app.register(authPlugin);
|
|
33
|
+
|
|
34
|
+
// Register routes
|
|
35
|
+
await app.register(itemRoutes, { prefix: '/api/v1/items' });
|
|
36
|
+
|
|
37
|
+
// Error handler
|
|
38
|
+
app.setErrorHandler(errorHandler);
|
|
39
|
+
|
|
40
|
+
return app;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Route Definition with TypeBox
|
|
47
|
+
|
|
48
|
+
### Schema-First Routes
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { Type } from '@sinclair/typebox';
|
|
52
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
53
|
+
import { ItemsService } from '../services/items.service';
|
|
54
|
+
|
|
55
|
+
// Schema definitions
|
|
56
|
+
const ItemSchema = Type.Object({
|
|
57
|
+
id: Type.String(),
|
|
58
|
+
title: Type.String(),
|
|
59
|
+
description: Type.Optional(Type.String()),
|
|
60
|
+
createdAt: Type.String(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const CreateItemSchema = Type.Object({
|
|
64
|
+
title: Type.String({ minLength: 1 }),
|
|
65
|
+
description: Type.Optional(Type.String()),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const UpdateItemSchema = Type.Partial(CreateItemSchema);
|
|
69
|
+
|
|
70
|
+
const IdParamSchema = Type.Object({
|
|
71
|
+
id: Type.String(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Route plugin
|
|
75
|
+
export const itemRoutes: FastifyPluginAsync = async (fastify) => {
|
|
76
|
+
const service = new ItemsService(fastify.db);
|
|
77
|
+
|
|
78
|
+
// GET /items
|
|
79
|
+
fastify.get('/', {
|
|
80
|
+
schema: {
|
|
81
|
+
response: {
|
|
82
|
+
200: Type.Object({
|
|
83
|
+
data: Type.Array(ItemSchema),
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
handler: async (request, reply) => {
|
|
88
|
+
const items = await service.findAll();
|
|
89
|
+
return { data: items };
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// GET /items/:id
|
|
94
|
+
fastify.get<{ Params: { id: string } }>('/:id', {
|
|
95
|
+
schema: {
|
|
96
|
+
params: IdParamSchema,
|
|
97
|
+
response: {
|
|
98
|
+
200: Type.Object({ data: ItemSchema }),
|
|
99
|
+
404: Type.Object({ error: Type.String() }),
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
handler: async (request, reply) => {
|
|
103
|
+
const item = await service.findById(request.params.id);
|
|
104
|
+
if (!item) {
|
|
105
|
+
reply.status(404);
|
|
106
|
+
return { error: 'Item not found' };
|
|
107
|
+
}
|
|
108
|
+
return { data: item };
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// POST /items
|
|
113
|
+
fastify.post('/', {
|
|
114
|
+
schema: {
|
|
115
|
+
body: CreateItemSchema,
|
|
116
|
+
response: {
|
|
117
|
+
201: Type.Object({ data: ItemSchema }),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
preHandler: fastify.authenticate,
|
|
121
|
+
handler: async (request, reply) => {
|
|
122
|
+
const item = await service.create(request.body);
|
|
123
|
+
reply.status(201);
|
|
124
|
+
return { data: item };
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// PUT /items/:id
|
|
129
|
+
fastify.put<{ Params: { id: string } }>('/:id', {
|
|
130
|
+
schema: {
|
|
131
|
+
params: IdParamSchema,
|
|
132
|
+
body: UpdateItemSchema,
|
|
133
|
+
response: {
|
|
134
|
+
200: Type.Object({ data: ItemSchema }),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
preHandler: fastify.authenticate,
|
|
138
|
+
handler: async (request, reply) => {
|
|
139
|
+
const item = await service.update(request.params.id, request.body);
|
|
140
|
+
return { data: item };
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// DELETE /items/:id
|
|
145
|
+
fastify.delete<{ Params: { id: string } }>('/:id', {
|
|
146
|
+
schema: {
|
|
147
|
+
params: IdParamSchema,
|
|
148
|
+
},
|
|
149
|
+
preHandler: fastify.authenticate,
|
|
150
|
+
handler: async (request, reply) => {
|
|
151
|
+
await service.delete(request.params.id);
|
|
152
|
+
reply.status(204).send();
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Plugin Patterns
|
|
161
|
+
|
|
162
|
+
### Database Plugin
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import fp from 'fastify-plugin';
|
|
166
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
167
|
+
|
|
168
|
+
interface DatabasePluginOptions {
|
|
169
|
+
connectionString?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
declare module 'fastify' {
|
|
173
|
+
interface FastifyInstance {
|
|
174
|
+
db: Database;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const plugin: FastifyPluginAsync<DatabasePluginOptions> = async (
|
|
179
|
+
fastify,
|
|
180
|
+
options
|
|
181
|
+
) => {
|
|
182
|
+
const connectionString = options.connectionString || process.env.DATABASE_URL;
|
|
183
|
+
const db = await createDatabaseConnection(connectionString);
|
|
184
|
+
|
|
185
|
+
// Decorate fastify instance
|
|
186
|
+
fastify.decorate('db', db);
|
|
187
|
+
|
|
188
|
+
// Cleanup on close
|
|
189
|
+
fastify.addHook('onClose', async () => {
|
|
190
|
+
await db.disconnect();
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const databasePlugin = fp(plugin, {
|
|
195
|
+
name: 'database',
|
|
196
|
+
dependencies: [],
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Authentication Plugin
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import fp from 'fastify-plugin';
|
|
204
|
+
import type { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
|
|
205
|
+
|
|
206
|
+
declare module 'fastify' {
|
|
207
|
+
interface FastifyInstance {
|
|
208
|
+
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
209
|
+
}
|
|
210
|
+
interface FastifyRequest {
|
|
211
|
+
user?: User;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const plugin: FastifyPluginAsync = async (fastify) => {
|
|
216
|
+
fastify.decorate('authenticate', async (request: FastifyRequest, reply: FastifyReply) => {
|
|
217
|
+
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
218
|
+
|
|
219
|
+
if (!token) {
|
|
220
|
+
reply.status(401).send({ error: 'Unauthorized' });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const user = await verifyToken(token);
|
|
225
|
+
if (!user) {
|
|
226
|
+
reply.status(401).send({ error: 'Invalid token' });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
request.user = user;
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const authPlugin = fp(plugin, {
|
|
235
|
+
name: 'auth',
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Error Handler
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
|
|
245
|
+
|
|
246
|
+
export function errorHandler(
|
|
247
|
+
error: FastifyError,
|
|
248
|
+
request: FastifyRequest,
|
|
249
|
+
reply: FastifyReply
|
|
250
|
+
) {
|
|
251
|
+
const statusCode = error.statusCode ?? 500;
|
|
252
|
+
|
|
253
|
+
// Log server errors
|
|
254
|
+
if (statusCode >= 500) {
|
|
255
|
+
request.log.error(error);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Validation errors
|
|
259
|
+
if (error.validation) {
|
|
260
|
+
return reply.status(400).send({
|
|
261
|
+
error: {
|
|
262
|
+
message: 'Validation failed',
|
|
263
|
+
code: 'VALIDATION_ERROR',
|
|
264
|
+
details: error.validation,
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Known errors
|
|
270
|
+
reply.status(statusCode).send({
|
|
271
|
+
error: {
|
|
272
|
+
message: error.message,
|
|
273
|
+
code: error.code ?? 'INTERNAL_ERROR',
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Hooks and Lifecycle
|
|
282
|
+
|
|
283
|
+
| Hook | When it runs | Use case |
|
|
284
|
+
|------|--------------|----------|
|
|
285
|
+
| onRequest | Start of request | Request ID, timing |
|
|
286
|
+
| preParsing | Before body parsing | Stream manipulation |
|
|
287
|
+
| preValidation | Before validation | Transform before validate |
|
|
288
|
+
| preHandler | After validation | Authorization |
|
|
289
|
+
| preSerialization | Before response serialization | Response transformation |
|
|
290
|
+
| onSend | Before response sent | Headers, logging |
|
|
291
|
+
| onResponse | After response sent | Metrics, cleanup |
|
|
292
|
+
| onError | On error | Error handling |
|
|
293
|
+
|
|
294
|
+
### Example Hook Usage
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Add request ID
|
|
298
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
299
|
+
request.requestId = crypto.randomUUID();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Log response time
|
|
303
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
304
|
+
request.log.info({
|
|
305
|
+
responseTime: reply.elapsedTime,
|
|
306
|
+
statusCode: reply.statusCode,
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Testing with Fastify Inject
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
317
|
+
import { buildApp } from '../app';
|
|
318
|
+
import type { FastifyInstance } from 'fastify';
|
|
319
|
+
|
|
320
|
+
describe('Item Routes', () => {
|
|
321
|
+
let app: FastifyInstance;
|
|
322
|
+
|
|
323
|
+
beforeAll(async () => {
|
|
324
|
+
app = await buildApp();
|
|
325
|
+
await app.ready();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
afterAll(async () => {
|
|
329
|
+
await app.close();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('GET /api/v1/items', () => {
|
|
333
|
+
it('should return all items', async () => {
|
|
334
|
+
const response = await app.inject({
|
|
335
|
+
method: 'GET',
|
|
336
|
+
url: '/api/v1/items',
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
expect(response.statusCode).toBe(200);
|
|
340
|
+
const body = response.json();
|
|
341
|
+
expect(body.data).toBeInstanceOf(Array);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
describe('POST /api/v1/items', () => {
|
|
346
|
+
it('should create item with valid data', async () => {
|
|
347
|
+
const response = await app.inject({
|
|
348
|
+
method: 'POST',
|
|
349
|
+
url: '/api/v1/items',
|
|
350
|
+
headers: {
|
|
351
|
+
authorization: 'Bearer valid-token',
|
|
352
|
+
},
|
|
353
|
+
payload: { title: 'Test Item' },
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(response.statusCode).toBe(201);
|
|
357
|
+
expect(response.json().data).toHaveProperty('id');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should return 400 with invalid data', async () => {
|
|
361
|
+
const response = await app.inject({
|
|
362
|
+
method: 'POST',
|
|
363
|
+
url: '/api/v1/items',
|
|
364
|
+
headers: {
|
|
365
|
+
authorization: 'Bearer valid-token',
|
|
366
|
+
},
|
|
367
|
+
payload: { title: '' },
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
expect(response.statusCode).toBe(400);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Project Structure
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
{API_PATH}/
|
|
382
|
+
├── plugins/
|
|
383
|
+
│ ├── database.ts # Database connection
|
|
384
|
+
│ ├── auth.ts # Authentication
|
|
385
|
+
│ └── swagger.ts # API documentation
|
|
386
|
+
├── routes/
|
|
387
|
+
│ ├── items/
|
|
388
|
+
│ │ ├── index.ts # Route registration
|
|
389
|
+
│ │ ├── handlers.ts # Route handlers
|
|
390
|
+
│ │ └── schemas.ts # TypeBox schemas
|
|
391
|
+
│ └── users/
|
|
392
|
+
├── services/
|
|
393
|
+
│ └── items.service.ts
|
|
394
|
+
├── handlers/
|
|
395
|
+
│ └── error.ts # Error handler
|
|
396
|
+
├── types/
|
|
397
|
+
│ └── fastify.d.ts # Type augmentations
|
|
398
|
+
└── app.ts
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Best Practices
|
|
404
|
+
|
|
405
|
+
### Good
|
|
406
|
+
|
|
407
|
+
- Use TypeBox or Zod type providers
|
|
408
|
+
- Use `fastify-plugin` for shared plugins
|
|
409
|
+
- Define schemas for all routes (validation + serialization)
|
|
410
|
+
- Use Pino logger (built-in)
|
|
411
|
+
- Use encapsulation for route-specific context
|
|
412
|
+
|
|
413
|
+
### Bad
|
|
414
|
+
|
|
415
|
+
- No schemas (lose validation and serialization)
|
|
416
|
+
- `console.log` (Fastify has Pino built-in)
|
|
417
|
+
- Breaking encapsulation unnecessarily
|
|
418
|
+
- Ignoring type providers
|
|
419
|
+
- Blocking operations in handlers
|