@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,416 @@
1
+ ---
2
+ name: decorators
3
+ description: Decorators and request/reply extensions in Fastify
4
+ metadata:
5
+ tags: decorators, extensions, customization, utilities
6
+ ---
7
+
8
+ # Decorators and Extensions
9
+
10
+ ## Understanding Decorators
11
+
12
+ Decorators add custom properties and methods to Fastify instances, requests, and replies:
13
+
14
+ ```typescript
15
+ import Fastify from 'fastify';
16
+
17
+ const app = Fastify();
18
+
19
+ // Decorate the Fastify instance
20
+ app.decorate('utility', {
21
+ formatDate: (date: Date) => date.toISOString(),
22
+ generateId: () => crypto.randomUUID(),
23
+ });
24
+
25
+ // Use in routes
26
+ app.get('/example', async function (request, reply) {
27
+ const id = this.utility.generateId();
28
+ return { id, timestamp: this.utility.formatDate(new Date()) };
29
+ });
30
+ ```
31
+
32
+ ## Decorator Types
33
+
34
+ Three types of decorators for different contexts:
35
+
36
+ ```typescript
37
+ // Instance decorator - available on fastify instance
38
+ app.decorate('config', { apiVersion: '1.0.0' });
39
+ app.decorate('db', databaseConnection);
40
+ app.decorate('cache', cacheClient);
41
+
42
+ // Request decorator - available on each request
43
+ app.decorateRequest('user', null); // Object property
44
+ app.decorateRequest('startTime', 0); // Primitive
45
+ app.decorateRequest('getData', function() { // Method
46
+ return this.body;
47
+ });
48
+
49
+ // Reply decorator - available on each reply
50
+ app.decorateReply('sendError', function(code: number, message: string) {
51
+ return this.code(code).send({ error: message });
52
+ });
53
+ app.decorateReply('success', function(data: unknown) {
54
+ return this.send({ success: true, data });
55
+ });
56
+ ```
57
+
58
+ ## TypeScript Declaration Merging
59
+
60
+ Extend Fastify types for type safety:
61
+
62
+ ```typescript
63
+ // Declare custom properties
64
+ declare module 'fastify' {
65
+ interface FastifyInstance {
66
+ config: {
67
+ apiVersion: string;
68
+ environment: string;
69
+ };
70
+ db: DatabaseClient;
71
+ cache: CacheClient;
72
+ }
73
+
74
+ interface FastifyRequest {
75
+ user: {
76
+ id: string;
77
+ email: string;
78
+ roles: string[];
79
+ } | null;
80
+ startTime: number;
81
+ requestId: string;
82
+ }
83
+
84
+ interface FastifyReply {
85
+ sendError: (code: number, message: string) => void;
86
+ success: (data: unknown) => void;
87
+ }
88
+ }
89
+
90
+ // Register decorators
91
+ app.decorate('config', {
92
+ apiVersion: '1.0.0',
93
+ environment: process.env.NODE_ENV,
94
+ });
95
+
96
+ app.decorateRequest('user', null);
97
+ app.decorateRequest('startTime', 0);
98
+
99
+ app.decorateReply('sendError', function (code: number, message: string) {
100
+ this.code(code).send({ error: message });
101
+ });
102
+ ```
103
+
104
+ ## Decorator Initialization
105
+
106
+ Initialize request/reply decorators in hooks:
107
+
108
+ ```typescript
109
+ // Decorators with primitive defaults are copied
110
+ app.decorateRequest('startTime', 0);
111
+
112
+ // Initialize in hook
113
+ app.addHook('onRequest', async (request) => {
114
+ request.startTime = Date.now();
115
+ });
116
+
117
+ // Object decorators need getter pattern for proper initialization
118
+ app.decorateRequest('context', null);
119
+
120
+ app.addHook('onRequest', async (request) => {
121
+ request.context = {
122
+ traceId: request.headers['x-trace-id'] || crypto.randomUUID(),
123
+ clientIp: request.ip,
124
+ userAgent: request.headers['user-agent'],
125
+ };
126
+ });
127
+ ```
128
+
129
+ ## Dependency Injection with Decorators
130
+
131
+ Use decorators for dependency injection:
132
+
133
+ ```typescript
134
+ import fp from 'fastify-plugin';
135
+
136
+ // Database plugin
137
+ export default fp(async function databasePlugin(fastify, options) {
138
+ const db = await createDatabaseConnection(options.connectionString);
139
+
140
+ fastify.decorate('db', db);
141
+
142
+ fastify.addHook('onClose', async () => {
143
+ await db.close();
144
+ });
145
+ });
146
+
147
+ // User service plugin
148
+ export default fp(async function userServicePlugin(fastify) {
149
+ // Depends on db decorator
150
+ if (!fastify.hasDecorator('db')) {
151
+ throw new Error('Database plugin must be registered first');
152
+ }
153
+
154
+ const userService = {
155
+ findById: (id: string) => fastify.db.query('SELECT * FROM users WHERE id = $1', [id]),
156
+ create: (data: CreateUserInput) => fastify.db.query(
157
+ 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
158
+ [data.name, data.email]
159
+ ),
160
+ };
161
+
162
+ fastify.decorate('userService', userService);
163
+ }, {
164
+ dependencies: ['database-plugin'],
165
+ });
166
+
167
+ // Use in routes
168
+ app.get('/users/:id', async function (request) {
169
+ const user = await this.userService.findById(request.params.id);
170
+ return user;
171
+ });
172
+ ```
173
+
174
+ ## Request Context Pattern
175
+
176
+ Build rich request context:
177
+
178
+ ```typescript
179
+ interface RequestContext {
180
+ traceId: string;
181
+ user: User | null;
182
+ permissions: Set<string>;
183
+ startTime: number;
184
+ metadata: Map<string, unknown>;
185
+ }
186
+
187
+ declare module 'fastify' {
188
+ interface FastifyRequest {
189
+ ctx: RequestContext;
190
+ }
191
+ }
192
+
193
+ app.decorateRequest('ctx', null);
194
+
195
+ app.addHook('onRequest', async (request) => {
196
+ request.ctx = {
197
+ traceId: request.headers['x-trace-id']?.toString() || crypto.randomUUID(),
198
+ user: null,
199
+ permissions: new Set(),
200
+ startTime: Date.now(),
201
+ metadata: new Map(),
202
+ };
203
+ });
204
+
205
+ // Auth hook populates user
206
+ app.addHook('preHandler', async (request) => {
207
+ const token = request.headers.authorization;
208
+ if (token) {
209
+ const user = await verifyToken(token);
210
+ request.ctx.user = user;
211
+ request.ctx.permissions = new Set(user.permissions);
212
+ }
213
+ });
214
+
215
+ // Use in handlers
216
+ app.get('/profile', async (request, reply) => {
217
+ if (!request.ctx.user) {
218
+ return reply.code(401).send({ error: 'Unauthorized' });
219
+ }
220
+
221
+ if (!request.ctx.permissions.has('read:profile')) {
222
+ return reply.code(403).send({ error: 'Forbidden' });
223
+ }
224
+
225
+ return request.ctx.user;
226
+ });
227
+ ```
228
+
229
+ ## Reply Helpers
230
+
231
+ Create consistent response methods:
232
+
233
+ ```typescript
234
+ declare module 'fastify' {
235
+ interface FastifyReply {
236
+ ok: (data?: unknown) => void;
237
+ created: (data: unknown) => void;
238
+ noContent: () => void;
239
+ badRequest: (message: string, details?: unknown) => void;
240
+ unauthorized: (message?: string) => void;
241
+ forbidden: (message?: string) => void;
242
+ notFound: (resource?: string) => void;
243
+ conflict: (message: string) => void;
244
+ serverError: (message?: string) => void;
245
+ }
246
+ }
247
+
248
+ app.decorateReply('ok', function (data?: unknown) {
249
+ this.code(200).send(data ?? { success: true });
250
+ });
251
+
252
+ app.decorateReply('created', function (data: unknown) {
253
+ this.code(201).send(data);
254
+ });
255
+
256
+ app.decorateReply('noContent', function () {
257
+ this.code(204).send();
258
+ });
259
+
260
+ app.decorateReply('badRequest', function (message: string, details?: unknown) {
261
+ this.code(400).send({
262
+ statusCode: 400,
263
+ error: 'Bad Request',
264
+ message,
265
+ details,
266
+ });
267
+ });
268
+
269
+ app.decorateReply('unauthorized', function (message = 'Authentication required') {
270
+ this.code(401).send({
271
+ statusCode: 401,
272
+ error: 'Unauthorized',
273
+ message,
274
+ });
275
+ });
276
+
277
+ app.decorateReply('notFound', function (resource = 'Resource') {
278
+ this.code(404).send({
279
+ statusCode: 404,
280
+ error: 'Not Found',
281
+ message: `${resource} not found`,
282
+ });
283
+ });
284
+
285
+ // Usage
286
+ app.get('/users/:id', async (request, reply) => {
287
+ const user = await db.users.findById(request.params.id);
288
+ if (!user) {
289
+ return reply.notFound('User');
290
+ }
291
+ return reply.ok(user);
292
+ });
293
+
294
+ app.post('/users', async (request, reply) => {
295
+ const user = await db.users.create(request.body);
296
+ return reply.created(user);
297
+ });
298
+ ```
299
+
300
+ ## Checking Decorators
301
+
302
+ Check if decorators exist before using:
303
+
304
+ ```typescript
305
+ // Check at registration time
306
+ app.register(async function (fastify) {
307
+ if (!fastify.hasDecorator('db')) {
308
+ throw new Error('Database decorator required');
309
+ }
310
+
311
+ if (!fastify.hasRequestDecorator('user')) {
312
+ throw new Error('User request decorator required');
313
+ }
314
+
315
+ if (!fastify.hasReplyDecorator('sendError')) {
316
+ throw new Error('sendError reply decorator required');
317
+ }
318
+
319
+ // Safe to use decorators
320
+ });
321
+ ```
322
+
323
+ ## Decorator Encapsulation
324
+
325
+ Decorators respect encapsulation by default:
326
+
327
+ ```typescript
328
+ app.register(async function pluginA(fastify) {
329
+ fastify.decorate('pluginAUtil', () => 'A');
330
+
331
+ fastify.get('/a', async function () {
332
+ return this.pluginAUtil(); // Works
333
+ });
334
+ });
335
+
336
+ app.register(async function pluginB(fastify) {
337
+ // this.pluginAUtil is NOT available here (encapsulated)
338
+
339
+ fastify.get('/b', async function () {
340
+ // this.pluginAUtil() would be undefined
341
+ });
342
+ });
343
+ ```
344
+
345
+ Use `fastify-plugin` to share decorators:
346
+
347
+ ```typescript
348
+ import fp from 'fastify-plugin';
349
+
350
+ export default fp(async function sharedDecorator(fastify) {
351
+ fastify.decorate('sharedUtil', () => 'shared');
352
+ });
353
+
354
+ // Now available to parent and sibling plugins
355
+ ```
356
+
357
+ ## Functional Decorators
358
+
359
+ Create decorators that return functions:
360
+
361
+ ```typescript
362
+ declare module 'fastify' {
363
+ interface FastifyInstance {
364
+ createValidator: <T>(schema: object) => (data: unknown) => T;
365
+ createRateLimiter: (options: RateLimitOptions) => RateLimiter;
366
+ }
367
+ }
368
+
369
+ app.decorate('createValidator', function <T>(schema: object) {
370
+ const validate = ajv.compile(schema);
371
+ return (data: unknown): T => {
372
+ if (!validate(data)) {
373
+ throw new ValidationError(validate.errors);
374
+ }
375
+ return data as T;
376
+ };
377
+ });
378
+
379
+ // Usage
380
+ const validateUser = app.createValidator<User>(userSchema);
381
+
382
+ app.post('/users', async (request) => {
383
+ const user = validateUser(request.body);
384
+ return db.users.create(user);
385
+ });
386
+ ```
387
+
388
+ ## Async Decorator Initialization
389
+
390
+ Handle async initialization properly:
391
+
392
+ ```typescript
393
+ import fp from 'fastify-plugin';
394
+
395
+ export default fp(async function asyncPlugin(fastify) {
396
+ // Async initialization
397
+ const connection = await createAsyncConnection();
398
+ const cache = await initializeCache();
399
+
400
+ fastify.decorate('asyncService', {
401
+ connection,
402
+ cache,
403
+ query: async (sql: string) => connection.query(sql),
404
+ });
405
+
406
+ fastify.addHook('onClose', async () => {
407
+ await connection.close();
408
+ await cache.disconnect();
409
+ });
410
+ });
411
+
412
+ // Plugin is fully initialized before routes execute
413
+ app.get('/data', async function () {
414
+ return this.asyncService.query('SELECT * FROM data');
415
+ });
416
+ ```