diff --git a/package.json b/package.json index 08186140f..e61a7a013 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "commitlint": "^18.4.3", "coveralls": "^3.0.3", "jest": "^29.2.1", + "jiti": "^2.4.2", "lefthook": "^1.5.5", "lerna": "^7.4.2", "madge": "^4.0.0", diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 2df7ab9dc..96570336b 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -901,3 +901,7 @@ export function assertDefined(value: T): asserts value is NonNullable { throw new Error(`Value is not defined`); } } + +export function isEsm(): boolean { + return typeof require === 'undefined'; +} diff --git a/packages/sql/package.json b/packages/sql/package.json index 0f344020c..8d87eb6ea 100644 --- a/packages/sql/package.json +++ b/packages/sql/package.json @@ -39,7 +39,8 @@ "@deepkit/logger": "^1.0.1", "@deepkit/orm": "^1.0.1", "@deepkit/stopwatch": "^1.0.1", - "@deepkit/type": "^1.0.1" + "@deepkit/type": "^1.0.1", + "jiti": "^2.4.2" }, "dependencies": { "@types/sqlstring": "^2.2.1", diff --git a/packages/sql/src/cli/base-command.ts b/packages/sql/src/cli/base-command.ts index 2bd371d80..9ca109d08 100644 --- a/packages/sql/src/cli/base-command.ts +++ b/packages/sql/src/cli/base-command.ts @@ -5,4 +5,9 @@ export class BaseCommand { * @description Sets the migration directory. */ protected migrationDir: string & Flag = ''; + + /** + * @description Sets the database path + */ + protected path?: string & Flag; } diff --git a/packages/sql/src/cli/migration-create-command.ts b/packages/sql/src/cli/migration-create-command.ts index 6d5470a74..96fb13335 100644 --- a/packages/sql/src/cli/migration-create-command.ts +++ b/packages/sql/src/cli/migration-create-command.ts @@ -51,6 +51,7 @@ export class MigrationCreateController extends BaseCommand implements Command { empty: boolean & Flag = false, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); if (!this.provider.databases.getDatabases().length) { this.logger.error('No databases detected. Use --path path/to/database.ts'); @@ -105,7 +106,7 @@ export class MigrationCreateController extends BaseCommand implements Command { let migrationName = ''; const date = new Date; - const { format } = require('date-fns'); + const { format } = await import('date-fns'); for (let i = 1; i < 100; i++) { migrationName = format(date, 'yyyyMMdd-HHmm'); if (i > 1) migrationName += '_' + i; diff --git a/packages/sql/src/cli/migration-down-command.ts b/packages/sql/src/cli/migration-down-command.ts index 963442aa7..1836f57dd 100644 --- a/packages/sql/src/cli/migration-down-command.ts +++ b/packages/sql/src/cli/migration-down-command.ts @@ -38,6 +38,7 @@ export class MigrationDownCommand extends BaseCommand { fake: boolean & Flag = false, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database); diff --git a/packages/sql/src/cli/migration-pending-command.ts b/packages/sql/src/cli/migration-pending-command.ts index 8233e3ebb..41d5905f5 100644 --- a/packages/sql/src/cli/migration-pending-command.ts +++ b/packages/sql/src/cli/migration-pending-command.ts @@ -38,6 +38,7 @@ export class MigrationPendingCommand extends BaseCommand { database?: string & Flag<{ char: 'db' }>, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database); diff --git a/packages/sql/src/cli/migration-up-command.ts b/packages/sql/src/cli/migration-up-command.ts index bfbe344a4..3d70f88c2 100644 --- a/packages/sql/src/cli/migration-up-command.ts +++ b/packages/sql/src/cli/migration-up-command.ts @@ -44,6 +44,7 @@ export class MigrationUpCommand extends BaseCommand { all: boolean & Flag = false, ): Promise { if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir); + if (this.path) await this.provider.addDatabase(this.path); const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database); diff --git a/packages/sql/src/migration/migration-provider.ts b/packages/sql/src/migration/migration-provider.ts index 8aa4940ba..534511d09 100644 --- a/packages/sql/src/migration/migration-provider.ts +++ b/packages/sql/src/migration/migration-provider.ts @@ -7,8 +7,7 @@ * * You should have received a copy of the MIT License along with this program. */ - -import { ClassType } from '@deepkit/core'; +import { ClassType, isEsm } from '@deepkit/core'; import { Database, DatabaseRegistry } from '@deepkit/orm'; import glob from 'fast-glob'; import { basename, join } from 'path'; @@ -18,10 +17,7 @@ export class MigrationProvider { protected databaseMap = new Map>(); protected migrationDir: string = 'migrations/'; - constructor( - public databases: DatabaseRegistry, - ) { - } + constructor(public databases: DatabaseRegistry) {} getMigrationDir(): string { return this.migrationDir; @@ -51,22 +47,66 @@ export class MigrationProvider { return migrationsPerDatabase; } + private async createJiti() { + const esm = isEsm(); + const { createJiti } = await import('jiti'); + return createJiti( + esm + ? // @ts-expect-error esm only + import.meta.url + : __filename, + ); + } + + async addDatabase(path: string): Promise { + const jiti = await this.createJiti(); + const exports = Object.values((await jiti.import(join(process.cwd(), path))) || {}); + if (!exports.length) { + throw new Error(`No database found in path ${path}`); + } + + let databaseInstance: Database | undefined; + let foundDatabaseClass: ClassType | undefined; + + for (const value of exports) { + if (value instanceof Database) { + databaseInstance = value; + break; + } + if (Object.getPrototypeOf(value) instanceof Database) { + foundDatabaseClass = value as ClassType; + } + } + + if (!databaseInstance) { + if (foundDatabaseClass) { + throw new Error( + `Found database class ${foundDatabaseClass.name} in path ${path} but it has to be instantiated an exported. export const database = new ${foundDatabaseClass.name}(/* ... */);`, + ); + } + throw new Error(`No database found in path ${path}`); + } + + this.databases.addDatabaseInstance(databaseInstance); + } + async getMigrations(migrationDir: string): Promise { - let migrations: Migration[] = []; + const jiti = await this.createJiti(); - const files = await glob('**/*.ts', { cwd: migrationDir }); + const files = await glob('**/!(*.d).+(ts|js)', { cwd: migrationDir }); + let migrations: Migration[] = []; for (const file of files) { const path = join(process.cwd(), migrationDir, file); - const name = basename(file.replace('.ts', '')); - const migration = await import(path); - if (migration && migration.SchemaMigration) { - const jo = new class extends (migration.SchemaMigration as ClassType) { + const name = basename(file.replace('.ts', '').replace('.js', '')); + const { SchemaMigration } = (await jiti.import<{ SchemaMigration?: ClassType }>(path)) || {}; + if (SchemaMigration) { + const jo = new (class extends (SchemaMigration as ClassType) { constructor() { super(); if (!this.name) this.name = name; } - }; + })(); migrations.push(jo); } } diff --git a/website/src/pages/documentation/orm/migrations.md b/website/src/pages/documentation/orm/migrations.md index d4adc1974..d4389e10a 100644 --- a/website/src/pages/documentation/orm/migrations.md +++ b/website/src/pages/documentation/orm/migrations.md @@ -24,11 +24,14 @@ import { SQLiteDatabaseAdapter } from '@deepkit/sqlite'; import { User } from './models'; export class SQLiteDatabase extends Database { - name = 'default'; - constructor() { - super(new SQLiteDatabaseAdapter('/tmp/myapp.sqlite'), [User]); - } + name = 'default'; + + constructor() { + super(new SQLiteDatabaseAdapter('/tmp/myapp.sqlite'), [User]); + } } + +export const database = new SQLiteDatabase(); ``` ```sh diff --git a/yarn.lock b/yarn.lock index 2b1df8f61..27d821824 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3774,6 +3774,7 @@ __metadata: "@deepkit/orm": ^1.0.1 "@deepkit/stopwatch": ^1.0.1 "@deepkit/type": ^1.0.1 + jiti: ^2.4.2 bin: deepkit-sql: ./bin/deepkit-sql.js languageName: unknown @@ -16603,6 +16604,15 @@ __metadata: languageName: node linkType: hard +"jiti@npm:^2.4.2": + version: 2.4.2 + resolution: "jiti@npm:2.4.2" + bin: + jiti: lib/jiti-cli.mjs + checksum: 4ceac133a08c8faff7eac84aabb917e85e8257f5ad659e843004ce76e981c457c390a220881748ac67ba1b940b9b729b30fb85cbaf6e7989f04b6002c94da331 + languageName: node + linkType: hard + "jpeg-js@npm:^0.4.4": version: 0.4.4 resolution: "jpeg-js@npm:0.4.4" @@ -22300,6 +22310,7 @@ __metadata: commitlint: "npm:^18.4.3" coveralls: "npm:^3.0.3" jest: "npm:^29.2.1" + jiti: "npm:^2.4.2" lefthook: "npm:^1.5.5" lerna: "npm:^7.4.2" madge: "npm:^4.0.0"