import { Connection , ConnectionOptions , createConnection } from 'typeorm' ; import { types as PostgresTypes } from 'pg' ; import { LoggerOptions } from 'typeorm/logger/LoggerOptions' ; import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions' ; import { Nullable , UUID } from '@mds-core/mds-types' ; import { uuid } from '@mds-core/mds-utils' ; import logger from '@mds-core/mds-logger' ; import AwaitLock from 'await-lock' ; import { MdsNamingStrategy } from './naming-strategies' ; import { RepositoryError } from './exceptions' ; const loggingOption = ( options ) : LoggerOptions => { return [ 'false' , 'true' , 'all' ] . includes ( options ) ? options !== 'false' : ( options . split ( ' ' ) as LoggerOptions ) ; } ; const ConnectionModes = [ 'ro' , 'rw' ] as const ; export type ConnectionMode = typeof ConnectionModes [ number ] ; PostgresTypes . setTypeParser ( 20 , Number ) ; export type ConnectionManagerOptions = Partial < Omit < PostgresConnectionOptions , 'cli' > > ; export type ConnectionManagerCliOptions = Partial < PostgresConnectionOptions [ 'cli' ] > ; export class ConnectionManager < TConnectionMode extends ConnectionMode > { private readonly connections : Map < TConnectionMode , Nullable < Connection > > = new Map ( ) ; private readonly lock = new AwaitLock ( ) ; private readonly instance : UUID = uuid ( ) ; private connectionName = ( mode ) : string => { const { prefix , instance } = this ; return `${ prefix }-${ mode }-${ instance }` ; } ; private connectionMode = ( mode ) : string => ( mode === 'ro' ? 'R/O' : 'R/W' ) ; private connectionOptions = ( mode ) : ConnectionOptions => { const { connectionName , options } = this ; const { PG_HOST , PG_HOST_READER , PG_PORT , PG_USER , PG_PASS , PG_NAME , PG_DEBUG = 'false' } = process . env ; return { name : connectionName ( mode ) , type : 'postgres' , host : ( mode === 'rw' ? PG_HOST : PG_HOST_READER ) || PG_HOST || 'localhost' , port : Number ( PG_PORT ) || 5432 , username : PG_USER , password : PG_PASS , database : PG_NAME , logging : loggingOption ( PG_DEBUG . toLowerCase ( ) ) , maxQueryExecutionTime : 3000 , logger : 'simple-console' , synchronize : false , migrationsRun : false , namingStrategy : new MdsNamingStrategy ( ) , ... options } ; } ; public connect = async ( mode ) : Promise < Connection > => { const { lock , connections , connectionOptions , connectionMode , connectionName } = this ; await lock . acquireAsync ( ) ; try { if ( ! connections . has ( mode ) ) { const options = connectionOptions ( mode ) ; logger . info ( `Initializing ${ connectionMode ( mode ) } connection: ${ options . name }` ) ; connections . set ( mode , await createConnection ( options ) ) ; } } finally { lock . release ( ) ; } const connection = connections . get ( mode ) ; if ( ! connection ) { throw RepositoryError ( Error ( `Connection ${ connectionName ( mode ) } not found` ) ) ; } if ( ! connection . isConnected ) { try { await connection . connect ( ) ; } catch ( error ) { throw RepositoryError ( error ) ; } } return connection ; } ; public disconnect = async ( mode ) => { const { lock , connections , connectionMode } = this ; try { await lock . acquireAsync ( ) ; const connection = connections . get ( mode ) ; if ( connection ) { if ( connection . isConnected ) { logger . info ( `Terminating ${ connectionMode ( mode ) } connection: ${ connection . options . name }` ) ; await connection . close ( ) ; } connections . delete ( mode ) ; } } finally { lock . release ( ) ; } } ; public cli = ( mode , options = { } ) => { const { connectionOptions } = this ; const { name , ... ormconfig } = connectionOptions ( mode ) ; return { ... ormconfig , cli : options } ; } ; constructor ( private prefix , private options = { } ) { } }