@ranimontagna/agent-toolkit 0.1.4 → 0.1.5
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 +282 -277
- package/docs/assets/install-plan.svg +29 -0
- package/docs/assets/install-skill-packages.svg +31 -0
- package/docs/assets/install-status.svg +32 -0
- package/package.json +10 -9
- package/setup-agent-toolkit.sh +1 -1
- package/skills/backend/fastify-best-practices/LICENSE +21 -0
- package/skills/backend/fastify-best-practices/NOTICE.md +11 -0
- package/skills/backend/fastify-best-practices/SKILL.md +75 -0
- package/skills/backend/fastify-best-practices/rules/authentication.md +521 -0
- package/skills/backend/fastify-best-practices/rules/configuration.md +217 -0
- package/skills/backend/fastify-best-practices/rules/content-type.md +387 -0
- package/skills/backend/fastify-best-practices/rules/cors-security.md +445 -0
- package/skills/backend/fastify-best-practices/rules/database.md +320 -0
- package/skills/backend/fastify-best-practices/rules/decorators.md +416 -0
- package/skills/backend/fastify-best-practices/rules/deployment.md +423 -0
- package/skills/backend/fastify-best-practices/rules/error-handling.md +412 -0
- package/skills/backend/fastify-best-practices/rules/hooks.md +464 -0
- package/skills/backend/fastify-best-practices/rules/http-proxy.md +247 -0
- package/skills/backend/fastify-best-practices/rules/logging.md +402 -0
- package/skills/backend/fastify-best-practices/rules/performance.md +425 -0
- package/skills/backend/fastify-best-practices/rules/plugins.md +320 -0
- package/skills/backend/fastify-best-practices/rules/routes.md +467 -0
- package/skills/backend/fastify-best-practices/rules/schemas.md +585 -0
- package/skills/backend/fastify-best-practices/rules/serialization.md +475 -0
- package/skills/backend/fastify-best-practices/rules/testing.md +536 -0
- package/skills/backend/fastify-best-practices/rules/typescript.md +458 -0
- package/skills/backend/fastify-best-practices/rules/websockets.md +421 -0
- package/skills/backend/fastify-best-practices/tile.json +11 -0
- package/skills/core/agent-toolkit-maintainer/SKILL.md +16 -14
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typescript
|
|
3
|
+
description: TypeScript integration with Fastify
|
|
4
|
+
metadata:
|
|
5
|
+
tags: typescript, types, generics, type-safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# TypeScript Integration
|
|
9
|
+
|
|
10
|
+
## Type Stripping with Node.js
|
|
11
|
+
|
|
12
|
+
Use Node.js built-in type stripping (Node.js 22.6+):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Run TypeScript directly
|
|
16
|
+
node --experimental-strip-types app.ts
|
|
17
|
+
|
|
18
|
+
# In Node.js 23+
|
|
19
|
+
node app.ts
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
// package.json
|
|
24
|
+
{
|
|
25
|
+
"type": "module",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"start": "node app.ts",
|
|
28
|
+
"dev": "node --watch app.ts"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// tsconfig.json for type stripping
|
|
35
|
+
{
|
|
36
|
+
"compilerOptions": {
|
|
37
|
+
"target": "ESNext",
|
|
38
|
+
"module": "NodeNext",
|
|
39
|
+
"moduleResolution": "NodeNext",
|
|
40
|
+
"verbatimModuleSyntax": true,
|
|
41
|
+
"erasableSyntaxOnly": true,
|
|
42
|
+
"noEmit": true,
|
|
43
|
+
"strict": true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Basic Type Safety
|
|
49
|
+
|
|
50
|
+
Type your Fastify application:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import Fastify, { type FastifyInstance, type FastifyRequest, type FastifyReply } from 'fastify';
|
|
54
|
+
|
|
55
|
+
const app: FastifyInstance = Fastify({ logger: true });
|
|
56
|
+
|
|
57
|
+
app.get('/health', async (request: FastifyRequest, reply: FastifyReply) => {
|
|
58
|
+
return { status: 'ok' };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await app.listen({ port: 3000 });
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Typing Route Handlers
|
|
65
|
+
|
|
66
|
+
Use generics to type request parts:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
70
|
+
|
|
71
|
+
interface CreateUserBody {
|
|
72
|
+
name: string;
|
|
73
|
+
email: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface UserParams {
|
|
77
|
+
id: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface UserQuery {
|
|
81
|
+
include?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Type the request with generics
|
|
85
|
+
app.post<{
|
|
86
|
+
Body: CreateUserBody;
|
|
87
|
+
}>('/users', async (request, reply) => {
|
|
88
|
+
const { name, email } = request.body; // Fully typed
|
|
89
|
+
return { name, email };
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
app.get<{
|
|
93
|
+
Params: UserParams;
|
|
94
|
+
Querystring: UserQuery;
|
|
95
|
+
}>('/users/:id', async (request) => {
|
|
96
|
+
const { id } = request.params; // string
|
|
97
|
+
const { include } = request.query; // string | undefined
|
|
98
|
+
return { id, include };
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Full route options typing
|
|
102
|
+
app.route<{
|
|
103
|
+
Params: UserParams;
|
|
104
|
+
Querystring: UserQuery;
|
|
105
|
+
Body: CreateUserBody;
|
|
106
|
+
Reply: { user: { id: string; name: string } };
|
|
107
|
+
}>({
|
|
108
|
+
method: 'PUT',
|
|
109
|
+
url: '/users/:id',
|
|
110
|
+
handler: async (request, reply) => {
|
|
111
|
+
return { user: { id: request.params.id, name: request.body.name } };
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Type Providers
|
|
117
|
+
|
|
118
|
+
Use @fastify/type-provider-typebox for runtime + compile-time safety:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import Fastify from 'fastify';
|
|
122
|
+
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
|
|
123
|
+
import { Type } from '@sinclair/typebox';
|
|
124
|
+
|
|
125
|
+
const app = Fastify().withTypeProvider<TypeBoxTypeProvider>();
|
|
126
|
+
|
|
127
|
+
const UserSchema = Type.Object({
|
|
128
|
+
id: Type.String(),
|
|
129
|
+
name: Type.String(),
|
|
130
|
+
email: Type.String({ format: 'email' }),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const CreateUserSchema = Type.Object({
|
|
134
|
+
name: Type.String({ minLength: 1 }),
|
|
135
|
+
email: Type.String({ format: 'email' }),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
app.post('/users', {
|
|
139
|
+
schema: {
|
|
140
|
+
body: CreateUserSchema,
|
|
141
|
+
response: {
|
|
142
|
+
201: UserSchema,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
}, async (request, reply) => {
|
|
146
|
+
// request.body is typed as { name: string; email: string }
|
|
147
|
+
const { name, email } = request.body;
|
|
148
|
+
|
|
149
|
+
reply.code(201);
|
|
150
|
+
return { id: 'generated', name, email };
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Typing Decorators
|
|
155
|
+
|
|
156
|
+
Extend Fastify types with declaration merging:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import Fastify from 'fastify';
|
|
160
|
+
|
|
161
|
+
// Declare types for decorators
|
|
162
|
+
declare module 'fastify' {
|
|
163
|
+
interface FastifyInstance {
|
|
164
|
+
config: {
|
|
165
|
+
port: number;
|
|
166
|
+
host: string;
|
|
167
|
+
};
|
|
168
|
+
db: Database;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface FastifyRequest {
|
|
172
|
+
user?: {
|
|
173
|
+
id: string;
|
|
174
|
+
email: string;
|
|
175
|
+
role: string;
|
|
176
|
+
};
|
|
177
|
+
startTime: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
interface FastifyReply {
|
|
181
|
+
sendSuccess: (data: unknown) => void;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const app = Fastify();
|
|
186
|
+
|
|
187
|
+
// Add decorators
|
|
188
|
+
app.decorate('config', { port: 3000, host: 'localhost' });
|
|
189
|
+
app.decorate('db', new Database());
|
|
190
|
+
|
|
191
|
+
app.decorateRequest('user', null);
|
|
192
|
+
app.decorateRequest('startTime', 0);
|
|
193
|
+
|
|
194
|
+
app.decorateReply('sendSuccess', function (data: unknown) {
|
|
195
|
+
this.send({ success: true, data });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Now fully typed
|
|
199
|
+
app.get('/profile', async (request, reply) => {
|
|
200
|
+
const user = request.user; // { id: string; email: string; role: string } | undefined
|
|
201
|
+
const config = app.config; // { port: number; host: string }
|
|
202
|
+
|
|
203
|
+
reply.sendSuccess({ user });
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Typing Plugins
|
|
208
|
+
|
|
209
|
+
Type plugin options and exports:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import fp from 'fastify-plugin';
|
|
213
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
214
|
+
|
|
215
|
+
interface DatabasePluginOptions {
|
|
216
|
+
connectionString: string;
|
|
217
|
+
poolSize?: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
declare module 'fastify' {
|
|
221
|
+
interface FastifyInstance {
|
|
222
|
+
db: {
|
|
223
|
+
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
|
|
224
|
+
close: () => Promise<void>;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const databasePlugin: FastifyPluginAsync<DatabasePluginOptions> = async (
|
|
230
|
+
fastify,
|
|
231
|
+
options,
|
|
232
|
+
) => {
|
|
233
|
+
const { connectionString, poolSize = 10 } = options;
|
|
234
|
+
|
|
235
|
+
const db = await createConnection(connectionString, poolSize);
|
|
236
|
+
|
|
237
|
+
fastify.decorate('db', {
|
|
238
|
+
query: (sql: string, params?: unknown[]) => db.query(sql, params),
|
|
239
|
+
close: () => db.end(),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
fastify.addHook('onClose', async () => {
|
|
243
|
+
await db.end();
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export default fp(databasePlugin, {
|
|
248
|
+
name: 'database',
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Typing Hooks
|
|
253
|
+
|
|
254
|
+
Type hook functions:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import type {
|
|
258
|
+
FastifyRequest,
|
|
259
|
+
FastifyReply,
|
|
260
|
+
onRequestHookHandler,
|
|
261
|
+
preHandlerHookHandler,
|
|
262
|
+
} from 'fastify';
|
|
263
|
+
|
|
264
|
+
const authHook: preHandlerHookHandler = async (
|
|
265
|
+
request: FastifyRequest,
|
|
266
|
+
reply: FastifyReply,
|
|
267
|
+
) => {
|
|
268
|
+
const token = request.headers.authorization;
|
|
269
|
+
if (!token) {
|
|
270
|
+
reply.code(401).send({ error: 'Unauthorized' });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
request.user = await verifyToken(token);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const timingHook: onRequestHookHandler = async (request) => {
|
|
277
|
+
request.startTime = Date.now();
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
app.addHook('onRequest', timingHook);
|
|
281
|
+
app.addHook('preHandler', authHook);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Typing Schema Objects
|
|
285
|
+
|
|
286
|
+
Create reusable typed schemas:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
290
|
+
|
|
291
|
+
// Define schema with const assertion for type inference
|
|
292
|
+
const userSchema = {
|
|
293
|
+
type: 'object',
|
|
294
|
+
properties: {
|
|
295
|
+
id: { type: 'string' },
|
|
296
|
+
name: { type: 'string' },
|
|
297
|
+
email: { type: 'string', format: 'email' },
|
|
298
|
+
},
|
|
299
|
+
required: ['id', 'name', 'email'],
|
|
300
|
+
} as const satisfies JSONSchema7;
|
|
301
|
+
|
|
302
|
+
// Infer TypeScript type from schema
|
|
303
|
+
type User = {
|
|
304
|
+
id: string;
|
|
305
|
+
name: string;
|
|
306
|
+
email: string;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
app.get<{ Reply: User }>('/users/:id', {
|
|
310
|
+
schema: {
|
|
311
|
+
response: {
|
|
312
|
+
200: userSchema,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
}, async (request) => {
|
|
316
|
+
return { id: '1', name: 'John', email: 'john@example.com' };
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Shared Types
|
|
321
|
+
|
|
322
|
+
Organize types in dedicated files:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// types/index.ts
|
|
326
|
+
export interface User {
|
|
327
|
+
id: string;
|
|
328
|
+
name: string;
|
|
329
|
+
email: string;
|
|
330
|
+
role: 'admin' | 'user';
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface CreateUserInput {
|
|
334
|
+
name: string;
|
|
335
|
+
email: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface PaginationQuery {
|
|
339
|
+
page?: number;
|
|
340
|
+
limit?: number;
|
|
341
|
+
sort?: string;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// routes/users.ts
|
|
345
|
+
import type { FastifyInstance } from 'fastify';
|
|
346
|
+
import type { User, CreateUserInput, PaginationQuery } from '../types/index.js';
|
|
347
|
+
|
|
348
|
+
export default async function userRoutes(fastify: FastifyInstance) {
|
|
349
|
+
fastify.get<{
|
|
350
|
+
Querystring: PaginationQuery;
|
|
351
|
+
Reply: { users: User[]; total: number };
|
|
352
|
+
}>('/', async (request) => {
|
|
353
|
+
const { page = 1, limit = 10 } = request.query;
|
|
354
|
+
// ...
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
fastify.post<{
|
|
358
|
+
Body: CreateUserInput;
|
|
359
|
+
Reply: User;
|
|
360
|
+
}>('/', async (request, reply) => {
|
|
361
|
+
reply.code(201);
|
|
362
|
+
// ...
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Type-Safe Route Registration
|
|
368
|
+
|
|
369
|
+
Create typed route factories:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import type { FastifyInstance, RouteOptions } from 'fastify';
|
|
373
|
+
|
|
374
|
+
function createCrudRoutes<T extends { id: string }>(
|
|
375
|
+
fastify: FastifyInstance,
|
|
376
|
+
options: {
|
|
377
|
+
prefix: string;
|
|
378
|
+
schema: {
|
|
379
|
+
item: object;
|
|
380
|
+
create: object;
|
|
381
|
+
update: object;
|
|
382
|
+
};
|
|
383
|
+
handlers: {
|
|
384
|
+
list: () => Promise<T[]>;
|
|
385
|
+
get: (id: string) => Promise<T | null>;
|
|
386
|
+
create: (data: unknown) => Promise<T>;
|
|
387
|
+
update: (id: string, data: unknown) => Promise<T>;
|
|
388
|
+
delete: (id: string) => Promise<void>;
|
|
389
|
+
};
|
|
390
|
+
},
|
|
391
|
+
) {
|
|
392
|
+
const { prefix, schema, handlers } = options;
|
|
393
|
+
|
|
394
|
+
fastify.get(`${prefix}`, {
|
|
395
|
+
schema: { response: { 200: { type: 'array', items: schema.item } } },
|
|
396
|
+
}, async () => handlers.list());
|
|
397
|
+
|
|
398
|
+
fastify.get(`${prefix}/:id`, {
|
|
399
|
+
schema: { response: { 200: schema.item } },
|
|
400
|
+
}, async (request) => {
|
|
401
|
+
const item = await handlers.get((request.params as { id: string }).id);
|
|
402
|
+
if (!item) throw { statusCode: 404, message: 'Not found' };
|
|
403
|
+
return item;
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// ... more routes
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Avoiding Type Gymnastics
|
|
411
|
+
|
|
412
|
+
Keep types simple and practical:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// GOOD - simple, readable types
|
|
416
|
+
interface UserRequest {
|
|
417
|
+
Params: { id: string };
|
|
418
|
+
Body: { name: string };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
app.put<UserRequest>('/users/:id', handler);
|
|
422
|
+
|
|
423
|
+
// AVOID - overly complex generic types
|
|
424
|
+
type DeepPartial<T> = T extends object ? {
|
|
425
|
+
[P in keyof T]?: DeepPartial<T[P]>;
|
|
426
|
+
} : T;
|
|
427
|
+
|
|
428
|
+
// AVOID - excessive type inference
|
|
429
|
+
type InferSchemaType<T> = T extends { properties: infer P }
|
|
430
|
+
? { [K in keyof P]: InferPropertyType<P[K]> }
|
|
431
|
+
: never;
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Type Checking Without Compilation
|
|
435
|
+
|
|
436
|
+
Use TypeScript for type checking only:
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
# Type check without emitting
|
|
440
|
+
npx tsc --noEmit
|
|
441
|
+
|
|
442
|
+
# Watch mode
|
|
443
|
+
npx tsc --noEmit --watch
|
|
444
|
+
|
|
445
|
+
# In CI
|
|
446
|
+
npm run typecheck
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
// package.json
|
|
451
|
+
{
|
|
452
|
+
"scripts": {
|
|
453
|
+
"start": "node app.ts",
|
|
454
|
+
"typecheck": "tsc --noEmit",
|
|
455
|
+
"test": "npm run typecheck && node --test"
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|