Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sql): add database when path is provided to migration commands #613

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"tsx": "^4.19.1",
"typedoc": "^0.23.17",
"typescript": "~5.4.5"
},
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,3 +889,7 @@ export function assertDefined<T>(value: T): asserts value is NonNullable<T> {
throw new Error(`Value is not defined`);
}
}

export function isEsm(): boolean {
return typeof require === 'undefined';
}
3 changes: 2 additions & 1 deletion packages/sql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"@deepkit/logger": "^1.0.1-alpha.13",
"@deepkit/orm": "^1.0.1-alpha.13",
"@deepkit/stopwatch": "^1.0.1-alpha.13",
"@deepkit/type": "^1.0.1-alpha.13"
"@deepkit/type": "^1.0.1-alpha.13",
"tsx": "4.19.1"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: remove

},
"dependencies": {
"@types/sqlstring": "^2.2.1",
Expand Down
5 changes: 5 additions & 0 deletions packages/sql/src/cli/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
3 changes: 2 additions & 1 deletion packages/sql/src/cli/migration-create-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class MigrationCreateController extends BaseCommand implements Command {
empty: boolean & Flag = false,
): Promise<void> {
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');
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/sql/src/cli/migration-down-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class MigrationDownCommand extends BaseCommand {
fake: boolean & Flag = false,
): Promise<void> {
if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir);
if (this.path) await this.provider.addDatabase(this.path);

const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database);

Expand Down
1 change: 1 addition & 0 deletions packages/sql/src/cli/migration-pending-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class MigrationPendingCommand extends BaseCommand {
database?: string & Flag<{ char: 'db' }>,
): Promise<void> {
if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir);
if (this.path) await this.provider.addDatabase(this.path);

const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database);

Expand Down
1 change: 1 addition & 0 deletions packages/sql/src/cli/migration-up-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class MigrationUpCommand extends BaseCommand {
all: boolean & Flag = false,
): Promise<void> {
if (this.migrationDir) this.provider.setMigrationDir(this.migrationDir);
if (this.path) await this.provider.addDatabase(this.path);

const migrationsPerDatabase = await this.provider.getMigrationsPerDatabase(database);

Expand Down
67 changes: 56 additions & 11 deletions packages/sql/src/migration/migration-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,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';
Expand Down Expand Up @@ -51,24 +51,69 @@ export class MigrationProvider {
return migrationsPerDatabase;
}

async getMigrations(migrationDir: string): Promise<Migration[]> {
let migrations: Migration[] = [];

const files = await glob('**/*.ts', { cwd: migrationDir });
require('ts-node').register({
// FIXME: esm imports doesn't work
private async registerTsNode() {
const esm = isEsm();
const { register } = await import('ts-node');
register({
esm,
preferTsExts: true,
experimentalTsImportSpecifiers: true,
compilerOptions: {
experimentalDecorators: true,
module: 'undefined' !== typeof require ? 'CommonJS' : 'ESNext',
module: esm ? 'ESNext' : 'CommonJS',
},
transpileOnly: true,
});
}

async addDatabase(path: string): Promise<void> {
if (path.endsWith('.ts')) await this.registerTsNode();

const exports = Object.values((await import(join(process.cwd(), path)) || {}));
if (!exports.length) {
throw new Error(`No database found in path ${path}`);
}

let databaseInstance: Database | undefined;
let foundDatabaseClass: ClassType<Database> | undefined;

for (const value of exports) {
if (value instanceof Database) {
marcus-sa marked this conversation as resolved.
Show resolved Hide resolved
databaseInstance = value;
break;
}
if (Object.getPrototypeOf(value) instanceof Database) {
marcus-sa marked this conversation as resolved.
Show resolved Hide resolved
foundDatabaseClass = value as ClassType<Database>;
}
}

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<Migration[]> {
let migrations: Migration[] = [];

let files = await glob('**/!(*.d).ts', { cwd: migrationDir });
if (files.length) {
await this.registerTsNode();
} else {
files = await glob('**/*.js', { cwd: migrationDir });
}

for (const file of files) {
const path = join(process.cwd(), migrationDir, file);
const name = basename(file.replace('.ts', ''));
const migration = require(path);
if (migration && migration.SchemaMigration) {
const jo = new class extends (migration.SchemaMigration as ClassType<Migration>) {
const name = basename(file.replace('.ts', '').replace('.js', ''));
const { SchemaMigration } = (await import(path) || {});
if (SchemaMigration) {
const jo = new class extends (SchemaMigration as ClassType<Migration>) {
constructor() {
super();
if (!this.name) this.name = name;
Expand Down
11 changes: 7 additions & 4 deletions website/src/pages/documentation/orm/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading