A flexible and powerful plugin system for React applications with multi-slot architecture and hooks support
npm install @ptkl/forge-plugin-systemA flexible, TypeScript-based plugin system for React applications that supports component slots, script hooks, and lifecycle management.
- 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
``bash`
npm install forge-plugin-system
`typescript
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);
}
}
};
`
The hookScript function returns results from plugin execution with intelligent aggregation:
`typescript
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' }
// ]
// }
`
`typescript`
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);
}
});
By default, hookScript intelligently aggregates results from multiple plugins:
`typescript
// 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 }]
`
`typescript${plugin} returned:
try {
const result = await hookScript('risky-hook', args, { throwOnError: true });
// Handle successful results
result.results.forEach(({ plugin, result }) => {
console.log(, result);`
});
} catch (error) {
// Handle first error encountered
console.error('Plugin execution failed:', error);
}
Plugins can return any type of data, and results are automatically aggregated:
`typescript
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 }
`
Define custom logic for combining plugin results:
`typescript
// 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
})
});
`
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
`typescript
import { PluginSlot } from 'forge-plugin-system';
function MyApp() {
return (
Advanced Usage
$3
By default, plugins execute concurrently. Use
throwOnError: true for sequential execution:`typescript
// 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
});
`$3
`typescript
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 (
Registered Plugins: {plugins.length}
);
}
`$3
Plugins can register permissions that are automatically prefixed with the plugin ID:
`typescript
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_management
`Using Plugin Permissions:
`typescript
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
...;
}
`$3
Enable debug mode to see detailed execution logs:
`typescript
import { enablePluginDebug } from 'forge-plugin-system';// Enable debugging
enablePluginDebug(true);
// Or in browser console
window.enableHookDebug(true);
window.debugPluginHooks(); // Show debug info
`API Reference
$3
Executes plugin scripts for the specified hook with intelligent result aggregation.
Parameters:
-
hook: string - Hook name to execute
- args?: any - Arguments to pass to plugin scripts
- options?: object - Execution options
- slot?: string - Target specific slot
- pluginName?: string - Target specific plugin
- force?: boolean - Force re-execution
- throwOnError?: boolean - Stop on first error
- returnIndividualResults?: boolean - Return per-plugin results instead of aggregated
- resultAggregator?: (results: any[]) => any - Custom aggregation functionReturns (default - aggregated):
`typescript
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):
`typescript
Promise<{
executed: string[]; // Successfully executed plugin names
errors: Array<{ plugin: string; error: Error }>; // Execution errors
results: Array<{ plugin: string; result: any }>; // Individual plugin results
}>
`$3
`typescript
interface Plugin {
meta: PluginMeta;
Component?: ComponentType;
execute?: (args?: any) => any | Promise;
cleanup?: () => void | Promise;
hooks?: {
[hookName: string]: (args?: any) => any | Promise;
};
slots?: {
[slotName: string]: {
components?: { [name: string]: ComponentType };
scripts?: { [hookName: string]: (args?: any) => any | Promise };
};
};
}
``MIT License - see LICENSE file for details.