From 9001b56d75b85b0e8efbadc7f80137d1aa14e76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Fri, 1 Apr 2022 15:01:34 +0200 Subject: [PATCH 1/5] feat(): support typeorm ^0.3.0 version --- lib/common/typeorm.decorators.ts | 24 ++-- lib/common/typeorm.utils.ts | 119 ++++++++++-------- lib/entities-metadata.storage.ts | 30 ++--- lib/helpers/get-custom-repository-entity.ts | 39 ------ lib/helpers/index.ts | 1 - lib/interfaces/typeorm-options.interface.ts | 16 +-- lib/typeorm-core.module.ts | 114 +++++++---------- lib/typeorm.constants.ts | 2 +- lib/typeorm.module.ts | 21 ++-- lib/typeorm.providers.ts | 32 ++--- package-lock.json | 106 ++++++++++------ package.json | 4 +- tests/ormconfig.json | 10 -- ...async-connection-factory-options.module.ts | 7 +- 14 files changed, 235 insertions(+), 290 deletions(-) delete mode 100644 lib/helpers/get-custom-repository-entity.ts delete mode 100644 lib/helpers/index.ts delete mode 100644 tests/ormconfig.json diff --git a/lib/common/typeorm.decorators.ts b/lib/common/typeorm.decorators.ts index 6650d8920..47176a3fc 100644 --- a/lib/common/typeorm.decorators.ts +++ b/lib/common/typeorm.decorators.ts @@ -1,26 +1,26 @@ import { Inject } from '@nestjs/common'; -import { Connection, ConnectionOptions } from 'typeorm'; +import { DataSource, DataSourceOptions } from 'typeorm'; import { EntityClassOrSchema } from '../interfaces/entity-class-or-schema.type'; -import { DEFAULT_CONNECTION_NAME } from '../typeorm.constants'; +import { DEFAULT_DATA_SOURCE_NAME } from '../typeorm.constants'; import { - getConnectionToken, + getDataSourceToken, getEntityManagerToken, getRepositoryToken, } from './typeorm.utils'; export const InjectRepository = ( entity: EntityClassOrSchema, - connection: string = DEFAULT_CONNECTION_NAME, -): ReturnType => Inject(getRepositoryToken(entity, connection)); + dataSource: string = DEFAULT_DATA_SOURCE_NAME, +): ReturnType => Inject(getRepositoryToken(entity, dataSource)); -export const InjectConnection: ( - connection?: Connection | ConnectionOptions | string, +export const InjectdataSource: ( + dataSource?: DataSource | DataSourceOptions | string, ) => ReturnType = ( - connection?: Connection | ConnectionOptions | string, -) => Inject(getConnectionToken(connection)); + dataSource?: DataSource | DataSourceOptions | string, +) => Inject(getDataSourceToken(dataSource)); export const InjectEntityManager: ( - connection?: Connection | ConnectionOptions | string, + dataSource?: DataSource | DataSourceOptions | string, ) => ReturnType = ( - connection?: Connection | ConnectionOptions | string, -) => Inject(getEntityManagerToken(connection)); + dataSource?: DataSource | DataSourceOptions | string, +) => Inject(getEntityManagerToken(dataSource)); diff --git a/lib/common/typeorm.utils.ts b/lib/common/typeorm.utils.ts index c9d4f3e68..d508a5f36 100644 --- a/lib/common/typeorm.utils.ts +++ b/lib/common/typeorm.utils.ts @@ -3,50 +3,53 @@ import { Observable } from 'rxjs'; import { delay, retryWhen, scan } from 'rxjs/operators'; import { AbstractRepository, - Connection, - ConnectionOptions, + DataSource, + DataSourceOptions, EntityManager, EntitySchema, - Repository + Repository, } from 'typeorm'; import { v4 as uuid } from 'uuid'; import { CircularDependencyException } from '../exceptions/circular-dependency.exception'; import { EntityClassOrSchema } from '../interfaces/entity-class-or-schema.type'; -import { DEFAULT_CONNECTION_NAME } from '../typeorm.constants'; +import { DEFAULT_DATA_SOURCE_NAME } from '../typeorm.constants'; const logger = new Logger('TypeOrmModule'); /** * This function generates an injection token for an Entity or Repository * @param {EntityClassOrSchema} entity parameter can either be an Entity or Repository - * @param {string} [connection='default'] Connection name + * @param {string} [dataSource='default'] DataSource name * @returns {string} The Entity | Repository injection token */ export function getRepositoryToken( entity: EntityClassOrSchema, - connection: Connection | ConnectionOptions | string = DEFAULT_CONNECTION_NAME, + dataSource: + | DataSource + | DataSourceOptions + | string = DEFAULT_DATA_SOURCE_NAME, ): Function | string { if (entity === null || entity === undefined) { throw new CircularDependencyException('@InjectRepository()'); } - const connectionPrefix = getConnectionPrefix(connection); + const dataSourcePrefix = getDataSourcePrefix(dataSource); if ( entity instanceof Function && (entity.prototype instanceof Repository || entity.prototype instanceof AbstractRepository) ) { - if (!connectionPrefix) { + if (!dataSourcePrefix) { return entity; } - return `${connectionPrefix}${getCustomRepositoryToken(entity)}`; + return `${dataSourcePrefix}${getCustomRepositoryToken(entity)}`; } if (entity instanceof EntitySchema) { - return `${connectionPrefix}${ + return `${dataSourcePrefix}${ entity.options.target ? entity.options.target.name : entity.options.name }Repository`; } - return `${connectionPrefix}${entity.name}Repository`; + return `${dataSourcePrefix}${entity.name}Repository`; } /** @@ -62,66 +65,75 @@ export function getCustomRepositoryToken(repository: Function): string { } /** - * This function returns a Connection injection token for the given Connection, ConnectionOptions or connection name. - * @param {Connection | ConnectionOptions | string} [connection='default'] This optional parameter is either - * a Connection, or a ConnectionOptions or a string. - * @returns {string | Function} The Connection injection token. + * This function returns a DataSource injection token for the given DataSource, DataSourceOptions or dataSource name. + * @param {DataSource | DataSourceOptions | string} [dataSource='default'] This optional parameter is either + * a DataSource, or a DataSourceOptions or a string. + * @returns {string | Function} The DataSource injection token. */ -export function getConnectionToken( - connection: Connection | ConnectionOptions | string = DEFAULT_CONNECTION_NAME, -): string | Function | Type { - return DEFAULT_CONNECTION_NAME === connection - ? Connection - : 'string' === typeof connection - ? `${connection}Connection` - : DEFAULT_CONNECTION_NAME === connection.name || !connection.name - ? Connection - : `${connection.name}Connection`; +export function getDataSourceToken( + dataSource: + | DataSource + | DataSourceOptions + | string = DEFAULT_DATA_SOURCE_NAME, +): string | Function | Type { + return DEFAULT_DATA_SOURCE_NAME === dataSource + ? DataSource + : 'string' === typeof dataSource + ? `${dataSource}DataSource` + : DEFAULT_DATA_SOURCE_NAME === dataSource.name || !dataSource.name + ? DataSource + : `${dataSource.name}DataSource`; } /** - * This function returns a Connection prefix based on the connection name - * @param {Connection | ConnectionOptions | string} [connection='default'] This optional parameter is either - * a Connection, or a ConnectionOptions or a string. - * @returns {string | Function} The Connection injection token. + * This function returns a DataSource prefix based on the dataSource name + * @param {DataSource | DataSourceOptions | string} [dataSource='default'] This optional parameter is either + * a DataSource, or a DataSourceOptions or a string. + * @returns {string | Function} The DataSource injection token. */ -export function getConnectionPrefix( - connection: Connection | ConnectionOptions | string = DEFAULT_CONNECTION_NAME, +export function getDataSourcePrefix( + dataSource: + | DataSource + | DataSourceOptions + | string = DEFAULT_DATA_SOURCE_NAME, ): string { - if (connection === DEFAULT_CONNECTION_NAME) { + if (dataSource === DEFAULT_DATA_SOURCE_NAME) { return ''; } - if (typeof connection === 'string') { - return connection + '_'; + if (typeof dataSource === 'string') { + return dataSource + '_'; } - if (connection.name === DEFAULT_CONNECTION_NAME || !connection.name) { + if (dataSource.name === DEFAULT_DATA_SOURCE_NAME || !dataSource.name) { return ''; } - return connection.name + '_'; + return dataSource.name + '_'; } /** - * This function returns an EntityManager injection token for the given Connection, ConnectionOptions or connection name. - * @param {Connection | ConnectionOptions | string} [connection='default'] This optional parameter is either - * a Connection, or a ConnectionOptions or a string. + * This function returns an EntityManager injection token for the given DataSource, DataSourceOptions or dataSource name. + * @param {DataSource | DataSourceOptions | string} [dataSource='default'] This optional parameter is either + * a DataSource, or a DataSourceOptions or a string. * @returns {string | Function} The EntityManager injection token. */ export function getEntityManagerToken( - connection: Connection | ConnectionOptions | string = DEFAULT_CONNECTION_NAME, + dataSource: + | DataSource + | DataSourceOptions + | string = DEFAULT_DATA_SOURCE_NAME, ): string | Function { - return DEFAULT_CONNECTION_NAME === connection + return DEFAULT_DATA_SOURCE_NAME === dataSource ? EntityManager - : 'string' === typeof connection - ? `${connection}EntityManager` - : DEFAULT_CONNECTION_NAME === connection.name || !connection.name + : 'string' === typeof dataSource + ? `${dataSource}EntityManager` + : DEFAULT_DATA_SOURCE_NAME === dataSource.name || !dataSource.name ? EntityManager - : `${connection.name}EntityManager`; + : `${dataSource.name}EntityManager`; } export function handleRetry( retryAttempts = 9, retryDelay = 3000, - connectionName = DEFAULT_CONNECTION_NAME, + dataSourceName = DEFAULT_DATA_SOURCE_NAME, verboseRetryLog = false, toRetry?: (err: any) => boolean, ): (source: Observable) => Observable { @@ -133,17 +145,18 @@ export function handleRetry( if (toRetry && !toRetry(error)) { throw error; } - const connectionInfo = - connectionName === DEFAULT_CONNECTION_NAME + const dataSourceInfo = + dataSourceName === DEFAULT_DATA_SOURCE_NAME ? '' - : ` (${connectionName})`; + : ` (${dataSourceName})`; const verboseMessage = verboseRetryLog ? ` Message: ${error.message}.` : ''; logger.error( - `Unable to connect to the database${connectionInfo}.${verboseMessage} Retrying (${errorCount + - 1})...`, + `Unable to connect to the database${dataSourceInfo}.${verboseMessage} Retrying (${ + errorCount + 1 + })...`, error.stack, ); if (errorCount + 1 >= retryAttempts) { @@ -157,8 +170,8 @@ export function handleRetry( ); } -export function getConnectionName(options: ConnectionOptions): string { - return options && options.name ? options.name : DEFAULT_CONNECTION_NAME; +export function getDataSourceName(options: DataSourceOptions): string { + return options && options.name ? options.name : DEFAULT_DATA_SOURCE_NAME; } export const generateString = (): string => uuid(); diff --git a/lib/entities-metadata.storage.ts b/lib/entities-metadata.storage.ts index 54bdbb5c7..135ff6da1 100644 --- a/lib/entities-metadata.storage.ts +++ b/lib/entities-metadata.storage.ts @@ -1,25 +1,25 @@ -import { Connection, ConnectionOptions } from 'typeorm'; +import { DataSource, DataSourceOptions } from 'typeorm'; import { EntityClassOrSchema } from './interfaces/entity-class-or-schema.type'; -type ConnectionToken = Connection | ConnectionOptions | string; +type DataSourceToken = DataSource | DataSourceOptions | string; export class EntitiesMetadataStorage { private static readonly storage = new Map(); - static addEntitiesByConnection( - connection: ConnectionToken, + static addEntitiesByDataSource( + dataSource: DataSourceToken, entities: EntityClassOrSchema[], ): void { - const connectionToken = - typeof connection === 'string' ? connection : connection.name; - if (!connectionToken) { + const dataSourceToken = + typeof dataSource === 'string' ? dataSource : dataSource.name; + if (!dataSourceToken) { return; } - let collection = this.storage.get(connectionToken); + let collection = this.storage.get(dataSourceToken); if (!collection) { collection = []; - this.storage.set(connectionToken, collection); + this.storage.set(dataSourceToken, collection); } entities.forEach((entity) => { if (collection!.includes(entity)) { @@ -29,15 +29,15 @@ export class EntitiesMetadataStorage { }); } - static getEntitiesByConnection( - connection: ConnectionToken, + static getEntitiesByDataSource( + dataSource: DataSourceToken, ): EntityClassOrSchema[] { - const connectionToken = - typeof connection === 'string' ? connection : connection.name; + const dataSourceToken = + typeof dataSource === 'string' ? dataSource : dataSource.name; - if (!connectionToken) { + if (!dataSourceToken) { return []; } - return this.storage.get(connectionToken) || []; + return this.storage.get(dataSourceToken) || []; } } diff --git a/lib/helpers/get-custom-repository-entity.ts b/lib/helpers/get-custom-repository-entity.ts deleted file mode 100644 index 974280bf4..000000000 --- a/lib/helpers/get-custom-repository-entity.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - AbstractRepository, - getMetadataArgsStorage, - Repository, -} from 'typeorm'; -import { EntityClassOrSchema } from '../interfaces/entity-class-or-schema.type'; - -export function getCustomRepositoryEntity( - entities: EntityClassOrSchema[], -): Array { - const customRepositoryEntities = new Array(); - const typeormEntityRepositories = getMetadataArgsStorage().entityRepositories; - - for (const entity of entities) { - const isCustomRepository = - entity instanceof Function && - (entity.prototype instanceof Repository || - entity.prototype instanceof AbstractRepository); - if (isCustomRepository) { - const entityRepositoryMetadataArgs = typeormEntityRepositories.find( - (repository) => { - return ( - repository.target === - (entity instanceof Function ? entity : (entity as any)?.constructor) - ); - }, - ); - if (entityRepositoryMetadataArgs) { - const targetEntity = entityRepositoryMetadataArgs.entity as EntityClassOrSchema; - const isEntityRegisteredAlready = entities.indexOf(targetEntity) !== -1; - - if (!isEntityRegisteredAlready) { - customRepositoryEntities.push(targetEntity); - } - } - } - } - return customRepositoryEntities; -} diff --git a/lib/helpers/index.ts b/lib/helpers/index.ts deleted file mode 100644 index 00dce66cf..000000000 --- a/lib/helpers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './get-custom-repository-entity'; diff --git a/lib/interfaces/typeorm-options.interface.ts b/lib/interfaces/typeorm-options.interface.ts index 1082239a5..8ee5eca27 100644 --- a/lib/interfaces/typeorm-options.interface.ts +++ b/lib/interfaces/typeorm-options.interface.ts @@ -1,5 +1,5 @@ import { ModuleMetadata, Type } from '@nestjs/common'; -import { Connection, ConnectionOptions } from 'typeorm'; +import { DataSource, DataSourceOptions } from 'typeorm'; export type TypeOrmModuleOptions = { /** @@ -24,15 +24,11 @@ export type TypeOrmModuleOptions = { * If `true`, entities will be loaded automatically. */ autoLoadEntities?: boolean; - /** - * If `true`, connection will not be closed on application shutdown. - */ - keepConnectionAlive?: boolean; /** * If `true`, will show verbose error messages on each connection retry. */ verboseRetryLog?: boolean; -} & Partial; +} & Partial; export interface TypeOrmOptionsFactory { createTypeOrmOptions( @@ -40,9 +36,9 @@ export interface TypeOrmOptionsFactory { ): Promise | TypeOrmModuleOptions; } -export type TypeOrmConnectionFactory = ( - options?: ConnectionOptions, -) => Promise; +export type TypeOrmDataSourceFactory = ( + options?: DataSourceOptions, +) => Promise; export interface TypeOrmModuleAsyncOptions extends Pick { @@ -52,6 +48,6 @@ export interface TypeOrmModuleAsyncOptions useFactory?: ( ...args: any[] ) => Promise | TypeOrmModuleOptions; - connectionFactory?: TypeOrmConnectionFactory; + dataSourceFactory?: TypeOrmDataSourceFactory; inject?: any[]; } diff --git a/lib/typeorm-core.module.ts b/lib/typeorm-core.module.ts index 3f512a645..dcd1b48a2 100644 --- a/lib/typeorm-core.module.ts +++ b/lib/typeorm-core.module.ts @@ -9,23 +9,18 @@ import { Type, } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; -import { defer, lastValueFrom, of } from 'rxjs'; -import { - Connection, - ConnectionOptions, - createConnection, - getConnectionManager, -} from 'typeorm'; +import { defer, lastValueFrom } from 'rxjs'; +import { DataSource, DataSourceOptions } from 'typeorm'; import { generateString, - getConnectionName, - getConnectionToken, + getDataSourceName, + getDataSourceToken, getEntityManagerToken, handleRetry, } from './common/typeorm.utils'; import { EntitiesMetadataStorage } from './entities-metadata.storage'; import { - TypeOrmConnectionFactory, + TypeOrmDataSourceFactory, TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, TypeOrmOptionsFactory, @@ -48,48 +43,48 @@ export class TypeOrmCoreModule implements OnApplicationShutdown { provide: TYPEORM_MODULE_OPTIONS, useValue: options, }; - const connectionProvider = { - provide: getConnectionToken(options as ConnectionOptions) as string, - useFactory: async () => await this.createConnectionFactory(options), + const dataSourceProvider = { + provide: getDataSourceToken(options as DataSourceOptions) as string, + useFactory: async () => await this.createDataSourceFactory(options), }; const entityManagerProvider = this.createEntityManagerProvider( - options as ConnectionOptions, + options as DataSourceOptions, ); return { module: TypeOrmCoreModule, providers: [ entityManagerProvider, - connectionProvider, + dataSourceProvider, typeOrmModuleOptions, ], - exports: [entityManagerProvider, connectionProvider], + exports: [entityManagerProvider, dataSourceProvider], }; } static forRootAsync(options: TypeOrmModuleAsyncOptions): DynamicModule { - const connectionProvider = { - provide: getConnectionToken(options as ConnectionOptions) as string, + const dataSourceProvider = { + provide: getDataSourceToken(options as DataSourceOptions) as string, useFactory: async (typeOrmOptions: TypeOrmModuleOptions) => { if (options.name) { - return await this.createConnectionFactory( + return await this.createDataSourceFactory( { ...typeOrmOptions, name: options.name, }, - options.connectionFactory, + options.dataSourceFactory, ); } - return await this.createConnectionFactory( + return await this.createDataSourceFactory( typeOrmOptions, - options.connectionFactory, + options.dataSourceFactory, ); }, inject: [TYPEORM_MODULE_OPTIONS], }; const entityManagerProvider = { - provide: getEntityManagerToken(options as ConnectionOptions) as string, - useFactory: (connection: Connection) => connection.manager, - inject: [getConnectionToken(options as ConnectionOptions)], + provide: getEntityManagerToken(options as DataSourceOptions) as string, + useFactory: (dataSource: DataSource) => dataSource.manager, + inject: [getDataSourceToken(options as DataSourceOptions)], }; const asyncProviders = this.createAsyncProviders(options); @@ -99,25 +94,22 @@ export class TypeOrmCoreModule implements OnApplicationShutdown { providers: [ ...asyncProviders, entityManagerProvider, - connectionProvider, + dataSourceProvider, { provide: TYPEORM_MODULE_ID, useValue: generateString(), }, ], - exports: [entityManagerProvider, connectionProvider], + exports: [entityManagerProvider, dataSourceProvider], }; } async onApplicationShutdown(): Promise { - if (this.options.keepConnectionAlive) { - return; - } - const connection = this.moduleRef.get( - getConnectionToken(this.options as ConnectionOptions) as Type, + const dataSource = this.moduleRef.get( + getDataSourceToken(this.options as DataSourceOptions) as Type, ); try { - connection && (await connection.close()); + dataSource && (await dataSource.destroy()); } catch (e) { this.logger.error(e?.message); } @@ -162,63 +154,51 @@ export class TypeOrmCoreModule implements OnApplicationShutdown { } private static createEntityManagerProvider( - options: ConnectionOptions, + options: DataSourceOptions, ): Provider { return { provide: getEntityManagerToken(options) as string, - useFactory: (connection: Connection) => connection.manager, - inject: [getConnectionToken(options)], + useFactory: (dataSource: DataSource) => dataSource.manager, + inject: [getDataSourceToken(options)], }; } - private static async createConnectionFactory( + private static async createDataSourceFactory( options: TypeOrmModuleOptions, - connectionFactory?: TypeOrmConnectionFactory, - ): Promise { - const connectionToken = getConnectionName(options as ConnectionOptions); - const createTypeormConnection = connectionFactory ?? createConnection; + dataSourceFactory?: TypeOrmDataSourceFactory, + ): Promise { + const dataSourceToken = getDataSourceName(options as DataSourceOptions); + const createTypeormDataSource = + dataSourceFactory ?? + ((options: DataSourceOptions) => new DataSource(options)); 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 {} - - if (!options.type) { - return createTypeormConnection(); - } + defer(async () => { if (!options.autoLoadEntities) { - return createTypeormConnection(options as ConnectionOptions); + const dataSource = await createTypeormDataSource( + options as DataSourceOptions, + ); + return dataSource.initialize(); } let entities = options.entities; - if (entities) { + if (Array.isArray(entities)) { entities = entities.concat( - EntitiesMetadataStorage.getEntitiesByConnection(connectionToken), + EntitiesMetadataStorage.getEntitiesByDataSource(dataSourceToken), ); } else { entities = - EntitiesMetadataStorage.getEntitiesByConnection(connectionToken); + EntitiesMetadataStorage.getEntitiesByDataSource(dataSourceToken); } - return createTypeormConnection({ + const dataSource = await createTypeormDataSource({ ...options, entities, - } as ConnectionOptions); + } as DataSourceOptions); + return dataSource.initialize(); }).pipe( handleRetry( options.retryAttempts, options.retryDelay, - connectionToken, + dataSourceToken, options.verboseRetryLog, options.toRetry, ), diff --git a/lib/typeorm.constants.ts b/lib/typeorm.constants.ts index 030fca9c9..e27d9844a 100644 --- a/lib/typeorm.constants.ts +++ b/lib/typeorm.constants.ts @@ -1,3 +1,3 @@ export const TYPEORM_MODULE_OPTIONS = 'TypeOrmModuleOptions'; export const TYPEORM_MODULE_ID = 'TypeOrmModuleId'; -export const DEFAULT_CONNECTION_NAME = 'default'; +export const DEFAULT_DATA_SOURCE_NAME = 'default'; diff --git a/lib/typeorm.module.ts b/lib/typeorm.module.ts index cf5024ceb..48b0f5a51 100644 --- a/lib/typeorm.module.ts +++ b/lib/typeorm.module.ts @@ -1,14 +1,13 @@ import { DynamicModule, Module } from '@nestjs/common'; -import { Connection, ConnectionOptions } from 'typeorm'; +import { DataSource, DataSourceOptions } from 'typeorm'; import { EntitiesMetadataStorage } from './entities-metadata.storage'; -import { getCustomRepositoryEntity } from './helpers/get-custom-repository-entity'; import { EntityClassOrSchema } from './interfaces/entity-class-or-schema.type'; import { TypeOrmModuleAsyncOptions, TypeOrmModuleOptions, } from './interfaces/typeorm-options.interface'; import { TypeOrmCoreModule } from './typeorm-core.module'; -import { DEFAULT_CONNECTION_NAME } from './typeorm.constants'; +import { DEFAULT_DATA_SOURCE_NAME } from './typeorm.constants'; import { createTypeOrmProviders } from './typeorm.providers'; @Module({}) @@ -22,17 +21,13 @@ export class TypeOrmModule { static forFeature( entities: EntityClassOrSchema[] = [], - connection: - | Connection - | ConnectionOptions - | string = DEFAULT_CONNECTION_NAME, + dataSource: + | DataSource + | DataSourceOptions + | string = DEFAULT_DATA_SOURCE_NAME, ): DynamicModule { - const providers = createTypeOrmProviders(entities, connection); - const customRepositoryEntities = getCustomRepositoryEntity(entities); - EntitiesMetadataStorage.addEntitiesByConnection(connection, [ - ...entities, - ...customRepositoryEntities, - ]); + const providers = createTypeOrmProviders(entities, dataSource); + EntitiesMetadataStorage.addEntitiesByDataSource(dataSource, [...entities]); return { module: TypeOrmModule, providers: providers, diff --git a/lib/typeorm.providers.ts b/lib/typeorm.providers.ts index 5b5ce0db7..f38ab2987 100644 --- a/lib/typeorm.providers.ts +++ b/lib/typeorm.providers.ts @@ -1,34 +1,20 @@ import { Provider } from '@nestjs/common'; -import { - AbstractRepository, - Connection, - ConnectionOptions, - getMetadataArgsStorage, - Repository, -} from 'typeorm'; -import { getConnectionToken, getRepositoryToken } from './common/typeorm.utils'; +import { DataSource, DataSourceOptions, getMetadataArgsStorage } from 'typeorm'; +import { getDataSourceToken, getRepositoryToken } from './common/typeorm.utils'; import { EntityClassOrSchema } from './interfaces/entity-class-or-schema.type'; export function createTypeOrmProviders( entities?: EntityClassOrSchema[], - connection?: Connection | ConnectionOptions | string, + dataSource?: DataSource | DataSourceOptions | string, ): Provider[] { return (entities || []).map((entity) => ({ - provide: getRepositoryToken(entity, connection), - useFactory: (connection: Connection) => { - if ( - entity instanceof Function && - (entity.prototype instanceof Repository || - entity.prototype instanceof AbstractRepository) - ) { - return connection.getCustomRepository(entity); - } - - return connection.options.type === 'mongodb' - ? connection.getMongoRepository(entity) - : connection.getRepository(entity); + provide: getRepositoryToken(entity, dataSource), + useFactory: (dataSource: DataSource) => { + return dataSource.options.type === 'mongodb' + ? dataSource.getMongoRepository(entity) + : dataSource.getRepository(entity); }, - inject: [getConnectionToken(connection)], + inject: [getDataSourceToken(dataSource)], /** * Extra property to workaround dynamic modules serialisation issue * that occurs when "TypeOrm#forFeature()" method is called with the same number diff --git a/package-lock.json b/package-lock.json index fbdecd9f7..3dde9b92c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2046,12 +2046,6 @@ "integrity": "sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw==", "dev": true }, - "@types/zen-observable": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", - "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==", - "dev": true - }, "@typescript-eslint/eslint-plugin": { "version": "5.9.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.1.tgz", @@ -3441,6 +3435,12 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==", + "dev": true + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -3684,9 +3684,9 @@ } }, "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", "dev": true }, "duplexer3": { @@ -10015,9 +10015,9 @@ } }, "typeorm": { - "version": "0.2.41", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.41.tgz", - "integrity": "sha512-/d8CLJJxKPgsnrZWiMyPI0rz2MFZnBQrnQ5XP3Vu3mswv2WPexb58QM6BEtmRmlTMYN5KFWUz8SKluze+wS9xw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.4.tgz", + "integrity": "sha512-6v3HH12viDhIQwQDod/B0Plt1o7IYIVDxP7zwatD6fzN+IDdqTTinW/sWNw84Edpbhh2t7XILTaQEqj0NXFP/Q==", "dev": true, "requires": { "@sqltools/formatter": "^1.2.2", @@ -10025,17 +10025,18 @@ "buffer": "^6.0.3", "chalk": "^4.1.0", "cli-highlight": "^2.1.11", - "debug": "^4.3.1", - "dotenv": "^8.2.0", - "glob": "^7.1.6", - "js-yaml": "^4.0.0", + "date-fns": "^2.28.0", + "debug": "^4.3.3", + "dotenv": "^16.0.0", + "glob": "^7.2.0", + "js-yaml": "^4.1.0", "mkdirp": "^1.0.4", "reflect-metadata": "^0.1.13", "sha.js": "^2.4.11", - "tslib": "^2.1.0", + "tslib": "^2.3.1", + "uuid": "^8.3.2", "xml2js": "^0.4.23", - "yargs": "^17.0.1", - "zen-observable-ts": "^1.0.0" + "yargs": "^17.3.1" }, "dependencies": { "ansi-styles": { @@ -10082,6 +10083,15 @@ "color-name": "~1.1.4" } }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -10117,6 +10127,32 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10133,19 +10169,25 @@ "dev": true }, "yargs": { - "version": "17.2.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz", - "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", + "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.0.0" } + }, + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true } } }, @@ -10690,22 +10732,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true - }, - "zen-observable": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==", - "dev": true - }, - "zen-observable-ts": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", - "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", - "dev": true, - "requires": { - "@types/zen-observable": "0.8.3", - "zen-observable": "0.8.15" - } } } } diff --git a/package.json b/package.json index 86f8e2134..735bd7d8e 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "rxjs": "7.5.2", "supertest": "6.2.1", "ts-jest": "27.1.3", - "typeorm": "0.2.41", + "typeorm": "0.3.4", "typescript": "4.5.4" }, "dependencies": { @@ -56,7 +56,7 @@ "@nestjs/core": "^8.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", - "typeorm": "^0.2.34" + "typeorm": "^0.3.0" }, "lint-staged": { "*.ts": [ diff --git a/tests/ormconfig.json b/tests/ormconfig.json deleted file mode 100644 index 378b0e0be..000000000 --- a/tests/ormconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "type": "mysql", - "host": "0.0.0.0", - "port": 3306, - "username": "root", - "password": "root", - "database": "test", - "entities": ["src/**/**.entity{.ts,.js}"], - "synchronize": true -} diff --git a/tests/src/async-connection-factory-options.module.ts b/tests/src/async-connection-factory-options.module.ts index 09eaa5064..c8cb1c44d 100644 --- a/tests/src/async-connection-factory-options.module.ts +++ b/tests/src/async-connection-factory-options.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { createConnection } from 'typeorm'; +import { DataSource } from 'typeorm'; import { TypeOrmModule } from '../../lib'; import { Photo } from './photo/photo.entity'; import { PhotoModule } from './photo/photo.module'; @@ -19,13 +19,12 @@ import { PhotoModule } from './photo/photo.module'; retryAttempts: 2, retryDelay: 1000, }), - connectionFactory: async (options) => { + dataSourceFactory: 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; + return new DataSource(options!); }, }), TypeOrmModule.forRoot({ From 9ce3a034f5c4b49ab3a56035f9f616fda835a309 Mon Sep 17 00:00:00 2001 From: Kamil Mysliwiec Date: Sat, 2 Apr 2022 18:23:14 +0200 Subject: [PATCH 2/5] Update lib/common/typeorm.decorators.ts --- lib/common/typeorm.decorators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/typeorm.decorators.ts b/lib/common/typeorm.decorators.ts index 47176a3fc..9734dd0eb 100644 --- a/lib/common/typeorm.decorators.ts +++ b/lib/common/typeorm.decorators.ts @@ -13,7 +13,7 @@ export const InjectRepository = ( dataSource: string = DEFAULT_DATA_SOURCE_NAME, ): ReturnType => Inject(getRepositoryToken(entity, dataSource)); -export const InjectdataSource: ( +export const InjectDataSource: ( dataSource?: DataSource | DataSourceOptions | string, ) => ReturnType = ( dataSource?: DataSource | DataSourceOptions | string, From 2f91da4d93a1b468e1c53290b78c0dc0c13a9783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Thu, 7 Apr 2022 11:46:57 +0200 Subject: [PATCH 3/5] chore(): add inject connection alias and mark it as deprecated --- lib/common/typeorm.decorators.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/common/typeorm.decorators.ts b/lib/common/typeorm.decorators.ts index 9734dd0eb..87523e64a 100644 --- a/lib/common/typeorm.decorators.ts +++ b/lib/common/typeorm.decorators.ts @@ -19,6 +19,9 @@ export const InjectDataSource: ( dataSource?: DataSource | DataSourceOptions | string, ) => Inject(getDataSourceToken(dataSource)); +/** @deprecated */ +export const InjectConnection = InjectDataSource; + export const InjectEntityManager: ( dataSource?: DataSource | DataSourceOptions | string, ) => ReturnType = ( From 3d3a3f1dc0fa1d058b4edbf10eb2dbbefb30c393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20My=C5=9Bliwiec?= Date: Fri, 8 Apr 2022 11:08:48 +0200 Subject: [PATCH 4/5] feat(): add get connection token alias for backward comp --- lib/common/typeorm.utils.ts | 3 ++ lib/typeorm-core.module.ts | 66 ++++++++++++++++++++++++++----------- package.json | 2 +- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/lib/common/typeorm.utils.ts b/lib/common/typeorm.utils.ts index d508a5f36..4bb2c7b22 100644 --- a/lib/common/typeorm.utils.ts +++ b/lib/common/typeorm.utils.ts @@ -85,6 +85,9 @@ export function getDataSourceToken( : `${dataSource.name}DataSource`; } +/** @deprecated */ +export const getConnectionToken = getDataSourceToken; + /** * This function returns a DataSource prefix based on the dataSource name * @param {DataSource | DataSourceOptions | string} [dataSource='default'] This optional parameter is either diff --git a/lib/typeorm-core.module.ts b/lib/typeorm-core.module.ts index dcd1b48a2..ca39b7c06 100644 --- a/lib/typeorm-core.module.ts +++ b/lib/typeorm-core.module.ts @@ -10,7 +10,7 @@ import { } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { defer, lastValueFrom } from 'rxjs'; -import { DataSource, DataSourceOptions } from 'typeorm'; +import { Connection, DataSource, DataSourceOptions } from 'typeorm'; import { generateString, getDataSourceName, @@ -44,26 +44,39 @@ export class TypeOrmCoreModule implements OnApplicationShutdown { useValue: options, }; const dataSourceProvider = { - provide: getDataSourceToken(options as DataSourceOptions) as string, + provide: getDataSourceToken(options as DataSourceOptions), useFactory: async () => await this.createDataSourceFactory(options), }; const entityManagerProvider = this.createEntityManagerProvider( options as DataSourceOptions, ); + + const providers = [ + entityManagerProvider, + dataSourceProvider, + typeOrmModuleOptions, + ]; + const exports = [entityManagerProvider, dataSourceProvider]; + + // TODO: "Connection" class is going to be removed in the next version of "typeorm" + if (dataSourceProvider.provide === DataSource) { + providers.push({ + provide: Connection, + useExisting: DataSource, + }); + exports.push(Connection); + } + return { module: TypeOrmCoreModule, - providers: [ - entityManagerProvider, - dataSourceProvider, - typeOrmModuleOptions, - ], - exports: [entityManagerProvider, dataSourceProvider], + providers, + exports, }; } static forRootAsync(options: TypeOrmModuleAsyncOptions): DynamicModule { const dataSourceProvider = { - provide: getDataSourceToken(options as DataSourceOptions) as string, + provide: getDataSourceToken(options as DataSourceOptions), useFactory: async (typeOrmOptions: TypeOrmModuleOptions) => { if (options.name) { return await this.createDataSourceFactory( @@ -88,19 +101,34 @@ export class TypeOrmCoreModule implements OnApplicationShutdown { }; const asyncProviders = this.createAsyncProviders(options); + const providers = [ + ...asyncProviders, + entityManagerProvider, + dataSourceProvider, + { + provide: TYPEORM_MODULE_ID, + useValue: generateString(), + }, + ]; + const exports: Array = [ + entityManagerProvider, + dataSourceProvider, + ]; + + // TODO: "Connection" class is going to be removed in the next version of "typeorm" + if (dataSourceProvider.provide === DataSource) { + providers.push({ + provide: Connection, + useExisting: DataSource, + }); + exports.push(Connection); + } + return { module: TypeOrmCoreModule, imports: options.imports, - providers: [ - ...asyncProviders, - entityManagerProvider, - dataSourceProvider, - { - provide: TYPEORM_MODULE_ID, - useValue: generateString(), - }, - ], - exports: [entityManagerProvider, dataSourceProvider], + providers, + exports, }; } diff --git a/package.json b/package.json index e013d5391..e8f258926 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nestjs/typeorm", - "version": "8.0.3", + "version": "9.0.0-next.2", "description": "Nest - modern, fast, powerful node.js web framework (@typeorm)", "author": "Kamil Mysliwiec", "license": "MIT", From a5e84c154f5f51fc94d0569f73aa0c67b6a9d515 Mon Sep 17 00:00:00 2001 From: Thomas Conner Date: Wed, 11 May 2022 12:25:23 -0400 Subject: [PATCH 5/5] fix: check if the dataSource is initialized before destroying it --- lib/typeorm-core.module.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/typeorm-core.module.ts b/lib/typeorm-core.module.ts index ca39b7c06..009083efd 100644 --- a/lib/typeorm-core.module.ts +++ b/lib/typeorm-core.module.ts @@ -137,7 +137,9 @@ export class TypeOrmCoreModule implements OnApplicationShutdown { getDataSourceToken(this.options as DataSourceOptions) as Type, ); try { - dataSource && (await dataSource.destroy()); + if (dataSource && dataSource.isInitialized) { + await dataSource.destroy(); + } } catch (e) { this.logger.error(e?.message); }