Dynamic registeration of plugins #154
Replies: 6 comments 1 reply
-
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);
};
// 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();
}
}
// 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. |
Beta Was this translation helpful? Give feedback.
-
Long term goals: Implement awilix DI for plugins with scoped lifetime management for each plugins |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
@yuvrajsab No actually, this map will already have beans associated with the name in them. NestJs assures that threads are singleton only. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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:
Beta Was this translation helpful? Give feedback.
All reactions