import { join , sep } from 'path' ; import { find } from 'fs-jetpack' ; import { Application } from 'egg' ; import { createConnection , getRepository , Connection , useContainer , } from 'typeorm' ; import { Container } from 'typedi' ; import { watch } from 'chokidar' ; import * as fs from 'fs-extra' ; import * as prettier from 'prettier' ; const hasTsLoader = typeof require . extensions [ '.ts' ] === 'function' ; export function formatCode ( text ) { return prettier . format ( text , { semi : false , tabWidth : 2 , singleQuote : true , parser : 'typescript' , trailingComma : 'all' , } ) ; } function handleConfig ( config , _env ) { if ( hasTsLoader ) { return config ; } const keys = [ 'entities' , 'migrations' , 'subscribers' ] ; for ( const key of keys ) { if ( config [ key ] ) { const newValue = config [ key ] . map ( ( item ) => item . replace ( /\.ts$/ , '.js' ) ) ; config [ key ] = newValue ; } } return config ; } async function connectDB ( app ) { const config = handleConfig ( app . config . typeorm , app . config . env ) ; useContainer ( Container ) ; const connection = await createConnection ( config ) ; app . context . connection = connection ; } function capitalizeFirstLetter ( str ) { return str . charAt ( 0 ) . toUpperCase ( ) + str . slice ( 1 ) ; } function getModelName ( file ) { const filename = file . split ( sep ) . pop ( ) || '' ; const name = capitalizeFirstLetter ( filename . replace ( /\.ts$|\.js$/g , '' ) ) ; return name ; } function writeTyping ( path , text ) { fs . writeFileSync ( path , formatCode ( text ) , { encoding : 'utf8' } ) ; } function getTypingText ( importText , repoText , entityText ) { const tpl = `
import 'egg'
import { Repository, Connection } from 'typeorm'
${ importText }

declare module 'egg' {
  interface Context {
    connection: Connection
    entity: {
      ${ entityText }
    }
    repo: {
      ${ repoText }
    }
  }
}
` ; return tpl ; } function formatPaths ( files ) { return files . map ( file => { const name = getModelName ( file ) ; file = file . split ( sep ) . join ( '/' ) ; const importPath = `../${ file }` . replace ( /\.ts$|\.js$/g , '' ) ; return { name , importPath , } ; } ) ; } function watchEntity ( app ) { const { baseDir } = app ; const entityDir = join ( baseDir , 'app' , 'entity' ) ; const typingsDir = join ( baseDir , 'typings' ) ; if ( ! fs . existsSync ( entityDir ) ) return ; fs . ensureDirSync ( typingsDir ) ; watch ( entityDir ) . on ( 'all' , ( eventType ) => { if ( [ 'add' , 'change' ] . includes ( eventType ) ) { createTyingFile ( app ) ; } if ( [ 'unlink' ] . includes ( eventType ) ) { createTyingFile ( app ) ; } } ) ; } function createTyingFile ( app ) { const { baseDir } = app ; const entityDir = join ( baseDir , 'app' , 'entity' ) ; const files = find ( entityDir , { matching : '*.ts' } ) ; const typingPath = join ( baseDir , 'typings' , 'typeorm.d.ts' ) ; const pathArr = formatPaths ( files ) ; const importText = pathArr . map ( i => `import ${ i . name } from '${ i . importPath }'` ) . join ( '\n' ) ; const repoText = pathArr . map ( i => `${ i . name }: Repository<${ i . name }>` ) . join ( '\n' ) ; const entityText = pathArr . map ( i => `${ i . name }: any` ) . join ( '\n' ) ; const text = getTypingText ( importText , repoText , entityText ) ; writeTyping ( typingPath , text ) ; } function getEntityFromPath ( app , entityPath ) { const connection = app . context . connection ; const fileModule = require ( entityPath ) ; const entities = Object . keys ( fileModule ) . reduce ( ( result , cur ) => { try { connection . getMetadata ( fileModule [ cur ] ) ; if ( ! result . includes ( fileModule [ cur ] ) ) { return [ ... result , fileModule [ cur ] ] ; } else { return result ; } } catch { } return result ; } , [ ] as any [ ] ) ; if ( ! entities . length ) { throw new Error ( `${ entityPath } 格式不正确，不存在 @entity` ) ; } return entities [ 0 ] ; } async function loadEntityAndModel ( app ) { const { baseDir } = app ; const entityDir = join ( baseDir , 'app' , 'entity' ) ; if ( ! fs . existsSync ( entityDir ) ) return ; const matching = hasTsLoader ? '*.ts' : '*.js' ; const files = find ( entityDir , { matching } ) ; app . context . repo = { } ; app . context . entity = { } ; try { for ( const file of files ) { const entityPath = join ( baseDir , file ) ; const entity = getEntityFromPath ( app , entityPath ) ; const name = getModelName ( file ) ; app . context . repo [ name ] = getRepository ( entity ) ; app . context . entity [ name ] = entity ; } } catch ( e ) { app . logger . error ( e ) ; } } export default async ( app ) => { const config = app . config . typeorm ; if ( ! config ) { throw new Error ( 'please config typeorm in config file' ) ; } app . beforeStart ( async ( ) => { try { await connectDB ( app ) ; watchEntity ( app ) ; await loadEntityAndModel ( app ) ; app . logger . info ( '[typeorm]' , '数据链接成功' ) ; } catch ( error ) { app . logger . error ( '[typeorm]' , '数据库链接失败' ) ; app . logger . error ( error ) ; } } ) ; } ;