outlet-orm 2.5.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 ADDED
@@ -0,0 +1,705 @@
1
+ # Outlet ORM
2
+
3
+ Un ORM JavaScript inspiré de Laravel Eloquent pour Node.js avec support pour MySQL, PostgreSQL et SQLite.
4
+
5
+ ## ✅ Prérequis et compatibilité
6
+
7
+ - Node.js >= 18 (recommandé/exigé)
8
+ - Installez le driver de base de données correspondant à votre SGBD (voir ci-dessous)
9
+
10
+ ## 🚀 Installation
11
+
12
+ ```bash
13
+ npm install outlet-orm
14
+ ```
15
+
16
+ ### Installer le driver de base de données
17
+
18
+ Outlet ORM utilise des peerDependencies optionnelles pour les drivers de base de données. Installez uniquement le driver dont vous avez besoin:
19
+
20
+ - MySQL/MariaDB: `npm install mysql2`
21
+ - PostgreSQL: `npm install pg`
22
+ - SQLite: `npm install sqlite3`
23
+
24
+ Si aucun driver n'est installé, un message d'erreur explicite vous indiquera lequel installer lors de la connexion.
25
+
26
+ ## ✨ Fonctionnalités clés
27
+
28
+ - API inspirée d'Eloquent (Active Record) pour un usage fluide
29
+ - Query Builder expressif: where/joins/order/limit/offset/paginate
30
+ - Filtres relationnels façon Laravel: `whereHas()`
31
+ - Existence/absence et agrégations: `has()`, `whereDoesntHave()`, `withCount()`
32
+ - Eager Loading des relations via `.with(...)`
33
+ - Relations: hasOne, hasMany, belongsTo, belongsToMany (avec attach/detach/sync)
34
+ - Casts automatiques (int, float, boolean, json, date...)
35
+ - Attributs masqués (`hidden`) et timestamps automatiques
36
+ - Incrément/Décrément atomiques: `increment()` et `decrement()`
37
+ - Aliases ergonomiques: `columns([...])`, `ordrer()` (alias typo de `orderBy`)
38
+ - Requêtes brutes: `executeRawQuery()` et `execute()` (résultats natifs du driver)
39
+ - Migrations complètes (create/alter/drop, index, foreign keys, batch tracking)
40
+ - CLI pratiques: `outlet-init`, `outlet-migrate`, `outlet-convert`
41
+ - Configuration via `.env` (chargée automatiquement)
42
+ - Multi-base de données: MySQL, PostgreSQL et SQLite
43
+ - Types TypeScript fournis
44
+
45
+ ## ⚡ Démarrage Rapide
46
+
47
+ ### Initialisation du projet
48
+
49
+ ```bash
50
+ # Créer la configuration initiale
51
+ outlet-init
52
+
53
+ # Créer une migration
54
+ outlet-migrate make create_users_table
55
+
56
+ # Exécuter les migrations
57
+ outlet-migrate
58
+ ```
59
+
60
+ ## 📖 Utilisation Rapide
61
+
62
+ ### Configuration de la connexion
63
+
64
+ Outlet ORM peut charger automatiquement le provider (driver) et les paramètres d’accès à la base de données depuis un fichier `.env` dans votre application. Les variables supportées incluent :
65
+
66
+ - DB_DRIVER (mysql, postgres, sqlite)
67
+ - DB_HOST, DB_PORT
68
+ - DB_USER / DB_USERNAME, DB_PASSWORD
69
+ - DB_DATABASE / DB_NAME
70
+ - Pour SQLite: DB_FILE ou SQLITE_DB ou SQLITE_FILENAME
71
+
72
+ Un exemple est fourni dans `.env.example`.
73
+
74
+ ```javascript
75
+ const { DatabaseConnection, Model } = require('outlet-orm');
76
+
77
+ // Configuration MySQL
78
+ // Option 1 – via .env (aucun paramètre nécessaire)
79
+ // DB_DRIVER=mysql, DB_HOST=localhost, DB_DATABASE=myapp, DB_USER=root, DB_PASSWORD=secret, DB_PORT=3306
80
+ const db = new DatabaseConnection();
81
+
82
+ // Option 2 – via objet de configuration (prend le dessus sur .env)
83
+ // const db = new DatabaseConnection({
84
+ // driver: 'mysql',
85
+ // host: 'localhost',
86
+ // database: 'myapp',
87
+ // user: 'root',
88
+ // password: 'secret',
89
+ // port: 3306
90
+ // });
91
+
92
+ // Définir la connexion par défaut
93
+ Model.setConnection(db);
94
+ ```
95
+
96
+ #### Variables d'environnement (.env) — Détails
97
+
98
+ - DB_DRIVER: `mysql` | `postgres` | `sqlite` (alias acceptés: `postgresql`, `sqlite3`)
99
+ - DB_HOST, DB_PORT: hôte/port (par défaut: `localhost`, ports par défaut selon driver)
100
+ - DB_USER | DB_USERNAME, DB_PASSWORD: identifiants
101
+ - DB_DATABASE | DB_NAME: nom de la base (MySQL/Postgres)
102
+ - SQLite spécifiquement: `DB_FILE` ou `SQLITE_DB` ou `SQLITE_FILENAME` (par défaut `:memory:`)
103
+
104
+ Les paramètres passés au constructeur de `DatabaseConnection` ont priorité sur `.env`.
105
+
106
+ ### Définir un modèle
107
+
108
+ ```javascript
109
+ class User extends Model {
110
+ static table = 'users';
111
+ static fillable = ['name', 'email', 'password'];
112
+ static hidden = ['password'];
113
+ static casts = {
114
+ id: 'int',
115
+ email_verified: 'boolean',
116
+ metadata: 'json'
117
+ };
118
+
119
+ // Relations
120
+ posts() {
121
+ return this.hasMany(Post, 'user_id');
122
+ }
123
+
124
+ profile() {
125
+ return this.hasOne(Profile, 'user_id');
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Opérations CRUD
131
+
132
+ #### Créer
133
+
134
+ ```javascript
135
+ // Méthode 1: create()
136
+ const user = await User.create({
137
+ name: 'John Doe',
138
+ email: 'john@example.com',
139
+ password: 'secret123'
140
+ });
141
+
142
+ // Méthode 2: new + save()
143
+ const user = new User({
144
+ name: 'Jane Doe',
145
+ email: 'jane@example.com'
146
+ });
147
+ user.setAttribute('password', 'secret456');
148
+ await user.save();
149
+ ```
150
+
151
+ #### Lire
152
+
153
+ ```javascript
154
+ // Tous les enregistrements
155
+ const users = await User.all();
156
+
157
+ // Par ID
158
+ const user = await User.find(1);
159
+
160
+ // Premier résultat
161
+ const firstUser = await User.first();
162
+
163
+ // Avec conditions
164
+ const activeUsers = await User
165
+ .where('status', 'active')
166
+ .where('age', '>', 18)
167
+ .get();
168
+
169
+ // Avec relations
170
+ const usersWithPosts = await User
171
+ .with('posts', 'profile')
172
+ .get();
173
+
174
+ // Ordonner et limiter
175
+ const recentUsers = await User
176
+ .orderBy('created_at', 'desc')
177
+ .limit(10)
178
+ .get();
179
+ ```
180
+
181
+ #### Mettre à jour
182
+
183
+ ```javascript
184
+ // Instance
185
+ const user = await User.find(1);
186
+ user.setAttribute('name', 'Updated Name');
187
+ await user.save();
188
+
189
+ // Bulk update
190
+ await User
191
+ .where('status', 'pending')
192
+ .update({ status: 'active' });
193
+
194
+ // One-liner façon Prisma (update + include)
195
+ const updated = await User
196
+ .where('id', 1)
197
+ .updateAndFetch({ name: 'Neo' }, ['profile', 'posts.comments']);
198
+
199
+ // Helpers par ID
200
+ const user1 = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
201
+ await User.updateById(2, { status: 'active' });
202
+ ```
203
+
204
+ #### Supprimer
205
+
206
+ ```javascript
207
+ // Instance
208
+ const user = await User.find(1);
209
+ await user.destroy();
210
+
211
+ // Bulk delete
212
+ await User
213
+ .where('status', 'banned')
214
+ .delete();
215
+ ```
216
+
217
+ ### Query Builder
218
+
219
+ ```javascript
220
+ // Where clauses
221
+ const users = await User
222
+ .where('age', '>', 18)
223
+ .where('status', 'active')
224
+ .orWhere('role', 'admin')
225
+ .get();
226
+
227
+ // Where In
228
+ const users = await User
229
+ .whereIn('id', [1, 2, 3, 4, 5])
230
+ .get();
231
+
232
+ // Where Null
233
+ const users = await User
234
+ .whereNull('deleted_at')
235
+ .get();
236
+
237
+ // Where Not Null
238
+ const users = await User
239
+ .whereNotNull('email_verified_at')
240
+ .get();
241
+
242
+ // Pagination
243
+ const result = await User.paginate(1, 15);
244
+ // {
245
+ // data: [...],
246
+ // total: 100,
247
+ // per_page: 15,
248
+ // current_page: 1,
249
+ // last_page: 7,
250
+ // from: 1,
251
+ // to: 15
252
+ // }
253
+
254
+ // Count
255
+ const count = await User.where('status', 'active').count();
256
+
257
+ // Joins
258
+ const result = await User
259
+ .join('profiles', 'users.id', 'profiles.user_id')
260
+ .leftJoin('countries', 'profiles.country_id', 'countries.id')
261
+ .whereLike('users.name', '%john%')
262
+ .whereBetween('users.age', [18, 65])
263
+ .select('users.*', 'profiles.bio', 'countries.name as country')
264
+ .orderBy('users.created_at', 'desc')
265
+ .get();
266
+
267
+ // Alias ergonomiques
268
+ const slim = await User
269
+ .columns(['id', 'name']) // alias de select(...)
270
+ .ordrer('created_at', 'desc') // alias typo de orderBy
271
+ .get();
272
+
273
+ // whereHas: filtrer les parents qui ont des enfants correspondants
274
+ // Exemple: Utilisateurs ayant au moins un post publié récemment
275
+ const authors = await User
276
+ .whereHas('posts', (q) => {
277
+ q.where('status', 'published').where('created_at', '>', new Date(Date.now() - 7*24*3600*1000));
278
+ })
279
+ .get();
280
+
281
+ // has: au moins N enfants
282
+ const prolific = await User.has('posts', '>=', 10).get();
283
+
284
+ // whereDoesntHave: aucun enfant
285
+ const orphans = await User.whereDoesntHave('posts').get();
286
+
287
+ // withCount: ajouter une colonne posts_count
288
+ const withCounts = await User.withCount('posts').get();
289
+
290
+ // Agrégations: distinct, groupBy, having
291
+ const stats = await User
292
+ .distinct()
293
+ .groupBy('status')
294
+ .having('COUNT(*)', '>', 5)
295
+ .get();
296
+ ```
297
+
298
+ ### Relations
299
+
300
+ #### One to One (hasOne)
301
+
302
+ ```javascript
303
+ class User extends Model {
304
+ profile() {
305
+ return this.hasOne(Profile, 'user_id');
306
+ }
307
+ }
308
+
309
+ const user = await User.find(1);
310
+ const profile = await user.profile().get();
311
+ ```
312
+
313
+ #### One to Many (hasMany)
314
+
315
+ ```javascript
316
+ class User extends Model {
317
+ posts() {
318
+ return this.hasMany(Post, 'user_id');
319
+ }
320
+ }
321
+
322
+ const user = await User.find(1);
323
+ const posts = await user.posts().get();
324
+ ```
325
+
326
+ #### Belongs To (belongsTo)
327
+
328
+ ```javascript
329
+ class Post extends Model {
330
+ author() {
331
+ return this.belongsTo(User, 'user_id');
332
+ }
333
+ }
334
+
335
+ const post = await Post.find(1);
336
+ const author = await post.author().get();
337
+ ```
338
+
339
+ #### Many to Many (belongsToMany)
340
+
341
+ ```javascript
342
+ class User extends Model {
343
+ roles() {
344
+ return this.belongsToMany(
345
+ Role,
346
+ 'user_roles', // Pivot table
347
+ 'user_id', // Foreign key
348
+ 'role_id' // Related key
349
+ );
350
+ }
351
+ }
352
+
353
+ const user = await User.find(1);
354
+ const roles = await user.roles().get();
355
+
356
+ // belongsToMany helpers
357
+ await user.roles().attach([1, 2]);
358
+ await user.roles().detach(2);
359
+ await user.roles().sync([1, 3]);
360
+ ```
361
+
362
+ #### Has Many Through (hasManyThrough)
363
+
364
+ Permet d'accéder à une relation distante via un modèle intermédiaire (ex: User -> Post -> Comment pour récupérer les comments d'un user sans passer par les posts).
365
+
366
+ ```javascript
367
+ class User extends Model {
368
+ posts() {
369
+ return this.hasMany(Post, 'user_id');
370
+ }
371
+
372
+ comments() {
373
+ // hasManyThrough(final, through, fkOnThrough?, throughKeyOnFinal?, localKey?, throughLocalKey?)
374
+ return this.hasManyThrough(Comment, Post, 'user_id', 'post_id');
375
+ }
376
+ }
377
+
378
+ const user = await User.find(1);
379
+ const comments = await user.comments().get();
380
+
381
+ // Eager load (avec contrainte):
382
+ const users = await User.with({ comments: q => q.where('created_at', '>', new Date(Date.now() - 7*24*3600*1000)) }).get();
383
+ ```
384
+
385
+ Par défaut, les clés sont inférées selon les conventions:
386
+
387
+ - foreignKeyOnThrough: `${parentTableSingular}_id`
388
+ - throughKeyOnFinal: `${throughTableSingular}_id`
389
+ - localKey: clé primaire du parent (par défaut `id`)
390
+ - throughLocalKey: clé primaire du modèle intermédiaire (par défaut `id`)
391
+
392
+ ### Eager Loading
393
+
394
+ ```javascript
395
+ // Charger les relations avec les résultats
396
+ const users = await User.with('posts', 'profile').get();
397
+
398
+ // Accéder aux relations chargées
399
+ users.forEach(user => {
400
+ console.log(user.getAttribute('name'));
401
+ console.log(user.relations.posts);
402
+ console.log(user.relations.profile);
403
+ });
404
+
405
+ // Chargement à la demande sur une instance existante
406
+ const user = await User.find(1);
407
+ await user.load('posts.comments', 'profile');
408
+ // Ou tableau
409
+ await user.load(['roles', 'permissions']);
410
+ ```
411
+
412
+ ### Casts
413
+
414
+ Les casts permettent de convertir automatiquement les attributs:
415
+
416
+ ```javascript
417
+ class User extends Model {
418
+ static casts = {
419
+ id: 'int',
420
+ age: 'integer',
421
+ balance: 'float',
422
+ email_verified: 'boolean',
423
+ metadata: 'json',
424
+ settings: 'array',
425
+ birthday: 'date'
426
+ };
427
+ }
428
+ ```
429
+
430
+ ### Attributs cachés
431
+
432
+ ```javascript
433
+ class User extends Model {
434
+ static hidden = ['password', 'secret_token'];
435
+ }
436
+
437
+ const user = await User.find(1);
438
+ console.log(user.toJSON()); // password et secret_token ne sont pas inclus
439
+ ```
440
+
441
+ ### Timestamps
442
+
443
+ ```javascript
444
+ // Activer les timestamps automatiques (activé par défaut)
445
+ class User extends Model {
446
+ static timestamps = true; // created_at et updated_at
447
+ }
448
+
449
+ // Désactiver les timestamps
450
+ class Log extends Model {
451
+ static timestamps = false;
452
+ }
453
+ ```
454
+
455
+ ## 🔧 Configuration avancée
456
+
457
+ ### Connexions multiples
458
+
459
+ ```javascript
460
+ const mysqlDb = new DatabaseConnection({
461
+ driver: 'mysql',
462
+ host: 'localhost',
463
+ database: 'app_db',
464
+ user: 'root',
465
+ password: 'secret'
466
+ });
467
+
468
+ const postgresDb = new DatabaseConnection({
469
+ driver: 'postgres',
470
+ host: 'localhost',
471
+ database: 'analytics_db',
472
+ user: 'postgres',
473
+ password: 'secret'
474
+ });
475
+
476
+ // Par modèle
477
+ class User extends Model {
478
+ static connection = mysqlDb;
479
+ }
480
+
481
+ class Analytics extends Model {
482
+ static connection = postgresDb;
483
+ }
484
+ ```
485
+
486
+ ### Clé primaire personnalisée
487
+
488
+ ```javascript
489
+ class User extends Model {
490
+ static primaryKey = 'user_id';
491
+ }
492
+ ```
493
+
494
+ ### Nom de table personnalisé
495
+
496
+ ```javascript
497
+ class User extends Model {
498
+ static table = 'app_users';
499
+ }
500
+ ```
501
+
502
+ ## 📝 API Reference
503
+
504
+ ### DatabaseConnection
505
+
506
+ - `new DatabaseConnection(config?)` — lit automatiquement `.env` si `config` est omis
507
+ - `connect()` — établit la connexion (appelé automatiquement au besoin)
508
+ - `select(table, query)` — exécute un SELECT (utilisé par le Query Builder)
509
+ - `insert(table, data)` / `insertMany(table, data[])`
510
+ - `update(table, data, query)` / `delete(table, query)`
511
+ - `count(table, query)` — retourne le total
512
+ - `executeRawQuery(sql, params?)` — résultats normalisés (tableau d’objets)
513
+ - `execute(sql, params?)` — résultats natifs du driver (utile pour migrations)
514
+ - `increment(table, column, query, amount?)` — mise à jour atomique
515
+ - `decrement(table, column, query, amount?)`
516
+ - `close()` / `disconnect()` — fermer la connexion
517
+
518
+ ### Model
519
+
520
+ - `static all()` - Récupérer tous les enregistrements
521
+ - `static find(id)` - Trouver par ID
522
+ - `static findOrFail(id)` - Trouver ou lancer une erreur
523
+ - `static where(column, operator, value)` - Ajouter une clause where
524
+ - `static create(attributes)` - Créer et sauvegarder
525
+ - `static insert(data)` - Insérer des données brutes
526
+ - `static update(attributes)` - Mise à jour bulk
527
+ - `static updateAndFetchById(id, attributes, relations?)` - Mise à jour par ID et retour du modèle (avec include)
528
+ - `static updateById(id, attributes)` - Mise à jour par ID
529
+ - `static delete()` - Suppression bulk
530
+ - `save()` - Sauvegarder l'instance
531
+ - `destroy()` - Supprimer l'instance
532
+ - `toJSON()` - Convertir en JSON
533
+ - `load(...relations)` - Charger des relations sur une instance, supporte la dot-notation
534
+
535
+ ### QueryBuilder
536
+
537
+ - `select(...columns)` - Sélectionner des colonnes
538
+ - `where(column, operator, value)` - Clause WHERE
539
+ - `whereIn(column, values)` - Clause WHERE IN
540
+ - `whereNull(column)` - Clause WHERE NULL
541
+ - `whereNotNull(column)` - Clause WHERE NOT NULL
542
+ - `orWhere(column, operator, value)` - Clause OR WHERE
543
+ - `orderBy(column, direction)` - Ordonner les résultats
544
+ - `limit(value)` - Limiter les résultats
545
+ - `offset(value)` - Décaler les résultats
546
+ - `with(...relations)` - Eager loading
547
+ - `get()` - Exécuter et récupérer
548
+ - `first()` - Premier résultat
549
+ - `paginate(page, perPage)` - Paginer les résultats
550
+ - `count()` - Compter les résultats
551
+ - `exists()` - Vérifier l’existence
552
+ - `whereBetween(column, [min, max])` - Intervalle
553
+ - `whereLike(column, pattern)` - LIKE
554
+ - `whereHas(relation, cb?)` - Filtrer par relation (INNER JOIN)
555
+ - `has(relation, opOrCount, [count])` - Existence relationnelle (GROUP BY/HAVING)
556
+ - `whereDoesntHave(relation)` - Absence de relation (LEFT JOIN IS NULL)
557
+ - `join(table, first, [operator], second)` - INNER JOIN
558
+ - `leftJoin(table, first, [operator], second)` - LEFT JOIN
559
+ - `withCount(relations)` - Ajoute {relation}_count via sous-requête
560
+ - `distinct()` - SELECT DISTINCT
561
+ - `groupBy(...cols)` - GROUP BY
562
+ - `having(column, operator, value)` - HAVING
563
+ - `insert(data)` - Insérer des données (array => insertMany)
564
+ - `update(attributes)` - Mise à jour bulk
565
+ - `updateAndFetch(attributes, relations?)` - Mise à jour + premier enregistrement (avec include)
566
+ - `delete()` - Suppression bulk
567
+ - `increment(column, amount?)` - Incrément atomique
568
+ - `decrement(column, amount?)` - Décrément atomique
569
+ - `columns([...])` - Alias de `select(...cols)`
570
+ - `ordrer(column, direction?)` - Alias typo de `orderBy`
571
+
572
+ ## 🛠️ Outils CLI
573
+
574
+ ### 1. Initialisation d'un projet
575
+
576
+ ```bash
577
+ outlet-init
578
+ ```
579
+
580
+ Crée un nouveau projet avec configuration de base de données, modèle exemple et fichier d'utilisation.
581
+
582
+ Depuis la version actuelle, outlet-init peut aussi générer un fichier `.env` avec les paramètres saisis (driver, hôte, port, utilisateur, mot de passe, base de données ou fichier SQLite). Si `.env` existe déjà, il n'est pas modifié.
583
+
584
+ Astuce: dans les environnements CI/tests, vous pouvez désactiver l'installation automatique du driver en définissant `OUTLET_INIT_NO_INSTALL=1`.
585
+
586
+ ### 2. Système de Migrations
587
+
588
+ ```bash
589
+ # Créer une migration
590
+ outlet-migrate make create_users_table
591
+
592
+ # Exécuter les migrations
593
+ outlet-migrate
594
+ # Option 1: migrate
595
+
596
+ # Rollback dernière migration
597
+ outlet-migrate
598
+ # Option 2: rollback
599
+
600
+ # Voir le statut
601
+ outlet-migrate
602
+ # Option 6: status
603
+
604
+ # Reset toutes les migrations
605
+ outlet-migrate
606
+ # Option 3: reset
607
+
608
+ # Refresh (reset + migrate)
609
+ outlet-migrate
610
+ # Option 4: refresh
611
+
612
+ # Fresh (drop all + migrate)
613
+ outlet-migrate
614
+ # Option 5: fresh
615
+ # Exécuter les migrations en se basant sur .env si database/config.js est absent
616
+ # (DB_DRIVER, DB_HOST, DB_DATABASE, etc.)
617
+ outlet-migrate migrate
618
+
619
+ # Voir le statut
620
+ outlet-migrate status
621
+
622
+ # Annuler la dernière migration
623
+ outlet-migrate rollback --steps 1
624
+
625
+ # Astuce: si le fichier database/config.js existe, il est prioritaire sur .env
626
+ ```
627
+
628
+ **Fonctionnalités des Migrations :**
629
+
630
+ - ✅ **Création et gestion des migrations** (create, alter, drop tables)
631
+ - ✅ **Types de colonnes** : id, string, text, integer, boolean, date, datetime, timestamp, decimal, float, json, enum, uuid, foreignId
632
+ - ✅ **Modificateurs** : nullable, default, unique, index, unsigned, autoIncrement, comment, after, first
633
+ - ✅ **Clés étrangères** : foreign(), constrained(), onDelete(), onUpdate(), CASCADE
634
+ - ✅ **Index** : index(), unique(), fullText()
635
+ - ✅ **Manipulation de colonnes** : renameColumn(), dropColumn(), dropTimestamps()
636
+ - ✅ **Migrations réversibles** : Méthodes up() et down()
637
+ - ✅ **Batch tracking** : Rollback précis par batch
638
+ - ✅ **SQL personnalisé** : execute() pour commandes avancées
639
+ - ✅ **Multi-DB** : Support MySQL, PostgreSQL, SQLite
640
+
641
+ **Documentation complète :**
642
+
643
+ - [MIGRATIONS.md](docs/MIGRATIONS.md) - Guide complet des migrations
644
+
645
+ ### 3. Conversion SQL vers ORM
646
+
647
+ ```bash
648
+ outlet-convert
649
+ ```
650
+
651
+ Convertit automatiquement des schémas SQL en modèles ORM :
652
+
653
+ #### Option 1 : Depuis un fichier SQL local
654
+
655
+ - Parsez des fichiers `.sql` contenant des instructions `CREATE TABLE`
656
+ - Génère automatiquement les modèles avec relations, casts, fillable, hidden
657
+
658
+ #### Option 2 : Depuis une base de données connectée
659
+
660
+ - Connectez-vous à MySQL, PostgreSQL ou SQLite
661
+ - Liste toutes les tables et génère les modèles correspondants
662
+ - Détecte automatiquement les relations et types de données
663
+
664
+ **Fonctionnalités de conversion :**
665
+
666
+ - ✅ Détection automatique des types et casts
667
+ - ✅ **Génération automatique de TOUTES les relations** :
668
+ - `belongsTo` : Détecté via clés étrangères
669
+ - `hasMany` : Généré automatiquement comme inverse de `belongsTo`
670
+ - `hasOne` : Détecté via clés étrangères UNIQUE
671
+ - `belongsToMany` : Détecté via tables pivot
672
+ - ✅ Relations récursives (auto-relations)
673
+ - ✅ Détection des champs sensibles (password, token, etc.)
674
+ - ✅ Support des timestamps automatiques
675
+ - ✅ Conversion des noms de tables en classes PascalCase
676
+
677
+ ### 4. Utilisation non-interactive (CI/CD)
678
+
679
+ Les commandes de migration supportent un mode non-interactif pratique pour l’automatisation:
680
+
681
+ ```bash
682
+ # Exécuter les migrations en lisant la config depuis .env
683
+ outlet-migrate migrate
684
+
685
+ # Voir le statut
686
+ outlet-migrate status
687
+
688
+ # Annuler N étapes
689
+ outlet-migrate rollback --steps 1
690
+ ```
691
+
692
+ Astuce: si `database/config.js` est présent, il a priorité sur `.env`.
693
+
694
+ **Documentation complète :**
695
+
696
+ - [SQL_CONVERSION.md](docs/SQL_CONVERSION.md) - Guide de conversion
697
+ - [RELATIONS_DETECTION.md](docs/RELATIONS_DETECTION.md) - Détection des relations
698
+
699
+ ## 🤝 Contribution
700
+
701
+ Les contributions sont les bienvenues! N'hésitez pas à ouvrir une issue ou un pull request.
702
+
703
+ ## 📄 Licence
704
+
705
+ MIT