outlet-orm 2.5.1 → 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,23 +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
- - Contrôle de visibilité des attributs cachés: `withHidden()` et `withoutHidden()`
37
- - Incrément/Décrément atomiques: `increment()` et `decrement()`
38
- - Aliases ergonomiques: `columns([...])`, `ordrer()` (alias typo de `orderBy`)
39
- - Requêtes brutes: `executeRawQuery()` et `execute()` (résultats natifs du driver)
40
- - Migrations complètes (create/alter/drop, index, foreign keys, batch tracking)
41
- - CLI pratiques: `outlet-init`, `outlet-migrate`, `outlet-convert`
42
- - Configuration via `.env` (chargée automatiquement)
43
- - Multi-base de données: MySQL, PostgreSQL et SQLite
44
- - 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
45
60
 
46
61
  ## ⚡ Démarrage Rapide
47
62
 
@@ -55,66 +70,104 @@ outlet-init
55
70
  outlet-migrate make create_users_table
56
71
 
57
72
  # Exécuter les migrations
58
- outlet-migrate
73
+ outlet-migrate migrate
59
74
  ```
60
75
 
61
- ## 📖 Utilisation Rapide
76
+ ## 📖 Utilisation
62
77
 
63
78
  ### Configuration de la connexion
64
79
 
65
- 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`
66
83
 
67
- - DB_DRIVER (mysql, postgres, sqlite)
68
- - DB_HOST, DB_PORT
69
- - DB_USER / DB_USERNAME, DB_PASSWORD
70
- - DB_DATABASE / DB_NAME
71
- - 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
+ ```
72
92
 
73
- 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 :
74
109
 
75
110
  ```javascript
76
111
  const { DatabaseConnection, Model } = require('outlet-orm');
77
112
 
78
- // Configuration MySQL
79
113
  // Option 1 – via .env (aucun paramètre nécessaire)
80
- // DB_DRIVER=mysql, DB_HOST=localhost, DB_DATABASE=myapp, DB_USER=root, DB_PASSWORD=secret, DB_PORT=3306
81
114
  const db = new DatabaseConnection();
82
115
 
83
- // Option 2 – via objet de configuration (prend le dessus sur .env)
84
- // const db = new DatabaseConnection({
85
- // driver: 'mysql',
86
- // host: 'localhost',
87
- // database: 'myapp',
88
- // user: 'root',
89
- // password: 'secret',
90
- // port: 3306
91
- // });
92
-
93
- // 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)
94
127
  Model.setConnection(db);
95
128
  ```
96
129
 
97
- #### 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:` |
98
141
 
99
- - DB_DRIVER: `mysql` | `postgres` | `sqlite` (alias acceptés: `postgresql`, `sqlite3`)
100
- - DB_HOST, DB_PORT: hôte/port (par défaut: `localhost`, ports par défaut selon driver)
101
- - DB_USER | DB_USERNAME, DB_PASSWORD: identifiants
102
- - DB_DATABASE | DB_NAME: nom de la base (MySQL/Postgres)
103
- - SQLite spécifiquement: `DB_FILE` ou `SQLITE_DB` ou `SQLITE_FILENAME` (par défaut `:memory:`)
142
+ ### Importation
104
143
 
105
- 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
+ ```
106
154
 
107
155
  ### Définir un modèle
108
156
 
109
157
  ```javascript
158
+ const { Model } = require('outlet-orm');
159
+
110
160
  class User extends Model {
111
161
  static table = 'users';
162
+ static primaryKey = 'id'; // Par défaut: 'id'
163
+ static timestamps = true; // Par défaut: true
112
164
  static fillable = ['name', 'email', 'password'];
113
165
  static hidden = ['password'];
114
166
  static casts = {
115
167
  id: 'int',
116
168
  email_verified: 'boolean',
117
- metadata: 'json'
169
+ metadata: 'json',
170
+ birthday: 'date'
118
171
  };
119
172
 
120
173
  // Relations
@@ -147,6 +200,9 @@ const user = new User({
147
200
  });
148
201
  user.setAttribute('password', 'secret456');
149
202
  await user.save();
203
+
204
+ // Insert brut (sans créer d'instance)
205
+ await User.insert({ name: 'Bob', email: 'bob@example.com' });
150
206
  ```
151
207
 
152
208
  #### Lire
@@ -157,6 +213,7 @@ const users = await User.all();
157
213
 
158
214
  // Par ID
159
215
  const user = await User.find(1);
216
+ const user = await User.findOrFail(1); // Lance une erreur si non trouvé
160
217
 
161
218
  // Premier résultat
162
219
  const firstUser = await User.first();
@@ -167,7 +224,7 @@ const activeUsers = await User
167
224
  .where('age', '>', 18)
168
225
  .get();
169
226
 
170
- // Avec relations
227
+ // Avec relations (Eager Loading)
171
228
  const usersWithPosts = await User
172
229
  .with('posts', 'profile')
173
230
  .get();
@@ -192,13 +249,13 @@ await User
192
249
  .where('status', 'pending')
193
250
  .update({ status: 'active' });
194
251
 
195
- // One-liner façon Prisma (update + include)
252
+ // Update + Fetch (comme Prisma)
196
253
  const updated = await User
197
254
  .where('id', 1)
198
- .updateAndFetch({ name: 'Neo' }, ['profile', 'posts.comments']);
255
+ .updateAndFetch({ name: 'Neo' }, ['profile', 'posts']);
199
256
 
200
257
  // Helpers par ID
201
- const user1 = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
258
+ const user = await User.updateAndFetchById(1, { name: 'Trinity' }, ['profile']);
202
259
  await User.updateById(2, { status: 'active' });
203
260
  ```
204
261
 
@@ -225,82 +282,73 @@ const users = await User
225
282
  .orWhere('role', 'admin')
226
283
  .get();
227
284
 
228
- // Where In
229
- const users = await User
230
- .whereIn('id', [1, 2, 3, 4, 5])
231
- .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();
232
288
 
233
- // Where Null
234
- const users = await User
235
- .whereNull('deleted_at')
236
- .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();
237
292
 
238
- // Where Not Null
239
- const users = await User
240
- .whereNotNull('email_verified_at')
241
- .get();
293
+ // Where Between / Like
294
+ const adults = await User.whereBetween('age', [18, 65]).get();
295
+ const johns = await User.whereLike('name', '%john%').get();
242
296
 
243
297
  // Pagination
244
298
  const result = await User.paginate(1, 15);
245
- // {
246
- // data: [...],
247
- // total: 100,
248
- // per_page: 15,
249
- // current_page: 1,
250
- // last_page: 7,
251
- // from: 1,
252
- // to: 15
253
- // }
299
+ // { data: [...], total: 100, per_page: 15, current_page: 1, last_page: 7, from: 1, to: 15 }
254
300
 
255
- // Count
301
+ // Count / Exists
256
302
  const count = await User.where('status', 'active').count();
303
+ const hasUsers = await User.where('role', 'admin').exists();
257
304
 
258
305
  // Joins
259
306
  const result = await User
260
307
  .join('profiles', 'users.id', 'profiles.user_id')
261
308
  .leftJoin('countries', 'profiles.country_id', 'countries.id')
262
- .whereLike('users.name', '%john%')
263
- .whereBetween('users.age', [18, 65])
264
309
  .select('users.*', 'profiles.bio', 'countries.name as country')
265
- .orderBy('users.created_at', 'desc')
266
310
  .get();
267
311
 
268
- // Alias ergonomiques
269
- const slim = await User
270
- .columns(['id', 'name']) // alias de select(...)
271
- .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)
272
317
  .get();
273
318
 
274
- // whereHas: filtrer les parents qui ont des enfants correspondants
275
- // 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é
276
328
  const authors = await User
277
329
  .whereHas('posts', (q) => {
278
- q.where('status', 'published').where('created_at', '>', new Date(Date.now() - 7*24*3600*1000));
330
+ q.where('status', 'published');
279
331
  })
280
332
  .get();
281
333
 
282
- // has: au moins N enfants
334
+ // has: Au moins N enfants
283
335
  const prolific = await User.has('posts', '>=', 10).get();
284
336
 
285
- // whereDoesntHave: aucun enfant
286
- const orphans = await User.whereDoesntHave('posts').get();
337
+ // whereDoesntHave: Aucun enfant
338
+ const noPostUsers = await User.whereDoesntHave('posts').get();
287
339
 
288
- // withCount: ajouter une colonne posts_count
340
+ // withCount: Ajouter une colonne {relation}_count
289
341
  const withCounts = await User.withCount('posts').get();
290
-
291
- // Agrégations: distinct, groupBy, having
292
- const stats = await User
293
- .distinct()
294
- .groupBy('status')
295
- .having('COUNT(*)', '>', 5)
296
- .get();
342
+ // Chaque user aura: user.getAttribute('posts_count')
297
343
  ```
298
344
 
299
- ### Relations
345
+ ## 🔗 Relations
300
346
 
301
- #### One to One (hasOne)
347
+ ### One to One (hasOne)
302
348
 
303
349
  ```javascript
350
+ const { Model } = require('outlet-orm');
351
+
304
352
  class User extends Model {
305
353
  profile() {
306
354
  return this.hasOne(Profile, 'user_id');
@@ -311,9 +359,11 @@ const user = await User.find(1);
311
359
  const profile = await user.profile().get();
312
360
  ```
313
361
 
314
- #### One to Many (hasMany)
362
+ ### One to Many (hasMany)
315
363
 
316
364
  ```javascript
365
+ const { Model } = require('outlet-orm');
366
+
317
367
  class User extends Model {
318
368
  posts() {
319
369
  return this.hasMany(Post, 'user_id');
@@ -324,9 +374,11 @@ const user = await User.find(1);
324
374
  const posts = await user.posts().get();
325
375
  ```
326
376
 
327
- #### Belongs To (belongsTo)
377
+ ### Belongs To (belongsTo)
328
378
 
329
379
  ```javascript
380
+ const { Model } = require('outlet-orm');
381
+
330
382
  class Post extends Model {
331
383
  author() {
332
384
  return this.belongsTo(User, 'user_id');
@@ -337,16 +389,18 @@ const post = await Post.find(1);
337
389
  const author = await post.author().get();
338
390
  ```
339
391
 
340
- #### Many to Many (belongsToMany)
392
+ ### Many to Many (belongsToMany)
341
393
 
342
394
  ```javascript
395
+ const { Model } = require('outlet-orm');
396
+
343
397
  class User extends Model {
344
398
  roles() {
345
399
  return this.belongsToMany(
346
400
  Role,
347
- 'user_roles', // Pivot table
348
- 'user_id', // Foreign key
349
- 'role_id' // Related key
401
+ 'user_roles', // Table pivot
402
+ 'user_id', // FK vers User
403
+ 'role_id' // FK vers Role
350
404
  );
351
405
  }
352
406
  }
@@ -354,76 +408,135 @@ class User extends Model {
354
408
  const user = await User.find(1);
355
409
  const roles = await user.roles().get();
356
410
 
357
- // belongsToMany helpers
358
- await user.roles().attach([1, 2]);
359
- await user.roles().detach(2);
360
- 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)
361
415
  ```
362
416
 
363
- #### Has Many Through (hasManyThrough)
417
+ ### Has Many Through (hasManyThrough)
364
418
 
365
- 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.
366
420
 
367
421
  ```javascript
368
- class User extends Model {
369
- posts() {
370
- return this.hasMany(Post, 'user_id');
371
- }
422
+ const { Model } = require('outlet-orm');
372
423
 
424
+ class User extends Model {
425
+ // User -> Post -> Comment
373
426
  comments() {
374
- // hasManyThrough(final, through, fkOnThrough?, throughKeyOnFinal?, localKey?, throughLocalKey?)
375
427
  return this.hasManyThrough(Comment, Post, 'user_id', 'post_id');
376
428
  }
377
429
  }
378
430
 
379
431
  const user = await User.find(1);
380
- const comments = await user.comments().get();
432
+ const allComments = await user.comments().get();
433
+ ```
434
+
435
+ ### Has One Through (hasOneThrough)
381
436
 
382
- // Eager load (avec contrainte):
383
- const users = await User.with({ comments: q => q.where('created_at', '>', new Date(Date.now() - 7*24*3600*1000)) }).get();
437
+ ```javascript
438
+ const { Model } = require('outlet-orm');
439
+
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();
384
449
  ```
385
450
 
386
- 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.
454
+
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
+ ```
387
490
 
388
- - foreignKeyOnThrough: `${parentTableSingular}_id`
389
- - throughKeyOnFinal: `${throughTableSingular}_id`
390
- - localKey: clé primaire du parent (par défaut `id`)
391
- - throughLocalKey: clé primaire du modèle intermédiaire (par défaut `id`)
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
392
495
 
393
496
  ### Eager Loading
394
497
 
395
498
  ```javascript
396
- // Charger les relations avec les résultats
397
- 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']);
398
514
 
399
515
  // Accéder aux relations chargées
400
516
  users.forEach(user => {
401
- console.log(user.getAttribute('name'));
402
517
  console.log(user.relations.posts);
403
518
  console.log(user.relations.profile);
404
519
  });
405
-
406
- // Chargement à la demande sur une instance existante
407
- const user = await User.find(1);
408
- await user.load('posts.comments', 'profile');
409
- // Ou tableau
410
- await user.load(['roles', 'permissions']);
411
520
  ```
412
521
 
522
+ ## 🎭 Attributs
523
+
413
524
  ### Casts
414
525
 
415
- Les casts permettent de convertir automatiquement les attributs:
526
+ Les casts convertissent automatiquement les attributs:
416
527
 
417
528
  ```javascript
529
+ const { Model } = require('outlet-orm');
530
+
418
531
  class User extends Model {
419
532
  static casts = {
420
- id: 'int',
533
+ id: 'int', // ou 'integer'
421
534
  age: 'integer',
422
- balance: 'float',
423
- email_verified: 'boolean',
424
- metadata: 'json',
425
- settings: 'array',
426
- 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
427
540
  };
428
541
  }
429
542
  ```
@@ -431,29 +544,28 @@ class User extends Model {
431
544
  ### Attributs cachés
432
545
 
433
546
  ```javascript
547
+ const { Model } = require('outlet-orm');
548
+
434
549
  class User extends Model {
435
550
  static hidden = ['password', 'secret_token'];
436
551
  }
437
552
 
438
553
  const user = await User.find(1);
439
- console.log(user.toJSON()); // password et secret_token ne sont pas inclus
554
+ console.log(user.toJSON()); // password et secret_token exclus
440
555
  ```
441
556
 
442
557
  #### Afficher les attributs cachés
443
558
 
444
- Parfois, vous devez inclure les attributs cachés dans les résultats, par exemple lors de l'authentification :
445
-
446
559
  ```javascript
447
- // Inclure les attributs cachés dans les résultats de la requête
560
+ // Inclure les attributs cachés
448
561
  const user = await User.withHidden().where('email', 'john@example.com').first();
449
- console.log(user.toJSON()); // password est inclus
562
+ console.log(user.toJSON()); // password inclus
450
563
 
451
- // Alternative : contrôler la visibilité avec un booléen
452
- const userWithPassword = await User.withoutHidden(true).where('email', 'john@example.com').first();
453
- // true = afficher les attributs cachés
454
- // false (défaut) = masquer les attributs cachés
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)
455
567
 
456
- // Utilisation typique pour l'authentification
568
+ // Cas d'usage: authentification
457
569
  const user = await User.withHidden().where('email', email).first();
458
570
  if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
459
571
  // Authentification réussie
@@ -463,61 +575,256 @@ if (user && await bcrypt.compare(password, user.getAttribute('password'))) {
463
575
  ### Timestamps
464
576
 
465
577
  ```javascript
466
- // 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)
467
581
  class User extends Model {
468
- static timestamps = true; // created_at et updated_at
582
+ static timestamps = true;
469
583
  }
470
584
 
471
- // Désactiver les timestamps
585
+ // Désactiver
472
586
  class Log extends Model {
473
587
  static timestamps = false;
474
588
  }
475
589
  ```
476
590
 
477
- ## 🔧 Configuration avancée
591
+ ## 🔄 Transactions
478
592
 
479
- ### Connexions multiples
593
+ Outlet ORM supporte les transactions pour garantir l'intégrité des données:
480
594
 
481
595
  ```javascript
482
- const mysqlDb = new DatabaseConnection({
483
- driver: 'mysql',
484
- host: 'localhost',
485
- database: 'app_db',
486
- user: 'root',
487
- password: 'secret'
488
- });
596
+ const { DatabaseConnection, Model } = require('outlet-orm');
489
597
 
490
- const postgresDb = new DatabaseConnection({
491
- driver: 'postgres',
492
- host: 'localhost',
493
- database: 'analytics_db',
494
- user: 'postgres',
495
- 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;
496
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
+ ```
497
617
 
498
- // Par modèle
499
- class User extends Model {
500
- 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
629
+ }
630
+
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é');
501
647
  }
502
648
 
503
- class Analytics extends Model {
504
- static connection = postgresDb;
649
+ // Restaurer
650
+ await post.restore();
651
+
652
+ // Supprimer définitivement
653
+ await post.forceDelete();
654
+ ```
655
+
656
+ ## 🔬 Scopes
657
+
658
+ ### Scopes Globaux
659
+
660
+ Appliqués automatiquement à toutes les requêtes:
661
+
662
+ ```javascript
663
+ const { Model } = require('outlet-orm');
664
+
665
+ class Post extends Model {
666
+ static table = 'posts';
505
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();
506
682
  ```
507
683
 
508
- ### Clé primaire personnalisée
684
+ ## 📣 Events / Hooks
685
+
686
+ Interceptez les opérations sur vos modèles:
509
687
 
510
688
  ```javascript
689
+ const { Model } = require('outlet-orm');
690
+
511
691
  class User extends Model {
512
- static primaryKey = 'user_id';
692
+ static table = 'users';
513
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) => {});
514
737
  ```
515
738
 
516
- ### Nom de table personnalisé
739
+ ## Validation
740
+
741
+ Validation basique intégrée:
517
742
 
518
743
  ```javascript
744
+ const { Model } = require('outlet-orm');
745
+
519
746
  class User extends Model {
520
- 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');
521
828
  }
522
829
  ```
523
830
 
@@ -525,119 +832,167 @@ class User extends Model {
525
832
 
526
833
  ### DatabaseConnection
527
834
 
528
- - `new DatabaseConnection(config?)` lit automatiquement `.env` si `config` est omis
529
- - `connect()` — établit la connexion (appelé automatiquement au besoin)
530
- - `select(table, query)` exécute un SELECT (utilisé par le Query Builder)
531
- - `insert(table, data)` / `insertMany(table, data[])`
532
- - `update(table, data, query)` / `delete(table, query)`
533
- - `count(table, query)` retourne le total
534
- - `executeRawQuery(sql, params?)` résultats normalisés (tableau d’objets)
535
- - `execute(sql, params?)` résultats natifs du driver (utile pour migrations)
536
- - `increment(table, column, query, amount?)` mise à jour atomique
537
- - `decrement(table, column, query, amount?)`
538
- - `close()` / `disconnect()` fermer la connexion
539
-
540
- ### Model
541
-
542
- - `static all()` - Récupérer tous les enregistrements
543
- - `static find(id)` - Trouver par ID
544
- - `static findOrFail(id)` - Trouver ou lancer une erreur
545
- - `static where(column, operator, value)` - Ajouter une clause where
546
- - `static create(attributes)` - Créer et sauvegarder
547
- - `static insert(data)` - Insérer des données brutes
548
- - `static update(attributes)` - Mise à jour bulk
549
- - `static updateAndFetchById(id, attributes, relations?)` - Mise à jour par ID et retour du modèle (avec include)
550
- - `static updateById(id, attributes)` - Mise à jour par ID
551
- - `static delete()` - Suppression bulk
552
- - `static withHidden()` - Inclure les attributs cachés dans les résultats
553
- - `static withoutHidden(show?)` - Contrôler la visibilité des attributs cachés (false = masquer, true = afficher)
554
- - `save()` - Sauvegarder l'instance
555
- - `destroy()` - Supprimer l'instance
556
- - `toJSON()` - Convertir en JSON
557
- - `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 |
558
926
 
559
927
  ### QueryBuilder
560
928
 
561
- - `select(...columns)` - Sélectionner des colonnes
562
- - `where(column, operator, value)` - Clause WHERE
563
- - `whereIn(column, values)` - Clause WHERE IN
564
- - `whereNull(column)` - Clause WHERE NULL
565
- - `whereNotNull(column)` - Clause WHERE NOT NULL
566
- - `orWhere(column, operator, value)` - Clause OR WHERE
567
- - `orderBy(column, direction)` - Ordonner les résultats
568
- - `limit(value)` - Limiter les résultats
569
- - `offset(value)` - Décaler les résultats
570
- - `with(...relations)` - Eager loading
571
- - `get()` - Exécuter et récupérer
572
- - `first()` - Premier résultat
573
- - `paginate(page, perPage)` - Paginer les résultats
574
- - `count()` - Compter les résultats
575
- - `exists()` - Vérifier l’existence
576
- - `whereBetween(column, [min, max])` - Intervalle
577
- - `whereLike(column, pattern)` - LIKE
578
- - `whereHas(relation, cb?)` - Filtrer par relation (INNER JOIN)
579
- - `has(relation, opOrCount, [count])` - Existence relationnelle (GROUP BY/HAVING)
580
- - `whereDoesntHave(relation)` - Absence de relation (LEFT JOIN IS NULL)
581
- - `join(table, first, [operator], second)` - INNER JOIN
582
- - `leftJoin(table, first, [operator], second)` - LEFT JOIN
583
- - `withCount(relations)` - Ajoute {relation}_count via sous-requête
584
- - `distinct()` - SELECT DISTINCT
585
- - `groupBy(...cols)` - GROUP BY
586
- - `having(column, operator, value)` - HAVING
587
- - `insert(data)` - Insérer des données (array => insertMany)
588
- - `update(attributes)` - Mise à jour bulk
589
- - `updateAndFetch(attributes, relations?)` - Mise à jour + premier enregistrement (avec include)
590
- - `delete()` - Suppression bulk
591
- - `increment(column, amount?)` - Incrément atomique
592
- - `decrement(column, amount?)` - Décrément atomique
593
- - `columns([...])` - Alias de `select(...cols)`
594
- - `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 |
595
970
 
596
971
  ## 🛠️ Outils CLI
597
972
 
598
- ### 1. Initialisation d'un projet
973
+ ### outlet-init
974
+
975
+ Initialise un nouveau projet avec configuration de base de données.
599
976
 
600
977
  ```bash
601
978
  outlet-init
602
979
  ```
603
980
 
604
- Crée un nouveau projet avec configuration de base de données, modèle exemple et fichier d'utilisation.
605
-
606
- 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
607
986
 
608
- 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
609
988
 
610
- ### 2. Système de Migrations
989
+ Système complet de migrations.
611
990
 
612
991
  ```bash
613
992
  # Créer une migration
614
993
  outlet-migrate make create_users_table
615
994
 
616
995
  # Exécuter les migrations
617
- outlet-migrate
618
- # Option 1: migrate
619
-
620
- # Rollback dernière migration
621
- outlet-migrate
622
- # Option 2: rollback
623
-
624
- # Voir le statut
625
- outlet-migrate
626
- # Option 6: status
627
-
628
- # Reset toutes les migrations
629
- outlet-migrate
630
- # Option 3: reset
631
-
632
- # Refresh (reset + migrate)
633
- outlet-migrate
634
- # Option 4: refresh
635
-
636
- # Fresh (drop all + migrate)
637
- outlet-migrate
638
- # Option 5: fresh
639
- # Exécuter les migrations en se basant sur .env si database/config.js est absent
640
- # (DB_DRIVER, DB_HOST, DB_DATABASE, etc.)
641
996
  outlet-migrate migrate
642
997
 
643
998
  # Voir le statut
@@ -646,84 +1001,66 @@ outlet-migrate status
646
1001
  # Annuler la dernière migration
647
1002
  outlet-migrate rollback --steps 1
648
1003
 
649
- # Astuce: si le fichier database/config.js existe, il est prioritaire sur .env
650
- ```
1004
+ # Reset toutes les migrations
1005
+ outlet-migrate reset --yes
651
1006
 
652
- **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
+ ```
653
1013
 
654
- - ✅ **Création et gestion des migrations** (create, alter, drop tables)
655
- - ✅ **Types de colonnes** : id, string, text, integer, boolean, date, datetime, timestamp, decimal, float, json, enum, uuid, foreignId
656
- - ✅ **Modificateurs** : nullable, default, unique, index, unsigned, autoIncrement, comment, after, first
657
- - ✅ **Clés étrangères** : foreign(), constrained(), onDelete(), onUpdate(), CASCADE
658
- - ✅ **Index** : index(), unique(), fullText()
659
- - ✅ **Manipulation de colonnes** : renameColumn(), dropColumn(), dropTimestamps()
660
- - ✅ **Migrations réversibles** : Méthodes up() et down()
661
- - ✅ **Batch tracking** : Rollback précis par batch
662
- - ✅ **SQL personnalisé** : execute() pour commandes avancées
663
- - ✅ **Multi-DB** : Support MySQL, PostgreSQL, SQLite
1014
+ **Fonctionnalités des Migrations:**
664
1015
 
665
- **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
666
1025
 
667
- - [MIGRATIONS.md](docs/MIGRATIONS.md) - Guide complet des migrations
1026
+ ### outlet-convert
668
1027
 
669
- ### 3. Conversion SQL vers ORM
1028
+ Convertit des schémas SQL en modèles ORM.
670
1029
 
671
1030
  ```bash
672
1031
  outlet-convert
673
1032
  ```
674
1033
 
675
- Convertit automatiquement des schémas SQL en modèles ORM :
676
-
677
- #### Option 1 : Depuis un fichier SQL local
678
-
679
- - Parsez des fichiers `.sql` contenant des instructions `CREATE TABLE`
680
- - Génère automatiquement les modèles avec relations, casts, fillable, hidden
681
-
682
- #### Option 2 : Depuis une base de données connectée
683
-
684
- - Connectez-vous à MySQL, PostgreSQL ou SQLite
685
- - Liste toutes les tables et génère les modèles correspondants
686
- - Détecte automatiquement les relations et types de données
687
-
688
- **Fonctionnalités de conversion :**
1034
+ **Options:**
1035
+ 1. Depuis un fichier SQL local
1036
+ 2. Depuis une base de données connectée
689
1037
 
1038
+ **Fonctionnalités:**
690
1039
  - ✅ Détection automatique des types et casts
691
- - ✅ **Génération automatique de TOUTES les relations** :
692
- - `belongsTo` : Détecté via clés étrangères
693
- - `hasMany` : Généré automatiquement comme inverse de `belongsTo`
694
- - `hasOne` : Détecté via clés étrangères UNIQUE
695
- - `belongsToMany` : Détecté via tables pivot
1040
+ - ✅ Génération automatique de TOUTES les relations (belongsTo, hasMany, hasOne, belongsToMany)
696
1041
  - ✅ Relations récursives (auto-relations)
697
1042
  - ✅ Détection des champs sensibles (password, token, etc.)
698
1043
  - ✅ Support des timestamps automatiques
699
- - ✅ Conversion des noms de tables en classes PascalCase
700
-
701
- ### 4. Utilisation non-interactive (CI/CD)
1044
+ - ✅ Conversion des noms en PascalCase
702
1045
 
703
- Les commandes de migration supportent un mode non-interactif pratique pour l’automatisation:
1046
+ ## 📚 Documentation
704
1047
 
705
- ```bash
706
- # Exécuter les migrations en lisant la config depuis .env
707
- outlet-migrate migrate
708
-
709
- # Voir le statut
710
- outlet-migrate status
711
-
712
- # Annuler N étapes
713
- outlet-migrate rollback --steps 1
714
- ```
715
-
716
- Astuce: si `database/config.js` est présent, il a priorité sur `.env`.
717
-
718
- **Documentation complète :**
719
-
720
- - [SQL_CONVERSION.md](docs/SQL_CONVERSION.md) - Guide de conversion
721
- - [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)
722
1053
 
723
1054
  ## 🤝 Contribution
724
1055
 
725
1056
  Les contributions sont les bienvenues! N'hésitez pas à ouvrir une issue ou un pull request.
726
1057
 
1058
+ Voir [CONTRIBUTING.md](CONTRIBUTING.md) pour les guidelines.
1059
+
727
1060
  ## 📄 Licence
728
1061
 
729
- MIT
1062
+ MIT - Voir [LICENSE](LICENSE) pour plus de détails.
1063
+
1064
+ ---
1065
+
1066
+ Créé par [omgbwa-yasse](https://github.com/omgbwa-yasse)