outlet-orm 5.0.0 → 5.5.2
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 +1325 -1312
- package/bin/init.js +397 -379
- package/bin/migrate.js +544 -440
- package/bin/reverse.js +602 -0
- package/package.json +88 -76
- package/src/DatabaseConnection.js +98 -46
- package/src/Migrations/MigrationManager.js +329 -326
- package/src/Model.js +1141 -1118
- package/src/QueryBuilder.js +134 -35
- package/src/RawExpression.js +11 -0
- package/src/Relations/BelongsToManyRelation.js +466 -466
- package/src/Schema/Schema.js +830 -790
- package/src/Seeders/Seeder.js +60 -0
- package/src/Seeders/SeederManager.js +105 -0
- package/src/index.js +55 -49
- package/types/index.d.ts +674 -660
package/README.md
CHANGED
|
@@ -1,1312 +1,1325 @@
|
|
|
1
|
-
# Outlet ORM
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/outlet-orm)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
|
|
6
|
-
Un ORM JavaScript
|
|
7
|
-
|
|
8
|
-
📚 **[Documentation
|
|
9
|
-
|
|
10
|
-
## ✅
|
|
11
|
-
|
|
12
|
-
- Node.js >= 18 (
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
## 🚀 Installation
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install outlet-orm
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
###
|
|
22
|
-
|
|
23
|
-
Outlet ORM utilise des peerDependencies optionnelles pour les drivers de
|
|
24
|
-
|
|
25
|
-
- MySQL/MariaDB: `npm install mysql2`
|
|
26
|
-
- PostgreSQL: `npm install pg`
|
|
27
|
-
- SQLite: `npm install sqlite3`
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## 📁 Structure de Projet
|
|
32
|
-
|
|
33
|
-
Organisez votre projet utilisant Outlet ORM avec une **architecture en couches** (
|
|
34
|
-
|
|
35
|
-
> 🔐 **Sécurité** :
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
mon-projet/
|
|
39
|
-
├── .env # ⚠️ JAMAIS
|
|
40
|
-
├── .env.example # Template
|
|
41
|
-
├── .gitignore
|
|
42
|
-
├── package.json
|
|
43
|
-
│
|
|
44
|
-
├── src/ # 📦 Code source centralisé
|
|
45
|
-
│ ├── index.js #
|
|
46
|
-
│ │
|
|
47
|
-
│ ├── config/ # ⚙️ Configuration
|
|
48
|
-
│ │ ├── app.js #
|
|
49
|
-
│ │ ├── database.js # Config DB (lit .env)
|
|
50
|
-
│ │ └── security.js # CORS, helmet, rate limit
|
|
51
|
-
│ │
|
|
52
|
-
│ ├── models/ # 📊
|
|
53
|
-
│ │ ├── index.js # Export centralisé des models
|
|
54
|
-
│ │ ├── User.js
|
|
55
|
-
│ │ ├── Post.js
|
|
56
|
-
│ │ └── Comment.js
|
|
57
|
-
│ │
|
|
58
|
-
│ ├── repositories/ # 🗄️
|
|
59
|
-
│ │ ├── BaseRepository.js #
|
|
60
|
-
│ │ ├── UserRepository.js #
|
|
61
|
-
│ │ └── PostRepository.js
|
|
62
|
-
│ │
|
|
63
|
-
│ ├── services/ # 💼
|
|
64
|
-
│ │ ├── AuthService.js # Logique d'authentification
|
|
65
|
-
│ │ ├── UserService.js #
|
|
66
|
-
│ │ ├── PostService.js
|
|
67
|
-
│ │ └── EmailService.js # Service externe (emails)
|
|
68
|
-
│ │
|
|
69
|
-
│ ├── controllers/ # 🎮
|
|
70
|
-
│ │ ├── AuthController.js
|
|
71
|
-
│ │ ├── UserController.js
|
|
72
|
-
│ │ └── PostController.js
|
|
73
|
-
│ │
|
|
74
|
-
│ ├── routes/ # 🛤️
|
|
75
|
-
│ │ ├── index.js #
|
|
76
|
-
│ │ ├── auth.routes.js
|
|
77
|
-
│ │ ├── user.routes.js
|
|
78
|
-
│ │ └── post.routes.js
|
|
79
|
-
│ │
|
|
80
|
-
│ ├── middlewares/ # 🔒 Middlewares
|
|
81
|
-
│ │ ├── auth.js # JWT verification
|
|
82
|
-
│ │ ├── authorize.js # RBAC / permissions
|
|
83
|
-
│ │ ├── rateLimiter.js # Protection DDoS
|
|
84
|
-
│ │ ├── validator.js # Validation request body
|
|
85
|
-
│ │ └── errorHandler.js # Gestion
|
|
86
|
-
│ │
|
|
87
|
-
│ ├── validators/ # ✅
|
|
88
|
-
│ │ ├── authValidator.js
|
|
89
|
-
│ │ └── userValidator.js
|
|
90
|
-
│ │
|
|
91
|
-
│ └── utils/ # 🔧 Utilitaires
|
|
92
|
-
│ ├── hash.js # bcrypt wrapper
|
|
93
|
-
│ ├── token.js # JWT helpers
|
|
94
|
-
│ ├── logger.js # Winston/Pino config
|
|
95
|
-
│ └── response.js #
|
|
96
|
-
│
|
|
97
|
-
├── database/
|
|
98
|
-
│ ├── config.js # Config migrations (outlet-init)
|
|
99
|
-
│ ├── migrations/ #
|
|
100
|
-
│ └──
|
|
101
|
-
│ └── UserSeeder.js
|
|
102
|
-
│
|
|
103
|
-
├── public/ # ✅
|
|
104
|
-
│ ├── images/
|
|
105
|
-
│ ├── css/
|
|
106
|
-
│ └── js/
|
|
107
|
-
│
|
|
108
|
-
├── uploads/ # ⚠️
|
|
109
|
-
│
|
|
110
|
-
├── logs/ # 📋 Journaux (
|
|
111
|
-
│
|
|
112
|
-
└── tests/ # 🧪 Tests
|
|
113
|
-
├── unit/ # Tests unitaires
|
|
114
|
-
│ ├── services/
|
|
115
|
-
│ └── models/
|
|
116
|
-
├── integration/ #
|
|
117
|
-
│ └── api/
|
|
118
|
-
└── fixtures/ #
|
|
119
|
-
└── users.json
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### 🏗️ Architecture
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
126
|
-
│ HTTP Request │
|
|
127
|
-
└─────────────────────────────────────────────────────────────┘
|
|
128
|
-
│
|
|
129
|
-
▼
|
|
130
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
131
|
-
│ MIDDLEWARES: auth → validate → rateLimiter → errorHandler │
|
|
132
|
-
└─────────────────────────────────────────────────────────────┘
|
|
133
|
-
│
|
|
134
|
-
▼
|
|
135
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
136
|
-
│ ROUTES → CONTROLLERS (
|
|
137
|
-
│
|
|
138
|
-
└─────────────────────────────────────────────────────────────┘
|
|
139
|
-
│
|
|
140
|
-
▼
|
|
141
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
142
|
-
│ SERVICES (
|
|
143
|
-
│ Logique métier, orchestration,
|
|
144
|
-
└─────────────────────────────────────────────────────────────┘
|
|
145
|
-
│
|
|
146
|
-
▼
|
|
147
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
148
|
-
│ REPOSITORIES (
|
|
149
|
-
│ Abstraction des
|
|
150
|
-
└─────────────────────────────────────────────────────────────┘
|
|
151
|
-
│
|
|
152
|
-
▼
|
|
153
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
154
|
-
│ MODELS (Outlet ORM) → DATABASE │
|
|
155
|
-
└─────────────────────────────────────────────────────────────┘
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### 📋
|
|
159
|
-
|
|
160
|
-
|
|
|
161
|
-
|--------|---------|----------------|-----------|
|
|
162
|
-
| **Présentation** | `controllers/` |
|
|
163
|
-
| **Métier** | `services/` | Logique business, orchestration,
|
|
164
|
-
| **
|
|
165
|
-
| **
|
|
166
|
-
|
|
167
|
-
### ✅
|
|
168
|
-
|
|
169
|
-
- **
|
|
170
|
-
- **
|
|
171
|
-
- **
|
|
172
|
-
- **
|
|
173
|
-
|
|
174
|
-
### 📝
|
|
175
|
-
|
|
176
|
-
```javascript
|
|
177
|
-
// routes/user.routes.js
|
|
178
|
-
router.get('/users/:id', auth, UserController.show);
|
|
179
|
-
|
|
180
|
-
// controllers/UserController.js
|
|
181
|
-
async show(req, res) {
|
|
182
|
-
const user = await userService.findById(req.params.id);
|
|
183
|
-
res.json({ data: user });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// services/UserService.js
|
|
187
|
-
async findById(id) {
|
|
188
|
-
const user = await userRepository.findWithPosts(id);
|
|
189
|
-
if (!user) throw new NotFoundError('User not found');
|
|
190
|
-
return user;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// repositories/UserRepository.js
|
|
194
|
-
async findWithPosts(id) {
|
|
195
|
-
return User.with('posts').find(id);
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
## ✨
|
|
200
|
-
|
|
201
|
-
- **API
|
|
202
|
-
- **Query Builder expressif**: where/joins/order/limit/offset/paginate
|
|
203
|
-
- **
|
|
204
|
-
- **Eager Loading** des
|
|
205
|
-
- **Relations
|
|
206
|
-
- `hasOne`, `hasMany`, `belongsTo`, `belongsToMany` (avec attach/detach/sync)
|
|
207
|
-
- `hasManyThrough`, `hasOneThrough` (
|
|
208
|
-
- `morphOne`, `morphMany`, `morphTo` (
|
|
209
|
-
- **Transactions**
|
|
210
|
-
- **Soft Deletes**:
|
|
211
|
-
- **Scopes**:
|
|
212
|
-
- **Events/Hooks**: `creating`, `created`, `updating`, `updated`, `deleting`, `deleted`, etc.
|
|
213
|
-
- **Validation**:
|
|
214
|
-
- **Query Logging**: mode debug avec `enableQueryLog()` et `getQueryLog()`
|
|
215
|
-
- **Pool PostgreSQL**:
|
|
216
|
-
- **Protection SQL**: sanitization automatique des identifiants
|
|
217
|
-
- **Casts automatiques** (int, float, boolean, json, date...)
|
|
218
|
-
- **
|
|
219
|
-
- **
|
|
220
|
-
- **
|
|
221
|
-
- **Aliases ergonomiques**: `columns([...])`, `ordrer()` (alias typo de `orderBy`)
|
|
222
|
-
- **
|
|
223
|
-
- **Migrations
|
|
224
|
-
- **CLI pratiques**: `outlet-init`, `outlet-migrate`, `outlet-convert`
|
|
225
|
-
- **Configuration via `.env`** (
|
|
226
|
-
- **Multi-
|
|
227
|
-
- **
|
|
228
|
-
|
|
229
|
-
## ⚡
|
|
230
|
-
|
|
231
|
-
### Initialisation du projet
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
#
|
|
235
|
-
outlet-init
|
|
236
|
-
|
|
237
|
-
#
|
|
238
|
-
outlet-migrate make create_users_table
|
|
239
|
-
|
|
240
|
-
#
|
|
241
|
-
outlet-migrate migrate
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
####
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
###
|
|
324
|
-
|
|
325
|
-
```javascript
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
//
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
//
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
```javascript
|
|
450
|
-
//
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
//
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
await
|
|
702
|
-
|
|
703
|
-
//
|
|
704
|
-
users.
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
```javascript
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const user = await User.
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
//
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
//
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
//
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
User.
|
|
919
|
-
// Nettoyage des
|
|
920
|
-
});
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
|
1037
|
-
|
|
1038
|
-
| `
|
|
1039
|
-
| `
|
|
1040
|
-
| `
|
|
1041
|
-
| `
|
|
1042
|
-
|
|
|
1043
|
-
| `
|
|
1044
|
-
| `
|
|
1045
|
-
| `
|
|
1046
|
-
| `
|
|
1047
|
-
| `
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
|
1052
|
-
|
|
1053
|
-
| `
|
|
1054
|
-
| `
|
|
1055
|
-
|
|
|
1056
|
-
| `
|
|
1057
|
-
| `
|
|
1058
|
-
| `
|
|
1059
|
-
| `
|
|
1060
|
-
| `
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
|
1065
|
-
|
|
1066
|
-
| `
|
|
1067
|
-
| `
|
|
1068
|
-
| `
|
|
1069
|
-
| `
|
|
1070
|
-
| `
|
|
1071
|
-
| `
|
|
1072
|
-
| `
|
|
1073
|
-
| `
|
|
1074
|
-
| `
|
|
1075
|
-
| `
|
|
1076
|
-
| `
|
|
1077
|
-
| `
|
|
1078
|
-
|
|
|
1079
|
-
| `
|
|
1080
|
-
| `
|
|
1081
|
-
|
|
|
1082
|
-
| `
|
|
1083
|
-
| `
|
|
1084
|
-
| `
|
|
1085
|
-
| `
|
|
1086
|
-
|
|
|
1087
|
-
| `
|
|
1088
|
-
| `
|
|
1089
|
-
| `
|
|
1090
|
-
| `
|
|
1091
|
-
|
|
|
1092
|
-
| `
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
|
1097
|
-
|
|
1098
|
-
| `
|
|
1099
|
-
|
|
|
1100
|
-
| `
|
|
1101
|
-
| `
|
|
1102
|
-
| `
|
|
1103
|
-
| `
|
|
1104
|
-
| `
|
|
1105
|
-
| `
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
|
1110
|
-
|
|
1111
|
-
|
|
|
1112
|
-
| `
|
|
1113
|
-
| `
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
|
1118
|
-
|
|
1119
|
-
| `
|
|
1120
|
-
|
|
|
1121
|
-
| `
|
|
1122
|
-
| `
|
|
1123
|
-
| `
|
|
1124
|
-
|
|
|
1125
|
-
| `
|
|
1126
|
-
| `
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
|
1131
|
-
|
|
1132
|
-
| `
|
|
1133
|
-
| `
|
|
1134
|
-
| `
|
|
1135
|
-
| `
|
|
1136
|
-
| `
|
|
1137
|
-
| `
|
|
1138
|
-
| `
|
|
1139
|
-
| `
|
|
1140
|
-
| `
|
|
1141
|
-
| `
|
|
1142
|
-
| `
|
|
1143
|
-
| `
|
|
1144
|
-
| `
|
|
1145
|
-
| `
|
|
1146
|
-
| `
|
|
1147
|
-
| `
|
|
1148
|
-
| `
|
|
1149
|
-
| `
|
|
1150
|
-
| `
|
|
1151
|
-
| `
|
|
1152
|
-
| `
|
|
1153
|
-
| `
|
|
1154
|
-
| `
|
|
1155
|
-
| `
|
|
1156
|
-
| `
|
|
1157
|
-
| `
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
-
|
|
1241
|
-
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1
|
+
# Outlet ORM
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/outlet-orm)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Un ORM JavaScript inspired de Laravel Eloquent pour Node.js avec support pour MySQL, PostgreSQL et SQLite.
|
|
7
|
+
|
|
8
|
+
📚 **[Documentation complete available dans `/docs`](./docs/INDEX.md)**
|
|
9
|
+
|
|
10
|
+
## ✅ Prerequisites and compatibility
|
|
11
|
+
|
|
12
|
+
- Node.js >= 18 (recommended/required)
|
|
13
|
+
- Install the database driver corresponding to your DBMS (see below)
|
|
14
|
+
|
|
15
|
+
## 🚀 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install outlet-orm
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Install the database driver
|
|
22
|
+
|
|
23
|
+
Outlet ORM utilise des peerDependencies optionnelles pour les drivers de database. Installez uniquement le driver dont vous avez besoin:
|
|
24
|
+
|
|
25
|
+
- MySQL/MariaDB: `npm install mysql2`
|
|
26
|
+
- PostgreSQL: `npm install pg`
|
|
27
|
+
- SQLite: `npm install sqlite3`
|
|
28
|
+
|
|
29
|
+
If no driver is installed, an explicit error message will tell you which one to install when connecting.
|
|
30
|
+
|
|
31
|
+
## 📁 Structure de Projet recommended
|
|
32
|
+
|
|
33
|
+
Organisez votre projet utilisant Outlet ORM avec une **architecture en couches** (recommended pour la production) :
|
|
34
|
+
|
|
35
|
+
> 🔐 **Sécurité** : See the [Security Guide](./docs/SECURITY.md) pour les bonnes pratiques.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
mon-projet/
|
|
39
|
+
├── .env # ⚠️ JAMAIS committed (dans .gitignore)
|
|
40
|
+
├── .env.example # Template without secrets
|
|
41
|
+
├── .gitignore
|
|
42
|
+
├── package.json
|
|
43
|
+
│
|
|
44
|
+
├── src/ # 📦 Code source centralisé
|
|
45
|
+
│ ├── index.js # Entry point de l'application
|
|
46
|
+
│ │
|
|
47
|
+
│ ├── config/ # ⚙️ Configuration
|
|
48
|
+
│ │ ├── app.js # General config (port, env)
|
|
49
|
+
│ │ ├── database.js # Config DB (lit .env)
|
|
50
|
+
│ │ └── security.js # CORS, helmet, rate limit
|
|
51
|
+
│ │
|
|
52
|
+
│ ├── models/ # 📊 Data Layer (Entities)
|
|
53
|
+
│ │ ├── index.js # Export centralisé des models
|
|
54
|
+
│ │ ├── User.js
|
|
55
|
+
│ │ ├── Post.js
|
|
56
|
+
│ │ └── Comment.js
|
|
57
|
+
│ │
|
|
58
|
+
│ ├── repositories/ # 🗄️ Data Access Layer
|
|
59
|
+
│ │ ├── BaseRepository.js # Generic CRUD methods
|
|
60
|
+
│ │ ├── UserRepository.js # Specific queries User
|
|
61
|
+
│ │ └── PostRepository.js
|
|
62
|
+
│ │
|
|
63
|
+
│ ├── services/ # 💼 Business Layer (Business Logic)
|
|
64
|
+
│ │ ├── AuthService.js # Logique d'authentification
|
|
65
|
+
│ │ ├── UserService.js # User business logic
|
|
66
|
+
│ │ ├── PostService.js
|
|
67
|
+
│ │ └── EmailService.js # Service externe (emails)
|
|
68
|
+
│ │
|
|
69
|
+
│ ├── controllers/ # 🎮 Presentation Layer (HTTP)
|
|
70
|
+
│ │ ├── AuthController.js
|
|
71
|
+
│ │ ├── UserController.js
|
|
72
|
+
│ │ └── PostController.js
|
|
73
|
+
│ │
|
|
74
|
+
│ ├── routes/ # 🛤️ Route definitions
|
|
75
|
+
│ │ ├── index.js # Route aggregator
|
|
76
|
+
│ │ ├── auth.routes.js
|
|
77
|
+
│ │ ├── user.routes.js
|
|
78
|
+
│ │ └── post.routes.js
|
|
79
|
+
│ │
|
|
80
|
+
│ ├── middlewares/ # 🔒 Middlewares
|
|
81
|
+
│ │ ├── auth.js # JWT verification
|
|
82
|
+
│ │ ├── authorize.js # RBAC / permissions
|
|
83
|
+
│ │ ├── rateLimiter.js # Protection DDoS
|
|
84
|
+
│ │ ├── validator.js # Validation request body
|
|
85
|
+
│ │ └── errorHandler.js # Gestion centralisede erreurs
|
|
86
|
+
│ │
|
|
87
|
+
│ ├── validators/ # ✅ Validation schemas
|
|
88
|
+
│ │ ├── authValidator.js
|
|
89
|
+
│ │ └── userValidator.js
|
|
90
|
+
│ │
|
|
91
|
+
│ └── utils/ # 🔧 Utilitaires
|
|
92
|
+
│ ├── hash.js # bcrypt wrapper
|
|
93
|
+
│ ├── token.js # JWT helpers
|
|
94
|
+
│ ├── logger.js # Winston/Pino config
|
|
95
|
+
│ └── response.js # API response formatting
|
|
96
|
+
│
|
|
97
|
+
├── database/
|
|
98
|
+
│ ├── config.js # Config migrations (outlet-init)
|
|
99
|
+
│ ├── migrations/ # Migration files
|
|
100
|
+
│ └── seeds/ # Test/demo data
|
|
101
|
+
│ └── UserSeeder.js
|
|
102
|
+
│
|
|
103
|
+
├── public/ # ✅ Public static files
|
|
104
|
+
│ ├── images/
|
|
105
|
+
│ ├── css/
|
|
106
|
+
│ └── js/
|
|
107
|
+
│
|
|
108
|
+
├── uploads/ # ⚠️ Uploaded files
|
|
109
|
+
│
|
|
110
|
+
├── logs/ # 📋 Journaux (not versioned)
|
|
111
|
+
│
|
|
112
|
+
└── tests/ # 🧪 Tests
|
|
113
|
+
├── unit/ # Tests unitaires
|
|
114
|
+
│ ├── services/
|
|
115
|
+
│ └── models/
|
|
116
|
+
├── integration/ # Integration tests
|
|
117
|
+
│ └── api/
|
|
118
|
+
└── fixtures/ # Test data
|
|
119
|
+
└── users.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 🏗️ Layered Architecture
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
126
|
+
│ HTTP Request │
|
|
127
|
+
└─────────────────────────────────────────────────────────────┘
|
|
128
|
+
│
|
|
129
|
+
▼
|
|
130
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
131
|
+
│ MIDDLEWARES: auth → validate → rateLimiter → errorHandler │
|
|
132
|
+
└─────────────────────────────────────────────────────────────┘
|
|
133
|
+
│
|
|
134
|
+
▼
|
|
135
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
136
|
+
│ ROUTES → CONTROLLERS (Presentation Layer) │
|
|
137
|
+
│ Receives the request, calls the service, returns a response │
|
|
138
|
+
└─────────────────────────────────────────────────────────────┘
|
|
139
|
+
│
|
|
140
|
+
▼
|
|
141
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
142
|
+
│ SERVICES (Business Layer / Business Logic) │
|
|
143
|
+
│ Logique métier, orchestration, rules business │
|
|
144
|
+
└─────────────────────────────────────────────────────────────┘
|
|
145
|
+
│
|
|
146
|
+
▼
|
|
147
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
148
|
+
│ REPOSITORIES (Data Access Layer) │
|
|
149
|
+
│ Abstraction des queries DB, utilise les Models │
|
|
150
|
+
└─────────────────────────────────────────────────────────────┘
|
|
151
|
+
│
|
|
152
|
+
▼
|
|
153
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
154
|
+
│ MODELS (Outlet ORM) → DATABASE │
|
|
155
|
+
└─────────────────────────────────────────────────────────────┘
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 📋 Role of each layer
|
|
159
|
+
|
|
160
|
+
| Layer | Folder | Responsibility | Depends on |
|
|
161
|
+
|--------|---------|----------------|-----------|
|
|
162
|
+
| **Présentation** | `controllers/` | Handle HTTP, validate entrées, format responses | Services |
|
|
163
|
+
| **Métier** | `services/` | Logique business, orchestration, rules | Repositories |
|
|
164
|
+
| **Data** | `repositories/` | Complex DB queries, abstraction | Models |
|
|
165
|
+
| **Entities** | `models/` | Entity definitions, relationships, validations | Outlet ORM |
|
|
166
|
+
|
|
167
|
+
### ✅ Benefits of this architecture
|
|
168
|
+
|
|
169
|
+
- **Testability** : Each layer can be tested independently
|
|
170
|
+
- **Maintainability** : Clear separation of responsibilities
|
|
171
|
+
- **Scalability** : Easy to add new features
|
|
172
|
+
- **Reusability** : Services reusable from CLI, workers, etc.
|
|
173
|
+
|
|
174
|
+
### 📝 Example workflow
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
// routes/user.routes.js
|
|
178
|
+
router.get('/users/:id', auth, UserController.show);
|
|
179
|
+
|
|
180
|
+
// controllers/UserController.js
|
|
181
|
+
async show(req, res) {
|
|
182
|
+
const user = await userService.findById(req.params.id);
|
|
183
|
+
res.json({ data: user });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// services/UserService.js
|
|
187
|
+
async findById(id) {
|
|
188
|
+
const user = await userRepository.findWithPosts(id);
|
|
189
|
+
if (!user) throw new NotFoundError('User not found');
|
|
190
|
+
return user;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// repositories/UserRepository.js
|
|
194
|
+
async findWithPosts(id) {
|
|
195
|
+
return User.with('posts').find(id);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## ✨ Key features
|
|
200
|
+
|
|
201
|
+
- **API inspirede d'Eloquent** (Active Record) pour un usage fluide
|
|
202
|
+
- **Query Builder expressif**: where/joins/order/limit/offset/paginate
|
|
203
|
+
- **Relationship filters Laravel-style**: `whereHas()`, `has()`, `whereDoesntHave()`, `withCount()`
|
|
204
|
+
- **Eager Loading** des relationships via `.with(...)` avec constraints et dot-notation
|
|
205
|
+
- **Relations completes**:
|
|
206
|
+
- `hasOne`, `hasMany`, `belongsTo`, `belongsToMany` (avec attach/detach/sync)
|
|
207
|
+
- `hasManyThrough`, `hasOneThrough` (relationships transitives)
|
|
208
|
+
- `morphOne`, `morphMany`, `morphTo` (relationships polymorphiques)
|
|
209
|
+
- **Transactions** completes: `beginTransaction()`, `commit()`, `rollback()`, `transaction()`
|
|
210
|
+
- **Soft Deletes**: soft deletion avec `deleted_at`, `withTrashed()`, `onlyTrashed()`, `restore()`
|
|
211
|
+
- **Scopes**: global and local to reuse your filters
|
|
212
|
+
- **Events/Hooks**: `creating`, `created`, `updating`, `updated`, `deleting`, `deleted`, etc.
|
|
213
|
+
- **Validation**: rules built-in basic (`required`, `email`, `min`, `max`, etc.)
|
|
214
|
+
- **Query Logging**: mode debug avec `enableQueryLog()` et `getQueryLog()`
|
|
215
|
+
- **Pool PostgreSQL**: pooled connections pour better performance
|
|
216
|
+
- **Protection SQL**: sanitization automatique des identifiants
|
|
217
|
+
- **Casts automatiques** (int, float, boolean, json, date...)
|
|
218
|
+
- **Hidden attributes** (`hidden`) et timestamps automatiques
|
|
219
|
+
- **Visibility control** des attributs cachés: `withHidden()` et `withoutHidden()`
|
|
220
|
+
- **Atomic increment/decrement**: `increment()` et `decrement()`
|
|
221
|
+
- **Aliases ergonomiques**: `columns([...])`, `ordrer()` (alias typo de `orderBy`)
|
|
222
|
+
- **Raw queries**: `executeRawQuery()` et `execute()` (native driver results)
|
|
223
|
+
- **Migrations completes** (create/alter/drop, index, foreign keys, batch tracking)
|
|
224
|
+
- **CLI pratiques**: `outlet-init`, `outlet-migrate`, `outlet-convert`
|
|
225
|
+
- **Configuration via `.env`** (loaded automatically)
|
|
226
|
+
- **Multi-database**: MySQL, PostgreSQL et SQLite
|
|
227
|
+
- **Complete TypeScript types** avec Generic Model et Schema Builder typed (v4.0.0+)
|
|
228
|
+
|
|
229
|
+
## ⚡ Quick Start
|
|
230
|
+
|
|
231
|
+
### Initialisation du projet
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Create la configuration initiale
|
|
235
|
+
outlet-init
|
|
236
|
+
|
|
237
|
+
# Create une migration
|
|
238
|
+
outlet-migrate make create_users_table
|
|
239
|
+
|
|
240
|
+
# Run les migrations
|
|
241
|
+
outlet-migrate migrate
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 🌱 Seeding rapide
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Create un seeder
|
|
248
|
+
outlet-migrate make:seed UserSeeder
|
|
249
|
+
|
|
250
|
+
# Run les seeds (DatabaseSeeder prioritaire)
|
|
251
|
+
outlet-migrate seed
|
|
252
|
+
|
|
253
|
+
# Run un specific seeder
|
|
254
|
+
outlet-migrate seed --class UserSeeder
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 📖 Usage
|
|
258
|
+
|
|
259
|
+
### Connection configuration
|
|
260
|
+
|
|
261
|
+
Outlet ORM charge automatiquement la connection depuis le file `.env`. **Plus besoin d'importer DatabaseConnection !**
|
|
262
|
+
|
|
263
|
+
#### `.env` file
|
|
264
|
+
|
|
265
|
+
```env
|
|
266
|
+
DB_DRIVER=mysql
|
|
267
|
+
DB_HOST=localhost
|
|
268
|
+
DB_DATABASE=myapp
|
|
269
|
+
DB_USER=root
|
|
270
|
+
DB_PASSWORD=secret
|
|
271
|
+
DB_PORT=3306
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Simplified usage
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
const { Model } = require('outlet-orm');
|
|
278
|
+
|
|
279
|
+
class User extends Model {
|
|
280
|
+
static table = 'users';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// C'est tout ! La connection est automatique
|
|
284
|
+
const users = await User.all();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### Manual configuration (optional)
|
|
288
|
+
|
|
289
|
+
If you need to control the connection :
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
293
|
+
|
|
294
|
+
// Option 1 – via .env (no parameters required)
|
|
295
|
+
const db = new DatabaseConnection();
|
|
296
|
+
|
|
297
|
+
// Option 2 – via objet de configuration
|
|
298
|
+
const db = new DatabaseConnection({
|
|
299
|
+
driver: 'mysql',
|
|
300
|
+
host: 'localhost',
|
|
301
|
+
database: 'myapp',
|
|
302
|
+
user: 'root',
|
|
303
|
+
password: 'secret',
|
|
304
|
+
port: 3306
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Définir la connection manuellement (optionnel)
|
|
308
|
+
Model.setConnection(db);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### Environment variables (.env)
|
|
312
|
+
|
|
313
|
+
| Variable | Description | Default |
|
|
314
|
+
|----------|-------------|------------|
|
|
315
|
+
| `DB_DRIVER` | `mysql`, `postgres`, `sqlite` | `mysql` |
|
|
316
|
+
| `DB_HOST` | Hôte de la base | `localhost` |
|
|
317
|
+
| `DB_PORT` | Port de connection | Selon driver |
|
|
318
|
+
| `DB_USER` / `DB_USERNAME` | Identifiant | - |
|
|
319
|
+
| `DB_PASSWORD` | Mot de passe | - |
|
|
320
|
+
| `DB_DATABASE` / `DB_NAME` | Nom de la base | - |
|
|
321
|
+
| `DB_FILE` / `SQLITE_DB` | SQLite file | `:memory:` |
|
|
322
|
+
|
|
323
|
+
### Importation
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
// CommonJS - Import simple (connection automatique via .env)
|
|
327
|
+
const { Model } = require('outlet-orm');
|
|
328
|
+
|
|
329
|
+
// ES Modules
|
|
330
|
+
import { Model } from 'outlet-orm';
|
|
331
|
+
|
|
332
|
+
// Si besoin de Manual control sur la connection
|
|
333
|
+
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Define a model
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
const { Model } = require('outlet-orm');
|
|
340
|
+
|
|
341
|
+
// Define related models (see Relationships)
|
|
342
|
+
class Post extends Model { static table = 'posts'; }
|
|
343
|
+
class Profile extends Model { static table = 'profiles'; }
|
|
344
|
+
|
|
345
|
+
class User extends Model {
|
|
346
|
+
static table = 'users';
|
|
347
|
+
static primaryKey = 'id'; // Default: 'id'
|
|
348
|
+
static timestamps = true; // Default: true
|
|
349
|
+
static fillable = ['name', 'email', 'password'];
|
|
350
|
+
static hidden = ['password'];
|
|
351
|
+
static casts = {
|
|
352
|
+
id: 'int',
|
|
353
|
+
email_verified: 'boolean',
|
|
354
|
+
metadata: 'json',
|
|
355
|
+
birthday: 'date'
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// Relations
|
|
359
|
+
posts() {
|
|
360
|
+
return this.hasMany(Post, 'user_id');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
profile() {
|
|
364
|
+
return this.hasOne(Profile, 'user_id');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### CRUD operations
|
|
370
|
+
|
|
371
|
+
#### Create
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
// Method 1: create()
|
|
375
|
+
const user = await User.create({
|
|
376
|
+
name: 'John Doe',
|
|
377
|
+
email: 'john@example.com',
|
|
378
|
+
password: 'secret123'
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Method 2: new + save()
|
|
382
|
+
const user = new User({
|
|
383
|
+
name: 'Jane Doe',
|
|
384
|
+
email: 'jane@example.com'
|
|
385
|
+
});
|
|
386
|
+
user.setAttribute('password', 'secret456');
|
|
387
|
+
await user.save();
|
|
388
|
+
|
|
389
|
+
// Insert brut (sans create d'instance)
|
|
390
|
+
await User.insert({ name: 'Bob', email: 'bob@example.com' });
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### Lire
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
// Tous les enregistrements
|
|
397
|
+
const users = await User.all();
|
|
398
|
+
|
|
399
|
+
// Par ID
|
|
400
|
+
const user = await User.find(1);
|
|
401
|
+
const user = await User.findOrFail(1); // Throws an error if not found
|
|
402
|
+
|
|
403
|
+
// First result
|
|
404
|
+
const firstUser = await User.first();
|
|
405
|
+
|
|
406
|
+
// Avec conditions
|
|
407
|
+
const activeUsers = await User
|
|
408
|
+
.where('status', 'active')
|
|
409
|
+
.where('age', '>', 18)
|
|
410
|
+
.get();
|
|
411
|
+
|
|
412
|
+
// Avec relationships (Eager Loading)
|
|
413
|
+
const usersWithPosts = await User
|
|
414
|
+
.with('posts', 'profile')
|
|
415
|
+
.get();
|
|
416
|
+
|
|
417
|
+
// Ordonner et limiter
|
|
418
|
+
const recentUsers = await User
|
|
419
|
+
.orderBy('created_at', 'desc')
|
|
420
|
+
.limit(10)
|
|
421
|
+
.get();
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### Update
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
// Instance
|
|
428
|
+
const user = await User.find(1);
|
|
429
|
+
user.setAttribute('name', 'Updated Name');
|
|
430
|
+
await user.save();
|
|
431
|
+
|
|
432
|
+
// Bulk update
|
|
433
|
+
await User
|
|
434
|
+
.where('status', 'pending')
|
|
435
|
+
.update({ status: 'active' });
|
|
436
|
+
|
|
437
|
+
// Update + Fetch (comme Prisma)
|
|
438
|
+
const updated = await User
|
|
439
|
+
.where('id', 1)
|
|
440
|
+
.updateAndFetch({ name: 'Neo' }, ['profile', 'posts']);
|
|
441
|
+
|
|
442
|
+
// Helpers par ID
|
|
443
|
+
const user = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
|
|
444
|
+
await User.updateById(2, { status: 'active' });
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### Delete
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
// Instance
|
|
451
|
+
const user = await User.find(1);
|
|
452
|
+
await user.destroy();
|
|
453
|
+
|
|
454
|
+
// Bulk delete
|
|
455
|
+
await User
|
|
456
|
+
.where('status', 'banned')
|
|
457
|
+
.delete();
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Query Builder
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
// Where clauses
|
|
464
|
+
const users = await User
|
|
465
|
+
.where('age', '>', 18)
|
|
466
|
+
.where('status', 'active')
|
|
467
|
+
.orWhere('role', 'admin')
|
|
468
|
+
.get();
|
|
469
|
+
|
|
470
|
+
// Where In / Not In
|
|
471
|
+
const users = await User.whereIn('id', [1, 2, 3, 4, 5]).get();
|
|
472
|
+
const users = await User.whereNotIn('status', ['banned', 'deleted']).get();
|
|
473
|
+
|
|
474
|
+
// Where Null / Not Null
|
|
475
|
+
const users = await User.whereNull('deleted_at').get();
|
|
476
|
+
const verified = await User.whereNotNull('email_verified_at').get();
|
|
477
|
+
|
|
478
|
+
// Where Between / Like
|
|
479
|
+
const adults = await User.whereBetween('age', [18, 65]).get();
|
|
480
|
+
const johns = await User.whereLike('name', '%john%').get();
|
|
481
|
+
|
|
482
|
+
// Pagination
|
|
483
|
+
const result = await User.paginate(1, 15);
|
|
484
|
+
// { data: [...], total: 100, per_page: 15, current_page: 1, last_page: 7, from: 1, to: 15 }
|
|
485
|
+
|
|
486
|
+
// Count / Exists
|
|
487
|
+
const count = await User.where('status', 'active').count();
|
|
488
|
+
const hasUsers = await User.where('role', 'admin').exists();
|
|
489
|
+
|
|
490
|
+
// Joins
|
|
491
|
+
const result = await User
|
|
492
|
+
.join('profiles', 'users.id', 'profiles.user_id')
|
|
493
|
+
.leftJoin('countries', 'profiles.country_id', 'countries.id')
|
|
494
|
+
.select('users.*', 'profiles.bio', 'countries.name as country')
|
|
495
|
+
.get();
|
|
496
|
+
|
|
497
|
+
// Aggregations
|
|
498
|
+
const stats = await User
|
|
499
|
+
.distinct()
|
|
500
|
+
.groupBy('status')
|
|
501
|
+
.having('COUNT(*)', '>', 5)
|
|
502
|
+
.get();
|
|
503
|
+
|
|
504
|
+
// Incrément / Décrément atomique
|
|
505
|
+
await User.where('id', 1).increment('login_count');
|
|
506
|
+
await User.where('id', 1).decrement('credits', 10);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Relationship filters
|
|
510
|
+
|
|
511
|
+
```javascript
|
|
512
|
+
// whereHas: Utilisateurs ayant au moins un post publié
|
|
513
|
+
const authors = await User
|
|
514
|
+
.whereHas('posts', (q) => {
|
|
515
|
+
q.where('status', 'published');
|
|
516
|
+
})
|
|
517
|
+
.get();
|
|
518
|
+
|
|
519
|
+
// has: Au moins N enfants
|
|
520
|
+
const prolific = await User.has('posts', '>=', 10).get();
|
|
521
|
+
|
|
522
|
+
// whereDoesntHave: No children
|
|
523
|
+
const noPostUsers = await User.whereDoesntHave('posts').get();
|
|
524
|
+
|
|
525
|
+
// withCount: Ajouter une colonne {relation}_count
|
|
526
|
+
const withCounts = await User.withCount('posts').get();
|
|
527
|
+
// Chaque user aura: user.getAttribute('posts_count')
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## 🔗 Relations
|
|
531
|
+
|
|
532
|
+
### One to One (hasOne)
|
|
533
|
+
|
|
534
|
+
```javascript
|
|
535
|
+
const { Model } = require('outlet-orm');
|
|
536
|
+
|
|
537
|
+
class Profile extends Model { static table = 'profiles'; }
|
|
538
|
+
|
|
539
|
+
class User extends Model {
|
|
540
|
+
static table = 'users';
|
|
541
|
+
|
|
542
|
+
profile() {
|
|
543
|
+
return this.hasOne(Profile, 'user_id');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const user = await User.find(1);
|
|
548
|
+
const profile = await user.profile().get();
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### One to Many (hasMany)
|
|
552
|
+
|
|
553
|
+
```javascript
|
|
554
|
+
const { Model } = require('outlet-orm');
|
|
555
|
+
|
|
556
|
+
class Post extends Model { static table = 'posts'; }
|
|
557
|
+
|
|
558
|
+
class User extends Model {
|
|
559
|
+
static table = 'users';
|
|
560
|
+
|
|
561
|
+
posts() {
|
|
562
|
+
return this.hasMany(Post, 'user_id');
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const user = await User.find(1);
|
|
567
|
+
const posts = await user.posts().get();
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Belongs To (belongsTo)
|
|
571
|
+
|
|
572
|
+
```javascript
|
|
573
|
+
const { Model } = require('outlet-orm');
|
|
574
|
+
|
|
575
|
+
class User extends Model { static table = 'users'; }
|
|
576
|
+
|
|
577
|
+
class Post extends Model {
|
|
578
|
+
static table = 'posts';
|
|
579
|
+
|
|
580
|
+
author() {
|
|
581
|
+
return this.belongsTo(User, 'user_id');
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const post = await Post.find(1);
|
|
586
|
+
const author = await post.author().get();
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Many to Many (belongsToMany)
|
|
590
|
+
|
|
591
|
+
```javascript
|
|
592
|
+
const { Model } = require('outlet-orm');
|
|
593
|
+
|
|
594
|
+
class Role extends Model { static table = 'roles'; }
|
|
595
|
+
|
|
596
|
+
class User extends Model {
|
|
597
|
+
static table = 'users';
|
|
598
|
+
|
|
599
|
+
roles() {
|
|
600
|
+
return this.belongsToMany(
|
|
601
|
+
Role,
|
|
602
|
+
'user_roles', // Table pivot
|
|
603
|
+
'user_id', // FK vers User
|
|
604
|
+
'role_id' // FK vers Role
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const user = await User.find(1);
|
|
610
|
+
const roles = await user.roles().get();
|
|
611
|
+
|
|
612
|
+
// Méthodes pivot
|
|
613
|
+
await user.roles().attach([1, 2]); // Attach roles
|
|
614
|
+
await user.roles().detach(2); // Detach a role
|
|
615
|
+
await user.roles().sync([1, 3]); // Synchroniser (remplace tout)
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Has Many Through (hasManyThrough)
|
|
619
|
+
|
|
620
|
+
Access a distant relationship via an intermediate model.
|
|
621
|
+
|
|
622
|
+
```javascript
|
|
623
|
+
const { Model } = require('outlet-orm');
|
|
624
|
+
|
|
625
|
+
class User extends Model {
|
|
626
|
+
// User -> Post -> Comment
|
|
627
|
+
comments() {
|
|
628
|
+
return this.hasManyThrough(Comment, Post, 'user_id', 'post_id');
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const user = await User.find(1);
|
|
633
|
+
const allComments = await user.comments().get();
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### Has One Through (hasOneThrough)
|
|
637
|
+
|
|
638
|
+
```javascript
|
|
639
|
+
const { Model } = require('outlet-orm');
|
|
640
|
+
|
|
641
|
+
class User extends Model {
|
|
642
|
+
// User -> Profile -> Country
|
|
643
|
+
country() {
|
|
644
|
+
return this.hasOneThrough(Country, Profile, 'user_id', 'country_id');
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const user = await User.find(1);
|
|
649
|
+
const country = await user.country().get();
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Polymorphic relationships
|
|
653
|
+
|
|
654
|
+
Polymorphic relationships allow a model to belong to multiple other models.
|
|
655
|
+
|
|
656
|
+
```javascript
|
|
657
|
+
const { Model } = require('outlet-orm');
|
|
658
|
+
|
|
659
|
+
// Configuration du morph map
|
|
660
|
+
Model.setMorphMap({
|
|
661
|
+
'posts': Post,
|
|
662
|
+
'videos': Video
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Models
|
|
666
|
+
class Post extends Model {
|
|
667
|
+
comments() {
|
|
668
|
+
return this.morphMany(Comment, 'commentable');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
class Video extends Model {
|
|
673
|
+
comments() {
|
|
674
|
+
return this.morphMany(Comment, 'commentable');
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
class Comment extends Model {
|
|
679
|
+
commentable() {
|
|
680
|
+
return this.morphTo('commentable');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Usage
|
|
685
|
+
const post = await Post.find(1);
|
|
686
|
+
const comments = await post.comments().get();
|
|
687
|
+
|
|
688
|
+
const comment = await Comment.find(1);
|
|
689
|
+
const parent = await comment.commentable().get(); // Post ou Video
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
**Relations polymorphiques availables:**
|
|
693
|
+
- `morphOne(Related, 'morphName')` - One-to-One polymorphique
|
|
694
|
+
- `morphMany(Related, 'morphName')` - One-to-Many polymorphique
|
|
695
|
+
- `morphTo('morphName')` - Inverse polymorphique
|
|
696
|
+
|
|
697
|
+
### Eager Loading
|
|
698
|
+
|
|
699
|
+
```javascript
|
|
700
|
+
// Charger plusieurs relationships
|
|
701
|
+
const users = await User.with('posts', 'profile', 'roles').get();
|
|
702
|
+
|
|
703
|
+
// Charger avec constraints
|
|
704
|
+
const users = await User.with({
|
|
705
|
+
posts: (q) => q.where('status', 'published').orderBy('created_at', 'desc')
|
|
706
|
+
}).get();
|
|
707
|
+
|
|
708
|
+
// Load nested relationships (dot notation)
|
|
709
|
+
const users = await User.with('posts.comments.author').get();
|
|
710
|
+
|
|
711
|
+
// Charger sur une instance existante
|
|
712
|
+
const user = await User.find(1);
|
|
713
|
+
await user.load('posts', 'profile');
|
|
714
|
+
await user.load(['roles', 'posts.comments']);
|
|
715
|
+
|
|
716
|
+
// Access loaded relationships
|
|
717
|
+
users.forEach(user => {
|
|
718
|
+
console.log(user.relationships.posts);
|
|
719
|
+
console.log(user.relationships.profile);
|
|
720
|
+
});
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
## 🎭 Attributs
|
|
724
|
+
|
|
725
|
+
### Casts
|
|
726
|
+
|
|
727
|
+
Les casts convertissent automatiquement les attributs:
|
|
728
|
+
|
|
729
|
+
```javascript
|
|
730
|
+
const { Model } = require('outlet-orm');
|
|
731
|
+
|
|
732
|
+
class User extends Model {
|
|
733
|
+
static casts = {
|
|
734
|
+
id: 'int', // ou 'integer'
|
|
735
|
+
age: 'integer',
|
|
736
|
+
balance: 'float', // ou 'double'
|
|
737
|
+
email_verified: 'boolean', // ou 'bool'
|
|
738
|
+
metadata: 'json', // Parse JSON
|
|
739
|
+
settings: 'array', // Parse JSON en array
|
|
740
|
+
birthday: 'date' // Convertit en Date
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### Hidden attributes
|
|
746
|
+
|
|
747
|
+
```javascript
|
|
748
|
+
const { Model } = require('outlet-orm');
|
|
749
|
+
|
|
750
|
+
class User extends Model {
|
|
751
|
+
static hidden = ['password', 'secret_token'];
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const user = await User.find(1);
|
|
755
|
+
console.log(user.toJSON()); // password et secret_token exclus
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
#### Show hidden attributes
|
|
759
|
+
|
|
760
|
+
```javascript
|
|
761
|
+
// Include hidden attributes
|
|
762
|
+
const user = await User.withHidden().where('email', 'john@example.com').first();
|
|
763
|
+
console.log(user.toJSON()); // password inclus
|
|
764
|
+
|
|
765
|
+
// Control with a boolean
|
|
766
|
+
const user = await User.withoutHidden(true).first(); // true = afficher
|
|
767
|
+
const user = await User.withoutHidden(false).first(); // false = hide (default)
|
|
768
|
+
|
|
769
|
+
// Cas d'usage: authentification
|
|
770
|
+
const user = await User.withHidden().where('email', email).first();
|
|
771
|
+
if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
|
|
772
|
+
// Authentication successful
|
|
773
|
+
}
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Timestamps
|
|
777
|
+
|
|
778
|
+
```javascript
|
|
779
|
+
const { Model } = require('outlet-orm');
|
|
780
|
+
|
|
781
|
+
// Activés Default (created_at, updated_at)
|
|
782
|
+
class User extends Model {
|
|
783
|
+
static timestamps = true;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Disable
|
|
787
|
+
class Log extends Model {
|
|
788
|
+
static timestamps = false;
|
|
789
|
+
}
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
## 🔄 Transactions
|
|
793
|
+
|
|
794
|
+
Outlet ORM supports transactions to guarantee data integrity:
|
|
795
|
+
|
|
796
|
+
```javascript
|
|
797
|
+
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
798
|
+
|
|
799
|
+
// Method 1: Automatic callback (recommended)
|
|
800
|
+
const db = Model.connection;
|
|
801
|
+
const result = await db.transaction(async (connection) => {
|
|
802
|
+
const user = await User.create({ name: 'John', email: 'john@example.com' });
|
|
803
|
+
await Account.create({ user_id: user.getAttribute('id'), balance: 0 });
|
|
804
|
+
return user;
|
|
805
|
+
});
|
|
806
|
+
// Automatic commit, rollback on error
|
|
807
|
+
|
|
808
|
+
// Method 2: Manual control
|
|
809
|
+
await db.beginTransaction();
|
|
810
|
+
try {
|
|
811
|
+
await User.create({ name: 'Jane' });
|
|
812
|
+
await db.commit();
|
|
813
|
+
} catch (error) {
|
|
814
|
+
await db.rollback();
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
## 🗑️ Soft Deletes
|
|
820
|
+
|
|
821
|
+
Suppression logique avec colonne `deleted_at`:
|
|
822
|
+
|
|
823
|
+
```javascript
|
|
824
|
+
const { Model } = require('outlet-orm');
|
|
825
|
+
|
|
826
|
+
class Post extends Model {
|
|
827
|
+
static table = 'posts';
|
|
828
|
+
static softDeletes = true;
|
|
829
|
+
// static DELETED_AT = 'deleted_at'; // Personnalisable
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Les queries excluent automatiquement les deleteds
|
|
833
|
+
const posts = await Post.all(); // Seulement les non-deleteds
|
|
834
|
+
|
|
835
|
+
// Inclure les deleteds
|
|
836
|
+
const allPosts = await Post.withTrashed().get();
|
|
837
|
+
|
|
838
|
+
// Seulement les deleteds
|
|
839
|
+
const trashedPosts = await Post.onlyTrashed().get();
|
|
840
|
+
|
|
841
|
+
// Delete (soft delete)
|
|
842
|
+
const post = await Post.find(1);
|
|
843
|
+
await post.destroy(); // Sets deleted_at to the current date
|
|
844
|
+
|
|
845
|
+
// Check if deleted
|
|
846
|
+
if (post.trashed()) {
|
|
847
|
+
console.log('This post is deleted');
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Restaurer
|
|
851
|
+
await post.restore();
|
|
852
|
+
|
|
853
|
+
// Delete permanently
|
|
854
|
+
await post.forceDelete();
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
## 🔬 Scopes
|
|
858
|
+
|
|
859
|
+
### Scopes Globaux
|
|
860
|
+
|
|
861
|
+
Applied automatically to all queries:
|
|
862
|
+
|
|
863
|
+
```javascript
|
|
864
|
+
const { Model } = require('outlet-orm');
|
|
865
|
+
|
|
866
|
+
class Post extends Model {
|
|
867
|
+
static table = 'posts';
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Ajouter un scope global
|
|
871
|
+
Post.addGlobalScope('published', (query) => {
|
|
872
|
+
query.where('status', 'published');
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// Toutes les queries filtrent automatiquement
|
|
876
|
+
const posts = await Post.all(); // Published only
|
|
877
|
+
|
|
878
|
+
// Disable temporairement un scope
|
|
879
|
+
const allPosts = await Post.withoutGlobalScope('published').get();
|
|
880
|
+
|
|
881
|
+
// Disable tous les scopes
|
|
882
|
+
const rawPosts = await Post.withoutGlobalScopes().get();
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
## 📣 Events / Hooks
|
|
886
|
+
|
|
887
|
+
Intercept operations on your models:
|
|
888
|
+
|
|
889
|
+
```javascript
|
|
890
|
+
const { Model } = require('outlet-orm');
|
|
891
|
+
|
|
892
|
+
class User extends Model {
|
|
893
|
+
static table = 'users';
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Before creation
|
|
897
|
+
User.creating((user) => {
|
|
898
|
+
user.setAttribute('uuid', generateUUID());
|
|
899
|
+
// Retourner false pour rollback
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// After creation
|
|
903
|
+
User.created((user) => {
|
|
904
|
+
console.log(`Utilisateur ${user.getAttribute('id')} créé`);
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// Before update
|
|
908
|
+
User.updating((user) => {
|
|
909
|
+
user.setAttribute('updated_at', new Date());
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// After update
|
|
913
|
+
User.updated((user) => {
|
|
914
|
+
// Notifier les systèmes externes
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
// saving/saved events (creation AND update)
|
|
918
|
+
User.saving((user) => {
|
|
919
|
+
// Nettoyage des data
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
User.saved((user) => {
|
|
923
|
+
// Cache invalidation
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// Avant/après suppression
|
|
927
|
+
User.deleting((user) => {
|
|
928
|
+
// Checks before deletion
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
User.deleted((user) => {
|
|
932
|
+
// Nettoyage des relationships
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// Pour les soft deletes
|
|
936
|
+
User.restoring((user) => {});
|
|
937
|
+
User.restored((user) => {});
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
## ✅ Validation
|
|
941
|
+
|
|
942
|
+
Built-in basic validation:
|
|
943
|
+
|
|
944
|
+
```javascript
|
|
945
|
+
const { Model } = require('outlet-orm');
|
|
946
|
+
|
|
947
|
+
class User extends Model {
|
|
948
|
+
static table = 'users';
|
|
949
|
+
static rules = {
|
|
950
|
+
name: 'required|string|min:2|max:100',
|
|
951
|
+
email: 'required|email',
|
|
952
|
+
age: 'numeric|min:0|max:150',
|
|
953
|
+
role: 'in:admin,user,guest',
|
|
954
|
+
password: 'required|min:8'
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const user = new User({
|
|
959
|
+
name: 'J',
|
|
960
|
+
email: 'invalid-email',
|
|
961
|
+
age: 200
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// Valider
|
|
965
|
+
const { valid, errors } = user.validate();
|
|
966
|
+
console.log(valid); // false
|
|
967
|
+
console.log(errors);
|
|
968
|
+
// {
|
|
969
|
+
// name: ['name must be at least 2 characters'],
|
|
970
|
+
// email: ['email must be a valid email'],
|
|
971
|
+
// age: ['age must not exceed 150']
|
|
972
|
+
// }
|
|
973
|
+
|
|
974
|
+
// Valider ou lancer une erreur
|
|
975
|
+
try {
|
|
976
|
+
user.validateOrFail();
|
|
977
|
+
} catch (error) {
|
|
978
|
+
console.log(error.errors);
|
|
979
|
+
}
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Available rules
|
|
983
|
+
|
|
984
|
+
| Règle | Description |
|
|
985
|
+
|-------|-------------|
|
|
986
|
+
| `required` | Champ obligatoire |
|
|
987
|
+
| `string` | Must be a string |
|
|
988
|
+
| `number` / `numeric` | Must be a number |
|
|
989
|
+
| `email` | Format email valide |
|
|
990
|
+
| `boolean` | Must be a boolean |
|
|
991
|
+
| `date` | Date valide |
|
|
992
|
+
| `min:N` | Minimum N (longueur ou value) |
|
|
993
|
+
| `max:N` | Maximum N (longueur ou value) |
|
|
994
|
+
| `in:a,b,c` | Valeur parmi la liste |
|
|
995
|
+
| `regex:pattern` | Match le pattern regex |
|
|
996
|
+
|
|
997
|
+
## 📊 Query Logging
|
|
998
|
+
|
|
999
|
+
Debug mode to analyse your queries:
|
|
1000
|
+
|
|
1001
|
+
```javascript
|
|
1002
|
+
const { Model } = require('outlet-orm');
|
|
1003
|
+
|
|
1004
|
+
// Activer le logging
|
|
1005
|
+
const db = Model.getConnection();
|
|
1006
|
+
db.enableQueryLog();
|
|
1007
|
+
|
|
1008
|
+
// Run des queries
|
|
1009
|
+
await User.where('status', 'active').get();
|
|
1010
|
+
await Post.with('author').get();
|
|
1011
|
+
|
|
1012
|
+
// Retrieve the log
|
|
1013
|
+
const queries = db.getQueryLog();
|
|
1014
|
+
console.log(queries);
|
|
1015
|
+
// [
|
|
1016
|
+
// { sql: 'SELECT * FROM users WHERE status = ?', params: ['active'], duration: 15, timestamp: Date },
|
|
1017
|
+
// { sql: 'SELECT * FROM posts', params: [], duration: 8, timestamp: Date }
|
|
1018
|
+
// ]
|
|
1019
|
+
|
|
1020
|
+
// Vider le log
|
|
1021
|
+
db.flushQueryLog();
|
|
1022
|
+
|
|
1023
|
+
// Disable le logging
|
|
1024
|
+
db.disableQueryLog();
|
|
1025
|
+
|
|
1026
|
+
// Check if active
|
|
1027
|
+
if (db.isLogging()) {
|
|
1028
|
+
console.log('Logging actif');
|
|
1029
|
+
}
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
## 📝 API Reference
|
|
1033
|
+
|
|
1034
|
+
### DatabaseConnection
|
|
1035
|
+
|
|
1036
|
+
| Method | Description |
|
|
1037
|
+
|---------|-------------|
|
|
1038
|
+
| `new DatabaseConnection(config?)` | Creates a connection (lit `.env` si config omis) |
|
|
1039
|
+
| `connect()` | Establishes the connection (appelé automatiquement) |
|
|
1040
|
+
| `beginTransaction()` | Starts a transaction |
|
|
1041
|
+
| `commit()` | Valide la transaction |
|
|
1042
|
+
| `rollback()` | Annule la transaction |
|
|
1043
|
+
| `transaction(callback)` | Runs in a transaction (auto commit/rollback) |
|
|
1044
|
+
| `select(table, query)` | Runs a SELECT |
|
|
1045
|
+
| `insert(table, data)` | Inserts a record |
|
|
1046
|
+
| `insertMany(table, data[])` | Inserts multiple records |
|
|
1047
|
+
| `update(table, data, query)` | Updates records |
|
|
1048
|
+
| `delete(table, query)` | Supprime des enregistrements |
|
|
1049
|
+
| `count(table, query)` | Compte les enregistrements |
|
|
1050
|
+
| `executeRawQuery(sql, params?)` | Requête brute (résultats normalisés) |
|
|
1051
|
+
| `execute(sql, params?)` | Requête brute (résultats natifs driver) |
|
|
1052
|
+
| `increment(table, column, query, amount?)` | Incrément atomique |
|
|
1053
|
+
| `decrement(table, column, query, amount?)` | Décrément atomique |
|
|
1054
|
+
| `close()` / `disconnect()` | Ferme la connection |
|
|
1055
|
+
| **Query Logging (static)** | |
|
|
1056
|
+
| `enableQueryLog()` | Active le logging des queries |
|
|
1057
|
+
| `disableQueryLog()` | Désactive le logging |
|
|
1058
|
+
| `getQueryLog()` | Returns the query log |
|
|
1059
|
+
| `flushQueryLog()` | Vide le log |
|
|
1060
|
+
| `isLogging()` | Checks whether logging is active |
|
|
1061
|
+
|
|
1062
|
+
### Model (methods statiques)
|
|
1063
|
+
|
|
1064
|
+
| Method | Description |
|
|
1065
|
+
|---------|-------------|
|
|
1066
|
+
| `setConnection(db)` | Définit la connection Default |
|
|
1067
|
+
| `getConnection()` | Gets the connection (v3.0.0+) |
|
|
1068
|
+
| `setMorphMap(map)` | Defines polymorphic mapping |
|
|
1069
|
+
| `query()` | Retourne un QueryBuilder |
|
|
1070
|
+
| `all()` | Tous les enregistrements |
|
|
1071
|
+
| `find(id)` | Trouve par ID |
|
|
1072
|
+
| `findOrFail(id)` | Trouve ou lance une erreur |
|
|
1073
|
+
| `first()` | Premier enregistrement |
|
|
1074
|
+
| `where(col, op?, val)` | Clause WHERE |
|
|
1075
|
+
| `whereIn(col, vals)` | Clause WHERE IN |
|
|
1076
|
+
| `whereNull(col)` | Clause WHERE NULL |
|
|
1077
|
+
| `whereNotNull(col)` | Clause WHERE NOT NULL |
|
|
1078
|
+
| `create(attrs)` | Creates and saves |
|
|
1079
|
+
| `insert(data)` | Insert brut |
|
|
1080
|
+
| `update(attrs)` | Update bulk |
|
|
1081
|
+
| `updateById(id, attrs)` | Update par ID |
|
|
1082
|
+
| `updateAndFetchById(id, attrs, rels?)` | Update + fetch avec relationships |
|
|
1083
|
+
| `delete()` | Delete bulk |
|
|
1084
|
+
| `with(...rels)` | Eager loading |
|
|
1085
|
+
| `withHidden()` | Includes hidden attributes |
|
|
1086
|
+
| `withoutHidden(show?)` | Contrôle visibilité |
|
|
1087
|
+
| `orderBy(col, dir?)` | Tri |
|
|
1088
|
+
| `limit(n)` / `offset(n)` | Limite/Offset |
|
|
1089
|
+
| `paginate(page, perPage)` | Pagination |
|
|
1090
|
+
| `count()` | Compte |
|
|
1091
|
+
| **Soft Deletes** | |
|
|
1092
|
+
| `withTrashed()` | Inclut les deleteds |
|
|
1093
|
+
| `onlyTrashed()` | Seulement les deleteds |
|
|
1094
|
+
| **Scopes** | |
|
|
1095
|
+
| `addGlobalScope(name, cb)` | Adds a global scope |
|
|
1096
|
+
| `removeGlobalScope(name)` | Removes a scope |
|
|
1097
|
+
| `withoutGlobalScope(name)` | Query without one scope |
|
|
1098
|
+
| `withoutGlobalScopes()` | Query without all scopes |
|
|
1099
|
+
| **Events** | |
|
|
1100
|
+
| `on(event, callback)` | Enregistre un listener |
|
|
1101
|
+
| `creating(cb)` / `created(cb)` | Creation events |
|
|
1102
|
+
| `updating(cb)` / `updated(cb)` | Update events |
|
|
1103
|
+
| `saving(cb)` / `saved(cb)` | Events sauvegarde |
|
|
1104
|
+
| `deleting(cb)` / `deleted(cb)` | Events suppression |
|
|
1105
|
+
| `restoring(cb)` / `restored(cb)` | Events restauration |
|
|
1106
|
+
|
|
1107
|
+
### Model (methods d'instance)
|
|
1108
|
+
|
|
1109
|
+
| Method | Description |
|
|
1110
|
+
|---------|-------------|
|
|
1111
|
+
| `fill(attrs)` | Fills attributes |
|
|
1112
|
+
| `setAttribute(key, val)` | Sets an attribute |
|
|
1113
|
+
| `getAttribute(key)` | Gets an attribute |
|
|
1114
|
+
| `save()` | Sauvegarde (insert ou update) |
|
|
1115
|
+
| `destroy()` | Supprime l'instance (soft si activé) |
|
|
1116
|
+
| `load(...rels)` | Charge des relationships |
|
|
1117
|
+
| `getDirty()` | Attributs modifiés |
|
|
1118
|
+
| `isDirty()` | Has been modified? |
|
|
1119
|
+
| `toJSON()` | Convertit en objet |
|
|
1120
|
+
| **Soft Deletes** | |
|
|
1121
|
+
| `trashed()` | Is deleted? |
|
|
1122
|
+
| `restore()` | Restore le model |
|
|
1123
|
+
| `forceDelete()` | Permanent deletion |
|
|
1124
|
+
| **Validation** | |
|
|
1125
|
+
| `validate()` | Valide selon les rules |
|
|
1126
|
+
| `validateOrFail()` | Valide ou lance erreur |
|
|
1127
|
+
|
|
1128
|
+
### QueryBuilder
|
|
1129
|
+
|
|
1130
|
+
| Method | Description |
|
|
1131
|
+
|---------|-------------|
|
|
1132
|
+
| `select(...cols)` / `columns([...])` | Column selection |
|
|
1133
|
+
| `distinct()` | SELECT DISTINCT |
|
|
1134
|
+
| `where(col, op?, val)` | Clause WHERE |
|
|
1135
|
+
| `whereIn(col, vals)` | WHERE IN |
|
|
1136
|
+
| `whereNotIn(col, vals)` | WHERE NOT IN |
|
|
1137
|
+
| `whereNull(col)` | WHERE NULL |
|
|
1138
|
+
| `whereNotNull(col)` | WHERE NOT NULL |
|
|
1139
|
+
| `orWhere(col, op?, val)` | OR WHERE |
|
|
1140
|
+
| `whereBetween(col, [min, max])` | WHERE BETWEEN |
|
|
1141
|
+
| `whereLike(col, pattern)` | WHERE LIKE |
|
|
1142
|
+
| `whereHas(rel, cb?)` | Filtre par relation |
|
|
1143
|
+
| `has(rel, op?, count)` | Existence relationnelle |
|
|
1144
|
+
| `whereDoesntHave(rel)` | Absence de relation |
|
|
1145
|
+
| `orderBy(col, dir?)` / `ordrer(...)` | Tri |
|
|
1146
|
+
| `limit(n)` / `take(n)` | Limite |
|
|
1147
|
+
| `offset(n)` / `skip(n)` | Offset |
|
|
1148
|
+
| `groupBy(...cols)` | GROUP BY |
|
|
1149
|
+
| `having(col, op, val)` | HAVING |
|
|
1150
|
+
| `join(table, first, op?, second)` | INNER JOIN |
|
|
1151
|
+
| `leftJoin(table, first, op?, second)` | LEFT JOIN |
|
|
1152
|
+
| `with(...rels)` | Eager loading |
|
|
1153
|
+
| `withCount(rels)` | Ajoute {rel}_count |
|
|
1154
|
+
| `withTrashed()` | Inclut les deleteds |
|
|
1155
|
+
| `onlyTrashed()` | Seulement les deleteds |
|
|
1156
|
+
| `withoutGlobalScope(name)` | Sans un scope global |
|
|
1157
|
+
| `withoutGlobalScopes()` | Sans tous les scopes |
|
|
1158
|
+
| `get()` | Runs and returns all |
|
|
1159
|
+
| `first()` | First result |
|
|
1160
|
+
| `firstOrFail()` | Premier ou erreur |
|
|
1161
|
+
| `paginate(page, perPage)` | Pagination |
|
|
1162
|
+
| `count()` | Compte |
|
|
1163
|
+
| `exists()` | Checks existence |
|
|
1164
|
+
| `insert(data)` | Insert |
|
|
1165
|
+
| `update(attrs)` | Update |
|
|
1166
|
+
| `updateAndFetch(attrs, rels?)` | Update + fetch |
|
|
1167
|
+
| `delete()` | Delete |
|
|
1168
|
+
| `increment(col, amount?)` | Incrément atomique |
|
|
1169
|
+
| `decrement(col, amount?)` | Décrément atomique |
|
|
1170
|
+
| `clone()` | Clone le query builder |
|
|
1171
|
+
|
|
1172
|
+
## 🛠️ CLI tools
|
|
1173
|
+
|
|
1174
|
+
### outlet-init
|
|
1175
|
+
|
|
1176
|
+
Initialise un nouveau projet avec configuration de database.
|
|
1177
|
+
|
|
1178
|
+
```bash
|
|
1179
|
+
outlet-init
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
Generates:
|
|
1183
|
+
- Configuration file `database/config.js`
|
|
1184
|
+
- `.env` file with settings
|
|
1185
|
+
- Example model
|
|
1186
|
+
- Usage file
|
|
1187
|
+
|
|
1188
|
+
### outlet-migrate
|
|
1189
|
+
|
|
1190
|
+
complete migration system.
|
|
1191
|
+
|
|
1192
|
+
```bash
|
|
1193
|
+
# Create une migration
|
|
1194
|
+
outlet-migrate make create_users_table
|
|
1195
|
+
|
|
1196
|
+
# Run les migrations
|
|
1197
|
+
outlet-migrate migrate
|
|
1198
|
+
|
|
1199
|
+
# See le statut
|
|
1200
|
+
outlet-migrate status
|
|
1201
|
+
|
|
1202
|
+
# Roll back the latest migration
|
|
1203
|
+
outlet-migrate rollback --steps 1
|
|
1204
|
+
|
|
1205
|
+
# Reset toutes les migrations
|
|
1206
|
+
outlet-migrate reset --yes
|
|
1207
|
+
|
|
1208
|
+
# Refresh (reset + migrate)
|
|
1209
|
+
outlet-migrate refresh --yes
|
|
1210
|
+
|
|
1211
|
+
# Fresh (drop all + migrate)
|
|
1212
|
+
outlet-migrate fresh --yes
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
**Features des Migrations:**
|
|
1216
|
+
|
|
1217
|
+
- ✅ Creation and management of migrations (create, alter, drop tables)
|
|
1218
|
+
- ✅ Types de colonnes: id, string, text, integer, boolean, date, datetime, timestamp, decimal, float, json, enum, uuid, foreignId
|
|
1219
|
+
- ✅ Modificateurs: nullable, default, unique, index, unsigned, autoIncrement, comment, after, first
|
|
1220
|
+
- ✅ Foreign keys: foreign(), constrained(), onDelete(), onUpdate(), CASCADE
|
|
1221
|
+
- ✅ Index: index(), unique(), fullText()
|
|
1222
|
+
- ✅ Manipulation: renameColumn(), dropColumn(), dropTimestamps()
|
|
1223
|
+
- ✅ Reversible migrations: Méthodes up() et down()
|
|
1224
|
+
- ✅ Batch tracking: Precise rollback by batch
|
|
1225
|
+
- ✅ Custom SQL: execute() pour advanced commands
|
|
1226
|
+
|
|
1227
|
+
### outlet-convert
|
|
1228
|
+
|
|
1229
|
+
Converts SQL schemas into ORM models.
|
|
1230
|
+
|
|
1231
|
+
```bash
|
|
1232
|
+
outlet-convert
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
**Options:**
|
|
1236
|
+
1. Depuis un file SQL local
|
|
1237
|
+
2. From a connected database
|
|
1238
|
+
|
|
1239
|
+
**Features:**
|
|
1240
|
+
- ✅ Detection automatique des types et casts
|
|
1241
|
+
- ✅ Automatic generation of ALL relationships (belongsTo, hasMany, hasOne, belongsToMany)
|
|
1242
|
+
- ✅ Recursive relationships (auto-relationships)
|
|
1243
|
+
- ✅ Detection des champs sensibles (password, token, etc.)
|
|
1244
|
+
- ✅ Support des timestamps automatiques
|
|
1245
|
+
- ✅ Conversion des noms en PascalCase
|
|
1246
|
+
|
|
1247
|
+
## 📚 Documentation
|
|
1248
|
+
|
|
1249
|
+
- [Guide des Migrations](docs/MIGRATIONS.md)
|
|
1250
|
+
- [Conversion SQL](docs/SQL_CONVERSION.md)
|
|
1251
|
+
- [Detection des Relations](docs/RELATIONS_DETECTION.md)
|
|
1252
|
+
- [Quick Start Guide](docs/QUICKSTART.md)
|
|
1253
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
1254
|
+
- [**TypeScript (complet)**](docs/TYPESCRIPT.md)
|
|
1255
|
+
|
|
1256
|
+
## 📘 TypeScript Support
|
|
1257
|
+
|
|
1258
|
+
Outlet ORM v4.0.0 inclut des définitions TypeScript completes avec support des **generics pour les attributs typeds**.
|
|
1259
|
+
|
|
1260
|
+
### Typed models
|
|
1261
|
+
|
|
1262
|
+
```typescript
|
|
1263
|
+
import { Model, HasManyRelation } from 'outlet-orm';
|
|
1264
|
+
|
|
1265
|
+
interface UserAttributes {
|
|
1266
|
+
id: number;
|
|
1267
|
+
name: string;
|
|
1268
|
+
email: string;
|
|
1269
|
+
role: 'admin' | 'user';
|
|
1270
|
+
created_at: Date;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
class User extends Model<UserAttributes> {
|
|
1274
|
+
static table = 'users';
|
|
1275
|
+
static fillable = ['name', 'email', 'role'];
|
|
1276
|
+
|
|
1277
|
+
posts(): HasManyRelation<Post> {
|
|
1278
|
+
return this.hasMany(Post, 'user_id');
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// Type-safe getAttribute/setAttribute
|
|
1283
|
+
const user = await User.find(1);
|
|
1284
|
+
const name: string = user.getAttribute('name'); // ✅ Inferred type
|
|
1285
|
+
const role: 'admin' | 'user' = user.getAttribute('role');
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
### Migrations typedes
|
|
1289
|
+
|
|
1290
|
+
```typescript
|
|
1291
|
+
import { MigrationInterface, Schema, TableBuilder } from 'outlet-orm';
|
|
1292
|
+
|
|
1293
|
+
export const migration: MigrationInterface = {
|
|
1294
|
+
name: 'create_users_table',
|
|
1295
|
+
|
|
1296
|
+
async up(): Promise<void> {
|
|
1297
|
+
await Schema.create('users', (table: TableBuilder) => {
|
|
1298
|
+
table.id();
|
|
1299
|
+
table.string('name');
|
|
1300
|
+
table.string('email').unique();
|
|
1301
|
+
table.timestamps();
|
|
1302
|
+
});
|
|
1303
|
+
},
|
|
1304
|
+
|
|
1305
|
+
async down(): Promise<void> {
|
|
1306
|
+
await Schema.dropIfExists('users');
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
📖 [Guide TypeScript complet](docs/TYPESCRIPT.md)
|
|
1312
|
+
|
|
1313
|
+
## 🤝 Contributions
|
|
1314
|
+
|
|
1315
|
+
Contributions are welcome! Feel free to open an issue or pull request.
|
|
1316
|
+
|
|
1317
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
|
1318
|
+
|
|
1319
|
+
## 📄 Licence
|
|
1320
|
+
|
|
1321
|
+
MIT - See [LICENSE](LICENSE) for details.
|
|
1322
|
+
|
|
1323
|
+
---
|
|
1324
|
+
|
|
1325
|
+
Created by [omgbwa-yasse](https://github.com/omgbwa-yasse)
|