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