ilana-orm 1.0.17 → 1.0.19

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
@@ -127,7 +127,10 @@ import Database from 'ilana-orm/database/connection.js';
127
127
  const config = {
128
128
  default: process.env.DB_CONNECTION || 'mysql',
129
129
  timezone: process.env.DB_TIMEZONE || 'UTC',
130
-
130
+
131
+ // Set to true (or use process.env.NODE_ENV === 'development') to log all SQL queries
132
+ logging: false,
133
+
131
134
  connections: {
132
135
  sqlite: {
133
136
  client: 'sqlite3',
@@ -136,7 +139,7 @@ const config = {
136
139
  },
137
140
  useNullAsDefault: true
138
141
  },
139
-
142
+
140
143
  mysql: {
141
144
  client: 'mysql2',
142
145
  connection: {
@@ -148,7 +151,7 @@ const config = {
148
151
  timezone: process.env.DB_TIMEZONE || 'UTC'
149
152
  }
150
153
  },
151
-
154
+
152
155
  postgres: {
153
156
  client: 'pg',
154
157
  connection: {
@@ -160,12 +163,12 @@ const config = {
160
163
  }
161
164
  }
162
165
  },
163
-
166
+
164
167
  migrations: {
165
168
  directory: './${structure.databaseDir}/migrations',
166
169
  tableName: 'migrations'
167
170
  },
168
-
171
+
169
172
  seeds: {
170
173
  directory: './${structure.databaseDir}/seeds'
171
174
  }
@@ -186,7 +189,10 @@ const Database = require('ilana-orm/database/connection');
186
189
  const config = {
187
190
  default: process.env.DB_CONNECTION || 'mysql',
188
191
  timezone: process.env.DB_TIMEZONE || 'UTC',
189
-
192
+
193
+ // Set to true (or use process.env.NODE_ENV === 'development') to log all SQL queries
194
+ logging: false,
195
+
190
196
  connections: {
191
197
  sqlite: {
192
198
  client: 'sqlite3',
@@ -195,7 +201,7 @@ const config = {
195
201
  },
196
202
  useNullAsDefault: true
197
203
  },
198
-
204
+
199
205
  mysql: {
200
206
  client: 'mysql2',
201
207
  connection: {
@@ -207,7 +213,7 @@ const config = {
207
213
  timezone: process.env.DB_TIMEZONE || 'UTC'
208
214
  }
209
215
  },
210
-
216
+
211
217
  postgres: {
212
218
  client: 'pg',
213
219
  connection: {
@@ -219,12 +225,12 @@ const config = {
219
225
  }
220
226
  }
221
227
  },
222
-
228
+
223
229
  migrations: {
224
230
  directory: './${structure.databaseDir}/migrations',
225
231
  tableName: 'migrations'
226
232
  },
227
-
233
+
228
234
  seeds: {
229
235
  directory: './${structure.databaseDir}/seeds'
230
236
  }
@@ -277,90 +283,20 @@ function getModelTemplate(className, tableName) {
277
283
 
278
284
  if (isTypeScriptProject()) {
279
285
  return `import Model from 'ilana-orm/orm/Model';
280
- // import { MoneyCast, EncryptedCast } from 'ilana-orm/orm/CustomCasts';
281
286
 
282
287
  export default class ${className} extends Model {
283
288
  protected static table = '${tableName}';
284
- protected static timestamps = true;
285
- protected static softDeletes = false;
286
-
287
- // For UUID primary keys, uncomment:
288
- // protected static keyType = 'string' as const;
289
- // protected static incrementing = false;
290
-
291
- protected fillable: string[] = [];
292
- protected hidden: string[] = [];
293
- protected appends: string[] = [];
294
- protected casts = {
295
- // Basic casts
296
- // is_active: 'boolean' as const,
297
- // metadata: 'json' as const,
298
- // tags: 'array' as const,
299
-
300
- // Custom casts
301
- // price: new MoneyCast(),
302
- // secret: new EncryptedCast('your-key'),
303
- };
304
-
305
- // Define relationships here
306
- // example() {
307
- // return this.hasMany(RelatedModel, 'foreign_key');
308
- // }
309
-
310
- // Define scopes here
311
- // static scopeActive(query: any) {
312
- // query.where('is_active', true);
313
- // }
314
-
315
- // Register for polymorphic relationships
316
- // static {
317
- // this.register();
318
- // }
289
+ protected static fillable: string[] = [];
319
290
  }
320
291
  `;
321
292
  }
322
293
 
323
294
  if (isESModule) {
324
295
  return `import Model from 'ilana-orm/orm/Model';
325
- // import { MoneyCast, EncryptedCast } from 'ilana-orm/orm/CustomCasts';
326
296
 
327
297
  class ${className} extends Model {
328
298
  static table = '${tableName}';
329
- static timestamps = true;
330
- static softDeletes = false;
331
-
332
- // For UUID primary keys, uncomment:
333
- // static keyType = 'string';
334
- // static incrementing = false;
335
-
336
- fillable = [];
337
- hidden = [];
338
- appends = [];
339
- casts = {
340
- // Basic casts
341
- // is_active: 'boolean',
342
- // metadata: 'json',
343
- // tags: 'array',
344
-
345
- // Custom casts
346
- // price: new MoneyCast(),
347
- // secret: new EncryptedCast('your-key'),
348
- };
349
-
350
- // Define relationships here
351
- // example() {
352
- // return this.hasMany(RelatedModel, 'foreign_key');
353
- // }
354
-
355
- // Define scopes here
356
- // static scopeActive(query) {
357
- // query.where('is_active', true);
358
- // }
359
-
360
- // Register for polymorphic relationships
361
- // static {
362
- // this.register();
363
- // }
299
+ static fillable = [];
364
300
  }
365
301
 
366
302
  export default ${className};
@@ -368,45 +304,10 @@ export default ${className};
368
304
  }
369
305
 
370
306
  return `const Model = require('ilana-orm/orm/Model');
371
- // const { MoneyCast, EncryptedCast } = require('ilana-orm/orm/CustomCasts');
372
307
 
373
308
  class ${className} extends Model {
374
309
  static table = '${tableName}';
375
- static timestamps = true;
376
- static softDeletes = false;
377
-
378
- // For UUID primary keys, uncomment:
379
- // static keyType = 'string';
380
- // static incrementing = false;
381
-
382
- fillable = [];
383
- hidden = [];
384
- appends = [];
385
- casts = {
386
- // Basic casts
387
- // is_active: 'boolean',
388
- // metadata: 'json',
389
- // tags: 'array',
390
-
391
- // Custom casts
392
- // price: new MoneyCast(),
393
- // secret: new EncryptedCast('your-key'),
394
- };
395
-
396
- // Define relationships here
397
- // example() {
398
- // return this.hasMany(RelatedModel, 'foreign_key');
399
- // }
400
-
401
- // Define scopes here
402
- // static scopeActive(query) {
403
- // query.where('is_active', true);
404
- // }
405
-
406
- // Register for polymorphic relationships
407
- // static {
408
- // this.register();
409
- // }
310
+ static fillable = [];
410
311
  }
411
312
 
412
313
  module.exports = ${className};
@@ -421,11 +322,7 @@ function getPivotModelTemplate(className, tableName) {
421
322
 
422
323
  export default class ${className} extends Model {
423
324
  protected static table = '${tableName}';
424
- protected static timestamps = true;
425
-
426
- protected fillable: string[] = [];
427
-
428
- // Define pivot relationships here
325
+ protected static fillable: string[] = [];
429
326
  }
430
327
  `;
431
328
  }
@@ -435,11 +332,7 @@ export default class ${className} extends Model {
435
332
 
436
333
  class ${className} extends Model {
437
334
  static table = '${tableName}';
438
- static timestamps = true;
439
-
440
- fillable = [];
441
-
442
- // Define pivot relationships here
335
+ static fillable = [];
443
336
  }
444
337
 
445
338
  export default ${className};
@@ -450,11 +343,7 @@ export default ${className};
450
343
 
451
344
  class ${className} extends Model {
452
345
  static table = '${tableName}';
453
- static timestamps = true;
454
-
455
- fillable = [];
456
-
457
- // Define pivot relationships here
346
+ static fillable = [];
458
347
  }
459
348
 
460
349
  module.exports = ${className};
@@ -471,29 +360,27 @@ function generateFactory(className) {
471
360
  }
472
361
 
473
362
  const modelPath = structure.hasSrc ? `../models/${className}.js` : `../../models/${className}.js`;
363
+ const isESModule = isESModuleProject();
474
364
  const template = isTypeScriptProject() ?
475
365
  `import { defineFactory } from 'ilana-orm/orm/Factory.js';
476
366
  import ${className} from '${modelPath}';
477
367
 
478
368
  export default defineFactory(${className}, (faker) => ({
479
- // Define your factory attributes here
480
369
  // name: faker.person.fullName(),
481
- // email: faker.internet.email(),
482
- }))
483
- .state('example', (faker) => ({
484
- // Define state modifications here
370
+ }));
371
+ ` : isESModule ?
372
+ `import { defineFactory } from 'ilana-orm/orm/Factory';
373
+ import ${className} from '${modelPath}';
374
+
375
+ export default defineFactory(${className}, (faker) => ({
376
+ // name: faker.person.fullName(),
485
377
  }));
486
378
  ` :
487
379
  `const { defineFactory } = require('ilana-orm/orm/Factory');
488
380
  const ${className} = require('${modelPath.replace('.js', '')}');
489
381
 
490
382
  module.exports = defineFactory(${className}, (faker) => ({
491
- // Define your factory attributes here
492
383
  // name: faker.person.fullName(),
493
- // email: faker.internet.email(),
494
- }))
495
- .state('example', (faker) => ({
496
- // Define state modifications here
497
384
  }));
498
385
  `;
499
386
 
@@ -511,34 +398,38 @@ function generateSeeder(className) {
511
398
  }
512
399
 
513
400
  const modelPath = structure.hasSrc ? `../models/${className}.js` : `../../models/${className}.js`;
401
+ const isESModule = isESModuleProject();
514
402
  const template = isTypeScriptProject() ?
515
403
  `import Seeder from 'ilana-orm/orm/Seeder.js';
404
+ import { factory } from 'ilana-orm/orm/Factory.js';
516
405
  import ${className} from '${modelPath}';
517
406
  import '../factories/${className}Factory.js';
518
407
 
519
408
  export default class ${className}Seeder extends Seeder {
520
409
  async run(): Promise<void> {
521
- console.log('Seeding ${className.toLowerCase()}s...');
522
-
523
- // Create sample data
524
- await ${className}.factory().times(10).create();
410
+ await factory(${className}).times(10).create();
411
+ }
412
+ }
413
+ ` : isESModule ?
414
+ `import Seeder from 'ilana-orm/orm/Seeder';
415
+ import { factory } from 'ilana-orm/orm/Factory';
416
+ import ${className} from '${modelPath}';
417
+ import '../factories/${className}Factory.js';
525
418
 
526
- console.log('${className}s seeded successfully');
419
+ export default class ${className}Seeder extends Seeder {
420
+ async run() {
421
+ await factory(${className}).times(10).create();
527
422
  }
528
423
  }
529
424
  ` :
530
425
  `const Seeder = require('ilana-orm/orm/Seeder');
426
+ const { factory } = require('ilana-orm/orm/Factory');
531
427
  const ${className} = require('${modelPath.replace('.js', '')}');
532
428
  require('../factories/${className}Factory');
533
429
 
534
430
  class ${className}Seeder extends Seeder {
535
431
  async run() {
536
- console.log('Seeding ${className.toLowerCase()}s...');
537
-
538
- // Create sample data
539
- await ${className}.factory().times(10).create();
540
-
541
- console.log('${className}s seeded successfully');
432
+ await factory(${className}).times(10).create();
542
433
  }
543
434
  }
544
435
 
@@ -823,13 +714,13 @@ function parseMigrationsForTable(tableName, migrationsDir) {
823
714
 
824
715
  // Match column definitions: table.string('col'), table.integer('col').nullable(), etc.
825
716
  const colRegex = new RegExp(
826
- `${alias}\\.(\\w+)\\s*\\(\\s*['"]([^'"]+)['"](?:[^)]*)?\\)([^;\\n]*)`,
717
+ `${alias}\\.(\\w+)\\s*\\(\\s*['"]([^'"]+)['"]([^)]*)\\)([^;\\n]*)`,
827
718
  'g'
828
719
  );
829
720
 
830
721
  let colMatch;
831
722
  while ((colMatch = colRegex.exec(block)) !== null) {
832
- const [, method, colName, rest] = colMatch;
723
+ const [, method, colName, args, rest] = colMatch;
833
724
  const knexDef = KNEX_COLUMN_MAP[method];
834
725
  if (!knexDef) continue;
835
726
 
@@ -837,7 +728,17 @@ function parseMigrationsForTable(tableName, migrationsDir) {
837
728
  const isNotNullable = /\.notNullable\(\)/.test(rest);
838
729
  const nullable = isNullable ? true : isNotNullable ? false : knexDef.nullable;
839
730
 
840
- columns[colName] = { type: knexDef.type, nullable };
731
+ // For enum columns, extract the values array and build a union type
732
+ let tsType = knexDef.type;
733
+ if (method === 'enum') {
734
+ const valuesMatch = args.match(/\[([^\]]+)\]/);
735
+ if (valuesMatch) {
736
+ const values = [...valuesMatch[1].matchAll(/['"]([^'"]+)['"]/g)].map(m => `'${m[1]}'`);
737
+ if (values.length) tsType = values.join(' | ');
738
+ }
739
+ }
740
+
741
+ columns[colName] = { type: tsType, nullable };
841
742
  }
842
743
  }
843
744
  }
@@ -1574,4 +1475,5 @@ if (require.main === module) {
1574
1475
  main();
1575
1476
  }
1576
1477
 
1577
- module.exports = commands;
1478
+ module.exports = commands;
1479
+ module.exports._templates = { getModelTemplate, getPivotModelTemplate };
@@ -2,6 +2,7 @@ import { Knex } from 'knex';
2
2
 
3
3
  export interface DatabaseConfig {
4
4
  default: string;
5
+ logging?: boolean;
5
6
  connections: {
6
7
  [name: string]: Knex.Config;
7
8
  };
@@ -27,6 +28,8 @@ export default class Database {
27
28
  private static _currentTransaction: Knex.Transaction | null;
28
29
 
29
30
  static configure(config: DatabaseConfig): void;
31
+ static enableLogging(): void;
32
+ static disableLogging(): void;
30
33
  static connection(name?: string): Knex;
31
34
  static getDefaultConnection(): string;
32
35
  static hasConnection(name: string): boolean;
@@ -3,11 +3,33 @@ const knex = require('knex');
3
3
  class Database {
4
4
  static connections = new Map();
5
5
  static _currentTransaction = null;
6
+ static _logging = false;
7
+
8
+ static enableLogging() {
9
+ this._logging = true;
10
+ }
11
+
12
+ static disableLogging() {
13
+ this._logging = false;
14
+ }
15
+
16
+ static _log(sql, bindings, ms) {
17
+ if (!this._logging) return;
18
+ const bound = sql.replace(/\?/g, () => {
19
+ const val = bindings?.shift();
20
+ if (val === null || val === undefined) return 'NULL';
21
+ if (typeof val === 'string') return `'${val}'`;
22
+ return val;
23
+ });
24
+ const time = ms !== undefined ? ` \x1b[90m— ${ms}ms\x1b[0m` : '';
25
+ console.log(`\x1b[36m[IlanaORM]\x1b[0m ${bound}${time}`);
26
+ }
6
27
 
7
28
  static configure(config) {
8
29
  this.config = config;
9
30
  this.defaultConnection = config.default;
10
-
31
+ if (config.logging) this._logging = true;
32
+
11
33
  // Initialize all configured connections
12
34
  for (const [name, connConfig] of Object.entries(config.connections)) {
13
35
  try {
@@ -21,6 +43,23 @@ class Database {
21
43
  directory: './seeds'
22
44
  }
23
45
  });
46
+
47
+ // Attach query logger
48
+ connection.on('query', (query) => {
49
+ this._queryStart = Date.now();
50
+ this._pendingQuery = query.sql;
51
+ this._pendingBindings = [...(query.bindings || [])];
52
+ });
53
+ connection.on('query-response', (response, query) => {
54
+ const ms = Date.now() - (this._queryStart || Date.now());
55
+ this._log(query.sql, [...(query.bindings || [])], ms);
56
+ });
57
+ connection.on('query-error', (error, query) => {
58
+ if (this._logging) {
59
+ console.error(`\x1b[36m[IlanaORM]\x1b[0m \x1b[31mERROR\x1b[0m ${query.sql}`);
60
+ }
61
+ });
62
+
24
63
  this.connections.set(name, connection);
25
64
  } catch (error) {
26
65
  if (error.code === 'MODULE_NOT_FOUND' && error.message.includes(connConfig.client)) {
@@ -207,6 +207,10 @@ class SchemaBuilder {
207
207
  return Promise.resolve();
208
208
  }
209
209
 
210
+ enableVectorExtension() {
211
+ return this.knex.raw('CREATE EXTENSION IF NOT EXISTS vector');
212
+ }
213
+
210
214
  createEnum(name, values) {
211
215
  if (this.knex.client.config.client === 'pg') {
212
216
  return this.knex.raw(`CREATE TYPE ${name} AS ENUM (${values.map(v => `'${v}'`).join(', ')})`);
package/ilana.config.js CHANGED
@@ -1,4 +1,4 @@
1
- const Database = require('ilana/database/connection').default;
1
+ const Database = require('./database/connection');
2
2
 
3
3
  const config = {
4
4
  default: 'sqlite',
package/index.d.ts CHANGED
@@ -5,5 +5,22 @@ export { default as Database } from './database/connection';
5
5
  export * from './orm/Relation';
6
6
  export * from './orm/CustomCasts';
7
7
 
8
+ export declare class FExpression {
9
+ constructor(column: string);
10
+ plus(n: number): any;
11
+ minus(n: number): any;
12
+ times(n: number): any;
13
+ divide(n: number): any;
14
+ }
15
+ export declare function F(column: string): FExpression;
16
+
17
+ export declare class ModelNotFoundException extends Error {
18
+ name: 'ModelNotFoundException';
19
+ model: string;
20
+ id?: any;
21
+ constructor(model: string, id?: any);
22
+ toResponse(): { status: 404; message: string };
23
+ }
24
+
8
25
  // Default export
9
26
  export { default } from './orm/Model';
package/index.edge.js ADDED
@@ -0,0 +1,10 @@
1
+ // Edge runtime entry point — no auto-loading of ilana.config.js.
2
+ // Use this when running in Cloudflare Workers, Deno, Bun, or Next.js edge routes.
3
+ // You must call Database.configure(config) explicitly before using any models.
4
+ //
5
+ // import { Model, Database } from 'ilana-orm/edge';
6
+ // Database.configure({ default: 'pg', connections: { pg: { ... } } });
7
+
8
+ 'use strict';
9
+ global.__ILANA_EDGE__ = true;
10
+ module.exports = require('./index.js');
package/index.edge.mjs ADDED
@@ -0,0 +1,37 @@
1
+ // Edge runtime entry point — ESM wrapper.
2
+ // Sets __ILANA_EDGE__ before any model is loaded so the auto-loader is skipped.
3
+ import { createRequire } from 'module';
4
+ const require = createRequire(import.meta.url);
5
+
6
+ globalThis.__ILANA_EDGE__ = true;
7
+ const exports = require('./index.js');
8
+
9
+ export const {
10
+ Model,
11
+ QueryBuilder,
12
+ Collection,
13
+ Database,
14
+ DB,
15
+ SchemaBuilder,
16
+ MigrationRunner,
17
+ Seeder,
18
+ Factory,
19
+ defineFactory,
20
+ ModelNotFoundException,
21
+ F,
22
+ HasOne,
23
+ HasMany,
24
+ BelongsTo,
25
+ BelongsToMany,
26
+ HasManyThrough,
27
+ MorphTo,
28
+ MorphOne,
29
+ MorphMany,
30
+ MoneyCast,
31
+ EncryptedCast,
32
+ JsonCast,
33
+ ArrayCast,
34
+ DateCast,
35
+ } = exports;
36
+
37
+ export default exports.Model;
package/index.js CHANGED
@@ -10,6 +10,8 @@ const Seeder = require('./orm/Seeder');
10
10
  const Factory = require('./orm/Factory');
11
11
  const Relation = require('./orm/Relation');
12
12
  const CustomCasts = require('./orm/CustomCasts');
13
+ const { ModelNotFoundException } = require('./orm/Errors');
14
+ const { F } = require('./orm/F');
13
15
 
14
16
  module.exports = {
15
17
  Model,
@@ -22,11 +24,13 @@ module.exports = {
22
24
  Seeder,
23
25
  Factory: Factory.Factory,
24
26
  defineFactory: Factory.defineFactory,
25
-
27
+ ModelNotFoundException,
28
+ F,
29
+
26
30
  // Relationships
27
31
  MorphOne: Relation.MorphOne,
28
32
  ...Relation,
29
-
33
+
30
34
  // Custom Casts
31
35
  ...CustomCasts
32
36
  };
package/index.mjs CHANGED
@@ -15,6 +15,8 @@ export const {
15
15
  Seeder,
16
16
  Factory,
17
17
  defineFactory,
18
+ ModelNotFoundException,
19
+ F,
18
20
  Relation,
19
21
  HasOne,
20
22
  HasMany,
package/jest.config.js ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ setupFiles: ['<rootDir>/tests/setup.js'],
4
+ };
package/orm/Errors.js ADDED
@@ -0,0 +1,18 @@
1
+ class ModelNotFoundException extends Error {
2
+ constructor(model, id) {
3
+ const message = id !== undefined
4
+ ? `${model} with id ${id} not found`
5
+ : `${model} not found`;
6
+ super(message);
7
+ this.name = 'ModelNotFoundException';
8
+ this.model = model;
9
+ this.id = id;
10
+ if (Error.captureStackTrace) Error.captureStackTrace(this, ModelNotFoundException);
11
+ }
12
+
13
+ toResponse() {
14
+ return { status: 404, message: this.message };
15
+ }
16
+ }
17
+
18
+ module.exports = { ModelNotFoundException };
package/orm/F.js ADDED
@@ -0,0 +1,22 @@
1
+ const Database = require('../database/connection');
2
+
3
+ class FExpression {
4
+ constructor(column) {
5
+ this.column = column;
6
+ }
7
+
8
+ _raw(op, n) {
9
+ return Database.raw(`?? ${op} ?`, [this.column, n]);
10
+ }
11
+
12
+ plus(n) { return this._raw('+', n); }
13
+ minus(n) { return this._raw('-', n); }
14
+ times(n) { return this._raw('*', n); }
15
+ divide(n) { return this._raw('/', n); }
16
+ }
17
+
18
+ function F(column) {
19
+ return new FExpression(column);
20
+ }
21
+
22
+ module.exports = { F, FExpression };