@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.
Files changed (30) hide show
  1. package/README.md +282 -277
  2. package/docs/assets/install-plan.svg +29 -0
  3. package/docs/assets/install-skill-packages.svg +31 -0
  4. package/docs/assets/install-status.svg +32 -0
  5. package/package.json +10 -9
  6. package/setup-agent-toolkit.sh +1 -1
  7. package/skills/backend/fastify-best-practices/LICENSE +21 -0
  8. package/skills/backend/fastify-best-practices/NOTICE.md +11 -0
  9. package/skills/backend/fastify-best-practices/SKILL.md +75 -0
  10. package/skills/backend/fastify-best-practices/rules/authentication.md +521 -0
  11. package/skills/backend/fastify-best-practices/rules/configuration.md +217 -0
  12. package/skills/backend/fastify-best-practices/rules/content-type.md +387 -0
  13. package/skills/backend/fastify-best-practices/rules/cors-security.md +445 -0
  14. package/skills/backend/fastify-best-practices/rules/database.md +320 -0
  15. package/skills/backend/fastify-best-practices/rules/decorators.md +416 -0
  16. package/skills/backend/fastify-best-practices/rules/deployment.md +423 -0
  17. package/skills/backend/fastify-best-practices/rules/error-handling.md +412 -0
  18. package/skills/backend/fastify-best-practices/rules/hooks.md +464 -0
  19. package/skills/backend/fastify-best-practices/rules/http-proxy.md +247 -0
  20. package/skills/backend/fastify-best-practices/rules/logging.md +402 -0
  21. package/skills/backend/fastify-best-practices/rules/performance.md +425 -0
  22. package/skills/backend/fastify-best-practices/rules/plugins.md +320 -0
  23. package/skills/backend/fastify-best-practices/rules/routes.md +467 -0
  24. package/skills/backend/fastify-best-practices/rules/schemas.md +585 -0
  25. package/skills/backend/fastify-best-practices/rules/serialization.md +475 -0
  26. package/skills/backend/fastify-best-practices/rules/testing.md +536 -0
  27. package/skills/backend/fastify-best-practices/rules/typescript.md +458 -0
  28. package/skills/backend/fastify-best-practices/rules/websockets.md +421 -0
  29. package/skills/backend/fastify-best-practices/tile.json +11 -0
  30. package/skills/core/agent-toolkit-maintainer/SKILL.md +16 -14
@@ -0,0 +1,585 @@
1
+ ---
2
+ name: schemas
3
+ description: JSON Schema validation in Fastify with TypeBox
4
+ metadata:
5
+ tags: validation, json-schema, schemas, ajv, typebox
6
+ ---
7
+
8
+ # JSON Schema Validation
9
+
10
+ ## Use TypeBox for Type-Safe Schemas
11
+
12
+ **Prefer TypeBox for defining schemas.** It provides TypeScript types automatically and compiles to JSON Schema:
13
+
14
+ ```typescript
15
+ import Fastify from 'fastify';
16
+ import { Type, type Static } from '@sinclair/typebox';
17
+
18
+ const app = Fastify();
19
+
20
+ // Define schema with TypeBox - get TypeScript types for free
21
+ const CreateUserBody = Type.Object({
22
+ name: Type.String({ minLength: 1, maxLength: 100 }),
23
+ email: Type.String({ format: 'email' }),
24
+ age: Type.Optional(Type.Integer({ minimum: 0, maximum: 150 })),
25
+ });
26
+
27
+ const UserResponse = Type.Object({
28
+ id: Type.String({ format: 'uuid' }),
29
+ name: Type.String(),
30
+ email: Type.String(),
31
+ createdAt: Type.String({ format: 'date-time' }),
32
+ });
33
+
34
+ // TypeScript types are derived automatically
35
+ type CreateUserBodyType = Static<typeof CreateUserBody>;
36
+ type UserResponseType = Static<typeof UserResponse>;
37
+
38
+ app.post<{
39
+ Body: CreateUserBodyType;
40
+ Reply: UserResponseType;
41
+ }>('/users', {
42
+ schema: {
43
+ body: CreateUserBody,
44
+ response: {
45
+ 201: UserResponse,
46
+ },
47
+ },
48
+ }, async (request, reply) => {
49
+ // request.body is fully typed as CreateUserBodyType
50
+ const user = await createUser(request.body);
51
+ reply.code(201);
52
+ return user;
53
+ });
54
+ ```
55
+
56
+ ## TypeBox Common Patterns
57
+
58
+ ```typescript
59
+ import { Type, type Static } from '@sinclair/typebox';
60
+
61
+ // Enums
62
+ const Status = Type.Union([
63
+ Type.Literal('active'),
64
+ Type.Literal('inactive'),
65
+ Type.Literal('pending'),
66
+ ]);
67
+
68
+ // Arrays
69
+ const Tags = Type.Array(Type.String(), { minItems: 1, maxItems: 10 });
70
+
71
+ // Nested objects
72
+ const Address = Type.Object({
73
+ street: Type.String(),
74
+ city: Type.String(),
75
+ country: Type.String(),
76
+ zip: Type.Optional(Type.String()),
77
+ });
78
+
79
+ // References (reusable schemas)
80
+ const User = Type.Object({
81
+ id: Type.String({ format: 'uuid' }),
82
+ name: Type.String(),
83
+ address: Address,
84
+ tags: Tags,
85
+ status: Status,
86
+ });
87
+
88
+ // Nullable
89
+ const NullableString = Type.Union([Type.String(), Type.Null()]);
90
+
91
+ // Record/Map
92
+ const Metadata = Type.Record(Type.String(), Type.Unknown());
93
+ ```
94
+
95
+ ## Register TypeBox Schemas Globally
96
+
97
+ ```typescript
98
+ import { Type, type Static } from '@sinclair/typebox';
99
+
100
+ // Define shared schemas
101
+ const ErrorResponse = Type.Object({
102
+ error: Type.String(),
103
+ message: Type.String(),
104
+ statusCode: Type.Integer(),
105
+ });
106
+
107
+ const PaginationQuery = Type.Object({
108
+ page: Type.Integer({ minimum: 1, default: 1 }),
109
+ limit: Type.Integer({ minimum: 1, maximum: 100, default: 20 }),
110
+ });
111
+
112
+ // Register globally
113
+ app.addSchema(Type.Object({ $id: 'ErrorResponse', ...ErrorResponse }));
114
+ app.addSchema(Type.Object({ $id: 'PaginationQuery', ...PaginationQuery }));
115
+
116
+ // Reference in routes
117
+ app.get('/items', {
118
+ schema: {
119
+ querystring: { $ref: 'PaginationQuery#' },
120
+ response: {
121
+ 400: { $ref: 'ErrorResponse#' },
122
+ },
123
+ },
124
+ }, handler);
125
+ ```
126
+
127
+ ## Plain JSON Schema (Alternative)
128
+
129
+ You can also use plain JSON Schema directly:
130
+
131
+ ```typescript
132
+ import Fastify from 'fastify';
133
+
134
+ const app = Fastify();
135
+
136
+ const createUserSchema = {
137
+ body: {
138
+ type: 'object',
139
+ properties: {
140
+ name: { type: 'string', minLength: 1, maxLength: 100 },
141
+ email: { type: 'string', format: 'email' },
142
+ age: { type: 'integer', minimum: 0, maximum: 150 },
143
+ },
144
+ required: ['name', 'email'],
145
+ additionalProperties: false,
146
+ },
147
+ response: {
148
+ 201: {
149
+ type: 'object',
150
+ properties: {
151
+ id: { type: 'string', format: 'uuid' },
152
+ name: { type: 'string' },
153
+ email: { type: 'string' },
154
+ createdAt: { type: 'string', format: 'date-time' },
155
+ },
156
+ },
157
+ },
158
+ };
159
+
160
+ app.post('/users', { schema: createUserSchema }, async (request, reply) => {
161
+ const user = await createUser(request.body);
162
+ reply.code(201);
163
+ return user;
164
+ });
165
+ ```
166
+
167
+ ## Request Validation Parts
168
+
169
+ Validate different parts of the request:
170
+
171
+ ```typescript
172
+ const fullRequestSchema = {
173
+ // URL parameters
174
+ params: {
175
+ type: 'object',
176
+ properties: {
177
+ id: { type: 'string', format: 'uuid' },
178
+ },
179
+ required: ['id'],
180
+ },
181
+
182
+ // Query string
183
+ querystring: {
184
+ type: 'object',
185
+ properties: {
186
+ include: { type: 'string', enum: ['posts', 'comments', 'all'] },
187
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
188
+ },
189
+ },
190
+
191
+ // Request headers
192
+ headers: {
193
+ type: 'object',
194
+ properties: {
195
+ 'x-api-key': { type: 'string', minLength: 32 },
196
+ },
197
+ required: ['x-api-key'],
198
+ },
199
+
200
+ // Request body
201
+ body: {
202
+ type: 'object',
203
+ properties: {
204
+ data: { type: 'object' },
205
+ },
206
+ required: ['data'],
207
+ },
208
+ };
209
+
210
+ app.put('/resources/:id', { schema: fullRequestSchema }, handler);
211
+ ```
212
+
213
+ ## Shared Schemas with $id
214
+
215
+ Define reusable schemas with `$id` and reference them with `$ref`:
216
+
217
+ ```typescript
218
+ // Add shared schemas to Fastify
219
+ app.addSchema({
220
+ $id: 'user',
221
+ type: 'object',
222
+ properties: {
223
+ id: { type: 'string', format: 'uuid' },
224
+ name: { type: 'string' },
225
+ email: { type: 'string', format: 'email' },
226
+ createdAt: { type: 'string', format: 'date-time' },
227
+ },
228
+ required: ['id', 'name', 'email'],
229
+ });
230
+
231
+ app.addSchema({
232
+ $id: 'userCreate',
233
+ type: 'object',
234
+ properties: {
235
+ name: { type: 'string', minLength: 1 },
236
+ email: { type: 'string', format: 'email' },
237
+ },
238
+ required: ['name', 'email'],
239
+ additionalProperties: false,
240
+ });
241
+
242
+ app.addSchema({
243
+ $id: 'error',
244
+ type: 'object',
245
+ properties: {
246
+ statusCode: { type: 'integer' },
247
+ error: { type: 'string' },
248
+ message: { type: 'string' },
249
+ },
250
+ });
251
+
252
+ // Reference shared schemas
253
+ app.post('/users', {
254
+ schema: {
255
+ body: { $ref: 'userCreate#' },
256
+ response: {
257
+ 201: { $ref: 'user#' },
258
+ 400: { $ref: 'error#' },
259
+ },
260
+ },
261
+ }, handler);
262
+
263
+ app.get('/users/:id', {
264
+ schema: {
265
+ params: {
266
+ type: 'object',
267
+ properties: { id: { type: 'string', format: 'uuid' } },
268
+ required: ['id'],
269
+ },
270
+ response: {
271
+ 200: { $ref: 'user#' },
272
+ 404: { $ref: 'error#' },
273
+ },
274
+ },
275
+ }, handler);
276
+ ```
277
+
278
+ ## Array Schemas
279
+
280
+ Define schemas for array responses:
281
+
282
+ ```typescript
283
+ app.addSchema({
284
+ $id: 'userList',
285
+ type: 'object',
286
+ properties: {
287
+ users: {
288
+ type: 'array',
289
+ items: { $ref: 'user#' },
290
+ },
291
+ total: { type: 'integer' },
292
+ page: { type: 'integer' },
293
+ pageSize: { type: 'integer' },
294
+ },
295
+ });
296
+
297
+ app.get('/users', {
298
+ schema: {
299
+ querystring: {
300
+ type: 'object',
301
+ properties: {
302
+ page: { type: 'integer', minimum: 1, default: 1 },
303
+ pageSize: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
304
+ },
305
+ },
306
+ response: {
307
+ 200: { $ref: 'userList#' },
308
+ },
309
+ },
310
+ }, handler);
311
+ ```
312
+
313
+ ## Custom Formats
314
+
315
+ Add custom validation formats:
316
+
317
+ ```typescript
318
+ import Fastify from 'fastify';
319
+
320
+ const app = Fastify({
321
+ ajv: {
322
+ customOptions: {
323
+ formats: {
324
+ 'iso-country': /^[A-Z]{2}$/,
325
+ 'phone': /^\+?[1-9]\d{1,14}$/,
326
+ },
327
+ },
328
+ },
329
+ });
330
+
331
+ // Or add formats dynamically
332
+ app.addSchema({
333
+ $id: 'address',
334
+ type: 'object',
335
+ properties: {
336
+ street: { type: 'string' },
337
+ country: { type: 'string', format: 'iso-country' },
338
+ phone: { type: 'string', format: 'phone' },
339
+ },
340
+ });
341
+ ```
342
+
343
+ ## Custom Keywords
344
+
345
+ Add custom validation keywords:
346
+
347
+ ```typescript
348
+ import Fastify from 'fastify';
349
+ import Ajv from 'ajv';
350
+
351
+ const app = Fastify({
352
+ ajv: {
353
+ customOptions: {
354
+ keywords: [
355
+ {
356
+ keyword: 'isEven',
357
+ type: 'number',
358
+ validate: (schema: boolean, data: number) => {
359
+ if (schema) {
360
+ return data % 2 === 0;
361
+ }
362
+ return true;
363
+ },
364
+ errors: false,
365
+ },
366
+ ],
367
+ },
368
+ },
369
+ });
370
+
371
+ // Use custom keyword
372
+ app.post('/numbers', {
373
+ schema: {
374
+ body: {
375
+ type: 'object',
376
+ properties: {
377
+ value: { type: 'integer', isEven: true },
378
+ },
379
+ },
380
+ },
381
+ }, handler);
382
+ ```
383
+
384
+ ## Coercion
385
+
386
+ Fastify coerces types by default for query strings and params:
387
+
388
+ ```typescript
389
+ // Query string "?page=5&active=true" becomes:
390
+ // { page: 5, active: true } (number and boolean, not strings)
391
+
392
+ app.get('/items', {
393
+ schema: {
394
+ querystring: {
395
+ type: 'object',
396
+ properties: {
397
+ page: { type: 'integer' }, // "5" -> 5
398
+ active: { type: 'boolean' }, // "true" -> true
399
+ tags: {
400
+ type: 'array',
401
+ items: { type: 'string' }, // "a,b,c" -> ["a", "b", "c"]
402
+ },
403
+ },
404
+ },
405
+ },
406
+ }, handler);
407
+ ```
408
+
409
+ ## Validation Error Handling
410
+
411
+ Customize validation error responses:
412
+
413
+ ```typescript
414
+ app.setErrorHandler((error, request, reply) => {
415
+ if (error.validation) {
416
+ reply.code(400).send({
417
+ error: 'Validation Error',
418
+ message: 'Request validation failed',
419
+ details: error.validation.map((err) => ({
420
+ field: err.instancePath || err.params?.missingProperty,
421
+ message: err.message,
422
+ keyword: err.keyword,
423
+ })),
424
+ });
425
+ return;
426
+ }
427
+
428
+ // Handle other errors
429
+ reply.code(error.statusCode || 500).send({
430
+ error: error.name,
431
+ message: error.message,
432
+ });
433
+ });
434
+ ```
435
+
436
+ ## Schema Compiler Options
437
+
438
+ Configure the Ajv schema compiler:
439
+
440
+ ```typescript
441
+ import Fastify from 'fastify';
442
+
443
+ const app = Fastify({
444
+ ajv: {
445
+ customOptions: {
446
+ removeAdditional: 'all', // Remove extra properties
447
+ useDefaults: true, // Apply default values
448
+ coerceTypes: true, // Coerce types
449
+ allErrors: true, // Report all errors, not just first
450
+ },
451
+ plugins: [
452
+ require('ajv-formats'), // Add format validators
453
+ ],
454
+ },
455
+ });
456
+ ```
457
+
458
+ ## Nullable Fields
459
+
460
+ Handle nullable fields properly:
461
+
462
+ ```typescript
463
+ app.addSchema({
464
+ $id: 'profile',
465
+ type: 'object',
466
+ properties: {
467
+ name: { type: 'string' },
468
+ bio: { type: ['string', 'null'] }, // Can be string or null
469
+ avatar: {
470
+ oneOf: [
471
+ { type: 'string', format: 'uri' },
472
+ { type: 'null' },
473
+ ],
474
+ },
475
+ },
476
+ });
477
+ ```
478
+
479
+ ## Conditional Validation
480
+
481
+ Use if/then/else for conditional validation:
482
+
483
+ ```typescript
484
+ app.addSchema({
485
+ $id: 'payment',
486
+ type: 'object',
487
+ properties: {
488
+ method: { type: 'string', enum: ['card', 'bank'] },
489
+ cardNumber: { type: 'string' },
490
+ bankAccount: { type: 'string' },
491
+ },
492
+ required: ['method'],
493
+ if: {
494
+ properties: { method: { const: 'card' } },
495
+ },
496
+ then: {
497
+ required: ['cardNumber'],
498
+ },
499
+ else: {
500
+ required: ['bankAccount'],
501
+ },
502
+ });
503
+ ```
504
+
505
+ ## Schema Organization
506
+
507
+ Organize schemas in a dedicated file:
508
+
509
+ ```typescript
510
+ // schemas/index.ts
511
+ export const schemas = [
512
+ {
513
+ $id: 'user',
514
+ type: 'object',
515
+ properties: {
516
+ id: { type: 'string', format: 'uuid' },
517
+ name: { type: 'string' },
518
+ email: { type: 'string', format: 'email' },
519
+ },
520
+ },
521
+ {
522
+ $id: 'error',
523
+ type: 'object',
524
+ properties: {
525
+ statusCode: { type: 'integer' },
526
+ error: { type: 'string' },
527
+ message: { type: 'string' },
528
+ },
529
+ },
530
+ ];
531
+
532
+ // app.ts
533
+ import { schemas } from './schemas/index.js';
534
+
535
+ for (const schema of schemas) {
536
+ app.addSchema(schema);
537
+ }
538
+ ```
539
+
540
+ ## OpenAPI/Swagger Integration
541
+
542
+ Schemas work directly with @fastify/swagger:
543
+
544
+ ```typescript
545
+ import fastifySwagger from '@fastify/swagger';
546
+ import fastifySwaggerUi from '@fastify/swagger-ui';
547
+
548
+ app.register(fastifySwagger, {
549
+ openapi: {
550
+ info: {
551
+ title: 'My API',
552
+ version: '1.0.0',
553
+ },
554
+ },
555
+ });
556
+
557
+ app.register(fastifySwaggerUi, {
558
+ routePrefix: '/docs',
559
+ });
560
+
561
+ // Schemas are automatically converted to OpenAPI definitions
562
+ ```
563
+
564
+ ## Performance Considerations
565
+
566
+ Response schemas enable fast-json-stringify for serialization:
567
+
568
+ ```typescript
569
+ // With response schema - uses fast-json-stringify (faster)
570
+ app.get('/users', {
571
+ schema: {
572
+ response: {
573
+ 200: {
574
+ type: 'array',
575
+ items: { $ref: 'user#' },
576
+ },
577
+ },
578
+ },
579
+ }, handler);
580
+
581
+ // Without response schema - uses JSON.stringify (slower)
582
+ app.get('/users-slow', handler);
583
+ ```
584
+
585
+ Always define response schemas for production APIs to benefit from optimized serialization.