@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,475 @@
1
+ ---
2
+ name: serialization
3
+ description: Response serialization in Fastify with TypeBox
4
+ metadata:
5
+ tags: serialization, response, json, fast-json-stringify, typebox
6
+ ---
7
+
8
+ # Response Serialization
9
+
10
+ ## Use TypeBox for Type-Safe Response Schemas
11
+
12
+ Define response schemas with TypeBox for automatic TypeScript types and fast serialization:
13
+
14
+ ```typescript
15
+ import Fastify from 'fastify';
16
+ import { Type, type Static } from '@sinclair/typebox';
17
+
18
+ const app = Fastify();
19
+
20
+ // Define response schema with TypeBox
21
+ const UserResponse = Type.Object({
22
+ id: Type.String(),
23
+ name: Type.String(),
24
+ email: Type.String(),
25
+ });
26
+
27
+ const UsersResponse = Type.Array(UserResponse);
28
+
29
+ type UserResponseType = Static<typeof UserResponse>;
30
+
31
+ // With TypeBox schema - uses fast-json-stringify (faster) + TypeScript types
32
+ app.get<{ Reply: Static<typeof UsersResponse> }>('/users', {
33
+ schema: {
34
+ response: {
35
+ 200: UsersResponse,
36
+ },
37
+ },
38
+ }, async () => {
39
+ return db.users.findAll();
40
+ });
41
+
42
+ // Without schema - uses JSON.stringify (slower), no type safety
43
+ app.get('/users-slow', async () => {
44
+ return db.users.findAll();
45
+ });
46
+ ```
47
+
48
+ ## Fast JSON Stringify
49
+
50
+ Fastify uses `fast-json-stringify` when response schemas are defined. This provides:
51
+
52
+ 1. **Performance**: 2-3x faster serialization than JSON.stringify
53
+ 2. **Security**: Only defined properties are serialized (strips sensitive data)
54
+ 3. **Type coercion**: Ensures output matches the schema
55
+ 4. **TypeScript**: Full type inference with TypeBox
56
+
57
+ ## Response Schema Benefits
58
+
59
+ 1. **Performance**: 2-3x faster serialization
60
+ 2. **Security**: Only defined properties are included
61
+ 3. **Documentation**: OpenAPI/Swagger integration
62
+ 4. **Type coercion**: Ensures correct output types
63
+
64
+ ```typescript
65
+ app.get('/user/:id', {
66
+ schema: {
67
+ response: {
68
+ 200: {
69
+ type: 'object',
70
+ properties: {
71
+ id: { type: 'string' },
72
+ name: { type: 'string' },
73
+ // password is NOT in schema, so it's stripped
74
+ },
75
+ },
76
+ },
77
+ },
78
+ }, async (request) => {
79
+ const user = await db.users.findById(request.params.id);
80
+ // Even if user has password field, it won't be serialized
81
+ return user;
82
+ });
83
+ ```
84
+
85
+ ## Multiple Status Code Schemas
86
+
87
+ Define schemas for different response codes:
88
+
89
+ ```typescript
90
+ app.get('/users/:id', {
91
+ schema: {
92
+ response: {
93
+ 200: {
94
+ type: 'object',
95
+ properties: {
96
+ id: { type: 'string' },
97
+ name: { type: 'string' },
98
+ email: { type: 'string' },
99
+ },
100
+ },
101
+ 404: {
102
+ type: 'object',
103
+ properties: {
104
+ statusCode: { type: 'integer' },
105
+ error: { type: 'string' },
106
+ message: { type: 'string' },
107
+ },
108
+ },
109
+ },
110
+ },
111
+ }, async (request, reply) => {
112
+ const user = await db.users.findById(request.params.id);
113
+
114
+ if (!user) {
115
+ reply.code(404);
116
+ return { statusCode: 404, error: 'Not Found', message: 'User not found' };
117
+ }
118
+
119
+ return user;
120
+ });
121
+ ```
122
+
123
+ ## Default Response Schema
124
+
125
+ Use 'default' for common error responses:
126
+
127
+ ```typescript
128
+ app.get('/resource', {
129
+ schema: {
130
+ response: {
131
+ 200: { $ref: 'resource#' },
132
+ '4xx': {
133
+ type: 'object',
134
+ properties: {
135
+ statusCode: { type: 'integer' },
136
+ error: { type: 'string' },
137
+ message: { type: 'string' },
138
+ },
139
+ },
140
+ '5xx': {
141
+ type: 'object',
142
+ properties: {
143
+ statusCode: { type: 'integer' },
144
+ error: { type: 'string' },
145
+ },
146
+ },
147
+ },
148
+ },
149
+ }, handler);
150
+ ```
151
+
152
+ ## Custom Serializers
153
+
154
+ Create custom serialization functions:
155
+
156
+ ```typescript
157
+ // Per-route serializer
158
+ app.get('/custom', {
159
+ schema: {
160
+ response: {
161
+ 200: {
162
+ type: 'object',
163
+ properties: {
164
+ value: { type: 'string' },
165
+ },
166
+ },
167
+ },
168
+ },
169
+ serializerCompiler: ({ schema }) => {
170
+ return (data) => {
171
+ // Custom serialization logic
172
+ return JSON.stringify({
173
+ value: String(data.value).toUpperCase(),
174
+ serializedAt: new Date().toISOString(),
175
+ });
176
+ };
177
+ },
178
+ }, async () => {
179
+ return { value: 'hello' };
180
+ });
181
+ ```
182
+
183
+ ## Shared Serializers
184
+
185
+ Use the global serializer compiler:
186
+
187
+ ```typescript
188
+ import Fastify from 'fastify';
189
+
190
+ const app = Fastify({
191
+ serializerCompiler: ({ schema, method, url, httpStatus }) => {
192
+ // Custom compilation logic
193
+ const stringify = fastJson(schema);
194
+ return (data) => stringify(data);
195
+ },
196
+ });
197
+ ```
198
+
199
+ ## Serialization with Type Coercion
200
+
201
+ fast-json-stringify coerces types:
202
+
203
+ ```typescript
204
+ app.get('/data', {
205
+ schema: {
206
+ response: {
207
+ 200: {
208
+ type: 'object',
209
+ properties: {
210
+ count: { type: 'integer' }, // '5' -> 5
211
+ active: { type: 'boolean' }, // 'true' -> true
212
+ tags: {
213
+ type: 'array',
214
+ items: { type: 'string' }, // [1, 2] -> ['1', '2']
215
+ },
216
+ },
217
+ },
218
+ },
219
+ },
220
+ }, async () => {
221
+ return {
222
+ count: '5', // Coerced to integer
223
+ active: 'true', // Coerced to boolean
224
+ tags: [1, 2, 3], // Coerced to strings
225
+ };
226
+ });
227
+ ```
228
+
229
+ ## Nullable Fields
230
+
231
+ Handle nullable fields properly:
232
+
233
+ ```typescript
234
+ app.get('/profile', {
235
+ schema: {
236
+ response: {
237
+ 200: {
238
+ type: 'object',
239
+ properties: {
240
+ name: { type: 'string' },
241
+ bio: { type: ['string', 'null'] },
242
+ avatar: {
243
+ oneOf: [
244
+ { type: 'string', format: 'uri' },
245
+ { type: 'null' },
246
+ ],
247
+ },
248
+ },
249
+ },
250
+ },
251
+ },
252
+ }, async () => {
253
+ return {
254
+ name: 'John',
255
+ bio: null,
256
+ avatar: null,
257
+ };
258
+ });
259
+ ```
260
+
261
+ ## Additional Properties
262
+
263
+ Control extra properties in response:
264
+
265
+ ```typescript
266
+ // Strip additional properties (default)
267
+ app.get('/strict', {
268
+ schema: {
269
+ response: {
270
+ 200: {
271
+ type: 'object',
272
+ properties: {
273
+ id: { type: 'string' },
274
+ name: { type: 'string' },
275
+ },
276
+ additionalProperties: false,
277
+ },
278
+ },
279
+ },
280
+ }, async () => {
281
+ return { id: '1', name: 'John', secret: 'hidden' };
282
+ // Output: { "id": "1", "name": "John" }
283
+ });
284
+
285
+ // Allow additional properties
286
+ app.get('/flexible', {
287
+ schema: {
288
+ response: {
289
+ 200: {
290
+ type: 'object',
291
+ properties: {
292
+ id: { type: 'string' },
293
+ },
294
+ additionalProperties: true,
295
+ },
296
+ },
297
+ },
298
+ }, async () => {
299
+ return { id: '1', extra: 'included' };
300
+ // Output: { "id": "1", "extra": "included" }
301
+ });
302
+ ```
303
+
304
+ ## Nested Objects
305
+
306
+ Serialize nested structures:
307
+
308
+ ```typescript
309
+ app.addSchema({
310
+ $id: 'address',
311
+ type: 'object',
312
+ properties: {
313
+ street: { type: 'string' },
314
+ city: { type: 'string' },
315
+ country: { type: 'string' },
316
+ },
317
+ });
318
+
319
+ app.get('/user', {
320
+ schema: {
321
+ response: {
322
+ 200: {
323
+ type: 'object',
324
+ properties: {
325
+ name: { type: 'string' },
326
+ address: { $ref: 'address#' },
327
+ contacts: {
328
+ type: 'array',
329
+ items: {
330
+ type: 'object',
331
+ properties: {
332
+ type: { type: 'string' },
333
+ value: { type: 'string' },
334
+ },
335
+ },
336
+ },
337
+ },
338
+ },
339
+ },
340
+ },
341
+ }, async () => {
342
+ return {
343
+ name: 'John',
344
+ address: { street: '123 Main', city: 'Boston', country: 'USA' },
345
+ contacts: [
346
+ { type: 'email', value: 'john@example.com' },
347
+ { type: 'phone', value: '+1234567890' },
348
+ ],
349
+ };
350
+ });
351
+ ```
352
+
353
+ ## Date Serialization
354
+
355
+ Handle dates consistently:
356
+
357
+ ```typescript
358
+ app.get('/events', {
359
+ schema: {
360
+ response: {
361
+ 200: {
362
+ type: 'array',
363
+ items: {
364
+ type: 'object',
365
+ properties: {
366
+ name: { type: 'string' },
367
+ date: { type: 'string', format: 'date-time' },
368
+ },
369
+ },
370
+ },
371
+ },
372
+ },
373
+ }, async () => {
374
+ const events = await db.events.findAll();
375
+
376
+ // Convert Date objects to ISO strings
377
+ return events.map((e) => ({
378
+ ...e,
379
+ date: e.date.toISOString(),
380
+ }));
381
+ });
382
+ ```
383
+
384
+ ## BigInt Serialization
385
+
386
+ Handle BigInt values:
387
+
388
+ ```typescript
389
+ // BigInt is not JSON serializable by default
390
+ app.get('/large-number', {
391
+ schema: {
392
+ response: {
393
+ 200: {
394
+ type: 'object',
395
+ properties: {
396
+ id: { type: 'string' }, // Serialize as string
397
+ count: { type: 'integer' },
398
+ },
399
+ },
400
+ },
401
+ },
402
+ }, async () => {
403
+ const bigValue = 9007199254740993n;
404
+
405
+ return {
406
+ id: bigValue.toString(), // Convert to string
407
+ count: Number(bigValue), // Or number if safe
408
+ };
409
+ });
410
+ ```
411
+
412
+ ## Stream Responses
413
+
414
+ Stream responses bypass serialization:
415
+
416
+ ```typescript
417
+ import { createReadStream } from 'node:fs';
418
+
419
+ app.get('/file', async (request, reply) => {
420
+ const stream = createReadStream('./data.json');
421
+ reply.type('application/json');
422
+ return reply.send(stream);
423
+ });
424
+
425
+ // Streaming JSON array
426
+ app.get('/stream', async (request, reply) => {
427
+ reply.type('application/json');
428
+
429
+ const cursor = db.users.findCursor();
430
+
431
+ reply.raw.write('[');
432
+ let first = true;
433
+
434
+ for await (const user of cursor) {
435
+ if (!first) reply.raw.write(',');
436
+ reply.raw.write(JSON.stringify(user));
437
+ first = false;
438
+ }
439
+
440
+ reply.raw.write(']');
441
+ reply.raw.end();
442
+ });
443
+ ```
444
+
445
+ ## Pre-Serialization Hook
446
+
447
+ Modify data before serialization:
448
+
449
+ ```typescript
450
+ app.addHook('preSerialization', async (request, reply, payload) => {
451
+ // Add metadata to responses
452
+ if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
453
+ return {
454
+ ...payload,
455
+ _links: {
456
+ self: request.url,
457
+ },
458
+ };
459
+ }
460
+ return payload;
461
+ });
462
+ ```
463
+
464
+ ## Disable Serialization
465
+
466
+ Skip serialization for specific routes:
467
+
468
+ ```typescript
469
+ app.get('/raw', async (request, reply) => {
470
+ const data = JSON.stringify({ raw: true });
471
+ reply.type('application/json');
472
+ reply.serializer((payload) => payload); // Pass through
473
+ return data;
474
+ });
475
+ ```