outlet-orm 2.5.0 → 3.1.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 CHANGED
@@ -1,7 +1,12 @@
1
1
  # Outlet ORM
2
2
 
3
+ [![npm version](https://badge.fury.io/js/outlet-orm.svg)](https://www.npmjs.com/package/outlet-orm)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
3
6
  Un ORM JavaScript inspiré de Laravel Eloquent pour Node.js avec support pour MySQL, PostgreSQL et SQLite.
4
7
 
8
+ 📚 **[Documentation complète disponible dans `/docs`](./docs/INDEX.md)**
9
+
5
10
  ## ✅ Prérequis et compatibilité
6
11
 
7
12
  - Node.js >= 18 (recommandé/exigé)
@@ -25,22 +30,33 @@ Si aucun driver n'est installé, un message d'erreur explicite vous indiquera le
25
30
 
26
31
  ## ✨ Fonctionnalités clés
27
32
 
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
33
+ - **API inspirée d'Eloquent** (Active Record) pour un usage fluide
34
+ - **Query Builder expressif**: where/joins/order/limit/offset/paginate
35
+ - **Filtres relationnels façon Laravel**: `whereHas()`, `has()`, `whereDoesntHave()`, `withCount()`
36
+ - **Eager Loading** des relations via `.with(...)` avec contraintes et dot-notation
37
+ - **Relations complètes**:
38
+ - `hasOne`, `hasMany`, `belongsTo`, `belongsToMany` (avec attach/detach/sync)
39
+ - `hasManyThrough`, `hasOneThrough` (relations transitives)
40
+ - `morphOne`, `morphMany`, `morphTo` (relations polymorphiques)
41
+ - **Transactions** complètes: `beginTransaction()`, `commit()`, `rollback()`, `transaction()`
42
+ - **Soft Deletes**: suppression logique avec `deleted_at`, `withTrashed()`, `onlyTrashed()`, `restore()`
43
+ - **Scopes**: globaux et locaux pour réutiliser vos filtres
44
+ - **Events/Hooks**: `creating`, `created`, `updating`, `updated`, `deleting`, `deleted`, etc.
45
+ - **Validation**: règles basiques intégrées (`required`, `email`, `min`, `max`, etc.)
46
+ - **Query Logging**: mode debug avec `enableQueryLog()` et `getQueryLog()`
47
+ - **Pool PostgreSQL**: connexions poolées pour de meilleures performances
48
+ - **Protection SQL**: sanitization automatique des identifiants
49
+ - **Casts automatiques** (int, float, boolean, json, date...)
50
+ - **Attributs masqués** (`hidden`) et timestamps automatiques
51
+ - **Contrôle de visibilité** des attributs cachés: `withHidden()` et `withoutHidden()`
52
+ - **Incrément/Décrément atomiques**: `increment()` et `decrement()`
53
+ - **Aliases ergonomiques**: `columns([...])`, `ordrer()` (alias typo de `orderBy`)
54
+ - **Requêtes brutes**: `executeRawQuery()` et `execute()` (résultats natifs du driver)
55
+ - **Migrations complètes** (create/alter/drop, index, foreign keys, batch tracking)
56
+ - **CLI pratiques**: `outlet-init`, `outlet-migrate`, `outlet-convert`
57
+ - **Configuration via `.env`** (chargée automatiquement)
58
+ - **Multi-base de données**: MySQL, PostgreSQL et SQLite
59
+ - **Types TypeScript** fournis
44
60
 
45
61
  ## ⚡ Démarrage Rapide
46
62
 
@@ -54,66 +70,104 @@ outlet-init
54
70
  outlet-migrate make create_users_table
55
71
 
56
72
  # Exécuter les migrations
57
- outlet-migrate
73
+ outlet-migrate migrate
58
74
  ```
59
75
 
60
- ## 📖 Utilisation Rapide
76
+ ## 📖 Utilisation
61
77
 
62
78
  ### Configuration de la connexion
63
79
 
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 :
80
+ Outlet ORM charge automatiquement la connexion depuis le fichier `.env`. **Plus besoin d'importer DatabaseConnection !**
81
+
82
+ #### Fichier `.env`
65
83
 
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
84
+ ```env
85
+ DB_DRIVER=mysql
86
+ DB_HOST=localhost
87
+ DB_DATABASE=myapp
88
+ DB_USER=root
89
+ DB_PASSWORD=secret
90
+ DB_PORT=3306
91
+ ```
71
92
 
72
- Un exemple est fourni dans `.env.example`.
93
+ #### Utilisation simplifiée
94
+
95
+ ```javascript
96
+ const { Model } = require('outlet-orm');
97
+
98
+ class User extends Model {
99
+ static table = 'users';
100
+ }
101
+
102
+ // C'est tout ! La connexion est automatique
103
+ const users = await User.all();
104
+ ```
105
+
106
+ #### Configuration manuelle (optionnel)
107
+
108
+ Si vous avez besoin de contrôler la connexion :
73
109
 
74
110
  ```javascript
75
111
  const { DatabaseConnection, Model } = require('outlet-orm');
76
112
 
77
- // Configuration MySQL
78
113
  // 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
114
  const db = new DatabaseConnection();
81
115
 
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
116
+ // Option 2 – via objet de configuration
117
+ const db = new DatabaseConnection({
118
+ driver: 'mysql',
119
+ host: 'localhost',
120
+ database: 'myapp',
121
+ user: 'root',
122
+ password: 'secret',
123
+ port: 3306
124
+ });
125
+
126
+ // Définir la connexion manuellement (optionnel)
93
127
  Model.setConnection(db);
94
128
  ```
95
129
 
96
- #### Variables d'environnement (.env) — Détails
130
+ #### Variables d'environnement (.env)
131
+
132
+ | Variable | Description | Par défaut |
133
+ |----------|-------------|------------|
134
+ | `DB_DRIVER` | `mysql`, `postgres`, `sqlite` | `mysql` |
135
+ | `DB_HOST` | Hôte de la base | `localhost` |
136
+ | `DB_PORT` | Port de connexion | Selon driver |
137
+ | `DB_USER` / `DB_USERNAME` | Identifiant | - |
138
+ | `DB_PASSWORD` | Mot de passe | - |
139
+ | `DB_DATABASE` / `DB_NAME` | Nom de la base | - |
140
+ | `DB_FILE` / `SQLITE_DB` | Fichier SQLite | `:memory:` |
97
141
 
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:`)
142
+ ### Importation
103
143
 
104
- Les paramètres passés au constructeur de `DatabaseConnection` ont priorité sur `.env`.
144
+ ```javascript
145
+ // CommonJS - Import simple (connexion automatique via .env)
146
+ const { Model } = require('outlet-orm');
147
+
148
+ // ES Modules
149
+ import { Model } from 'outlet-orm';
150
+
151
+ // Si besoin de contrôle manuel sur la connexion
152
+ const { DatabaseConnection, Model } = require('outlet-orm');
153
+ ```
105
154
 
106
155
  ### Définir un modèle
107
156
 
108
157
  ```javascript
158
+ const { Model } = require('outlet-orm');
159
+
109
160
  class User extends Model {
110
161
  static table = 'users';
162
+ static primaryKey = 'id'; // Par défaut: 'id'
163
+ static timestamps = true; // Par défaut: true
111
164
  static fillable = ['name', 'email', 'password'];
112
165
  static hidden = ['password'];
113
166
  static casts = {
114
167
  id: 'int',
115
168
  email_verified: 'boolean',
116
- metadata: 'json'
169
+ metadata: 'json',
170
+ birthday: 'date'
117
171
  };
118
172
 
119
173
  // Relations
@@ -146,6 +200,9 @@ const user = new User({
146
200
  });
147
201
  user.setAttribute('password', 'secret456');
148
202
  await user.save();
203
+
204
+ // Insert brut (sans créer d'instance)
205
+ await User.insert({ name: 'Bob', email: 'bob@example.com' });
149
206
  ```
150
207
 
151
208
  #### Lire
@@ -156,6 +213,7 @@ const users = await User.all();
156
213
 
157
214
  // Par ID
158
215
  const user = await User.find(1);
216
+ const user = await User.findOrFail(1); // Lance une erreur si non trouvé
159
217
 
160
218
  // Premier résultat
161
219
  const firstUser = await User.first();
@@ -166,7 +224,7 @@ const activeUsers = await User
166
224
  .where('age', '>', 18)
167
225
  .get();
168
226
 
169
- // Avec relations
227
+ // Avec relations (Eager Loading)
170
228
  const usersWithPosts = await User
171
229
  .with('posts', 'profile')
172
230
  .get();
@@ -191,13 +249,13 @@ await User
191
249
  .where('status', 'pending')
192
250
  .update({ status: 'active' });
193
251
 
194
- // One-liner façon Prisma (update + include)
252
+ // Update + Fetch (comme Prisma)
195
253
  const updated = await User
196
254
  .where('id', 1)
197
- .updateAndFetch({ name: 'Neo' }, ['profile', 'posts.comments']);
255
+ .updateAndFetch({ name: 'Neo' }, ['profile', 'posts']);
198
256
 
199
257
  // Helpers par ID
200
- const user1 = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
258
+ const user = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
201
259
  await User.updateById(2, { status: 'active' });
202
260
  ```
203
261
 
@@ -224,82 +282,73 @@ const users = await User
224
282
  .orWhere('role', 'admin')
225
283
  .get();
226
284
 
227
- // Where In
228
- const users = await User
229
- .whereIn('id', [1, 2, 3, 4, 5])
230
- .get();
285
+ // Where In / Not In
286
+ const users = await User.whereIn('id', [1, 2, 3, 4, 5]).get();
287
+ const users = await User.whereNotIn('status', ['banned', 'deleted']).get();
231
288
 
232
- // Where Null
233
- const users = await User
234
- .whereNull('deleted_at')
235
- .get();
289
+ // Where Null / Not Null
290
+ const users = await User.whereNull('deleted_at').get();
291
+ const verified = await User.whereNotNull('email_verified_at').get();
236
292
 
237
- // Where Not Null
238
- const users = await User
239
- .whereNotNull('email_verified_at')
240
- .get();
293
+ // Where Between / Like
294
+ const adults = await User.whereBetween('age', [18, 65]).get();
295
+ const johns = await User.whereLike('name', '%john%').get();
241
296
 
242
297
  // Pagination
243
298
  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
- // }
299
+ // { data: [...], total: 100, per_page: 15, current_page: 1, last_page: 7, from: 1, to: 15 }
253
300
 
254
- // Count
301
+ // Count / Exists
255
302
  const count = await User.where('status', 'active').count();
303
+ const hasUsers = await User.where('role', 'admin').exists();
256
304
 
257
305
  // Joins
258
306
  const result = await User
259
307
  .join('profiles', 'users.id', 'profiles.user_id')
260
308
  .leftJoin('countries', 'profiles.country_id', 'countries.id')
261
- .whereLike('users.name', '%john%')
262
- .whereBetween('users.age', [18, 65])
263
309
  .select('users.*', 'profiles.bio', 'countries.name as country')
264
- .orderBy('users.created_at', 'desc')
265
310
  .get();
266
311
 
267
- // Alias ergonomiques
268
- const slim = await User
269
- .columns(['id', 'name']) // alias de select(...)
270
- .ordrer('created_at', 'desc') // alias typo de orderBy
312
+ // Agrégations
313
+ const stats = await User
314
+ .distinct()
315
+ .groupBy('status')
316
+ .having('COUNT(*)', '>', 5)
271
317
  .get();
272
318
 
273
- // whereHas: filtrer les parents qui ont des enfants correspondants
274
- // Exemple: Utilisateurs ayant au moins un post publié récemment
319
+ // Incrément / Décrément atomique
320
+ await User.where('id', 1).increment('login_count');
321
+ await User.where('id', 1).decrement('credits', 10);
322
+ ```
323
+
324
+ ### Filtres relationnels
325
+
326
+ ```javascript
327
+ // whereHas: Utilisateurs ayant au moins un post publié
275
328
  const authors = await User
276
329
  .whereHas('posts', (q) => {
277
- q.where('status', 'published').where('created_at', '>', new Date(Date.now() - 7*24*3600*1000));
330
+ q.where('status', 'published');
278
331
  })
279
332
  .get();
280
333
 
281
- // has: au moins N enfants
334
+ // has: Au moins N enfants
282
335
  const prolific = await User.has('posts', '>=', 10).get();
283
336
 
284
- // whereDoesntHave: aucun enfant
285
- const orphans = await User.whereDoesntHave('posts').get();
337
+ // whereDoesntHave: Aucun enfant
338
+ const noPostUsers = await User.whereDoesntHave('posts').get();
286
339
 
287
- // withCount: ajouter une colonne posts_count
340
+ // withCount: Ajouter une colonne {relation}_count
288
341
  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();
342
+ // Chaque user aura: user.getAttribute('posts_count')
296
343
  ```
297
344
 
298
- ### Relations
345
+ ## 🔗 Relations
299
346
 
300
- #### One to One (hasOne)
347
+ ### One to One (hasOne)
301
348
 
302
349
  ```javascript
350
+ const { Model } = require('outlet-orm');
351
+
303
352
  class User extends Model {
304
353
  profile() {
305
354
  return this.hasOne(Profile, 'user_id');
@@ -310,9 +359,11 @@ const user = await User.find(1);
310
359
  const profile = await user.profile().get();
311
360
  ```
312
361
 
313
- #### One to Many (hasMany)
362
+ ### One to Many (hasMany)
314
363
 
315
364
  ```javascript
365
+ const { Model } = require('outlet-orm');
366
+
316
367
  class User extends Model {
317
368
  posts() {
318
369
  return this.hasMany(Post, 'user_id');
@@ -323,9 +374,11 @@ const user = await User.find(1);
323
374
  const posts = await user.posts().get();
324
375
  ```
325
376
 
326
- #### Belongs To (belongsTo)
377
+ ### Belongs To (belongsTo)
327
378
 
328
379
  ```javascript
380
+ const { Model } = require('outlet-orm');
381
+
329
382
  class Post extends Model {
330
383
  author() {
331
384
  return this.belongsTo(User, 'user_id');
@@ -336,16 +389,18 @@ const post = await Post.find(1);
336
389
  const author = await post.author().get();
337
390
  ```
338
391
 
339
- #### Many to Many (belongsToMany)
392
+ ### Many to Many (belongsToMany)
340
393
 
341
394
  ```javascript
395
+ const { Model } = require('outlet-orm');
396
+
342
397
  class User extends Model {
343
398
  roles() {
344
399
  return this.belongsToMany(
345
400
  Role,
346
- 'user_roles', // Pivot table
347
- 'user_id', // Foreign key
348
- 'role_id' // Related key
401
+ 'user_roles', // Table pivot
402
+ 'user_id', // FK vers User
403
+ 'role_id' // FK vers Role
349
404
  );
350
405
  }
351
406
  }
@@ -353,76 +408,135 @@ class User extends Model {
353
408
  const user = await User.find(1);
354
409
  const roles = await user.roles().get();
355
410
 
356
- // belongsToMany helpers
357
- await user.roles().attach([1, 2]);
358
- await user.roles().detach(2);
359
- await user.roles().sync([1, 3]);
411
+ // Méthodes pivot
412
+ await user.roles().attach([1, 2]); // Attacher des rôles
413
+ await user.roles().detach(2); // Détacher un rôle
414
+ await user.roles().sync([1, 3]); // Synchroniser (remplace tout)
360
415
  ```
361
416
 
362
- #### Has Many Through (hasManyThrough)
417
+ ### Has Many Through (hasManyThrough)
363
418
 
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).
419
+ Accéder à une relation distante via un modèle intermédiaire.
365
420
 
366
421
  ```javascript
367
- class User extends Model {
368
- posts() {
369
- return this.hasMany(Post, 'user_id');
370
- }
422
+ const { Model } = require('outlet-orm');
371
423
 
424
+ class User extends Model {
425
+ // User -> Post -> Comment
372
426
  comments() {
373
- // hasManyThrough(final, through, fkOnThrough?, throughKeyOnFinal?, localKey?, throughLocalKey?)
374
427
  return this.hasManyThrough(Comment, Post, 'user_id', 'post_id');
375
428
  }
376
429
  }
377
430
 
378
431
  const user = await User.find(1);
379
- const comments = await user.comments().get();
432
+ const allComments = await user.comments().get();
433
+ ```
434
+
435
+ ### Has One Through (hasOneThrough)
436
+
437
+ ```javascript
438
+ const { Model } = require('outlet-orm');
380
439
 
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();
440
+ class User extends Model {
441
+ // User -> Profile -> Country
442
+ country() {
443
+ return this.hasOneThrough(Country, Profile, 'user_id', 'country_id');
444
+ }
445
+ }
446
+
447
+ const user = await User.find(1);
448
+ const country = await user.country().get();
383
449
  ```
384
450
 
385
- Par défaut, les clés sont inférées selon les conventions:
451
+ ### Relations Polymorphiques
452
+
453
+ Les relations polymorphiques permettent à un modèle d'appartenir à plusieurs autres modèles.
386
454
 
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`)
455
+ ```javascript
456
+ const { Model } = require('outlet-orm');
457
+
458
+ // Configuration du morph map
459
+ Model.setMorphMap({
460
+ 'posts': Post,
461
+ 'videos': Video
462
+ });
463
+
464
+ // Modèles
465
+ class Post extends Model {
466
+ comments() {
467
+ return this.morphMany(Comment, 'commentable');
468
+ }
469
+ }
470
+
471
+ class Video extends Model {
472
+ comments() {
473
+ return this.morphMany(Comment, 'commentable');
474
+ }
475
+ }
476
+
477
+ class Comment extends Model {
478
+ commentable() {
479
+ return this.morphTo('commentable');
480
+ }
481
+ }
482
+
483
+ // Usage
484
+ const post = await Post.find(1);
485
+ const comments = await post.comments().get();
486
+
487
+ const comment = await Comment.find(1);
488
+ const parent = await comment.commentable().get(); // Post ou Video
489
+ ```
490
+
491
+ **Relations polymorphiques disponibles:**
492
+ - `morphOne(Related, 'morphName')` - One-to-One polymorphique
493
+ - `morphMany(Related, 'morphName')` - One-to-Many polymorphique
494
+ - `morphTo('morphName')` - Inverse polymorphique
391
495
 
392
496
  ### Eager Loading
393
497
 
394
498
  ```javascript
395
- // Charger les relations avec les résultats
396
- const users = await User.with('posts', 'profile').get();
499
+ // Charger plusieurs relations
500
+ const users = await User.with('posts', 'profile', 'roles').get();
501
+
502
+ // Charger avec contraintes
503
+ const users = await User.with({
504
+ posts: (q) => q.where('status', 'published').orderBy('created_at', 'desc')
505
+ }).get();
506
+
507
+ // Charger des relations imbriquées (dot notation)
508
+ const users = await User.with('posts.comments.author').get();
509
+
510
+ // Charger sur une instance existante
511
+ const user = await User.find(1);
512
+ await user.load('posts', 'profile');
513
+ await user.load(['roles', 'posts.comments']);
397
514
 
398
515
  // Accéder aux relations chargées
399
516
  users.forEach(user => {
400
- console.log(user.getAttribute('name'));
401
517
  console.log(user.relations.posts);
402
518
  console.log(user.relations.profile);
403
519
  });
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
520
  ```
411
521
 
522
+ ## 🎭 Attributs
523
+
412
524
  ### Casts
413
525
 
414
- Les casts permettent de convertir automatiquement les attributs:
526
+ Les casts convertissent automatiquement les attributs:
415
527
 
416
528
  ```javascript
529
+ const { Model } = require('outlet-orm');
530
+
417
531
  class User extends Model {
418
532
  static casts = {
419
- id: 'int',
533
+ id: 'int', // ou 'integer'
420
534
  age: 'integer',
421
- balance: 'float',
422
- email_verified: 'boolean',
423
- metadata: 'json',
424
- settings: 'array',
425
- birthday: 'date'
535
+ balance: 'float', // ou 'double'
536
+ email_verified: 'boolean', // ou 'bool'
537
+ metadata: 'json', // Parse JSON
538
+ settings: 'array', // Parse JSON en array
539
+ birthday: 'date' // Convertit en Date
426
540
  };
427
541
  }
428
542
  ```
@@ -430,72 +544,287 @@ class User extends Model {
430
544
  ### Attributs cachés
431
545
 
432
546
  ```javascript
547
+ const { Model } = require('outlet-orm');
548
+
433
549
  class User extends Model {
434
550
  static hidden = ['password', 'secret_token'];
435
551
  }
436
552
 
437
553
  const user = await User.find(1);
438
- console.log(user.toJSON()); // password et secret_token ne sont pas inclus
554
+ console.log(user.toJSON()); // password et secret_token exclus
555
+ ```
556
+
557
+ #### Afficher les attributs cachés
558
+
559
+ ```javascript
560
+ // Inclure les attributs cachés
561
+ const user = await User.withHidden().where('email', 'john@example.com').first();
562
+ console.log(user.toJSON()); // password inclus
563
+
564
+ // Contrôler avec un booléen
565
+ const user = await User.withoutHidden(true).first(); // true = afficher
566
+ const user = await User.withoutHidden(false).first(); // false = masquer (défaut)
567
+
568
+ // Cas d'usage: authentification
569
+ const user = await User.withHidden().where('email', email).first();
570
+ if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
571
+ // Authentification réussie
572
+ }
439
573
  ```
440
574
 
441
575
  ### Timestamps
442
576
 
443
577
  ```javascript
444
- // Activer les timestamps automatiques (activé par défaut)
578
+ const { Model } = require('outlet-orm');
579
+
580
+ // Activés par défaut (created_at, updated_at)
445
581
  class User extends Model {
446
- static timestamps = true; // created_at et updated_at
582
+ static timestamps = true;
447
583
  }
448
584
 
449
- // Désactiver les timestamps
585
+ // Désactiver
450
586
  class Log extends Model {
451
587
  static timestamps = false;
452
588
  }
453
589
  ```
454
590
 
455
- ## 🔧 Configuration avancée
591
+ ## 🔄 Transactions
456
592
 
457
- ### Connexions multiples
593
+ Outlet ORM supporte les transactions pour garantir l'intégrité des données:
458
594
 
459
595
  ```javascript
460
- const mysqlDb = new DatabaseConnection({
461
- driver: 'mysql',
462
- host: 'localhost',
463
- database: 'app_db',
464
- user: 'root',
465
- password: 'secret'
466
- });
596
+ const { DatabaseConnection, Model } = require('outlet-orm');
467
597
 
468
- const postgresDb = new DatabaseConnection({
469
- driver: 'postgres',
470
- host: 'localhost',
471
- database: 'analytics_db',
472
- user: 'postgres',
473
- password: 'secret'
598
+ // Méthode 1: Callback automatique (recommandé)
599
+ const db = Model.connection;
600
+ const result = await db.transaction(async (connection) => {
601
+ const user = await User.create({ name: 'John', email: 'john@example.com' });
602
+ await Account.create({ user_id: user.getAttribute('id'), balance: 0 });
603
+ return user;
474
604
  });
605
+ // Commit automatique, rollback si erreur
606
+
607
+ // Méthode 2: Contrôle manuel
608
+ await db.beginTransaction();
609
+ try {
610
+ await User.create({ name: 'Jane' });
611
+ await db.commit();
612
+ } catch (error) {
613
+ await db.rollback();
614
+ throw error;
615
+ }
616
+ ```
475
617
 
476
- // Par modèle
477
- class User extends Model {
478
- static connection = mysqlDb;
618
+ ## 🗑️ Soft Deletes
619
+
620
+ Suppression logique avec colonne `deleted_at`:
621
+
622
+ ```javascript
623
+ const { Model } = require('outlet-orm');
624
+
625
+ class Post extends Model {
626
+ static table = 'posts';
627
+ static softDeletes = true;
628
+ // static DELETED_AT = 'deleted_at'; // Personnalisable
479
629
  }
480
630
 
481
- class Analytics extends Model {
482
- static connection = postgresDb;
631
+ // Les requêtes excluent automatiquement les supprimés
632
+ const posts = await Post.all(); // Seulement les non-supprimés
633
+
634
+ // Inclure les supprimés
635
+ const allPosts = await Post.withTrashed().get();
636
+
637
+ // Seulement les supprimés
638
+ const trashedPosts = await Post.onlyTrashed().get();
639
+
640
+ // Supprimer (soft delete)
641
+ const post = await Post.find(1);
642
+ await post.destroy(); // Met deleted_at à la date actuelle
643
+
644
+ // Vérifier si supprimé
645
+ if (post.trashed()) {
646
+ console.log('Ce post est supprimé');
483
647
  }
648
+
649
+ // Restaurer
650
+ await post.restore();
651
+
652
+ // Supprimer définitivement
653
+ await post.forceDelete();
484
654
  ```
485
655
 
486
- ### Clé primaire personnalisée
656
+ ## 🔬 Scopes
657
+
658
+ ### Scopes Globaux
659
+
660
+ Appliqués automatiquement à toutes les requêtes:
487
661
 
488
662
  ```javascript
663
+ const { Model } = require('outlet-orm');
664
+
665
+ class Post extends Model {
666
+ static table = 'posts';
667
+ }
668
+
669
+ // Ajouter un scope global
670
+ Post.addGlobalScope('published', (query) => {
671
+ query.where('status', 'published');
672
+ });
673
+
674
+ // Toutes les requêtes filtrent automatiquement
675
+ const posts = await Post.all(); // Seulement les publiés
676
+
677
+ // Désactiver temporairement un scope
678
+ const allPosts = await Post.withoutGlobalScope('published').get();
679
+
680
+ // Désactiver tous les scopes
681
+ const rawPosts = await Post.withoutGlobalScopes().get();
682
+ ```
683
+
684
+ ## 📣 Events / Hooks
685
+
686
+ Interceptez les opérations sur vos modèles:
687
+
688
+ ```javascript
689
+ const { Model } = require('outlet-orm');
690
+
489
691
  class User extends Model {
490
- static primaryKey = 'user_id';
692
+ static table = 'users';
491
693
  }
694
+
695
+ // Avant création
696
+ User.creating((user) => {
697
+ user.setAttribute('uuid', generateUUID());
698
+ // Retourner false pour annuler
699
+ });
700
+
701
+ // Après création
702
+ User.created((user) => {
703
+ console.log(`Utilisateur ${user.getAttribute('id')} créé`);
704
+ });
705
+
706
+ // Avant mise à jour
707
+ User.updating((user) => {
708
+ user.setAttribute('updated_at', new Date());
709
+ });
710
+
711
+ // Après mise à jour
712
+ User.updated((user) => {
713
+ // Notifier les systèmes externes
714
+ });
715
+
716
+ // Événements saving/saved (création ET mise à jour)
717
+ User.saving((user) => {
718
+ // Nettoyage des données
719
+ });
720
+
721
+ User.saved((user) => {
722
+ // Cache invalidation
723
+ });
724
+
725
+ // Avant/après suppression
726
+ User.deleting((user) => {
727
+ // Vérifications avant suppression
728
+ });
729
+
730
+ User.deleted((user) => {
731
+ // Nettoyage des relations
732
+ });
733
+
734
+ // Pour les soft deletes
735
+ User.restoring((user) => {});
736
+ User.restored((user) => {});
492
737
  ```
493
738
 
494
- ### Nom de table personnalisé
739
+ ## Validation
740
+
741
+ Validation basique intégrée:
495
742
 
496
743
  ```javascript
744
+ const { Model } = require('outlet-orm');
745
+
497
746
  class User extends Model {
498
- static table = 'app_users';
747
+ static table = 'users';
748
+ static rules = {
749
+ name: 'required|string|min:2|max:100',
750
+ email: 'required|email',
751
+ age: 'numeric|min:0|max:150',
752
+ role: 'in:admin,user,guest',
753
+ password: 'required|min:8'
754
+ };
755
+ }
756
+
757
+ const user = new User({
758
+ name: 'J',
759
+ email: 'invalid-email',
760
+ age: 200
761
+ });
762
+
763
+ // Valider
764
+ const { valid, errors } = user.validate();
765
+ console.log(valid); // false
766
+ console.log(errors);
767
+ // {
768
+ // name: ['name must be at least 2 characters'],
769
+ // email: ['email must be a valid email'],
770
+ // age: ['age must not exceed 150']
771
+ // }
772
+
773
+ // Valider ou lancer une erreur
774
+ try {
775
+ user.validateOrFail();
776
+ } catch (error) {
777
+ console.log(error.errors);
778
+ }
779
+ ```
780
+
781
+ ### Règles disponibles
782
+
783
+ | Règle | Description |
784
+ |-------|-------------|
785
+ | `required` | Champ obligatoire |
786
+ | `string` | Doit être une chaîne |
787
+ | `number` / `numeric` | Doit être un nombre |
788
+ | `email` | Format email valide |
789
+ | `boolean` | Doit être un booléen |
790
+ | `date` | Date valide |
791
+ | `min:N` | Minimum N (longueur ou valeur) |
792
+ | `max:N` | Maximum N (longueur ou valeur) |
793
+ | `in:a,b,c` | Valeur parmi la liste |
794
+ | `regex:pattern` | Match le pattern regex |
795
+
796
+ ## 📊 Query Logging
797
+
798
+ Mode debug pour analyser vos requêtes:
799
+
800
+ ```javascript
801
+ const { Model } = require('outlet-orm');
802
+
803
+ // Activer le logging
804
+ const db = Model.getConnection();
805
+ db.enableQueryLog();
806
+
807
+ // Exécuter des requêtes
808
+ await User.where('status', 'active').get();
809
+ await Post.with('author').get();
810
+
811
+ // Récupérer le log
812
+ const queries = db.getQueryLog();
813
+ console.log(queries);
814
+ // [
815
+ // { sql: 'SELECT * FROM users WHERE status = ?', params: ['active'], duration: 15, timestamp: Date },
816
+ // { sql: 'SELECT * FROM posts', params: [], duration: 8, timestamp: Date }
817
+ // ]
818
+
819
+ // Vider le log
820
+ db.flushQueryLog();
821
+
822
+ // Désactiver le logging
823
+ db.disableQueryLog();
824
+
825
+ // Vérifier si actif
826
+ if (db.isLogging()) {
827
+ console.log('Logging actif');
499
828
  }
500
829
  ```
501
830
 
@@ -503,117 +832,167 @@ class User extends Model {
503
832
 
504
833
  ### DatabaseConnection
505
834
 
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
835
+ | Méthode | Description |
836
+ |---------|-------------|
837
+ | `new DatabaseConnection(config?)` | Crée une connexion (lit `.env` si config omis) |
838
+ | `connect()` | Établit la connexion (appelé automatiquement) |
839
+ | `beginTransaction()` | Démarre une transaction |
840
+ | `commit()` | Valide la transaction |
841
+ | `rollback()` | Annule la transaction |
842
+ | `transaction(callback)` | Exécute dans une transaction (auto commit/rollback) |
843
+ | `select(table, query)` | Exécute un SELECT |
844
+ | `insert(table, data)` | Insère un enregistrement |
845
+ | `insertMany(table, data[])` | Insère plusieurs enregistrements |
846
+ | `update(table, data, query)` | Met à jour des enregistrements |
847
+ | `delete(table, query)` | Supprime des enregistrements |
848
+ | `count(table, query)` | Compte les enregistrements |
849
+ | `executeRawQuery(sql, params?)` | Requête brute (résultats normalisés) |
850
+ | `execute(sql, params?)` | Requête brute (résultats natifs driver) |
851
+ | `increment(table, column, query, amount?)` | Incrément atomique |
852
+ | `decrement(table, column, query, amount?)` | Décrément atomique |
853
+ | `close()` / `disconnect()` | Ferme la connexion |
854
+ | **Query Logging (static)** | |
855
+ | `enableQueryLog()` | Active le logging des requêtes |
856
+ | `disableQueryLog()` | Désactive le logging |
857
+ | `getQueryLog()` | Retourne le log des requêtes |
858
+ | `flushQueryLog()` | Vide le log |
859
+ | `isLogging()` | Vérifie si le logging est actif |
860
+
861
+ ### Model (méthodes statiques)
862
+
863
+ | Méthode | Description |
864
+ |---------|-------------|
865
+ | `setConnection(db)` | Définit la connexion par défaut |
866
+ | `getConnection()` | Récupère la connexion (v3.0.0+) |
867
+ | `setMorphMap(map)` | Définit le mapping polymorphique |
868
+ | `query()` | Retourne un QueryBuilder |
869
+ | `all()` | Tous les enregistrements |
870
+ | `find(id)` | Trouve par ID |
871
+ | `findOrFail(id)` | Trouve ou lance une erreur |
872
+ | `first()` | Premier enregistrement |
873
+ | `where(col, op?, val)` | Clause WHERE |
874
+ | `whereIn(col, vals)` | Clause WHERE IN |
875
+ | `whereNull(col)` | Clause WHERE NULL |
876
+ | `whereNotNull(col)` | Clause WHERE NOT NULL |
877
+ | `create(attrs)` | Crée et sauvegarde |
878
+ | `insert(data)` | Insert brut |
879
+ | `update(attrs)` | Update bulk |
880
+ | `updateById(id, attrs)` | Update par ID |
881
+ | `updateAndFetchById(id, attrs, rels?)` | Update + fetch avec relations |
882
+ | `delete()` | Delete bulk |
883
+ | `with(...rels)` | Eager loading |
884
+ | `withHidden()` | Inclut les attributs cachés |
885
+ | `withoutHidden(show?)` | Contrôle visibilité |
886
+ | `orderBy(col, dir?)` | Tri |
887
+ | `limit(n)` / `offset(n)` | Limite/Offset |
888
+ | `paginate(page, perPage)` | Pagination |
889
+ | `count()` | Compte |
890
+ | **Soft Deletes** | |
891
+ | `withTrashed()` | Inclut les supprimés |
892
+ | `onlyTrashed()` | Seulement les supprimés |
893
+ | **Scopes** | |
894
+ | `addGlobalScope(name, cb)` | Ajoute un scope global |
895
+ | `removeGlobalScope(name)` | Supprime un scope |
896
+ | `withoutGlobalScope(name)` | Requête sans un scope |
897
+ | `withoutGlobalScopes()` | Requête sans tous les scopes |
898
+ | **Events** | |
899
+ | `on(event, callback)` | Enregistre un listener |
900
+ | `creating(cb)` / `created(cb)` | Events création |
901
+ | `updating(cb)` / `updated(cb)` | Events mise à jour |
902
+ | `saving(cb)` / `saved(cb)` | Events sauvegarde |
903
+ | `deleting(cb)` / `deleted(cb)` | Events suppression |
904
+ | `restoring(cb)` / `restored(cb)` | Events restauration |
905
+
906
+ ### Model (méthodes d'instance)
907
+
908
+ | Méthode | Description |
909
+ |---------|-------------|
910
+ | `fill(attrs)` | Remplit les attributs |
911
+ | `setAttribute(key, val)` | Définit un attribut |
912
+ | `getAttribute(key)` | Récupère un attribut |
913
+ | `save()` | Sauvegarde (insert ou update) |
914
+ | `destroy()` | Supprime l'instance (soft si activé) |
915
+ | `load(...rels)` | Charge des relations |
916
+ | `getDirty()` | Attributs modifiés |
917
+ | `isDirty()` | A été modifié? |
918
+ | `toJSON()` | Convertit en objet |
919
+ | **Soft Deletes** | |
920
+ | `trashed()` | Est supprimé? |
921
+ | `restore()` | Restaure le modèle |
922
+ | `forceDelete()` | Suppression définitive |
923
+ | **Validation** | |
924
+ | `validate()` | Valide selon les règles |
925
+ | `validateOrFail()` | Valide ou lance erreur |
534
926
 
535
927
  ### QueryBuilder
536
928
 
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`
929
+ | Méthode | Description |
930
+ |---------|-------------|
931
+ | `select(...cols)` / `columns([...])` | Sélection de colonnes |
932
+ | `distinct()` | SELECT DISTINCT |
933
+ | `where(col, op?, val)` | Clause WHERE |
934
+ | `whereIn(col, vals)` | WHERE IN |
935
+ | `whereNotIn(col, vals)` | WHERE NOT IN |
936
+ | `whereNull(col)` | WHERE NULL |
937
+ | `whereNotNull(col)` | WHERE NOT NULL |
938
+ | `orWhere(col, op?, val)` | OR WHERE |
939
+ | `whereBetween(col, [min, max])` | WHERE BETWEEN |
940
+ | `whereLike(col, pattern)` | WHERE LIKE |
941
+ | `whereHas(rel, cb?)` | Filtre par relation |
942
+ | `has(rel, op?, count)` | Existence relationnelle |
943
+ | `whereDoesntHave(rel)` | Absence de relation |
944
+ | `orderBy(col, dir?)` / `ordrer(...)` | Tri |
945
+ | `limit(n)` / `take(n)` | Limite |
946
+ | `offset(n)` / `skip(n)` | Offset |
947
+ | `groupBy(...cols)` | GROUP BY |
948
+ | `having(col, op, val)` | HAVING |
949
+ | `join(table, first, op?, second)` | INNER JOIN |
950
+ | `leftJoin(table, first, op?, second)` | LEFT JOIN |
951
+ | `with(...rels)` | Eager loading |
952
+ | `withCount(rels)` | Ajoute {rel}_count |
953
+ | `withTrashed()` | Inclut les supprimés |
954
+ | `onlyTrashed()` | Seulement les supprimés |
955
+ | `withoutGlobalScope(name)` | Sans un scope global |
956
+ | `withoutGlobalScopes()` | Sans tous les scopes |
957
+ | `get()` | Exécute et retourne tous |
958
+ | `first()` | Premier résultat |
959
+ | `firstOrFail()` | Premier ou erreur |
960
+ | `paginate(page, perPage)` | Pagination |
961
+ | `count()` | Compte |
962
+ | `exists()` | Vérifie l'existence |
963
+ | `insert(data)` | Insert |
964
+ | `update(attrs)` | Update |
965
+ | `updateAndFetch(attrs, rels?)` | Update + fetch |
966
+ | `delete()` | Delete |
967
+ | `increment(col, amount?)` | Incrément atomique |
968
+ | `decrement(col, amount?)` | Décrément atomique |
969
+ | `clone()` | Clone le query builder |
571
970
 
572
971
  ## 🛠️ Outils CLI
573
972
 
574
- ### 1. Initialisation d'un projet
973
+ ### outlet-init
974
+
975
+ Initialise un nouveau projet avec configuration de base de données.
575
976
 
576
977
  ```bash
577
978
  outlet-init
578
979
  ```
579
980
 
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é.
981
+ Génère:
982
+ - Fichier de configuration `database/config.js`
983
+ - Fichier `.env` avec les paramètres
984
+ - Modèle exemple
985
+ - Fichier d'utilisation
583
986
 
584
- Astuce: dans les environnements CI/tests, vous pouvez désactiver l'installation automatique du driver en définissant `OUTLET_INIT_NO_INSTALL=1`.
987
+ ### outlet-migrate
585
988
 
586
- ### 2. Système de Migrations
989
+ Système complet de migrations.
587
990
 
588
991
  ```bash
589
992
  # Créer une migration
590
993
  outlet-migrate make create_users_table
591
994
 
592
995
  # 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
996
  outlet-migrate migrate
618
997
 
619
998
  # Voir le statut
@@ -622,84 +1001,66 @@ outlet-migrate status
622
1001
  # Annuler la dernière migration
623
1002
  outlet-migrate rollback --steps 1
624
1003
 
625
- # Astuce: si le fichier database/config.js existe, il est prioritaire sur .env
626
- ```
1004
+ # Reset toutes les migrations
1005
+ outlet-migrate reset --yes
627
1006
 
628
- **Fonctionnalités des Migrations :**
1007
+ # Refresh (reset + migrate)
1008
+ outlet-migrate refresh --yes
1009
+
1010
+ # Fresh (drop all + migrate)
1011
+ outlet-migrate fresh --yes
1012
+ ```
629
1013
 
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
1014
+ **Fonctionnalités des Migrations:**
640
1015
 
641
- **Documentation complète :**
1016
+ - Création et gestion des migrations (create, alter, drop tables)
1017
+ - ✅ Types de colonnes: id, string, text, integer, boolean, date, datetime, timestamp, decimal, float, json, enum, uuid, foreignId
1018
+ - ✅ Modificateurs: nullable, default, unique, index, unsigned, autoIncrement, comment, after, first
1019
+ - ✅ Clés étrangères: foreign(), constrained(), onDelete(), onUpdate(), CASCADE
1020
+ - ✅ Index: index(), unique(), fullText()
1021
+ - ✅ Manipulation: renameColumn(), dropColumn(), dropTimestamps()
1022
+ - ✅ Migrations réversibles: Méthodes up() et down()
1023
+ - ✅ Batch tracking: Rollback précis par batch
1024
+ - ✅ SQL personnalisé: execute() pour commandes avancées
642
1025
 
643
- - [MIGRATIONS.md](docs/MIGRATIONS.md) - Guide complet des migrations
1026
+ ### outlet-convert
644
1027
 
645
- ### 3. Conversion SQL vers ORM
1028
+ Convertit des schémas SQL en modèles ORM.
646
1029
 
647
1030
  ```bash
648
1031
  outlet-convert
649
1032
  ```
650
1033
 
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 :**
1034
+ **Options:**
1035
+ 1. Depuis un fichier SQL local
1036
+ 2. Depuis une base de données connectée
665
1037
 
1038
+ **Fonctionnalités:**
666
1039
  - ✅ 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
1040
+ - ✅ Génération automatique de TOUTES les relations (belongsTo, hasMany, hasOne, belongsToMany)
672
1041
  - ✅ Relations récursives (auto-relations)
673
1042
  - ✅ Détection des champs sensibles (password, token, etc.)
674
1043
  - ✅ Support des timestamps automatiques
675
- - ✅ Conversion des noms de tables en classes PascalCase
676
-
677
- ### 4. Utilisation non-interactive (CI/CD)
1044
+ - ✅ Conversion des noms en PascalCase
678
1045
 
679
- Les commandes de migration supportent un mode non-interactif pratique pour l’automatisation:
1046
+ ## 📚 Documentation
680
1047
 
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
1048
+ - [Guide des Migrations](docs/MIGRATIONS.md)
1049
+ - [Conversion SQL](docs/SQL_CONVERSION.md)
1050
+ - [Détection des Relations](docs/RELATIONS_DETECTION.md)
1051
+ - [Guide de démarrage rapide](docs/QUICKSTART.md)
1052
+ - [Architecture](docs/ARCHITECTURE.md)
698
1053
 
699
1054
  ## 🤝 Contribution
700
1055
 
701
1056
  Les contributions sont les bienvenues! N'hésitez pas à ouvrir une issue ou un pull request.
702
1057
 
1058
+ Voir [CONTRIBUTING.md](CONTRIBUTING.md) pour les guidelines.
1059
+
703
1060
  ## 📄 Licence
704
1061
 
705
- MIT
1062
+ MIT - Voir [LICENSE](LICENSE) pour plus de détails.
1063
+
1064
+ ---
1065
+
1066
+ Créé par [omgbwa-yasse](https://github.com/omgbwa-yasse)