@kanian77/choux
v0.2.8
Published
A simple plugin framework for TypeScript.
Readme
Choux Plugin System
Choux is a modular, type-safe plugin system for Node.js/TypeScript applications, built on top of the @kanian77/tject dependency injection framework. It enables plugin lifecycle management and custom hooks, making it easy to extend your application with isolated, reusable modules.
Core Concepts
Plugin
A plugin is a class extending the abstract Plugin base class (src/core/Plugin.ts). Each plugin must define metadata (name, version, optional description and dependencies) and can implement lifecycle methods:
onLoad: Called when the plugin is loaded, before dependencies are resolved.onInit: Called after dependencies are resolved and injected.onDestroy: Called when the plugin is being unloaded.
Plugins are implemented as PluginModule classes, which extend the base Module class from @kanian77/tject.
Plugin Loading & Management
- PluginManager (
src/core/PluginManager.ts): Manages the initialization, lifecycle, and shutdown of plugins. It provides methods to load individual plugins, get loaded plugin instances, and properly shutdown plugins. - ChouxFacade (
src/core/ChouxFacade.ts): A unified facade that combines PluginManager and HookRegistry functionality. This is the recommended way to interact with Choux as it provides a single interface for all plugin and hook operations. - ChouxModule (
src/core/ChouxModule.ts): The main entry point module that provides the core services (PluginManager, HookRegistry, and ChouxFacade). Must be imported and bootstrapped in your application.
Dependency Injection
All plugins and core services use the DI system from @kanian77/tject. Tokens (see src/lib/types/tokens.ts) are used to register and inject services. Core tokens are defined as Symbols:
PLUGIN_MANAGER_TOKEN: For accessing the PluginManagerHOOK_REGISTRY_TOKEN: For accessing the HookRegistryCHOUX_FACADE_TOKEN: For accessing the ChouxFacade (recommended)
Hooks & Events
- HookRegistry (
src/core/HookRegistry.ts): Central registry for registering, triggering, and managing hooks/events. Plugins can register handlers for lifecycle or custom hooks, and trigger hooks to communicate with other plugins. - Lifecycle Hooks: Standard hooks for plugin load/init/destroy and application start/shutdown. Access them via the
LIFECYCLE_HOOKSconstant:APPLICATION_START: 'core:application-start'BEFORE_PLUGIN_LOAD: 'core:before-plugin-load'AFTER_PLUGIN_LOAD: 'core:after-plugin-load'BEFORE_PLUGIN_INIT: 'core:before-plugin-init'AFTER_PLUGIN_INIT: 'core:after-plugin-init'BEFORE_PLUGIN_DESTROY: 'core:before-plugin-destroy'AFTER_PLUGIN_DESTROY: 'core:after-plugin-destroy'APPLICATION_SHUTDOWN: 'core:application-shutdown'
- Custom Hooks: Plugins can define and trigger their own hooks for extensibility.
Decorators & Utilities
- Hook Decorators: Three decorators work together to enable hook functionality:
@EnableHooks: Class-level decorator that must be applied to any class using hook decorators@Hook(hookName): Method decorator for registering standard hook handlers in classes@RequestHook(hookName): Method decorator for registering request hooks that return values (only one handler per hook)
- Function Utilities (
src/lib/functions/): Helper functions for hook registration and plugin management. - Types (
src/lib/types/): Shared interfaces and type definitions for plugins, hook registry, plugin manager, and metadata.
Usage
Bootstrapping
To use Choux in your application:
- Import and bootstrap the ChouxModule:
import { bootstrap, inject } from '@kanian77/tject';
import {
ChouxModule,
CHOUX_FACADE_TOKEN,
type IChouxFacade,
} from '@kanian77/choux';
import { YourPluginModule } from './plugins/your-plugin/module';
// Bootstrap the core module
bootstrap(ChouxModule);
// Get the Choux facade (recommended approach)
const choux = inject<IChouxFacade>(CHOUX_FACADE_TOKEN);
// Initialize your plugins
await choux.initializePlugins([YourPluginModule]);Creating a Plugin
Create a new plugin by extending the Plugin class and defining it as a PluginModule:
import { Plugin, PluginModule, EnableHooks, Hook } from '@kanian77/choux';
import { Service } from '@kanian77/tject';
// Define a unique symbol token for your plugin
export const YOUR_PLUGIN_TOKEN = Symbol('YOUR_PLUGIN');
@Service({ token: YOUR_PLUGIN_TOKEN })
@EnableHooks
class YourPlugin extends Plugin {
readonly metadata = {
name: 'your-plugin',
version: '1.0.0',
description: 'Your plugin description',
};
async onInit() {
// Initialize your plugin
}
// Example of using hooks
@Hook('some:event')
onSomeEvent(data: any) {
// Handle the event
}
}
export const YourPluginModule = new PluginModule({
token: YOUR_PLUGIN_TOKEN,
providers: [
{
provide: YOUR_PLUGIN_TOKEN,
useClass: YourPlugin,
},
// Add your plugin's services here
],
});Accessing Plugin Services
You can inject services provided by plugins using their tokens:
import { inject } from '@kanian77/tject';
import { YOUR_SERVICE_TOKEN } from './tokens';
import { IYourService } from './services';
const yourService = inject<IYourService>(YOUR_SERVICE_TOKEN);
yourService.doSomething();Using the ChouxFacade
The ChouxFacade provides a unified interface for both plugin management and hook operations:
import { inject } from '@kanian77/tject';
import { CHOUX_FACADE_TOKEN, type ChouxFacade } from '@kanian77/choux';
const choux = inject<ChouxFacade>(CHOUX_FACADE_TOKEN);
// Plugin management
await choux.initializePlugins([PluginModule1, PluginModule2]);
const loadedPlugin = choux.getPlugin('plugin-name'); // Returns { plugin: PluginInstance, metadata: PluginMetadata }
const allPlugins = choux.getAllPlugins();
await choux.shutdown();
// Hook operations - trigger events
await choux.trigger('custom:event', eventData);
// Hook operations - request values
const config = await choux.request<ConfigType>('config:get');
// Register hooks programmatically
choux.register('custom:event', target, handlerFunction);
choux.registerRequest('custom:query', target, queryFunction);Directory Overview
src/core/: Core plugin system (Plugin, PluginManager, HookRegistry, ChouxFacade, ChouxModule)src/decorators/: Hook decorators (@EnableHooks, @Hook, @RequestHook) and registration utilitiessrc/lib/functions/: Helper functions for hooks and pluginssrc/lib/types/: Shared type definitions and tokens
License
MIT
What's Already Great ✅
- Core Architecture: Deep integration with
@kanian77/tjectfor robust DI - Lifecycle Management: Load, init, destroy with proper ordering
- Hook System: Both decorators and manual registration
- Simplicity: Direct plugin loading without file system complexity
Areas for Enhancement 🔧
1. Plugin Configuration System
// src/core/pluginConfig.ts
export interface PluginConfig {
[pluginName: string]: {
enabled?: boolean;
config?: Record<string, any>;
};
}
// In PluginManager
async initializePlugins(plugins: PluginModule[], config?: PluginConfig): Promise<void>2. Plugin Validation & Health Checks
// Add to Plugin base class
async healthCheck?(): Promise<{ healthy: boolean; message?: string }>;
// Plugin metadata validation
export function validatePluginMetadata(metadata: PluginMetadata): string[] {
const errors: string[] = [];
if (!metadata.name) errors.push('Plugin name is required');
// ... more validations
return errors;
}3. Error Handling & Recovery
// Enhanced error handling in PluginManager
interface PluginLoadError {
pluginName: string;
error: Error;
phase: 'load' | 'init';
}
// Add retry mechanisms for failed plugins
async retryFailedPlugin(pluginName: string): Promise<void>