ilana-orm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,4763 @@
1
+ <div align="center">
2
+ <img src="ilana.png" alt="IlanaORM Logo" width="200" height="200">
3
+ <h1>IlanaORM</h1>
4
+ </div>
5
+
6
+ **Ìlànà** (pronounced "ee-LAH-nah") - A Yoruba word meaning "pattern," "system," or "protocol."
7
+
8
+ A fully-featured, Eloquent-style ORM for Node.js with automatic TypeScript support. IlanaORM provides complete feature parity with Laravel's Eloquent ORM, following established patterns and protocols for database interaction, modeling, querying, relationships, events, casting, migrations, and more.
9
+
10
+ ## Table of Contents
11
+
12
+ - [Features](#features)
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [Configuration](#configuration)
16
+ - [CLI Commands](#cli-commands)
17
+ - [Models](#models)
18
+ - [Query Builder](#query-builder)
19
+ - [Relationships](#relationships)
20
+ - [Migrations](#migrations)
21
+ - [Seeders](#seeders)
22
+ - [Model Factories](#model-factories)
23
+ - [Database Connection](#database-connection)
24
+ - [Schema Builder](#schema-builder)
25
+ - [Transactions](#transactions)
26
+ - [Advanced Features](#advanced-features)
27
+ - [Complete API Reference](#complete-api-reference)
28
+ - [TypeScript Support](#typescript-support)
29
+ - [Performance & Best Practices](#performance--best-practices)
30
+ - [Testing](#testing)
31
+
32
+ ## Features
33
+
34
+ ### 🏗️ **Active Record Pattern**
35
+
36
+ - Full CRUD operations with intuitive, chainable API
37
+ - Model-based database interactions
38
+ - Automatic table mapping and attribute handling
39
+ - Built-in validation and mass assignment protection
40
+ - Direct property access for model attributes
41
+
42
+ ### 🔗 **Advanced Relationships**
43
+
44
+ - **One-to-One**: `hasOne()`, `belongsTo()`
45
+ - **One-to-Many**: `hasMany()`, `belongsTo()`
46
+ - **Many-to-Many**: `belongsToMany()` with pivot tables and timestamps
47
+ - **Polymorphic**: `morphTo()`, `morphMany()` with model registry
48
+ - **Has-Many-Through**: Complex nested relationships
49
+ - **Eager Loading**: Prevent N+1 queries with `with()` and constraints
50
+ - **Lazy Loading**: Load relations on-demand with `load()`
51
+ - **String Model References**: Use string references to avoid circular dependencies
52
+
53
+ ### 🔍 **Fluent Query Builder**
54
+
55
+ - Chainable methods for complex queries
56
+ - Raw SQL support when needed
57
+ - Subqueries and joins with multiple database support
58
+ - Aggregation functions (count, sum, avg, min, max)
59
+ - Conditional queries with `when()`
60
+ - Query scopes with automatic proxy support
61
+ - JSON queries for PostgreSQL and MySQL
62
+ - Date-specific queries (whereDate, whereMonth, whereYear)
63
+
64
+ ### 🗄️ **Database Management**
65
+
66
+ - **Migrations**: Version control with rollback, fresh, and status commands
67
+ - **Schema Builder**: Create, modify, drop tables and columns
68
+ - **Seeders**: Populate database with test/initial data
69
+ - **Multiple Connections**: Support for multiple databases with connection-specific operations
70
+ - **Database Timezone Support**: Configurable timezone handling
71
+ - **Laravel-Style Transactions**: Automatic retry, seamless model integration, and connection support
72
+
73
+ ### 🏭 **Model Factories**
74
+
75
+ - Generate realistic test data with Faker.js integration
76
+ - Model states for different scenarios
77
+ - Relationship factories for complex data structures
78
+ - TypeScript-aware factory generation
79
+
80
+ ### ⏰ **Lifecycle Management**
81
+
82
+ - **Soft Deletes**: Mark records as deleted without removal
83
+ - **Timestamps**: Automatic `created_at` and `updated_at` with timezone support
84
+ - **Model Events**: Hook into model lifecycle (creating, created, updating, etc.)
85
+ - **Observers**: Organize event handling logic into dedicated classes
86
+ - **Model Registry**: Automatic model registration for polymorphic relationships
87
+
88
+ ### 🛡️ **Developer Experience & Type Safety**
89
+
90
+ - **JavaScript First**: Works out of the box with JavaScript projects
91
+ - **Automatic TypeScript Support**: Detects TypeScript projects and generates typed code
92
+ - **IntelliSense Support**: Full IDE autocompletion for both JS and TS
93
+ - **CLI Tools**: Comprehensive code generation and database management
94
+ - **UUID Support**: Non-incrementing primary keys with automatic generation
95
+ - **Custom Casts**: Built-in and custom attribute casting
96
+ - **Pagination**: Standard, simple, and cursor-based pagination
97
+
98
+ ### 🗃️ **Database Support**
99
+
100
+ - **PostgreSQL**: Full support with JSON operations and advanced features
101
+ - **MySQL/MariaDB**: Complete compatibility with JSON functions
102
+ - **SQLite**: Perfect for development and testing with null defaults
103
+
104
+ ## Installation
105
+
106
+ ```bash
107
+ # npm
108
+ npm install ilana-orm
109
+
110
+ # yarn
111
+ yarn add ilana-orm
112
+
113
+ # pnpm
114
+ pnpm add ilana-orm
115
+ ```
116
+
117
+ **All database drivers (PostgreSQL, MySQL, SQLite) and dotenv are included by default** - no additional installation required!
118
+
119
+ ## Quick Start
120
+
121
+ ### 1. Initialize Project
122
+
123
+ #### Automatic Setup (Recommended)
124
+
125
+ ```bash
126
+ # Initialize IlanaORM in your project
127
+ npx ilana setup
128
+ ```
129
+
130
+ This command will:
131
+
132
+ - Create the `ilana.config.js` configuration file
133
+ - Generate the `database/migrations/` directory
134
+ - Generate the `database/seeds/` directory
135
+ - Generate the `database/factories/` directory
136
+ - Generate the `models/` directory
137
+ - Create a sample `.env` file with database variables
138
+
139
+ #### Manual Setup
140
+
141
+ If you prefer not to run the setup command, you can manually create the required files and directories:
142
+
143
+ ```bash
144
+ # Create directories
145
+ mkdir -p database/migrations database/seeds database/factories models
146
+
147
+ # Create config file (see configuration section below)
148
+ touch ilana.config.js
149
+ ```
150
+
151
+ ### 2. Configure Database
152
+
153
+ Create `ilana.config.js` in your project root:
154
+
155
+ ```javascript
156
+ module.exports = {
157
+ default: "sqlite",
158
+
159
+ connections: {
160
+ sqlite: {
161
+ client: "sqlite3",
162
+ connection: {
163
+ filename: "./database.sqlite",
164
+ },
165
+ },
166
+
167
+ mysql: {
168
+ client: "mysql2",
169
+ connection: {
170
+ host: "localhost",
171
+ port: 3306,
172
+ user: "your_username",
173
+ password: "your_password",
174
+ database: "your_database",
175
+ },
176
+ },
177
+
178
+ postgres: {
179
+ client: "pg",
180
+ connection: {
181
+ host: "localhost",
182
+ port: 5432,
183
+ user: "your_username",
184
+ password: "your_password",
185
+ database: "your_database",
186
+ },
187
+ },
188
+ },
189
+
190
+ migrations: {
191
+ directory: "./migrations",
192
+ tableName: "migrations",
193
+ },
194
+
195
+ seeds: {
196
+ directory: "./seeds",
197
+ },
198
+ };
199
+ ```
200
+
201
+ ### 3. Create Your First Model
202
+
203
+ ```bash
204
+ # Generate model with migration
205
+ npx ilana make:model User --migration
206
+ ```
207
+
208
+ This creates:
209
+
210
+ - `models/User.js` - The model file (or `.ts` if TypeScript project detected)
211
+ - `database/migrations/xxxx_create_users_table.js` - Migration file (or `.ts` if TypeScript project)
212
+
213
+ ### 4. Define the Model
214
+
215
+ **JavaScript:**
216
+
217
+ ```javascript
218
+ // models/User.js
219
+ const Model = require("ilana-orm/orm/Model");
220
+
221
+ class User extends Model {
222
+ static table = "users";
223
+ static timestamps = true;
224
+ static softDeletes = false;
225
+
226
+ fillable = ["name", "email", "password"];
227
+ hidden = ["password"];
228
+ casts = {
229
+ email_verified_at: "date",
230
+ is_active: "boolean",
231
+ metadata: "json",
232
+ };
233
+
234
+ // Relationships - use string references to avoid circular dependencies
235
+ posts() {
236
+ return this.hasMany("Post", "user_id");
237
+ }
238
+
239
+ roles() {
240
+ return this.belongsToMany("Role", "user_roles", "user_id", "role_id");
241
+ }
242
+
243
+ // Register for polymorphic relationships
244
+ static {
245
+ this.register();
246
+ }
247
+ }
248
+
249
+ module.exports = User;
250
+ ```
251
+
252
+ **TypeScript (auto-generated when `tsconfig.json` detected):**
253
+
254
+ ```typescript
255
+ // models/User.ts
256
+ import Model from "ilana-orm/orm/Model";
257
+
258
+ export default class User extends Model {
259
+ protected static table = "users";
260
+ protected static timestamps = true;
261
+ protected static softDeletes = false;
262
+
263
+ protected fillable: string[] = ["name", "email", "password"];
264
+ protected hidden: string[] = ["password"];
265
+ protected casts = {
266
+ email_verified_at: "date" as const,
267
+ is_active: "boolean" as const,
268
+ metadata: "json" as const,
269
+ };
270
+
271
+ // Relationships - use string references to avoid circular dependencies
272
+ posts() {
273
+ return this.hasMany("Post", "user_id");
274
+ }
275
+
276
+ roles() {
277
+ return this.belongsToMany("Role", "user_roles", "user_id", "role_id");
278
+ }
279
+
280
+ // Register for polymorphic relationships
281
+ static {
282
+ this.register();
283
+ }
284
+ }
285
+ ```
286
+
287
+ ````
288
+
289
+ ### 5. Run Migration
290
+
291
+ ```bash
292
+ npx ilana migrate
293
+ ````
294
+
295
+ ### 6. Start Using the Model
296
+
297
+ ```javascript
298
+ const User = require("./models/User");
299
+
300
+ // Create user
301
+ const user = await User.create({
302
+ name: "John Doe",
303
+ email: "john@example.com",
304
+ password: "secret",
305
+ });
306
+
307
+ // Query users
308
+ const users = await User.query()
309
+ .where("is_active", true)
310
+ .orderBy("created_at", "desc")
311
+ .get();
312
+
313
+ // Update user
314
+ await user.update({ name: "John Smith" });
315
+
316
+ // Delete user
317
+ await user.delete();
318
+ ```
319
+
320
+ ## Configuration
321
+
322
+ ### Complete Configuration Options
323
+
324
+ ```javascript
325
+ // ilana.config.js
326
+ module.exports = {
327
+ // Default connection name
328
+ default: "mysql_primary",
329
+
330
+ // Database connections
331
+ connections: {
332
+ mysql_primary: {
333
+ client: "mysql2",
334
+ connection: {
335
+ host: "primary.mysql.com",
336
+ port: 3306,
337
+ user: "primary_user",
338
+ password: "primary_password",
339
+ database: "primary_db",
340
+ charset: "utf8mb4",
341
+ timezone: "UTC",
342
+ },
343
+ pool: {
344
+ min: 2,
345
+ max: 10,
346
+ acquireTimeoutMillis: 30000,
347
+ createTimeoutMillis: 30000,
348
+ destroyTimeoutMillis: 5000,
349
+ idleTimeoutMillis: 30000,
350
+ reapIntervalMillis: 1000,
351
+ createRetryIntervalMillis: 100,
352
+ },
353
+ migrations: {
354
+ tableName: "migrations",
355
+ directory: "./migrations",
356
+ },
357
+ seeds: {
358
+ directory: "./seeds",
359
+ },
360
+ },
361
+
362
+ postgres_analytics: {
363
+ client: "pg",
364
+ connection: {
365
+ host: "analytics.postgres.com",
366
+ port: 5432,
367
+ user: "analytics_user",
368
+ password: "analytics_password",
369
+ database: "analytics_db",
370
+ ssl: { rejectUnauthorized: false },
371
+ searchPath: ["public", "analytics"],
372
+ },
373
+ pool: {
374
+ min: 1,
375
+ max: 5,
376
+ },
377
+ migrations: {
378
+ tableName: "analytics_migrations",
379
+ directory: "./migrations/analytics",
380
+ schemaName: "analytics",
381
+ },
382
+ },
383
+
384
+ sqlite_test: {
385
+ client: "sqlite3",
386
+ connection: {
387
+ filename: "./test.sqlite",
388
+ },
389
+ useNullAsDefault: true,
390
+ migrations: {
391
+ directory: "./migrations/test",
392
+ },
393
+ },
394
+ },
395
+
396
+ // Global migration settings
397
+ migrations: {
398
+ directory: "./migrations",
399
+ tableName: "migrations",
400
+ schemaName: "public", // PostgreSQL only
401
+ extension: "ts", // or 'js'
402
+ loadExtensions: [".ts", ".js"],
403
+ sortDirsSeparately: false,
404
+ stub: "./migration-stub.ts", // Custom migration template
405
+ },
406
+
407
+ // Global seed settings
408
+ seeds: {
409
+ directory: "./seeds",
410
+ extension: "ts",
411
+ loadExtensions: [".ts", ".js"],
412
+ stub: "./seed-stub.ts", // Custom seed template
413
+ },
414
+
415
+ // Model settings
416
+ models: {
417
+ directory: "./models",
418
+ extension: "ts",
419
+ },
420
+
421
+ // Factory settings
422
+ factories: {
423
+ directory: "./factories",
424
+ extension: "ts",
425
+ },
426
+
427
+ // Debugging
428
+ debug: process.env.NODE_ENV === "development",
429
+
430
+ // Logging
431
+ log: {
432
+ warn(message) {
433
+ console.warn(message);
434
+ },
435
+ error(message) {
436
+ console.error(message);
437
+ },
438
+ deprecate(message) {
439
+ console.warn("DEPRECATED:", message);
440
+ },
441
+ debug(message) {
442
+ if (process.env.NODE_ENV === "development") {
443
+ console.log("DEBUG:", message);
444
+ }
445
+ },
446
+ },
447
+ };
448
+ ```
449
+
450
+ ### Environment Variables
451
+
452
+ ```bash
453
+ # .env
454
+ DB_CONNECTION=mysql
455
+ DB_HOST=localhost
456
+ DB_PORT=3306
457
+ DB_DATABASE=your_database
458
+ DB_USERNAME=your_username
459
+ DB_PASSWORD=your_password
460
+ DB_TIMEZONE=America/New_York
461
+
462
+ # SQLite Configuration
463
+ # DB_CONNECTION=sqlite
464
+ # DB_FILENAME=./database.sqlite
465
+
466
+ # PostgreSQL Configuration
467
+ # DB_CONNECTION=postgres
468
+ # DB_HOST=localhost
469
+ # DB_PORT=5432
470
+ # DB_DATABASE=your_database
471
+ # DB_USERNAME=postgres
472
+ # DB_PASSWORD=your_password
473
+ ```
474
+
475
+ ```javascript
476
+ // ilana.config.js with environment variables
477
+ require("dotenv").config();
478
+ const Database = require("ilana-orm/database/connection");
479
+
480
+ const config = {
481
+ default: process.env.DB_CONNECTION || "mysql",
482
+ timezone: process.env.DB_TIMEZONE || "UTC",
483
+
484
+ connections: {
485
+ sqlite: {
486
+ client: "sqlite3",
487
+ connection: {
488
+ filename: process.env.DB_FILENAME || "./database.sqlite",
489
+ },
490
+ useNullAsDefault: true,
491
+ },
492
+
493
+ mysql: {
494
+ client: "mysql2",
495
+ connection: {
496
+ host: process.env.DB_HOST,
497
+ port: process.env.DB_PORT,
498
+ user: process.env.DB_USERNAME,
499
+ password: process.env.DB_PASSWORD,
500
+ database: process.env.DB_DATABASE,
501
+ timezone: process.env.DB_TIMEZONE || "UTC",
502
+ },
503
+ },
504
+
505
+ postgres: {
506
+ client: "pg",
507
+ connection: {
508
+ host: process.env.DB_HOST,
509
+ port: process.env.DB_PORT,
510
+ user: process.env.DB_USERNAME,
511
+ password: process.env.DB_PASSWORD,
512
+ database: process.env.DB_DATABASE,
513
+ },
514
+ },
515
+ },
516
+
517
+ migrations: {
518
+ directory: "./database/migrations",
519
+ tableName: "migrations",
520
+ },
521
+
522
+ seeds: {
523
+ directory: "./database/seeds",
524
+ },
525
+ };
526
+
527
+ // Auto-initialize database connections
528
+ Database.configure(config);
529
+
530
+ module.exports = config;
531
+ ```
532
+
533
+ ## CLI Commands
534
+
535
+ IlanaORM provides a comprehensive CLI for managing your database and models:
536
+
537
+ ### Model Generation
538
+
539
+ ```bash
540
+ # Generate a model
541
+ npx ilana make:model User
542
+
543
+ # Generate model with migration
544
+ npx ilana make:model User --migration
545
+ npx ilana make:model User -m
546
+
547
+ # Generate model with factory
548
+ npx ilana make:model User --factory
549
+ npx ilana make:model User -f
550
+
551
+ # Generate model with seeder
552
+ npx ilana make:model User --seed
553
+ npx ilana make:model User -s
554
+
555
+ # Generate model with all extras
556
+ npx ilana make:model User --all
557
+ npx ilana make:model User -a
558
+
559
+ # Generate pivot model
560
+ npx ilana make:model UserRole --pivot
561
+ ```
562
+
563
+ ### Migration Commands
564
+
565
+ ```bash
566
+ # Create migration
567
+ npx ilana make:migration create_users_table
568
+ npx ilana make:migration add_column_to_users --table=users
569
+ npx ilana make:migration create_posts_table --create=posts
570
+
571
+ # Run migrations
572
+ npx ilana migrate
573
+ npx ilana migrate --connection=mysql
574
+ npx ilana migrate --connection=postgres_analytics
575
+
576
+ # Run specific migration file
577
+ npx ilana migrate --only=20231201_create_users_table.ts
578
+
579
+ # Run migrations up to specific batch
580
+ npx ilana migrate --to=20231201_120000
581
+
582
+ # Rollback migrations
583
+ npx ilana migrate:rollback
584
+ npx ilana migrate:rollback --step=2
585
+ npx ilana migrate:rollback --to=20231201_120000
586
+ npx ilana migrate:rollback --connection=postgres_analytics
587
+
588
+ # Reset all migrations
589
+ npx ilana migrate:reset
590
+ npx ilana migrate:reset --connection=mysql
591
+
592
+ # Fresh migration (drop all + migrate)
593
+ npx ilana migrate:fresh
594
+ npx ilana migrate:fresh --connection=postgres_analytics
595
+
596
+ # Fresh with seeding
597
+ npx ilana migrate:fresh --seed
598
+ npx ilana migrate:fresh --seed --connection=mysql
599
+
600
+ # Check migration status
601
+ npx ilana migrate:status
602
+ npx ilana migrate:status --connection=postgres_analytics
603
+
604
+ # List completed migrations
605
+ npx ilana migrate:list
606
+
607
+ # Unlock migrations (if stuck)
608
+ npx ilana migrate:unlock
609
+ ```
610
+
611
+ ### Seeder Commands
612
+
613
+ ```bash
614
+ # Create seeder
615
+ npx ilana make:seeder UserSeeder
616
+
617
+ # Run all seeders
618
+ npx ilana seed
619
+
620
+ # Run specific seeder
621
+ npx ilana seed --class=UserSeeder
622
+
623
+ # Run seeders for specific connection
624
+ npx ilana seed --connection=mysql
625
+ ```
626
+
627
+ ### Factory Commands
628
+
629
+ ```bash
630
+ # Create factory
631
+ npx ilana make:factory UserFactory
632
+ ```
633
+
634
+ ### Database Commands
635
+
636
+ ```bash
637
+ # Drop all tables
638
+ npx ilana db:wipe
639
+
640
+ # Run all seeders
641
+ npx ilana db:seed
642
+ ```
643
+
644
+ ### Observer Commands
645
+
646
+ ```bash
647
+ # Create model observer
648
+ npx ilana make:observer UserObserver
649
+
650
+ # Create observer for specific model
651
+ npx ilana make:observer UserObserver --model=User
652
+ npx ilana make:observer PostObserver --model=Post
653
+ ```
654
+
655
+ ### Cast Commands
656
+
657
+ ```bash
658
+ # Create custom cast
659
+ npx ilana make:cast MoneyCast
660
+ ```
661
+
662
+ ## Models
663
+
664
+ ### Basic Model Definition
665
+
666
+ **JavaScript:**
667
+
668
+ ```javascript
669
+ const Model = require("ilana-orm/orm/Model");
670
+
671
+ class User extends Model {
672
+ // Table configuration
673
+ static table = "users";
674
+ static primaryKey = "id";
675
+ static keyType = "number"; // or 'string' for UUID
676
+ static incrementing = true;
677
+
678
+ // Connection (optional)
679
+ static connection = "mysql";
680
+
681
+ // Timestamps
682
+ static timestamps = true;
683
+ static createdAt = "created_at";
684
+ static updatedAt = "updated_at";
685
+
686
+ // Soft deletes
687
+ static softDeletes = true;
688
+ static deletedAt = "deleted_at";
689
+
690
+ // Mass assignment protection
691
+ fillable = ["name", "email", "password"];
692
+ guarded = ["id", "created_at", "updated_at"];
693
+
694
+ // Hidden attributes (won't appear in JSON)
695
+ hidden = ["password", "remember_token"];
696
+
697
+ // Appended attributes (accessors to include in JSON)
698
+ appends = ["full_name", "avatar_url"];
699
+
700
+ // Attribute casting
701
+ casts = {
702
+ email_verified_at: "date",
703
+ preferences: "json",
704
+ is_admin: "boolean",
705
+ };
706
+
707
+ // Default values
708
+ attributes = {
709
+ is_active: true,
710
+ role: "user",
711
+ };
712
+ }
713
+
714
+ module.exports = User;
715
+ ```
716
+
717
+ **TypeScript:**
718
+
719
+ ```typescript
720
+ import Model from "ilana-orm/orm/Model";
721
+
722
+ export default class User extends Model {
723
+ // Table configuration
724
+ protected static table = "users";
725
+ protected static primaryKey = "id";
726
+ protected static keyType = "number" as const; // or 'string' for UUID
727
+ protected static incrementing = true;
728
+
729
+ // Connection (optional)
730
+ protected static connection = "mysql";
731
+
732
+ // Timestamps
733
+ protected static timestamps = true;
734
+ protected static createdAt = "created_at";
735
+ protected static updatedAt = "updated_at";
736
+
737
+ // Soft deletes
738
+ protected static softDeletes = true;
739
+ protected static deletedAt = "deleted_at";
740
+
741
+ // Mass assignment protection
742
+ protected fillable: string[] = ["name", "email", "password"];
743
+ protected guarded: string[] = ["id", "created_at", "updated_at"];
744
+
745
+ // Hidden attributes (won't appear in JSON)
746
+ protected hidden: string[] = ["password", "remember_token"];
747
+
748
+ // Appended attributes (accessors to include in JSON)
749
+ protected appends: string[] = ["full_name", "avatar_url"];
750
+
751
+ // Attribute casting
752
+ protected casts = {
753
+ email_verified_at: "date" as const,
754
+ preferences: "json" as const,
755
+ is_admin: "boolean" as const,
756
+ };
757
+
758
+ // Default values
759
+ protected attributes = {
760
+ is_active: true,
761
+ role: "user",
762
+ };
763
+ }
764
+ ```
765
+
766
+ ````
767
+
768
+ ### UUID Primary Keys
769
+
770
+ **JavaScript:**
771
+ ```javascript
772
+ class User extends Model {
773
+ static table = 'users';
774
+ static keyType = 'string';
775
+ static incrementing = false;
776
+ }
777
+
778
+ module.exports = User;
779
+
780
+ // Usage
781
+ const user = await User.create({
782
+ name: 'John Doe',
783
+ email: 'john@example.com',
784
+ });
785
+ // user.id will be a generated UUID
786
+ ````
787
+
788
+ **TypeScript:**
789
+
790
+ ```typescript
791
+ export default class User extends Model {
792
+ protected static table = "users";
793
+ protected static keyType = "string" as const;
794
+ protected static incrementing = false;
795
+
796
+ // Attributes
797
+ id!: string; // UUID primary key
798
+ name!: string;
799
+ email!: string;
800
+ }
801
+
802
+ // Usage
803
+ const user = await User.create({
804
+ name: "John Doe",
805
+ email: "john@example.com",
806
+ });
807
+ // user.id will be a generated UUID
808
+ ```
809
+
810
+ ````
811
+
812
+ ### Attribute Casting
813
+
814
+ **JavaScript:**
815
+ ```javascript
816
+ class User extends Model {
817
+ casts = {
818
+ // Date casting
819
+ email_verified_at: 'date',
820
+ birth_date: 'date',
821
+
822
+ // Boolean casting
823
+ is_admin: 'boolean',
824
+ is_active: 'boolean',
825
+
826
+ // Number casting
827
+ age: 'number',
828
+ salary: 'float',
829
+
830
+ // JSON casting
831
+ preferences: 'json',
832
+ metadata: 'object',
833
+ tags: 'array',
834
+ };
835
+ }
836
+ ````
837
+
838
+ **TypeScript:**
839
+
840
+ ```typescript
841
+ class User extends Model {
842
+ protected casts = {
843
+ // Date casting
844
+ email_verified_at: "date" as const,
845
+ birth_date: "date" as const,
846
+
847
+ // Boolean casting
848
+ is_admin: "boolean" as const,
849
+ is_active: "boolean" as const,
850
+
851
+ // Number casting
852
+ age: "number" as const,
853
+ salary: "float" as const,
854
+
855
+ // JSON casting
856
+ preferences: "json" as const,
857
+ metadata: "object" as const,
858
+ tags: "array" as const,
859
+ };
860
+ }
861
+ ```
862
+
863
+ ````
864
+
865
+ ### Custom Casts
866
+
867
+ **JavaScript:**
868
+ ```javascript
869
+ // Built-in custom casts
870
+ const {
871
+ MoneyCast,
872
+ EncryptedCast,
873
+ JsonCast,
874
+ ArrayCast,
875
+ DateCast,
876
+ } = require('ilana-orm/orm/CustomCasts');
877
+
878
+ class Product extends Model {
879
+ casts = {
880
+ price: new MoneyCast(),
881
+ secret_data: new EncryptedCast('your-encryption-key'),
882
+ metadata: new JsonCast(),
883
+ tags: new ArrayCast(),
884
+ published_at: new DateCast(),
885
+ };
886
+ }
887
+
888
+ // Define custom cast
889
+ class MoneyCast {
890
+ get(value) {
891
+ return value ? parseFloat(value) / 100 : null;
892
+ }
893
+
894
+ set(value) {
895
+ return value ? Math.round(value * 100) : null;
896
+ }
897
+ }
898
+
899
+ // Generate custom cast with CLI
900
+ // ilana make:cast MoneyCast
901
+ // Creates: casts/MoneyCast.js
902
+ ````
903
+
904
+ **TypeScript:**
905
+
906
+ ```typescript
907
+ // Built-in custom casts
908
+ import {
909
+ MoneyCast,
910
+ EncryptedCast,
911
+ JsonCast,
912
+ ArrayCast,
913
+ DateCast,
914
+ } from "ilana-orm/orm/CustomCasts";
915
+
916
+ class Product extends Model {
917
+ protected casts = {
918
+ price: new MoneyCast(),
919
+ secret_data: new EncryptedCast("your-encryption-key"),
920
+ metadata: new JsonCast(),
921
+ tags: new ArrayCast(),
922
+ published_at: new DateCast(),
923
+ };
924
+ }
925
+
926
+ // Define custom cast
927
+ class MoneyCast {
928
+ get(value: any) {
929
+ return value ? parseFloat(value) / 100 : null;
930
+ }
931
+
932
+ set(value: any) {
933
+ return value ? Math.round(value * 100) : null;
934
+ }
935
+ }
936
+
937
+ // Generate custom cast with CLI
938
+ // ilana make:cast MoneyCast
939
+ // Creates: casts/MoneyCast.ts
940
+ ```
941
+
942
+ ````
943
+
944
+ ### Mutators and Accessors
945
+
946
+ **JavaScript:**
947
+ ```javascript
948
+ class User extends Model {
949
+ // Appended attributes (automatically included in JSON)
950
+ appends = ['full_name', 'avatar_url'];
951
+
952
+ // Mutator - transform data when setting
953
+ setPasswordAttribute(value) {
954
+ return value ? bcrypt.hashSync(value, 10) : value;
955
+ }
956
+
957
+ setEmailAttribute(value) {
958
+ return value ? value.toLowerCase().trim() : value;
959
+ }
960
+
961
+ // Accessor - transform data when getting
962
+ getFullNameAttribute() {
963
+ return `${this.first_name} ${this.last_name}`;
964
+ }
965
+
966
+ getAvatarUrlAttribute() {
967
+ return this.avatar
968
+ ? `/uploads/avatars/${this.avatar}`
969
+ : '/images/default-avatar.png';
970
+ }
971
+ }
972
+
973
+ // Usage
974
+ const user = await User.find(1);
975
+ console.log(user.toJSON());
976
+ // Output includes: { id: 1, first_name: 'John', last_name: 'Doe', full_name: 'John Doe', avatar_url: '/images/default-avatar.png' }
977
+ ````
978
+
979
+ **TypeScript:**
980
+
981
+ ```typescript
982
+ class User extends Model {
983
+ // Appended attributes (automatically included in JSON)
984
+ protected appends: string[] = ["full_name", "avatar_url"];
985
+
986
+ // Mutator - transform data when setting
987
+ setPasswordAttribute(value: string) {
988
+ return value ? bcrypt.hashSync(value, 10) : value;
989
+ }
990
+
991
+ setEmailAttribute(value: string) {
992
+ return value ? value.toLowerCase().trim() : value;
993
+ }
994
+
995
+ // Accessor - transform data when getting
996
+ getFullNameAttribute(): string {
997
+ return `${this.first_name} ${this.last_name}`;
998
+ }
999
+
1000
+ getAvatarUrlAttribute(): string {
1001
+ return this.avatar
1002
+ ? `/uploads/avatars/${this.avatar}`
1003
+ : "/images/default-avatar.png";
1004
+ }
1005
+ }
1006
+
1007
+ // Usage
1008
+ const user = await User.find(1);
1009
+ console.log(user.toJSON());
1010
+ // Output includes: { id: 1, first_name: 'John', last_name: 'Doe', full_name: 'John Doe', avatar_url: '/images/default-avatar.png' }
1011
+ ```
1012
+
1013
+ ````
1014
+
1015
+ ### Model Events
1016
+
1017
+ **JavaScript:**
1018
+ ```javascript
1019
+ class User extends Model {
1020
+ static {
1021
+ // Register events when class is loaded
1022
+ this.creating(async (user) => {
1023
+ user.email = user.email.toLowerCase();
1024
+ });
1025
+
1026
+ this.created(async (user) => {
1027
+ await sendWelcomeEmail(user.email);
1028
+ });
1029
+
1030
+ this.updating(async (user) => {
1031
+ if (user.isDirty('email')) {
1032
+ user.email_verified_at = null;
1033
+ }
1034
+ });
1035
+
1036
+ this.updated(async (user) => {
1037
+ await syncUserData(user);
1038
+ });
1039
+
1040
+ this.saving(async (user) => {
1041
+ user.updated_at = new Date();
1042
+ });
1043
+
1044
+ this.saved(async (user) => {
1045
+ await clearUserCache(user.id);
1046
+ });
1047
+
1048
+ this.deleting(async (user) => {
1049
+ await user.posts().delete();
1050
+ });
1051
+
1052
+ this.deleted(async (user) => {
1053
+ await cleanupUserFiles(user.id);
1054
+ });
1055
+ }
1056
+ }
1057
+ ````
1058
+
1059
+ **TypeScript:**
1060
+
1061
+ ```typescript
1062
+ class User extends Model {
1063
+ static {
1064
+ // Register events when class is loaded
1065
+ this.creating(async (user) => {
1066
+ user.email = user.email.toLowerCase();
1067
+ });
1068
+
1069
+ this.created(async (user) => {
1070
+ await sendWelcomeEmail(user.email);
1071
+ });
1072
+
1073
+ this.updating(async (user) => {
1074
+ if (user.isDirty("email")) {
1075
+ user.email_verified_at = null;
1076
+ }
1077
+ });
1078
+
1079
+ this.updated(async (user) => {
1080
+ await syncUserData(user);
1081
+ });
1082
+
1083
+ this.saving(async (user) => {
1084
+ user.updated_at = new Date();
1085
+ });
1086
+
1087
+ this.saved(async (user) => {
1088
+ await clearUserCache(user.id);
1089
+ });
1090
+
1091
+ this.deleting(async (user) => {
1092
+ await user.posts().delete();
1093
+ });
1094
+
1095
+ this.deleted(async (user) => {
1096
+ await cleanupUserFiles(user.id);
1097
+ });
1098
+ }
1099
+ }
1100
+ ```
1101
+
1102
+ ````
1103
+
1104
+ ### Model Observers
1105
+
1106
+ Observers provide a clean way to organize model event handling logic into separate classes, promoting better code organization and reusability.
1107
+
1108
+ #### Creating Observers
1109
+
1110
+ ```bash
1111
+ # Generate observer
1112
+ ilana make:observer UserObserver
1113
+
1114
+ # Generate observer for specific model
1115
+ ilana make:observer UserObserver --model=User
1116
+ ````
1117
+
1118
+ #### Observer Structure
1119
+
1120
+ **JavaScript:**
1121
+
1122
+ ```javascript
1123
+ // observers/UserObserver.js
1124
+ const User = require("../models/User");
1125
+
1126
+ class UserObserver {
1127
+ async creating(user) {
1128
+ // Logic before creating user
1129
+ user.email = user.email.toLowerCase();
1130
+ user.uuid = generateUuid();
1131
+ }
1132
+
1133
+ async created(user) {
1134
+ // Logic after creating user
1135
+ await sendWelcomeEmail(user.email);
1136
+ await createUserProfile(user.id);
1137
+ }
1138
+
1139
+ async updating(user) {
1140
+ // Logic before updating user
1141
+ if (user.isDirty("email")) {
1142
+ user.email_verified_at = null;
1143
+ }
1144
+ }
1145
+
1146
+ async updated(user) {
1147
+ // Logic after updating user
1148
+ await syncUserData(user);
1149
+ await clearUserCache(user.id);
1150
+ }
1151
+
1152
+ async saving(user) {
1153
+ // Logic before saving (create or update)
1154
+ user.updated_at = new Date();
1155
+ }
1156
+
1157
+ async saved(user) {
1158
+ // Logic after saving (create or update)
1159
+ await logUserActivity(user);
1160
+ }
1161
+
1162
+ async deleting(user) {
1163
+ // Logic before deleting user
1164
+ await user.posts().delete();
1165
+ await user.comments().delete();
1166
+ }
1167
+
1168
+ async deleted(user) {
1169
+ // Logic after deleting user
1170
+ await cleanupUserFiles(user.id);
1171
+ await removeFromExternalServices(user);
1172
+ }
1173
+
1174
+ async restoring(user) {
1175
+ // Logic before restoring soft-deleted user
1176
+ await validateUserRestore(user);
1177
+ }
1178
+
1179
+ async restored(user) {
1180
+ // Logic after restoring soft-deleted user
1181
+ await sendAccountRestoredEmail(user);
1182
+ }
1183
+ }
1184
+
1185
+ module.exports = UserObserver;
1186
+ ```
1187
+
1188
+ **TypeScript:**
1189
+
1190
+ ```typescript
1191
+ // observers/UserObserver.ts
1192
+ import User from "../models/User";
1193
+
1194
+ export default class UserObserver {
1195
+ async creating(user: User): Promise<void> {
1196
+ // Logic before creating user
1197
+ user.email = user.email.toLowerCase();
1198
+ user.uuid = generateUuid();
1199
+ }
1200
+
1201
+ async created(user: User): Promise<void> {
1202
+ // Logic after creating user
1203
+ await sendWelcomeEmail(user.email);
1204
+ await createUserProfile(user.id);
1205
+ }
1206
+
1207
+ async updating(user: User): Promise<void> {
1208
+ // Logic before updating user
1209
+ if (user.isDirty("email")) {
1210
+ user.email_verified_at = null;
1211
+ }
1212
+ }
1213
+
1214
+ async updated(user: User): Promise<void> {
1215
+ // Logic after updating user
1216
+ await syncUserData(user);
1217
+ await clearUserCache(user.id);
1218
+ }
1219
+
1220
+ async saving(user: User): Promise<void> {
1221
+ // Logic before saving (create or update)
1222
+ user.updated_at = new Date();
1223
+ }
1224
+
1225
+ async saved(user: User): Promise<void> {
1226
+ // Logic after saving (create or update)
1227
+ await logUserActivity(user);
1228
+ }
1229
+
1230
+ async deleting(user: User): Promise<void> {
1231
+ // Logic before deleting user
1232
+ await user.posts().delete();
1233
+ await user.comments().delete();
1234
+ }
1235
+
1236
+ async deleted(user: User): Promise<void> {
1237
+ // Logic after deleting user
1238
+ await cleanupUserFiles(user.id);
1239
+ await removeFromExternalServices(user);
1240
+ }
1241
+
1242
+ async restoring(user: User): Promise<void> {
1243
+ // Logic before restoring soft-deleted user
1244
+ await validateUserRestore(user);
1245
+ }
1246
+
1247
+ async restored(user: User): Promise<void> {
1248
+ // Logic after restoring soft-deleted user
1249
+ await sendAccountRestoredEmail(user);
1250
+ }
1251
+ }
1252
+ ```
1253
+
1254
+ ````
1255
+
1256
+ #### Registering Observers
1257
+
1258
+ **JavaScript:**
1259
+ ```javascript
1260
+ // Register observer class
1261
+ User.observe(UserObserver);
1262
+
1263
+ // Register multiple observers
1264
+ User.observe(UserObserver);
1265
+ User.observe(AuditObserver);
1266
+ User.observe(EmailNotificationObserver);
1267
+
1268
+ // Register observer with events object
1269
+ User.observe({
1270
+ creating: async (user) => {
1271
+ user.email = user.email.toLowerCase();
1272
+ },
1273
+ created: async (user) => {
1274
+ await sendWelcomeEmail(user.email);
1275
+ },
1276
+ });
1277
+ ````
1278
+
1279
+ **TypeScript:**
1280
+
1281
+ ```typescript
1282
+ // Register observer class
1283
+ User.observe(UserObserver);
1284
+
1285
+ // Register multiple observers
1286
+ User.observe(UserObserver);
1287
+ User.observe(AuditObserver);
1288
+ User.observe(EmailNotificationObserver);
1289
+
1290
+ // Register observer with events object
1291
+ User.observe({
1292
+ creating: async (user) => {
1293
+ user.email = user.email.toLowerCase();
1294
+ },
1295
+ created: async (user) => {
1296
+ await sendWelcomeEmail(user.email);
1297
+ },
1298
+ });
1299
+ ```
1300
+
1301
+ ````
1302
+
1303
+ #### Observer Registration Patterns
1304
+
1305
+ ```typescript
1306
+ // 1. Application Bootstrap
1307
+ // app.ts or index.ts
1308
+ import User from "./models/User";
1309
+ import UserObserver from "./observers/UserObserver";
1310
+
1311
+ // Register observers during app initialization
1312
+ User.observe(UserObserver);
1313
+
1314
+ // 2. Service Provider Pattern
1315
+ // providers/ObserverServiceProvider.ts
1316
+ export class ObserverServiceProvider {
1317
+ static register(): void {
1318
+ User.observe(UserObserver);
1319
+ Post.observe(PostObserver);
1320
+ Order.observe(OrderObserver);
1321
+ }
1322
+ }
1323
+
1324
+ // app.ts
1325
+ ObserverServiceProvider.register();
1326
+
1327
+ // 3. Dedicated Observer Registration
1328
+ // observers/index.ts
1329
+ import User from "../models/User";
1330
+ import Post from "../models/Post";
1331
+ import UserObserver from "./UserObserver";
1332
+ import PostObserver from "./PostObserver";
1333
+ import AuditObserver from "./AuditObserver";
1334
+
1335
+ // Register all observers
1336
+ User.observe(UserObserver);
1337
+ User.observe(AuditObserver);
1338
+ Post.observe(PostObserver);
1339
+ Post.observe(AuditObserver);
1340
+
1341
+ // app.ts
1342
+ import "./observers"; // Auto-registers all observers
1343
+ ````
1344
+
1345
+ #### Reusable Observers
1346
+
1347
+ ```typescript
1348
+ // observers/AuditObserver.ts
1349
+ export default class AuditObserver {
1350
+ async created(model: any): Promise<void> {
1351
+ await AuditLog.create({
1352
+ model_type: model.constructor.name,
1353
+ model_id: model.id,
1354
+ action: "created",
1355
+ data: model.toJSON(),
1356
+ });
1357
+ }
1358
+
1359
+ async updated(model: any): Promise<void> {
1360
+ await AuditLog.create({
1361
+ model_type: model.constructor.name,
1362
+ model_id: model.id,
1363
+ action: "updated",
1364
+ changes: model.getDirty(),
1365
+ });
1366
+ }
1367
+
1368
+ async deleted(model: any): Promise<void> {
1369
+ await AuditLog.create({
1370
+ model_type: model.constructor.name,
1371
+ model_id: model.id,
1372
+ action: "deleted",
1373
+ });
1374
+ }
1375
+ }
1376
+
1377
+ // Use across multiple models
1378
+ User.observe(AuditObserver);
1379
+ Post.observe(AuditObserver);
1380
+ Product.observe(AuditObserver);
1381
+ ```
1382
+
1383
+ #### Conditional Observer Registration
1384
+
1385
+ ```typescript
1386
+ // Environment-specific observers
1387
+ if (process.env.NODE_ENV === "production") {
1388
+ User.observe(ProductionUserObserver);
1389
+ } else {
1390
+ User.observe(DevelopmentUserObserver);
1391
+ }
1392
+
1393
+ // Feature-based observers
1394
+ if (config.features.emailNotifications) {
1395
+ User.observe(EmailNotificationObserver);
1396
+ }
1397
+
1398
+ if (config.features.analytics) {
1399
+ User.observe(AnalyticsObserver);
1400
+ }
1401
+
1402
+ // A/B testing observers
1403
+ if (user.isInExperimentGroup("new_onboarding")) {
1404
+ User.observe(NewOnboardingObserver);
1405
+ } else {
1406
+ User.observe(StandardOnboardingObserver);
1407
+ }
1408
+ ```
1409
+
1410
+ #### Observer vs Model Events
1411
+
1412
+ **Use Model Events for:**
1413
+
1414
+ - Core business logic that's integral to the model
1415
+ - Simple, single-purpose operations
1416
+ - Logic that should always run
1417
+
1418
+ **Use Observers for:**
1419
+
1420
+ - Side effects and cross-cutting concerns
1421
+ - Complex logic that can be organized into classes
1422
+ - Logic that might be conditionally applied
1423
+ - Reusable functionality across multiple models
1424
+ - Better testing and mocking capabilities
1425
+
1426
+ ### Query Scopes
1427
+
1428
+ **JavaScript:**
1429
+
1430
+ ```javascript
1431
+ class Post extends Model {
1432
+ // Simple scope
1433
+ static scopePublished(query) {
1434
+ return query.where("is_published", true);
1435
+ }
1436
+
1437
+ // Scope with parameters
1438
+ static scopeOfType(query, type) {
1439
+ return query.where("type", type);
1440
+ }
1441
+
1442
+ // Complex scope
1443
+ static scopePopular(query, threshold = 100) {
1444
+ return query.where("views", ">", threshold).orderBy("views", "desc");
1445
+ }
1446
+ }
1447
+
1448
+ // Usage
1449
+ const posts = await Post.query()
1450
+ .published()
1451
+ .ofType("article")
1452
+ .popular(500)
1453
+ .get();
1454
+ ```
1455
+
1456
+ **TypeScript:**
1457
+
1458
+ ```typescript
1459
+ class Post extends Model {
1460
+ // Simple scope
1461
+ static scopePublished(query: any) {
1462
+ return query.where("is_published", true);
1463
+ }
1464
+
1465
+ // Scope with parameters
1466
+ static scopeOfType(query: any, type: string) {
1467
+ return query.where("type", type);
1468
+ }
1469
+
1470
+ // Complex scope
1471
+ static scopePopular(query: any, threshold = 100) {
1472
+ return query.where("views", ">", threshold).orderBy("views", "desc");
1473
+ }
1474
+ }
1475
+
1476
+ // Usage
1477
+ const posts = await Post.query()
1478
+ .published()
1479
+ .ofType("article")
1480
+ .popular(500)
1481
+ .get();
1482
+ ```
1483
+
1484
+ ````
1485
+
1486
+ ## Query Builder
1487
+
1488
+ ### Basic Queries
1489
+
1490
+ **JavaScript:**
1491
+ ```javascript
1492
+ // Select all
1493
+ const users = await User.all();
1494
+
1495
+ // Find by primary key
1496
+ const user = await User.find(1);
1497
+ const user = await User.findOrFail(1); // Throws if not found
1498
+
1499
+ // First record
1500
+ const user = await User.first();
1501
+ const user = await User.firstOrFail(); // Throws if not found
1502
+
1503
+ // Create or find
1504
+ const user = await User.firstOrCreate(
1505
+ { email: 'john@example.com' },
1506
+ { name: 'John Doe' }
1507
+ );
1508
+
1509
+ // Update or create
1510
+ const user = await User.updateOrCreate(
1511
+ { email: 'john@example.com' },
1512
+ { name: 'John Smith', is_active: true }
1513
+ );
1514
+ ````
1515
+
1516
+ **TypeScript:**
1517
+
1518
+ ```typescript
1519
+ // Select all
1520
+ const users = await User.all();
1521
+
1522
+ // Find by primary key
1523
+ const user = await User.find(1);
1524
+ const user = await User.findOrFail(1); // Throws if not found
1525
+
1526
+ // First record
1527
+ const user = await User.first();
1528
+ const user = await User.firstOrFail(); // Throws if not found
1529
+
1530
+ // Create or find
1531
+ const user = await User.firstOrCreate(
1532
+ { email: "john@example.com" },
1533
+ { name: "John Doe" }
1534
+ );
1535
+
1536
+ // Update or create
1537
+ const user = await User.updateOrCreate(
1538
+ { email: "john@example.com" },
1539
+ { name: "John Smith", is_active: true }
1540
+ );
1541
+ ```
1542
+
1543
+ ````
1544
+
1545
+ ### Where Clauses
1546
+
1547
+ **JavaScript:**
1548
+ ```javascript
1549
+ // Basic where
1550
+ const users = await User.query()
1551
+ .where('is_active', true)
1552
+ .where('age', '>', 18)
1553
+ .get();
1554
+
1555
+ // Where with operator
1556
+ const users = await User.query()
1557
+ .where('age', '>=', 21)
1558
+ .where('name', 'like', '%john%')
1559
+ .get();
1560
+
1561
+ // Or where
1562
+ const users = await User.query()
1563
+ .where('role', 'admin')
1564
+ .orWhere('role', 'moderator')
1565
+ .get();
1566
+
1567
+ // Where in
1568
+ const users = await User.query()
1569
+ .whereIn('role', ['admin', 'editor', 'author'])
1570
+ .get();
1571
+
1572
+ // Where null/not null
1573
+ const users = await User.query()
1574
+ .whereNull('deleted_at')
1575
+ .whereNotNull('email_verified_at')
1576
+ .get();
1577
+
1578
+ // Where between
1579
+ const users = await User.query().whereBetween('age', [18, 65]).get();
1580
+
1581
+ // JSON queries (database-specific)
1582
+ const users = await User.query()
1583
+ .whereJsonContains('preferences', { theme: 'dark' })
1584
+ .whereJsonLength('tags', '>', 3)
1585
+ .get();
1586
+
1587
+ // Date queries
1588
+ const users = await User.query()
1589
+ .whereDate('created_at', '2023-12-01')
1590
+ .whereMonth('created_at', 12)
1591
+ .whereYear('created_at', 2023)
1592
+ .get();
1593
+
1594
+ // Exists queries
1595
+ const users = await User.query()
1596
+ .whereExists((query) => {
1597
+ query.select('*').from('posts').whereRaw('posts.user_id = users.id');
1598
+ })
1599
+ .get();
1600
+
1601
+ // Conditional queries
1602
+ const users = await User.query()
1603
+ .when(filters.role, (query, role) => {
1604
+ query.where('role', role);
1605
+ })
1606
+ .when(filters.search, (query, search) => {
1607
+ query.where('name', 'like', `%${search}%`);
1608
+ })
1609
+ .get();
1610
+ ````
1611
+
1612
+ **TypeScript:**
1613
+
1614
+ ```typescript
1615
+ // Basic where
1616
+ const users = await User.query()
1617
+ .where("is_active", true)
1618
+ .where("age", ">", 18)
1619
+ .get();
1620
+
1621
+ // Where with operator
1622
+ const users = await User.query()
1623
+ .where("age", ">=", 21)
1624
+ .where("name", "like", "%john%")
1625
+ .get();
1626
+
1627
+ // Or where
1628
+ const users = await User.query()
1629
+ .where("role", "admin")
1630
+ .orWhere("role", "moderator")
1631
+ .get();
1632
+
1633
+ // Where in
1634
+ const users = await User.query()
1635
+ .whereIn("role", ["admin", "editor", "author"])
1636
+ .get();
1637
+
1638
+ // Where null/not null
1639
+ const users = await User.query()
1640
+ .whereNull("deleted_at")
1641
+ .whereNotNull("email_verified_at")
1642
+ .get();
1643
+
1644
+ // Where between
1645
+ const users = await User.query().whereBetween("age", [18, 65]).get();
1646
+
1647
+ // JSON queries (database-specific)
1648
+ const users = await User.query()
1649
+ .whereJsonContains("preferences", { theme: "dark" })
1650
+ .whereJsonLength("tags", ">", 3)
1651
+ .get();
1652
+
1653
+ // Date queries
1654
+ const users = await User.query()
1655
+ .whereDate("created_at", "2023-12-01")
1656
+ .whereMonth("created_at", 12)
1657
+ .whereYear("created_at", 2023)
1658
+ .get();
1659
+
1660
+ // Exists queries
1661
+ const users = await User.query()
1662
+ .whereExists((query) => {
1663
+ query.select("*").from("posts").whereRaw("posts.user_id = users.id");
1664
+ })
1665
+ .get();
1666
+
1667
+ // Conditional queries
1668
+ const users = await User.query()
1669
+ .when(filters.role, (query, role) => {
1670
+ query.where("role", role);
1671
+ })
1672
+ .when(filters.search, (query, search) => {
1673
+ query.where("name", "like", `%${search}%`);
1674
+ })
1675
+ .get();
1676
+ ```
1677
+
1678
+ ````
1679
+
1680
+ ### Joins and Aggregates
1681
+
1682
+ **JavaScript:**
1683
+ ```javascript
1684
+ // Inner join
1685
+ const posts = await Post.query()
1686
+ .join('users', 'posts.user_id', 'users.id')
1687
+ .select('posts.*', 'users.name as author_name')
1688
+ .get();
1689
+
1690
+ // Aggregates
1691
+ const count = await User.query().count();
1692
+ const avgAge = await User.query().avg('age');
1693
+ const totalSalary = await User.query().sum('salary');
1694
+
1695
+ // Group by with having
1696
+ const roleStats = await User.query()
1697
+ .select('role')
1698
+ .selectRaw('COUNT(*) as count')
1699
+ .groupBy('role')
1700
+ .having('count', '>', 10)
1701
+ .get();
1702
+ ````
1703
+
1704
+ **TypeScript:**
1705
+
1706
+ ```typescript
1707
+ // Inner join
1708
+ const posts = await Post.query()
1709
+ .join("users", "posts.user_id", "users.id")
1710
+ .select("posts.*", "users.name as author_name")
1711
+ .get();
1712
+
1713
+ // Aggregates
1714
+ const count = await User.query().count();
1715
+ const avgAge = await User.query().avg("age");
1716
+ const totalSalary = await User.query().sum("salary");
1717
+
1718
+ // Group by with having
1719
+ const roleStats = await User.query()
1720
+ .select("role")
1721
+ .selectRaw("COUNT(*) as count")
1722
+ .groupBy("role")
1723
+ .having("count", ">", 10)
1724
+ .get();
1725
+ ```
1726
+
1727
+ ````
1728
+
1729
+ ### Ordering and Limiting
1730
+
1731
+ **JavaScript:**
1732
+ ```javascript
1733
+ const users = await User.query()
1734
+ .orderBy('name')
1735
+ .orderBy('created_at', 'desc')
1736
+ .limit(10)
1737
+ .offset(20)
1738
+ .get();
1739
+ ````
1740
+
1741
+ **TypeScript:**
1742
+
1743
+ ```typescript
1744
+ const users = await User.query()
1745
+ .orderBy("name")
1746
+ .orderBy("created_at", "desc")
1747
+ .limit(10)
1748
+ .offset(20)
1749
+ .get();
1750
+ ```
1751
+
1752
+ ````
1753
+
1754
+ ### Raw Queries
1755
+
1756
+ **JavaScript:**
1757
+ ```javascript
1758
+ // Raw where
1759
+ const users = await User.query()
1760
+ .whereRaw('age > ? AND salary < ?', [25, 50000])
1761
+ .get();
1762
+
1763
+ // Raw select
1764
+ const users = await User.query().selectRaw('*, YEAR(created_at) as year').get();
1765
+ ````
1766
+
1767
+ **TypeScript:**
1768
+
1769
+ ```typescript
1770
+ // Raw where
1771
+ const users = await User.query()
1772
+ .whereRaw("age > ? AND salary < ?", [25, 50000])
1773
+ .get();
1774
+
1775
+ // Raw select
1776
+ const users = await User.query().selectRaw("*, YEAR(created_at) as year").get();
1777
+ ```
1778
+
1779
+ ````
1780
+
1781
+ ## Relationships
1782
+
1783
+ **Important:** To avoid circular dependency issues, always use string references for related models in relationships instead of importing the model classes directly.
1784
+
1785
+ ### One-to-One
1786
+
1787
+ **JavaScript:**
1788
+ ```javascript
1789
+ class User extends Model {
1790
+ // User has one profile
1791
+ profile() {
1792
+ return this.hasOne(Profile, 'user_id');
1793
+ }
1794
+ }
1795
+
1796
+ class Profile extends Model {
1797
+ // Profile belongs to user
1798
+ user() {
1799
+ return this.belongsTo(User, 'user_id');
1800
+ }
1801
+ }
1802
+
1803
+ // Usage
1804
+ const user = await User.with('profile').first();
1805
+ const profile = user.profile;
1806
+ ````
1807
+
1808
+ **TypeScript:**
1809
+
1810
+ ```typescript
1811
+ class User extends Model {
1812
+ // User has one profile
1813
+ profile() {
1814
+ return this.hasOne(Profile, "user_id");
1815
+ }
1816
+ }
1817
+
1818
+ class Profile extends Model {
1819
+ // Profile belongs to user
1820
+ user() {
1821
+ return this.belongsTo(User, "user_id");
1822
+ }
1823
+ }
1824
+
1825
+ // Usage
1826
+ const user = await User.with("profile").first();
1827
+ const profile = user.profile;
1828
+ ```
1829
+
1830
+ ````
1831
+
1832
+ ### One-to-Many
1833
+
1834
+ **JavaScript:**
1835
+ ```javascript
1836
+ class User extends Model {
1837
+ // User has many posts
1838
+ posts() {
1839
+ return this.hasMany(Post, 'user_id');
1840
+ }
1841
+ }
1842
+
1843
+ class Post extends Model {
1844
+ // Post belongs to user
1845
+ author() {
1846
+ return this.belongsTo(User, 'user_id');
1847
+ }
1848
+ }
1849
+
1850
+ // Usage
1851
+ const user = await User.with('posts').first();
1852
+ const posts = user.posts;
1853
+ ````
1854
+
1855
+ **TypeScript:**
1856
+
1857
+ ```typescript
1858
+ class User extends Model {
1859
+ // User has many posts
1860
+ posts() {
1861
+ return this.hasMany(Post, "user_id");
1862
+ }
1863
+ }
1864
+
1865
+ class Post extends Model {
1866
+ // Post belongs to user
1867
+ author() {
1868
+ return this.belongsTo(User, "user_id");
1869
+ }
1870
+ }
1871
+
1872
+ // Usage
1873
+ const user = await User.with("posts").first();
1874
+ const posts = user.posts;
1875
+ ```
1876
+
1877
+ ````
1878
+
1879
+ ### Many-to-Many
1880
+
1881
+ **JavaScript:**
1882
+ ```javascript
1883
+ class User extends Model {
1884
+ // User belongs to many roles
1885
+ roles() {
1886
+ return this.belongsToMany(Role, 'user_roles', 'user_id', 'role_id')
1887
+ .withPivot('assigned_at', 'assigned_by')
1888
+ .withTimestamps();
1889
+ }
1890
+ }
1891
+
1892
+ class Role extends Model {
1893
+ // Role belongs to many users
1894
+ users() {
1895
+ return this.belongsToMany(User, 'user_roles', 'role_id', 'user_id');
1896
+ }
1897
+ }
1898
+
1899
+ // Usage
1900
+ const user = await User.with('roles').first();
1901
+ const roles = user.roles;
1902
+
1903
+ // Access pivot data
1904
+ roles.forEach((role) => {
1905
+ console.log(role.pivot.assigned_at);
1906
+ });
1907
+ ````
1908
+
1909
+ **TypeScript:**
1910
+
1911
+ ```typescript
1912
+ class User extends Model {
1913
+ // User belongs to many roles
1914
+ roles() {
1915
+ return this.belongsToMany(Role, "user_roles", "user_id", "role_id")
1916
+ .withPivot("assigned_at", "assigned_by")
1917
+ .withTimestamps();
1918
+ }
1919
+ }
1920
+
1921
+ class Role extends Model {
1922
+ // Role belongs to many users
1923
+ users() {
1924
+ return this.belongsToMany(User, "user_roles", "role_id", "user_id");
1925
+ }
1926
+ }
1927
+
1928
+ // Usage
1929
+ const user = await User.with("roles").first();
1930
+ const roles = user.roles;
1931
+
1932
+ // Access pivot data
1933
+ roles.forEach((role) => {
1934
+ console.log(role.pivot.assigned_at);
1935
+ });
1936
+ ```
1937
+
1938
+ ````
1939
+
1940
+ ### Polymorphic Relationships
1941
+
1942
+ **JavaScript:**
1943
+ ```javascript
1944
+ class Comment extends Model {
1945
+ // Comment can belong to Post or Video
1946
+ commentable() {
1947
+ return this.morphTo('commentable');
1948
+ }
1949
+ }
1950
+
1951
+ class Post extends Model {
1952
+ // Post has many comments (polymorphic)
1953
+ comments() {
1954
+ return this.morphMany(Comment, 'commentable');
1955
+ }
1956
+ }
1957
+
1958
+ class Video extends Model {
1959
+ // Video has many comments (polymorphic)
1960
+ comments() {
1961
+ return this.morphMany(Comment, 'commentable');
1962
+ }
1963
+ }
1964
+ ````
1965
+
1966
+ **TypeScript:**
1967
+
1968
+ ```typescript
1969
+ class Comment extends Model {
1970
+ // Comment can belong to Post or Video
1971
+ commentable() {
1972
+ return this.morphTo("commentable");
1973
+ }
1974
+ }
1975
+
1976
+ class Post extends Model {
1977
+ // Post has many comments (polymorphic)
1978
+ comments() {
1979
+ return this.morphMany(Comment, "commentable");
1980
+ }
1981
+ }
1982
+
1983
+ class Video extends Model {
1984
+ // Video has many comments (polymorphic)
1985
+ comments() {
1986
+ return this.morphMany(Comment, "commentable");
1987
+ }
1988
+ }
1989
+ ```
1990
+
1991
+ ````
1992
+
1993
+ ### Has-Many-Through
1994
+
1995
+ **JavaScript:**
1996
+ ```javascript
1997
+ class Country extends Model {
1998
+ // Country has many posts through users
1999
+ posts() {
2000
+ return this.hasManyThrough(Post, User, 'country_id', 'user_id');
2001
+ }
2002
+ }
2003
+ ````
2004
+
2005
+ **TypeScript:**
2006
+
2007
+ ```typescript
2008
+ class Country extends Model {
2009
+ // Country has many posts through users
2010
+ posts() {
2011
+ return this.hasManyThrough(Post, User, "country_id", "user_id");
2012
+ }
2013
+ }
2014
+ ```
2015
+
2016
+ ````
2017
+
2018
+ ### Eager Loading
2019
+
2020
+ **JavaScript:**
2021
+ ```javascript
2022
+ // Basic eager loading
2023
+ const users = await User.with('posts').get();
2024
+
2025
+ // Multiple relationships
2026
+ const users = await User.with('posts', 'roles', 'profile').get();
2027
+
2028
+ // Nested relationships
2029
+ const users = await User.with('posts.comments').get();
2030
+
2031
+ // Constrained eager loading
2032
+ const users = await User.query()
2033
+ .withConstraints('posts', (query) => {
2034
+ query.where('is_published', true).orderBy('created_at', 'desc').limit(5);
2035
+ })
2036
+ .get();
2037
+
2038
+ // Lazy loading
2039
+ const user = await User.first();
2040
+ await user.load('posts');
2041
+
2042
+ // Load missing relations only
2043
+ await user.loadMissing('posts', 'roles');
2044
+
2045
+ // Count relationships
2046
+ const users = await User.withCount('posts').get();
2047
+ // Each user will have posts_count attribute
2048
+
2049
+ // Constrained eager loading
2050
+ const users = await User.query()
2051
+ .withConstraints('posts', (query) => {
2052
+ query.where('is_published', true).limit(5);
2053
+ })
2054
+ .get();
2055
+ ````
2056
+
2057
+ **TypeScript:**
2058
+
2059
+ ```typescript
2060
+ // Basic eager loading
2061
+ const users = await User.with("posts").get();
2062
+
2063
+ // Multiple relationships
2064
+ const users = await User.with("posts", "roles", "profile").get();
2065
+
2066
+ // Nested relationships
2067
+ const users = await User.with("posts.comments").get();
2068
+
2069
+ // Constrained eager loading
2070
+ const users = await User.query()
2071
+ .withConstraints("posts", (query) => {
2072
+ query.where("is_published", true).orderBy("created_at", "desc").limit(5);
2073
+ })
2074
+ .get();
2075
+
2076
+ // Lazy loading
2077
+ const user = await User.first();
2078
+ await user.load("posts");
2079
+
2080
+ // Count relationships
2081
+ const users = await User.withCount("posts").get();
2082
+ // Each user will have posts_count attribute
2083
+ ```
2084
+
2085
+ ````
2086
+
2087
+ ## Migrations
2088
+
2089
+ ### Creating Migrations
2090
+
2091
+ ```bash
2092
+ # Create a new migration
2093
+ ilana make:migration create_users_table
2094
+
2095
+ # Create migration for existing table
2096
+ ilana make:migration add_avatar_to_users_table --table=users
2097
+ ````
2098
+
2099
+ ### Migration Structure
2100
+
2101
+ ```typescript
2102
+ import { SchemaBuilder } from "ilana-orm";
2103
+
2104
+ export default class CreateUsersTable {
2105
+ async up(schema: SchemaBuilder): Promise<void> {
2106
+ await schema.createTable("users", (table) => {
2107
+ // Primary key
2108
+ table.increments("id");
2109
+ // For UUID: table.uuid('id').primary();
2110
+
2111
+ // Basic columns
2112
+ table.string("name").notNullable();
2113
+ table.string("email").unique().notNullable();
2114
+ table.string("password").notNullable();
2115
+
2116
+ // Nullable columns
2117
+ table.string("avatar").nullable();
2118
+ table.timestamp("email_verified_at").nullable();
2119
+
2120
+ // Timestamps
2121
+ table.timestamps(true, true);
2122
+
2123
+ // Soft deletes
2124
+ table.timestamp("deleted_at").nullable();
2125
+
2126
+ // Indexes
2127
+ table.index("email");
2128
+ table.index(["name", "email"]);
2129
+ });
2130
+ }
2131
+
2132
+ async down(schema: SchemaBuilder): Promise<void> {
2133
+ await schema.dropTable("users");
2134
+ }
2135
+ }
2136
+ ```
2137
+
2138
+ ### Column Types
2139
+
2140
+ **JavaScript:**
2141
+
2142
+ ```javascript
2143
+ class CreateProductsTable {
2144
+ async up(schema) {
2145
+ await schema.createTable("products", (table) => {
2146
+ // Primary key types
2147
+ table.increments("id"); // Auto-incrementing integer
2148
+ table.bigIncrements("big_id"); // Auto-incrementing big integer
2149
+ // table.uuid('id').primary(); // UUID primary key
2150
+
2151
+ // Numeric types
2152
+ table.integer("quantity");
2153
+ table.bigInteger("views");
2154
+ table.smallInteger("priority");
2155
+ table.tinyInteger("status_code");
2156
+ table.decimal("price", 8, 2); // precision, scale
2157
+ table.float("rating", 3, 1); // precision, scale
2158
+ table.double("coordinates");
2159
+ table.real("measurement");
2160
+
2161
+ // String types
2162
+ table.string("name", 255); // VARCHAR with length
2163
+ table.text("description"); // TEXT
2164
+ table.longText("content"); // LONGTEXT (MySQL)
2165
+ table.mediumText("summary"); // MEDIUMTEXT (MySQL)
2166
+ table.char("code", 10); // CHAR with fixed length
2167
+ table.varchar("slug", 100); // VARCHAR (alias for string)
2168
+
2169
+ // Date/Time types
2170
+ table.date("release_date"); // DATE
2171
+ table.time("available_time"); // TIME
2172
+ table.datetime("published_at"); // DATETIME
2173
+ table.timestamp("created_at"); // TIMESTAMP
2174
+ table.timestamps(); // created_at & updated_at
2175
+ table.timestamps(true, true); // with timezone
2176
+
2177
+ // Boolean
2178
+ table.boolean("is_active").defaultTo(true);
2179
+
2180
+ // JSON types
2181
+ table.json("metadata"); // JSON (all databases)
2182
+ table.jsonb("settings"); // JSONB (PostgreSQL only)
2183
+
2184
+ // Binary types
2185
+ table.binary("file_data"); // BLOB/BYTEA
2186
+ table.varbinary("hash", 32); // VARBINARY
2187
+
2188
+ // UUID
2189
+ table.uuid("uuid");
2190
+
2191
+ // Enum (MySQL/PostgreSQL)
2192
+ table.enum("status", ["draft", "published", "archived"]);
2193
+
2194
+ // Set (MySQL only)
2195
+ table.set("permissions", ["read", "write", "delete"]);
2196
+
2197
+ // Geometry types (PostgreSQL/MySQL)
2198
+ table.geometry("location");
2199
+ table.point("coordinates");
2200
+ table.lineString("path");
2201
+ table.polygon("area");
2202
+
2203
+ // Array types (PostgreSQL only)
2204
+ table.specificType("tags", "text[]");
2205
+ table.specificType("scores", "integer[]");
2206
+
2207
+ // Network types (PostgreSQL only)
2208
+ table.inet("ip_address");
2209
+ table.macaddr("mac_address");
2210
+
2211
+ // Range types (PostgreSQL only)
2212
+ table.specificType("price_range", "numrange");
2213
+ table.specificType("date_range", "daterange");
2214
+
2215
+ // Full-text search (PostgreSQL)
2216
+ table.specificType("search_vector", "tsvector");
2217
+
2218
+ // Custom types
2219
+ table.specificType("custom_type", "your_custom_type");
2220
+
2221
+ // Foreign keys
2222
+ table.integer("user_id").unsigned();
2223
+ table.foreign("user_id").references("id").inTable("users");
2224
+
2225
+ // Shorthand foreign key
2226
+ table.foreignId("category_id").constrained();
2227
+ table.foreignUuid("parent_id").constrained("products");
2228
+ });
2229
+ }
2230
+ }
2231
+ ```
2232
+
2233
+ **TypeScript:**
2234
+
2235
+ ```typescript
2236
+ export default class CreateProductsTable {
2237
+ async up(schema: SchemaBuilder): Promise<void> {
2238
+ await schema.createTable("products", (table) => {
2239
+ // Primary key types
2240
+ table.increments("id"); // Auto-incrementing integer
2241
+ table.bigIncrements("big_id"); // Auto-incrementing big integer
2242
+ // table.uuid('id').primary(); // UUID primary key
2243
+
2244
+ // Numeric types
2245
+ table.integer("quantity");
2246
+ table.bigInteger("views");
2247
+ table.smallInteger("priority");
2248
+ table.tinyInteger("status_code");
2249
+ table.decimal("price", 8, 2); // precision, scale
2250
+ table.float("rating", 3, 1); // precision, scale
2251
+ table.double("coordinates");
2252
+ table.real("measurement");
2253
+
2254
+ // String types
2255
+ table.string("name", 255); // VARCHAR with length
2256
+ table.text("description"); // TEXT
2257
+ table.longText("content"); // LONGTEXT (MySQL)
2258
+ table.mediumText("summary"); // MEDIUMTEXT (MySQL)
2259
+ table.char("code", 10); // CHAR with fixed length
2260
+ table.varchar("slug", 100); // VARCHAR (alias for string)
2261
+
2262
+ // Date/Time types
2263
+ table.date("release_date"); // DATE
2264
+ table.time("available_time"); // TIME
2265
+ table.datetime("published_at"); // DATETIME
2266
+ table.timestamp("created_at"); // TIMESTAMP
2267
+ table.timestamps(); // created_at & updated_at
2268
+ table.timestamps(true, true); // with timezone
2269
+
2270
+ // Boolean
2271
+ table.boolean("is_active").defaultTo(true);
2272
+
2273
+ // JSON types
2274
+ table.json("metadata"); // JSON (all databases)
2275
+ table.jsonb("settings"); // JSONB (PostgreSQL only)
2276
+
2277
+ // Binary types
2278
+ table.binary("file_data"); // BLOB/BYTEA
2279
+ table.varbinary("hash", 32); // VARBINARY
2280
+
2281
+ // UUID
2282
+ table.uuid("uuid");
2283
+
2284
+ // Enum (MySQL/PostgreSQL)
2285
+ table.enum("status", ["draft", "published", "archived"]);
2286
+
2287
+ // Set (MySQL only)
2288
+ table.set("permissions", ["read", "write", "delete"]);
2289
+
2290
+ // Geometry types (PostgreSQL/MySQL)
2291
+ table.geometry("location");
2292
+ table.point("coordinates");
2293
+ table.lineString("path");
2294
+ table.polygon("area");
2295
+
2296
+ // Array types (PostgreSQL only)
2297
+ table.specificType("tags", "text[]");
2298
+ table.specificType("scores", "integer[]");
2299
+
2300
+ // Network types (PostgreSQL only)
2301
+ table.inet("ip_address");
2302
+ table.macaddr("mac_address");
2303
+
2304
+ // Range types (PostgreSQL only)
2305
+ table.specificType("price_range", "numrange");
2306
+ table.specificType("date_range", "daterange");
2307
+
2308
+ // Full-text search (PostgreSQL)
2309
+ table.specificType("search_vector", "tsvector");
2310
+
2311
+ // Custom types
2312
+ table.specificType("custom_type", "your_custom_type");
2313
+
2314
+ // Foreign keys
2315
+ table.integer("user_id").unsigned();
2316
+ table.foreign("user_id").references("id").inTable("users");
2317
+
2318
+ // Shorthand foreign key
2319
+ table.foreignId("category_id").constrained();
2320
+ table.foreignUuid("parent_id").constrained("products");
2321
+ });
2322
+ }
2323
+ }
2324
+ ```
2325
+
2326
+ ````
2327
+
2328
+ ### Column Modifiers and Constraints
2329
+
2330
+ ```typescript
2331
+ export default class CreateUsersTable {
2332
+ async up(schema: SchemaBuilder): Promise<void> {
2333
+ await schema.createTable("users", (table) => {
2334
+ table.increments("id");
2335
+
2336
+ // Nullable/Not nullable
2337
+ table.string("name").notNullable();
2338
+ table.string("nickname").nullable();
2339
+
2340
+ // Default values
2341
+ table.boolean("is_active").defaultTo(true);
2342
+ table.timestamp("created_at").defaultTo(schema.fn.now());
2343
+ table.string("role").defaultTo("user");
2344
+ table.integer("login_count").defaultTo(0);
2345
+
2346
+ // Unique constraints
2347
+ table.string("email").unique();
2348
+ table.string("username").unique("unique_username");
2349
+
2350
+ // Indexes
2351
+ table.string("slug").index();
2352
+ table.string("search_vector").index("search_idx");
2353
+
2354
+ // Composite indexes
2355
+ table.index(["name", "email"], "name_email_idx");
2356
+ table.unique(["email", "tenant_id"], "unique_email_per_tenant");
2357
+
2358
+ // Comments
2359
+ table.string("api_key").comment("User API key for external services");
2360
+
2361
+ // Unsigned (for integers)
2362
+ table.integer("age").unsigned();
2363
+
2364
+ // Auto increment
2365
+ table.integer("order_number").autoIncrement();
2366
+
2367
+ // Column positioning (MySQL only)
2368
+ table.string("middle_name").after("first_name");
2369
+ table.string("prefix").first();
2370
+
2371
+ // Check constraints (PostgreSQL/SQLite)
2372
+ table.integer("age").checkPositive();
2373
+ table.string("email").checkRegex("^[^@]+@[^@]+.[^@]+$");
2374
+
2375
+ // Generated columns (MySQL 5.7+/PostgreSQL)
2376
+ table
2377
+ .string("full_name")
2378
+ .generatedAs('CONCAT(first_name, " ", last_name)');
2379
+
2380
+ // Collation (MySQL/PostgreSQL)
2381
+ table.string("name").collate("utf8_unicode_ci");
2382
+ });
2383
+ }
2384
+ }
2385
+ ````
2386
+
2387
+ ### Modifying Tables
2388
+
2389
+ ```typescript
2390
+ // Add columns
2391
+ export default class AddAvatarToUsersTable {
2392
+ async up(schema: SchemaBuilder): Promise<void> {
2393
+ await schema.table("users", (table) => {
2394
+ table.string("avatar").nullable().after("email");
2395
+ table.text("bio").nullable();
2396
+ table.timestamp("last_login_at").nullable();
2397
+
2398
+ // Add index
2399
+ table.index("last_login_at");
2400
+
2401
+ // Add foreign key
2402
+ table.integer("department_id").unsigned().nullable();
2403
+ table.foreign("department_id").references("id").inTable("departments");
2404
+ });
2405
+ }
2406
+
2407
+ async down(schema: SchemaBuilder): Promise<void> {
2408
+ await schema.table("users", (table) => {
2409
+ // Drop foreign key first
2410
+ table.dropForeign(["department_id"]);
2411
+
2412
+ // Drop columns
2413
+ table.dropColumn(["avatar", "bio", "last_login_at", "department_id"]);
2414
+
2415
+ // Drop index
2416
+ table.dropIndex(["last_login_at"]);
2417
+ });
2418
+ }
2419
+ }
2420
+
2421
+ // Modify existing columns
2422
+ export default class ModifyUsersTable {
2423
+ async up(schema: SchemaBuilder): Promise<void> {
2424
+ await schema.table("users", (table) => {
2425
+ // Change column type
2426
+ table.text("bio").alter();
2427
+
2428
+ // Rename column
2429
+ table.renameColumn("name", "full_name");
2430
+
2431
+ // Change column to nullable
2432
+ table.string("phone").nullable().alter();
2433
+
2434
+ // Change default value
2435
+ table.boolean("is_active").defaultTo(false).alter();
2436
+
2437
+ // Add/drop constraints
2438
+ table.string("email").unique().alter();
2439
+ table.dropUnique(["username"]);
2440
+
2441
+ // Modify index
2442
+ table.dropIndex(["old_column"]);
2443
+ table.index(["new_column"]);
2444
+ });
2445
+ }
2446
+
2447
+ async down(schema: SchemaBuilder): Promise<void> {
2448
+ await schema.table("users", (table) => {
2449
+ table.string("bio").alter();
2450
+ table.renameColumn("full_name", "name");
2451
+ table.string("phone").notNullable().alter();
2452
+ table.boolean("is_active").defaultTo(true).alter();
2453
+ });
2454
+ }
2455
+ }
2456
+ ```
2457
+
2458
+ ### Indexes and Foreign Keys
2459
+
2460
+ ```typescript
2461
+ export default class CreatePostsTable {
2462
+ async up(schema: SchemaBuilder): Promise<void> {
2463
+ await schema.createTable("posts", (table) => {
2464
+ table.increments("id");
2465
+ table.string("title");
2466
+ table.text("content");
2467
+ table.integer("user_id").unsigned();
2468
+ table.integer("category_id").unsigned();
2469
+ table.timestamps();
2470
+
2471
+ // Simple indexes
2472
+ table.index("title");
2473
+ table.index("created_at");
2474
+
2475
+ // Composite indexes
2476
+ table.index(["user_id", "created_at"], "user_posts_idx");
2477
+ table.index(["category_id", "is_published"], "category_published_idx");
2478
+
2479
+ // Unique indexes
2480
+ table.unique(["user_id", "slug"], "unique_user_slug");
2481
+
2482
+ // Partial indexes (PostgreSQL)
2483
+ table.index(["title"], "published_posts_title_idx", {
2484
+ where: "is_published = true",
2485
+ });
2486
+
2487
+ // Full-text indexes (MySQL)
2488
+ table.index(["title", "content"], "fulltext_idx", "FULLTEXT");
2489
+
2490
+ // Spatial indexes (MySQL/PostgreSQL)
2491
+ table.index(["location"], "location_idx", "SPATIAL");
2492
+
2493
+ // Foreign keys with actions
2494
+ table
2495
+ .foreign("user_id")
2496
+ .references("id")
2497
+ .inTable("users")
2498
+ .onDelete("CASCADE")
2499
+ .onUpdate("CASCADE");
2500
+
2501
+ table
2502
+ .foreign("category_id")
2503
+ .references("id")
2504
+ .inTable("categories")
2505
+ .onDelete("SET NULL")
2506
+ .onUpdate("RESTRICT");
2507
+
2508
+ // Named foreign keys
2509
+ table
2510
+ .foreign("user_id", "fk_posts_user_id")
2511
+ .references("id")
2512
+ .inTable("users");
2513
+
2514
+ // Shorthand foreign keys
2515
+ table.foreignId("author_id").constrained("users");
2516
+ table.foreignUuid("parent_id").constrained("posts");
2517
+ });
2518
+ }
2519
+
2520
+ async down(schema: SchemaBuilder): Promise<void> {
2521
+ await schema.dropTable("posts");
2522
+ }
2523
+ }
2524
+ ```
2525
+
2526
+ ### Database-Specific Features
2527
+
2528
+ ```typescript
2529
+ // PostgreSQL specific features
2530
+ export default class PostgreSQLFeatures {
2531
+ async up(schema: SchemaBuilder): Promise<void> {
2532
+ // Create schema
2533
+ await schema.createSchema("analytics");
2534
+
2535
+ // Create table in specific schema
2536
+ await schema.createTable("analytics.events", (table) => {
2537
+ table.uuid("id").primary();
2538
+ table.jsonb("data");
2539
+ table.specificType("tags", "text[]");
2540
+ table.timestamp("created_at").defaultTo(schema.fn.now());
2541
+
2542
+ // GIN index for JSONB
2543
+ table.index(["data"], "events_data_gin", "GIN");
2544
+
2545
+ // Partial index
2546
+ table.index(["created_at"], "recent_events_idx", {
2547
+ where: "created_at > NOW() - INTERVAL '30 days'",
2548
+ });
2549
+ });
2550
+
2551
+ // Create extension
2552
+ await schema.raw('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
2553
+
2554
+ // Create custom type
2555
+ await schema.raw(`
2556
+ CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy')
2557
+ `);
2558
+ }
2559
+ }
2560
+
2561
+ // MySQL specific features
2562
+ export default class MySQLFeatures {
2563
+ async up(schema: SchemaBuilder): Promise<void> {
2564
+ await schema.createTable("products", (table) => {
2565
+ table.increments("id");
2566
+ table.string("name");
2567
+ table.text("description");
2568
+
2569
+ // Full-text index
2570
+ table.index(["name", "description"], "fulltext_idx", "FULLTEXT");
2571
+
2572
+ // JSON column with generated column
2573
+ table.json("attributes");
2574
+ table
2575
+ .string("brand")
2576
+ .generatedAs('JSON_UNQUOTE(JSON_EXTRACT(attributes, "$.brand"))');
2577
+
2578
+ // Spatial data
2579
+ table.point("location");
2580
+ table.index(["location"], "location_idx", "SPATIAL");
2581
+ });
2582
+
2583
+ // Set table engine and charset
2584
+ await schema.raw(`
2585
+ ALTER TABLE products
2586
+ ENGINE=InnoDB
2587
+ DEFAULT CHARSET=utf8mb4
2588
+ COLLATE=utf8mb4_unicode_ci
2589
+ `);
2590
+ }
2591
+ }
2592
+ ```
2593
+
2594
+ ### Migration Utilities
2595
+
2596
+ ```typescript
2597
+ export default class UtilityMigration {
2598
+ async up(schema: SchemaBuilder): Promise<void> {
2599
+ // Check if table exists
2600
+ if (await schema.hasTable("users")) {
2601
+ console.log("Users table already exists");
2602
+ return;
2603
+ }
2604
+
2605
+ // Check if column exists
2606
+ if (await schema.hasColumn("users", "email")) {
2607
+ console.log("Email column already exists");
2608
+ return;
2609
+ }
2610
+
2611
+ // Raw SQL execution
2612
+ await schema.raw("SET foreign_key_checks = 0");
2613
+
2614
+ // Create table with raw SQL
2615
+ await schema.raw(`
2616
+ CREATE TABLE IF NOT EXISTS custom_table (
2617
+ id INT AUTO_INCREMENT PRIMARY KEY,
2618
+ data JSON,
2619
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
2620
+ )
2621
+ `);
2622
+
2623
+ // Conditional operations based on database
2624
+ if (schema.client.config.client === "mysql2") {
2625
+ await schema.raw("ALTER TABLE users ADD FULLTEXT(name, bio)");
2626
+ } else if (schema.client.config.client === "pg") {
2627
+ await schema.raw(
2628
+ "CREATE INDEX CONCURRENTLY idx_users_name ON users(name)"
2629
+ );
2630
+ }
2631
+ }
2632
+ }
2633
+ ```
2634
+
2635
+ ### Running Migrations
2636
+
2637
+ ```bash
2638
+ # Run all pending migrations
2639
+ ilana migrate
2640
+ ilana migrate --connection=mysql
2641
+ ilana migrate --connection=postgres_analytics
2642
+
2643
+ # Run specific migration file
2644
+ ilana migrate --only=20231201_create_users_table.ts
2645
+
2646
+ # Run migrations up to specific batch
2647
+ ilana migrate --to=20231201_120000
2648
+
2649
+ # Rollback migrations
2650
+ ilana migrate:rollback
2651
+ ilana migrate:rollback --step=2
2652
+ ilana migrate:rollback --to=20231201_120000
2653
+ ilana migrate:rollback --connection=postgres_analytics
2654
+
2655
+ # Reset all migrations
2656
+ ilana migrate:reset
2657
+ ilana migrate:reset --connection=mysql
2658
+
2659
+ # Fresh migration (drop all + migrate)
2660
+ ilana migrate:fresh
2661
+ ilana migrate:fresh --connection=postgres_analytics
2662
+
2663
+ # Fresh with seeding
2664
+ ilana migrate:fresh --seed
2665
+ ilana migrate:fresh --seed --connection=mysql
2666
+
2667
+ # Check migration status
2668
+ ilana migrate:status
2669
+ ilana migrate:status --connection=postgres_analytics
2670
+
2671
+ # List completed migrations
2672
+ ilana migrate:list
2673
+
2674
+ # Unlock migrations (if stuck)
2675
+ ilana migrate:unlock
2676
+ ```
2677
+
2678
+ ## Seeders
2679
+
2680
+ ### Creating Seeders
2681
+
2682
+ ```bash
2683
+ # Create a seeder
2684
+ ilana make:seeder UserSeeder
2685
+ ```
2686
+
2687
+ ### Seeder Structure
2688
+
2689
+ **JavaScript:**
2690
+
2691
+ ```javascript
2692
+ const Seeder = require("ilana-orm/orm/Seeder");
2693
+ const User = require("../../models/User");
2694
+
2695
+ class UserSeeder extends Seeder {
2696
+ async run() {
2697
+ // Create admin user
2698
+ await User.create({
2699
+ name: "Admin User",
2700
+ email: "admin@example.com",
2701
+ password: "password",
2702
+ role: "admin",
2703
+ });
2704
+
2705
+ // Create test users using factory
2706
+ await User.factory().times(50).create();
2707
+
2708
+ // Create users with specific states
2709
+ await User.factory().times(5).state("admin").create();
2710
+ }
2711
+ }
2712
+
2713
+ module.exports = UserSeeder;
2714
+ ```
2715
+
2716
+ **TypeScript:**
2717
+
2718
+ ```typescript
2719
+ import Seeder from "ilana-orm/orm/Seeder";
2720
+ import User from "../../models/User";
2721
+
2722
+ export default class UserSeeder extends Seeder {
2723
+ async run(): Promise<void> {
2724
+ // Create admin user
2725
+ await User.create({
2726
+ name: "Admin User",
2727
+ email: "admin@example.com",
2728
+ password: "password",
2729
+ role: "admin",
2730
+ });
2731
+
2732
+ // Create test users using factory
2733
+ await User.factory().times(50).create();
2734
+
2735
+ // Create users with specific states
2736
+ await User.factory().times(5).state("admin").create();
2737
+ }
2738
+ }
2739
+ ```
2740
+
2741
+ ````
2742
+
2743
+ ### Advanced Seeding Techniques
2744
+
2745
+ ```typescript
2746
+ export default class DatabaseSeeder extends Seeder {
2747
+ async run(): Promise<void> {
2748
+ // Disable foreign key checks
2749
+ await this.disableForeignKeyChecks();
2750
+
2751
+ // Truncate tables in correct order
2752
+ await this.truncateInOrder([
2753
+ "user_roles",
2754
+ "posts",
2755
+ "users",
2756
+ "roles",
2757
+ "categories",
2758
+ ]);
2759
+
2760
+ // Seed in dependency order
2761
+ await this.call([
2762
+ RoleSeeder,
2763
+ CategorySeeder,
2764
+ UserSeeder,
2765
+ PostSeeder,
2766
+ UserRoleSeeder,
2767
+ ]);
2768
+
2769
+ // Re-enable foreign key checks
2770
+ await this.enableForeignKeyChecks();
2771
+ }
2772
+
2773
+ private async truncateInOrder(tables: string[]): Promise<void> {
2774
+ for (const table of tables) {
2775
+ await this.db.raw(`TRUNCATE TABLE ${table}`);
2776
+ }
2777
+ }
2778
+
2779
+ private async call(seeders: any[]): Promise<void> {
2780
+ for (const SeederClass of seeders) {
2781
+ const seeder = new SeederClass();
2782
+ await seeder.run();
2783
+ console.log(`Seeded: ${SeederClass.name}`);
2784
+ }
2785
+ }
2786
+
2787
+ private async disableForeignKeyChecks(): Promise<void> {
2788
+ const client = this.db.client.config.client;
2789
+
2790
+ if (client === "mysql2") {
2791
+ await this.db.raw("SET FOREIGN_KEY_CHECKS = 0");
2792
+ } else if (client === "pg") {
2793
+ await this.db.raw("SET session_replication_role = replica");
2794
+ }
2795
+ }
2796
+
2797
+ private async enableForeignKeyChecks(): Promise<void> {
2798
+ const client = this.db.client.config.client;
2799
+
2800
+ if (client === "mysql2") {
2801
+ await this.db.raw("SET FOREIGN_KEY_CHECKS = 1");
2802
+ } else if (client === "pg") {
2803
+ await this.db.raw("SET session_replication_role = DEFAULT");
2804
+ }
2805
+ }
2806
+ }
2807
+ ````
2808
+
2809
+ ### Connection-Specific Seeding
2810
+
2811
+ ```typescript
2812
+ export default class AnalyticsSeeder extends Seeder {
2813
+ // Specify connection for this seeder
2814
+ protected connection = "analytics_db";
2815
+
2816
+ async run(): Promise<void> {
2817
+ // This will run on analytics_db connection
2818
+ await AnalyticsEvent.create({
2819
+ event_type: "user_signup",
2820
+ data: { source: "web" },
2821
+ created_at: new Date(),
2822
+ });
2823
+ }
2824
+ }
2825
+ ```
2826
+
2827
+ ### Conditional and Environment-Specific Seeding
2828
+
2829
+ ```typescript
2830
+ export default class UserSeeder extends Seeder {
2831
+ async run(): Promise<void> {
2832
+ // Only seed if no users exist
2833
+ const userCount = await User.count();
2834
+
2835
+ if (userCount === 0) {
2836
+ await this.seedUsers();
2837
+ }
2838
+
2839
+ // Environment-specific seeding
2840
+ if (process.env.NODE_ENV === "development") {
2841
+ await this.seedTestData();
2842
+ }
2843
+
2844
+ if (process.env.NODE_ENV === "production") {
2845
+ await this.seedProductionData();
2846
+ }
2847
+
2848
+ // Feature flag based seeding
2849
+ if (process.env.ENABLE_PREMIUM_FEATURES === "true") {
2850
+ await this.seedPremiumFeatures();
2851
+ }
2852
+ }
2853
+
2854
+ private async seedUsers(): Promise<void> {
2855
+ // Create admin user
2856
+ await User.create({
2857
+ name: "System Admin",
2858
+ email: "admin@example.com",
2859
+ password: "secure_password",
2860
+ role: "admin",
2861
+ });
2862
+
2863
+ // Create regular users
2864
+ await User.factory().times(10).create();
2865
+ }
2866
+
2867
+ private async seedTestData(): Promise<void> {
2868
+ // Test users with known credentials
2869
+ await User.create({
2870
+ name: "Test User",
2871
+ email: "test@example.com",
2872
+ password: "password",
2873
+ });
2874
+
2875
+ // Create users with all possible states
2876
+ await User.factory().state("admin").create();
2877
+ await User.factory().state("inactive").create();
2878
+ await User.factory().state("premium").create();
2879
+ }
2880
+
2881
+ private async seedProductionData(): Promise<void> {
2882
+ // Only essential data for production
2883
+ await User.create({
2884
+ name: "System Administrator",
2885
+ email: process.env.ADMIN_EMAIL,
2886
+ password: process.env.ADMIN_PASSWORD,
2887
+ role: "admin",
2888
+ });
2889
+ }
2890
+
2891
+ private async seedPremiumFeatures(): Promise<void> {
2892
+ await Feature.create({
2893
+ name: "Premium Analytics",
2894
+ is_premium: true,
2895
+ is_enabled: true,
2896
+ });
2897
+ }
2898
+ }
2899
+ ```
2900
+
2901
+ ### Seeder with Progress Tracking
2902
+
2903
+ ```typescript
2904
+ export default class LargeDataSeeder extends Seeder {
2905
+ async run(): Promise<void> {
2906
+ const totalUsers = 10000;
2907
+
2908
+ // Built-in progress tracking
2909
+ await this.progress(totalUsers, async (updateProgress) => {
2910
+ const users = await this.createInBatches(User.factory(), totalUsers, {
2911
+ is_active: true,
2912
+ });
2913
+ updateProgress(users.length);
2914
+ });
2915
+
2916
+ console.log("User seeding completed!");
2917
+ }
2918
+ }
2919
+
2920
+ // Advanced seeder utilities
2921
+ export default class DatabaseSeeder extends Seeder {
2922
+ async run(): Promise<void> {
2923
+ // Run seeders with connection mapping
2924
+ await this.callWith(
2925
+ {
2926
+ UserSeeder: UserSeeder,
2927
+ PostSeeder: PostSeeder,
2928
+ },
2929
+ "mysql_primary"
2930
+ );
2931
+
2932
+ // Run seeder only once (idempotent)
2933
+ await this.callOnce(AdminSeeder, "admin_user_setup");
2934
+
2935
+ // Wipe entire database
2936
+ await this.wipeDatabase();
2937
+ }
2938
+ }
2939
+ ```
2940
+
2941
+ ### Running Seeders
2942
+
2943
+ ```bash
2944
+ # Run all seeders
2945
+ npx ilana seed
2946
+
2947
+ # Run specific seeder
2948
+ npx ilana seed --class=UserSeeder
2949
+
2950
+ # Run seeders for specific connection
2951
+ npx ilana seed --connection=mysql
2952
+ npx ilana seed --connection=analytics_db
2953
+
2954
+ # Run seeders with environment
2955
+ NODE_ENV=development ilana seed
2956
+ NODE_ENV=production ilana seed --class=ProductionSeeder
2957
+
2958
+ # Fresh migration with seeding
2959
+ npx ilana migrate:fresh --seed
2960
+ npx ilana migrate:fresh --seed --connection=analytics_db
2961
+
2962
+ # Seed specific connection after migration
2963
+ npx ilana migrate --connection=analytics_db
2964
+ npx ilana seed --connection=analytics_db
2965
+ ```
2966
+
2967
+ ## Model Factories
2968
+
2969
+ ### Defining Factories
2970
+
2971
+ **JavaScript:**
2972
+
2973
+ ```javascript
2974
+ const { defineFactory } = require("ilana-orm/orm/Factory");
2975
+ const { faker } = require("@faker-js/faker");
2976
+ const User = require("../../models/User");
2977
+
2978
+ module.exports = defineFactory(User, () => ({
2979
+ name: faker.person.fullName(),
2980
+ email: faker.internet.email(),
2981
+ password: "password123",
2982
+ age: faker.number.int({ min: 18, max: 80 }),
2983
+ is_active: true,
2984
+ }));
2985
+ ```
2986
+
2987
+ **TypeScript:**
2988
+
2989
+ ```typescript
2990
+ import { defineFactory } from "ilana-orm/orm/Factory";
2991
+ import { faker } from "@faker-js/faker";
2992
+ import User from "../../models/User";
2993
+
2994
+ export default defineFactory(User, () => ({
2995
+ name: faker.person.fullName(),
2996
+ email: faker.internet.email(),
2997
+ password: "password123",
2998
+ age: faker.number.int({ min: 18, max: 80 }),
2999
+ is_active: true,
3000
+ }));
3001
+ ```
3002
+
3003
+ ````
3004
+
3005
+ ### Factory States
3006
+
3007
+ **JavaScript:**
3008
+ ```javascript
3009
+ module.exports = defineFactory(User, () => ({
3010
+ name: faker.person.fullName(),
3011
+ email: faker.internet.email(),
3012
+ password: 'password123',
3013
+ role: 'user',
3014
+ }))
3015
+ .state('admin', () => ({
3016
+ role: 'admin',
3017
+ is_admin: true,
3018
+ }))
3019
+ .state('inactive', () => ({
3020
+ is_active: false,
3021
+ }));
3022
+ ````
3023
+
3024
+ **TypeScript:**
3025
+
3026
+ ```typescript
3027
+ export default defineFactory(User, () => ({
3028
+ name: faker.person.fullName(),
3029
+ email: faker.internet.email(),
3030
+ password: "password123",
3031
+ role: "user",
3032
+ }))
3033
+ .state("admin", () => ({
3034
+ role: "admin",
3035
+ is_admin: true,
3036
+ }))
3037
+ .state("inactive", () => ({
3038
+ is_active: false,
3039
+ }));
3040
+ ```
3041
+
3042
+ ````
3043
+
3044
+ ### Using Factories
3045
+
3046
+ **JavaScript:**
3047
+ ```javascript
3048
+ // Create single model
3049
+ const user = await User.factory().create();
3050
+
3051
+ // Create multiple models
3052
+ const users = await User.factory().times(10).create();
3053
+
3054
+ // Create with specific attributes
3055
+ const user = await User.factory().create({
3056
+ name: 'John Doe',
3057
+ email: 'john@example.com',
3058
+ });
3059
+
3060
+ // Create with state
3061
+ const admin = await User.factory().state('admin').create();
3062
+
3063
+ // Create with multiple states
3064
+ const premiumAdmin = await User.factory()
3065
+ .state('admin')
3066
+ .state('premium')
3067
+ .create();
3068
+
3069
+ // Make without saving
3070
+ const user = User.factory().make();
3071
+ const users = User.factory().times(5).make();
3072
+
3073
+ // Create raw attributes (plain objects)
3074
+ const userData = User.factory().raw();
3075
+ const usersData = User.factory().times(3).raw();
3076
+
3077
+ // Advanced factory methods
3078
+ const user = await User.factory()
3079
+ .configure((factory) => {
3080
+ factory.resetSequence('email');
3081
+ })
3082
+ .when(someCondition, (factory) => {
3083
+ factory.state('premium');
3084
+ })
3085
+ .unless(otherCondition, (factory) => {
3086
+ factory.state('basic');
3087
+ })
3088
+ .create();
3089
+
3090
+ // Create with relationships
3091
+ const user = await User.factory().createWithRelations(
3092
+ {},
3093
+ {
3094
+ roles: [{ id: 1, name: 'admin' }],
3095
+ }
3096
+ );
3097
+
3098
+ // Batch creation for performance
3099
+ const users = await User.factory().times(10000).createInBatches(500);
3100
+ ````
3101
+
3102
+ **TypeScript:**
3103
+
3104
+ ```typescript
3105
+ // Create single model
3106
+ const user = await User.factory().create();
3107
+
3108
+ // Create multiple models
3109
+ const users = await User.factory().times(10).create();
3110
+
3111
+ // Create with specific attributes
3112
+ const user = await User.factory().create({
3113
+ name: "John Doe",
3114
+ email: "john@example.com",
3115
+ });
3116
+
3117
+ // Create with state
3118
+ const admin = await User.factory().state("admin").create();
3119
+
3120
+ // Create with multiple states
3121
+ const premiumAdmin = await User.factory()
3122
+ .state("admin")
3123
+ .state("premium")
3124
+ .create();
3125
+
3126
+ // Make without saving
3127
+ const user = User.factory().make();
3128
+ const users = User.factory().times(5).make();
3129
+
3130
+ // Create raw attributes (plain objects)
3131
+ const userData = User.factory().raw();
3132
+ const usersData = User.factory().times(3).raw();
3133
+
3134
+ // Advanced factory methods
3135
+ const user = await User.factory()
3136
+ .configure((factory) => {
3137
+ factory.resetSequence("email");
3138
+ })
3139
+ .when(someCondition, (factory) => {
3140
+ factory.state("premium");
3141
+ })
3142
+ .unless(otherCondition, (factory) => {
3143
+ factory.state("basic");
3144
+ })
3145
+ .create();
3146
+
3147
+ // Create with relationships
3148
+ const user = await User.factory().createWithRelations(
3149
+ {},
3150
+ {
3151
+ roles: [{ id: 1, name: "admin" }],
3152
+ }
3153
+ );
3154
+
3155
+ // Batch creation for performance
3156
+ const users = await User.factory().times(10000).createInBatches(500);
3157
+ ```
3158
+
3159
+ ````
3160
+
3161
+ ### Factory Relationships
3162
+
3163
+ ```typescript
3164
+ // Post factory with automatic user creation
3165
+ export const PostFactory = defineFactory(Post, () => ({
3166
+ title: faker.lorem.sentence(),
3167
+ content: faker.lorem.paragraphs(3),
3168
+ is_published: faker.datatype.boolean(),
3169
+ published_at: faker.date.past(),
3170
+ })).afterCreating(async (post) => {
3171
+ // Create user if not provided
3172
+ if (!post.user_id) {
3173
+ const user = await User.factory().create();
3174
+ post.user_id = user.id;
3175
+ await post.save();
3176
+ }
3177
+ });
3178
+
3179
+ // Factory with relationship method
3180
+ export const PostFactory = defineFactory(Post, () => ({
3181
+ title: faker.lorem.sentence(),
3182
+ content: faker.lorem.paragraphs(3),
3183
+ }))
3184
+ .for("user", () => User.factory()) // Define relationship
3185
+ .for("category", () => Category.factory());
3186
+
3187
+ // Usage
3188
+ const post = await Post.factory()
3189
+ .for("user", User.factory().state("admin"))
3190
+ .create();
3191
+
3192
+ // Create with existing relationships
3193
+ const user = await User.factory().create();
3194
+ const posts = await Post.factory().times(5).create({ user_id: user.id });
3195
+
3196
+ // Create nested relationships
3197
+ const userWithPosts = await User.factory()
3198
+ .has(Post.factory().times(3), "posts")
3199
+ .create();
3200
+
3201
+ // Many-to-many relationships
3202
+ const userWithRoles = await User.factory()
3203
+ .hasAttached(Role.factory().times(2), "roles")
3204
+ .create();
3205
+ ````
3206
+
3207
+ ### Factory Sequences
3208
+
3209
+ ```typescript
3210
+ export default defineFactory(User, () => {
3211
+ let sequence = 0;
3212
+
3213
+ return {
3214
+ name: faker.person.fullName(),
3215
+ email: () => `user${++sequence}@example.com`,
3216
+ username: () => `user${sequence}`,
3217
+ order: () => sequence,
3218
+ };
3219
+ });
3220
+
3221
+ // Global sequence
3222
+ let globalUserSequence = 0;
3223
+
3224
+ export default defineFactory(User, () => ({
3225
+ name: faker.person.fullName(),
3226
+ email: `user${++globalUserSequence}@example.com`,
3227
+ sequence_number: globalUserSequence,
3228
+ }));
3229
+ ```
3230
+
3231
+ ### Factory Callbacks and Hooks
3232
+
3233
+ ```typescript
3234
+ export default defineFactory(User, () => ({
3235
+ name: faker.person.fullName(),
3236
+ email: faker.internet.email(),
3237
+ password: "password123",
3238
+ }))
3239
+ .afterMaking((user) => {
3240
+ // Called after making (before saving)
3241
+ user.slug = user.name.toLowerCase().replace(/\s+/g, "-");
3242
+ user.display_name = user.name.toUpperCase();
3243
+ })
3244
+ .afterCreating(async (user) => {
3245
+ // Called after creating (after saving)
3246
+ await user.profile().create({
3247
+ bio: faker.lorem.paragraph(),
3248
+ avatar: faker.image.avatar(),
3249
+ });
3250
+
3251
+ // Send welcome email
3252
+ await sendWelcomeEmail(user.email);
3253
+ })
3254
+ .beforeMaking((attributes) => {
3255
+ // Modify attributes before making
3256
+ if (!attributes.email) {
3257
+ attributes.email = `${attributes.name.replace(/\s+/g, ".")}@example.com`;
3258
+ }
3259
+ return attributes;
3260
+ })
3261
+ .beforeCreating(async (user) => {
3262
+ // Called before saving to database
3263
+ user.email_verified_at = new Date();
3264
+ });
3265
+ ```
3266
+
3267
+ ### Factory Traits and Complex States
3268
+
3269
+ ```typescript
3270
+ export default defineFactory(User, () => ({
3271
+ name: faker.person.fullName(),
3272
+ email: faker.internet.email(),
3273
+ password: "password123",
3274
+ role: "user",
3275
+ is_active: true,
3276
+ subscription_type: "free",
3277
+ }))
3278
+ .state("admin", () => ({
3279
+ role: "admin",
3280
+ is_admin: true,
3281
+ permissions: ["read", "write", "delete", "admin"],
3282
+ }))
3283
+ .state("inactive", () => ({
3284
+ is_active: false,
3285
+ deactivated_at: faker.date.past(),
3286
+ deactivation_reason: "user_request",
3287
+ }))
3288
+ .state("premium", () => ({
3289
+ subscription_type: "premium",
3290
+ subscription_expires_at: faker.date.future(),
3291
+ premium_features: ["analytics", "priority_support"],
3292
+ }))
3293
+ .state("verified", () => ({
3294
+ email_verified_at: faker.date.past(),
3295
+ phone_verified_at: faker.date.past(),
3296
+ }))
3297
+ .state("with_profile", () => ({}))
3298
+ .afterCreating(async (user, evaluator) => {
3299
+ if (evaluator.hasState("with_profile")) {
3300
+ await user.profile().create({
3301
+ bio: faker.lorem.paragraph(),
3302
+ website: faker.internet.url(),
3303
+ location: faker.location.city(),
3304
+ });
3305
+ }
3306
+ });
3307
+
3308
+ // Usage with multiple states
3309
+ const user = await User.factory()
3310
+ .state("admin")
3311
+ .state("premium")
3312
+ .state("verified")
3313
+ .state("with_profile")
3314
+ .create();
3315
+ ```
3316
+
3317
+ ### Factory with Custom Logic
3318
+
3319
+ ```typescript
3320
+ export default defineFactory(Product, () => {
3321
+ const categories = ["electronics", "clothing", "books", "home", "sports"];
3322
+ const category = faker.helpers.arrayElement(categories);
3323
+
3324
+ // Category-specific logic
3325
+ const getCategorySpecificData = (cat: string) => {
3326
+ switch (cat) {
3327
+ case "electronics":
3328
+ return {
3329
+ warranty_months: faker.number.int({ min: 6, max: 36 }),
3330
+ brand: faker.helpers.arrayElement(["Apple", "Samsung", "Sony"]),
3331
+ };
3332
+ case "clothing":
3333
+ return {
3334
+ size: faker.helpers.arrayElement(["XS", "S", "M", "L", "XL"]),
3335
+ color: faker.color.human(),
3336
+ };
3337
+ case "books":
3338
+ return {
3339
+ isbn: faker.string.numeric(13),
3340
+ pages: faker.number.int({ min: 100, max: 800 }),
3341
+ };
3342
+ default:
3343
+ return {};
3344
+ }
3345
+ };
3346
+
3347
+ return {
3348
+ name: faker.commerce.productName(),
3349
+ description: faker.commerce.productDescription(),
3350
+ price: faker.commerce.price({ min: 10, max: 1000 }),
3351
+ category,
3352
+ sku: `${category.toUpperCase()}-${faker.string.alphanumeric(8)}`,
3353
+ in_stock: faker.datatype.boolean(),
3354
+ stock_quantity: faker.number.int({ min: 0, max: 100 }),
3355
+ ...getCategorySpecificData(category),
3356
+ };
3357
+ });
3358
+ ```
3359
+
3360
+ ### Factory Performance Optimization
3361
+
3362
+ ```typescript
3363
+ // Batch creation for better performance
3364
+ const users = await User.factory().times(1000).create();
3365
+
3366
+ // Create in chunks to avoid memory issues
3367
+ const createUsersInChunks = async (total: number, chunkSize: number = 100) => {
3368
+ const chunks = Math.ceil(total / chunkSize);
3369
+
3370
+ for (let i = 0; i < chunks; i++) {
3371
+ const currentBatchSize = Math.min(chunkSize, total - i * chunkSize);
3372
+ await User.factory().times(currentBatchSize).create();
3373
+ console.log(`Created chunk ${i + 1}/${chunks}`);
3374
+ }
3375
+ };
3376
+
3377
+ await createUsersInChunks(10000, 500);
3378
+
3379
+ // High-performance bulk factory
3380
+ import { BulkFactory, bulkFactory } from "ilana-orm";
3381
+
3382
+ const bulk = bulkFactory(User, (faker) => ({
3383
+ name: faker.person.fullName(),
3384
+ email: faker.internet.email(),
3385
+ }));
3386
+
3387
+ const users = await bulk.setBatchSize(1000).create(50000); // Creates 50k users efficiently
3388
+
3389
+ // Factory traits for reusable modifications
3390
+ import { trait } from "ilana-orm";
3391
+
3392
+ const premiumTrait = trait<User>((factory) => factory.state("premium"));
3393
+
3394
+ const user = await User.factory()
3395
+ .configure((factory) => premiumTrait.apply(factory))
3396
+ .create();
3397
+
3398
+ // Global sequence management
3399
+ import { globalSequence, resetGlobalSequence } from "ilana-orm";
3400
+
3401
+ const userFactory = defineFactory(User, () => ({
3402
+ name: faker.person.fullName(),
3403
+ email: `user${globalSequence("user")}@example.com`,
3404
+ }));
3405
+
3406
+ // Reset sequences when needed
3407
+ resetGlobalSequence("user");
3408
+ ```
3409
+
3410
+ ## Transactions
3411
+
3412
+ IlanaORM provides Laravel-style transaction support with automatic retry capabilities and seamless model integration.
3413
+
3414
+ **JavaScript:**
3415
+ ```javascript
3416
+ const { DB } = require('ilana-orm');
3417
+
3418
+ // Laravel-style callback transaction with automatic retry
3419
+ await DB.transaction(async () => {
3420
+ const user = await User.create({ name: 'John Doe', email: 'john@example.com' });
3421
+ const post = await Post.create({ title: 'Hello World', user_id: user.id });
3422
+
3423
+ // All operations automatically use the current transaction
3424
+ await user.update({ last_post_id: post.id });
3425
+
3426
+ if (someCondition) {
3427
+ throw new Error('Rollback transaction'); // Automatically rolls back
3428
+ }
3429
+
3430
+ // Transaction commits automatically if no errors
3431
+ });
3432
+
3433
+ // Transaction with retry attempts
3434
+ await DB.transaction(async () => {
3435
+ // Your transaction logic here
3436
+ const user = await User.create({ name: 'Jane Doe' });
3437
+ await processPayment(user);
3438
+ }, 3); // Retry up to 3 times on deadlock/serialization failures
3439
+
3440
+ // Transaction on specific connection
3441
+ await DB.transaction(async () => {
3442
+ const analyticsData = await AnalyticsEvent.create({ event: 'user_signup' });
3443
+ }, 1, 'analytics_db');
3444
+
3445
+ // Manual transaction control
3446
+ const trx = await DB.beginTransaction();
3447
+
3448
+ try {
3449
+ const user = await User.create({ name: 'Manual User' });
3450
+ const profile = await Profile.create({ user_id: user.id, bio: 'Test bio' });
3451
+
3452
+ await DB.commit();
3453
+ } catch (error) {
3454
+ await DB.rollback();
3455
+ throw error;
3456
+ }
3457
+
3458
+ // Manual transaction on specific connection
3459
+ const trx = await DB.beginTransaction('postgres_analytics');
3460
+
3461
+ try {
3462
+ // Operations on analytics database
3463
+ await AnalyticsEvent.on('postgres_analytics').create({ event: 'conversion' });
3464
+ await DB.commit();
3465
+ } catch (error) {
3466
+ await DB.rollback();
3467
+ throw error;
3468
+ }
3469
+
3470
+ // Model operations automatically detect current transaction
3471
+ const user = await User.create({ name: 'Auto Transaction' }); // Uses current transaction if active
3472
+
3473
+ // Explicit transaction passing (when needed)
3474
+ const specificTrx = await DB.beginTransaction();
3475
+ const user = await User.on(specificTrx).create({ name: 'Explicit Transaction' });
3476
+ ```
3477
+
3478
+ **TypeScript:**
3479
+
3480
+ ```typescript
3481
+ import { DB } from "ilana-orm";
3482
+
3483
+ // Laravel-style callback transaction with automatic retry
3484
+ await DB.transaction(async () => {
3485
+ const user = await User.create({ name: "John Doe", email: "john@example.com" });
3486
+ const post = await Post.create({ title: "Hello World", user_id: user.id });
3487
+
3488
+ // All operations automatically use the current transaction
3489
+ await user.update({ last_post_id: post.id });
3490
+
3491
+ if (someCondition) {
3492
+ throw new Error("Rollback transaction"); // Automatically rolls back
3493
+ }
3494
+
3495
+ // Transaction commits automatically if no errors
3496
+ });
3497
+
3498
+ // Transaction with retry attempts
3499
+ await DB.transaction(async () => {
3500
+ // Your transaction logic here
3501
+ const user = await User.create({ name: "Jane Doe" });
3502
+ await processPayment(user);
3503
+ }, 3); // Retry up to 3 times on deadlock/serialization failures
3504
+
3505
+ // Transaction on specific connection
3506
+ await DB.transaction(async () => {
3507
+ const analyticsData = await AnalyticsEvent.create({ event: "user_signup" });
3508
+ }, 1, "analytics_db");
3509
+
3510
+ // Manual transaction control
3511
+ const trx = await DB.beginTransaction();
3512
+
3513
+ try {
3514
+ const user = await User.create({ name: "Manual User" });
3515
+ const profile = await Profile.create({ user_id: user.id, bio: "Test bio" });
3516
+
3517
+ await DB.commit();
3518
+ } catch (error) {
3519
+ await DB.rollback();
3520
+ throw error;
3521
+ }
3522
+
3523
+ // Manual transaction on specific connection
3524
+ const trx = await DB.beginTransaction("postgres_analytics");
3525
+
3526
+ try {
3527
+ // Operations on analytics database
3528
+ await AnalyticsEvent.on("postgres_analytics").create({ event: "conversion" });
3529
+ await DB.commit();
3530
+ } catch (error) {
3531
+ await DB.rollback();
3532
+ throw error;
3533
+ }
3534
+
3535
+ // Model operations automatically detect current transaction
3536
+ const user = await User.create({ name: "Auto Transaction" }); // Uses current transaction if active
3537
+
3538
+ // Explicit transaction passing (when needed)
3539
+ const specificTrx = await DB.beginTransaction();
3540
+ const user = await User.on(specificTrx).create({ name: "Explicit Transaction" });
3541
+ ```
3542
+
3543
+ ### Transaction Features
3544
+
3545
+ - **Automatic Integration**: Model operations automatically detect and use the current transaction
3546
+ - **Retry Logic**: Built-in retry mechanism for deadlock and serialization failures
3547
+ - **Connection Support**: Transactions work with multiple database connections
3548
+ - **Laravel Compatibility**: Exact same API as Laravel's database transactions
3549
+ - **Error Handling**: Automatic rollback on exceptions, manual control when needed
3550
+ - **Nested Transactions**: Support for savepoints in databases that support them
3551
+
3552
+ ### Transaction Best Practices
3553
+
3554
+ ```typescript
3555
+ // ✅ Good: Use callback style for automatic management
3556
+ await DB.transaction(async () => {
3557
+ const user = await User.create(userData);
3558
+ await sendWelcomeEmail(user.email);
3559
+ await logUserCreation(user.id);
3560
+ });
3561
+
3562
+ // ✅ Good: Specify retry attempts for critical operations
3563
+ await DB.transaction(async () => {
3564
+ await processPayment(paymentData);
3565
+ await updateInventory(items);
3566
+ }, 3); // Retry up to 3 times
3567
+
3568
+ // ✅ Good: Use manual control for complex scenarios
3569
+ const trx = await DB.beginTransaction();
3570
+ try {
3571
+ const result = await complexOperation();
3572
+ if (result.needsApproval) {
3573
+ // Don't commit yet, wait for approval
3574
+ return { transaction: trx, result };
3575
+ }
3576
+ await DB.commit();
3577
+ } catch (error) {
3578
+ await DB.rollback();
3579
+ throw error;
3580
+ }
3581
+
3582
+ // ❌ Avoid: Long-running transactions
3583
+ // await DB.transaction(async () => {
3584
+ // await processLargeDataset(); // This could take minutes
3585
+ // });
3586
+
3587
+ // ❌ Avoid: Transactions for read-only operations
3588
+ // await DB.transaction(async () => {
3589
+ // const users = await User.all(); // No need for transaction
3590
+ // return users;
3591
+ // });
3592
+ ```
3593
+
3594
+ ## Advanced Features
3595
+
3596
+ ### Collections
3597
+
3598
+ IlanaORM returns Laravel-style Collections with powerful data manipulation methods:
3599
+
3600
+ **JavaScript:**
3601
+
3602
+ ```javascript
3603
+ // Query returns Collection instance
3604
+ const users = await User.all(); // Returns Collection
3605
+
3606
+ // Static factory methods
3607
+ const collection = Collection.make([1, 2, 3, 4, 5]);
3608
+ const numbers = Collection.range(1, 10);
3609
+ const items = Collection.times(5, (i) => ({ id: i, name: `Item ${i}` }));
3610
+
3611
+ // Data manipulation
3612
+ const activeUsers = users.filter((user) => user.is_active);
3613
+ const userNames = users.pluck("name");
3614
+ const uniqueRoles = users.pluck("role").unique();
3615
+ const usersByRole = users.groupBy("role");
3616
+ const sortedUsers = users.sortBy("created_at");
3617
+
3618
+ // Advanced operations
3619
+ const [admins, regular] = users.partition((user) => user.is_admin);
3620
+ const userMap = users.keyBy("id");
3621
+ const roleCounts = users.countBy("role");
3622
+ const randomUsers = users.random(3);
3623
+ const shuffled = users.shuffle();
3624
+
3625
+ // Functional programming
3626
+ const result = users
3627
+ .filter((user) => user.is_active)
3628
+ .take(10)
3629
+ .tap((collection) => console.log(`Processing ${collection.length} users`))
3630
+ .pipe((collection) => collection.pluck("email"))
3631
+ .when(someCondition, (collection) => collection.unique())
3632
+ .unless(otherCondition, (collection) => collection.shuffle());
3633
+
3634
+ // Aggregations
3635
+ const totalSalary = users.sum("salary");
3636
+ const averageAge = users.avg("age");
3637
+ const oldestUser = users.max("age");
3638
+ const youngestUser = users.min("age");
3639
+
3640
+ // Chunking
3641
+ const chunks = users.chunk(100);
3642
+ chunks.forEach((chunk) => {
3643
+ console.log(`Processing chunk of ${chunk.length} users`);
3644
+ });
3645
+
3646
+ // Conditional operations
3647
+ users
3648
+ .whenEmpty((collection) => console.log("No users found"))
3649
+ .whenNotEmpty((collection) =>
3650
+ console.log(`Found ${collection.length} users`)
3651
+ );
3652
+ ```
3653
+
3654
+ **TypeScript:**
3655
+
3656
+ ```typescript
3657
+ // Query returns Collection instance
3658
+ const users = await User.all(); // Returns Collection<User>
3659
+
3660
+ // Static factory methods
3661
+ const collection = Collection.make([1, 2, 3, 4, 5]);
3662
+ const numbers = Collection.range(1, 10);
3663
+ const items = Collection.times(5, (i) => ({ id: i, name: `Item ${i}` }));
3664
+
3665
+ // Data manipulation
3666
+ const activeUsers = users.filter((user) => user.is_active);
3667
+ const userNames = users.pluck("name");
3668
+ const uniqueRoles = users.pluck("role").unique();
3669
+ const usersByRole = users.groupBy("role");
3670
+ const sortedUsers = users.sortBy("created_at");
3671
+
3672
+ // Advanced operations
3673
+ const [admins, regular] = users.partition((user) => user.is_admin);
3674
+ const userMap = users.keyBy("id");
3675
+ const roleCounts = users.countBy("role");
3676
+ const randomUsers = users.random(3);
3677
+ const shuffled = users.shuffle();
3678
+
3679
+ // Functional programming
3680
+ const result = users
3681
+ .filter((user) => user.is_active)
3682
+ .take(10)
3683
+ .tap((collection) => console.log(`Processing ${collection.length} users`))
3684
+ .pipe((collection) => collection.pluck("email"))
3685
+ .when(someCondition, (collection) => collection.unique())
3686
+ .unless(otherCondition, (collection) => collection.shuffle());
3687
+
3688
+ // Aggregations
3689
+ const totalSalary = users.sum("salary");
3690
+ const averageAge = users.avg("age");
3691
+ const oldestUser = users.max("age");
3692
+ const youngestUser = users.min("age");
3693
+
3694
+ // Chunking
3695
+ const chunks = users.chunk(100);
3696
+ chunks.forEach((chunk) => {
3697
+ console.log(`Processing chunk of ${chunk.length} users`);
3698
+ });
3699
+
3700
+ // Conditional operations
3701
+ users
3702
+ .whenEmpty((collection) => console.log("No users found"))
3703
+ .whenNotEmpty((collection) =>
3704
+ console.log(`Found ${collection.length} users`)
3705
+ );
3706
+ ```
3707
+
3708
+ ````
3709
+
3710
+ ### Soft Deletes
3711
+
3712
+ **JavaScript:**
3713
+ ```javascript
3714
+ class User extends Model {
3715
+ static softDeletes = true;
3716
+ }
3717
+
3718
+ // Soft delete
3719
+ await user.delete(); // Sets deleted_at timestamp
3720
+
3721
+ // Query with trashed records
3722
+ const users = await User.withTrashed().get();
3723
+
3724
+ // Only trashed records
3725
+ const trashedUsers = await User.onlyTrashed().get();
3726
+
3727
+ // Restore soft deleted record
3728
+ await user.restore();
3729
+
3730
+ // Force delete (permanent)
3731
+ await user.forceDelete();
3732
+
3733
+ // Check if model is trashed
3734
+ if (user.trashed()) {
3735
+ console.log('User is soft deleted');
3736
+ }
3737
+ ````
3738
+
3739
+ **TypeScript:**
3740
+
3741
+ ```typescript
3742
+ class User extends Model {
3743
+ protected static softDeletes = true;
3744
+ }
3745
+
3746
+ // Soft delete
3747
+ await user.delete(); // Sets deleted_at timestamp
3748
+
3749
+ // Query with trashed records
3750
+ const users = await User.withTrashed().get();
3751
+
3752
+ // Only trashed records
3753
+ const trashedUsers = await User.onlyTrashed().get();
3754
+
3755
+ // Restore soft deleted record
3756
+ await user.restore();
3757
+
3758
+ // Force delete (permanent)
3759
+ await user.forceDelete();
3760
+
3761
+ // Check if model is trashed
3762
+ if (user.trashed()) {
3763
+ console.log("User is soft deleted");
3764
+ }
3765
+ ```
3766
+
3767
+ ````
3768
+
3769
+ ### Pagination
3770
+
3771
+ **JavaScript:**
3772
+ ```javascript
3773
+ // Basic pagination
3774
+ const result = await User.query().paginate(1, 15);
3775
+ // {
3776
+ // data: User[],
3777
+ // total: 100,
3778
+ // perPage: 15,
3779
+ // currentPage: 1,
3780
+ // lastPage: 7,
3781
+ // from: 1,
3782
+ // to: 15,
3783
+ // nextPage: 2
3784
+ // }
3785
+
3786
+ // Simple pagination (no total count)
3787
+ const result = await User.query().simplePaginate(1, 15);
3788
+ // { data: User[], hasMore: boolean }
3789
+
3790
+ // Enhanced cursor pagination (for large datasets)
3791
+ const result = await User.query()
3792
+ .orderBy('id', 'desc')
3793
+ .cursorPaginate(15, cursor, 'id', 'desc');
3794
+ // {
3795
+ // data: User[],
3796
+ // nextCursor?: string,
3797
+ // prevCursor?: string,
3798
+ // hasNextPage: boolean,
3799
+ // hasPrevPage: boolean,
3800
+ // path: string,
3801
+ // perPage: number
3802
+ // }
3803
+ ````
3804
+
3805
+ **TypeScript:**
3806
+
3807
+ ```typescript
3808
+ // Basic pagination
3809
+ const result = await User.query().paginate(1, 15);
3810
+ // {
3811
+ // data: User[],
3812
+ // total: 100,
3813
+ // perPage: 15,
3814
+ // currentPage: 1,
3815
+ // lastPage: 7,
3816
+ // from: 1,
3817
+ // to: 15,
3818
+ // nextPage: 2
3819
+ // }
3820
+
3821
+ // Simple pagination (no total count)
3822
+ const result = await User.query().simplePaginate(1, 15);
3823
+ // { data: User[], hasMore: boolean }
3824
+
3825
+ // Enhanced cursor pagination (for large datasets)
3826
+ const result = await User.query()
3827
+ .orderBy("id", "desc")
3828
+ .cursorPaginate(15, cursor, "id", "desc");
3829
+ // {
3830
+ // data: User[],
3831
+ // nextCursor?: string,
3832
+ // prevCursor?: string,
3833
+ // hasNextPage: boolean,
3834
+ // hasPrevPage: boolean,
3835
+ // path: string,
3836
+ // perPage: number
3837
+ // }
3838
+ ```
3839
+
3840
+ ````
3841
+
3842
+ ### Chunking
3843
+
3844
+ **JavaScript:**
3845
+ ```javascript
3846
+ // Process records in chunks
3847
+ await User.query().chunk(100, async (users) => {
3848
+ for (const user of users) {
3849
+ await processUser(user);
3850
+ }
3851
+ });
3852
+
3853
+ // Memory efficient iteration with configurable chunk size
3854
+ for await (const user of User.query().lazy(500)) {
3855
+ await processUser(user);
3856
+ }
3857
+
3858
+ // Cursor-based iteration for large datasets
3859
+ for await (const user of User.query().cursor(1000)) {
3860
+ await processUser(user);
3861
+ }
3862
+ ````
3863
+
3864
+ **TypeScript:**
3865
+
3866
+ ```typescript
3867
+ // Process records in chunks
3868
+ await User.query().chunk(100, async (users) => {
3869
+ for (const user of users) {
3870
+ await processUser(user);
3871
+ }
3872
+ });
3873
+
3874
+ // Memory efficient iteration with configurable chunk size
3875
+ for await (const user of User.query().lazy(500)) {
3876
+ await processUser(user);
3877
+ }
3878
+
3879
+ // Cursor-based iteration for large datasets
3880
+ for await (const user of User.query().cursor(1000)) {
3881
+ await processUser(user);
3882
+ }
3883
+ ```
3884
+
3885
+ ````
3886
+
3887
+ ### Transactions
3888
+
3889
+ **JavaScript:**
3890
+ ```javascript
3891
+ const { Database } = require('ilana-orm');
3892
+
3893
+ // Basic transaction
3894
+ await Database.transaction(async (trx) => {
3895
+ const user = await User.create({ name: 'John' });
3896
+ const post = await Post.create({ title: 'Hello', user_id: user.id });
3897
+
3898
+ if (someCondition) {
3899
+ throw new Error('Rollback transaction');
3900
+ }
3901
+ });
3902
+
3903
+ // Manual transaction control
3904
+ const trx = await Database.beginTransaction();
3905
+
3906
+ try {
3907
+ const user = await User.create({ name: 'John' }, { transaction: trx });
3908
+ await trx.commit();
3909
+ } catch (error) {
3910
+ await trx.rollback();
3911
+ throw error;
3912
+ }
3913
+ ````
3914
+
3915
+ **TypeScript:**
3916
+
3917
+ ```typescript
3918
+ import { Database } from "ilana-orm";
3919
+
3920
+ // Basic transaction
3921
+ await Database.transaction(async (trx) => {
3922
+ const user = await User.create({ name: "John" });
3923
+ const post = await Post.create({ title: "Hello", user_id: user.id });
3924
+
3925
+ if (someCondition) {
3926
+ throw new Error("Rollback transaction");
3927
+ }
3928
+ });
3929
+
3930
+ // Manual transaction control
3931
+ const trx = await Database.beginTransaction();
3932
+
3933
+ try {
3934
+ const user = await User.create({ name: "John" }, { transaction: trx });
3935
+ await trx.commit();
3936
+ } catch (error) {
3937
+ await trx.rollback();
3938
+ throw error;
3939
+ }
3940
+ ```
3941
+
3942
+ ````
3943
+
3944
+ ### Multiple Database Connections
3945
+
3946
+ **JavaScript:**
3947
+ ```javascript
3948
+ // Use specific connection
3949
+ const users = await User.on('mysql_secondary').get();
3950
+
3951
+ // Model with specific connection
3952
+ class AnalyticsData extends Model {
3953
+ static connection = 'analytics_db';
3954
+ }
3955
+
3956
+ // Runtime connection switching
3957
+ const users = await User.query().on('reporting_db').get();
3958
+ ````
3959
+
3960
+ **TypeScript:**
3961
+
3962
+ ```typescript
3963
+ // Use specific connection
3964
+ const users = await User.on("mysql_secondary").get();
3965
+
3966
+ // Model with specific connection
3967
+ class AnalyticsData extends Model {
3968
+ protected static connection = "analytics_db";
3969
+ }
3970
+
3971
+ // Runtime connection switching
3972
+ const users = await User.query().on("reporting_db").get();
3973
+ ```
3974
+
3975
+ ````
3976
+
3977
+ ## TypeScript Support
3978
+
3979
+ ### Type-Safe Models
3980
+
3981
+ ```typescript
3982
+ import { Model } from "ilana-orm";
3983
+
3984
+ interface UserAttributes {
3985
+ id: number;
3986
+ name: string;
3987
+ email: string;
3988
+ age?: number;
3989
+ is_active: boolean;
3990
+ created_at: Date;
3991
+ updated_at: Date;
3992
+ }
3993
+
3994
+ class User extends Model<UserAttributes> {
3995
+ protected static table = "users";
3996
+
3997
+ // Typed attributes
3998
+ declare id: number;
3999
+ declare name: string;
4000
+ declare email: string;
4001
+ declare age?: number;
4002
+ declare is_active: boolean;
4003
+ declare created_at: Date;
4004
+ declare updated_at: Date;
4005
+
4006
+ // Typed relationships
4007
+ posts(): HasMany<Post> {
4008
+ return this.hasMany(Post, "user_id");
4009
+ }
4010
+ }
4011
+ ````
4012
+
4013
+ ### Generic Query Builder
4014
+
4015
+ ```typescript
4016
+ // Type-safe queries
4017
+ const users: User[] = await User.query()
4018
+ .where("is_active", true)
4019
+ .where("age", ">", 18)
4020
+ .get();
4021
+
4022
+ // Type-safe factory
4023
+ const user = await User.factory().create({
4024
+ name: "John Doe", // TypeScript validates this
4025
+ email: "john@example.com",
4026
+ });
4027
+ ```
4028
+
4029
+ ## Performance & Best Practices
4030
+
4031
+ ### Query Optimization
4032
+
4033
+ ```typescript
4034
+ // Use select to limit columns
4035
+ const users = await User.query().select("id", "name", "email").get();
4036
+
4037
+ // Avoid N+1 queries with eager loading
4038
+ const users = await User.with("posts", "profile").get();
4039
+
4040
+ // Use exists instead of loading relations for checks
4041
+ const usersWithPosts = await User.query().whereHas("posts").get();
4042
+
4043
+ // Use chunking for large datasets
4044
+ await User.query().chunk(1000, async (users) => {
4045
+ await processUsers(users);
4046
+ });
4047
+ ```
4048
+
4049
+ ### Security Best Practices
4050
+
4051
+ ```typescript
4052
+ class User extends Model {
4053
+ // Always use fillable or guarded
4054
+ protected fillable = ["name", "email", "password"];
4055
+
4056
+ // Hide sensitive data
4057
+ protected hidden = ["password", "remember_token"];
4058
+
4059
+ // Use mutators for sensitive data
4060
+ setPasswordAttribute(value: string) {
4061
+ this.attributes.password = bcrypt.hashSync(value, 10);
4062
+ }
4063
+ }
4064
+
4065
+ // Use parameterized queries
4066
+ const users = await User.query()
4067
+ .where("email", email) // Safe
4068
+ .get();
4069
+
4070
+ // Avoid raw queries with user input
4071
+ // Bad: whereRaw(`name = '${userInput}'`)
4072
+ // Good: whereRaw('name = ?', [userInput])
4073
+ ```
4074
+
4075
+ ### Model Organization
4076
+
4077
+ ```typescript
4078
+ class User extends Model {
4079
+ // 1. Static properties
4080
+ protected static table = "users";
4081
+ protected static softDeletes = true;
4082
+
4083
+ // 2. Instance properties
4084
+ protected fillable = ["name", "email"];
4085
+ protected hidden = ["password"];
4086
+ protected casts = {
4087
+ email_verified_at: "date" as const,
4088
+ };
4089
+
4090
+ // 3. Relationships
4091
+ posts() {
4092
+ return this.hasMany(Post, "user_id");
4093
+ }
4094
+
4095
+ // 4. Scopes
4096
+ static scopeActive(query: any) {
4097
+ return query.where("is_active", true);
4098
+ }
4099
+
4100
+ // 5. Accessors/Mutators
4101
+ getFullNameAttribute(): string {
4102
+ return `${this.first_name} ${this.last_name}`;
4103
+ }
4104
+
4105
+ // 6. Custom methods
4106
+ async sendWelcomeEmail(): Promise<void> {
4107
+ // Implementation
4108
+ }
4109
+ }
4110
+ ```
4111
+
4112
+ ## Testing
4113
+
4114
+ ### Model Testing
4115
+
4116
+ ```typescript
4117
+ import { describe, it, beforeEach, afterEach } from "mocha";
4118
+ import { expect } from "chai";
4119
+ import User from "../models/User";
4120
+ import { Database } from "ilana-orm";
4121
+
4122
+ describe("User Model", () => {
4123
+ beforeEach(async () => {
4124
+ await Database.migrate();
4125
+ });
4126
+
4127
+ afterEach(async () => {
4128
+ await Database.rollback();
4129
+ });
4130
+
4131
+ it("should create a user", async () => {
4132
+ const user = await User.create({
4133
+ name: "John Doe",
4134
+ email: "john@example.com",
4135
+ password: "password",
4136
+ });
4137
+
4138
+ expect(user.id).to.exist;
4139
+ expect(user.name).to.equal("John Doe");
4140
+ });
4141
+
4142
+ it("should have posts relationship", async () => {
4143
+ const user = await User.factory().create();
4144
+ await Post.factory().times(3).create({ user_id: user.id });
4145
+
4146
+ const userWithPosts = await User.with("posts").find(user.id);
4147
+ expect(userWithPosts.posts).to.have.length(3);
4148
+ });
4149
+ });
4150
+ ```
4151
+
4152
+ ### Factory Testing
4153
+
4154
+ ```typescript
4155
+ describe("User Factory", () => {
4156
+ it("should create user with factory", async () => {
4157
+ const user = await User.factory().create();
4158
+
4159
+ expect(user.name).to.exist;
4160
+ expect(user.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
4161
+ });
4162
+
4163
+ it("should create admin user with state", async () => {
4164
+ const admin = await User.factory().state("admin").create();
4165
+
4166
+ expect(admin.role).to.equal("admin");
4167
+ expect(admin.is_admin).to.be.true;
4168
+ });
4169
+ });
4170
+ ```
4171
+
4172
+ ## Complete API Reference
4173
+
4174
+ ### Model API
4175
+
4176
+ #### Static Methods
4177
+
4178
+ ```javascript
4179
+ // Query methods
4180
+ User.all(); // Get all records
4181
+ User.find(id); // Find by primary key
4182
+ User.findBy(column, value); // Find by column value
4183
+ User.findOrFail(id); // Find or throw error
4184
+ User.first(); // Get first record
4185
+ User.firstOrFail(); // First or throw error
4186
+ User.latest(column); // Order by column desc
4187
+ User.oldest(column); // Order by column asc
4188
+ User.query(); // Get query builder
4189
+ User.with(...relations); // Eager load relations
4190
+ User.withCount(...relations); // Load relation counts
4191
+ User.on(connection); // Use specific connection
4192
+
4193
+ // Creation methods
4194
+ User.make(attributes); // Create new instance (not saved)
4195
+ User.create(attributes); // Create new record
4196
+ User.insert(data); // Insert raw data
4197
+ User.insertGetId(data); // Insert and get ID
4198
+ User.firstOrCreate(search, create); // Find or create
4199
+ User.firstOrNew(search, create); // Find or new instance
4200
+ User.updateOrCreate(search, update); // Update or create
4201
+ User.firstOrNew(search, create); // Find or new instance
4202
+ User.upsert(data, unique, update); // Upsert records
4203
+
4204
+ // Deletion methods
4205
+ User.destroy(ids); // Delete by IDs
4206
+ User.withTrashed(); // Include soft deleted
4207
+ User.onlyTrashed(); // Only soft deleted
4208
+ User.withoutTrashed(); // Exclude soft deleted
4209
+
4210
+ // Configuration
4211
+ User.getTableName(); // Get table name
4212
+ User.getPrimaryKey(); // Get primary key
4213
+ User.getKeyType(); // Get key type
4214
+ User.getIncrementing(); // Get incrementing flag
4215
+ User.getConnectionName(); // Get connection name
4216
+ User.generateUuid(); // Generate UUID
4217
+
4218
+ // Events
4219
+ User.creating(callback); // Before creating
4220
+ User.created(callback); // After creating
4221
+ User.updating(callback); // Before updating
4222
+ User.updated(callback); // After updating
4223
+ User.saving(callback); // Before saving
4224
+ User.saved(callback); // After saving
4225
+ User.deleting(callback); // Before deleting
4226
+ User.deleted(callback); // After deleting
4227
+ User.restoring(callback); // Before restoring
4228
+ User.restored(callback); // After restoring
4229
+ User.observe(observer); // Register observer
4230
+ User.fireEvent(event, model); // Fire event
4231
+
4232
+ // Scopes
4233
+ User.addGlobalScope(name, scope); // Add global scope
4234
+ User.removeGlobalScope(name); // Remove global scope
4235
+ User.withoutGlobalScope(name); // Query without scope
4236
+ User.withoutGlobalScope(name); // Query without scope
4237
+
4238
+ // Factory
4239
+ User.factory(); // Get factory instance
4240
+ User.register(); // Register in registry
4241
+ ```
4242
+
4243
+ #### Instance Methods
4244
+
4245
+ ```javascript
4246
+ // Persistence
4247
+ user.save(); // Save model
4248
+ user.update(attributes); // Update model
4249
+ user.delete(); // Delete model
4250
+ user.forceDelete(); // Force delete
4251
+ user.restore(); // Restore soft deleted
4252
+ user.forceDelete(); // Force delete
4253
+ user.restore(); // Restore soft deleted
4254
+
4255
+ // Attributes
4256
+ user.fill(attributes); // Mass assign
4257
+ user.getAttribute(key); // Get attribute
4258
+ user.setAttribute(key, value); // Set attribute
4259
+ user.getKey(); // Get primary key value
4260
+ user.isFillable(key); // Check if fillable
4261
+ user.isDirty(key); // Check if dirty
4262
+ user.getDirty(); // Get dirty attributes
4263
+ user.getOriginal(key); // Get original value
4264
+ user.syncOriginal(); // Sync original
4265
+ user.only(keys); // Get only specified attributes
4266
+ user.except(keys); // Get all except specified
4267
+ user.trashed(); // Check if soft deleted
4268
+ user.trashed(); // Check if soft deleted
4269
+
4270
+ // Relations
4271
+ user.load(...relations); // Lazy load relations
4272
+ user.loadMissing(...relations); // Load missing relations
4273
+ user.getRelation(key); // Get loaded relation
4274
+ user.relationLoaded(key); // Check if loaded
4275
+
4276
+ // Serialization
4277
+ user.toJSON(); // Convert to JSON
4278
+ user.toArray(); // Convert to array
4279
+ user.makeHidden(attributes); // Hide attributes
4280
+ user.makeVisible(attributes); // Show attributes
4281
+ user.append(attributes); // Append accessors
4282
+ ```
4283
+
4284
+ ### QueryBuilder API
4285
+
4286
+ #### Where Clauses
4287
+
4288
+ ```javascript
4289
+ query.where(column, operator, value);
4290
+ query.where(column, value); // Equals operator
4291
+ query.orWhere(column, operator, value);
4292
+ query.whereIn(column, values);
4293
+ query.whereNotIn(column, values);
4294
+ query.whereNull(column);
4295
+ query.whereNotNull(column);
4296
+ query.whereBetween(column, [min, max]);
4297
+ query.whereNotBetween(column, [min, max]);
4298
+ query.whereRaw(sql, bindings);
4299
+ query.orWhereRaw(sql, bindings);
4300
+
4301
+ // JSON queries
4302
+ query.whereJsonContains(column, value);
4303
+ query.whereJsonLength(column, operator, value);
4304
+
4305
+ // Date queries
4306
+ query.whereDate(column, date);
4307
+ query.whereMonth(column, month);
4308
+ query.whereYear(column, year);
4309
+ query.whereDay(column, day);
4310
+ query.whereTime(column, time);
4311
+
4312
+ // Subqueries
4313
+ query.whereExists(callback);
4314
+ query.whereNotExists(callback);
4315
+ query.whereIn(column, subquery);
4316
+ query.whereNotIn(column, subquery);
4317
+
4318
+ // Conditional
4319
+ query.when(condition, callback, otherwise);
4320
+ query.unless(condition, callback, otherwise);
4321
+ ```
4322
+
4323
+ #### Joins
4324
+
4325
+ ```javascript
4326
+ query.join(table, first, operator, second);
4327
+ query.leftJoin(table, first, operator, second);
4328
+ query.rightJoin(table, first, operator, second);
4329
+ query.crossJoin(table);
4330
+ query.innerJoin(table, first, operator, second);
4331
+ ```
4332
+
4333
+ #### Ordering & Grouping
4334
+
4335
+ ```javascript
4336
+ query.orderBy(column, direction);
4337
+ query.latest(column);
4338
+ query.oldest(column);
4339
+ query.inRandomOrder();
4340
+ query.groupBy(...columns);
4341
+ query.having(column, operator, value);
4342
+ query.havingRaw(sql, bindings);
4343
+ ```
4344
+
4345
+ #### Limiting & Offsetting
4346
+
4347
+ ```javascript
4348
+ query.limit(count);
4349
+ query.offset(count);
4350
+ query.take(count);
4351
+ query.skip(count);
4352
+ query.forPage(page, perPage);
4353
+ ```
4354
+
4355
+ #### Selection
4356
+
4357
+ ```javascript
4358
+ query.select(...columns);
4359
+ query.selectRaw(sql, bindings);
4360
+ query.distinct();
4361
+ query.addSelect(...columns);
4362
+ ```
4363
+
4364
+ #### Aggregates
4365
+
4366
+ ```javascript
4367
+ query.count(column);
4368
+ query.sum(column);
4369
+ query.avg(column);
4370
+ query.min(column);
4371
+ query.max(column);
4372
+ ```
4373
+
4374
+ #### Execution
4375
+
4376
+ ```javascript
4377
+ query.get(); // Get all results
4378
+ query.first(); // Get first result
4379
+ query.firstOrFail(); // First or throw
4380
+ query.find(id); // Find by ID
4381
+ query.findOrFail(id); // Find or throw
4382
+ query.pluck(column); // Get column values
4383
+ query.exists(); // Check existence
4384
+ query.doesntExist(); // Check non-existence
4385
+ ```
4386
+
4387
+ #### Pagination
4388
+
4389
+ ```javascript
4390
+ query.paginate(page, perPage); // Standard pagination
4391
+ query.simplePaginate(page, perPage); // Simple pagination
4392
+ query.cursorPaginate(perPage, cursor, column, direction);
4393
+ ```
4394
+
4395
+ #### Chunking
4396
+
4397
+ ```javascript
4398
+ query.chunk(size, callback); // Process in chunks
4399
+ query.cursor(chunkSize); // Cursor iteration
4400
+ query.lazy(chunkSize); // Lazy iteration
4401
+ ```
4402
+
4403
+ #### Modification
4404
+
4405
+ ```javascript
4406
+ query.insert(data);
4407
+ query.insertGetId(data);
4408
+ query.update(data);
4409
+ query.delete();
4410
+ query.upsert(data, uniqueBy, update);
4411
+ ```
4412
+
4413
+ #### Eager Loading
4414
+
4415
+ ```javascript
4416
+ query.with(...relations);
4417
+ query.withConstraints(relation, callback);
4418
+ query.withCount(...relations);
4419
+ query.whereHas(relation, callback);
4420
+ query.whereDoesntHave(relation);
4421
+ query.has(relation, operator, count);
4422
+ ```
4423
+
4424
+ #### Locking
4425
+
4426
+ ```javascript
4427
+ query.lockForUpdate();
4428
+ query.sharedLock();
4429
+ query.skipLocked();
4430
+ query.noWait();
4431
+ ```
4432
+
4433
+ ### Relationship API
4434
+
4435
+ #### Defining Relations
4436
+
4437
+ ```javascript
4438
+ // One-to-One
4439
+ this.hasOne(related, foreignKey, localKey);
4440
+ this.belongsTo(related, foreignKey, ownerKey);
4441
+
4442
+ // One-to-Many
4443
+ this.hasMany(related, foreignKey, localKey);
4444
+
4445
+ // Many-to-Many
4446
+ this.belongsToMany(
4447
+ related,
4448
+ pivotTable,
4449
+ foreignPivotKey,
4450
+ relatedPivotKey,
4451
+ parentKey,
4452
+ relatedKey
4453
+ )
4454
+ .withPivot(...columns)
4455
+ .withTimestamps();
4456
+
4457
+ // Has-Many-Through
4458
+ this.hasManyThrough(
4459
+ related,
4460
+ through,
4461
+ firstKey,
4462
+ secondKey,
4463
+ localKey,
4464
+ secondLocalKey
4465
+ );
4466
+
4467
+ // Polymorphic
4468
+ this.morphTo(morphType, morphId);
4469
+ this.morphMany(related, morphType, morphId);
4470
+ ```
4471
+
4472
+ #### Relationship Methods
4473
+
4474
+ ```javascript
4475
+ // BelongsToMany specific
4476
+ relation.attach(id, attributes);
4477
+ relation.detach(id);
4478
+ relation.sync(ids);
4479
+ relation.toggle(ids);
4480
+ relation.updateExistingPivot(id, attributes);
4481
+ ```
4482
+
4483
+ ### Collection API
4484
+
4485
+ ```javascript
4486
+ // Static methods
4487
+ Collection.make(items);
4488
+ Collection.times(count, callback);
4489
+ Collection.range(start, end);
4490
+
4491
+ // Instance methods
4492
+ collection.filter(callback);
4493
+ collection.map(callback);
4494
+ collection.reduce(callback, initial);
4495
+ collection.first();
4496
+ collection.last();
4497
+ collection.pluck(key);
4498
+ collection.unique(key);
4499
+ collection.groupBy(key);
4500
+ collection.sortBy(key);
4501
+ collection.sortByDesc(key);
4502
+ collection.where(key, value);
4503
+ collection.firstWhere(key, value);
4504
+ collection.sum(key);
4505
+ collection.avg(key);
4506
+ collection.min(key);
4507
+ collection.max(key);
4508
+ collection.chunk(size);
4509
+ collection.reject(callback);
4510
+ collection.partition(callback);
4511
+ collection.keyBy(key);
4512
+ collection.countBy(key);
4513
+ collection.flatten();
4514
+ collection.take(count);
4515
+ collection.skip(count);
4516
+ collection.random(count);
4517
+ collection.shuffle();
4518
+ collection.tap(callback);
4519
+ collection.pipe(callback);
4520
+ collection.whenEmpty(callback);
4521
+ collection.whenNotEmpty(callback);
4522
+ collection.unless(condition, callback);
4523
+ collection.when(condition, callback);
4524
+ collection.toArray();
4525
+ collection.isEmpty();
4526
+ collection.isNotEmpty();
4527
+ ```
4528
+
4529
+ ### Factory API
4530
+
4531
+ ```javascript
4532
+ // Definition
4533
+ defineFactory(Model, definition);
4534
+ factory(Model);
4535
+ factoryForModel(Model);
4536
+ createFactory(Model, definition);
4537
+
4538
+ // Factory methods
4539
+ factory.state(name, definition);
4540
+ factory.afterCreating(callback);
4541
+ factory.afterMaking(callback);
4542
+ factory.beforeCreating(callback);
4543
+ factory.beforeMaking(callback);
4544
+ factory.times(count);
4545
+ factory.as(state);
4546
+ factory.for(relation, factory);
4547
+ factory.has(factory, relation);
4548
+ factory.hasAttached(factory, relation);
4549
+ factory.sequence();
4550
+ factory.sequenceFor(key);
4551
+ factory.resetSequence(key);
4552
+ factory.configure(callback);
4553
+ factory.when(condition, callback);
4554
+ factory.unless(condition, callback);
4555
+
4556
+ // Creation methods
4557
+ factory.raw(attributes);
4558
+ factory.make(attributes);
4559
+ factory.create(attributes);
4560
+ factory.createWithRelations(attributes, relations);
4561
+ factory.createInBatches(batchSize, attributes);
4562
+
4563
+ // Bulk factory
4564
+ bulkFactory(Model, definition);
4565
+ bulkFactory.setBatchSize(size);
4566
+ bulkFactory.create(count);
4567
+
4568
+ // Global sequences
4569
+ globalSequence(key);
4570
+ resetGlobalSequence(key);
4571
+ ```
4572
+
4573
+ ### Database API
4574
+
4575
+ ```javascript
4576
+ // Configuration
4577
+ Database.configure(config);
4578
+ Database.connection(name);
4579
+ Database.getDefaultConnection();
4580
+ Database.hasConnection(name);
4581
+ Database.getInstance();
4582
+
4583
+ // Transactions
4584
+ Database.transaction(callback, attempts, connection);
4585
+ Database.beginTransaction(connection);
4586
+ Database.commit(trx);
4587
+ Database.rollback(trx);
4588
+ Database.getCurrentTransaction();
4589
+
4590
+ // Query methods
4591
+ Database.table(tableName, connection);
4592
+ Database.raw(sql, bindings);
4593
+ ```
4594
+
4595
+ ### DB Facade API
4596
+
4597
+ ```javascript
4598
+ // Laravel-style transaction methods
4599
+ DB.transaction(callback, attempts, connection); // Execute transaction with retry
4600
+ DB.beginTransaction(connection); // Begin manual transaction
4601
+ DB.commit(trx); // Commit transaction
4602
+ DB.rollback(trx); // Rollback transaction
4603
+
4604
+ // Query methods
4605
+ DB.table(table, connection); // Get query builder for table
4606
+ DB.raw(sql, bindings, connection); // Execute raw SQL
4607
+ DB.connection(name); // Get specific connection
4608
+ DB.getDefaultConnection(); // Get default connection name
4609
+ ```
4610
+
4611
+ ### SchemaBuilder API
4612
+
4613
+ ```javascript
4614
+ // Table operations
4615
+ schema.createTable(name, callback);
4616
+ schema.dropTable(name);
4617
+ schema.dropTableIfExists(name);
4618
+ schema.renameTable(from, to);
4619
+ schema.hasTable(name);
4620
+ schema.hasColumn(table, column);
4621
+ schema.table(name, callback);
4622
+ schema.alterTable(name, callback);
4623
+
4624
+ // Column types
4625
+ table.increments(name);
4626
+ table.bigIncrements(name);
4627
+ table.string(name, length);
4628
+ table.text(name);
4629
+ table.integer(name);
4630
+ table.bigInteger(name);
4631
+ table.float(name, precision, scale);
4632
+ table.double(name);
4633
+ table.decimal(name, precision, scale);
4634
+ table.boolean(name);
4635
+ table.date(name);
4636
+ table.datetime(name);
4637
+ table.time(name);
4638
+ table.timestamp(name);
4639
+ table.timestamps(useTimestamps, defaultToNow);
4640
+ table.json(name);
4641
+ table.jsonb(name);
4642
+ table.uuid(name);
4643
+ table.binary(name);
4644
+ table.enum(name, values);
4645
+ table.specificType(name, type);
4646
+
4647
+ // Column modifiers
4648
+ column.nullable();
4649
+ column.notNullable();
4650
+ column.defaultTo(value);
4651
+ column.unique(indexName);
4652
+ column.primary();
4653
+ column.index(indexName);
4654
+ column.comment(text);
4655
+ column.unsigned();
4656
+ column.after(columnName);
4657
+ column.first();
4658
+
4659
+ // Indexes and constraints
4660
+ table.primary(columns);
4661
+ table.unique(columns, indexName);
4662
+ table.index(columns, indexName);
4663
+ table
4664
+ .foreign(columns, indexName)
4665
+ .references(columns)
4666
+ .inTable(table)
4667
+ .onDelete(action)
4668
+ .onUpdate(action);
4669
+ table.dropPrimary();
4670
+ table.dropUnique(columns);
4671
+ table.dropIndex(columns);
4672
+ table.dropForeign(columns);
4673
+
4674
+ // PostgreSQL specific
4675
+ schema.createSchema(name);
4676
+ schema.dropSchema(name);
4677
+ schema.enableExtension(name);
4678
+ schema.createEnum(name, values);
4679
+ schema.dropEnum(name);
4680
+ ```
4681
+
4682
+ ### MigrationRunner API
4683
+
4684
+ ```javascript
4685
+ // Migration operations
4686
+ runner.migrate(connection, onlyFile, toFile);
4687
+ runner.rollback(steps, connection, toFile);
4688
+ runner.reset(connection);
4689
+ runner.refresh(connection);
4690
+ runner.fresh(connection);
4691
+ runner.status(connection);
4692
+ runner.list(connection);
4693
+ runner.unlock(connection);
4694
+ runner.wipe(connection);
4695
+
4696
+ // Migration generation
4697
+ runner.generateMigration(name, tableName, isCreate);
4698
+ runner.getAllMigrationFiles();
4699
+ runner.loadMigration(filename);
4700
+ runner.getNextBatchNumber();
4701
+ ```
4702
+
4703
+ ### Seeder API
4704
+
4705
+ ```javascript
4706
+ // Seeder methods
4707
+ seeder.run();
4708
+ seeder.call(seeders);
4709
+ seeder.callWith(seeders, connection);
4710
+ seeder.callOnce(SeederClass, identifier);
4711
+ seeder.progress(total, callback);
4712
+ seeder.createInBatches(factory, count, attributes);
4713
+
4714
+ // Database utilities
4715
+ seeder.disableForeignKeyChecks();
4716
+ seeder.enableForeignKeyChecks();
4717
+ seeder.truncate(table);
4718
+ seeder.truncateInOrder(tables);
4719
+ seeder.wipeDatabase();
4720
+ ```
4721
+
4722
+ ### ModelRegistry API
4723
+
4724
+ ```javascript
4725
+ ModelRegistry.register(name, model);
4726
+ ModelRegistry.get(name);
4727
+ ModelRegistry.has(name);
4728
+ ModelRegistry.all();
4729
+ ModelRegistry.clear();
4730
+ ```
4731
+
4732
+ ### Custom Casts API
4733
+
4734
+ ```javascript
4735
+ // Built-in casts
4736
+ new MoneyCast();
4737
+ new EncryptedCast(key);
4738
+ new JsonCast();
4739
+ new ArrayCast();
4740
+ new DateCast();
4741
+
4742
+ // Custom cast interface
4743
+ class CustomCast {
4744
+ get(value) {
4745
+ return value;
4746
+ }
4747
+ set(value) {
4748
+ return value;
4749
+ }
4750
+ }
4751
+ ```
4752
+
4753
+ ## Contributing
4754
+
4755
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
4756
+
4757
+ ## License
4758
+
4759
+ MIT License - see the [LICENSE](LICENSE) file for details.
4760
+
4761
+ ---
4762
+
4763
+ **IlanaORM** - Following the patterns and protocols of modern database interaction.