refactor(server): config system (#11081)

This commit is contained in:
forehalo
2025-03-27 12:32:28 +00:00
parent 7091111f85
commit 0ea38680fa
274 changed files with 7583 additions and 5841 deletions
+45 -115
View File
@@ -1,27 +1,19 @@
import {
DynamicModule,
ExecutionContext,
ForwardReference,
Logger,
Module,
} from '@nestjs/common';
import { DynamicModule, ExecutionContext } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { ClsPluginTransactional } from '@nestjs-cls/transactional';
import { TransactionalAdapterPrisma } from '@nestjs-cls/transactional-adapter-prisma';
import { PrismaClient } from '@prisma/client';
import { Request, Response } from 'express';
import { get } from 'lodash-es';
import { ClsModule } from 'nestjs-cls';
import { AppController } from './app.controller';
import {
getOptionalModuleMetadata,
getRequestIdFromHost,
getRequestIdFromRequest,
ScannerModule,
} from './base';
import { CacheModule } from './base/cache';
import { AFFiNEConfig, ConfigModule, mergeConfigOverride } from './base/config';
import { ConfigModule } from './base/config';
import { ErrorModule } from './base/error';
import { EventModule } from './base/event';
import { GqlModule } from './base/graphql';
@@ -32,12 +24,11 @@ import { MetricsModule } from './base/metrics';
import { MutexModule } from './base/mutex';
import { PrismaModule } from './base/prisma';
import { RedisModule } from './base/redis';
import { RuntimeModule } from './base/runtime';
import { StorageProviderModule } from './base/storage';
import { RateLimiterModule } from './base/throttler';
import { WebSocketModule } from './base/websocket';
import { AuthModule } from './core/auth';
import { ADD_ENABLED_FEATURES, ServerConfigModule } from './core/config';
import { ServerConfigModule, ServerConfigResolverModule } from './core/config';
import { DocStorageModule } from './core/doc';
import { DocRendererModule } from './core/doc-renderer';
import { DocServiceModule } from './core/doc-service';
@@ -52,10 +43,16 @@ import { SyncModule } from './core/sync';
import { UserModule } from './core/user';
import { VersionModule } from './core/version';
import { WorkspaceModule } from './core/workspaces';
import { Env } from './env';
import { ModelsModule } from './models';
import { REGISTERED_PLUGINS } from './plugins';
import { CaptchaModule } from './plugins/captcha';
import { CopilotModule } from './plugins/copilot';
import { CustomerIoModule } from './plugins/customerio';
import { GCloudModule } from './plugins/gcloud';
import { LicenseModule } from './plugins/license';
import { ENABLED_PLUGINS } from './plugins/registry';
import { OAuthModule } from './plugins/oauth';
import { PaymentModule } from './plugins/payment';
import { WorkerModule } from './plugins/worker';
export const FunctionalityModules = [
ClsModule.forRoot({
@@ -91,126 +88,64 @@ export const FunctionalityModules = [
}),
],
}),
ConfigModule.forRoot(),
RuntimeModule,
LoggerModule,
ScannerModule,
PrismaModule,
EventModule,
ConfigModule,
RedisModule,
CacheModule,
MutexModule,
PrismaModule,
MetricsModule,
RateLimiterModule,
StorageProviderModule,
HelpersModule,
ErrorModule,
LoggerModule,
WebSocketModule,
JobModule.forRoot(),
ModelsModule,
];
function filterOptionalModule(
config: AFFiNEConfig,
module: AFFiNEModule | Promise<DynamicModule> | ForwardReference<any>
) {
// can't deal with promise or forward reference
if (module instanceof Promise || 'forwardRef' in module) {
return module;
}
const requirements = getOptionalModuleMetadata(module, 'requires');
// if condition not set or condition met, include the module
if (requirements?.length) {
const nonMetRequirements = requirements.filter(c => {
const value = get(config, c);
return (
value === undefined ||
value === null ||
(typeof value === 'string' && value.trim().length === 0)
);
});
if (nonMetRequirements.length) {
const name = 'module' in module ? module.module.name : module.name;
if (!config.node.test) {
new Logger(name).warn(
`${name} is not enabled because of the required configuration is not satisfied.`,
'Unsatisfied configuration:',
...nonMetRequirements.map(config => ` AFFiNE.${config}`)
);
}
return null;
}
}
const predicator = getOptionalModuleMetadata(module, 'if');
if (predicator && !predicator(config)) {
return null;
}
const contribution = getOptionalModuleMetadata(module, 'contributesTo');
if (contribution) {
ADD_ENABLED_FEATURES(contribution);
}
const subModules = getOptionalModuleMetadata(module, 'imports');
const filteredSubModules = subModules
?.map(subModule => filterOptionalModule(config, subModule))
.filter(Boolean);
Reflect.defineMetadata('imports', filteredSubModules, module);
return module;
}
export class AppModuleBuilder {
private readonly modules: AFFiNEModule[] = [];
constructor(private readonly config: AFFiNEConfig) {}
use(...modules: AFFiNEModule[]): this {
modules.forEach(m => {
const result = filterOptionalModule(this.config, m);
if (result) {
this.modules.push(m);
}
this.modules.push(m);
});
return this;
}
useIf(
predicator: (config: AFFiNEConfig) => boolean,
...modules: AFFiNEModule[]
): this {
if (predicator(this.config)) {
useIf(predicator: () => boolean, ...modules: AFFiNEModule[]): this {
if (predicator()) {
this.use(...modules);
}
return this;
}
compile() {
@Module({
imports: this.modules,
controllers: [AppController],
})
compile(): DynamicModule {
class AppModule {}
return AppModule;
return {
module: AppModule,
imports: this.modules,
controllers: [AppController],
};
}
}
export function buildAppModule() {
AFFiNE = mergeConfigOverride(AFFiNE);
const factor = new AppModuleBuilder(AFFiNE);
export function buildAppModule(env: Env) {
const factor = new AppModuleBuilder();
factor
// basic
.use(...FunctionalityModules)
.use(ModelsModule)
// enable schedule module on graphql server and doc service
.useIf(
config => config.flavor.graphql || config.flavor.doc,
() => env.flavors.graphql || env.flavors.doc,
ScheduleModule.forRoot()
)
@@ -219,46 +154,41 @@ export function buildAppModule() {
// business modules
.use(
ServerConfigModule,
FeatureModule,
QuotaModule,
DocStorageModule,
NotificationModule,
MailModule
)
// renderer server only
.useIf(() => env.flavors.renderer, DocRendererModule)
// sync server only
.useIf(config => config.flavor.sync, SyncModule)
.useIf(() => env.flavors.sync, SyncModule)
// graphql server only
.useIf(
config => config.flavor.graphql,
VersionModule,
() => env.flavors.graphql,
GqlModule,
VersionModule,
StorageModule,
ServerConfigModule,
ServerConfigResolverModule,
WorkspaceModule,
LicenseModule
LicenseModule,
PaymentModule,
CopilotModule,
CaptchaModule,
OAuthModule,
CustomerIoModule
)
// doc service only
.useIf(config => config.flavor.doc, DocServiceModule)
.useIf(() => env.flavors.doc, DocServiceModule)
// self hosted server only
.useIf(config => config.isSelfhosted, SelfhostModule)
.useIf(config => config.flavor.renderer, DocRendererModule);
.useIf(() => env.selfhosted, WorkerModule, SelfhostModule)
// plugin modules
ENABLED_PLUGINS.forEach(name => {
const plugin = REGISTERED_PLUGINS.get(name);
if (!plugin) {
new Logger('AppBuilder').warn(`Unknown plugin ${name}`);
return;
}
factor.use(plugin);
});
// gcloud
.useIf(() => env.gcp, GCloudModule);
return factor.compile();
}
export const AppModule = buildAppModule();
export const AppModule = buildAppModule(env);