Skip to content

Commit

Permalink
refactor: decouple database initialization from the provided implemen…
Browse files Browse the repository at this point in the history
…tation
  • Loading branch information
Char2sGu committed May 27, 2022
1 parent 171c7ff commit f19bf74
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
HttpBadRequestError,
HttpBody,
} from "@deepkit/http";
import { InjectDatabaseSession } from "src/database/database.module";
import { InjectDatabaseSession } from "src/database/database.tokens";
import { HttpUnauthorizedError } from "src/shared/http-error";
import { User } from "src/user/user.entity";

Expand Down
39 changes: 39 additions & 0 deletions src/database/database-initializer.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { EventDispatcher } from "@deepkit/event";
import { SQLiteDatabaseAdapter } from "@deepkit/sqlite";

import { DatabaseConfig } from "./database.config";
import { InjectDatabase } from "./database.tokens";
import { forwardDatabaseEvents } from "./database-event";

export class DatabaseInitializer {
constructor(
private database: InjectDatabase,
private config: Pick<DatabaseConfig, "logging">,
private eventDispatcher: EventDispatcher,
) {}

async initialize(): Promise<void> {
await this.fixConnectionLogger();
if (this.config.logging) this.database.logger.enableLogging();
forwardDatabaseEvents(this.database, this.eventDispatcher);
}

/**
* Temporary workaround for SQLite connection logger.
*
* SQLite uses single connection, thus once a connection is created, all the
* following operations will reuse that connection.
*
* The first connection is created during migration, which accidentally
* doesn't have access to our logger and created a new one, thus all the
* operations will not use our logger.
*
* Should be removed once fixed.
*/
private async fixConnectionLogger() {
const adapter = this.database.adapter as SQLiteDatabaseAdapter;
const connection = await adapter.connectionPool.getConnection();
connection["logger"] = this.database.logger;
connection.release();
}
}
11 changes: 10 additions & 1 deletion src/database/database.listener.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { eventDispatcher } from "@deepkit/event";
import { onServerBootstrap } from "@deepkit/framework";
import { httpWorkflow } from "@deepkit/http";
import { DatabaseSession } from "@deepkit/orm";

import { DATABASE_SESSION } from "./database.module";
import { DATABASE_SESSION } from "./database.tokens";
import { DatabaseInitializer } from "./database-initializer.service";

export class DatabaseListener {
constructor(private databaseInitializer: DatabaseInitializer) {}

@eventDispatcher.listen(onServerBootstrap)
async onServerBootstrap(): Promise<void> {
await this.databaseInitializer.initialize();
}

@eventDispatcher.listen(httpWorkflow.onController, 1000)
async afterHttpController(
event: typeof httpWorkflow.onController.event,
Expand Down
36 changes: 13 additions & 23 deletions src/database/database.module.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import { createModule } from "@deepkit/app";
import { ClassType } from "@deepkit/core";
import { Inject } from "@deepkit/injector";
import * as orm from "@deepkit/orm"; // We have to use namespace import here as a temporary workaround, otherwise the application will not be able to bootstrap. This will be fixed in the next release.
import * as orm from "@deepkit/orm"; // temporary workaround: we have to use namespace import here as a temporary workaround, otherwise the application will not be able to bootstrap. This will be fixed in the next release

import { DatabaseConfig } from "./database.config";
import { DatabaseListener } from "./database.listener";
import { SQLiteDatabase } from "./database.provider";

// temporary workaround: when database is provided using a class extending
// `Database`, and some entities is extending a base class and passing generic
// type arguments, the debugger will fail to work
// https://github.com/deepkit/deepkit-framework/issues/241
export const DATABASE = "token:database"; // temporary workaround: type `symbol` is missing in type `ExportType`, so we use string instead
class DatabaseFactoryToken {} // temporary workaround for https://github.com/deepkit/deepkit-framework/issues/240
export type InjectDatabase = Inject<orm.Database, typeof DATABASE>;

// temporary workaround: `Database` cannot be used as a token when framework
// debug mode is enabled
export const DATABASE_SESSION = "token:database-session"; // temporary workaround: type `symbol` is missing in type `ExportType`, so we use string instead
export type InjectDatabaseSession = Inject<
orm.DatabaseSession<orm.DatabaseAdapter>,
typeof DATABASE_SESSION
>;
import {
DATABASE,
DATABASE_SESSION,
DatabaseFactoryToken,
} from "./database.tokens";
import { DatabaseInitializer } from "./database-initializer.service";

export class DatabaseModule extends createModule(
{
Expand All @@ -34,23 +23,24 @@ export class DatabaseModule extends createModule(
(db as orm.Database).createSession(),
scope: "http",
},
DatabaseInitializer,
],
listeners: [DatabaseListener],
exports: [DATABASE, DATABASE_SESSION],
config: DatabaseConfig,
},
"database",
) {
protected entities = new DatabaseEntitySet();
private entities = new DatabaseEntitySet();

override process(): void {
this.addProvider({ provide: DatabaseEntitySet, useValue: this.entities });
}

withEntities(...entities: ClassType[]): this {
entities.forEach((entity) => this.entities.add(entity));
return this;
}

override process(): void {
this.addProvider({ provide: DatabaseEntitySet, useValue: this.entities });
}
}

export class DatabaseEntitySet extends Set<ClassType> {}
31 changes: 1 addition & 30 deletions src/database/database.provider.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,13 @@
import { EventDispatcher } from "@deepkit/event";
import { Database } from "@deepkit/orm";
import { SQLiteDatabaseAdapter } from "@deepkit/sqlite";

import { DatabaseConfig } from "./database.config";
import { DatabaseEntitySet } from "./database.module";
import { forwardDatabaseEvents } from "./database-event";

export class SQLiteDatabase extends Database {
override name = "default";

constructor(
url: DatabaseConfig["url"],
logging: DatabaseConfig["logging"],
entities: DatabaseEntitySet,
eventDispatcher: EventDispatcher,
) {
constructor(url: DatabaseConfig["url"], entities: DatabaseEntitySet) {
super(new SQLiteDatabaseAdapter(url), [...entities]);
forwardDatabaseEvents(this, eventDispatcher);
this.fixConnectionLogger();
if (logging) this.logger.enableLogging();
}

/**
* Temporary workaround for SQLite connection logger.
*
* SQLite uses single connection, thus once a connection is created, all the
* following operations will reuse that connection.
*
* The first connection is created during migration, which accidentally
* doesn't have access to our logger and created a new one, thus all the
* operations will not use our logger.
*
* Should be removed once fixed.
*/
private async fixConnectionLogger() {
const adapter = this.adapter as SQLiteDatabaseAdapter;
const connection = await adapter.connectionPool.getConnection();
connection["logger"] = this.logger;
connection.release();
}
}
18 changes: 18 additions & 0 deletions src/database/database.tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Inject } from "@deepkit/injector";
import * as orm from "@deepkit/orm"; // temporary workaround: we have to use namespace import here as a temporary workaround, otherwise the application will not be able to bootstrap. This will be fixed in the next release

// temporary workaround: when database is provided using a class extending
// `Database`, and some entities is extending a base class and passing generic
// type arguments, the debugger will fail to work
// https://github.com/deepkit/deepkit-framework/issues/241
export const DATABASE = "token:database"; // temporary workaround: type `symbol` is missing in type `ExportType`, so we use string instead
export class DatabaseFactoryToken {} // temporary workaround for https://github.com/deepkit/deepkit-framework/issues/240
export type InjectDatabase = Inject<orm.Database, typeof DATABASE>;

// temporary workaround: `Database` cannot be used as a token when framework
// debug mode is enabled
export const DATABASE_SESSION = "token:database-session"; // temporary workaround: type `symbol` is missing in type `ExportType`, so we use string instead
export type InjectDatabaseSession = Inject<
orm.DatabaseSession<orm.DatabaseAdapter>,
typeof DATABASE_SESSION
>;
2 changes: 1 addition & 1 deletion src/user/user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { http, HttpQueries } from "@deepkit/http";
import { InjectDatabaseSession } from "src/database/database.module";
import { InjectDatabaseSession } from "src/database/database.tokens";
import { ResourceService } from "src/resource/resource.service";
import { ResourceFilterMap } from "src/resource/resource-filter.typings";
import {
Expand Down

0 comments on commit f19bf74

Please sign in to comment.