npm package: @ptkl/forge-plugin-system
Description: A flexible and powerful plugin system for React applications with multi-slot architecture and hooks support
Author: Strahinja Djuric
Latest version: 1.7.0

# 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

```bash
npm install forge-plugin-system
```

## Basic Usage

### Plugin Definition

```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);
    }
  }
};
```

### Hook Script Execution with Results

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' }
//   ]
// }
```

### Available Hook Options

```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);
  }
});
```

### Result Aggregation

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 }]
```

### Error Handling

```typescript
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:

```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 }
```

### Custom Aggregation

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
  })
});
```

### Lifecycle Hooks

Standard lifecycle hooks are available:

- `app::init` - Application initialization
- `app::start` - Application start
- `app::cleanup` - Application cleanup
- `before::enter` - Before route/component enter
- `before::leave` - Before route/component leave
- `plugin::load` - Plugin loading
- `plugin::execute` - Plugin execution
- `slot::enter` - Slot activation

### Component Slots

```typescript
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:

```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 
});
```

### Plugin Registry Management

```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 (
    <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:

```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 <div>...</div>;
}
```

### Debugging

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

### hookScript(hook, args?, options?)

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 function

**Returns (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
}>
```

### Plugin Interface

```typescript
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.