mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
- [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
72 lines
1.8 KiB
TypeScript
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);
|
|
}
|
|
}
|