@ptkl/forge-plugin-system
v1.4.1
Published
A flexible and powerful plugin system for React applications with multi-slot architecture and hooks support
Maintainers
Readme
Forge Plugin System
A flexible, TypeScript-based plugin system for React applications that supports component slots, script hooks, and lifecycle management.
Features
- Component Slots: Render plugin components in designated slots
- Script Hooks: Execute plugin scripts with lifecycle hooks
- Return Values: Capture and return results from plugin script execution
- Async Support: Full support for asynchronous plugin operations
- Error Handling: Comprehensive error handling with detailed feedback
- TypeScript: Full TypeScript support with type safety
Installation
npm install forge-plugin-systemBasic Usage
Plugin Definition
import { Plugin } from 'forge-plugin-system';
const myPlugin: Plugin = {
meta: {
name: 'my-plugin',
version: '1.0.0',
enabled: true
},
// Component for rendering
Component: MyPluginComponent,
// Script execution
execute: (args) => {
console.log('Plugin executed with args:', args);
return { success: true, data: 'processed' };
},
// Lifecycle hooks
hooks: {
'app::init': () => console.log('App initialized'),
'custom::hook': (args) => {
// Process args and return result
return processData(args);
}
}
};Hook Script Execution with Results
The hookScript function returns results from plugin execution with intelligent aggregation:
import { hookScript } from 'forge-plugin-system';
// Default behavior - aggregated results
const result = await hookScript('custom::hook', { data: 'input' });
console.log(result);
// Output:
// {
// executed: ['plugin1', 'plugin2'], // Successfully executed plugins
// errors: [], // Any errors that occurred
// result: combinedOutput // Aggregated result from all plugins
// }
// Get individual plugin results when needed
const individualResults = await hookScript('custom::hook', { data: 'input' }, {
returnIndividualResults: true
});
console.log(individualResults);
// Output:
// {
// executed: ['plugin1', 'plugin2'],
// errors: [],
// results: [ // Individual results per plugin
// { plugin: 'plugin1', result: 'output1' },
// { plugin: 'plugin2', result: 'output2' }
// ]
// }Available Hook Options
const result = await hookScript('my-hook', args, {
slot: 'specific-slot', // Target specific slot
pluginName: 'specific-plugin', // Target specific plugin
force: true, // Force re-execution
throwOnError: false, // Continue on errors (default)
returnIndividualResults: false, // Return per-plugin results instead of aggregated
resultAggregator: (results) => { // Custom aggregation function
return results.reduce((acc, val) => acc + val, 0);
}
});Result Aggregation
By default, hookScript intelligently aggregates results from multiple plugins:
// Object results are merged
const objectResults = await hookScript('get-config');
// If plugins return: [{ a: 1 }, { b: 2 }]
// Result: { a: 1, b: 2 }
// Array results are flattened
const arrayResults = await hookScript('get-items');
// If plugins return: [['item1'], ['item2', 'item3']]
// Result: ['item1', 'item2', 'item3']
// Single result is returned as-is
const singleResult = await hookScript('calculate', { a: 5, b: 3 });
// If one plugin returns: 8
// Result: 8
// Mixed types become arrays
const mixedResults = await hookScript('mixed-hook');
// If plugins return: ['text', 42, { obj: true }]
// Result: ['text', 42, { obj: true }]Error Handling
try {
const result = await hookScript('risky-hook', args, { throwOnError: true });
// Handle successful results
result.results.forEach(({ plugin, result }) => {
console.log(`${plugin} returned:`, result);
});
} catch (error) {
// Handle first error encountered
console.error('Plugin execution failed:', error);
}Plugin Types and Return Values
Plugins can return any type of data, and results are automatically aggregated:
const dataPlugin: Plugin = {
meta: { name: 'data-plugin' },
hooks: {
'fetch-users': async () => {
const users = await fetchUsersFromAPI();
return { users, count: users.length };
},
'calculate': (args) => {
return args.a + args.b;
},
'transform': (args) => {
return args.data.map(item => transform(item));
}
}
};
// Usage - aggregated results by default
const userData = await hookScript('fetch-users');
console.log(userData.result); // Combined data from all plugins
const calcResult = await hookScript('calculate', { a: 5, b: 3 });
console.log(calcResult.result); // 8 (if only one plugin) or aggregated results
// Get individual results when needed
const { results } = await hookScript('fetch-users', null, {
returnIndividualResults: true
});
const firstPluginData = results[0]?.result; // { users: [...], count: 10 }Custom Aggregation
Define custom logic for combining plugin results:
// Collect all user data from multiple sources
const allUsers = await hookScript('get-users', null, {
resultAggregator: (results) => {
const users = [];
results.forEach(result => {
if (result.users) users.push(...result.users);
});
return { users: [...new Set(users)], total: users.length };
}
});
// Calculate statistics across plugins
const stats = await hookScript('calculate-metrics', data, {
resultAggregator: (results) => ({
min: Math.min(...results),
max: Math.max(...results),
avg: results.reduce((a, b) => a + b, 0) / results.length
})
});Lifecycle Hooks
Standard lifecycle hooks are available:
app::init- Application initializationapp::start- Application startapp::cleanup- Application cleanupbefore::enter- Before route/component enterbefore::leave- Before route/component leaveplugin::load- Plugin loadingplugin::execute- Plugin executionslot::enter- Slot activation
Component Slots
import { PluginSlot } from 'forge-plugin-system';
function MyApp() {
return (
<div>
<PluginSlot
slotName="header"
executeScripts={true} // Also execute scripts for this slot
/>
<main>
<PluginSlot slotName="content" />
</main>
<PluginSlot slotName="footer" />
</div>
);
}Advanced Usage
Sequential vs Concurrent Execution
By default, plugins execute concurrently. Use throwOnError: true for sequential execution:
// Concurrent (default) - all plugins run in parallel
const concurrent = await hookScript('process-data', data);
// Sequential - stops on first error
const sequential = await hookScript('critical-process', data, {
throwOnError: true
});Plugin Registry Management
import { usePlugins } from 'forge-plugin-system';
function PluginManager() {
const { plugins, registerPlugin, getStatus } = usePlugins();
const handlePluginAdd = (plugin: Plugin) => {
registerPlugin(plugin);
};
const status = getStatus();
console.log(`${status.executedPlugins}/${status.totalPlugins} plugins executed`);
return (
<div>
<h3>Registered Plugins: {plugins.length}</h3>
<button onClick={() => handlePluginAdd(newPlugin)}>
Add Plugin
</button>
</div>
);
}Plugin Permissions
Plugins can register permissions that are automatically prefixed with the plugin ID:
import { Plugin } from 'forge-plugin-system';
const myPlugin: Plugin = {
meta: {
name: 'Sattrakt',
version: '1.0.0',
id: 'sattrakt' // Plugin ID used as prefix
},
// Register permissions WITHOUT prefix
permissions: [
'custom_analytics',
'advanced_filtering',
'export_special_reports',
'integration_management'
],
// ... rest of plugin
};
// Permissions are automatically prefixed:
// sattrakt::custom_analytics
// sattrakt::advanced_filtering
// sattrakt::export_special_reports
// sattrakt::integration_managementUsing Plugin Permissions:
import { usePluginPermissions } from 'forge-plugin-system';
function MyComponent() {
const {
getAllPermissions,
getPermissions,
hasPermission,
getPluginsWithPermission
} = usePluginPermissions();
// Get all permissions from all plugins
const allPerms = getAllPermissions();
// ['sattrakt::custom_analytics', 'sattrakt::advanced_filtering', ...]
// Get permissions for a specific plugin
const sattraktPerms = getPermissions('sattrakt');
// Check if a permission exists
if (hasPermission('sattrakt::custom_analytics')) {
// Show analytics feature
}
// Find all plugins with a specific permission
const pluginsWithExport = getPluginsWithPermission('export_special_reports');
// ['sattrakt']
return <div>...</div>;
}Debugging
Enable debug mode to see detailed execution logs:
import { enablePluginDebug } from 'forge-plugin-system';
// Enable debugging
enablePluginDebug(true);
// Or in browser console
window.enableHookDebug(true);
window.debugPluginHooks(); // Show debug infoAPI Reference
hookScript(hook, args?, options?)
Executes plugin scripts for the specified hook with intelligent result aggregation.
Parameters:
hook: string- Hook name to executeargs?: any- Arguments to pass to plugin scriptsoptions?: object- Execution optionsslot?: string- Target specific slotpluginName?: string- Target specific pluginforce?: boolean- Force re-executionthrowOnError?: boolean- Stop on first errorreturnIndividualResults?: boolean- Return per-plugin results instead of aggregatedresultAggregator?: (results: any[]) => any- Custom aggregation function
Returns (default - aggregated):
Promise<{
executed: string[]; // Successfully executed plugin names
errors: Array<{ plugin: string; error: Error }>; // Execution errors
result?: any; // Aggregated result from all plugins
}>Returns (when returnIndividualResults: true):
Promise<{
executed: string[]; // Successfully executed plugin names
errors: Array<{ plugin: string; error: Error }>; // Execution errors
results: Array<{ plugin: string; result: any }>; // Individual plugin results
}>Plugin Interface
interface Plugin {
meta: PluginMeta;
Component?: ComponentType<any>;
execute?: (args?: any) => any | Promise<any>;
cleanup?: () => void | Promise<void>;
hooks?: {
[hookName: string]: (args?: any) => any | Promise<any>;
};
slots?: {
[slotName: string]: {
components?: { [name: string]: ComponentType<any> };
scripts?: { [hookName: string]: (args?: any) => any | Promise<any> };
};
};
}License
MIT License - see LICENSE file for details.
