Files
AFFiNE-Mirror/packages/backend/server/src/fundamentals/nestjs/optional-module.ts
liuyi e516e0db23 refactor(server): plugin modules (#5630)
- [x] separates modules into `fundamental`, `core`, `plugins`
- [x] optional modules with `@OptionalModule` decorator to install modules with requirements met(`requires`, `if`)
- [x] `module.contributesTo` defines optional features that will be enabled if module registered
- [x] `AFFiNE.plugins.use('payment', {})` to enable a optional/plugin module
- [x] `PaymentModule` is the first plugin module
- [x] GraphQLSchema will not be generated for non-included modules
- [x] Frontend can use `ServerConfigType` query to detect which features are enabled
- [x] override existing provider globally
2024-01-22 07:40:28 +00:00

72 lines
1.8 KiB
TypeScript

import {
DynamicModule,
Module,
ModuleMetadata,
Provider,
Type,
} from '@nestjs/common';
import { omit } from 'lodash-es';
import { Config, ConfigPaths } from '../config';
interface OptionalModuleMetadata extends ModuleMetadata {
/**
* Only install module if given config paths are defined in AFFiNE config.
*/
requires?: ConfigPaths[];
/**
* Only install module if the predication returns true.
*/
if?: (config: Config) => boolean;
/**
* Defines which feature will be enabled if the module installed.
*/
contributesTo?: import('../../core/config').ServerFeature; // avoid circlar dependency
/**
* Defines which providers provided by other modules will be overridden if the module installed.
*/
overrides?: Provider[];
}
const additionalOptions = [
'contributesTo',
'requires',
'if',
'overrides',
] as const satisfies Array<keyof OptionalModuleMetadata>;
type OptionalDynamicModule = DynamicModule & OptionalModuleMetadata;
export function OptionalModule(metadata: OptionalModuleMetadata) {
return (target: Type) => {
additionalOptions.forEach(option => {
if (Object.hasOwn(metadata, option)) {
Reflect.defineMetadata(option, metadata[option], target);
}
});
if (metadata.overrides) {
metadata.providers = (metadata.providers ?? []).concat(
metadata.overrides
);
metadata.exports = (metadata.exports ?? []).concat(metadata.overrides);
}
const nestMetadata = omit(metadata, additionalOptions);
Module(nestMetadata)(target);
};
}
export function getOptionalModuleMetadata<
T extends keyof OptionalModuleMetadata,
>(target: Type | OptionalDynamicModule, key: T): OptionalModuleMetadata[T] {
if ('module' in target) {
return target[key];
} else {
return Reflect.getMetadata(key, target);
}
}