outlet-orm 3.2.0 → 4.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
@@ -28,6 +28,35 @@ Outlet ORM utilise des peerDependencies optionnelles pour les drivers de base de
28
28
 
29
29
  Si aucun driver n'est installé, un message d'erreur explicite vous indiquera lequel installer lors de la connexion.
30
30
 
31
+ ## 📁 Structure de Projet Recommandée
32
+
33
+ Organisez votre projet utilisant Outlet ORM comme suit :
34
+
35
+ ```
36
+ mon-projet/
37
+ ├── .env # Configuration de la base de données
38
+ ├── package.json
39
+ ├── database/
40
+ │ ├── config.js # Config migrations (généré par outlet-init)
41
+ │ └── migrations/ # Vos fichiers de migration
42
+ │ ├── 20240101_create_users_table.js
43
+ │ └── 20240102_create_posts_table.js
44
+ ├── models/ # Vos classes Model
45
+ │ ├── User.js
46
+ │ ├── Post.js
47
+ │ └── Comment.js
48
+ ├── src/ # Votre code applicatif
49
+ │ └── index.js
50
+ └── tests/ # Vos tests
51
+ └── models.test.js
52
+ ```
53
+
54
+ | Dossier | Rôle | Créé par |
55
+ |---------|------|----------|
56
+ | `database/config.js` | Configuration des migrations | `outlet-init` |
57
+ | `database/migrations/` | Fichiers de migration | `outlet-migrate make` |
58
+ | `models/` | Vos classes Model | Vous (recommandé) |
59
+
31
60
  ## ✨ Fonctionnalités clés
32
61
 
33
62
  - **API inspirée d'Eloquent** (Active Record) pour un usage fluide
@@ -56,7 +85,7 @@ Si aucun driver n'est installé, un message d'erreur explicite vous indiquera le
56
85
  - **CLI pratiques**: `outlet-init`, `outlet-migrate`, `outlet-convert`
57
86
  - **Configuration via `.env`** (chargée automatiquement)
58
87
  - **Multi-base de données**: MySQL, PostgreSQL et SQLite
59
- - **Types TypeScript** fournis
88
+ - **Types TypeScript complets** avec Generic Model et Schema Builder typé (v4.0.0+)
60
89
 
61
90
  ## ⚡ Démarrage Rapide
62
91
 
@@ -1070,6 +1099,64 @@ outlet-convert
1070
1099
  - [Détection des Relations](docs/RELATIONS_DETECTION.md)
1071
1100
  - [Guide de démarrage rapide](docs/QUICKSTART.md)
1072
1101
  - [Architecture](docs/ARCHITECTURE.md)
1102
+ - [**TypeScript (complet)**](docs/TYPESCRIPT.md)
1103
+
1104
+ ## 📘 TypeScript Support
1105
+
1106
+ Outlet ORM v4.0.0 inclut des définitions TypeScript complètes avec support des **generics pour les attributs typés**.
1107
+
1108
+ ### Modèles typés
1109
+
1110
+ ```typescript
1111
+ import { Model, HasManyRelation } from 'outlet-orm';
1112
+
1113
+ interface UserAttributes {
1114
+ id: number;
1115
+ name: string;
1116
+ email: string;
1117
+ role: 'admin' | 'user';
1118
+ created_at: Date;
1119
+ }
1120
+
1121
+ class User extends Model<UserAttributes> {
1122
+ static table = 'users';
1123
+ static fillable = ['name', 'email', 'role'];
1124
+
1125
+ posts(): HasManyRelation<Post> {
1126
+ return this.hasMany(Post, 'user_id');
1127
+ }
1128
+ }
1129
+
1130
+ // Type-safe getAttribute/setAttribute
1131
+ const user = await User.find(1);
1132
+ const name: string = user.getAttribute('name'); // ✅ Type inféré
1133
+ const role: 'admin' | 'user' = user.getAttribute('role');
1134
+ ```
1135
+
1136
+ ### Migrations typées
1137
+
1138
+ ```typescript
1139
+ import { MigrationInterface, Schema, TableBuilder } from 'outlet-orm';
1140
+
1141
+ export const migration: MigrationInterface = {
1142
+ name: 'create_users_table',
1143
+
1144
+ async up(): Promise<void> {
1145
+ await Schema.create('users', (table: TableBuilder) => {
1146
+ table.id();
1147
+ table.string('name');
1148
+ table.string('email').unique();
1149
+ table.timestamps();
1150
+ });
1151
+ },
1152
+
1153
+ async down(): Promise<void> {
1154
+ await Schema.dropIfExists('users');
1155
+ }
1156
+ };
1157
+ ```
1158
+
1159
+ 📖 [Guide TypeScript complet](docs/TYPESCRIPT.md)
1073
1160
 
1074
1161
  ## 🤝 Contribution
1075
1162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "outlet-orm",
3
- "version": "3.2.0",
3
+ "version": "4.1.0",
4
4
  "description": "A Laravel Eloquent-inspired ORM for Node.js with support for MySQL, PostgreSQL, and SQLite",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -66,8 +66,10 @@
66
66
  "sqlite3": { "optional": true }
67
67
  },
68
68
  "devDependencies": {
69
+ "@types/node": "^20.10.0",
69
70
  "eslint": "^8.50.0",
70
- "jest": "^29.7.0"
71
+ "jest": "^29.7.0",
72
+ "typescript": "^5.3.0"
71
73
  },
72
74
  "engines": {
73
75
  "node": ">=18.0.0"
package/src/Model.js CHANGED
@@ -24,7 +24,7 @@ class Model {
24
24
  creating: [],
25
25
  created: [],
26
26
  updating: [],
27
- updating: [],
27
+ updated: [],
28
28
  saving: [],
29
29
  saved: [],
30
30
  deleting: [],
package/types/index.d.ts CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  declare module 'outlet-orm' {
4
4
 
5
+ // ==================== Type Utilities ====================
6
+
7
+ /** Event names for Model lifecycle hooks */
8
+ export type ModelEventName =
9
+ | 'creating' | 'created'
10
+ | 'updating' | 'updated'
11
+ | 'saving' | 'saved'
12
+ | 'deleting' | 'deleted'
13
+ | 'restoring' | 'restored';
14
+
15
+ /** SQL WHERE operators */
16
+ export type WhereOperator =
17
+ | '=' | '!=' | '<>'
18
+ | '>' | '>=' | '<' | '<='
19
+ | 'LIKE' | 'NOT LIKE' | 'like' | 'not like'
20
+ | 'IN' | 'NOT IN' | 'in' | 'not in'
21
+ | 'BETWEEN' | 'NOT BETWEEN'
22
+ | 'IS NULL' | 'IS NOT NULL';
23
+
24
+ /** Base attributes every model has */
25
+ export interface BaseModelAttributes {
26
+ id?: number | string;
27
+ created_at?: Date | string;
28
+ updated_at?: Date | string;
29
+ deleted_at?: Date | string | null;
30
+ }
31
+
32
+ /** Insert operation result */
33
+ export interface InsertResult {
34
+ insertId: number | string;
35
+ affectedRows: number;
36
+ }
37
+
38
+ /** Update operation result */
39
+ export interface UpdateResult {
40
+ affectedRows: number;
41
+ changedRows?: number;
42
+ }
43
+
44
+ /** Delete operation result */
45
+ export interface DeleteResult {
46
+ affectedRows: number;
47
+ }
48
+
5
49
  // ==================== Database Connection ====================
6
50
 
7
51
  export interface DatabaseConfig {
@@ -39,18 +83,18 @@ declare module 'outlet-orm' {
39
83
  static isLogging(): boolean;
40
84
 
41
85
  select(table: string, query: QueryObject): Promise<any[]>;
42
- insert(table: string, data: Record<string, any>): Promise<{ insertId: any; affectedRows: number }>;
86
+ insert(table: string, data: Record<string, any>): Promise<InsertResult>;
43
87
  insertMany(table: string, data: Record<string, any>[]): Promise<{ affectedRows: number }>;
44
- update(table: string, data: Record<string, any>, query: QueryObject): Promise<{ affectedRows: number }>;
45
- delete(table: string, query: QueryObject): Promise<{ affectedRows: number }>;
88
+ update(table: string, data: Record<string, any>, query: QueryObject): Promise<UpdateResult>;
89
+ delete(table: string, query: QueryObject): Promise<DeleteResult>;
46
90
  count(table: string, query: QueryObject): Promise<number>;
47
91
  executeRawQuery(sql: string, params?: any[]): Promise<any[]>;
48
92
  /** Execute raw SQL and return driver-native results (used by migrations) */
49
93
  execute(sql: string, params?: any[]): Promise<any>;
50
94
  /** Atomically increment a column respecting query wheres */
51
- increment(table: string, column: string, query: QueryObject, amount?: number): Promise<{ affectedRows: number }>;
95
+ increment(table: string, column: string, query: QueryObject, amount?: number): Promise<UpdateResult>;
52
96
  /** Atomically decrement a column respecting query wheres */
53
- decrement(table: string, column: string, query: QueryObject, amount?: number): Promise<{ affectedRows: number }>;
97
+ decrement(table: string, column: string, query: QueryObject, amount?: number): Promise<UpdateResult>;
54
98
  close(): Promise<void>;
55
99
  /** Backwards-compatible alias used by CLI */
56
100
  disconnect(): Promise<void>;
@@ -72,7 +116,7 @@ declare module 'outlet-orm' {
72
116
 
73
117
  export interface WhereClause {
74
118
  column?: string;
75
- operator?: string;
119
+ operator?: WhereOperator | string;
76
120
  value?: any;
77
121
  values?: any[];
78
122
  type: 'basic' | 'in' | 'notIn' | 'null' | 'notNull' | 'between' | 'like';
@@ -178,7 +222,32 @@ declare module 'outlet-orm' {
178
222
 
179
223
  export type CastType = 'int' | 'integer' | 'float' | 'double' | 'string' | 'bool' | 'boolean' | 'array' | 'json' | 'date' | 'datetime' | 'timestamp';
180
224
 
181
- export type ValidationRule = 'required' | 'string' | 'number' | 'numeric' | 'email' | 'boolean' | 'date' | `min:${number}` | `max:${number}` | `in:${string}` | `regex:${string}`;
225
+ /** Individual validation rule types */
226
+ export type ValidationRule =
227
+ | 'required'
228
+ | 'string'
229
+ | 'number'
230
+ | 'numeric'
231
+ | 'integer'
232
+ | 'email'
233
+ | 'url'
234
+ | 'boolean'
235
+ | 'date'
236
+ | 'array'
237
+ | 'object'
238
+ | 'nullable'
239
+ | `min:${number}`
240
+ | `max:${number}`
241
+ | `between:${number},${number}`
242
+ | `in:${string}`
243
+ | `not_in:${string}`
244
+ | `regex:${string}`
245
+ | `size:${number}`
246
+ | `digits:${number}`
247
+ | `digits_between:${number},${number}`;
248
+
249
+ /** Validation rule as pipe-separated string */
250
+ export type ValidationRuleString = string;
182
251
 
183
252
  export interface ValidationResult {
184
253
  valid: boolean;
@@ -187,7 +256,11 @@ declare module 'outlet-orm' {
187
256
 
188
257
  export type EventCallback<T extends Model = Model> = (model: T) => boolean | void | Promise<boolean | void>;
189
258
 
190
- export class Model {
259
+ /**
260
+ * Base Model class with optional generic for typed attributes
261
+ * @template TAttributes - Type of model attributes (defaults to Record<string, any>)
262
+ */
263
+ export class Model<TAttributes extends Record<string, any> = Record<string, any>> {
191
264
  static table: string;
192
265
  static primaryKey: string;
193
266
  static timestamps: boolean;
@@ -204,21 +277,25 @@ declare module 'outlet-orm' {
204
277
  static globalScopes: Record<string, (qb: QueryBuilder<any>) => void>;
205
278
 
206
279
  // Events
207
- static eventListeners: Record<string, EventCallback[]>;
280
+ static eventListeners: Record<ModelEventName, EventCallback[]>;
208
281
 
209
282
  // Validation
210
- static rules: Record<string, string | ValidationRule[]>;
283
+ static rules: Record<string, ValidationRuleString | ValidationRule[]>;
211
284
 
212
- attributes: Record<string, any>;
213
- original: Record<string, any>;
285
+ /** Model attributes with optional typing */
286
+ attributes: TAttributes;
287
+ /** Original attributes before modifications */
288
+ original: TAttributes;
289
+ /** Loaded relations */
214
290
  relations: Record<string, any>;
291
+ /** Whether the model exists in database */
215
292
  exists: boolean;
216
293
 
217
- constructor(attributes?: Record<string, any>);
294
+ constructor(attributes?: Partial<TAttributes>);
218
295
 
219
- // Event registration
220
- static on(event: string, callback: EventCallback): void;
221
- static fireEvent(event: string, model: Model): Promise<boolean>;
296
+ // Event registration with typed event names
297
+ static on(event: ModelEventName, callback: EventCallback): void;
298
+ static fireEvent(event: ModelEventName, model: Model): Promise<boolean>;
222
299
  static creating(callback: EventCallback): void;
223
300
  static created(callback: EventCallback): void;
224
301
  static updating(callback: EventCallback): void;
@@ -273,16 +350,21 @@ declare module 'outlet-orm' {
273
350
  /** Control visibility of hidden attributes (false = hide, true = show) */
274
351
  static withoutHidden<T extends Model>(this: new () => T, show?: boolean): QueryBuilder<T>;
275
352
 
276
- // Instance methods
277
- fill(attributes: Record<string, any>): this;
353
+ // Instance methods with typed attributes
354
+ /** Fill model with attributes */
355
+ fill(attributes: Partial<TAttributes>): this;
356
+ /** Set a single attribute with type safety */
357
+ setAttribute<K extends keyof TAttributes>(key: K, value: TAttributes[K]): this;
278
358
  setAttribute(key: string, value: any): this;
359
+ /** Get a single attribute with type safety */
360
+ getAttribute<K extends keyof TAttributes>(key: K): TAttributes[K];
279
361
  getAttribute(key: string): any;
280
362
  castAttribute(key: string, value: any): any;
281
363
  save(): Promise<this>;
282
364
  destroy(): Promise<boolean>;
283
- getDirty(): Record<string, any>;
365
+ getDirty(): Partial<TAttributes>;
284
366
  isDirty(): boolean;
285
- toJSON(): Record<string, any>;
367
+ toJSON(): TAttributes;
286
368
  /** Load relations on an existing instance. Supports dot-notation and arrays. */
287
369
  load(...relations: string[] | [string[]]): Promise<this>;
288
370
 
@@ -315,6 +397,21 @@ declare module 'outlet-orm' {
315
397
  localKey?: string,
316
398
  throughLocalKey?: string
317
399
  ): HasManyThroughRelation<T>;
400
+ /** Has one through intermediate model */
401
+ hasOneThrough<T extends Model>(
402
+ relatedFinal: new () => T,
403
+ through: new () => Model,
404
+ foreignKeyOnThrough?: string,
405
+ throughKeyOnFinal?: string,
406
+ localKey?: string,
407
+ throughLocalKey?: string
408
+ ): HasOneThroughRelation<T>;
409
+ /** Polymorphic one-to-one relation */
410
+ morphOne<T extends Model>(related: new () => T, name: string, typeColumn?: string, idColumn?: string, localKey?: string): MorphOneRelation<T>;
411
+ /** Polymorphic one-to-many relation */
412
+ morphMany<T extends Model>(related: new () => T, name: string, typeColumn?: string, idColumn?: string, localKey?: string): MorphManyRelation<T>;
413
+ /** Inverse polymorphic relation */
414
+ morphTo<T extends Model>(): MorphToRelation<T>;
318
415
  }
319
416
 
320
417
  // ==================== Relations ====================
@@ -379,4 +476,185 @@ declare module 'outlet-orm' {
379
476
  get(): Promise<T | null>;
380
477
  eagerLoad(models: Model[], relationName: string, constraint?: (qb: QueryBuilder<T>) => void): Promise<void>;
381
478
  }
479
+
480
+ // ==================== Schema Builder (Migrations) ====================
481
+
482
+ /** Foreign key action types */
483
+ export type ForeignKeyAction = 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT';
484
+
485
+ /** Schema builder for database migrations */
486
+ export interface SchemaBuilder {
487
+ /** Create a new table */
488
+ createTable(name: string, callback: (table: TableBuilder) => void): Promise<void>;
489
+ /** Drop a table */
490
+ dropTable(name: string): Promise<void>;
491
+ /** Drop a table if it exists */
492
+ dropTableIfExists(name: string): Promise<void>;
493
+ /** Rename a table */
494
+ renameTable(from: string, to: string): Promise<void>;
495
+ /** Check if table exists */
496
+ hasTable(name: string): Promise<boolean>;
497
+ /** Modify an existing table */
498
+ table(name: string, callback: (table: TableBuilder) => void): Promise<void>;
499
+ /** Check if column exists */
500
+ hasColumn(table: string, column: string): Promise<boolean>;
501
+ }
502
+
503
+ /** Table builder for creating/modifying tables */
504
+ export interface TableBuilder {
505
+ // Primary keys
506
+ /** Auto-incrementing primary key */
507
+ id(name?: string): ColumnBuilder;
508
+ /** Big integer auto-incrementing primary key */
509
+ bigIncrements(name?: string): ColumnBuilder;
510
+ /** UUID primary key */
511
+ uuid(name?: string): ColumnBuilder;
512
+
513
+ // Numeric types
514
+ /** Integer column */
515
+ integer(name: string): ColumnBuilder;
516
+ /** Big integer column */
517
+ bigInteger(name: string): ColumnBuilder;
518
+ /** Small integer column */
519
+ smallInteger(name: string): ColumnBuilder;
520
+ /** Tiny integer column */
521
+ tinyInteger(name: string): ColumnBuilder;
522
+ /** Decimal column */
523
+ decimal(name: string, precision?: number, scale?: number): ColumnBuilder;
524
+ /** Float column */
525
+ float(name: string, precision?: number, scale?: number): ColumnBuilder;
526
+ /** Double column */
527
+ double(name: string, precision?: number, scale?: number): ColumnBuilder;
528
+
529
+ // String types
530
+ /** String/VARCHAR column */
531
+ string(name: string, length?: number): ColumnBuilder;
532
+ /** Text column */
533
+ text(name: string): ColumnBuilder;
534
+ /** Medium text column */
535
+ mediumText(name: string): ColumnBuilder;
536
+ /** Long text column */
537
+ longText(name: string): ColumnBuilder;
538
+ /** Char column */
539
+ char(name: string, length?: number): ColumnBuilder;
540
+
541
+ // Boolean
542
+ /** Boolean column */
543
+ boolean(name: string): ColumnBuilder;
544
+
545
+ // Date/Time
546
+ /** Date column */
547
+ date(name: string): ColumnBuilder;
548
+ /** DateTime column */
549
+ dateTime(name: string): ColumnBuilder;
550
+ /** Timestamp column */
551
+ timestamp(name: string): ColumnBuilder;
552
+ /** Time column */
553
+ time(name: string): ColumnBuilder;
554
+ /** Year column */
555
+ year(name: string): ColumnBuilder;
556
+ /** Add created_at and updated_at columns */
557
+ timestamps(): void;
558
+ /** Add nullable deleted_at column for soft deletes */
559
+ softDeletes(): void;
560
+
561
+ // Binary
562
+ /** Binary/Blob column */
563
+ binary(name: string): ColumnBuilder;
564
+ /** Blob column */
565
+ blob(name: string): ColumnBuilder;
566
+
567
+ // JSON
568
+ /** JSON column */
569
+ json(name: string): ColumnBuilder;
570
+ /** JSONB column (PostgreSQL) */
571
+ jsonb(name: string): ColumnBuilder;
572
+
573
+ // Enum
574
+ /** Enum column */
575
+ enum(name: string, values: string[]): ColumnBuilder;
576
+
577
+ // Indexes
578
+ /** Add primary key constraint */
579
+ primary(columns: string | string[]): void;
580
+ /** Add unique constraint */
581
+ unique(columns: string | string[]): void;
582
+ /** Add index */
583
+ index(columns: string | string[], name?: string): void;
584
+ /** Add foreign key */
585
+ foreign(column: string): ForeignKeyBuilder;
586
+
587
+ // Modifications
588
+ /** Drop a column */
589
+ dropColumn(name: string | string[]): void;
590
+ /** Rename a column */
591
+ renameColumn(from: string, to: string): void;
592
+ /** Drop an index */
593
+ dropIndex(name: string): void;
594
+ /** Drop a unique constraint */
595
+ dropUnique(name: string): void;
596
+ /** Drop a foreign key */
597
+ dropForeign(name: string): void;
598
+ }
599
+
600
+ /** Column builder for column definitions */
601
+ export interface ColumnBuilder {
602
+ /** Make column nullable */
603
+ nullable(): this;
604
+ /** Set default value */
605
+ default(value: any): this;
606
+ /** Make column unsigned (numeric only) */
607
+ unsigned(): this;
608
+ /** Add unique constraint */
609
+ unique(): this;
610
+ /** Make column primary key */
611
+ primary(): this;
612
+ /** Add index */
613
+ index(): this;
614
+ /** Add auto increment */
615
+ autoIncrement(): this;
616
+ /** Add comment */
617
+ comment(text: string): this;
618
+ /** Add after clause (MySQL) */
619
+ after(column: string): this;
620
+ /** Add first clause (MySQL) */
621
+ first(): this;
622
+ /** Set character set */
623
+ charset(charset: string): this;
624
+ /** Set collation */
625
+ collation(collation: string): this;
626
+ /** Add references for foreign key */
627
+ references(column: string): ForeignKeyBuilder;
628
+ }
629
+
630
+ /** Foreign key builder */
631
+ export interface ForeignKeyBuilder {
632
+ /** Referenced table */
633
+ on(table: string): this;
634
+ /** Referenced column */
635
+ references(column: string): this;
636
+ /** On delete action */
637
+ onDelete(action: ForeignKeyAction): this;
638
+ /** On update action */
639
+ onUpdate(action: ForeignKeyAction): this;
640
+ }
641
+
642
+ /** Migration interface for typed migrations */
643
+ export interface MigrationInterface {
644
+ /** Run the migration */
645
+ up(schema: SchemaBuilder): Promise<void>;
646
+ /** Reverse the migration */
647
+ down(schema: SchemaBuilder): Promise<void>;
648
+ }
649
+
650
+ /** Schema class export */
651
+ export class Schema {
652
+ static create(name: string, callback: (table: TableBuilder) => void): Promise<void>;
653
+ static drop(name: string): Promise<void>;
654
+ static dropIfExists(name: string): Promise<void>;
655
+ static table(name: string, callback: (table: TableBuilder) => void): Promise<void>;
656
+ static rename(from: string, to: string): Promise<void>;
657
+ static hasTable(name: string): Promise<boolean>;
658
+ static hasColumn(table: string, column: string): Promise<boolean>;
659
+ }
382
660
  }