From 88ba3f5a168f4ef8233b954fe1908a274637d6c7 Mon Sep 17 00:00:00 2001 From: liuyi Date: Fri, 6 Jun 2025 13:39:02 +0800 Subject: [PATCH] add config module --- src/modules/core/core.module.ts | 5 +- src/modules/core/helpers/app.ts | 92 +++++++++++++++++++++++++++++++ src/modules/core/helpers/utils.ts | 16 ++++++ src/modules/core/types.ts | 37 +++++++++++++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/modules/core/helpers/app.ts create mode 100644 src/modules/core/types.ts diff --git a/src/modules/core/core.module.ts b/src/modules/core/core.module.ts index 0caf892..b50b837 100644 --- a/src/modules/core/core.module.ts +++ b/src/modules/core/core.module.ts @@ -1,8 +1,11 @@ import { DynamicModule, Module } from '@nestjs/common'; +import { Configure } from '../config/configure'; + @Module({}) export class CoreModule { - static forRoot(): DynamicModule { + static async forRoot(configure: Configure): Promise { + await configure.store('app.name'); return { module: CoreModule, global: true, diff --git a/src/modules/core/helpers/app.ts b/src/modules/core/helpers/app.ts new file mode 100644 index 0000000..971cf47 --- /dev/null +++ b/src/modules/core/helpers/app.ts @@ -0,0 +1,92 @@ +import { BadGatewayException, Global, Module, ModuleMetadata, Type } from '@nestjs/common'; + +import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; +import { useContainer } from 'class-validator'; + +import { omit } from 'lodash'; + +import { ConfigModule } from '@/modules/config/config.module'; +import { Configure } from '@/modules/config/configure'; + +import { DEFAULT_VALIDATION_CONFIG } from '@/modules/content/constants'; + +import { CoreModule } from '../core.module'; +import { AppFilter } from '../providers/app.filter'; +import { AppInterceptor } from '../providers/app.interceptor'; +import { AppPipe } from '../providers/app.pipe'; +import { App, CreateOptions } from '../types'; + +import { CreateModule } from './utils'; + +export const app: App = { configure: new Configure() }; + +export const createApp = (options: CreateOptions) => async (): Promise => { + const { config, builder } = options; + + await app.configure.initialize(config.factories, config.storage); + + if (!app.configure.has('app')) { + throw new BadGatewayException('App config not exists'); + } + + const BootModule = await createBootModule(app.configure, options); + + app.container = await builder({ configure: app.configure, BootModule }); + + if (app.configure.has('app.prefix')) { + app.container.setGlobalPrefix(await app.configure.get('app.prefix')); + } + useContainer(app.container.select(BootModule), { fallbackOnErrors: true }); + return app; +}; + +export async function createBootModule( + configure: Configure, + options: Pick, +): Promise> { + const { globals = {}, providers = [] } = options; + const modules = await options.modules(configure); + const imports: ModuleMetadata['imports'] = ( + await Promise.all([ + ...modules, + ConfigModule.forRoot(configure), + await CoreModule.forRoot(configure), + ]) + ).map((item) => { + if ('module' in item) { + const meta = omit(item, ['module', 'global']); + Module(meta)(item.module); + if (item.global) { + Global()(item.module); + } + return item.module; + } + return item; + }); + + if (globals.pipe !== null) { + const pipe = globals.pipe + ? globals.pipe(configure) + : new AppPipe(DEFAULT_VALIDATION_CONFIG); + providers.push({ provide: APP_PIPE, useValue: pipe }); + } + + if (globals.intercepter !== null) { + providers.push({ + provide: APP_INTERCEPTOR, + useClass: globals.intercepter ?? AppInterceptor, + }); + } + + if (globals.filter !== null) { + providers.push({ + provide: APP_FILTER, + useClass: AppFilter, + }); + } + + return CreateModule('BootModule', () => ({ + imports, + providers, + })); +} diff --git a/src/modules/core/helpers/utils.ts b/src/modules/core/helpers/utils.ts index f4c3633..83fcc67 100644 --- a/src/modules/core/helpers/utils.ts +++ b/src/modules/core/helpers/utils.ts @@ -1,3 +1,4 @@ +import { Module, ModuleMetadata, Type } from '@nestjs/common'; import deepmerge from 'deepmerge'; import { isNil } from 'lodash'; @@ -39,3 +40,18 @@ export function isAsyncFunction>( const AsyncFunction = (async () => {}).constructor; return callback instanceof AsyncFunction === true; } + +export function CreateModule( + target: string | Type, + metaSetter: () => ModuleMetadata = () => ({}), +): Type { + let ModuleClass: Type; + if (typeof target === 'string') { + ModuleClass = class {}; + Object.defineProperty(ModuleClass, 'name', { value: target }); + } else { + ModuleClass = target; + } + Module(metaSetter())(ModuleClass); + return ModuleClass; +} diff --git a/src/modules/core/types.ts b/src/modules/core/types.ts new file mode 100644 index 0000000..d9240cb --- /dev/null +++ b/src/modules/core/types.ts @@ -0,0 +1,37 @@ +import { ModuleMetadata, PipeTransform, Type } from '@nestjs/common'; +import { NestFastifyApplication } from '@nestjs/platform-fastify'; + +import { Configure } from '../config/configure'; +import { ConfigStorageOption, ConfigureFactory } from '../config/types'; + +export type App = { + container?: NestFastifyApplication; + + configure: Configure; +}; + +export interface CreateOptions { + modules: (configure: Configure) => Promise>; + + builder: ContainerBuilder; + + globals?: { + pipe?: (configure: Configure) => PipeTransform | null; + + intercepter?: Type | null; + + filter?: Type | null; + }; + + providers?: ModuleMetadata['providers']; + + config: { + factories: Record>; + + storage: ConfigStorageOption; + }; +} + +export interface ContainerBuilder { + (params: { configure: Configure; BootModule: Type }): Promise; +}