outlet-orm 4.2.0 → 5.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 +1312 -1199
- package/bin/init.js +379 -221
- package/bin/migrate.js +440 -442
- package/package.json +76 -77
- package/src/Database/DatabaseConnection.js +4 -0
- package/{lib → src}/Migrations/Migration.js +48 -48
- package/{lib → src}/Migrations/MigrationManager.js +326 -326
- package/src/Model.js +1118 -1118
- package/{lib → src}/Schema/Schema.js +790 -790
- package/src/index.js +49 -31
- package/types/index.d.ts +660 -660
- package/lib/Database/DatabaseConnection.js +0 -4
package/README.md
CHANGED
|
@@ -1,1199 +1,1312 @@
|
|
|
1
|
-
# Outlet ORM
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/outlet-orm)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
|
|
6
|
-
Un ORM JavaScript inspiré de Laravel Eloquent pour Node.js avec support pour MySQL, PostgreSQL et SQLite.
|
|
7
|
-
|
|
8
|
-
📚 **[Documentation complète disponible dans `/docs`](./docs/INDEX.md)**
|
|
9
|
-
|
|
10
|
-
## ✅ Prérequis et compatibilité
|
|
11
|
-
|
|
12
|
-
- Node.js >= 18 (recommandé/exigé)
|
|
13
|
-
- Installez le driver de base de données correspondant à votre SGBD (voir ci-dessous)
|
|
14
|
-
|
|
15
|
-
## 🚀 Installation
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install outlet-orm
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Installer le driver de base de données
|
|
22
|
-
|
|
23
|
-
Outlet ORM utilise des peerDependencies optionnelles pour les drivers de base de données. 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
|
-
Si aucun driver n'est installé, un message d'erreur explicite vous indiquera lequel installer lors de la connexion.
|
|
30
|
-
|
|
31
|
-
## 📁 Structure de Projet Recommandée
|
|
32
|
-
|
|
33
|
-
Organisez votre projet utilisant Outlet ORM
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
├──
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
│
|
|
44
|
-
├──
|
|
45
|
-
│ ├──
|
|
46
|
-
│
|
|
47
|
-
│
|
|
48
|
-
├──
|
|
49
|
-
│ ├──
|
|
50
|
-
│
|
|
51
|
-
│
|
|
52
|
-
├──
|
|
53
|
-
│ ├── index.js
|
|
54
|
-
│ ├──
|
|
55
|
-
│
|
|
56
|
-
|
|
57
|
-
│
|
|
58
|
-
│
|
|
59
|
-
├──
|
|
60
|
-
│ ├──
|
|
61
|
-
│ └──
|
|
62
|
-
|
|
63
|
-
│ ├──
|
|
64
|
-
│ ├──
|
|
65
|
-
│ ├──
|
|
66
|
-
│ ├──
|
|
67
|
-
│
|
|
68
|
-
│
|
|
69
|
-
├──
|
|
70
|
-
│
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
const users = await User.all();
|
|
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
|
-
|
|
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
|
-
const
|
|
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
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
//
|
|
394
|
-
const
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
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
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const
|
|
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
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
//
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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
|
-
|
|
919
|
-
|
|
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
|
-
|
|
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
|
-
| `count()` | Compte |
|
|
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
|
-
|
|
1
|
+
# Outlet ORM
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/outlet-orm)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Un ORM JavaScript inspiré de Laravel Eloquent pour Node.js avec support pour MySQL, PostgreSQL et SQLite.
|
|
7
|
+
|
|
8
|
+
📚 **[Documentation complète disponible dans `/docs`](./docs/INDEX.md)**
|
|
9
|
+
|
|
10
|
+
## ✅ Prérequis et compatibilité
|
|
11
|
+
|
|
12
|
+
- Node.js >= 18 (recommandé/exigé)
|
|
13
|
+
- Installez le driver de base de données correspondant à votre SGBD (voir ci-dessous)
|
|
14
|
+
|
|
15
|
+
## 🚀 Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install outlet-orm
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Installer le driver de base de données
|
|
22
|
+
|
|
23
|
+
Outlet ORM utilise des peerDependencies optionnelles pour les drivers de base de données. 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
|
+
Si aucun driver n'est installé, un message d'erreur explicite vous indiquera lequel installer lors de la connexion.
|
|
30
|
+
|
|
31
|
+
## 📁 Structure de Projet Recommandée
|
|
32
|
+
|
|
33
|
+
Organisez votre projet utilisant Outlet ORM avec une **architecture en couches** (recommandée pour la production) :
|
|
34
|
+
|
|
35
|
+
> 🔐 **Sécurité** : Voir le [Guide de Sécurité](./docs/SECURITY.md) pour les bonnes pratiques.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
mon-projet/
|
|
39
|
+
├── .env # ⚠️ JAMAIS commité (dans .gitignore)
|
|
40
|
+
├── .env.example # Template sans secrets
|
|
41
|
+
├── .gitignore
|
|
42
|
+
├── package.json
|
|
43
|
+
│
|
|
44
|
+
├── src/ # 📦 Code source centralisé
|
|
45
|
+
│ ├── index.js # Point d'entrée de l'application
|
|
46
|
+
│ │
|
|
47
|
+
│ ├── config/ # ⚙️ Configuration
|
|
48
|
+
│ │ ├── app.js # Config générale (port, env)
|
|
49
|
+
│ │ ├── database.js # Config DB (lit .env)
|
|
50
|
+
│ │ └── security.js # CORS, helmet, rate limit
|
|
51
|
+
│ │
|
|
52
|
+
│ ├── models/ # 📊 Couche Data (Entities)
|
|
53
|
+
│ │ ├── index.js # Export centralisé des models
|
|
54
|
+
│ │ ├── User.js
|
|
55
|
+
│ │ ├── Post.js
|
|
56
|
+
│ │ └── Comment.js
|
|
57
|
+
│ │
|
|
58
|
+
│ ├── repositories/ # 🗄️ Couche Accès Données
|
|
59
|
+
│ │ ├── BaseRepository.js # Méthodes CRUD génériques
|
|
60
|
+
│ │ ├── UserRepository.js # Requêtes spécifiques User
|
|
61
|
+
│ │ └── PostRepository.js
|
|
62
|
+
│ │
|
|
63
|
+
│ ├── services/ # 💼 Couche Métier (Business Logic)
|
|
64
|
+
│ │ ├── AuthService.js # Logique d'authentification
|
|
65
|
+
│ │ ├── UserService.js # Logique métier utilisateur
|
|
66
|
+
│ │ ├── PostService.js
|
|
67
|
+
│ │ └── EmailService.js # Service externe (emails)
|
|
68
|
+
│ │
|
|
69
|
+
│ ├── controllers/ # 🎮 Couche Présentation (HTTP)
|
|
70
|
+
│ │ ├── AuthController.js
|
|
71
|
+
│ │ ├── UserController.js
|
|
72
|
+
│ │ └── PostController.js
|
|
73
|
+
│ │
|
|
74
|
+
│ ├── routes/ # 🛤️ Définition des routes
|
|
75
|
+
│ │ ├── index.js # Agrégateur de routes
|
|
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 centralisée erreurs
|
|
86
|
+
│ │
|
|
87
|
+
│ ├── validators/ # ✅ Schémas de validation
|
|
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 # Formatage réponses API
|
|
96
|
+
│
|
|
97
|
+
├── database/
|
|
98
|
+
│ ├── config.js # Config migrations (outlet-init)
|
|
99
|
+
│ ├── migrations/ # Fichiers de migration
|
|
100
|
+
│ └── seeders/ # Données de test/démo
|
|
101
|
+
│ └── UserSeeder.js
|
|
102
|
+
│
|
|
103
|
+
├── public/ # ✅ Fichiers statiques publics
|
|
104
|
+
│ ├── images/
|
|
105
|
+
│ ├── css/
|
|
106
|
+
│ └── js/
|
|
107
|
+
│
|
|
108
|
+
├── uploads/ # ⚠️ Fichiers uploadés
|
|
109
|
+
│
|
|
110
|
+
├── logs/ # 📋 Journaux (non versionnés)
|
|
111
|
+
│
|
|
112
|
+
└── tests/ # 🧪 Tests
|
|
113
|
+
├── unit/ # Tests unitaires
|
|
114
|
+
│ ├── services/
|
|
115
|
+
│ └── models/
|
|
116
|
+
├── integration/ # Tests d'intégration
|
|
117
|
+
│ └── api/
|
|
118
|
+
└── fixtures/ # Données de test
|
|
119
|
+
└── users.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 🏗️ Architecture en Couches
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
126
|
+
│ HTTP Request │
|
|
127
|
+
└─────────────────────────────────────────────────────────────┘
|
|
128
|
+
│
|
|
129
|
+
▼
|
|
130
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
131
|
+
│ MIDDLEWARES: auth → validate → rateLimiter → errorHandler │
|
|
132
|
+
└─────────────────────────────────────────────────────────────┘
|
|
133
|
+
│
|
|
134
|
+
▼
|
|
135
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
136
|
+
│ ROUTES → CONTROLLERS (Couche Présentation) │
|
|
137
|
+
│ Reçoit la requête, appelle le service, retourne réponse │
|
|
138
|
+
└─────────────────────────────────────────────────────────────┘
|
|
139
|
+
│
|
|
140
|
+
▼
|
|
141
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
142
|
+
│ SERVICES (Couche Métier / Business Logic) │
|
|
143
|
+
│ Logique métier, orchestration, règles business │
|
|
144
|
+
└─────────────────────────────────────────────────────────────┘
|
|
145
|
+
│
|
|
146
|
+
▼
|
|
147
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
148
|
+
│ REPOSITORIES (Couche Accès Données) │
|
|
149
|
+
│ Abstraction des requêtes DB, utilise les Models │
|
|
150
|
+
└─────────────────────────────────────────────────────────────┘
|
|
151
|
+
│
|
|
152
|
+
▼
|
|
153
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
154
|
+
│ MODELS (Outlet ORM) → DATABASE │
|
|
155
|
+
└─────────────────────────────────────────────────────────────┘
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 📋 Rôle de chaque couche
|
|
159
|
+
|
|
160
|
+
| Couche | Dossier | Responsabilité | Dépend de |
|
|
161
|
+
|--------|---------|----------------|-----------|
|
|
162
|
+
| **Présentation** | `controllers/` | Traiter HTTP, valider entrées, formater réponses | Services |
|
|
163
|
+
| **Métier** | `services/` | Logique business, orchestration, règles | Repositories |
|
|
164
|
+
| **Données** | `repositories/` | Requêtes DB complexes, abstraction | Models |
|
|
165
|
+
| **Entités** | `models/` | Définition des entités, relations, validations | Outlet ORM |
|
|
166
|
+
|
|
167
|
+
### ✅ Avantages de cette architecture
|
|
168
|
+
|
|
169
|
+
- **Testabilité** : Chaque couche peut être testée indépendamment
|
|
170
|
+
- **Maintenabilité** : Séparation claire des responsabilités
|
|
171
|
+
- **Scalabilité** : Facile d'ajouter de nouvelles fonctionnalités
|
|
172
|
+
- **Réutilisabilité** : Services utilisables depuis CLI, workers, etc.
|
|
173
|
+
|
|
174
|
+
### 📝 Exemple de flux
|
|
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
|
+
## ✨ Fonctionnalités clés
|
|
200
|
+
|
|
201
|
+
- **API inspirée d'Eloquent** (Active Record) pour un usage fluide
|
|
202
|
+
- **Query Builder expressif**: where/joins/order/limit/offset/paginate
|
|
203
|
+
- **Filtres relationnels façon Laravel**: `whereHas()`, `has()`, `whereDoesntHave()`, `withCount()`
|
|
204
|
+
- **Eager Loading** des relations via `.with(...)` avec contraintes et dot-notation
|
|
205
|
+
- **Relations complètes**:
|
|
206
|
+
- `hasOne`, `hasMany`, `belongsTo`, `belongsToMany` (avec attach/detach/sync)
|
|
207
|
+
- `hasManyThrough`, `hasOneThrough` (relations transitives)
|
|
208
|
+
- `morphOne`, `morphMany`, `morphTo` (relations polymorphiques)
|
|
209
|
+
- **Transactions** complètes: `beginTransaction()`, `commit()`, `rollback()`, `transaction()`
|
|
210
|
+
- **Soft Deletes**: suppression logique avec `deleted_at`, `withTrashed()`, `onlyTrashed()`, `restore()`
|
|
211
|
+
- **Scopes**: globaux et locaux pour réutiliser vos filtres
|
|
212
|
+
- **Events/Hooks**: `creating`, `created`, `updating`, `updated`, `deleting`, `deleted`, etc.
|
|
213
|
+
- **Validation**: règles basiques intégrées (`required`, `email`, `min`, `max`, etc.)
|
|
214
|
+
- **Query Logging**: mode debug avec `enableQueryLog()` et `getQueryLog()`
|
|
215
|
+
- **Pool PostgreSQL**: connexions poolées pour de meilleures performances
|
|
216
|
+
- **Protection SQL**: sanitization automatique des identifiants
|
|
217
|
+
- **Casts automatiques** (int, float, boolean, json, date...)
|
|
218
|
+
- **Attributs masqués** (`hidden`) et timestamps automatiques
|
|
219
|
+
- **Contrôle de visibilité** des attributs cachés: `withHidden()` et `withoutHidden()`
|
|
220
|
+
- **Incrément/Décrément atomiques**: `increment()` et `decrement()`
|
|
221
|
+
- **Aliases ergonomiques**: `columns([...])`, `ordrer()` (alias typo de `orderBy`)
|
|
222
|
+
- **Requêtes brutes**: `executeRawQuery()` et `execute()` (résultats natifs du driver)
|
|
223
|
+
- **Migrations complètes** (create/alter/drop, index, foreign keys, batch tracking)
|
|
224
|
+
- **CLI pratiques**: `outlet-init`, `outlet-migrate`, `outlet-convert`
|
|
225
|
+
- **Configuration via `.env`** (chargée automatiquement)
|
|
226
|
+
- **Multi-base de données**: MySQL, PostgreSQL et SQLite
|
|
227
|
+
- **Types TypeScript complets** avec Generic Model et Schema Builder typé (v4.0.0+)
|
|
228
|
+
|
|
229
|
+
## ⚡ Démarrage Rapide
|
|
230
|
+
|
|
231
|
+
### Initialisation du projet
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Créer la configuration initiale
|
|
235
|
+
outlet-init
|
|
236
|
+
|
|
237
|
+
# Créer une migration
|
|
238
|
+
outlet-migrate make create_users_table
|
|
239
|
+
|
|
240
|
+
# Exécuter les migrations
|
|
241
|
+
outlet-migrate migrate
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## 📖 Utilisation
|
|
245
|
+
|
|
246
|
+
### Configuration de la connexion
|
|
247
|
+
|
|
248
|
+
Outlet ORM charge automatiquement la connexion depuis le fichier `.env`. **Plus besoin d'importer DatabaseConnection !**
|
|
249
|
+
|
|
250
|
+
#### Fichier `.env`
|
|
251
|
+
|
|
252
|
+
```env
|
|
253
|
+
DB_DRIVER=mysql
|
|
254
|
+
DB_HOST=localhost
|
|
255
|
+
DB_DATABASE=myapp
|
|
256
|
+
DB_USER=root
|
|
257
|
+
DB_PASSWORD=secret
|
|
258
|
+
DB_PORT=3306
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Utilisation simplifiée
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
const { Model } = require('outlet-orm');
|
|
265
|
+
|
|
266
|
+
class User extends Model {
|
|
267
|
+
static table = 'users';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// C'est tout ! La connexion est automatique
|
|
271
|
+
const users = await User.all();
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### Configuration manuelle (optionnel)
|
|
275
|
+
|
|
276
|
+
Si vous avez besoin de contrôler la connexion :
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
280
|
+
|
|
281
|
+
// Option 1 – via .env (aucun paramètre nécessaire)
|
|
282
|
+
const db = new DatabaseConnection();
|
|
283
|
+
|
|
284
|
+
// Option 2 – via objet de configuration
|
|
285
|
+
const db = new DatabaseConnection({
|
|
286
|
+
driver: 'mysql',
|
|
287
|
+
host: 'localhost',
|
|
288
|
+
database: 'myapp',
|
|
289
|
+
user: 'root',
|
|
290
|
+
password: 'secret',
|
|
291
|
+
port: 3306
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Définir la connexion manuellement (optionnel)
|
|
295
|
+
Model.setConnection(db);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### Variables d'environnement (.env)
|
|
299
|
+
|
|
300
|
+
| Variable | Description | Par défaut |
|
|
301
|
+
|----------|-------------|------------|
|
|
302
|
+
| `DB_DRIVER` | `mysql`, `postgres`, `sqlite` | `mysql` |
|
|
303
|
+
| `DB_HOST` | Hôte de la base | `localhost` |
|
|
304
|
+
| `DB_PORT` | Port de connexion | Selon driver |
|
|
305
|
+
| `DB_USER` / `DB_USERNAME` | Identifiant | - |
|
|
306
|
+
| `DB_PASSWORD` | Mot de passe | - |
|
|
307
|
+
| `DB_DATABASE` / `DB_NAME` | Nom de la base | - |
|
|
308
|
+
| `DB_FILE` / `SQLITE_DB` | Fichier SQLite | `:memory:` |
|
|
309
|
+
|
|
310
|
+
### Importation
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
// CommonJS - Import simple (connexion automatique via .env)
|
|
314
|
+
const { Model } = require('outlet-orm');
|
|
315
|
+
|
|
316
|
+
// ES Modules
|
|
317
|
+
import { Model } from 'outlet-orm';
|
|
318
|
+
|
|
319
|
+
// Si besoin de contrôle manuel sur la connexion
|
|
320
|
+
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Définir un modèle
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
const { Model } = require('outlet-orm');
|
|
327
|
+
|
|
328
|
+
// Définition des modèles liés (voir Relations)
|
|
329
|
+
class Post extends Model { static table = 'posts'; }
|
|
330
|
+
class Profile extends Model { static table = 'profiles'; }
|
|
331
|
+
|
|
332
|
+
class User extends Model {
|
|
333
|
+
static table = 'users';
|
|
334
|
+
static primaryKey = 'id'; // Par défaut: 'id'
|
|
335
|
+
static timestamps = true; // Par défaut: true
|
|
336
|
+
static fillable = ['name', 'email', 'password'];
|
|
337
|
+
static hidden = ['password'];
|
|
338
|
+
static casts = {
|
|
339
|
+
id: 'int',
|
|
340
|
+
email_verified: 'boolean',
|
|
341
|
+
metadata: 'json',
|
|
342
|
+
birthday: 'date'
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Relations
|
|
346
|
+
posts() {
|
|
347
|
+
return this.hasMany(Post, 'user_id');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
profile() {
|
|
351
|
+
return this.hasOne(Profile, 'user_id');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Opérations CRUD
|
|
357
|
+
|
|
358
|
+
#### Créer
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
// Méthode 1: create()
|
|
362
|
+
const user = await User.create({
|
|
363
|
+
name: 'John Doe',
|
|
364
|
+
email: 'john@example.com',
|
|
365
|
+
password: 'secret123'
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Méthode 2: new + save()
|
|
369
|
+
const user = new User({
|
|
370
|
+
name: 'Jane Doe',
|
|
371
|
+
email: 'jane@example.com'
|
|
372
|
+
});
|
|
373
|
+
user.setAttribute('password', 'secret456');
|
|
374
|
+
await user.save();
|
|
375
|
+
|
|
376
|
+
// Insert brut (sans créer d'instance)
|
|
377
|
+
await User.insert({ name: 'Bob', email: 'bob@example.com' });
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Lire
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
// Tous les enregistrements
|
|
384
|
+
const users = await User.all();
|
|
385
|
+
|
|
386
|
+
// Par ID
|
|
387
|
+
const user = await User.find(1);
|
|
388
|
+
const user = await User.findOrFail(1); // Lance une erreur si non trouvé
|
|
389
|
+
|
|
390
|
+
// Premier résultat
|
|
391
|
+
const firstUser = await User.first();
|
|
392
|
+
|
|
393
|
+
// Avec conditions
|
|
394
|
+
const activeUsers = await User
|
|
395
|
+
.where('status', 'active')
|
|
396
|
+
.where('age', '>', 18)
|
|
397
|
+
.get();
|
|
398
|
+
|
|
399
|
+
// Avec relations (Eager Loading)
|
|
400
|
+
const usersWithPosts = await User
|
|
401
|
+
.with('posts', 'profile')
|
|
402
|
+
.get();
|
|
403
|
+
|
|
404
|
+
// Ordonner et limiter
|
|
405
|
+
const recentUsers = await User
|
|
406
|
+
.orderBy('created_at', 'desc')
|
|
407
|
+
.limit(10)
|
|
408
|
+
.get();
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### Mettre à jour
|
|
412
|
+
|
|
413
|
+
```javascript
|
|
414
|
+
// Instance
|
|
415
|
+
const user = await User.find(1);
|
|
416
|
+
user.setAttribute('name', 'Updated Name');
|
|
417
|
+
await user.save();
|
|
418
|
+
|
|
419
|
+
// Bulk update
|
|
420
|
+
await User
|
|
421
|
+
.where('status', 'pending')
|
|
422
|
+
.update({ status: 'active' });
|
|
423
|
+
|
|
424
|
+
// Update + Fetch (comme Prisma)
|
|
425
|
+
const updated = await User
|
|
426
|
+
.where('id', 1)
|
|
427
|
+
.updateAndFetch({ name: 'Neo' }, ['profile', 'posts']);
|
|
428
|
+
|
|
429
|
+
// Helpers par ID
|
|
430
|
+
const user = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
|
|
431
|
+
await User.updateById(2, { status: 'active' });
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Supprimer
|
|
435
|
+
|
|
436
|
+
```javascript
|
|
437
|
+
// Instance
|
|
438
|
+
const user = await User.find(1);
|
|
439
|
+
await user.destroy();
|
|
440
|
+
|
|
441
|
+
// Bulk delete
|
|
442
|
+
await User
|
|
443
|
+
.where('status', 'banned')
|
|
444
|
+
.delete();
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Query Builder
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
// Where clauses
|
|
451
|
+
const users = await User
|
|
452
|
+
.where('age', '>', 18)
|
|
453
|
+
.where('status', 'active')
|
|
454
|
+
.orWhere('role', 'admin')
|
|
455
|
+
.get();
|
|
456
|
+
|
|
457
|
+
// Where In / Not In
|
|
458
|
+
const users = await User.whereIn('id', [1, 2, 3, 4, 5]).get();
|
|
459
|
+
const users = await User.whereNotIn('status', ['banned', 'deleted']).get();
|
|
460
|
+
|
|
461
|
+
// Where Null / Not Null
|
|
462
|
+
const users = await User.whereNull('deleted_at').get();
|
|
463
|
+
const verified = await User.whereNotNull('email_verified_at').get();
|
|
464
|
+
|
|
465
|
+
// Where Between / Like
|
|
466
|
+
const adults = await User.whereBetween('age', [18, 65]).get();
|
|
467
|
+
const johns = await User.whereLike('name', '%john%').get();
|
|
468
|
+
|
|
469
|
+
// Pagination
|
|
470
|
+
const result = await User.paginate(1, 15);
|
|
471
|
+
// { data: [...], total: 100, per_page: 15, current_page: 1, last_page: 7, from: 1, to: 15 }
|
|
472
|
+
|
|
473
|
+
// Count / Exists
|
|
474
|
+
const count = await User.where('status', 'active').count();
|
|
475
|
+
const hasUsers = await User.where('role', 'admin').exists();
|
|
476
|
+
|
|
477
|
+
// Joins
|
|
478
|
+
const result = await User
|
|
479
|
+
.join('profiles', 'users.id', 'profiles.user_id')
|
|
480
|
+
.leftJoin('countries', 'profiles.country_id', 'countries.id')
|
|
481
|
+
.select('users.*', 'profiles.bio', 'countries.name as country')
|
|
482
|
+
.get();
|
|
483
|
+
|
|
484
|
+
// Agrégations
|
|
485
|
+
const stats = await User
|
|
486
|
+
.distinct()
|
|
487
|
+
.groupBy('status')
|
|
488
|
+
.having('COUNT(*)', '>', 5)
|
|
489
|
+
.get();
|
|
490
|
+
|
|
491
|
+
// Incrément / Décrément atomique
|
|
492
|
+
await User.where('id', 1).increment('login_count');
|
|
493
|
+
await User.where('id', 1).decrement('credits', 10);
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Filtres relationnels
|
|
497
|
+
|
|
498
|
+
```javascript
|
|
499
|
+
// whereHas: Utilisateurs ayant au moins un post publié
|
|
500
|
+
const authors = await User
|
|
501
|
+
.whereHas('posts', (q) => {
|
|
502
|
+
q.where('status', 'published');
|
|
503
|
+
})
|
|
504
|
+
.get();
|
|
505
|
+
|
|
506
|
+
// has: Au moins N enfants
|
|
507
|
+
const prolific = await User.has('posts', '>=', 10).get();
|
|
508
|
+
|
|
509
|
+
// whereDoesntHave: Aucun enfant
|
|
510
|
+
const noPostUsers = await User.whereDoesntHave('posts').get();
|
|
511
|
+
|
|
512
|
+
// withCount: Ajouter une colonne {relation}_count
|
|
513
|
+
const withCounts = await User.withCount('posts').get();
|
|
514
|
+
// Chaque user aura: user.getAttribute('posts_count')
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## 🔗 Relations
|
|
518
|
+
|
|
519
|
+
### One to One (hasOne)
|
|
520
|
+
|
|
521
|
+
```javascript
|
|
522
|
+
const { Model } = require('outlet-orm');
|
|
523
|
+
|
|
524
|
+
class Profile extends Model { static table = 'profiles'; }
|
|
525
|
+
|
|
526
|
+
class User extends Model {
|
|
527
|
+
static table = 'users';
|
|
528
|
+
|
|
529
|
+
profile() {
|
|
530
|
+
return this.hasOne(Profile, 'user_id');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const user = await User.find(1);
|
|
535
|
+
const profile = await user.profile().get();
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### One to Many (hasMany)
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
const { Model } = require('outlet-orm');
|
|
542
|
+
|
|
543
|
+
class Post extends Model { static table = 'posts'; }
|
|
544
|
+
|
|
545
|
+
class User extends Model {
|
|
546
|
+
static table = 'users';
|
|
547
|
+
|
|
548
|
+
posts() {
|
|
549
|
+
return this.hasMany(Post, 'user_id');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const user = await User.find(1);
|
|
554
|
+
const posts = await user.posts().get();
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Belongs To (belongsTo)
|
|
558
|
+
|
|
559
|
+
```javascript
|
|
560
|
+
const { Model } = require('outlet-orm');
|
|
561
|
+
|
|
562
|
+
class User extends Model { static table = 'users'; }
|
|
563
|
+
|
|
564
|
+
class Post extends Model {
|
|
565
|
+
static table = 'posts';
|
|
566
|
+
|
|
567
|
+
author() {
|
|
568
|
+
return this.belongsTo(User, 'user_id');
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const post = await Post.find(1);
|
|
573
|
+
const author = await post.author().get();
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Many to Many (belongsToMany)
|
|
577
|
+
|
|
578
|
+
```javascript
|
|
579
|
+
const { Model } = require('outlet-orm');
|
|
580
|
+
|
|
581
|
+
class Role extends Model { static table = 'roles'; }
|
|
582
|
+
|
|
583
|
+
class User extends Model {
|
|
584
|
+
static table = 'users';
|
|
585
|
+
|
|
586
|
+
roles() {
|
|
587
|
+
return this.belongsToMany(
|
|
588
|
+
Role,
|
|
589
|
+
'user_roles', // Table pivot
|
|
590
|
+
'user_id', // FK vers User
|
|
591
|
+
'role_id' // FK vers Role
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const user = await User.find(1);
|
|
597
|
+
const roles = await user.roles().get();
|
|
598
|
+
|
|
599
|
+
// Méthodes pivot
|
|
600
|
+
await user.roles().attach([1, 2]); // Attacher des rôles
|
|
601
|
+
await user.roles().detach(2); // Détacher un rôle
|
|
602
|
+
await user.roles().sync([1, 3]); // Synchroniser (remplace tout)
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Has Many Through (hasManyThrough)
|
|
606
|
+
|
|
607
|
+
Accéder à une relation distante via un modèle intermédiaire.
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
const { Model } = require('outlet-orm');
|
|
611
|
+
|
|
612
|
+
class User extends Model {
|
|
613
|
+
// User -> Post -> Comment
|
|
614
|
+
comments() {
|
|
615
|
+
return this.hasManyThrough(Comment, Post, 'user_id', 'post_id');
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const user = await User.find(1);
|
|
620
|
+
const allComments = await user.comments().get();
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Has One Through (hasOneThrough)
|
|
624
|
+
|
|
625
|
+
```javascript
|
|
626
|
+
const { Model } = require('outlet-orm');
|
|
627
|
+
|
|
628
|
+
class User extends Model {
|
|
629
|
+
// User -> Profile -> Country
|
|
630
|
+
country() {
|
|
631
|
+
return this.hasOneThrough(Country, Profile, 'user_id', 'country_id');
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const user = await User.find(1);
|
|
636
|
+
const country = await user.country().get();
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Relations Polymorphiques
|
|
640
|
+
|
|
641
|
+
Les relations polymorphiques permettent à un modèle d'appartenir à plusieurs autres modèles.
|
|
642
|
+
|
|
643
|
+
```javascript
|
|
644
|
+
const { Model } = require('outlet-orm');
|
|
645
|
+
|
|
646
|
+
// Configuration du morph map
|
|
647
|
+
Model.setMorphMap({
|
|
648
|
+
'posts': Post,
|
|
649
|
+
'videos': Video
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// Modèles
|
|
653
|
+
class Post extends Model {
|
|
654
|
+
comments() {
|
|
655
|
+
return this.morphMany(Comment, 'commentable');
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
class Video extends Model {
|
|
660
|
+
comments() {
|
|
661
|
+
return this.morphMany(Comment, 'commentable');
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
class Comment extends Model {
|
|
666
|
+
commentable() {
|
|
667
|
+
return this.morphTo('commentable');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Usage
|
|
672
|
+
const post = await Post.find(1);
|
|
673
|
+
const comments = await post.comments().get();
|
|
674
|
+
|
|
675
|
+
const comment = await Comment.find(1);
|
|
676
|
+
const parent = await comment.commentable().get(); // Post ou Video
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Relations polymorphiques disponibles:**
|
|
680
|
+
- `morphOne(Related, 'morphName')` - One-to-One polymorphique
|
|
681
|
+
- `morphMany(Related, 'morphName')` - One-to-Many polymorphique
|
|
682
|
+
- `morphTo('morphName')` - Inverse polymorphique
|
|
683
|
+
|
|
684
|
+
### Eager Loading
|
|
685
|
+
|
|
686
|
+
```javascript
|
|
687
|
+
// Charger plusieurs relations
|
|
688
|
+
const users = await User.with('posts', 'profile', 'roles').get();
|
|
689
|
+
|
|
690
|
+
// Charger avec contraintes
|
|
691
|
+
const users = await User.with({
|
|
692
|
+
posts: (q) => q.where('status', 'published').orderBy('created_at', 'desc')
|
|
693
|
+
}).get();
|
|
694
|
+
|
|
695
|
+
// Charger des relations imbriquées (dot notation)
|
|
696
|
+
const users = await User.with('posts.comments.author').get();
|
|
697
|
+
|
|
698
|
+
// Charger sur une instance existante
|
|
699
|
+
const user = await User.find(1);
|
|
700
|
+
await user.load('posts', 'profile');
|
|
701
|
+
await user.load(['roles', 'posts.comments']);
|
|
702
|
+
|
|
703
|
+
// Accéder aux relations chargées
|
|
704
|
+
users.forEach(user => {
|
|
705
|
+
console.log(user.relations.posts);
|
|
706
|
+
console.log(user.relations.profile);
|
|
707
|
+
});
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
## 🎭 Attributs
|
|
711
|
+
|
|
712
|
+
### Casts
|
|
713
|
+
|
|
714
|
+
Les casts convertissent automatiquement les attributs:
|
|
715
|
+
|
|
716
|
+
```javascript
|
|
717
|
+
const { Model } = require('outlet-orm');
|
|
718
|
+
|
|
719
|
+
class User extends Model {
|
|
720
|
+
static casts = {
|
|
721
|
+
id: 'int', // ou 'integer'
|
|
722
|
+
age: 'integer',
|
|
723
|
+
balance: 'float', // ou 'double'
|
|
724
|
+
email_verified: 'boolean', // ou 'bool'
|
|
725
|
+
metadata: 'json', // Parse JSON
|
|
726
|
+
settings: 'array', // Parse JSON en array
|
|
727
|
+
birthday: 'date' // Convertit en Date
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Attributs cachés
|
|
733
|
+
|
|
734
|
+
```javascript
|
|
735
|
+
const { Model } = require('outlet-orm');
|
|
736
|
+
|
|
737
|
+
class User extends Model {
|
|
738
|
+
static hidden = ['password', 'secret_token'];
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const user = await User.find(1);
|
|
742
|
+
console.log(user.toJSON()); // password et secret_token exclus
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
#### Afficher les attributs cachés
|
|
746
|
+
|
|
747
|
+
```javascript
|
|
748
|
+
// Inclure les attributs cachés
|
|
749
|
+
const user = await User.withHidden().where('email', 'john@example.com').first();
|
|
750
|
+
console.log(user.toJSON()); // password inclus
|
|
751
|
+
|
|
752
|
+
// Contrôler avec un booléen
|
|
753
|
+
const user = await User.withoutHidden(true).first(); // true = afficher
|
|
754
|
+
const user = await User.withoutHidden(false).first(); // false = masquer (défaut)
|
|
755
|
+
|
|
756
|
+
// Cas d'usage: authentification
|
|
757
|
+
const user = await User.withHidden().where('email', email).first();
|
|
758
|
+
if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
|
|
759
|
+
// Authentification réussie
|
|
760
|
+
}
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Timestamps
|
|
764
|
+
|
|
765
|
+
```javascript
|
|
766
|
+
const { Model } = require('outlet-orm');
|
|
767
|
+
|
|
768
|
+
// Activés par défaut (created_at, updated_at)
|
|
769
|
+
class User extends Model {
|
|
770
|
+
static timestamps = true;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Désactiver
|
|
774
|
+
class Log extends Model {
|
|
775
|
+
static timestamps = false;
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
## 🔄 Transactions
|
|
780
|
+
|
|
781
|
+
Outlet ORM supporte les transactions pour garantir l'intégrité des données:
|
|
782
|
+
|
|
783
|
+
```javascript
|
|
784
|
+
const { DatabaseConnection, Model } = require('outlet-orm');
|
|
785
|
+
|
|
786
|
+
// Méthode 1: Callback automatique (recommandé)
|
|
787
|
+
const db = Model.connection;
|
|
788
|
+
const result = await db.transaction(async (connection) => {
|
|
789
|
+
const user = await User.create({ name: 'John', email: 'john@example.com' });
|
|
790
|
+
await Account.create({ user_id: user.getAttribute('id'), balance: 0 });
|
|
791
|
+
return user;
|
|
792
|
+
});
|
|
793
|
+
// Commit automatique, rollback si erreur
|
|
794
|
+
|
|
795
|
+
// Méthode 2: Contrôle manuel
|
|
796
|
+
await db.beginTransaction();
|
|
797
|
+
try {
|
|
798
|
+
await User.create({ name: 'Jane' });
|
|
799
|
+
await db.commit();
|
|
800
|
+
} catch (error) {
|
|
801
|
+
await db.rollback();
|
|
802
|
+
throw error;
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
## 🗑️ Soft Deletes
|
|
807
|
+
|
|
808
|
+
Suppression logique avec colonne `deleted_at`:
|
|
809
|
+
|
|
810
|
+
```javascript
|
|
811
|
+
const { Model } = require('outlet-orm');
|
|
812
|
+
|
|
813
|
+
class Post extends Model {
|
|
814
|
+
static table = 'posts';
|
|
815
|
+
static softDeletes = true;
|
|
816
|
+
// static DELETED_AT = 'deleted_at'; // Personnalisable
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Les requêtes excluent automatiquement les supprimés
|
|
820
|
+
const posts = await Post.all(); // Seulement les non-supprimés
|
|
821
|
+
|
|
822
|
+
// Inclure les supprimés
|
|
823
|
+
const allPosts = await Post.withTrashed().get();
|
|
824
|
+
|
|
825
|
+
// Seulement les supprimés
|
|
826
|
+
const trashedPosts = await Post.onlyTrashed().get();
|
|
827
|
+
|
|
828
|
+
// Supprimer (soft delete)
|
|
829
|
+
const post = await Post.find(1);
|
|
830
|
+
await post.destroy(); // Met deleted_at à la date actuelle
|
|
831
|
+
|
|
832
|
+
// Vérifier si supprimé
|
|
833
|
+
if (post.trashed()) {
|
|
834
|
+
console.log('Ce post est supprimé');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Restaurer
|
|
838
|
+
await post.restore();
|
|
839
|
+
|
|
840
|
+
// Supprimer définitivement
|
|
841
|
+
await post.forceDelete();
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
## 🔬 Scopes
|
|
845
|
+
|
|
846
|
+
### Scopes Globaux
|
|
847
|
+
|
|
848
|
+
Appliqués automatiquement à toutes les requêtes:
|
|
849
|
+
|
|
850
|
+
```javascript
|
|
851
|
+
const { Model } = require('outlet-orm');
|
|
852
|
+
|
|
853
|
+
class Post extends Model {
|
|
854
|
+
static table = 'posts';
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Ajouter un scope global
|
|
858
|
+
Post.addGlobalScope('published', (query) => {
|
|
859
|
+
query.where('status', 'published');
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// Toutes les requêtes filtrent automatiquement
|
|
863
|
+
const posts = await Post.all(); // Seulement les publiés
|
|
864
|
+
|
|
865
|
+
// Désactiver temporairement un scope
|
|
866
|
+
const allPosts = await Post.withoutGlobalScope('published').get();
|
|
867
|
+
|
|
868
|
+
// Désactiver tous les scopes
|
|
869
|
+
const rawPosts = await Post.withoutGlobalScopes().get();
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
## 📣 Events / Hooks
|
|
873
|
+
|
|
874
|
+
Interceptez les opérations sur vos modèles:
|
|
875
|
+
|
|
876
|
+
```javascript
|
|
877
|
+
const { Model } = require('outlet-orm');
|
|
878
|
+
|
|
879
|
+
class User extends Model {
|
|
880
|
+
static table = 'users';
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Avant création
|
|
884
|
+
User.creating((user) => {
|
|
885
|
+
user.setAttribute('uuid', generateUUID());
|
|
886
|
+
// Retourner false pour annuler
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
// Après création
|
|
890
|
+
User.created((user) => {
|
|
891
|
+
console.log(`Utilisateur ${user.getAttribute('id')} créé`);
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// Avant mise à jour
|
|
895
|
+
User.updating((user) => {
|
|
896
|
+
user.setAttribute('updated_at', new Date());
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// Après mise à jour
|
|
900
|
+
User.updated((user) => {
|
|
901
|
+
// Notifier les systèmes externes
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// Événements saving/saved (création ET mise à jour)
|
|
905
|
+
User.saving((user) => {
|
|
906
|
+
// Nettoyage des données
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
User.saved((user) => {
|
|
910
|
+
// Cache invalidation
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
// Avant/après suppression
|
|
914
|
+
User.deleting((user) => {
|
|
915
|
+
// Vérifications avant suppression
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
User.deleted((user) => {
|
|
919
|
+
// Nettoyage des relations
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
// Pour les soft deletes
|
|
923
|
+
User.restoring((user) => {});
|
|
924
|
+
User.restored((user) => {});
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
## ✅ Validation
|
|
928
|
+
|
|
929
|
+
Validation basique intégrée:
|
|
930
|
+
|
|
931
|
+
```javascript
|
|
932
|
+
const { Model } = require('outlet-orm');
|
|
933
|
+
|
|
934
|
+
class User extends Model {
|
|
935
|
+
static table = 'users';
|
|
936
|
+
static rules = {
|
|
937
|
+
name: 'required|string|min:2|max:100',
|
|
938
|
+
email: 'required|email',
|
|
939
|
+
age: 'numeric|min:0|max:150',
|
|
940
|
+
role: 'in:admin,user,guest',
|
|
941
|
+
password: 'required|min:8'
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const user = new User({
|
|
946
|
+
name: 'J',
|
|
947
|
+
email: 'invalid-email',
|
|
948
|
+
age: 200
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
// Valider
|
|
952
|
+
const { valid, errors } = user.validate();
|
|
953
|
+
console.log(valid); // false
|
|
954
|
+
console.log(errors);
|
|
955
|
+
// {
|
|
956
|
+
// name: ['name must be at least 2 characters'],
|
|
957
|
+
// email: ['email must be a valid email'],
|
|
958
|
+
// age: ['age must not exceed 150']
|
|
959
|
+
// }
|
|
960
|
+
|
|
961
|
+
// Valider ou lancer une erreur
|
|
962
|
+
try {
|
|
963
|
+
user.validateOrFail();
|
|
964
|
+
} catch (error) {
|
|
965
|
+
console.log(error.errors);
|
|
966
|
+
}
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
### Règles disponibles
|
|
970
|
+
|
|
971
|
+
| Règle | Description |
|
|
972
|
+
|-------|-------------|
|
|
973
|
+
| `required` | Champ obligatoire |
|
|
974
|
+
| `string` | Doit être une chaîne |
|
|
975
|
+
| `number` / `numeric` | Doit être un nombre |
|
|
976
|
+
| `email` | Format email valide |
|
|
977
|
+
| `boolean` | Doit être un booléen |
|
|
978
|
+
| `date` | Date valide |
|
|
979
|
+
| `min:N` | Minimum N (longueur ou valeur) |
|
|
980
|
+
| `max:N` | Maximum N (longueur ou valeur) |
|
|
981
|
+
| `in:a,b,c` | Valeur parmi la liste |
|
|
982
|
+
| `regex:pattern` | Match le pattern regex |
|
|
983
|
+
|
|
984
|
+
## 📊 Query Logging
|
|
985
|
+
|
|
986
|
+
Mode debug pour analyser vos requêtes:
|
|
987
|
+
|
|
988
|
+
```javascript
|
|
989
|
+
const { Model } = require('outlet-orm');
|
|
990
|
+
|
|
991
|
+
// Activer le logging
|
|
992
|
+
const db = Model.getConnection();
|
|
993
|
+
db.enableQueryLog();
|
|
994
|
+
|
|
995
|
+
// Exécuter des requêtes
|
|
996
|
+
await User.where('status', 'active').get();
|
|
997
|
+
await Post.with('author').get();
|
|
998
|
+
|
|
999
|
+
// Récupérer le log
|
|
1000
|
+
const queries = db.getQueryLog();
|
|
1001
|
+
console.log(queries);
|
|
1002
|
+
// [
|
|
1003
|
+
// { sql: 'SELECT * FROM users WHERE status = ?', params: ['active'], duration: 15, timestamp: Date },
|
|
1004
|
+
// { sql: 'SELECT * FROM posts', params: [], duration: 8, timestamp: Date }
|
|
1005
|
+
// ]
|
|
1006
|
+
|
|
1007
|
+
// Vider le log
|
|
1008
|
+
db.flushQueryLog();
|
|
1009
|
+
|
|
1010
|
+
// Désactiver le logging
|
|
1011
|
+
db.disableQueryLog();
|
|
1012
|
+
|
|
1013
|
+
// Vérifier si actif
|
|
1014
|
+
if (db.isLogging()) {
|
|
1015
|
+
console.log('Logging actif');
|
|
1016
|
+
}
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
## 📝 API Reference
|
|
1020
|
+
|
|
1021
|
+
### DatabaseConnection
|
|
1022
|
+
|
|
1023
|
+
| Méthode | Description |
|
|
1024
|
+
|---------|-------------|
|
|
1025
|
+
| `new DatabaseConnection(config?)` | Crée une connexion (lit `.env` si config omis) |
|
|
1026
|
+
| `connect()` | Établit la connexion (appelé automatiquement) |
|
|
1027
|
+
| `beginTransaction()` | Démarre une transaction |
|
|
1028
|
+
| `commit()` | Valide la transaction |
|
|
1029
|
+
| `rollback()` | Annule la transaction |
|
|
1030
|
+
| `transaction(callback)` | Exécute dans une transaction (auto commit/rollback) |
|
|
1031
|
+
| `select(table, query)` | Exécute un SELECT |
|
|
1032
|
+
| `insert(table, data)` | Insère un enregistrement |
|
|
1033
|
+
| `insertMany(table, data[])` | Insère plusieurs enregistrements |
|
|
1034
|
+
| `update(table, data, query)` | Met à jour des enregistrements |
|
|
1035
|
+
| `delete(table, query)` | Supprime des enregistrements |
|
|
1036
|
+
| `count(table, query)` | Compte les enregistrements |
|
|
1037
|
+
| `executeRawQuery(sql, params?)` | Requête brute (résultats normalisés) |
|
|
1038
|
+
| `execute(sql, params?)` | Requête brute (résultats natifs driver) |
|
|
1039
|
+
| `increment(table, column, query, amount?)` | Incrément atomique |
|
|
1040
|
+
| `decrement(table, column, query, amount?)` | Décrément atomique |
|
|
1041
|
+
| `close()` / `disconnect()` | Ferme la connexion |
|
|
1042
|
+
| **Query Logging (static)** | |
|
|
1043
|
+
| `enableQueryLog()` | Active le logging des requêtes |
|
|
1044
|
+
| `disableQueryLog()` | Désactive le logging |
|
|
1045
|
+
| `getQueryLog()` | Retourne le log des requêtes |
|
|
1046
|
+
| `flushQueryLog()` | Vide le log |
|
|
1047
|
+
| `isLogging()` | Vérifie si le logging est actif |
|
|
1048
|
+
|
|
1049
|
+
### Model (méthodes statiques)
|
|
1050
|
+
|
|
1051
|
+
| Méthode | Description |
|
|
1052
|
+
|---------|-------------|
|
|
1053
|
+
| `setConnection(db)` | Définit la connexion par défaut |
|
|
1054
|
+
| `getConnection()` | Récupère la connexion (v3.0.0+) |
|
|
1055
|
+
| `setMorphMap(map)` | Définit le mapping polymorphique |
|
|
1056
|
+
| `query()` | Retourne un QueryBuilder |
|
|
1057
|
+
| `all()` | Tous les enregistrements |
|
|
1058
|
+
| `find(id)` | Trouve par ID |
|
|
1059
|
+
| `findOrFail(id)` | Trouve ou lance une erreur |
|
|
1060
|
+
| `first()` | Premier enregistrement |
|
|
1061
|
+
| `where(col, op?, val)` | Clause WHERE |
|
|
1062
|
+
| `whereIn(col, vals)` | Clause WHERE IN |
|
|
1063
|
+
| `whereNull(col)` | Clause WHERE NULL |
|
|
1064
|
+
| `whereNotNull(col)` | Clause WHERE NOT NULL |
|
|
1065
|
+
| `create(attrs)` | Crée et sauvegarde |
|
|
1066
|
+
| `insert(data)` | Insert brut |
|
|
1067
|
+
| `update(attrs)` | Update bulk |
|
|
1068
|
+
| `updateById(id, attrs)` | Update par ID |
|
|
1069
|
+
| `updateAndFetchById(id, attrs, rels?)` | Update + fetch avec relations |
|
|
1070
|
+
| `delete()` | Delete bulk |
|
|
1071
|
+
| `with(...rels)` | Eager loading |
|
|
1072
|
+
| `withHidden()` | Inclut les attributs cachés |
|
|
1073
|
+
| `withoutHidden(show?)` | Contrôle visibilité |
|
|
1074
|
+
| `orderBy(col, dir?)` | Tri |
|
|
1075
|
+
| `limit(n)` / `offset(n)` | Limite/Offset |
|
|
1076
|
+
| `paginate(page, perPage)` | Pagination |
|
|
1077
|
+
| `count()` | Compte |
|
|
1078
|
+
| **Soft Deletes** | |
|
|
1079
|
+
| `withTrashed()` | Inclut les supprimés |
|
|
1080
|
+
| `onlyTrashed()` | Seulement les supprimés |
|
|
1081
|
+
| **Scopes** | |
|
|
1082
|
+
| `addGlobalScope(name, cb)` | Ajoute un scope global |
|
|
1083
|
+
| `removeGlobalScope(name)` | Supprime un scope |
|
|
1084
|
+
| `withoutGlobalScope(name)` | Requête sans un scope |
|
|
1085
|
+
| `withoutGlobalScopes()` | Requête sans tous les scopes |
|
|
1086
|
+
| **Events** | |
|
|
1087
|
+
| `on(event, callback)` | Enregistre un listener |
|
|
1088
|
+
| `creating(cb)` / `created(cb)` | Events création |
|
|
1089
|
+
| `updating(cb)` / `updated(cb)` | Events mise à jour |
|
|
1090
|
+
| `saving(cb)` / `saved(cb)` | Events sauvegarde |
|
|
1091
|
+
| `deleting(cb)` / `deleted(cb)` | Events suppression |
|
|
1092
|
+
| `restoring(cb)` / `restored(cb)` | Events restauration |
|
|
1093
|
+
|
|
1094
|
+
### Model (méthodes d'instance)
|
|
1095
|
+
|
|
1096
|
+
| Méthode | Description |
|
|
1097
|
+
|---------|-------------|
|
|
1098
|
+
| `fill(attrs)` | Remplit les attributs |
|
|
1099
|
+
| `setAttribute(key, val)` | Définit un attribut |
|
|
1100
|
+
| `getAttribute(key)` | Récupère un attribut |
|
|
1101
|
+
| `save()` | Sauvegarde (insert ou update) |
|
|
1102
|
+
| `destroy()` | Supprime l'instance (soft si activé) |
|
|
1103
|
+
| `load(...rels)` | Charge des relations |
|
|
1104
|
+
| `getDirty()` | Attributs modifiés |
|
|
1105
|
+
| `isDirty()` | A été modifié? |
|
|
1106
|
+
| `toJSON()` | Convertit en objet |
|
|
1107
|
+
| **Soft Deletes** | |
|
|
1108
|
+
| `trashed()` | Est supprimé? |
|
|
1109
|
+
| `restore()` | Restaure le modèle |
|
|
1110
|
+
| `forceDelete()` | Suppression définitive |
|
|
1111
|
+
| **Validation** | |
|
|
1112
|
+
| `validate()` | Valide selon les règles |
|
|
1113
|
+
| `validateOrFail()` | Valide ou lance erreur |
|
|
1114
|
+
|
|
1115
|
+
### QueryBuilder
|
|
1116
|
+
|
|
1117
|
+
| Méthode | Description |
|
|
1118
|
+
|---------|-------------|
|
|
1119
|
+
| `select(...cols)` / `columns([...])` | Sélection de colonnes |
|
|
1120
|
+
| `distinct()` | SELECT DISTINCT |
|
|
1121
|
+
| `where(col, op?, val)` | Clause WHERE |
|
|
1122
|
+
| `whereIn(col, vals)` | WHERE IN |
|
|
1123
|
+
| `whereNotIn(col, vals)` | WHERE NOT IN |
|
|
1124
|
+
| `whereNull(col)` | WHERE NULL |
|
|
1125
|
+
| `whereNotNull(col)` | WHERE NOT NULL |
|
|
1126
|
+
| `orWhere(col, op?, val)` | OR WHERE |
|
|
1127
|
+
| `whereBetween(col, [min, max])` | WHERE BETWEEN |
|
|
1128
|
+
| `whereLike(col, pattern)` | WHERE LIKE |
|
|
1129
|
+
| `whereHas(rel, cb?)` | Filtre par relation |
|
|
1130
|
+
| `has(rel, op?, count)` | Existence relationnelle |
|
|
1131
|
+
| `whereDoesntHave(rel)` | Absence de relation |
|
|
1132
|
+
| `orderBy(col, dir?)` / `ordrer(...)` | Tri |
|
|
1133
|
+
| `limit(n)` / `take(n)` | Limite |
|
|
1134
|
+
| `offset(n)` / `skip(n)` | Offset |
|
|
1135
|
+
| `groupBy(...cols)` | GROUP BY |
|
|
1136
|
+
| `having(col, op, val)` | HAVING |
|
|
1137
|
+
| `join(table, first, op?, second)` | INNER JOIN |
|
|
1138
|
+
| `leftJoin(table, first, op?, second)` | LEFT JOIN |
|
|
1139
|
+
| `with(...rels)` | Eager loading |
|
|
1140
|
+
| `withCount(rels)` | Ajoute {rel}_count |
|
|
1141
|
+
| `withTrashed()` | Inclut les supprimés |
|
|
1142
|
+
| `onlyTrashed()` | Seulement les supprimés |
|
|
1143
|
+
| `withoutGlobalScope(name)` | Sans un scope global |
|
|
1144
|
+
| `withoutGlobalScopes()` | Sans tous les scopes |
|
|
1145
|
+
| `get()` | Exécute et retourne tous |
|
|
1146
|
+
| `first()` | Premier résultat |
|
|
1147
|
+
| `firstOrFail()` | Premier ou erreur |
|
|
1148
|
+
| `paginate(page, perPage)` | Pagination |
|
|
1149
|
+
| `count()` | Compte |
|
|
1150
|
+
| `exists()` | Vérifie l'existence |
|
|
1151
|
+
| `insert(data)` | Insert |
|
|
1152
|
+
| `update(attrs)` | Update |
|
|
1153
|
+
| `updateAndFetch(attrs, rels?)` | Update + fetch |
|
|
1154
|
+
| `delete()` | Delete |
|
|
1155
|
+
| `increment(col, amount?)` | Incrément atomique |
|
|
1156
|
+
| `decrement(col, amount?)` | Décrément atomique |
|
|
1157
|
+
| `clone()` | Clone le query builder |
|
|
1158
|
+
|
|
1159
|
+
## 🛠️ Outils CLI
|
|
1160
|
+
|
|
1161
|
+
### outlet-init
|
|
1162
|
+
|
|
1163
|
+
Initialise un nouveau projet avec configuration de base de données.
|
|
1164
|
+
|
|
1165
|
+
```bash
|
|
1166
|
+
outlet-init
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
Génère:
|
|
1170
|
+
- Fichier de configuration `database/config.js`
|
|
1171
|
+
- Fichier `.env` avec les paramètres
|
|
1172
|
+
- Modèle exemple
|
|
1173
|
+
- Fichier d'utilisation
|
|
1174
|
+
|
|
1175
|
+
### outlet-migrate
|
|
1176
|
+
|
|
1177
|
+
Système complet de migrations.
|
|
1178
|
+
|
|
1179
|
+
```bash
|
|
1180
|
+
# Créer une migration
|
|
1181
|
+
outlet-migrate make create_users_table
|
|
1182
|
+
|
|
1183
|
+
# Exécuter les migrations
|
|
1184
|
+
outlet-migrate migrate
|
|
1185
|
+
|
|
1186
|
+
# Voir le statut
|
|
1187
|
+
outlet-migrate status
|
|
1188
|
+
|
|
1189
|
+
# Annuler la dernière migration
|
|
1190
|
+
outlet-migrate rollback --steps 1
|
|
1191
|
+
|
|
1192
|
+
# Reset toutes les migrations
|
|
1193
|
+
outlet-migrate reset --yes
|
|
1194
|
+
|
|
1195
|
+
# Refresh (reset + migrate)
|
|
1196
|
+
outlet-migrate refresh --yes
|
|
1197
|
+
|
|
1198
|
+
# Fresh (drop all + migrate)
|
|
1199
|
+
outlet-migrate fresh --yes
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
**Fonctionnalités des Migrations:**
|
|
1203
|
+
|
|
1204
|
+
- ✅ Création et gestion des migrations (create, alter, drop tables)
|
|
1205
|
+
- ✅ Types de colonnes: id, string, text, integer, boolean, date, datetime, timestamp, decimal, float, json, enum, uuid, foreignId
|
|
1206
|
+
- ✅ Modificateurs: nullable, default, unique, index, unsigned, autoIncrement, comment, after, first
|
|
1207
|
+
- ✅ Clés étrangères: foreign(), constrained(), onDelete(), onUpdate(), CASCADE
|
|
1208
|
+
- ✅ Index: index(), unique(), fullText()
|
|
1209
|
+
- ✅ Manipulation: renameColumn(), dropColumn(), dropTimestamps()
|
|
1210
|
+
- ✅ Migrations réversibles: Méthodes up() et down()
|
|
1211
|
+
- ✅ Batch tracking: Rollback précis par batch
|
|
1212
|
+
- ✅ SQL personnalisé: execute() pour commandes avancées
|
|
1213
|
+
|
|
1214
|
+
### outlet-convert
|
|
1215
|
+
|
|
1216
|
+
Convertit des schémas SQL en modèles ORM.
|
|
1217
|
+
|
|
1218
|
+
```bash
|
|
1219
|
+
outlet-convert
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
**Options:**
|
|
1223
|
+
1. Depuis un fichier SQL local
|
|
1224
|
+
2. Depuis une base de données connectée
|
|
1225
|
+
|
|
1226
|
+
**Fonctionnalités:**
|
|
1227
|
+
- ✅ Détection automatique des types et casts
|
|
1228
|
+
- ✅ Génération automatique de TOUTES les relations (belongsTo, hasMany, hasOne, belongsToMany)
|
|
1229
|
+
- ✅ Relations récursives (auto-relations)
|
|
1230
|
+
- ✅ Détection des champs sensibles (password, token, etc.)
|
|
1231
|
+
- ✅ Support des timestamps automatiques
|
|
1232
|
+
- ✅ Conversion des noms en PascalCase
|
|
1233
|
+
|
|
1234
|
+
## 📚 Documentation
|
|
1235
|
+
|
|
1236
|
+
- [Guide des Migrations](docs/MIGRATIONS.md)
|
|
1237
|
+
- [Conversion SQL](docs/SQL_CONVERSION.md)
|
|
1238
|
+
- [Détection des Relations](docs/RELATIONS_DETECTION.md)
|
|
1239
|
+
- [Guide de démarrage rapide](docs/QUICKSTART.md)
|
|
1240
|
+
- [Architecture](docs/ARCHITECTURE.md)
|
|
1241
|
+
- [**TypeScript (complet)**](docs/TYPESCRIPT.md)
|
|
1242
|
+
|
|
1243
|
+
## 📘 TypeScript Support
|
|
1244
|
+
|
|
1245
|
+
Outlet ORM v4.0.0 inclut des définitions TypeScript complètes avec support des **generics pour les attributs typés**.
|
|
1246
|
+
|
|
1247
|
+
### Modèles typés
|
|
1248
|
+
|
|
1249
|
+
```typescript
|
|
1250
|
+
import { Model, HasManyRelation } from 'outlet-orm';
|
|
1251
|
+
|
|
1252
|
+
interface UserAttributes {
|
|
1253
|
+
id: number;
|
|
1254
|
+
name: string;
|
|
1255
|
+
email: string;
|
|
1256
|
+
role: 'admin' | 'user';
|
|
1257
|
+
created_at: Date;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
class User extends Model<UserAttributes> {
|
|
1261
|
+
static table = 'users';
|
|
1262
|
+
static fillable = ['name', 'email', 'role'];
|
|
1263
|
+
|
|
1264
|
+
posts(): HasManyRelation<Post> {
|
|
1265
|
+
return this.hasMany(Post, 'user_id');
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Type-safe getAttribute/setAttribute
|
|
1270
|
+
const user = await User.find(1);
|
|
1271
|
+
const name: string = user.getAttribute('name'); // ✅ Type inféré
|
|
1272
|
+
const role: 'admin' | 'user' = user.getAttribute('role');
|
|
1273
|
+
```
|
|
1274
|
+
|
|
1275
|
+
### Migrations typées
|
|
1276
|
+
|
|
1277
|
+
```typescript
|
|
1278
|
+
import { MigrationInterface, Schema, TableBuilder } from 'outlet-orm';
|
|
1279
|
+
|
|
1280
|
+
export const migration: MigrationInterface = {
|
|
1281
|
+
name: 'create_users_table',
|
|
1282
|
+
|
|
1283
|
+
async up(): Promise<void> {
|
|
1284
|
+
await Schema.create('users', (table: TableBuilder) => {
|
|
1285
|
+
table.id();
|
|
1286
|
+
table.string('name');
|
|
1287
|
+
table.string('email').unique();
|
|
1288
|
+
table.timestamps();
|
|
1289
|
+
});
|
|
1290
|
+
},
|
|
1291
|
+
|
|
1292
|
+
async down(): Promise<void> {
|
|
1293
|
+
await Schema.dropIfExists('users');
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
📖 [Guide TypeScript complet](docs/TYPESCRIPT.md)
|
|
1299
|
+
|
|
1300
|
+
## 🤝 Contribution
|
|
1301
|
+
|
|
1302
|
+
Les contributions sont les bienvenues! N'hésitez pas à ouvrir une issue ou un pull request.
|
|
1303
|
+
|
|
1304
|
+
Voir [CONTRIBUTING.md](CONTRIBUTING.md) pour les guidelines.
|
|
1305
|
+
|
|
1306
|
+
## 📄 Licence
|
|
1307
|
+
|
|
1308
|
+
MIT - Voir [LICENSE](LICENSE) pour plus de détails.
|
|
1309
|
+
|
|
1310
|
+
---
|
|
1311
|
+
|
|
1312
|
+
Créé par [omgbwa-yasse](https://github.com/omgbwa-yasse)
|