@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,412 @@
1
+ ---
2
+ name: error-handling
3
+ description: Error handling patterns in Fastify
4
+ metadata:
5
+ tags: errors, exceptions, error-handler, validation
6
+ ---
7
+
8
+ # Error Handling in Fastify
9
+
10
+ ## Default Error Handler
11
+
12
+ Fastify has a built-in error handler. Thrown errors automatically become HTTP responses:
13
+
14
+ ```typescript
15
+ import Fastify from 'fastify';
16
+
17
+ const app = Fastify({ logger: true });
18
+
19
+ app.get('/users/:id', async (request) => {
20
+ const user = await findUser(request.params.id);
21
+ if (!user) {
22
+ // Throwing an error with statusCode sets the response status
23
+ const error = new Error('User not found');
24
+ error.statusCode = 404;
25
+ throw error;
26
+ }
27
+ return user;
28
+ });
29
+ ```
30
+
31
+ ## Custom Error Classes
32
+
33
+ Use `@fastify/error` for creating typed errors:
34
+
35
+ ```typescript
36
+ import createError from '@fastify/error';
37
+
38
+ const NotFoundError = createError('NOT_FOUND', '%s not found', 404);
39
+ const UnauthorizedError = createError('UNAUTHORIZED', 'Authentication required', 401);
40
+ const ForbiddenError = createError('FORBIDDEN', 'Access denied: %s', 403);
41
+ const ValidationError = createError('VALIDATION_ERROR', '%s', 400);
42
+ const ConflictError = createError('CONFLICT', '%s already exists', 409);
43
+
44
+ // Usage
45
+ app.get('/users/:id', async (request) => {
46
+ const user = await findUser(request.params.id);
47
+ if (!user) {
48
+ throw new NotFoundError('User');
49
+ }
50
+ return user;
51
+ });
52
+
53
+ app.post('/users', async (request) => {
54
+ const exists = await userExists(request.body.email);
55
+ if (exists) {
56
+ throw new ConflictError('Email');
57
+ }
58
+ return createUser(request.body);
59
+ });
60
+ ```
61
+
62
+ ## Custom Error Handler
63
+
64
+ Implement a centralized error handler:
65
+
66
+ ```typescript
67
+ import Fastify from 'fastify';
68
+ import type { FastifyError, FastifyRequest, FastifyReply } from 'fastify';
69
+
70
+ const app = Fastify({ logger: true });
71
+
72
+ app.setErrorHandler((error: FastifyError, request: FastifyRequest, reply: FastifyReply) => {
73
+ // Log the error
74
+ request.log.error({ err: error }, 'Request error');
75
+
76
+ // Handle validation errors
77
+ if (error.validation) {
78
+ return reply.code(400).send({
79
+ statusCode: 400,
80
+ error: 'Bad Request',
81
+ message: 'Validation failed',
82
+ details: error.validation,
83
+ });
84
+ }
85
+
86
+ // Handle known errors with status codes
87
+ const statusCode = error.statusCode ?? 500;
88
+ const code = error.code ?? 'INTERNAL_ERROR';
89
+
90
+ // Don't expose internal error details in production
91
+ const message = statusCode >= 500 && process.env.NODE_ENV === 'production'
92
+ ? 'Internal Server Error'
93
+ : error.message;
94
+
95
+ return reply.code(statusCode).send({
96
+ statusCode,
97
+ error: code,
98
+ message,
99
+ });
100
+ });
101
+ ```
102
+
103
+ ## Error Response Schema
104
+
105
+ Define consistent error response schemas:
106
+
107
+ ```typescript
108
+ app.addSchema({
109
+ $id: 'httpError',
110
+ type: 'object',
111
+ properties: {
112
+ statusCode: { type: 'integer' },
113
+ error: { type: 'string' },
114
+ message: { type: 'string' },
115
+ details: {
116
+ type: 'array',
117
+ items: {
118
+ type: 'object',
119
+ properties: {
120
+ field: { type: 'string' },
121
+ message: { type: 'string' },
122
+ },
123
+ },
124
+ },
125
+ },
126
+ required: ['statusCode', 'error', 'message'],
127
+ });
128
+
129
+ // Use in route schemas
130
+ app.get('/users/:id', {
131
+ schema: {
132
+ params: {
133
+ type: 'object',
134
+ properties: { id: { type: 'string' } },
135
+ required: ['id'],
136
+ },
137
+ response: {
138
+ 200: { $ref: 'user#' },
139
+ 404: { $ref: 'httpError#' },
140
+ 500: { $ref: 'httpError#' },
141
+ },
142
+ },
143
+ }, handler);
144
+ ```
145
+
146
+ ## Reply Helpers with @fastify/sensible
147
+
148
+ Use `@fastify/sensible` for standard HTTP errors:
149
+
150
+ ```typescript
151
+ import fastifySensible from '@fastify/sensible';
152
+
153
+ app.register(fastifySensible);
154
+
155
+ app.get('/users/:id', async (request, reply) => {
156
+ const user = await findUser(request.params.id);
157
+ if (!user) {
158
+ return reply.notFound('User not found');
159
+ }
160
+ if (!hasAccess(request.user, user)) {
161
+ return reply.forbidden('You cannot access this user');
162
+ }
163
+ return user;
164
+ });
165
+
166
+ // Available methods:
167
+ // reply.badRequest(message?)
168
+ // reply.unauthorized(message?)
169
+ // reply.forbidden(message?)
170
+ // reply.notFound(message?)
171
+ // reply.methodNotAllowed(message?)
172
+ // reply.conflict(message?)
173
+ // reply.gone(message?)
174
+ // reply.unprocessableEntity(message?)
175
+ // reply.tooManyRequests(message?)
176
+ // reply.internalServerError(message?)
177
+ // reply.notImplemented(message?)
178
+ // reply.badGateway(message?)
179
+ // reply.serviceUnavailable(message?)
180
+ // reply.gatewayTimeout(message?)
181
+ ```
182
+
183
+ ## Async Error Handling
184
+
185
+ Errors in async handlers are automatically caught:
186
+
187
+ ```typescript
188
+ // Errors are automatically caught and passed to error handler
189
+ app.get('/users', async (request) => {
190
+ const users = await db.users.findAll(); // If this throws, error handler catches it
191
+ return users;
192
+ });
193
+
194
+ // Explicit error handling for custom logic
195
+ app.get('/users/:id', async (request, reply) => {
196
+ try {
197
+ const user = await db.users.findById(request.params.id);
198
+ if (!user) {
199
+ return reply.code(404).send({ error: 'User not found' });
200
+ }
201
+ return user;
202
+ } catch (error) {
203
+ // Transform database errors
204
+ if (error.code === 'CONNECTION_ERROR') {
205
+ request.log.error({ err: error }, 'Database connection failed');
206
+ return reply.code(503).send({ error: 'Service temporarily unavailable' });
207
+ }
208
+ throw error; // Re-throw for error handler
209
+ }
210
+ });
211
+ ```
212
+
213
+ ## Hook Error Handling
214
+
215
+ Errors in hooks are handled the same way:
216
+
217
+ ```typescript
218
+ app.addHook('onRequest', async (request, reply) => {
219
+ const token = request.headers.authorization;
220
+ if (!token) {
221
+ // This error goes to the error handler
222
+ throw new UnauthorizedError();
223
+ }
224
+
225
+ try {
226
+ request.user = await verifyToken(token);
227
+ } catch (error) {
228
+ throw new UnauthorizedError();
229
+ }
230
+ });
231
+
232
+ // Or use reply to send response directly
233
+ app.addHook('onRequest', async (request, reply) => {
234
+ if (!request.headers.authorization) {
235
+ reply.code(401).send({ error: 'Unauthorized' });
236
+ return; // Must return to stop processing
237
+ }
238
+ });
239
+ ```
240
+
241
+ ## Not Found Handler
242
+
243
+ Customize the 404 response:
244
+
245
+ ```typescript
246
+ app.setNotFoundHandler(async (request, reply) => {
247
+ return reply.code(404).send({
248
+ statusCode: 404,
249
+ error: 'Not Found',
250
+ message: `Route ${request.method} ${request.url} not found`,
251
+ });
252
+ });
253
+
254
+ // With schema validation
255
+ app.setNotFoundHandler({
256
+ preValidation: async (request, reply) => {
257
+ // Pre-validation hook for 404 handler
258
+ },
259
+ }, async (request, reply) => {
260
+ return reply.code(404).send({ error: 'Not Found' });
261
+ });
262
+ ```
263
+
264
+ ## Error Wrapping
265
+
266
+ Wrap external errors with context:
267
+
268
+ ```typescript
269
+ import createError from '@fastify/error';
270
+
271
+ const DatabaseError = createError('DATABASE_ERROR', 'Database operation failed: %s', 500);
272
+ const ExternalServiceError = createError('EXTERNAL_SERVICE_ERROR', 'External service failed: %s', 502);
273
+
274
+ app.get('/users/:id', async (request) => {
275
+ try {
276
+ return await db.users.findById(request.params.id);
277
+ } catch (error) {
278
+ throw new DatabaseError(error.message, { cause: error });
279
+ }
280
+ });
281
+
282
+ app.get('/weather', async (request) => {
283
+ try {
284
+ return await weatherApi.fetch(request.query.city);
285
+ } catch (error) {
286
+ throw new ExternalServiceError(error.message, { cause: error });
287
+ }
288
+ });
289
+ ```
290
+
291
+ ## Validation Error Customization
292
+
293
+ Customize validation error format:
294
+
295
+ ```typescript
296
+ app.setErrorHandler((error, request, reply) => {
297
+ if (error.validation) {
298
+ const details = error.validation.map((err) => {
299
+ const field = err.instancePath
300
+ ? err.instancePath.slice(1).replace(/\//g, '.')
301
+ : err.params?.missingProperty || 'unknown';
302
+
303
+ return {
304
+ field,
305
+ message: err.message,
306
+ value: err.data,
307
+ };
308
+ });
309
+
310
+ return reply.code(400).send({
311
+ statusCode: 400,
312
+ error: 'Validation Error',
313
+ message: `Invalid ${error.validationContext}: ${details.map(d => d.field).join(', ')}`,
314
+ details,
315
+ });
316
+ }
317
+
318
+ // Handle other errors...
319
+ throw error;
320
+ });
321
+ ```
322
+
323
+ ## Error Cause Chain
324
+
325
+ Preserve error chains for debugging:
326
+
327
+ ```typescript
328
+ app.get('/complex-operation', async (request) => {
329
+ try {
330
+ await step1();
331
+ } catch (error) {
332
+ const wrapped = new Error('Step 1 failed', { cause: error });
333
+ wrapped.statusCode = 500;
334
+ throw wrapped;
335
+ }
336
+ });
337
+
338
+ // In error handler, log the full chain
339
+ app.setErrorHandler((error, request, reply) => {
340
+ // Log error with cause chain
341
+ let current = error;
342
+ const chain = [];
343
+ while (current) {
344
+ chain.push({
345
+ message: current.message,
346
+ code: current.code,
347
+ stack: current.stack,
348
+ });
349
+ current = current.cause;
350
+ }
351
+
352
+ request.log.error({ errorChain: chain }, 'Request failed');
353
+
354
+ reply.code(error.statusCode || 500).send({
355
+ error: error.message,
356
+ });
357
+ });
358
+ ```
359
+
360
+ ## Plugin-Scoped Error Handlers
361
+
362
+ Set error handlers at the plugin level:
363
+
364
+ ```typescript
365
+ app.register(async function apiRoutes(fastify) {
366
+ // This error handler only applies to routes in this plugin
367
+ fastify.setErrorHandler((error, request, reply) => {
368
+ request.log.error({ err: error }, 'API error');
369
+
370
+ reply.code(error.statusCode || 500).send({
371
+ error: {
372
+ code: error.code || 'API_ERROR',
373
+ message: error.message,
374
+ },
375
+ });
376
+ });
377
+
378
+ fastify.get('/data', async () => {
379
+ throw new Error('API-specific error');
380
+ });
381
+ }, { prefix: '/api' });
382
+ ```
383
+
384
+ ## Graceful Error Recovery
385
+
386
+ Handle errors gracefully without crashing:
387
+
388
+ ```typescript
389
+ app.get('/resilient', async (request, reply) => {
390
+ const results = await Promise.allSettled([
391
+ fetchPrimaryData(),
392
+ fetchSecondaryData(),
393
+ fetchOptionalData(),
394
+ ]);
395
+
396
+ const [primary, secondary, optional] = results;
397
+
398
+ if (primary.status === 'rejected') {
399
+ // Primary data is required
400
+ throw new Error('Primary data unavailable');
401
+ }
402
+
403
+ return {
404
+ data: primary.value,
405
+ secondary: secondary.status === 'fulfilled' ? secondary.value : null,
406
+ optional: optional.status === 'fulfilled' ? optional.value : null,
407
+ warnings: results
408
+ .filter((r) => r.status === 'rejected')
409
+ .map((r) => r.reason.message),
410
+ };
411
+ });
412
+ ```