outlet-orm 6.5.0 → 9.0.0
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 +130 -2
- package/bin/init.js +122 -0
- package/bin/mcp.js +78 -0
- package/bin/migrate.js +25 -0
- package/docs/skills/outlet-orm/ADVANCED.md +575 -0
- package/docs/skills/outlet-orm/AI.md +220 -0
- package/docs/skills/outlet-orm/API.md +522 -0
- package/docs/skills/outlet-orm/BACKUP.md +150 -0
- package/docs/skills/outlet-orm/MIGRATIONS.md +605 -0
- package/docs/skills/outlet-orm/MODELS.md +427 -0
- package/docs/skills/outlet-orm/QUERIES.md +345 -0
- package/docs/skills/outlet-orm/RELATIONS.md +555 -0
- package/docs/skills/outlet-orm/SECURITY.md +386 -0
- package/docs/skills/outlet-orm/SEEDS.md +98 -0
- package/docs/skills/outlet-orm/SKILL.md +205 -0
- package/docs/skills/outlet-orm/TYPESCRIPT.md +480 -0
- package/package.json +7 -3
- package/src/AI/AIPromptEnhancer.js +170 -0
- package/src/AI/AIQueryBuilder.js +234 -0
- package/src/AI/AIQueryOptimizer.js +185 -0
- package/src/AI/AISafetyGuardrails.js +146 -0
- package/src/AI/AISeeder.js +181 -0
- package/src/AI/AiBridgeManager.js +287 -0
- package/src/AI/Builders/TextBuilder.js +170 -0
- package/src/AI/Contracts/AudioProviderContract.js +29 -0
- package/src/AI/Contracts/ChatProviderContract.js +38 -0
- package/src/AI/Contracts/EmbeddingsProviderContract.js +19 -0
- package/src/AI/Contracts/ImageProviderContract.js +19 -0
- package/src/AI/Contracts/ModelsProviderContract.js +26 -0
- package/src/AI/Contracts/ToolContract.js +25 -0
- package/src/AI/Facades/AiBridge.js +79 -0
- package/src/AI/MCPServer.js +798 -0
- package/src/AI/PromptGenerator.js +318 -0
- package/src/AI/Providers/ClaudeProvider.js +64 -0
- package/src/AI/Providers/CustomOpenAIProvider.js +238 -0
- package/src/AI/Providers/GeminiProvider.js +68 -0
- package/src/AI/Providers/GrokProvider.js +46 -0
- package/src/AI/Providers/MistralProvider.js +21 -0
- package/src/AI/Providers/OllamaProvider.js +249 -0
- package/src/AI/Providers/OllamaTurboProvider.js +32 -0
- package/src/AI/Providers/OnnProvider.js +46 -0
- package/src/AI/Providers/OpenAIProvider.js +471 -0
- package/src/AI/Support/AudioNormalizer.js +37 -0
- package/src/AI/Support/ChatNormalizer.js +42 -0
- package/src/AI/Support/Document.js +77 -0
- package/src/AI/Support/DocumentAttachmentMapper.js +101 -0
- package/src/AI/Support/EmbeddingsNormalizer.js +30 -0
- package/src/AI/Support/Exceptions/ProviderError.js +22 -0
- package/src/AI/Support/FileSecurity.js +56 -0
- package/src/AI/Support/ImageNormalizer.js +62 -0
- package/src/AI/Support/JsonSchemaValidator.js +73 -0
- package/src/AI/Support/Message.js +40 -0
- package/src/AI/Support/StreamChunk.js +45 -0
- package/src/AI/Support/ToolChatRunner.js +160 -0
- package/src/AI/Support/ToolRegistry.js +62 -0
- package/src/AI/Tools/SystemInfoTool.js +25 -0
- package/src/index.js +77 -1
- package/types/index.d.ts +432 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# Outlet ORM - Security Best Practices
|
|
2
|
+
|
|
3
|
+
[← Back to Index](SKILL.md) | [Previous: API](API.md)
|
|
4
|
+
|
|
5
|
+
> 🔐 **Security**: This guide covers backend security practices when using Outlet ORM.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Security Checklist
|
|
10
|
+
|
|
11
|
+
### 🔴 Critical Priority
|
|
12
|
+
|
|
13
|
+
| Action | Description | Outlet ORM Feature |
|
|
14
|
+
|--------|-------------|-------------------|
|
|
15
|
+
|`.env`in`.gitignore`| Never commit secrets | Auto-connect from .env |
|
|
16
|
+
| Password hashing | Bcrypt with 10+ rounds | Use`utils/hash.js`|
|
|
17
|
+
| SQL Injection protection | Use ORM queries | ✅ Built-in protection |
|
|
18
|
+
| XSS protection | Sanitize inputs/outputs | Use middleware |
|
|
19
|
+
| Input validation | Validate ALL user data |`static rules`+ middleware |
|
|
20
|
+
|
|
21
|
+
### 🟠 Important Priority
|
|
22
|
+
|
|
23
|
+
| Action | Description |
|
|
24
|
+
|--------|-------------|
|
|
25
|
+
| Secure JWT | Short expiration, refresh tokens |
|
|
26
|
+
| Rate limiting | Limit requests per IP |
|
|
27
|
+
| Security headers | Use Helmet.js |
|
|
28
|
+
| CSRF protection | Token for forms |
|
|
29
|
+
| CORS setup | Whitelist origins |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Secure Project Structure (Layered Architecture)
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
my-project/
|
|
37
|
+
├── .env # ⚠️ NEVER commit
|
|
38
|
+
├── .env.example # Template without secrets
|
|
39
|
+
├── .gitignore
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── controllers/ # 🎮 HTTP handling only
|
|
42
|
+
│ ├── services/ # ⚙️ Business logic
|
|
43
|
+
│ ├── repositories/ # 📦 Data access layer
|
|
44
|
+
│ ├── models/ # 📊 outlet-orm models
|
|
45
|
+
│ ├── middlewares/ # 🔒 CRITICAL for security
|
|
46
|
+
│ │ ├── auth.js # JWT authentication
|
|
47
|
+
│ │ ├── authorization.js # RBAC permissions
|
|
48
|
+
│ │ ├── rateLimiter.js # Anti-DDoS
|
|
49
|
+
│ │ ├── validator.js # Input validation
|
|
50
|
+
│ │ └── errorHandler.js # Error handling
|
|
51
|
+
│ ├── config/ # 🔒 Centralized config
|
|
52
|
+
│ │ └── security.js # Rate limit, helmet, CORS
|
|
53
|
+
│ ├── utils/ # 🔒 Security utilities
|
|
54
|
+
│ │ ├── hash.js # Bcrypt password hashing
|
|
55
|
+
│ │ └── token.js # JWT token generation
|
|
56
|
+
│ └── validators/ # 🔒 Validation schemas
|
|
57
|
+
├── public/ # ✅ Only public folder
|
|
58
|
+
├── logs/ # 📋 Not versioned
|
|
59
|
+
└── tests/
|
|
60
|
+
```
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Outlet ORM Built-in Security
|
|
66
|
+
|
|
67
|
+
### ✅ SQL Injection Protection
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// ✅ SECURE - Parameters automatically escaped
|
|
71
|
+
const users = await User.where('email', userInput).get();
|
|
72
|
+
|
|
73
|
+
// ✅ SECURE - whereIn with array
|
|
74
|
+
const users = await User.whereIn('id', [1, 2, 3]).get();
|
|
75
|
+
|
|
76
|
+
// ⚠️ CAUTION with whereRaw - escape manually
|
|
77
|
+
const users = await User.whereRaw('email = ?', [userInput]).get();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### ✅ Mass Assignment Protection
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
class User extends Model {
|
|
84
|
+
// 🔒 Only these fields can be mass-assigned
|
|
85
|
+
static fillable = ['name', 'email'];
|
|
86
|
+
|
|
87
|
+
// 'role', 'is_admin' excluded = cannot be modified via create/fill
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ✅ SECURE - role is ignored even if in req.body
|
|
91
|
+
const user = await User.create(req.body);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### ✅ Hidden Attributes
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
class User extends Model {
|
|
98
|
+
// 🔒 Never exposed in JSON
|
|
99
|
+
static hidden = ['password', 'refresh_token', 'reset_token'];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const user = await User.find(1);
|
|
103
|
+
console.log(user.toJSON());
|
|
104
|
+
// { id: 1, name: "John", email: "..." }
|
|
105
|
+
// password is NOT included
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Secure Model Example
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const { Model } = require('outlet-orm');
|
|
114
|
+
const { hashPassword, verifyPassword } = require('../utils/hash');
|
|
115
|
+
|
|
116
|
+
class User extends Model {
|
|
117
|
+
static table = 'users';
|
|
118
|
+
|
|
119
|
+
// 🔒 Mass assignment protection
|
|
120
|
+
static fillable = ['name', 'email', 'password'];
|
|
121
|
+
|
|
122
|
+
// 🔒 Never expose sensitive data
|
|
123
|
+
static hidden = ['password', 'refresh_token', 'reset_token'];
|
|
124
|
+
|
|
125
|
+
// Type casting
|
|
126
|
+
static casts = {
|
|
127
|
+
id: 'int',
|
|
128
|
+
email_verified: 'boolean',
|
|
129
|
+
created_at: 'date'
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// 🔒 Validation rules
|
|
133
|
+
static rules = {
|
|
134
|
+
name: 'required|string|min:2|max:100',
|
|
135
|
+
email: 'required|email',
|
|
136
|
+
password: 'required|min:8'
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// 🔒 Hash password before saving
|
|
140
|
+
static boot() {
|
|
141
|
+
this.creating(async (user) => {
|
|
142
|
+
const password = user.getAttribute('password');
|
|
143
|
+
if (password) {
|
|
144
|
+
user.setAttribute('password', await hashPassword(password));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
this.updating(async (user) => {
|
|
149
|
+
const password = user.getAttribute('password');
|
|
150
|
+
// Only hash if password changed
|
|
151
|
+
if (password && !password.startsWith('$2b$')) {
|
|
152
|
+
user.setAttribute('password', await hashPassword(password));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 🔒 Password verification method
|
|
158
|
+
async checkPassword(password) {
|
|
159
|
+
return verifyPassword(password, this.getAttribute('password'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = User;
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Authentication Middleware
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// middlewares/auth.js
|
|
172
|
+
const jwt = require('jsonwebtoken');
|
|
173
|
+
const { User } = require('../models');
|
|
174
|
+
|
|
175
|
+
const authenticate = async (req, res, next) => {
|
|
176
|
+
try {
|
|
177
|
+
const authHeader = req.headers.authorization;
|
|
178
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
179
|
+
return res.status(401).json({ error: 'Token missing' });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const token = authHeader.split(' ')[1];
|
|
183
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
184
|
+
|
|
185
|
+
const user = await User.find(decoded.userId);
|
|
186
|
+
if (!user) {
|
|
187
|
+
return res.status(401).json({ error: 'User not found' });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
req.user = user;
|
|
191
|
+
next();
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error.name === 'TokenExpiredError') {
|
|
194
|
+
return res.status(401).json({ error: 'Token expired' });
|
|
195
|
+
}
|
|
196
|
+
return res.status(401).json({ error: 'Invalid token' });
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const authorize = (...roles) => {
|
|
201
|
+
return (req, res, next) => {
|
|
202
|
+
if (!req.user) {
|
|
203
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const userRole = req.user.getAttribute('role');
|
|
207
|
+
if (!roles.includes(userRole)) {
|
|
208
|
+
return res.status(403).json({ error: 'Access denied' });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
next();
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
module.exports = { authenticate, authorize };
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Secure Route Example
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
const express = require('express');
|
|
224
|
+
const { body } = require('express-validator');
|
|
225
|
+
const { authenticate, authorize } = require('../middlewares/auth');
|
|
226
|
+
const { validate } = require('../middlewares/validator');
|
|
227
|
+
const UserController = require('../controllers/UserController');
|
|
228
|
+
|
|
229
|
+
const router = express.Router();
|
|
230
|
+
|
|
231
|
+
// 🔒 Public routes with strict rate limiting
|
|
232
|
+
router.post('/register',
|
|
233
|
+
validate([
|
|
234
|
+
body('name').trim().isLength({ min: 2, max: 100 }).escape(),
|
|
235
|
+
body('email').isEmail().normalizeEmail(),
|
|
236
|
+
body('password').isLength({ min: 8 }),
|
|
237
|
+
]),
|
|
238
|
+
UserController.register
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// 🔒 Protected routes
|
|
242
|
+
router.get('/profile',
|
|
243
|
+
authenticate,
|
|
244
|
+
UserController.getProfile
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// 🔒 Admin only
|
|
248
|
+
router.delete('/users/:id',
|
|
249
|
+
authenticate,
|
|
250
|
+
authorize('admin'),
|
|
251
|
+
UserController.deleteUser
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
module.exports = router;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Security Utilities
|
|
260
|
+
|
|
261
|
+
### utils/hash.js
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
const bcrypt = require('bcrypt');
|
|
265
|
+
|
|
266
|
+
const SALT_ROUNDS = 12;
|
|
267
|
+
|
|
268
|
+
const hashPassword = async (password) => {
|
|
269
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const verifyPassword = async (password, hash) => {
|
|
273
|
+
return bcrypt.compare(password, hash);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
module.exports = { hashPassword, verifyPassword };
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### utils/token.js
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
const jwt = require('jsonwebtoken');
|
|
283
|
+
const crypto = require('crypto');
|
|
284
|
+
|
|
285
|
+
const generateAccessToken = (userId) => {
|
|
286
|
+
return jwt.sign(
|
|
287
|
+
{ userId },
|
|
288
|
+
process.env.JWT_SECRET,
|
|
289
|
+
{ expiresIn: process.env.JWT_EXPIRES_IN || '15m' }
|
|
290
|
+
);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const generateRefreshToken = () => {
|
|
294
|
+
return crypto.randomBytes(64).toString('hex');
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
module.exports = { generateAccessToken, generateRefreshToken };
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Security Configuration
|
|
303
|
+
|
|
304
|
+
### config/security.js
|
|
305
|
+
|
|
306
|
+
```javascript
|
|
307
|
+
module.exports = {
|
|
308
|
+
// Rate limiting
|
|
309
|
+
rateLimit: {
|
|
310
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
311
|
+
max: 100,
|
|
312
|
+
message: { error: 'Too many requests' }
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
// Strict rate limit for auth
|
|
316
|
+
authRateLimit: {
|
|
317
|
+
windowMs: 15 * 60 * 1000,
|
|
318
|
+
max: 5,
|
|
319
|
+
message: { error: 'Too many login attempts' }
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
// CORS
|
|
323
|
+
cors: {
|
|
324
|
+
origin: process.env.CORS_ORIGIN,
|
|
325
|
+
credentials: true,
|
|
326
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
// JWT
|
|
330
|
+
jwt: {
|
|
331
|
+
secret: process.env.JWT_SECRET,
|
|
332
|
+
expiresIn: '15m'
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Required Dependencies
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npm install helmet express-rate-limit xss-clean hpp bcrypt jsonwebtoken express-validator
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Common Security Mistakes
|
|
348
|
+
|
|
349
|
+
### ❌ DON'T
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
// ❌ Never store passwords in plain text
|
|
353
|
+
user.setAttribute('password', req.body.password);
|
|
354
|
+
|
|
355
|
+
// ❌ Never expose sensitive data
|
|
356
|
+
static hidden = []; // Empty!
|
|
357
|
+
|
|
358
|
+
// ❌ Never use raw queries with user input
|
|
359
|
+
await db.execute(`SELECT * FROM users WHERE email = '${email}'`);
|
|
360
|
+
|
|
361
|
+
// ❌ Never commit .env
|
|
362
|
+
// .gitignore missing .env
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### ✅ DO
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
// ✅ Hash passwords
|
|
369
|
+
user.setAttribute('password', await hashPassword(req.body.password));
|
|
370
|
+
|
|
371
|
+
// ✅ Hide sensitive fields
|
|
372
|
+
static hidden = ['password', 'refresh_token'];
|
|
373
|
+
|
|
374
|
+
// ✅ Use parameterised queries
|
|
375
|
+
await User.where('email', email).first();
|
|
376
|
+
|
|
377
|
+
// ✅ Use .env.example for templates
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## References
|
|
383
|
+
|
|
384
|
+
- [Full Security Guide](../../../docs/SECURITY.md)
|
|
385
|
+
- [Validation](ADVANCED.md#validation)
|
|
386
|
+
- [Events/Hooks](ADVANCED.md#events)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Outlet ORM - Seeders
|
|
2
|
+
|
|
3
|
+
[← Back to Index](SKILL.md) | [Previous: Migrations](MIGRATIONS.md)
|
|
4
|
+
|
|
5
|
+
## When to use seeders
|
|
6
|
+
|
|
7
|
+
Use seeders to:
|
|
8
|
+
|
|
9
|
+
- bootstrap reference data;
|
|
10
|
+
- prepare local development environments quickly;
|
|
11
|
+
- initializes integration test fixtures.
|
|
12
|
+
|
|
13
|
+
## CLI commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Create a seeder file
|
|
17
|
+
outlet-migrate make:seed UserSeeder
|
|
18
|
+
|
|
19
|
+
# Run all seeders (DatabaseSeeder is prioritised)
|
|
20
|
+
outlet-migrate seed
|
|
21
|
+
|
|
22
|
+
# Laravel-style alias
|
|
23
|
+
outlet-migrate db:seed
|
|
24
|
+
|
|
25
|
+
# Run one specific seeder
|
|
26
|
+
outlet-migrate seed --class UserSeeder
|
|
27
|
+
outlet-migrate seed -c UserSeeder
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Folder convention
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
database/
|
|
34
|
+
├── migrations/
|
|
35
|
+
└── seeds/
|
|
36
|
+
├── DatabaseSeeder.js
|
|
37
|
+
├── RoleSeeder.js
|
|
38
|
+
└── UserSeeder.js
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Seeder API quick reference
|
|
42
|
+
|
|
43
|
+
-`this.insert(table, rowOrRows)`inserts one row or many rows;
|
|
44
|
+
-`this.call('OtherSeeder')`runs another seeder;
|
|
45
|
+
-`this.truncate(table)`clears a table before re-seeding.
|
|
46
|
+
|
|
47
|
+
## Seeder example
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const { Seeder } = require('outlet-orm');
|
|
51
|
+
|
|
52
|
+
class RoleSeeder extends Seeder {
|
|
53
|
+
async run() {
|
|
54
|
+
await this.insert('roles', [
|
|
55
|
+
{ name: 'admin' },
|
|
56
|
+
{ name: 'editor' }
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = RoleSeeder;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## DatabaseSeeder orchestration
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const { Seeder } = require('outlet-orm');
|
|
68
|
+
|
|
69
|
+
class DatabaseSeeder extends Seeder {
|
|
70
|
+
async run() {
|
|
71
|
+
await this.call('RoleSeeder');
|
|
72
|
+
await this.call('UserSeeder');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = DatabaseSeeder;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Recommended flow
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
outlet-migrate migrate
|
|
83
|
+
outlet-migrate seed
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For a clean local reset:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
outlet-migrate fresh --yes
|
|
90
|
+
outlet-migrate seed
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Best practice
|
|
94
|
+
|
|
95
|
+
- Keep seeders deterministic and idempotent.
|
|
96
|
+
- Control FK order explicitly in`DatabaseSeeder`.
|
|
97
|
+
- Keep heavy bulk data in dedicated import scripts.
|
|
98
|
+
- Prefer unique constraints to prevent duplicate seed data.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: outlet-orm-best-practices
|
|
3
|
+
description: Outlet ORM is a Laravel Eloquent-inspired ORM for Node.js with MySQL, PostgreSQL, and SQLite support. Use this skill when working with Outlet ORM models, queries, relationships, migrations, backup, and database operations. v6.0.0 adds a full Backup module (BackupManager, BackupScheduler, AES-256-GCM encryption, TCP daemon).
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: omgbwa-yasse
|
|
7
|
+
version: "6.0.0"
|
|
8
|
+
source: https://github.com/omgbwa-yasse/outlet-orm
|
|
9
|
+
npm: https://www.npmjs.com/package/outlet-orm
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Outlet ORM Best Practices
|
|
13
|
+
|
|
14
|
+
Comprehensive guide for using Outlet ORM - a Laravel Eloquent-inspired ORM for Node.js/TypeScript with support for MySQL, PostgreSQL, and SQLite.
|
|
15
|
+
|
|
16
|
+
> 🆕 **v7.0.0**: AI Integration — MCP Server (Model Context Protocol), AI Safety Guardrails, Prompt-based project initialization. See [AI.md](AI.md).
|
|
17
|
+
>
|
|
18
|
+
> 🔖 **v6.5.0**: Accessors & Mutators, firstOrCreate/firstOrNew/updateOrCreate, upsert, Observer pattern, cursor/stream.
|
|
19
|
+
>
|
|
20
|
+
> 🔖 **v6.0.0**: Full Backup module — `BackupManager`, `BackupScheduler`, AES-256-GCM `BackupEncryption`, `BackupSocketServer` TCP daemon, `BackupSocketClient` with remote restore. See [BACKUP.md](BACKUP.md).
|
|
21
|
+
>
|
|
22
|
+
> 🔖 **v5.0.0**: Full TypeScript support with Generic Model, typed Schema Builder, MigrationInterface and Copilot Skills integration.
|
|
23
|
+
|
|
24
|
+
## Documentation Index
|
|
25
|
+
|
|
26
|
+
| Document | Description |
|
|
27
|
+
|----------|-------------|
|
|
28
|
+
| **[MODELS.md](MODELS.md)** | Model definition, CRUD, casts, timestamps, connections |
|
|
29
|
+
| **[QUERIES.md](QUERIES.md)** | Query Builder, WHERE clauses, joins, pagination |
|
|
30
|
+
| **[RELATIONS.md](RELATIONS.md)** | Relationships, Eager Loading, polymorphic, naming conventions |
|
|
31
|
+
| **[MIGRATIONS.md](MIGRATIONS.md)** | Schema Builder, CLI tools, column types, foreign keys |
|
|
32
|
+
| **[ADVANCED.md](ADVANCED.md)** | Transactions, Soft Deletes, Events, Validation, Best Practices |
|
|
33
|
+
| **[TYPESCRIPT.md](TYPESCRIPT.md)** | TypeScript types, generics, typed models, migrations |
|
|
34
|
+
| **[SECURITY.md](SECURITY.md)** | 🔐 Security best practices, authentication, authorisation |
|
|
35
|
+
| **[BACKUP.md](BACKUP.md)** | 🗄️ Backups, scheduling, AES-256-GCM encryption, TCP daemon, restore |
|
|
36
|
+
| **[AI.md](AI.md)** | 🤖 MCP Server, AI Safety Guardrails, Prompt-based Init |
|
|
37
|
+
| **[API.md](API.md)** | Complete API Reference |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## When to Apply
|
|
42
|
+
|
|
43
|
+
Reference these guidelines when:
|
|
44
|
+
- Defining models and table schemas [MODELS.md](MODELS.md)
|
|
45
|
+
- Building database queries [QUERIES.md](QUERIES.md)
|
|
46
|
+
- Implementing relationships [RELATIONS.md](RELATIONS.md)
|
|
47
|
+
- Using Eager Loading [RELATIONS.md](RELATIONS.md)
|
|
48
|
+
- Setting up migrations [MIGRATIONS.md](MIGRATIONS.md)
|
|
49
|
+
- Implementing transactions, soft deletes, events [ADVANCED.md](ADVANCED.md)
|
|
50
|
+
- Using TypeScript with typed models [TYPESCRIPT.md](TYPESCRIPT.md)
|
|
51
|
+
- Securing your backend application [SECURITY.md](SECURITY.md)
|
|
52
|
+
- Scheduling or encrypting database backups [BACKUP.md](BACKUP.md)
|
|
53
|
+
- Restoring a database from a backup file [BACKUP.md](BACKUP.md)
|
|
54
|
+
- Running a long-lived backup daemon over TCP [BACKUP.md](BACKUP.md)
|
|
55
|
+
- Exposing the ORM to AI agents via MCP [AI.md](AI.md)
|
|
56
|
+
- Generating projects from natural language prompts [AI.md](AI.md)
|
|
57
|
+
- Protecting against AI-initiated destructive operations [AI.md](AI.md)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Prerequisites
|
|
62
|
+
|
|
63
|
+
- **Node.js**: >= 18 (required)
|
|
64
|
+
- **Database Drivers** (install only needed):
|
|
65
|
+
- MySQL/MariaDB:`npm install mysql2`
|
|
66
|
+
- PostgreSQL:`npm install pg`
|
|
67
|
+
- SQLite:`npm install sqlite3`
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install outlet-orm
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Recommended Project Structure (Layered Architecture)
|
|
76
|
+
|
|
77
|
+
When using Outlet ORM, organise your project following the **Layered Architecture** pattern:
|
|
78
|
+
|
|
79
|
+
> 🔐 **Security**: See the [Security Guide](../../../docs/SECURITY.md) for best practices.
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
my-project/
|
|
83
|
+
├── .env # ⚠️ NEVER commit (in .gitignore)
|
|
84
|
+
├── .env.example # Template without secrets
|
|
85
|
+
├── .gitignore
|
|
86
|
+
├── package.json
|
|
87
|
+
├── src/
|
|
88
|
+
│ ├── index.js # Entry point
|
|
89
|
+
│ ├── controllers/ # 🎮 Presentation Layer
|
|
90
|
+
│ │ └── UserController.js
|
|
91
|
+
│ ├── services/ # ⚙️ Business Logic Layer
|
|
92
|
+
│ │ └── UserService.js
|
|
93
|
+
│ ├── repositories/ # 📦 Data Access Layer
|
|
94
|
+
│ │ └── UserRepository.js
|
|
95
|
+
│ ├── models/ # 📊 Models Layer (outlet-orm)
|
|
96
|
+
│ │ └── User.js
|
|
97
|
+
│ ├── middlewares/ # 🔒 Auth, validation, rate limit
|
|
98
|
+
│ │ ├── auth.js
|
|
99
|
+
│ │ ├── validator.js
|
|
100
|
+
│ │ └── errorHandler.js
|
|
101
|
+
│ ├── routes/ # 🛤️ Route definitions
|
|
102
|
+
│ │ └── index.js
|
|
103
|
+
│ ├── config/ # 🔒 Configuration
|
|
104
|
+
│ │ ├── database.js
|
|
105
|
+
│ │ └── security.js
|
|
106
|
+
│ └── utils/ # 🔒 Hash, tokens, helpers
|
|
107
|
+
│ └── helpers.js
|
|
108
|
+
├── database/
|
|
109
|
+
│ ├── config.js # Migration CLI config
|
|
110
|
+
│ ├── migrations/
|
|
111
|
+
│ ├── seeds/
|
|
112
|
+
│ └── backups/ # 🗄️ Backup files (full / partial / journal)
|
|
113
|
+
├── public/ # ✅ Static files only
|
|
114
|
+
├── logs/ # 📋 Not versioned
|
|
115
|
+
└── tests/
|
|
116
|
+
├── unit/
|
|
117
|
+
└── integration/
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Architecture Flow
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
124
|
+
│ HTTP REQUEST │
|
|
125
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
126
|
+
▼
|
|
127
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
128
|
+
│ 🛤️ ROUTES Route to correct controller │
|
|
129
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
130
|
+
▼
|
|
131
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
132
|
+
│ 🔒 MIDDLEWARES Validation, Auth, Rate Limiting │
|
|
133
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
134
|
+
▼
|
|
135
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
136
|
+
│ 🎮 CONTROLLERS HTTP handling (req/res) only │
|
|
137
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
138
|
+
▼
|
|
139
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
140
|
+
│ ⚙️ SERVICES Business logic, business rules │
|
|
141
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
142
|
+
▼
|
|
143
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
144
|
+
│ 📦 REPOSITORIES Data access abstraction (CRUD) │
|
|
145
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
146
|
+
▼
|
|
147
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
148
|
+
│ 📊 MODELS outlet-orm (User, Post, etc.) │
|
|
149
|
+
└─────────────────────────┬───────────────────────────────────┘
|
|
150
|
+
▼
|
|
151
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
152
|
+
│ DATABASE │
|
|
153
|
+
└─────────────────────────────────────────────────────────────┘
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Layer Responsibilities
|
|
157
|
+
|
|
158
|
+
| Layer | Files | Responsibility | Security |
|
|
159
|
+
|-------|-------|----------------|----------|
|
|
160
|
+
| **Controllers** |`src/controllers/`| HTTP only (req/res) | Input validation |
|
|
161
|
+
| **Services** |`src/services/`| Business logic, rules | Authorisation |
|
|
162
|
+
| **Repositories** |`src/repositories/`| DB abstraction, queries | Sanitisation |
|
|
163
|
+
| **Models** |`src/models/`| Data structure, relationships | Fillable/Hidden |
|
|
164
|
+
| **Middlewares** |`src/middlewares/`| Auth, validation, errors | 🔒 **Critical** |
|
|
165
|
+
| **Config** |`src/config/`| Environment variables | 🔒 Reads .env |
|
|
166
|
+
| **Utils** |`src/utils/`| Hash, tokens, helpers | 🔒 Never expose |
|
|
167
|
+
| **Backups** |`database/backups/`| Backup files (.sql, .json, .enc) | 🗄️ Encrypted at rest |
|
|
168
|
+
|
|
169
|
+
### Quick Setup Commands
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Initialise project structure
|
|
173
|
+
outlet-init
|
|
174
|
+
|
|
175
|
+
# Create a migration
|
|
176
|
+
outlet-migrate make create_users_table
|
|
177
|
+
|
|
178
|
+
# Run migrations
|
|
179
|
+
outlet-migrate migrate
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Rule Categories by Priority
|
|
185
|
+
|
|
186
|
+
| Priority | Category | Impact | Document |
|
|
187
|
+
|----------|----------|--------|----------|
|
|
188
|
+
| 1 | Model Definition | CRITICAL | [MODELS.md](MODELS.md) |
|
|
189
|
+
| 2 | Query Building | CRITICAL | [QUERIES.md](QUERIES.md) |
|
|
190
|
+
| 3 | Relationships | HIGH | [RELATIONS.md](RELATIONS.md) |
|
|
191
|
+
| 4 | Eager Loading | HIGH | [RELATIONS.md](RELATIONS.md) |
|
|
192
|
+
| 5 | Transactions | MEDIUM-HIGH | [ADVANCED.md](ADVANCED.md) |
|
|
193
|
+
| 6 | Soft Deletes | MEDIUM | [ADVANCED.md](ADVANCED.md) |
|
|
194
|
+
| 7 | Validation & Events | MEDIUM | [ADVANCED.md](ADVANCED.md) |
|
|
195
|
+
| 8 | Migrations & CLI | LOW-MEDIUM | [MIGRATIONS.md](MIGRATIONS.md) |
|
|
196
|
+
| 9 | Backup & Restore | MEDIUM | [BACKUP.md](BACKUP.md) |
|
|
197
|
+
| 10 | AI / MCP Integration | MEDIUM | [AI.md](AI.md) |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## References
|
|
202
|
+
|
|
203
|
+
- <https://github.com/omgbwa-yasse/outlet-orm>
|
|
204
|
+
- <https://www.npmjs.com/package/outlet-orm>
|
|
205
|
+
- <https://github.com/omgbwa-yasse/outlet-orm/blob/main/docs/INDEX.md>
|