Skip to content

Commit

Permalink
Merge branch 'IRCraziestTaxi-move-keepconnectionalive-into-retry'
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilmysliwiec committed Jul 8, 2021
2 parents 5da668f + eaefeb3 commit 39924fc
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 48 deletions.
7 changes: 6 additions & 1 deletion lib/interfaces/typeorm-options.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Type } from '@nestjs/common';
import { ModuleMetadata } from '@nestjs/common/interfaces';
import { ConnectionOptions } from 'typeorm';
import { Connection, ConnectionOptions } from 'typeorm';

export type TypeOrmModuleOptions = {
/**
Expand Down Expand Up @@ -41,6 +41,10 @@ export interface TypeOrmOptionsFactory {
): Promise<TypeOrmModuleOptions> | TypeOrmModuleOptions;
}

export type TypeOrmConnectionFactory = (
options?: ConnectionOptions,
) => Promise<Connection>;

export interface TypeOrmModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
name?: string;
Expand All @@ -49,5 +53,6 @@ export interface TypeOrmModuleAsyncOptions
useFactory?: (
...args: any[]
) => Promise<TypeOrmModuleOptions> | TypeOrmModuleOptions;
connectionFactory?: TypeOrmConnectionFactory;
inject?: any[];
}
107 changes: 60 additions & 47 deletions lib/typeorm-core.module.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
import {
DynamicModule,
Global,
Inject, Logger, Module,
Inject,
Logger,
Module,
OnApplicationShutdown,
Provider,
Type
Type,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { defer, lastValueFrom } from 'rxjs';
import { defer, lastValueFrom, of } from 'rxjs';
import {
Connection,
ConnectionOptions,
createConnection,
getConnectionManager
getConnectionManager,
} from 'typeorm';
import {
generateString,
getConnectionName,
getConnectionToken,
getEntityManagerToken,
handleRetry
handleRetry,
} from './common/typeorm.utils';
import { EntitiesMetadataStorage } from './entities-metadata.storage';
import {
TypeOrmConnectionFactory,
TypeOrmModuleAsyncOptions,
TypeOrmModuleOptions,
TypeOrmOptionsFactory
TypeOrmOptionsFactory,
} from './interfaces/typeorm-options.interface';
import { TYPEORM_MODULE_ID, TYPEORM_MODULE_OPTIONS } from './typeorm.constants';

Expand Down Expand Up @@ -68,12 +71,18 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {
provide: getConnectionToken(options as ConnectionOptions) as string,
useFactory: async (typeOrmOptions: TypeOrmModuleOptions) => {
if (options.name) {
return await this.createConnectionFactory({
...typeOrmOptions,
name: options.name,
});
return await this.createConnectionFactory(
{
...typeOrmOptions,
name: options.name,
},
options.connectionFactory,
);
}
return await this.createConnectionFactory(typeOrmOptions);
return await this.createConnectionFactory(
typeOrmOptions,
options.connectionFactory,
);
},
inject: [TYPEORM_MODULE_OPTIONS],
};
Expand Down Expand Up @@ -164,52 +173,56 @@ export class TypeOrmCoreModule implements OnApplicationShutdown {

private static async createConnectionFactory(
options: TypeOrmModuleOptions,
connectionFactory?: TypeOrmConnectionFactory,
): Promise<Connection> {
try {
if (options.keepConnectionAlive) {
const connectionName = getConnectionName(options as ConnectionOptions);
const manager = getConnectionManager();
if (manager.has(connectionName)) {
const connection = manager.get(connectionName);
if (connection.isConnected) {
return connection;
const connectionToken = getConnectionName(options as ConnectionOptions);
const createTypeormConnection = connectionFactory ?? createConnection;
return await lastValueFrom(
defer(() => {
try {
if (options.keepConnectionAlive) {
const connectionName = getConnectionName(
options as ConnectionOptions,
);
const manager = getConnectionManager();
if (manager.has(connectionName)) {
const connection = manager.get(connectionName);
if (connection.isConnected) {
return of(connection);
}
}
}
}
}
} catch {}
} catch {}

const connectionToken = getConnectionName(options as ConnectionOptions);
return lastValueFrom(defer(() => {
if (!options.type) {
return createConnection();
}
if (!options.autoLoadEntities) {
return createConnection(options as ConnectionOptions);
}
if (!options.type) {
return createTypeormConnection();
}
if (!options.autoLoadEntities) {
return createTypeormConnection(options as ConnectionOptions);
}

let entities = options.entities;
if (entities) {
entities = entities.concat(
EntitiesMetadataStorage.getEntitiesByConnection(connectionToken),
);
} else {
entities = EntitiesMetadataStorage.getEntitiesByConnection(
connectionToken,
);
}
return createConnection({
...options,
entities,
} as ConnectionOptions);
})
.pipe(
let entities = options.entities;
if (entities) {
entities = entities.concat(
EntitiesMetadataStorage.getEntitiesByConnection(connectionToken),
);
} else {
entities =
EntitiesMetadataStorage.getEntitiesByConnection(connectionToken);
}
return createTypeormConnection({
...options,
entities,
} as ConnectionOptions);
}).pipe(
handleRetry(
options.retryAttempts,
options.retryDelay,
connectionToken,
options.verboseRetryLog,
options.toRetry,
),
))
),
);
}
}
30 changes: 30 additions & 0 deletions tests/e2e/typeorm-async-connection-factory-options.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 { Server } from 'http';
import * as request from 'supertest';
import { AsyncConnectionFactoryOptionsFactoryModule } from '../src/async-connection-factory-options.module';

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

beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [AsyncConnectionFactoryOptionsFactoryModule],
}).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();
});
});
47 changes: 47 additions & 0 deletions tests/src/async-connection-factory-options.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Module } from '@nestjs/common';
import { createConnection } from 'typeorm';
import { TypeOrmModule } from '../../lib';
import { Photo } from './photo/photo.entity';
import { PhotoModule } from './photo/photo.module';

@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'postgres',
host: '0.0.0.0',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [Photo],
synchronize: true,
retryAttempts: 2,
retryDelay: 1000,
}),
connectionFactory: async (options) => {
// Realistically, this function would be used for more than simply creating a connection,
// i.e. checking for an existing and active connection prior to creating a new one.
// However, including that logic here causes runtime test errors about variables being used before assignment.
// Therefore, given the simple nature of this test case, simply create and return a connection.
const connection = await createConnection(options!);
return connection;
},
}),
TypeOrmModule.forRoot({
name: 'connection_2',
type: 'postgres',
host: '0.0.0.0',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [Photo],
synchronize: true,
retryAttempts: 2,
retryDelay: 1000,
}),
PhotoModule,
],
})
export class AsyncConnectionFactoryOptionsFactoryModule {}

0 comments on commit 39924fc

Please sign in to comment.