@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,425 @@
1
+ ---
2
+ name: performance
3
+ description: Performance optimization for Fastify applications
4
+ metadata:
5
+ tags: performance, optimization, speed, benchmarking
6
+ ---
7
+
8
+ # Performance Optimization
9
+
10
+ ## Fastify is Fast by Default
11
+
12
+ Fastify is designed for performance. Key optimizations are built-in:
13
+
14
+ - Fast JSON serialization with `fast-json-stringify`
15
+ - Efficient routing with `find-my-way`
16
+ - Schema-based validation with `ajv` (compiled validators)
17
+ - Low overhead request/response handling
18
+
19
+ ## Use @fastify/under-pressure for Load Shedding
20
+
21
+ Protect your application from overload with `@fastify/under-pressure`:
22
+
23
+ ```typescript
24
+ import underPressure from '@fastify/under-pressure';
25
+
26
+ app.register(underPressure, {
27
+ maxEventLoopDelay: 1000, // Max event loop delay in ms
28
+ maxHeapUsedBytes: 1000000000, // Max heap used (~1GB)
29
+ maxRssBytes: 1500000000, // Max RSS (~1.5GB)
30
+ maxEventLoopUtilization: 0.98, // Max event loop utilization
31
+ pressureHandler: (request, reply, type, value) => {
32
+ reply.code(503).send({
33
+ error: 'Service Unavailable',
34
+ message: `Server under pressure: ${type}`,
35
+ });
36
+ },
37
+ });
38
+
39
+ // Health check that respects pressure
40
+ app.get('/health', async (request, reply) => {
41
+ return { status: 'ok' };
42
+ });
43
+ ```
44
+
45
+ ## Always Define Response Schemas
46
+
47
+ Response schemas enable fast-json-stringify, which is significantly faster than JSON.stringify:
48
+
49
+ ```typescript
50
+ // FAST - uses fast-json-stringify
51
+ app.get('/users', {
52
+ schema: {
53
+ response: {
54
+ 200: {
55
+ type: 'array',
56
+ items: {
57
+ type: 'object',
58
+ properties: {
59
+ id: { type: 'string' },
60
+ name: { type: 'string' },
61
+ email: { type: 'string' },
62
+ },
63
+ },
64
+ },
65
+ },
66
+ },
67
+ }, async () => {
68
+ return db.users.findAll();
69
+ });
70
+
71
+ // SLOW - uses JSON.stringify
72
+ app.get('/users-slow', async () => {
73
+ return db.users.findAll();
74
+ });
75
+ ```
76
+
77
+ ## Avoid Dynamic Schema Compilation
78
+
79
+ Add schemas at startup, not at request time:
80
+
81
+ ```typescript
82
+ // GOOD - schemas compiled at startup
83
+ app.addSchema({ $id: 'user', ... });
84
+
85
+ app.get('/users', {
86
+ schema: { response: { 200: { $ref: 'user#' } } },
87
+ }, handler);
88
+
89
+ // BAD - schema compiled per request
90
+ app.get('/users', async (request, reply) => {
91
+ const schema = getSchemaForUser(request.user);
92
+ // This is slow!
93
+ });
94
+ ```
95
+
96
+ ## Use Logger Wisely
97
+
98
+ Pino is fast, but excessive logging has overhead:
99
+
100
+ ```typescript
101
+ import Fastify from 'fastify';
102
+
103
+ // Set log level via environment variable
104
+ const app = Fastify({
105
+ logger: {
106
+ level: process.env.LOG_LEVEL || 'info',
107
+ },
108
+ });
109
+
110
+ // Avoid logging large objects
111
+ app.get('/data', async (request) => {
112
+ // BAD - logs entire payload
113
+ request.log.info({ data: largeObject }, 'Processing');
114
+
115
+ // GOOD - log only what's needed
116
+ request.log.info({ id: largeObject.id }, 'Processing');
117
+
118
+ return largeObject;
119
+ });
120
+ ```
121
+
122
+ ## Connection Pooling
123
+
124
+ Use connection pools for databases:
125
+
126
+ ```typescript
127
+ import postgres from 'postgres';
128
+
129
+ // Create pool at startup
130
+ const sql = postgres(process.env.DATABASE_URL, {
131
+ max: 20, // Maximum pool size
132
+ idle_timeout: 20,
133
+ connect_timeout: 10,
134
+ });
135
+
136
+ app.decorate('db', sql);
137
+
138
+ // Connections are reused
139
+ app.get('/users', async () => {
140
+ return app.db`SELECT * FROM users LIMIT 100`;
141
+ });
142
+ ```
143
+
144
+ ## Avoid Blocking the Event Loop
145
+
146
+ Use `piscina` for CPU-intensive operations. It provides a robust worker thread pool:
147
+
148
+ ```typescript
149
+ import Piscina from 'piscina';
150
+ import { join } from 'node:path';
151
+
152
+ const piscina = new Piscina({
153
+ filename: join(import.meta.dirname, 'workers', 'compute.js'),
154
+ });
155
+
156
+ app.post('/compute', async (request) => {
157
+ const result = await piscina.run(request.body);
158
+ return result;
159
+ });
160
+ ```
161
+
162
+ ```typescript
163
+ // workers/compute.js
164
+ export default function compute(data) {
165
+ // CPU-intensive work here
166
+ return processedResult;
167
+ }
168
+ ```
169
+
170
+ ## Stream Large Responses
171
+
172
+ Stream large payloads instead of buffering:
173
+
174
+ ```typescript
175
+ import { createReadStream } from 'node:fs';
176
+ import { pipeline } from 'node:stream/promises';
177
+
178
+ // GOOD - stream file
179
+ app.get('/large-file', async (request, reply) => {
180
+ const stream = createReadStream('./large-file.json');
181
+ reply.type('application/json');
182
+ return reply.send(stream);
183
+ });
184
+
185
+ // BAD - load entire file into memory
186
+ app.get('/large-file-bad', async () => {
187
+ const content = await fs.readFile('./large-file.json', 'utf-8');
188
+ return JSON.parse(content);
189
+ });
190
+
191
+ // Stream database results
192
+ app.get('/export', async (request, reply) => {
193
+ reply.type('application/json');
194
+
195
+ const cursor = db.users.findCursor();
196
+ reply.raw.write('[');
197
+
198
+ let first = true;
199
+ for await (const user of cursor) {
200
+ if (!first) reply.raw.write(',');
201
+ reply.raw.write(JSON.stringify(user));
202
+ first = false;
203
+ }
204
+
205
+ reply.raw.write(']');
206
+ reply.raw.end();
207
+ });
208
+ ```
209
+
210
+ ## Caching Strategies
211
+
212
+ Implement caching for expensive operations:
213
+
214
+ ```typescript
215
+ import { LRUCache } from 'lru-cache';
216
+
217
+ const cache = new LRUCache<string, unknown>({
218
+ max: 1000,
219
+ ttl: 60000, // 1 minute
220
+ });
221
+
222
+ app.get('/expensive/:id', async (request) => {
223
+ const { id } = request.params;
224
+ const cacheKey = `expensive:${id}`;
225
+
226
+ const cached = cache.get(cacheKey);
227
+ if (cached) {
228
+ return cached;
229
+ }
230
+
231
+ const result = await expensiveOperation(id);
232
+ cache.set(cacheKey, result);
233
+
234
+ return result;
235
+ });
236
+
237
+ // Cache control headers
238
+ app.get('/static-data', async (request, reply) => {
239
+ reply.header('Cache-Control', 'public, max-age=3600');
240
+ return { data: 'static' };
241
+ });
242
+ ```
243
+
244
+ ## Request Coalescing with async-cache-dedupe
245
+
246
+ Use `async-cache-dedupe` for deduplicating concurrent identical requests and caching:
247
+
248
+ ```typescript
249
+ import { createCache } from 'async-cache-dedupe';
250
+
251
+ const cache = createCache({
252
+ ttl: 60, // seconds
253
+ stale: 5, // serve stale while revalidating
254
+ storage: { type: 'memory' },
255
+ });
256
+
257
+ cache.define('fetchData', async (id: string) => {
258
+ return db.findById(id);
259
+ });
260
+
261
+ app.get('/data/:id', async (request) => {
262
+ const { id } = request.params;
263
+ // Automatically deduplicates concurrent requests for the same id
264
+ // and caches the result
265
+ return cache.fetchData(id);
266
+ });
267
+ ```
268
+
269
+ For distributed caching, use Redis storage:
270
+
271
+ ```typescript
272
+ import { createCache } from 'async-cache-dedupe';
273
+ import Redis from 'ioredis';
274
+
275
+ const redis = new Redis(process.env.REDIS_URL);
276
+
277
+ const cache = createCache({
278
+ ttl: 60,
279
+ storage: { type: 'redis', options: { client: redis } },
280
+ });
281
+ ```
282
+
283
+ ## Payload Limits
284
+
285
+ Set appropriate payload limits:
286
+
287
+ ```typescript
288
+ import Fastify from 'fastify';
289
+
290
+ const app = Fastify({
291
+ bodyLimit: 1048576, // 1MB default
292
+ });
293
+
294
+ // Per-route limit for file uploads
295
+ app.post('/upload', {
296
+ bodyLimit: 10485760, // 10MB for this route
297
+ }, uploadHandler);
298
+ ```
299
+
300
+ ## Compression
301
+
302
+ Use compression for responses:
303
+
304
+ ```typescript
305
+ import fastifyCompress from '@fastify/compress';
306
+
307
+ app.register(fastifyCompress, {
308
+ global: true,
309
+ threshold: 1024, // Only compress responses > 1KB
310
+ encodings: ['gzip', 'deflate'],
311
+ });
312
+
313
+ // Disable for specific route
314
+ app.get('/already-compressed', {
315
+ compress: false,
316
+ }, handler);
317
+ ```
318
+
319
+ ## Connection Timeouts
320
+
321
+ Configure appropriate timeouts:
322
+
323
+ ```typescript
324
+ import Fastify from 'fastify';
325
+
326
+ const app = Fastify({
327
+ connectionTimeout: 30000, // 30 seconds
328
+ keepAliveTimeout: 5000, // 5 seconds
329
+ });
330
+
331
+ // Per-route timeout
332
+ app.get('/long-operation', {
333
+ config: {
334
+ timeout: 60000, // 60 seconds
335
+ },
336
+ }, async (request) => {
337
+ return longOperation();
338
+ });
339
+ ```
340
+
341
+ ## Disable Unnecessary Features
342
+
343
+ Disable features you don't need:
344
+
345
+ ```typescript
346
+ import Fastify from 'fastify';
347
+
348
+ const app = Fastify({
349
+ disableRequestLogging: true, // If you don't need request logs
350
+ trustProxy: false, // If not behind proxy
351
+ caseSensitive: true, // Enable for slight performance gain
352
+ ignoreDuplicateSlashes: false,
353
+ });
354
+ ```
355
+
356
+ ## Benchmarking
357
+
358
+ Use autocannon for load testing:
359
+
360
+ ```bash
361
+ # Install
362
+ npm install -g autocannon
363
+
364
+ # Basic benchmark
365
+ autocannon http://localhost:3000/api/users
366
+
367
+ # With options
368
+ autocannon -c 100 -d 30 -p 10 http://localhost:3000/api/users
369
+ # -c: connections
370
+ # -d: duration in seconds
371
+ # -p: pipelining factor
372
+ ```
373
+
374
+ ```typescript
375
+ // Programmatic benchmarking
376
+ import autocannon from 'autocannon';
377
+
378
+ const result = await autocannon({
379
+ url: 'http://localhost:3000/api/users',
380
+ connections: 100,
381
+ duration: 30,
382
+ pipelining: 10,
383
+ });
384
+
385
+ console.log(autocannon.printResult(result));
386
+ ```
387
+
388
+ ## Profiling
389
+
390
+ Use `@platformatic/flame` for flame graph profiling:
391
+
392
+ ```bash
393
+ npx @platformatic/flame app.js
394
+ ```
395
+
396
+ This generates an interactive flame graph to identify performance bottlenecks.
397
+
398
+ ## Memory Management
399
+
400
+ Monitor and optimize memory usage:
401
+
402
+ ```typescript
403
+ // Add health endpoint with memory info
404
+ app.get('/health', async () => {
405
+ const memory = process.memoryUsage();
406
+ return {
407
+ status: 'ok',
408
+ memory: {
409
+ heapUsed: Math.round(memory.heapUsed / 1024 / 1024) + 'MB',
410
+ heapTotal: Math.round(memory.heapTotal / 1024 / 1024) + 'MB',
411
+ rss: Math.round(memory.rss / 1024 / 1024) + 'MB',
412
+ },
413
+ };
414
+ });
415
+
416
+ // Avoid memory leaks in closures
417
+ app.addHook('onRequest', async (request) => {
418
+ // BAD - holding reference to large object
419
+ const largeData = await loadLargeData();
420
+ request.getData = () => largeData;
421
+
422
+ // GOOD - load on demand
423
+ request.getData = () => loadLargeData();
424
+ });
425
+ ```