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 +4763 -0
- package/cli/ilana.js +928 -0
- package/database/DB.js +85 -0
- package/database/connection.js +114 -0
- package/database/schema-builder.js +219 -0
- package/ilana.config.js +61 -0
- package/ilana.png +0 -0
- package/index.js +34 -0
- package/orm/Collection.js +283 -0
- package/orm/CustomCasts.js +75 -0
- package/orm/Factory.js +372 -0
- package/orm/MigrationRunner.js +458 -0
- package/orm/Model.js +607 -0
- package/orm/ModelRegistry.js +26 -0
- package/orm/QueryBuilder.js +680 -0
- package/orm/Relation.js +299 -0
- package/orm/Seeder.js +153 -0
- package/package.json +71 -0
- package/test-role.js +26 -0
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.
|