diff --git a/package.json b/package.json index 08186140f..4d2e8d102 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "ts-jest": "^29.0.3", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", + "jiti": "^2.4.2", "typedoc": "^0.23.17", "typescript": "~5.7.3" }, 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..8d82c2502 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14213,6 +14213,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.5": + version: 4.8.1 + resolution: "get-tsconfig@npm:4.8.1" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 536ee85d202f604f4b5fb6be81bcd6e6d9a96846811e83e9acc6de4a04fb49506edea0e1b8cf1d5ee7af33e469916ec2809d4c5445ab8ae015a7a51fbd1572f9 + languageName: node + linkType: hard + "getpass@npm:^0.1.1": version: 0.1.7 resolution: "getpass@npm:0.1.7" @@ -22307,6 +22316,7 @@ __metadata: ts-jest: "npm:^29.0.3" ts-node: "npm:^10.9.1" ts-node-dev: "npm:^2.0.0" + tsx: "npm:^4.19.1" typedoc: "npm:^0.23.17" typescript: "npm:~5.7.3" languageName: unknown @@ -24391,6 +24401,22 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.19.1": + version: 4.19.1 + resolution: "tsx@npm:4.19.1" + dependencies: + esbuild: "npm:~0.23.0" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: cbea9baf57e7406fa0ecc2c03b9bb2501ee740dc28c938f949180a646a28e5d65e7cccbfba340508923bfd45e90320ef9eef7f815cae4515b6ef2ee429edc7ee + languageName: node + linkType: hard + "ttf2woff@npm:^2.0.1": version: 2.0.2 resolution: "ttf2woff@npm:2.0.2"