Skip to content

Commit

Permalink
feat(forRoot): Allow connection name as entry param
Browse files Browse the repository at this point in the history
  • Loading branch information
LeoAnesi committed Oct 9, 2021
1 parent 38ec510 commit 3570590
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 49 deletions.
29 changes: 21 additions & 8 deletions lib/common/typeorm.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
ConnectionOptions,
EntityManager,
EntitySchema,
Repository
Repository,
} from 'typeorm';
import { v4 as uuid } from 'uuid';
import { CircularDependencyException } from '../exceptions/circular-dependency.exception';
Expand Down Expand Up @@ -118,13 +118,19 @@ export function getEntityManagerToken(
: `${connection.name}EntityManager`;
}

export function handleRetry(
export function handleRetry({
retryAttempts = 9,
retryDelay = 3000,
connectionName = DEFAULT_CONNECTION_NAME,
verboseRetryLog = false,
toRetry?: (err: any) => boolean,
): <T>(source: Observable<T>) => Observable<T> {
toRetry,
}: {
retryAttempts?: number;
retryDelay?: number;
connectionName: string;
verboseRetryLog?: boolean;
toRetry?: (err: any) => boolean;
}): <T>(source: Observable<T>) => Observable<T> {
return <T>(source: Observable<T>) =>
source.pipe(
retryWhen((e) =>
Expand All @@ -142,8 +148,9 @@ export function handleRetry(
: '';

logger.error(
`Unable to connect to the database${connectionInfo}.${verboseMessage} Retrying (${errorCount +
1})...`,
`Unable to connect to the database${connectionInfo}.${verboseMessage} Retrying (${
errorCount + 1
})...`,
error.stack,
);
if (errorCount + 1 >= retryAttempts) {
Expand All @@ -157,8 +164,14 @@ export function handleRetry(
);
}

export function getConnectionName(options: { name?: string }): string {
return options && options.name ? options.name : DEFAULT_CONNECTION_NAME;
export function getConnectionName(
optionsOrName?: { name?: string } | string,
): string {
return typeof optionsOrName === 'string'
? optionsOrName
: optionsOrName && optionsOrName.name
? optionsOrName.name
: DEFAULT_CONNECTION_NAME;
}

export const generateString = (): string => uuid();
3 changes: 2 additions & 1 deletion lib/helpers/get-custom-repository-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export function getCustomRepositoryEntity(
},
);
if (entityRepositoryMetadataArgs) {
const targetEntity = entityRepositoryMetadataArgs.entity as EntityClassOrSchema;
const targetEntity =
entityRepositoryMetadataArgs.entity as EntityClassOrSchema;
const isEntityRegisteredAlready = entities.indexOf(targetEntity) !== -1;

if (!isEntityRegisteredAlready) {
Expand Down
74 changes: 46 additions & 28 deletions lib/typeorm-core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,19 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
private readonly moduleRef: ModuleRef,
) {}

static forRoot(options: TypeOrmModuleOptions = {}): DynamicModule {
static forRoot(
optionsOrName: TypeOrmModuleOptions | string = {},
): DynamicModule {
const typeOrmModuleOptions = {
provide: TYPEORM_MODULE_OPTIONS,
useValue: options,
useValue: optionsOrName,
};
const connectionProvider = {
provide: getConnectionToken(options) as string,
useFactory: async () => await this.createConnectionFactory(options),
provide: getConnectionToken(optionsOrName) as string,
useFactory: async () => await this.createConnectionFactory(optionsOrName),
};
const entityManagerProvider = this.createEntityManagerProvider(
options,
);
const entityManagerProvider =
this.createEntityManagerProvider(optionsOrName);
return {
module: TypeOrmCoreModule,
providers: [
Expand Down Expand Up @@ -162,7 +163,7 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
}

private static createEntityManagerProvider(
options: { name?: string },
options: { name?: string } | string,
): Provider {
return {
provide: getEntityManagerToken(options) as string,
Expand All @@ -171,19 +172,39 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
};
}

private static async createConnectionFactory(
private static createConnectionFactory(
options: TypeOrmModuleOptions,
connectionFactory?: TypeOrmConnectionFactory,
): Promise<Connection>;
private static createConnectionFactory(
optionsOrName: TypeOrmModuleOptions | string,
): Promise<Connection>;
private static async createConnectionFactory(
optionsOrName: TypeOrmModuleOptions | string,
connectionFactory?: TypeOrmConnectionFactory,
): Promise<Connection> {
const connectionToken = getConnectionName(options);
const isName = (
optionsOrName: TypeOrmModuleOptions | string,
): optionsOrName is string => typeof optionsOrName === 'string';
const connectionToken = getConnectionName(optionsOrName);
const createTypeormConnection = connectionFactory ?? createConnection;
const retryOptions = {
connectionName: connectionToken,
...(!isName(optionsOrName)
? {
retryAttempts: optionsOrName.retryAttempts,
retryDelay: optionsOrName.retryDelay,
verboseRetryLog: optionsOrName.verboseRetryLog,
toRetry: optionsOrName.toRetry,
}
: {}),
};

return await lastValueFrom(
defer(() => {
try {
if (options.keepConnectionAlive) {
const connectionName = getConnectionName(
options,
);
if (!isName(optionsOrName) && optionsOrName.keepConnectionAlive) {
const connectionName = getConnectionName(optionsOrName);
const manager = getConnectionManager();
if (manager.has(connectionName)) {
const connection = manager.get(connectionName);
Expand All @@ -194,14 +215,19 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
}
} catch {}

if (!options.type) {
if (isName(optionsOrName)) {
return (createTypeormConnection as typeof createConnection)(
optionsOrName,
);
}
if (!optionsOrName.type) {
return createTypeormConnection();
}
if (!options.autoLoadEntities) {
return createTypeormConnection(options as ConnectionOptions);
if (!optionsOrName.autoLoadEntities) {
return createTypeormConnection(optionsOrName as ConnectionOptions);
}

let entities = options.entities;
let entities = optionsOrName.entities;
if (entities) {
entities = entities.concat(
EntitiesMetadataStorage.getEntitiesByConnection(connectionToken),
Expand All @@ -211,18 +237,10 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
EntitiesMetadataStorage.getEntitiesByConnection(connectionToken);
}
return createTypeormConnection({
...options,
...optionsOrName,
entities,
} as ConnectionOptions);
}).pipe(
handleRetry(
options.retryAttempts,
options.retryDelay,
connectionToken,
options.verboseRetryLog,
options.toRetry,
),
),
}).pipe(handleRetry(retryOptions)),
);
}
}
4 changes: 2 additions & 2 deletions lib/typeorm.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { createTypeOrmProviders } from './typeorm.providers';

@Module({})
export class TypeOrmModule {
static forRoot(options?: TypeOrmModuleOptions): DynamicModule {
static forRoot(optionsOrName?: TypeOrmModuleOptions | string): DynamicModule {
return {
module: TypeOrmModule,
imports: [TypeOrmCoreModule.forRoot(options)],
imports: [TypeOrmCoreModule.forRoot(optionsOrName)],
};
}

Expand Down
31 changes: 31 additions & 0 deletions ormconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"type": "postgres",
"host": "0.0.0.0",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": [
"tests/src/**/**.{entity,schema}.{ts,js}"
],
"synchronize": true,
"retryAttempts": 2,
"retryDelay": 1000
},
{
"name": "connection_2",
"type": "postgres",
"host": "0.0.0.0",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": [
"tests/src/**/**.entity.{ts,js}"
],
"synchronize": true,
"retryAttempts": 2,
"retryDelay": 1000
}
]
30 changes: 30 additions & 0 deletions tests/e2e/typeorm-multiple-named-databases.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import * as request from 'supertest';
import { Server } from 'http';
import { MultipleNamedDatabasesModule } from '../src/multiple-named-databases.module';

describe('TypeOrm (async configuration)', () => {
let server: Server;
let app: INestApplication;

beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [MultipleNamedDatabasesModule],
}).compile();

app = module.createNestApplication();
server = app.getHttpServer();
await app.init();
});

it(`should return created entity`, () => {
return request(server)
.post('/photo')
.expect(201, { name: 'Nest', description: 'Is great!', views: 6000 });
});

afterEach(async () => {
await app.close();
});
});
10 changes: 0 additions & 10 deletions tests/ormconfig.json

This file was deleted.

12 changes: 12 additions & 0 deletions tests/src/multiple-named-databases.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '../../lib';
import { PhotoSchemaModule } from './photo/schema/photo-schema.module';

@Module({
imports: [
TypeOrmModule.forRoot(),
TypeOrmModule.forRoot('connection_2'),
PhotoSchemaModule,
],
})
export class MultipleNamedDatabasesModule {}

0 comments on commit 3570590

Please sign in to comment.