ilana-orm 1.0.12 → 1.0.14

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/cli/ilana.js CHANGED
@@ -90,6 +90,17 @@ function isESModuleProject() {
90
90
  return false;
91
91
  }
92
92
 
93
+ function getProjectStructure() {
94
+ const hasSrc = fs.existsSync(path.join(process.cwd(), 'src'));
95
+ return {
96
+ hasSrc,
97
+ modelsDir: hasSrc ? 'src/models' : 'models',
98
+ databaseDir: hasSrc ? 'src/database' : 'database',
99
+ observersDir: hasSrc ? 'src/observers' : 'observers',
100
+ castsDir: hasSrc ? 'src/casts' : 'casts'
101
+ };
102
+ }
103
+
93
104
  function getFileExtension() {
94
105
  return isTypeScriptProject() ? '.ts' : '.js';
95
106
  }
@@ -109,8 +120,9 @@ function pluralize(str) {
109
120
  }
110
121
 
111
122
  function getESModuleConfigTemplate() {
123
+ const structure = getProjectStructure();
112
124
  return `import 'dotenv/config';
113
- import Database from 'ilana-orm/database/connection';
125
+ import Database from 'ilana-orm/database/connection.js';
114
126
 
115
127
  const config = {
116
128
  default: process.env.DB_CONNECTION || 'mysql',
@@ -150,12 +162,12 @@ const config = {
150
162
  },
151
163
 
152
164
  migrations: {
153
- directory: './database/migrations',
165
+ directory: './${structure.databaseDir}/migrations',
154
166
  tableName: 'migrations'
155
167
  },
156
168
 
157
169
  seeds: {
158
- directory: './database/seeds'
170
+ directory: './${structure.databaseDir}/seeds'
159
171
  }
160
172
  };
161
173
 
@@ -167,6 +179,7 @@ export default config;
167
179
  }
168
180
 
169
181
  function getCommonJSConfigTemplate() {
182
+ const structure = getProjectStructure();
170
183
  return `require('dotenv').config();
171
184
  const Database = require('ilana-orm/database/connection');
172
185
 
@@ -208,12 +221,12 @@ const config = {
208
221
  },
209
222
 
210
223
  migrations: {
211
- directory: './database/migrations',
224
+ directory: './${structure.databaseDir}/migrations',
212
225
  tableName: 'migrations'
213
226
  },
214
227
 
215
228
  seeds: {
216
- directory: './database/seeds'
229
+ directory: './${structure.databaseDir}/seeds'
217
230
  }
218
231
  };
219
232
 
@@ -228,7 +241,8 @@ function generateModel(name, options = {}) {
228
241
  const className = toPascalCase(name);
229
242
  const tableName = pluralize(toSnakeCase(name));
230
243
  const fileName = `${className}${getFileExtension()}`;
231
- const filePath = path.join(process.cwd(), 'models', fileName);
244
+ const structure = getProjectStructure();
245
+ const filePath = path.join(process.cwd(), structure.modelsDir, fileName);
232
246
 
233
247
  if (fs.existsSync(filePath)) {
234
248
  console.error(`Model ${className} already exists at models/${fileName}`);
@@ -241,7 +255,7 @@ function generateModel(name, options = {}) {
241
255
 
242
256
  const template = options.pivot ? getPivotModelTemplate(className, tableName) : getModelTemplate(className, tableName);
243
257
  fs.writeFileSync(filePath, template);
244
- console.log(`Created model: models/${fileName}`);
258
+ console.log(`Created model: ${structure.modelsDir}/${fileName}`);
245
259
 
246
260
  if (options.migration || options.all) {
247
261
  const migrationName = `create_${tableName}_table`;
@@ -449,15 +463,17 @@ module.exports = ${className};
449
463
 
450
464
  function generateFactory(className) {
451
465
  const fileName = `${className}Factory${getFileExtension()}`;
452
- const filePath = path.join(process.cwd(), 'database/factories', fileName);
466
+ const structure = getProjectStructure();
467
+ const filePath = path.join(process.cwd(), structure.databaseDir, 'factories', fileName);
453
468
 
454
469
  if (!fs.existsSync(path.dirname(filePath))) {
455
470
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
456
471
  }
457
472
 
473
+ const modelPath = structure.hasSrc ? `../models/${className}.js` : `../../models/${className}.js`;
458
474
  const template = isTypeScriptProject() ?
459
475
  `import { defineFactory } from 'ilana-orm/orm/Factory.js';
460
- import ${className} from '../../models/${className}.js';
476
+ import ${className} from '${modelPath}';
461
477
 
462
478
  export default defineFactory(${className}, (faker) => ({
463
479
  // Define your factory attributes here
@@ -469,7 +485,7 @@ export default defineFactory(${className}, (faker) => ({
469
485
  }));
470
486
  ` :
471
487
  `const { defineFactory } = require('ilana-orm/orm/Factory');
472
- const ${className} = require('../../models/${className}');
488
+ const ${className} = require('${modelPath.replace('.js', '')}');
473
489
 
474
490
  module.exports = defineFactory(${className}, (faker) => ({
475
491
  // Define your factory attributes here
@@ -482,20 +498,22 @@ module.exports = defineFactory(${className}, (faker) => ({
482
498
  `;
483
499
 
484
500
  fs.writeFileSync(filePath, template);
485
- console.log(`Created factory: factories/${fileName}`);
501
+ console.log(`Created factory: ${structure.databaseDir}/factories/${fileName}`);
486
502
  }
487
503
 
488
504
  function generateSeeder(className) {
489
505
  const fileName = `${className}Seeder${getFileExtension()}`;
490
- const filePath = path.join(process.cwd(), 'database/seeds', fileName);
506
+ const structure = getProjectStructure();
507
+ const filePath = path.join(process.cwd(), structure.databaseDir, 'seeds', fileName);
491
508
 
492
509
  if (!fs.existsSync(path.dirname(filePath))) {
493
510
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
494
511
  }
495
512
 
513
+ const modelPath = structure.hasSrc ? `../models/${className}.js` : `../../models/${className}.js`;
496
514
  const template = isTypeScriptProject() ?
497
515
  `import Seeder from 'ilana-orm/orm/Seeder.js';
498
- import ${className} from '../../models/${className}.js';
516
+ import ${className} from '${modelPath}';
499
517
  import '../factories/${className}Factory.js';
500
518
 
501
519
  export default class ${className}Seeder extends Seeder {
@@ -510,7 +528,7 @@ export default class ${className}Seeder extends Seeder {
510
528
  }
511
529
  ` :
512
530
  `const Seeder = require('ilana-orm/orm/Seeder');
513
- const ${className} = require('../../models/${className}');
531
+ const ${className} = require('${modelPath.replace('.js', '')}');
514
532
  require('../factories/${className}Factory');
515
533
 
516
534
  class ${className}Seeder extends Seeder {
@@ -528,16 +546,16 @@ module.exports = ${className}Seeder;
528
546
  `;
529
547
 
530
548
  fs.writeFileSync(filePath, template);
531
- console.log(`Created seeder: seeds/${fileName}`);
549
+ console.log(`Created seeder: ${structure.databaseDir}/seeds/${fileName}`);
532
550
  }
533
551
 
534
552
  function getObserverTemplate(className, modelName) {
535
553
  const isESModule = isESModuleProject();
536
-
554
+
537
555
  if (isTypeScriptProject()) {
538
556
  const importStatement = modelName ? `import ${modelName} from '../models/${modelName}.js';\n\n` : '';
539
557
  const modelType = modelName || 'any';
540
-
558
+
541
559
  return `${importStatement}export default class ${className}Observer {
542
560
  async creating(model: ${modelType}): Promise<void> {
543
561
  // Logic before creating model
@@ -581,10 +599,10 @@ function getObserverTemplate(className, modelName) {
581
599
  }
582
600
  `;
583
601
  }
584
-
602
+
585
603
  if (isESModule) {
586
604
  const importStatement = modelName ? `import ${modelName} from '../models/${modelName}.js';\n\n` : '';
587
-
605
+
588
606
  return `${importStatement}class ${className}Observer {
589
607
  async creating(model) {
590
608
  // Logic before creating model
@@ -630,9 +648,9 @@ function getObserverTemplate(className, modelName) {
630
648
  export default ${className}Observer;
631
649
  `;
632
650
  }
633
-
651
+
634
652
  const importStatement = modelName ? `const ${modelName} = require('../models/${modelName}');\n\n` : '';
635
-
653
+
636
654
  return `${importStatement}class ${className}Observer {
637
655
  async creating(model) {
638
656
  // Logic before creating model
@@ -681,7 +699,7 @@ module.exports = ${className}Observer;
681
699
 
682
700
  function getCastTemplate(className) {
683
701
  const isESModule = isESModuleProject();
684
-
702
+
685
703
  if (isTypeScriptProject()) {
686
704
  return `export default class ${className}Cast {
687
705
  get(value: any): any {
@@ -696,7 +714,7 @@ function getCastTemplate(className) {
696
714
  }
697
715
  `;
698
716
  }
699
-
717
+
700
718
  if (isESModule) {
701
719
  return `class ${className}Cast {
702
720
  get(value) {
@@ -713,7 +731,7 @@ function getCastTemplate(className) {
713
731
  export default ${className}Cast;
714
732
  `;
715
733
  }
716
-
734
+
717
735
  return `class ${className}Cast {
718
736
  get(value) {
719
737
  // Transform value when retrieving from database
@@ -736,7 +754,13 @@ const commands = {
736
754
  console.log('Setting up Ilana ORM...');
737
755
 
738
756
  // Create directories
739
- const dirs = ['models', 'database/migrations', 'database/factories', 'database/seeds'];
757
+ const structure = getProjectStructure();
758
+ const dirs = [
759
+ structure.modelsDir,
760
+ `${structure.databaseDir}/migrations`,
761
+ `${structure.databaseDir}/factories`,
762
+ `${structure.databaseDir}/seeds`
763
+ ];
740
764
  for (const dir of dirs) {
741
765
  if (!fs.existsSync(dir)) {
742
766
  fs.mkdirSync(dir, { recursive: true });
@@ -1053,7 +1077,8 @@ DB_TIMEZONE=UTC
1053
1077
  }
1054
1078
  }
1055
1079
 
1056
- const seedsPath = path.join(process.cwd(), 'database/seeds');
1080
+ const structure = getProjectStructure();
1081
+ const seedsPath = path.join(process.cwd(), structure.databaseDir, 'seeds');
1057
1082
  if (!fs.existsSync(seedsPath)) {
1058
1083
  console.log('No seeds directory found');
1059
1084
  process.exit(0);
@@ -1125,7 +1150,8 @@ DB_TIMEZONE=UTC
1125
1150
 
1126
1151
  const className = toPascalCase(name.replace('Observer', ''));
1127
1152
  const fileName = `${className}Observer${getFileExtension()}`;
1128
- const filePath = path.join(process.cwd(), 'observers', fileName);
1153
+ const structure = getProjectStructure();
1154
+ const filePath = path.join(process.cwd(), structure.observersDir, fileName);
1129
1155
 
1130
1156
  if (!fs.existsSync(path.dirname(filePath))) {
1131
1157
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
@@ -1133,7 +1159,7 @@ DB_TIMEZONE=UTC
1133
1159
 
1134
1160
  const template = getObserverTemplate(className, modelName);
1135
1161
  fs.writeFileSync(filePath, template);
1136
- console.log(`Created observer: observers/${fileName}`);
1162
+ console.log(`Created observer: ${structure.observersDir}/${fileName}`);
1137
1163
 
1138
1164
  if (modelName) {
1139
1165
  console.log(`Observer configured for model: ${modelName}`);
@@ -1149,7 +1175,8 @@ DB_TIMEZONE=UTC
1149
1175
 
1150
1176
  const className = toPascalCase(name.replace('Cast', ''));
1151
1177
  const fileName = `${className}Cast${getFileExtension()}`;
1152
- const filePath = path.join(process.cwd(), 'casts', fileName);
1178
+ const structure = getProjectStructure();
1179
+ const filePath = path.join(process.cwd(), structure.castsDir, fileName);
1153
1180
 
1154
1181
  if (!fs.existsSync(path.dirname(filePath))) {
1155
1182
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
@@ -1157,7 +1184,7 @@ DB_TIMEZONE=UTC
1157
1184
 
1158
1185
  const template = getCastTemplate(className);
1159
1186
  fs.writeFileSync(filePath, template);
1160
- console.log(`Created cast: casts/${fileName}`);
1187
+ console.log(`Created cast: ${structure.castsDir}/${fileName}`);
1161
1188
  },
1162
1189
 
1163
1190
  help() {
@@ -0,0 +1,169 @@
1
+ import Database from './connection.mjs';
2
+
3
+ // DB facade - provides static methods for database operations
4
+ class DB {
5
+ static connection(name) {
6
+ return Database.connection(name);
7
+ }
8
+
9
+ static table(tableName, connection) {
10
+ return Database.table(tableName, connection);
11
+ }
12
+
13
+ static raw(query, bindings, connection) {
14
+ return Database.raw(query, bindings, connection);
15
+ }
16
+
17
+ static select(query, bindings, connection) {
18
+ return Database.select(query, bindings, connection);
19
+ }
20
+
21
+ static insert(table, data, connection) {
22
+ return Database.insert(table, data, connection);
23
+ }
24
+
25
+ static update(table, data, where, connection) {
26
+ return Database.update(table, data, where, connection);
27
+ }
28
+
29
+ static delete(table, where, connection) {
30
+ return Database.delete(table, where, connection);
31
+ }
32
+
33
+ static transaction(callback, connection) {
34
+ return Database.transaction(callback, connection);
35
+ }
36
+
37
+ static beginTransaction(connection) {
38
+ return Database.beginTransaction(connection);
39
+ }
40
+
41
+ static commit(trx) {
42
+ return Database.commit(trx);
43
+ }
44
+
45
+ static rollback(trx) {
46
+ return Database.rollback(trx);
47
+ }
48
+
49
+ static schema(connection) {
50
+ return Database.schema(connection);
51
+ }
52
+
53
+ static migrate(connection) {
54
+ return Database.migrate(connection);
55
+ }
56
+
57
+ static seed(connection) {
58
+ return Database.seed(connection);
59
+ }
60
+
61
+ static destroy(connection) {
62
+ return Database.destroy(connection);
63
+ }
64
+
65
+ static destroyAll() {
66
+ return Database.destroyAll();
67
+ }
68
+
69
+ static configure(config) {
70
+ return Database.configure(config);
71
+ }
72
+
73
+ static getInstance(connection) {
74
+ return Database.getInstance(connection);
75
+ }
76
+
77
+ static getConfig() {
78
+ return Database.getConfig();
79
+ }
80
+
81
+ static setConfig(config) {
82
+ return Database.setConfig(config);
83
+ }
84
+
85
+ // Query builder methods
86
+ static query(connection) {
87
+ return Database.getInstance(connection);
88
+ }
89
+
90
+ static from(table, connection) {
91
+ return Database.table(table, connection);
92
+ }
93
+
94
+ // Aggregation methods
95
+ static count(table, column = '*', connection) {
96
+ return Database.table(table, connection).count(column);
97
+ }
98
+
99
+ static sum(table, column, connection) {
100
+ return Database.table(table, connection).sum(column);
101
+ }
102
+
103
+ static avg(table, column, connection) {
104
+ return Database.table(table, connection).avg(column);
105
+ }
106
+
107
+ static min(table, column, connection) {
108
+ return Database.table(table, connection).min(column);
109
+ }
110
+
111
+ static max(table, column, connection) {
112
+ return Database.table(table, connection).max(column);
113
+ }
114
+
115
+ // Utility methods
116
+ static listen(event, callback) {
117
+ return Database.listen(event, callback);
118
+ }
119
+
120
+ static unlisten(event, callback) {
121
+ return Database.unlisten(event, callback);
122
+ }
123
+
124
+ static enableQueryLog() {
125
+ return Database.enableQueryLog();
126
+ }
127
+
128
+ static disableQueryLog() {
129
+ return Database.disableQueryLog();
130
+ }
131
+
132
+ static getQueryLog() {
133
+ return Database.getQueryLog();
134
+ }
135
+
136
+ static flushQueryLog() {
137
+ return Database.flushQueryLog();
138
+ }
139
+
140
+ static pretend(callback, connection) {
141
+ return Database.pretend(callback, connection);
142
+ }
143
+
144
+ static reconnect(connection) {
145
+ return Database.reconnect(connection);
146
+ }
147
+
148
+ static disconnect(connection) {
149
+ return Database.disconnect(connection);
150
+ }
151
+
152
+ static getConnectionName() {
153
+ return Database.getConnectionName();
154
+ }
155
+
156
+ static setConnectionName(name) {
157
+ return Database.setConnectionName(name);
158
+ }
159
+
160
+ static getTablePrefix(connection) {
161
+ return Database.getTablePrefix(connection);
162
+ }
163
+
164
+ static setTablePrefix(prefix, connection) {
165
+ return Database.setTablePrefix(prefix, connection);
166
+ }
167
+ }
168
+
169
+ export default DB;
@@ -0,0 +1,219 @@
1
+ import Database from './connection.mjs';
2
+
3
+ class SchemaBuilder {
4
+ constructor(connection) {
5
+ this.knex = Database.connection(connection);
6
+ this.currentTable = '';
7
+ }
8
+
9
+ createTable(tableName, callback) {
10
+ return this.knex.schema.createTable(tableName, callback);
11
+ }
12
+
13
+ dropTable(tableName) {
14
+ return this.knex.schema.dropTable(tableName);
15
+ }
16
+
17
+ dropTableIfExists(tableName) {
18
+ return this.knex.schema.dropTableIfExists(tableName);
19
+ }
20
+
21
+ renameTable(from, to) {
22
+ return this.knex.schema.renameTable(from, to);
23
+ }
24
+
25
+ hasTable(tableName) {
26
+ return this.knex.schema.hasTable(tableName);
27
+ }
28
+
29
+ hasColumn(tableName, columnName) {
30
+ return this.knex.schema.hasColumn(tableName, columnName);
31
+ }
32
+
33
+ table(tableName, callback) {
34
+ return this.knex.schema.table(tableName, callback);
35
+ }
36
+
37
+ alterTable(tableName, callback) {
38
+ return this.knex.schema.alterTable(tableName, callback);
39
+ }
40
+
41
+ raw(statement) {
42
+ return this.knex.raw(statement);
43
+ }
44
+
45
+ // PostgreSQL specific
46
+ createSchema(schemaName) {
47
+ return this.knex.schema.createSchema(schemaName);
48
+ }
49
+
50
+ dropSchema(schemaName) {
51
+ return this.knex.schema.dropSchema(schemaName);
52
+ }
53
+
54
+ // Advanced column types
55
+ jsonb(columnName) {
56
+ if (this.knex.client.config.client === 'pg') {
57
+ return this.knex.schema.jsonb ? this.knex.schema.jsonb(columnName) : this.knex.schema.json(columnName);
58
+ }
59
+ return this.knex.schema.json(columnName);
60
+ }
61
+
62
+ geometry(columnName, geometryType) {
63
+ return this.knex.schema.specificType(columnName, geometryType || 'geometry');
64
+ }
65
+
66
+ point(columnName) {
67
+ return this.knex.schema.specificType(columnName, 'point');
68
+ }
69
+
70
+ lineString(columnName) {
71
+ return this.knex.schema.specificType(columnName, 'linestring');
72
+ }
73
+
74
+ polygon(columnName) {
75
+ return this.knex.schema.specificType(columnName, 'polygon');
76
+ }
77
+
78
+ inet(columnName) {
79
+ return this.knex.schema.specificType(columnName, 'inet');
80
+ }
81
+
82
+ macaddr(columnName) {
83
+ return this.knex.schema.specificType(columnName, 'macaddr');
84
+ }
85
+
86
+ specificType(columnName, type) {
87
+ return this.knex.schema.specificType(columnName, type);
88
+ }
89
+
90
+ // Enhanced column modifiers with database-specific implementations
91
+ after(columnName) {
92
+ if (this.knex.client.config.client === 'mysql2') {
93
+ return this.knex.schema.raw(`AFTER ${columnName}`);
94
+ }
95
+ return this;
96
+ }
97
+
98
+ first() {
99
+ if (this.knex.client.config.client === 'mysql2') {
100
+ return this.knex.schema.raw('FIRST');
101
+ }
102
+ return this;
103
+ }
104
+
105
+ checkPositive(column) {
106
+ const client = this.knex.client.config.client;
107
+ if (client === 'pg') {
108
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_positive CHECK (${column} > 0)`);
109
+ } else if (client === 'mysql2') {
110
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_positive CHECK (${column} > 0)`);
111
+ }
112
+ return Promise.resolve();
113
+ }
114
+
115
+ checkRegex(column, pattern) {
116
+ const client = this.knex.client.config.client;
117
+ if (client === 'pg') {
118
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_regex CHECK (${column} ~ '${pattern}')`);
119
+ } else if (client === 'mysql2') {
120
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD CONSTRAINT ${column}_regex CHECK (${column} REGEXP '${pattern}')`);
121
+ }
122
+ return Promise.resolve();
123
+ }
124
+
125
+ generatedAs(column, expression) {
126
+ const client = this.knex.client.config.client;
127
+ if (client === 'mysql2') {
128
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD ${column} VARCHAR(255) GENERATED ALWAYS AS (${expression}) STORED`);
129
+ } else if (client === 'pg') {
130
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD ${column} TEXT GENERATED ALWAYS AS (${expression}) STORED`);
131
+ }
132
+ return Promise.resolve();
133
+ }
134
+
135
+ collate(tableName, collation) {
136
+ const client = this.knex.client.config.client;
137
+ if (client === 'mysql2') {
138
+ return this.knex.raw(`ALTER TABLE ${tableName} COLLATE ${collation}`);
139
+ } else if (client === 'pg') {
140
+ return this.knex.raw(`ALTER TABLE ${tableName} ALTER COLUMN name TYPE TEXT COLLATE "${collation}"`);
141
+ }
142
+ return Promise.resolve();
143
+ }
144
+
145
+ // Enhanced PostgreSQL types
146
+ array(columnName, type = 'text') {
147
+ if (this.knex.client.config.client === 'pg') {
148
+ return this.knex.schema.specificType(columnName, `${type}[]`);
149
+ }
150
+ return this.knex.schema.json(columnName);
151
+ }
152
+
153
+ numrange(columnName) {
154
+ return this.knex.schema.specificType(columnName, 'numrange');
155
+ }
156
+
157
+ daterange(columnName) {
158
+ return this.knex.schema.specificType(columnName, 'daterange');
159
+ }
160
+
161
+ tsvector(columnName) {
162
+ return this.knex.schema.specificType(columnName, 'tsvector');
163
+ }
164
+
165
+ // Enhanced MySQL types
166
+ fulltext(columns, indexName) {
167
+ if (this.knex.client.config.client === 'mysql2') {
168
+ const name = indexName || `${columns.join('_')}_fulltext`;
169
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD FULLTEXT INDEX ${name} (${columns.join(', ')})`);
170
+ }
171
+ return Promise.resolve();
172
+ }
173
+
174
+ spatial(column, indexName) {
175
+ if (this.knex.client.config.client === 'mysql2') {
176
+ const name = indexName || `${column}_spatial`;
177
+ return this.knex.raw(`ALTER TABLE ${this.currentTable} ADD SPATIAL INDEX ${name} (${column})`);
178
+ }
179
+ return Promise.resolve();
180
+ }
181
+
182
+ setCurrentTable(tableName) {
183
+ this.currentTable = tableName;
184
+ return this;
185
+ }
186
+
187
+ // Enhanced utility methods
188
+ get client() {
189
+ return this.knex.client;
190
+ }
191
+
192
+ get fn() {
193
+ return this.knex.fn;
194
+ }
195
+
196
+ // Database-specific utilities
197
+ enableExtension(name) {
198
+ if (this.knex.client.config.client === 'pg') {
199
+ return this.knex.raw(`CREATE EXTENSION IF NOT EXISTS "${name}"`);
200
+ }
201
+ return Promise.resolve();
202
+ }
203
+
204
+ createEnum(name, values) {
205
+ if (this.knex.client.config.client === 'pg') {
206
+ return this.knex.raw(`CREATE TYPE ${name} AS ENUM (${values.map(v => `'${v}'`).join(', ')})`);
207
+ }
208
+ return Promise.resolve();
209
+ }
210
+
211
+ dropEnum(name) {
212
+ if (this.knex.client.config.client === 'pg') {
213
+ return this.knex.raw(`DROP TYPE IF EXISTS ${name}`);
214
+ }
215
+ return Promise.resolve();
216
+ }
217
+ }
218
+
219
+ export default SchemaBuilder;
@@ -0,0 +1,198 @@
1
+ import { faker } from '@faker-js/faker';
2
+
3
+ class Factory {
4
+ constructor(model, definition) {
5
+ this.model = model;
6
+ this.definition = definition;
7
+ this.states = new Map();
8
+ this.afterCreating = [];
9
+ this.afterMaking = [];
10
+ this.count = 1;
11
+ this.activeStates = [];
12
+ }
13
+
14
+ static define(model, definition) {
15
+ return new Factory(model, definition);
16
+ }
17
+
18
+ state(name, attributes) {
19
+ if (typeof attributes === 'function') {
20
+ this.states.set(name, attributes);
21
+ } else {
22
+ this.states.set(name, () => attributes);
23
+ }
24
+ return this;
25
+ }
26
+
27
+ times(count) {
28
+ this.count = count;
29
+ return this;
30
+ }
31
+
32
+ as(stateName) {
33
+ this.activeStates.push(stateName);
34
+ return this;
35
+ }
36
+
37
+ afterCreating(callback) {
38
+ this.afterCreating.push(callback);
39
+ return this;
40
+ }
41
+
42
+ afterMaking(callback) {
43
+ this.afterMaking.push(callback);
44
+ return this;
45
+ }
46
+
47
+ async make(attributes = {}) {
48
+ const instances = [];
49
+
50
+ for (let i = 0; i < this.count; i++) {
51
+ const baseAttributes = this.definition(faker);
52
+
53
+ // Apply states
54
+ let stateAttributes = {};
55
+ for (const stateName of this.activeStates) {
56
+ if (this.states.has(stateName)) {
57
+ const stateDefinition = this.states.get(stateName);
58
+ stateAttributes = { ...stateAttributes, ...stateDefinition(faker) };
59
+ }
60
+ }
61
+
62
+ const finalAttributes = { ...baseAttributes, ...stateAttributes, ...attributes };
63
+ const instance = new this.model(finalAttributes);
64
+
65
+ // Run after making callbacks
66
+ for (const callback of this.afterMaking) {
67
+ await callback(instance);
68
+ }
69
+
70
+ instances.push(instance);
71
+ }
72
+
73
+ return this.count === 1 ? instances[0] : instances;
74
+ }
75
+
76
+ async create(attributes = {}) {
77
+ const instances = await this.make(attributes);
78
+ const instancesToSave = Array.isArray(instances) ? instances : [instances];
79
+ const savedInstances = [];
80
+
81
+ for (const instance of instancesToSave) {
82
+ const saved = await instance.save();
83
+
84
+ // Run after creating callbacks
85
+ for (const callback of this.afterCreating) {
86
+ await callback(saved);
87
+ }
88
+
89
+ savedInstances.push(saved);
90
+ }
91
+
92
+ return this.count === 1 ? savedInstances[0] : savedInstances;
93
+ }
94
+
95
+ async createMany(count, attributes = {}) {
96
+ return this.times(count).create(attributes);
97
+ }
98
+
99
+ async makeMany(count, attributes = {}) {
100
+ return this.times(count).make(attributes);
101
+ }
102
+
103
+ // Relationship factories
104
+ for(model) {
105
+ return new RelationshipFactory(this, model);
106
+ }
107
+
108
+ // Sequence support
109
+ sequence(callback) {
110
+ let counter = 0;
111
+ const originalDefinition = this.definition;
112
+
113
+ this.definition = (faker) => {
114
+ const baseAttributes = originalDefinition(faker);
115
+ const sequenceAttributes = callback(++counter, faker);
116
+ return { ...baseAttributes, ...sequenceAttributes };
117
+ };
118
+
119
+ return this;
120
+ }
121
+
122
+ // Conditional attributes
123
+ when(condition, attributes) {
124
+ const originalDefinition = this.definition;
125
+
126
+ this.definition = (faker) => {
127
+ const baseAttributes = originalDefinition(faker);
128
+ if (condition) {
129
+ const conditionalAttributes = typeof attributes === 'function'
130
+ ? attributes(faker)
131
+ : attributes;
132
+ return { ...baseAttributes, ...conditionalAttributes };
133
+ }
134
+ return baseAttributes;
135
+ };
136
+
137
+ return this;
138
+ }
139
+
140
+ // Raw attributes (bypass model instantiation)
141
+ raw(attributes = {}) {
142
+ const baseAttributes = this.definition(faker);
143
+
144
+ // Apply states
145
+ let stateAttributes = {};
146
+ for (const stateName of this.activeStates) {
147
+ if (this.states.has(stateName)) {
148
+ const stateDefinition = this.states.get(stateName);
149
+ stateAttributes = { ...stateAttributes, ...stateDefinition(faker) };
150
+ }
151
+ }
152
+
153
+ return { ...baseAttributes, ...stateAttributes, ...attributes };
154
+ }
155
+ }
156
+
157
+ class RelationshipFactory {
158
+ constructor(parentFactory, model) {
159
+ this.parentFactory = parentFactory;
160
+ this.model = model;
161
+ }
162
+
163
+ create(attributes = {}) {
164
+ // This would create related models
165
+ // Implementation depends on relationship type
166
+ return this.model.factory().create(attributes);
167
+ }
168
+
169
+ make(attributes = {}) {
170
+ return this.model.factory().make(attributes);
171
+ }
172
+ }
173
+
174
+ // Global factory registry
175
+ const factories = new Map();
176
+
177
+ export function defineFactory(model, definition) {
178
+ const factory = new Factory(model, definition);
179
+ factories.set(model.name, factory);
180
+
181
+ // Add factory method to model
182
+ model.factory = function(attributes) {
183
+ const factoryInstance = new Factory(model, definition);
184
+ if (attributes) {
185
+ return factoryInstance.make(attributes);
186
+ }
187
+ return factoryInstance;
188
+ };
189
+
190
+ return factory;
191
+ }
192
+
193
+ export function getFactory(modelName) {
194
+ return factories.get(modelName);
195
+ }
196
+
197
+ export { Factory, faker };
198
+ export default Factory;
package/orm/Seeder.mjs ADDED
@@ -0,0 +1,153 @@
1
+ import Database from '../database/connection.mjs';
2
+
3
+ class Seeder {
4
+ constructor() {
5
+ this.db = Database;
6
+ this.batchSize = 1000;
7
+ }
8
+
9
+ // Batch processing utilities
10
+ async createInBatches(factory, count, attributes = {}) {
11
+ const results = [];
12
+ const batches = Math.ceil(count / this.batchSize);
13
+
14
+ for (let i = 0; i < batches; i++) {
15
+ const currentBatchSize = Math.min(this.batchSize, count - (i * this.batchSize));
16
+ const batch = await factory.times(currentBatchSize).create(attributes);
17
+ results.push(...(Array.isArray(batch) ? batch : [batch]));
18
+
19
+ if (batches > 1) {
20
+ console.log(`Batch ${i + 1}/${batches} completed (${results.length}/${count})`);
21
+ }
22
+ }
23
+
24
+ return results;
25
+ }
26
+
27
+ async disableForeignKeyChecks() {
28
+ const client = this.db.connection(this.connection).client.config.client;
29
+
30
+ if (client === 'mysql2') {
31
+ await this.db.raw('SET FOREIGN_KEY_CHECKS = 0');
32
+ } else if (client === 'pg') {
33
+ await this.db.raw('SET session_replication_role = replica');
34
+ }
35
+ }
36
+
37
+ async enableForeignKeyChecks() {
38
+ const client = this.db.connection(this.connection).client.config.client;
39
+
40
+ if (client === 'mysql2') {
41
+ await this.db.raw('SET FOREIGN_KEY_CHECKS = 1');
42
+ } else if (client === 'pg') {
43
+ await this.db.raw('SET session_replication_role = DEFAULT');
44
+ }
45
+ }
46
+
47
+ async call(seeders) {
48
+ // Handle both single seeder and array of seeders
49
+ const seederArray = Array.isArray(seeders) ? seeders : [seeders];
50
+
51
+ for (const SeederClass of seederArray) {
52
+ const seeder = new SeederClass();
53
+ if (this.connection) {
54
+ seeder.connection = this.connection;
55
+ }
56
+ await seeder.run();
57
+ console.log(`Seeded: ${SeederClass.name}`);
58
+ }
59
+ }
60
+
61
+ async callWith(seeders, connection) {
62
+ for (const [name, SeederClass] of Object.entries(seeders)) {
63
+ const seeder = new SeederClass();
64
+ if (connection || this.connection) {
65
+ seeder.connection = connection || this.connection;
66
+ }
67
+ await seeder.run();
68
+ console.log(`Seeded: ${name}`);
69
+ }
70
+ }
71
+
72
+ async callOnce(SeederClass, identifier) {
73
+ const tableName = 'seeder_log';
74
+
75
+ // Ensure seeder log table exists
76
+ const knex = this.db.getInstance();
77
+ const hasTable = await knex.schema.hasTable(tableName);
78
+ if (!hasTable) {
79
+ await knex.schema.createTable(tableName, (table) => {
80
+ table.increments('id');
81
+ table.string('seeder');
82
+ table.timestamp('executed_at').defaultTo(knex.fn.now());
83
+ });
84
+ }
85
+
86
+ // Check if already executed
87
+ const exists = await this.db.table(tableName).where('seeder', identifier).first();
88
+ if (exists) {
89
+ console.log(`Skipped: ${identifier} (already executed)`);
90
+ return;
91
+ }
92
+
93
+ // Execute seeder
94
+ const seeder = new SeederClass();
95
+ if (this.connection) {
96
+ seeder.connection = this.connection;
97
+ }
98
+ await seeder.run();
99
+
100
+ // Log execution
101
+ await this.db.table(tableName).insert({
102
+ seeder: identifier,
103
+ executed_at: new Date()
104
+ });
105
+
106
+ console.log(`Seeded: ${identifier}`);
107
+ }
108
+
109
+ async progress(total, callback) {
110
+ let completed = 0;
111
+ const updateProgress = (count) => {
112
+ completed += count;
113
+ const percentage = Math.round((completed / total) * 100);
114
+ console.log(`Progress: ${percentage}% (${completed}/${total})`);
115
+ };
116
+
117
+ await callback(updateProgress);
118
+ }
119
+
120
+ async truncate(table) {
121
+ await this.db.table(table, this.connection).truncate();
122
+ }
123
+
124
+ async truncateInOrder(tables) {
125
+ await this.disableForeignKeyChecks();
126
+
127
+ for (const table of tables) {
128
+ await this.truncate(table);
129
+ }
130
+
131
+ await this.enableForeignKeyChecks();
132
+ }
133
+
134
+ async wipeDatabase() {
135
+ const knex = this.db.connection(this.connection);
136
+ let tables = [];
137
+
138
+ if (knex.client.config.client === 'sqlite3') {
139
+ const result = await knex.raw("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
140
+ tables = result.map((row) => row.name);
141
+ } else if (knex.client.config.client === 'mysql2') {
142
+ const result = await knex.raw('SHOW TABLES');
143
+ tables = result[0].map((row) => Object.values(row)[0]);
144
+ } else if (knex.client.config.client === 'pg') {
145
+ const result = await knex.raw("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
146
+ tables = result.rows.map((row) => row.tablename);
147
+ }
148
+
149
+ await this.truncateInOrder(tables);
150
+ }
151
+ }
152
+
153
+ export default Seeder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilana-orm",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "A fully-featured, Eloquent-style ORM for Node.js with TypeScript support",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -36,12 +36,15 @@
36
36
  "types": "./database/connection.d.ts"
37
37
  },
38
38
  "./database/schema-builder": {
39
+ "import": "./database/schema-builder.mjs",
39
40
  "require": "./database/schema-builder.js"
40
41
  },
41
42
  "./orm/Seeder": {
43
+ "import": "./orm/Seeder.mjs",
42
44
  "require": "./orm/Seeder.js"
43
45
  },
44
46
  "./orm/Factory": {
47
+ "import": "./orm/Factory.mjs",
45
48
  "require": "./orm/Factory.js"
46
49
  },
47
50
  "./orm/ModelRegistry": {
@@ -58,6 +61,7 @@
58
61
  "require": "./orm/MigrationRunner.js"
59
62
  },
60
63
  "./database/DB": {
64
+ "import": "./database/DB.mjs",
61
65
  "require": "./database/DB.js"
62
66
  }
63
67
  },