@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,423 @@
1
+ ---
2
+ name: deployment
3
+ description: Production deployment for Fastify applications
4
+ metadata:
5
+ tags: deployment, production, docker, kubernetes, scaling
6
+ ---
7
+
8
+ # Production Deployment
9
+
10
+ ## Graceful Shutdown with close-with-grace
11
+
12
+ Use `close-with-grace` for proper shutdown handling:
13
+
14
+ ```typescript
15
+ import Fastify from 'fastify';
16
+ import closeWithGrace from 'close-with-grace';
17
+
18
+ const app = Fastify({ logger: true });
19
+
20
+ // Register plugins and routes
21
+ await app.register(import('./plugins/index.js'));
22
+ await app.register(import('./routes/index.js'));
23
+
24
+ // Graceful shutdown handler
25
+ closeWithGrace({ delay: 10000 }, async ({ signal, err }) => {
26
+ if (err) {
27
+ app.log.error({ err }, 'Server closing due to error');
28
+ } else {
29
+ app.log.info({ signal }, 'Server closing due to signal');
30
+ }
31
+
32
+ await app.close();
33
+ });
34
+
35
+ // Start server
36
+ await app.listen({
37
+ port: parseInt(process.env.PORT || '3000', 10),
38
+ host: '0.0.0.0',
39
+ });
40
+
41
+ app.log.info(`Server listening on ${app.server.address()}`);
42
+ ```
43
+
44
+ ## Health Check Endpoints
45
+
46
+ Implement comprehensive health checks:
47
+
48
+ ```typescript
49
+ app.get('/health', async () => {
50
+ return { status: 'ok', timestamp: new Date().toISOString() };
51
+ });
52
+
53
+ app.get('/health/live', async () => {
54
+ return { status: 'ok' };
55
+ });
56
+
57
+ app.get('/health/ready', async (request, reply) => {
58
+ const checks = {
59
+ database: false,
60
+ cache: false,
61
+ };
62
+
63
+ try {
64
+ await app.db`SELECT 1`;
65
+ checks.database = true;
66
+ } catch {
67
+ // Database not ready
68
+ }
69
+
70
+ try {
71
+ await app.cache.ping();
72
+ checks.cache = true;
73
+ } catch {
74
+ // Cache not ready
75
+ }
76
+
77
+ const allHealthy = Object.values(checks).every(Boolean);
78
+
79
+ if (!allHealthy) {
80
+ reply.code(503);
81
+ }
82
+
83
+ return {
84
+ status: allHealthy ? 'ok' : 'degraded',
85
+ checks,
86
+ timestamp: new Date().toISOString(),
87
+ };
88
+ });
89
+
90
+ // Detailed health for monitoring
91
+ app.get('/health/details', {
92
+ preHandler: [app.authenticate, app.requireAdmin],
93
+ }, async () => {
94
+ const memory = process.memoryUsage();
95
+
96
+ return {
97
+ status: 'ok',
98
+ uptime: process.uptime(),
99
+ memory: {
100
+ heapUsed: Math.round(memory.heapUsed / 1024 / 1024),
101
+ heapTotal: Math.round(memory.heapTotal / 1024 / 1024),
102
+ rss: Math.round(memory.rss / 1024 / 1024),
103
+ },
104
+ version: process.env.APP_VERSION,
105
+ nodeVersion: process.version,
106
+ };
107
+ });
108
+ ```
109
+ ## Docker Configuration
110
+
111
+ Create an optimized Dockerfile:
112
+
113
+ ```dockerfile
114
+ # Build stage
115
+ FROM node:22-alpine AS builder
116
+
117
+ WORKDIR /app
118
+
119
+ COPY package*.json ./
120
+ RUN npm ci --only=production
121
+
122
+ COPY . .
123
+
124
+ # Production stage
125
+ FROM node:22-alpine
126
+
127
+ WORKDIR /app
128
+
129
+ # Run as non-root user
130
+ RUN addgroup -g 1001 -S nodejs && \
131
+ adduser -S nodejs -u 1001
132
+
133
+ # Copy from builder
134
+ COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
135
+ COPY --from=builder --chown=nodejs:nodejs /app/src ./src
136
+ COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
137
+
138
+ USER nodejs
139
+
140
+ EXPOSE 3000
141
+
142
+ ENV NODE_ENV=production
143
+ ENV PORT=3000
144
+
145
+ # Health check
146
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
147
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
148
+
149
+ CMD ["node", "src/app.ts"]
150
+ ```
151
+
152
+ ```yaml
153
+ # docker-compose.yml
154
+ services:
155
+ api:
156
+ build: .
157
+ ports:
158
+ - "3000:3000"
159
+ environment:
160
+ - NODE_ENV=production
161
+ - DATABASE_URL=postgres://user:pass@db:5432/app
162
+ - JWT_SECRET=${JWT_SECRET}
163
+ depends_on:
164
+ db:
165
+ condition: service_healthy
166
+ restart: unless-stopped
167
+
168
+ db:
169
+ image: postgres:16-alpine
170
+ environment:
171
+ - POSTGRES_USER=user
172
+ - POSTGRES_PASSWORD=pass
173
+ - POSTGRES_DB=app
174
+ volumes:
175
+ - pgdata:/var/lib/postgresql/data
176
+ healthcheck:
177
+ test: ["CMD-SHELL", "pg_isready -U user -d app"]
178
+ interval: 5s
179
+ timeout: 5s
180
+ retries: 5
181
+
182
+ volumes:
183
+ pgdata:
184
+ ```
185
+
186
+ ## Kubernetes Deployment
187
+
188
+ Deploy to Kubernetes:
189
+
190
+ ```yaml
191
+ # deployment.yaml
192
+ apiVersion: apps/v1
193
+ kind: Deployment
194
+ metadata:
195
+ name: fastify-api
196
+ spec:
197
+ replicas: 3
198
+ selector:
199
+ matchLabels:
200
+ app: fastify-api
201
+ template:
202
+ metadata:
203
+ labels:
204
+ app: fastify-api
205
+ spec:
206
+ containers:
207
+ - name: api
208
+ image: my-registry/fastify-api:latest
209
+ ports:
210
+ - containerPort: 3000
211
+ env:
212
+ - name: NODE_ENV
213
+ value: "production"
214
+ - name: DATABASE_URL
215
+ valueFrom:
216
+ secretKeyRef:
217
+ name: api-secrets
218
+ key: database-url
219
+ resources:
220
+ requests:
221
+ memory: "256Mi"
222
+ cpu: "100m"
223
+ limits:
224
+ memory: "512Mi"
225
+ cpu: "500m"
226
+ livenessProbe:
227
+ httpGet:
228
+ path: /health/live
229
+ port: 3000
230
+ initialDelaySeconds: 5
231
+ periodSeconds: 10
232
+ readinessProbe:
233
+ httpGet:
234
+ path: /health/ready
235
+ port: 3000
236
+ initialDelaySeconds: 5
237
+ periodSeconds: 5
238
+ lifecycle:
239
+ preStop:
240
+ exec:
241
+ command: ["/bin/sh", "-c", "sleep 5"]
242
+ ---
243
+ apiVersion: v1
244
+ kind: Service
245
+ metadata:
246
+ name: fastify-api
247
+ spec:
248
+ selector:
249
+ app: fastify-api
250
+ ports:
251
+ - port: 80
252
+ targetPort: 3000
253
+ type: ClusterIP
254
+ ```
255
+
256
+ ## Production Logger Configuration
257
+
258
+ Configure logging for production:
259
+
260
+ ```typescript
261
+ import Fastify from 'fastify';
262
+
263
+ const app = Fastify({
264
+ logger: {
265
+ level: process.env.LOG_LEVEL || 'info',
266
+ // JSON output for log aggregation
267
+ formatters: {
268
+ level: (label) => ({ level: label }),
269
+ bindings: (bindings) => ({
270
+ pid: bindings.pid,
271
+ hostname: bindings.hostname,
272
+ service: 'fastify-api',
273
+ version: process.env.APP_VERSION,
274
+ }),
275
+ },
276
+ timestamp: () => `,"time":"${new Date().toISOString()}"`,
277
+ // Redact sensitive data
278
+ redact: {
279
+ paths: [
280
+ 'req.headers.authorization',
281
+ 'req.headers.cookie',
282
+ '*.password',
283
+ '*.token',
284
+ '*.secret',
285
+ ],
286
+ censor: '[REDACTED]',
287
+ },
288
+ },
289
+ });
290
+ ```
291
+
292
+ ## Request Timeouts
293
+
294
+ Configure appropriate timeouts:
295
+
296
+ ```typescript
297
+ const app = Fastify({
298
+ connectionTimeout: 30000, // 30s connection timeout
299
+ keepAliveTimeout: 72000, // 72s keep-alive (longer than ALB 60s)
300
+ requestTimeout: 30000, // 30s request timeout
301
+ bodyLimit: 1048576, // 1MB body limit
302
+ });
303
+
304
+ // Per-route timeout
305
+ app.get('/long-operation', {
306
+ config: {
307
+ timeout: 60000, // 60s for this route
308
+ },
309
+ }, longOperationHandler);
310
+ ```
311
+
312
+ ## Trust Proxy Settings
313
+
314
+ Configure for load balancers:
315
+
316
+ ```typescript
317
+ const app = Fastify({
318
+ // Trust first proxy (load balancer)
319
+ trustProxy: true,
320
+
321
+ // Or trust specific proxies
322
+ trustProxy: ['127.0.0.1', '10.0.0.0/8'],
323
+
324
+ // Or number of proxies to trust
325
+ trustProxy: 1,
326
+ });
327
+
328
+ // Now request.ip returns real client IP
329
+ ```
330
+
331
+ ## Static File Serving
332
+
333
+ Serve static files efficiently. **Always use `import.meta.dirname` as the base path**, never `process.cwd()`:
334
+
335
+ ```typescript
336
+ import fastifyStatic from '@fastify/static';
337
+ import { join } from 'node:path';
338
+
339
+ app.register(fastifyStatic, {
340
+ root: join(import.meta.dirname, '..', 'public'),
341
+ prefix: '/static/',
342
+ maxAge: '1d',
343
+ immutable: true,
344
+ etag: true,
345
+ lastModified: true,
346
+ });
347
+ ```
348
+
349
+ ## Compression
350
+
351
+ Enable response compression:
352
+
353
+ ```typescript
354
+ import fastifyCompress from '@fastify/compress';
355
+
356
+ app.register(fastifyCompress, {
357
+ global: true,
358
+ threshold: 1024, // Only compress > 1KB
359
+ encodings: ['gzip', 'deflate'],
360
+ });
361
+ ```
362
+
363
+ ## Metrics and Monitoring
364
+
365
+ Expose Prometheus metrics:
366
+
367
+ ```typescript
368
+ import { register, collectDefaultMetrics, Counter, Histogram } from 'prom-client';
369
+
370
+ collectDefaultMetrics();
371
+
372
+ const httpRequestDuration = new Histogram({
373
+ name: 'http_request_duration_seconds',
374
+ help: 'Duration of HTTP requests in seconds',
375
+ labelNames: ['method', 'route', 'status'],
376
+ buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
377
+ });
378
+
379
+ const httpRequestTotal = new Counter({
380
+ name: 'http_requests_total',
381
+ help: 'Total number of HTTP requests',
382
+ labelNames: ['method', 'route', 'status'],
383
+ });
384
+
385
+ app.addHook('onResponse', (request, reply, done) => {
386
+ const route = request.routeOptions.url || request.url;
387
+ const labels = {
388
+ method: request.method,
389
+ route,
390
+ status: reply.statusCode,
391
+ };
392
+
393
+ httpRequestDuration.observe(labels, reply.elapsedTime / 1000);
394
+ httpRequestTotal.inc(labels);
395
+ done();
396
+ });
397
+
398
+ app.get('/metrics', async (request, reply) => {
399
+ reply.header('Content-Type', register.contentType);
400
+ return register.metrics();
401
+ });
402
+ ```
403
+
404
+ ## Zero-Downtime Deployments
405
+
406
+ Support rolling updates:
407
+
408
+ ```typescript
409
+ import closeWithGrace from 'close-with-grace';
410
+
411
+ // Stop accepting new connections gracefully
412
+ closeWithGrace({ delay: 30000 }, async ({ signal }) => {
413
+ app.log.info({ signal }, 'Received shutdown signal');
414
+
415
+ // Stop accepting new connections
416
+ // Existing connections continue to be served
417
+
418
+ // Wait for in-flight requests (handled by close-with-grace delay)
419
+ await app.close();
420
+
421
+ app.log.info('Server closed');
422
+ });
423
+ ```