@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.
- package/README.md +282 -277
- package/docs/assets/install-plan.svg +29 -0
- package/docs/assets/install-skill-packages.svg +31 -0
- package/docs/assets/install-status.svg +32 -0
- package/package.json +10 -9
- package/setup-agent-toolkit.sh +1 -1
- package/skills/backend/fastify-best-practices/LICENSE +21 -0
- package/skills/backend/fastify-best-practices/NOTICE.md +11 -0
- package/skills/backend/fastify-best-practices/SKILL.md +75 -0
- package/skills/backend/fastify-best-practices/rules/authentication.md +521 -0
- package/skills/backend/fastify-best-practices/rules/configuration.md +217 -0
- package/skills/backend/fastify-best-practices/rules/content-type.md +387 -0
- package/skills/backend/fastify-best-practices/rules/cors-security.md +445 -0
- package/skills/backend/fastify-best-practices/rules/database.md +320 -0
- package/skills/backend/fastify-best-practices/rules/decorators.md +416 -0
- package/skills/backend/fastify-best-practices/rules/deployment.md +423 -0
- package/skills/backend/fastify-best-practices/rules/error-handling.md +412 -0
- package/skills/backend/fastify-best-practices/rules/hooks.md +464 -0
- package/skills/backend/fastify-best-practices/rules/http-proxy.md +247 -0
- package/skills/backend/fastify-best-practices/rules/logging.md +402 -0
- package/skills/backend/fastify-best-practices/rules/performance.md +425 -0
- package/skills/backend/fastify-best-practices/rules/plugins.md +320 -0
- package/skills/backend/fastify-best-practices/rules/routes.md +467 -0
- package/skills/backend/fastify-best-practices/rules/schemas.md +585 -0
- package/skills/backend/fastify-best-practices/rules/serialization.md +475 -0
- package/skills/backend/fastify-best-practices/rules/testing.md +536 -0
- package/skills/backend/fastify-best-practices/rules/typescript.md +458 -0
- package/skills/backend/fastify-best-practices/rules/websockets.md +421 -0
- package/skills/backend/fastify-best-practices/tile.json +11 -0
- package/skills/core/agent-toolkit-maintainer/SKILL.md +16 -14
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: database
|
|
3
|
+
description: Database integration with Fastify using official adapters
|
|
4
|
+
metadata:
|
|
5
|
+
tags: database, postgres, mysql, mongodb, redis, sql
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Database Integration
|
|
9
|
+
|
|
10
|
+
## Use Official Fastify Database Adapters
|
|
11
|
+
|
|
12
|
+
Always use the official Fastify database plugins from the `@fastify` organization. They provide proper connection pooling, encapsulation, and integration with Fastify's lifecycle.
|
|
13
|
+
|
|
14
|
+
## PostgreSQL with @fastify/postgres
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import Fastify from 'fastify';
|
|
18
|
+
import fastifyPostgres from '@fastify/postgres';
|
|
19
|
+
|
|
20
|
+
const app = Fastify({ logger: true });
|
|
21
|
+
|
|
22
|
+
app.register(fastifyPostgres, {
|
|
23
|
+
connectionString: process.env.DATABASE_URL,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Use in routes
|
|
27
|
+
app.get('/users', async (request) => {
|
|
28
|
+
const client = await app.pg.connect();
|
|
29
|
+
try {
|
|
30
|
+
const { rows } = await client.query('SELECT * FROM users');
|
|
31
|
+
return rows;
|
|
32
|
+
} finally {
|
|
33
|
+
client.release();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Or use the pool directly for simple queries
|
|
38
|
+
app.get('/users/:id', async (request) => {
|
|
39
|
+
const { id } = request.params;
|
|
40
|
+
const { rows } = await app.pg.query(
|
|
41
|
+
'SELECT * FROM users WHERE id = $1',
|
|
42
|
+
[id],
|
|
43
|
+
);
|
|
44
|
+
return rows[0];
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Transactions
|
|
48
|
+
app.post('/transfer', async (request) => {
|
|
49
|
+
const { fromId, toId, amount } = request.body;
|
|
50
|
+
const client = await app.pg.connect();
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
await client.query('BEGIN');
|
|
54
|
+
await client.query(
|
|
55
|
+
'UPDATE accounts SET balance = balance - $1 WHERE id = $2',
|
|
56
|
+
[amount, fromId],
|
|
57
|
+
);
|
|
58
|
+
await client.query(
|
|
59
|
+
'UPDATE accounts SET balance = balance + $1 WHERE id = $2',
|
|
60
|
+
[amount, toId],
|
|
61
|
+
);
|
|
62
|
+
await client.query('COMMIT');
|
|
63
|
+
return { success: true };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
await client.query('ROLLBACK');
|
|
66
|
+
throw error;
|
|
67
|
+
} finally {
|
|
68
|
+
client.release();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## MySQL with @fastify/mysql
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import Fastify from 'fastify';
|
|
77
|
+
import fastifyMysql from '@fastify/mysql';
|
|
78
|
+
|
|
79
|
+
const app = Fastify({ logger: true });
|
|
80
|
+
|
|
81
|
+
app.register(fastifyMysql, {
|
|
82
|
+
promise: true,
|
|
83
|
+
connectionString: process.env.MYSQL_URL,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
app.get('/users', async (request) => {
|
|
87
|
+
const connection = await app.mysql.getConnection();
|
|
88
|
+
try {
|
|
89
|
+
const [rows] = await connection.query('SELECT * FROM users');
|
|
90
|
+
return rows;
|
|
91
|
+
} finally {
|
|
92
|
+
connection.release();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## MongoDB with @fastify/mongodb
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import Fastify from 'fastify';
|
|
101
|
+
import fastifyMongo from '@fastify/mongodb';
|
|
102
|
+
|
|
103
|
+
const app = Fastify({ logger: true });
|
|
104
|
+
|
|
105
|
+
app.register(fastifyMongo, {
|
|
106
|
+
url: process.env.MONGODB_URL,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
app.get('/users', async (request) => {
|
|
110
|
+
const users = await app.mongo.db
|
|
111
|
+
.collection('users')
|
|
112
|
+
.find({})
|
|
113
|
+
.toArray();
|
|
114
|
+
return users;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
app.get('/users/:id', async (request) => {
|
|
118
|
+
const { id } = request.params;
|
|
119
|
+
const user = await app.mongo.db
|
|
120
|
+
.collection('users')
|
|
121
|
+
.findOne({ _id: new app.mongo.ObjectId(id) });
|
|
122
|
+
return user;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
app.post('/users', async (request) => {
|
|
126
|
+
const result = await app.mongo.db
|
|
127
|
+
.collection('users')
|
|
128
|
+
.insertOne(request.body);
|
|
129
|
+
return { id: result.insertedId };
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Redis with @fastify/redis
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import Fastify from 'fastify';
|
|
137
|
+
import fastifyRedis from '@fastify/redis';
|
|
138
|
+
|
|
139
|
+
const app = Fastify({ logger: true });
|
|
140
|
+
|
|
141
|
+
app.register(fastifyRedis, {
|
|
142
|
+
url: process.env.REDIS_URL,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Caching example
|
|
146
|
+
app.get('/data/:key', async (request) => {
|
|
147
|
+
const { key } = request.params;
|
|
148
|
+
|
|
149
|
+
// Try cache first
|
|
150
|
+
const cached = await app.redis.get(`cache:${key}`);
|
|
151
|
+
if (cached) {
|
|
152
|
+
return JSON.parse(cached);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Fetch from database
|
|
156
|
+
const data = await fetchFromDatabase(key);
|
|
157
|
+
|
|
158
|
+
// Cache for 5 minutes
|
|
159
|
+
await app.redis.setex(`cache:${key}`, 300, JSON.stringify(data));
|
|
160
|
+
|
|
161
|
+
return data;
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Database as Plugin
|
|
166
|
+
|
|
167
|
+
Encapsulate database access in a plugin:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// plugins/database.ts
|
|
171
|
+
import fp from 'fastify-plugin';
|
|
172
|
+
import fastifyPostgres from '@fastify/postgres';
|
|
173
|
+
|
|
174
|
+
export default fp(async function databasePlugin(fastify) {
|
|
175
|
+
await fastify.register(fastifyPostgres, {
|
|
176
|
+
connectionString: fastify.config.DATABASE_URL,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Add health check
|
|
180
|
+
fastify.decorate('checkDatabaseHealth', async () => {
|
|
181
|
+
try {
|
|
182
|
+
await fastify.pg.query('SELECT 1');
|
|
183
|
+
return true;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}, {
|
|
189
|
+
name: 'database',
|
|
190
|
+
dependencies: ['config'],
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Repository Pattern
|
|
195
|
+
|
|
196
|
+
Abstract database access with repositories:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// repositories/user.repository.ts
|
|
200
|
+
import type { FastifyInstance } from 'fastify';
|
|
201
|
+
|
|
202
|
+
export interface User {
|
|
203
|
+
id: string;
|
|
204
|
+
email: string;
|
|
205
|
+
name: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function createUserRepository(app: FastifyInstance) {
|
|
209
|
+
return {
|
|
210
|
+
async findById(id: string): Promise<User | null> {
|
|
211
|
+
const { rows } = await app.pg.query(
|
|
212
|
+
'SELECT * FROM users WHERE id = $1',
|
|
213
|
+
[id],
|
|
214
|
+
);
|
|
215
|
+
return rows[0] || null;
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
219
|
+
const { rows } = await app.pg.query(
|
|
220
|
+
'SELECT * FROM users WHERE email = $1',
|
|
221
|
+
[email],
|
|
222
|
+
);
|
|
223
|
+
return rows[0] || null;
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
async create(data: Omit<User, 'id'>): Promise<User> {
|
|
227
|
+
const { rows } = await app.pg.query(
|
|
228
|
+
'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *',
|
|
229
|
+
[data.email, data.name],
|
|
230
|
+
);
|
|
231
|
+
return rows[0];
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
async update(id: string, data: Partial<User>): Promise<User | null> {
|
|
235
|
+
const fields = Object.keys(data);
|
|
236
|
+
const values = Object.values(data);
|
|
237
|
+
const setClause = fields
|
|
238
|
+
.map((f, i) => `${f} = $${i + 2}`)
|
|
239
|
+
.join(', ');
|
|
240
|
+
|
|
241
|
+
const { rows } = await app.pg.query(
|
|
242
|
+
`UPDATE users SET ${setClause} WHERE id = $1 RETURNING *`,
|
|
243
|
+
[id, ...values],
|
|
244
|
+
);
|
|
245
|
+
return rows[0] || null;
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
async delete(id: string): Promise<boolean> {
|
|
249
|
+
const { rowCount } = await app.pg.query(
|
|
250
|
+
'DELETE FROM users WHERE id = $1',
|
|
251
|
+
[id],
|
|
252
|
+
);
|
|
253
|
+
return rowCount > 0;
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Usage in plugin
|
|
259
|
+
import fp from 'fastify-plugin';
|
|
260
|
+
import { createUserRepository } from './repositories/user.repository.js';
|
|
261
|
+
|
|
262
|
+
export default fp(async function repositoriesPlugin(fastify) {
|
|
263
|
+
fastify.decorate('repositories', {
|
|
264
|
+
users: createUserRepository(fastify),
|
|
265
|
+
});
|
|
266
|
+
}, {
|
|
267
|
+
name: 'repositories',
|
|
268
|
+
dependencies: ['database'],
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Testing with Database
|
|
273
|
+
|
|
274
|
+
Use transactions for test isolation:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
278
|
+
import { build } from './app.js';
|
|
279
|
+
|
|
280
|
+
describe('User API', () => {
|
|
281
|
+
let app;
|
|
282
|
+
let client;
|
|
283
|
+
|
|
284
|
+
beforeEach(async () => {
|
|
285
|
+
app = await build();
|
|
286
|
+
client = await app.pg.connect();
|
|
287
|
+
await client.query('BEGIN');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
afterEach(async () => {
|
|
291
|
+
await client.query('ROLLBACK');
|
|
292
|
+
client.release();
|
|
293
|
+
await app.close();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should create a user', async (t) => {
|
|
297
|
+
const response = await app.inject({
|
|
298
|
+
method: 'POST',
|
|
299
|
+
url: '/users',
|
|
300
|
+
payload: { email: 'test@example.com', name: 'Test' },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
t.assert.equal(response.statusCode, 201);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Connection Pool Configuration
|
|
309
|
+
|
|
310
|
+
Configure connection pools appropriately:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
app.register(fastifyPostgres, {
|
|
314
|
+
connectionString: process.env.DATABASE_URL,
|
|
315
|
+
// Pool configuration
|
|
316
|
+
max: 20, // Maximum pool size
|
|
317
|
+
idleTimeoutMillis: 30000, // Close idle clients after 30s
|
|
318
|
+
connectionTimeoutMillis: 5000, // Timeout for new connections
|
|
319
|
+
});
|
|
320
|
+
```
|