outlet-orm 2.5.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.
@@ -0,0 +1,790 @@
1
+ /**
2
+ * Schema Builder - Inspired by Laravel Schema
3
+ * Provides a fluent interface for creating and modifying database tables
4
+ */
5
+
6
+ class Schema {
7
+ constructor(connection) {
8
+ this.connection = connection;
9
+ }
10
+
11
+ /**
12
+ * Create a new table
13
+ * @param {string} tableName
14
+ * @param {Function} callback
15
+ */
16
+ async create(tableName, callback) {
17
+ const blueprint = new Blueprint(tableName, this.connection);
18
+ callback(blueprint);
19
+ const statements = blueprint.toSql('create');
20
+
21
+ for (const sql of statements) {
22
+ await this.connection.execute(sql);
23
+ }
24
+ console.log(`✓ Table '${tableName}' created successfully`);
25
+ }
26
+
27
+ /**
28
+ * Modify an existing table
29
+ * @param {string} tableName
30
+ * @param {Function} callback
31
+ */
32
+ async table(tableName, callback) {
33
+ const blueprint = new Blueprint(tableName, this.connection);
34
+ blueprint.isModifying = true;
35
+ callback(blueprint);
36
+ const statements = blueprint.toSql('alter');
37
+
38
+ for (const sql of statements) {
39
+ await this.connection.execute(sql);
40
+ }
41
+ console.log(`✓ Table '${tableName}' modified successfully`);
42
+ }
43
+
44
+ /**
45
+ * Rename a table
46
+ * @param {string} from
47
+ * @param {string} to
48
+ */
49
+ async rename(from, to) {
50
+ const driver = this.connection.config.driver;
51
+ let sql;
52
+
53
+ switch (driver) {
54
+ case 'mysql':
55
+ sql = `RENAME TABLE ${from} TO ${to}`;
56
+ break;
57
+ case 'postgres':
58
+ case 'postgresql':
59
+ case 'sqlite':
60
+ sql = `ALTER TABLE ${from} RENAME TO ${to}`;
61
+ break;
62
+ default:
63
+ throw new Error(`Unsupported driver: ${driver}`);
64
+ }
65
+
66
+ await this.connection.execute(sql);
67
+ console.log(`✓ Table '${from}' renamed to '${to}'`);
68
+ }
69
+
70
+ /**
71
+ * Drop a table
72
+ * @param {string} tableName
73
+ */
74
+ async drop(tableName) {
75
+ const sql = `DROP TABLE ${tableName}`;
76
+ await this.connection.execute(sql);
77
+ console.log(`✓ Table '${tableName}' dropped successfully`);
78
+ }
79
+
80
+ /**
81
+ * Drop a table if it exists
82
+ * @param {string} tableName
83
+ */
84
+ async dropIfExists(tableName) {
85
+ const sql = `DROP TABLE IF EXISTS ${tableName}`;
86
+ await this.connection.execute(sql);
87
+ console.log(`✓ Table '${tableName}' dropped if existed`);
88
+ }
89
+
90
+ /**
91
+ * Check if a table exists
92
+ * @param {string} tableName
93
+ * @returns {Promise<boolean>}
94
+ */
95
+ async hasTable(tableName) {
96
+ const driver = this.connection.config.driver;
97
+ let sql;
98
+
99
+ switch (driver) {
100
+ case 'mysql':
101
+ sql = `SELECT COUNT(*) as count FROM information_schema.tables
102
+ WHERE table_schema = DATABASE() AND table_name = '${tableName}'`;
103
+ break;
104
+ case 'postgres':
105
+ case 'postgresql':
106
+ sql = `SELECT COUNT(*) as count FROM information_schema.tables
107
+ WHERE table_schema = 'public' AND table_name = '${tableName}'`;
108
+ break;
109
+ case 'sqlite':
110
+ sql = `SELECT COUNT(*) as count FROM sqlite_master
111
+ WHERE type='table' AND name='${tableName}'`;
112
+ break;
113
+ default:
114
+ throw new Error(`Unsupported driver: ${driver}`);
115
+ }
116
+
117
+ const result = await this.connection.execute(sql);
118
+ return result[0].count > 0;
119
+ }
120
+
121
+ /**
122
+ * Check if a column exists in a table
123
+ * @param {string} tableName
124
+ * @param {string} columnName
125
+ * @returns {Promise<boolean>}
126
+ */
127
+ async hasColumn(tableName, columnName) {
128
+ const driver = this.connection.config.driver;
129
+ let sql;
130
+
131
+ switch (driver) {
132
+ case 'mysql':
133
+ sql = `SELECT COUNT(*) as count FROM information_schema.columns
134
+ WHERE table_schema = DATABASE()
135
+ AND table_name = '${tableName}'
136
+ AND column_name = '${columnName}'`;
137
+ break;
138
+ case 'postgres':
139
+ case 'postgresql':
140
+ sql = `SELECT COUNT(*) as count FROM information_schema.columns
141
+ WHERE table_schema = 'public'
142
+ AND table_name = '${tableName}'
143
+ AND column_name = '${columnName}'`;
144
+ break;
145
+ case 'sqlite':
146
+ sql = `SELECT COUNT(*) as count FROM pragma_table_info('${tableName}')
147
+ WHERE name = '${columnName}'`;
148
+ break;
149
+ default:
150
+ throw new Error(`Unsupported driver: ${driver}`);
151
+ }
152
+
153
+ const result = await this.connection.execute(sql);
154
+ return result[0].count > 0;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Blueprint - Represents a table structure
160
+ */
161
+ class Blueprint {
162
+ constructor(tableName, connection) {
163
+ this.tableName = tableName;
164
+ this.connection = connection;
165
+ this.columns = [];
166
+ this.commands = [];
167
+ this.isModifying = false;
168
+ }
169
+
170
+ /**
171
+ * Create an auto-incrementing ID column
172
+ */
173
+ id(columnName = 'id') {
174
+ return this.bigIncrements(columnName);
175
+ }
176
+
177
+ /**
178
+ * Create a big integer auto-increment column
179
+ */
180
+ bigIncrements(columnName) {
181
+ const column = new ColumnDefinition(columnName, 'BIGINT');
182
+ column.autoIncrement().unsigned().primary();
183
+ this.columns.push(column);
184
+ return column;
185
+ }
186
+
187
+ /**
188
+ * Create a string column
189
+ */
190
+ string(columnName, length = 255) {
191
+ const column = new ColumnDefinition(columnName, 'VARCHAR', { length });
192
+ this.columns.push(column);
193
+ return column;
194
+ }
195
+
196
+ /**
197
+ * Create a text column
198
+ */
199
+ text(columnName) {
200
+ const column = new ColumnDefinition(columnName, 'TEXT');
201
+ this.columns.push(column);
202
+ return column;
203
+ }
204
+
205
+ /**
206
+ * Create an integer column
207
+ */
208
+ integer(columnName) {
209
+ const column = new ColumnDefinition(columnName, 'INT');
210
+ this.columns.push(column);
211
+ return column;
212
+ }
213
+
214
+ /**
215
+ * Create a big integer column
216
+ */
217
+ bigInteger(columnName) {
218
+ const column = new ColumnDefinition(columnName, 'BIGINT');
219
+ this.columns.push(column);
220
+ return column;
221
+ }
222
+
223
+ /**
224
+ * Create a boolean column
225
+ */
226
+ boolean(columnName) {
227
+ const column = new ColumnDefinition(columnName, 'TINYINT', { length: 1 });
228
+ this.columns.push(column);
229
+ return column;
230
+ }
231
+
232
+ /**
233
+ * Create a date column
234
+ */
235
+ date(columnName) {
236
+ const column = new ColumnDefinition(columnName, 'DATE');
237
+ this.columns.push(column);
238
+ return column;
239
+ }
240
+
241
+ /**
242
+ * Create a datetime column
243
+ */
244
+ datetime(columnName) {
245
+ const column = new ColumnDefinition(columnName, 'DATETIME');
246
+ this.columns.push(column);
247
+ return column;
248
+ }
249
+
250
+ /**
251
+ * Create a timestamp column
252
+ */
253
+ timestamp(columnName) {
254
+ const column = new ColumnDefinition(columnName, 'TIMESTAMP');
255
+ this.columns.push(column);
256
+ return column;
257
+ }
258
+
259
+ /**
260
+ * Create timestamps (created_at, updated_at)
261
+ */
262
+ timestamps(nullable = false) {
263
+ const createdAt = this.timestamp('created_at');
264
+ const updatedAt = this.timestamp('updated_at');
265
+
266
+ if (nullable) {
267
+ createdAt.nullable();
268
+ updatedAt.nullable();
269
+ } else {
270
+ createdAt.useCurrent();
271
+ updatedAt.useCurrent().useCurrentOnUpdate();
272
+ }
273
+
274
+ return this;
275
+ }
276
+
277
+ /**
278
+ * Create a soft delete column (deleted_at)
279
+ */
280
+ softDeletes(columnName = 'deleted_at') {
281
+ return this.timestamp(columnName).nullable();
282
+ }
283
+
284
+ /**
285
+ * Create a decimal column
286
+ */
287
+ decimal(columnName, precision = 8, scale = 2) {
288
+ const column = new ColumnDefinition(columnName, 'DECIMAL', { precision, scale });
289
+ this.columns.push(column);
290
+ return column;
291
+ }
292
+
293
+ /**
294
+ * Create a float column
295
+ */
296
+ float(columnName, precision = 8, scale = 2) {
297
+ const column = new ColumnDefinition(columnName, 'FLOAT', { precision, scale });
298
+ this.columns.push(column);
299
+ return column;
300
+ }
301
+
302
+ /**
303
+ * Create a JSON column
304
+ */
305
+ json(columnName) {
306
+ const column = new ColumnDefinition(columnName, 'JSON');
307
+ this.columns.push(column);
308
+ return column;
309
+ }
310
+
311
+ /**
312
+ * Create an enum column
313
+ */
314
+ enum(columnName, values) {
315
+ const column = new ColumnDefinition(columnName, 'ENUM', { values });
316
+ this.columns.push(column);
317
+ return column;
318
+ }
319
+
320
+ /**
321
+ * Create a UUID column
322
+ */
323
+ uuid(columnName) {
324
+ const column = new ColumnDefinition(columnName, 'CHAR', { length: 36 });
325
+ this.columns.push(column);
326
+ return column;
327
+ }
328
+
329
+ /**
330
+ * Create a foreign ID column
331
+ */
332
+ foreignId(columnName) {
333
+ const column = new ColumnDefinition(columnName, 'BIGINT');
334
+ column.unsigned();
335
+ this.columns.push(column);
336
+ return column;
337
+ }
338
+
339
+ /**
340
+ * Add a foreign key constraint
341
+ */
342
+ foreign(columnName) {
343
+ const foreignKey = new ForeignKeyDefinition(columnName);
344
+ this.commands.push({ type: 'foreign', foreignKey });
345
+ return foreignKey;
346
+ }
347
+
348
+ /**
349
+ * Add an index
350
+ */
351
+ index(columns, indexName = null) {
352
+ const cols = Array.isArray(columns) ? columns : [columns];
353
+ this.commands.push({
354
+ type: 'index',
355
+ columns: cols,
356
+ name: indexName || `${this.tableName}_${cols.join('_')}_index`
357
+ });
358
+ return this;
359
+ }
360
+
361
+ /**
362
+ * Add a unique index
363
+ */
364
+ unique(columns, indexName = null) {
365
+ const cols = Array.isArray(columns) ? columns : [columns];
366
+ this.commands.push({
367
+ type: 'unique',
368
+ columns: cols,
369
+ name: indexName || `${this.tableName}_${cols.join('_')}_unique`
370
+ });
371
+ return this;
372
+ }
373
+
374
+ /**
375
+ * Add a fulltext index
376
+ */
377
+ fullText(columns, indexName = null) {
378
+ const cols = Array.isArray(columns) ? columns : [columns];
379
+ this.commands.push({
380
+ type: 'fulltext',
381
+ columns: cols,
382
+ name: indexName || `${this.tableName}_${cols.join('_')}_fulltext`
383
+ });
384
+ return this;
385
+ }
386
+
387
+ /**
388
+ * Drop a column
389
+ */
390
+ dropColumn(columns) {
391
+ const cols = Array.isArray(columns) ? columns : [columns];
392
+ this.commands.push({ type: 'dropColumn', columns: cols });
393
+ return this;
394
+ }
395
+
396
+ /**
397
+ * Drop a foreign key
398
+ */
399
+ dropForeign(columns) {
400
+ const cols = Array.isArray(columns) ? columns : [columns];
401
+ this.commands.push({ type: 'dropForeign', columns: cols });
402
+ return this;
403
+ }
404
+
405
+ /**
406
+ * Drop an index
407
+ */
408
+ dropIndex(columns) {
409
+ const cols = Array.isArray(columns) ? columns : [columns];
410
+ const indexName = `${this.tableName}_${cols.join('_')}_index`;
411
+ this.commands.push({ type: 'dropIndex', name: indexName });
412
+ return this;
413
+ }
414
+
415
+ /**
416
+ * Drop timestamps
417
+ */
418
+ dropTimestamps() {
419
+ return this.dropColumn(['created_at', 'updated_at']);
420
+ }
421
+
422
+ /**
423
+ * Rename a column
424
+ */
425
+ renameColumn(from, to) {
426
+ this.commands.push({ type: 'renameColumn', from, to });
427
+ return this;
428
+ }
429
+
430
+ /**
431
+ * Generate SQL statements
432
+ * @param {string} action - 'create' or 'alter'
433
+ * @returns {string[]} Array of SQL statements
434
+ */
435
+ toSql(action) {
436
+ if (action === 'create') {
437
+ return [this.toCreateSql()];
438
+ }
439
+
440
+ if (action === 'alter') {
441
+ return this.toAlterSql();
442
+ }
443
+
444
+ return [];
445
+ }
446
+
447
+ /**
448
+ * Generate CREATE TABLE SQL
449
+ * @returns {string} SQL statement
450
+ */
451
+ toCreateSql() {
452
+ const driver = this.connection.config.driver;
453
+ const columnDefinitions = this.columns.map(col => col.toSql(driver)).join(',\n ');
454
+ const constraints = this.getConstraints();
455
+
456
+ let sql = `CREATE TABLE ${this.tableName} (\n ${columnDefinitions}`;
457
+
458
+ if (constraints) {
459
+ sql += `,\n ${constraints}`;
460
+ }
461
+
462
+ sql += '\n)';
463
+
464
+ return sql;
465
+ }
466
+
467
+ /**
468
+ * Generate ALTER TABLE SQL
469
+ * @returns {string[]} Array of SQL statements
470
+ */
471
+ toAlterSql() {
472
+ const statements = [];
473
+
474
+ // Add new columns
475
+ const driver = this.connection.config.driver;
476
+ for (const column of this.columns) {
477
+ let sql = `ALTER TABLE ${this.tableName} ADD COLUMN ${column.toSql(driver)}`;
478
+ statements.push(sql);
479
+ }
480
+
481
+ // Process commands
482
+ for (const command of this.commands) {
483
+ switch (command.type) {
484
+ case 'dropColumn':
485
+ for (const col of command.columns) {
486
+ statements.push(`ALTER TABLE ${this.tableName} DROP COLUMN ${col}`);
487
+ }
488
+ break;
489
+
490
+ case 'renameColumn':
491
+ statements.push(`ALTER TABLE ${this.tableName} CHANGE ${command.from} ${command.to}`);
492
+ break;
493
+
494
+ case 'foreign': {
495
+ const fk = command.foreignKey;
496
+ statements.push(
497
+ `ALTER TABLE ${this.tableName} ADD CONSTRAINT ${fk.name} ` +
498
+ `FOREIGN KEY (${fk.column}) REFERENCES ${fk.references.table}(${fk.references.column})` +
499
+ (fk.onDelete ? ` ON DELETE ${fk.onDelete}` : '') +
500
+ (fk.onUpdate ? ` ON UPDATE ${fk.onUpdate}` : '')
501
+ );
502
+ break;
503
+ }
504
+
505
+ case 'dropForeign': {
506
+ const fkName = `${this.tableName}_${command.columns.join('_')}_foreign`;
507
+ statements.push(`ALTER TABLE ${this.tableName} DROP FOREIGN KEY ${fkName}`);
508
+ break;
509
+ }
510
+
511
+ case 'index':
512
+ statements.push(
513
+ `ALTER TABLE ${this.tableName} ADD INDEX ${command.name} (${command.columns.join(', ')})`
514
+ );
515
+ break;
516
+
517
+ case 'unique':
518
+ statements.push(
519
+ `ALTER TABLE ${this.tableName} ADD UNIQUE ${command.name} (${command.columns.join(', ')})`
520
+ );
521
+ break;
522
+
523
+ case 'fulltext':
524
+ statements.push(
525
+ `ALTER TABLE ${this.tableName} ADD FULLTEXT ${command.name} (${command.columns.join(', ')})`
526
+ );
527
+ break;
528
+
529
+ case 'dropIndex':
530
+ statements.push(`ALTER TABLE ${this.tableName} DROP INDEX ${command.name}`);
531
+ break;
532
+ }
533
+ }
534
+
535
+ return statements;
536
+ }
537
+
538
+ /**
539
+ * Get table constraints (PRIMARY KEY, FOREIGN KEY, etc.)
540
+ */
541
+ getConstraints() {
542
+ const constraints = [];
543
+ const driver = this.connection?.config?.driver || 'mysql';
544
+
545
+ // Primary keys
546
+ const primaryKeys = this.columns.filter(col => col.isPrimary);
547
+ // In SQLite, if a column is autoincrementing integer PK, it must be declared at column level,
548
+ // so skip table-level PRIMARY KEY constraints to avoid duplication.
549
+ const hasSqliteAutoInc = driver === 'sqlite' && this.columns.some(col => col.isAutoIncrement);
550
+ if (primaryKeys.length > 0 && !hasSqliteAutoInc) {
551
+ const pkColumns = primaryKeys.map(col => col.name).join(', ');
552
+ constraints.push(`PRIMARY KEY (${pkColumns})`);
553
+ }
554
+
555
+ // Foreign keys
556
+ for (const command of this.commands) {
557
+ if (command.type === 'foreign') {
558
+ const fk = command.foreignKey;
559
+ let constraint = `CONSTRAINT ${fk.name} FOREIGN KEY (${fk.column}) ` +
560
+ `REFERENCES ${fk.references.table}(${fk.references.column})`;
561
+
562
+ if (fk.onDelete) {
563
+ constraint += ` ON DELETE ${fk.onDelete}`;
564
+ }
565
+ if (fk.onUpdate) {
566
+ constraint += ` ON UPDATE ${fk.onUpdate}`;
567
+ }
568
+
569
+ constraints.push(constraint);
570
+ } else if (command.type === 'unique') {
571
+ if (driver === 'sqlite') {
572
+ constraints.push(`UNIQUE (${command.columns.join(', ')})`);
573
+ } else {
574
+ constraints.push(`UNIQUE KEY ${command.name} (${command.columns.join(', ')})`);
575
+ }
576
+ } else if (command.type === 'index') {
577
+ if (driver !== 'sqlite') {
578
+ constraints.push(`KEY ${command.name} (${command.columns.join(', ')})`);
579
+ }
580
+ }
581
+ }
582
+
583
+ return constraints.join(',\n ');
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Column Definition
589
+ */
590
+ class ColumnDefinition {
591
+ constructor(name, type, options = {}) {
592
+ this.name = name;
593
+ this.type = type;
594
+ this.options = options;
595
+ this.isPrimary = false;
596
+ this.isUnique = false;
597
+ this.isNullable = false;
598
+ this.isUnsigned = false;
599
+ this.isAutoIncrement = false;
600
+ this.defaultValue = null;
601
+ this.commentText = null;
602
+ this.afterColumn = null;
603
+ this.isFirst = false;
604
+ this.useCurrentTimestamp = false;
605
+ this.useCurrentOnUpdateTimestamp = false;
606
+ }
607
+
608
+ primary() {
609
+ this.isPrimary = true;
610
+ return this;
611
+ }
612
+
613
+ unique() {
614
+ this.isUnique = true;
615
+ return this;
616
+ }
617
+
618
+ nullable() {
619
+ this.isNullable = true;
620
+ return this;
621
+ }
622
+
623
+ unsigned() {
624
+ this.isUnsigned = true;
625
+ return this;
626
+ }
627
+
628
+ autoIncrement() {
629
+ this.isAutoIncrement = true;
630
+ return this;
631
+ }
632
+
633
+ default(value) {
634
+ this.defaultValue = value;
635
+ return this;
636
+ }
637
+
638
+ comment(text) {
639
+ this.commentText = text;
640
+ return this;
641
+ }
642
+
643
+ after(columnName) {
644
+ this.afterColumn = columnName;
645
+ return this;
646
+ }
647
+
648
+ first() {
649
+ this.isFirst = true;
650
+ return this;
651
+ }
652
+
653
+ useCurrent() {
654
+ this.useCurrentTimestamp = true;
655
+ return this;
656
+ }
657
+
658
+ useCurrentOnUpdate() {
659
+ this.useCurrentOnUpdateTimestamp = true;
660
+ return this;
661
+ }
662
+
663
+ /**
664
+ * Generate SQL for this column
665
+ */
666
+ toSql(driver = 'mysql') {
667
+ let sql = `${this.name} ${this.getTypeDefinition(driver)}`;
668
+
669
+ if (this.isUnsigned && ['INT', 'BIGINT', 'TINYINT'].includes(this.type) && driver !== 'sqlite') {
670
+ sql += ' UNSIGNED';
671
+ }
672
+
673
+ if (!this.isNullable && !this.isPrimary) {
674
+ sql += ' NOT NULL';
675
+ }
676
+
677
+ if (this.isAutoIncrement) {
678
+ if (driver === 'sqlite') {
679
+ // In SQLite, autoincrement must be declared as INTEGER PRIMARY KEY AUTOINCREMENT
680
+ sql = `${this.name} INTEGER PRIMARY KEY AUTOINCREMENT`;
681
+ } else {
682
+ sql += ' AUTO_INCREMENT';
683
+ }
684
+ }
685
+
686
+ if (this.useCurrentTimestamp) {
687
+ sql += ' DEFAULT CURRENT_TIMESTAMP';
688
+ } else if (this.defaultValue !== null) {
689
+ sql += ` DEFAULT ${this.formatDefaultValue()}`;
690
+ }
691
+
692
+ if (this.useCurrentOnUpdateTimestamp && driver !== 'sqlite') {
693
+ sql += ' ON UPDATE CURRENT_TIMESTAMP';
694
+ }
695
+
696
+ if (this.isUnique) {
697
+ sql += ' UNIQUE';
698
+ }
699
+
700
+ if (this.commentText) {
701
+ sql += ` COMMENT '${this.commentText}'`;
702
+ }
703
+
704
+ return sql;
705
+ }
706
+
707
+ getTypeDefinition(driver = 'mysql') {
708
+ const { length, precision, scale, values } = this.options;
709
+
710
+ switch (this.type) {
711
+ case 'VARCHAR':
712
+ return driver === 'sqlite' ? 'TEXT' : `VARCHAR(${length})`;
713
+ case 'CHAR':
714
+ return driver === 'sqlite' ? 'TEXT' : `CHAR(${length})`;
715
+ case 'DECIMAL':
716
+ return `DECIMAL(${precision}, ${scale})`;
717
+ case 'FLOAT':
718
+ return precision ? `FLOAT(${precision}, ${scale})` : 'FLOAT';
719
+ case 'ENUM': {
720
+ if (driver === 'sqlite') return 'TEXT';
721
+ const enumValues = values.map(v => `'${v}'`).join(', ');
722
+ return `ENUM(${enumValues})`;
723
+ }
724
+ default:
725
+ return this.type;
726
+ }
727
+ }
728
+
729
+ formatDefaultValue() {
730
+ if (typeof this.defaultValue === 'string') {
731
+ return `'${this.defaultValue}'`;
732
+ }
733
+ return this.defaultValue;
734
+ }
735
+ }
736
+
737
+ /**
738
+ * Foreign Key Definition
739
+ */
740
+ class ForeignKeyDefinition {
741
+ constructor(column) {
742
+ this.column = column;
743
+ this.references = { table: null, column: 'id' };
744
+ this.onDelete = null;
745
+ this.onUpdate = null;
746
+ this.name = null;
747
+ }
748
+
749
+ references(column) {
750
+ this.references.column = column;
751
+ return this;
752
+ }
753
+
754
+ on(table) {
755
+ this.references.table = table;
756
+ this.name = `${table}_${this.column}_foreign`;
757
+ return this;
758
+ }
759
+
760
+ constrained(table = null) {
761
+ if (table) {
762
+ this.references.table = table;
763
+ } else {
764
+ // Infer table name from column name (remove _id suffix)
765
+ this.references.table = this.column.replace(/_id$/, '') + 's';
766
+ }
767
+ this.name = `${this.references.table}_${this.column}_foreign`;
768
+ return this;
769
+ }
770
+
771
+ onDelete(action) {
772
+ this.onDelete = action.toUpperCase();
773
+ return this;
774
+ }
775
+
776
+ onUpdate(action) {
777
+ this.onUpdate = action.toUpperCase();
778
+ return this;
779
+ }
780
+
781
+ cascadeOnDelete() {
782
+ return this.onDelete('cascade');
783
+ }
784
+
785
+ cascadeOnUpdate() {
786
+ return this.onUpdate('cascade');
787
+ }
788
+ }
789
+
790
+ module.exports = { Schema, Blueprint, ColumnDefinition, ForeignKeyDefinition };