Closed radhay-1199 closed 1 year ago
POC:
// plugin.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const PLUGIN_TOKEN = 'PLUGIN_TOKEN';
export const Plugin = (token: string): ClassDecorator => { return SetMetadata(PLUGIN_TOKEN, token); };
- Use the Plugin decorator to mark your plugin classes with the corresponding token:
```ts
// pdf-plugin.ts
import { Plugin } from './plugin.decorator';
@Plugin('pdf-plugin')
export class PdfPlugin {
generateDoc(): string {
// Logic to generate a PDF document
return 'Generated PDF document';
}
}
// plugin.factory.ts
import { Injectable } from '@nestjs/common';
import { PLUGIN_TOKEN } from './plugin.decorator';
@Injectable() export class PluginFactory { private plugins: Map<string, any> = new Map();
registerPlugin(token: string, plugin: any) { this.plugins.set(token, plugin); }
createPlugin(token: string): any {
const plugin = this.plugins.get(token);
if (!plugin) {
throw new Error(Plugin not found for token: ${token}
);
}
return new plugin();
}
}
- Scan for classes with the Plugin decorator in your app module and register them dynamically:
```ts
// app.module.ts
import { Module, ModuleRef, OnModuleInit, Reflector } from '@nestjs/common';
import { AppController } from './app.controller';
import { PluginFactory } from './plugin.factory';
import { PLUGIN_TOKEN } from './plugin.decorator';
@Module({
controllers: [AppController],
providers: [PluginFactory],
})
export class AppModule implements OnModuleInit {
constructor(
private readonly moduleRef: ModuleRef,
private readonly reflector: Reflector,
private readonly pluginFactory: PluginFactory,
) {}
onModuleInit() {
const providers = this.moduleRef.getProviders();
providers.forEach((provider) => {
const pluginToken = this.reflector.get<string>(PLUGIN_TOKEN, provider.metatype);
if (pluginToken) {
const pluginInstance = this.moduleRef.get(provider.metatype);
this.pluginFactory.registerPlugin(pluginToken, pluginInstance);
}
});
}
}
// doc.controller.ts
import { Controller, Get, Param, Inject } from '@nestjs/common';
import { PluginFactory } from './plugin.factory';
@Controller('documents') export class DocController { constructor( @Inject(PluginFactory) private readonly pluginFactory: PluginFactory, ) {}
@Get(':plugin/') generateDocument(@Param('plugin') pluginToken: string): string { const plugin = this.pluginFactory.createPlugin(pluginToken); const generatedDoc = plugin.generateDoc(); return generatedDoc; } }
With this approach, we can create new plugin classes with the Plugin decorator and their respective tokens. They will be automatically discovered and registered in the PluginFactory during the runtime of your NestJS application.
Long term goals: Implement awilix DI for plugins with scoped lifetime management for each plugins References- https://github.com/jeffijoe/awilix
@radhay-1199 but here
createPlugin(token: string): any {
const plugin = this.plugins.get(token);
if (!plugin) {
throw new Error(`Plugin not found for token: ${token}`);
}
return new plugin();
}
we are newing up the plugin instance whenever a request comes. These plugin classes should be singleton.
Looks good, agree with @yuvrajsab , let's make don't make these objects if already have one.
Now this is out of they way, we need to work on the signature of the input and output plugin methods.
@yuvrajsab No actually, this map will already have beans associated with the name in them. NestJs assures that threads are singleton only.
okay and @radhay-1199 one more question ki if a plugin class needs DI for let's say ConfigService or any other service then how are we gonna inject that or does this approach already handle that.
Problem statement: Everyone can create a plugin which is again a class with methods to generate a doc. Now currently if I do so I have to go to my factory and update the code there for the new plugin and in the app module too I have to go and add that as the provider. but I don't want this to be so much hassle. Proposed solution: