Plugins API
Plugin system for extending Stage Flow functionality.
Plugin Interface
Base interface for all plugins.
interface Plugin<TStage extends string, TData = unknown> {
/** Unique plugin name */
name: string;
/** Optional plugin version */
version?: string;
/** Optional plugin dependencies */
dependencies?: string[];
/** Plugin installation function */
install: (engine: StageFlowEngine<TStage, TData>) => void | Promise<void>;
/** Optional plugin uninstallation function */
uninstall?: (engine: StageFlowEngine<TStage, TData>) => void | Promise<void>;
/** Optional lifecycle hooks */
hooks?: {
beforeTransition?: (context: TransitionContext<TStage, TData>) => void | Promise<void>;
afterTransition?: (context: TransitionContext<TStage, TData>) => void | Promise<void>;
onStageEnter?: (context: StageContext<TStage, TData>) => void | Promise<void>;
onStageExit?: (context: StageContext<TStage, TData>) => void | Promise<void>;
};
/** Optional plugin-specific state */
state?: Record<string, unknown>;
}
LoggingPlugin
Plugin for logging stage transitions and events.
import { LoggingPlugin } from '@stage-flow/plugins';
enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
NONE = 4
}
interface LoggingPluginConfig {
/** Minimum log level to output */
level: LogLevel;
/** Whether to include timestamps in logs */
includeTimestamp: boolean;
/** Whether to include stage context in logs */
includeContext: boolean;
/** Whether to log stage transitions */
logTransitions: boolean;
/** Whether to log stage enter/exit events */
logStageEvents: boolean;
/** Custom log prefix */
prefix: string;
/** Custom logger function (defaults to console) */
logger?: {
debug: (message: string, ...args: unknown[]) => void;
info: (message: string, ...args: unknown[]) => void;
warn: (message: string, ...args: unknown[]) => void;
error: (message: string, ...args: unknown[]) => void;
};
/** Whether to enable development mode features */
developmentMode: boolean;
}
class LoggingPlugin<TStage extends string, TData = unknown> implements Plugin<TStage, TData> {
constructor(config?: Partial<LoggingPluginConfig>);
/** Update plugin configuration */
updateConfig(config: Partial<LoggingPluginConfig>): void;
/** Get current configuration */
getConfig(): LoggingPluginConfig;
/** Create a logging plugin instance with default configuration */
static create<TStage extends string, TData = unknown>(
config?: Partial<LoggingPluginConfig>
): LoggingPlugin<TStage, TData>;
/** Create a logging plugin instance for development */
static createForDevelopment<TStage extends string, TData = unknown>(
config?: Partial<LoggingPluginConfig>
): LoggingPlugin<TStage, TData>;
/** Create a logging plugin instance for production */
static createForProduction<TStage extends string, TData = unknown>(
config?: Partial<LoggingPluginConfig>
): LoggingPlugin<TStage, TData>;
}
PersistencePlugin
Plugin for saving and restoring application state.
import { PersistencePlugin } from '@stage-flow/plugins';
interface PersistencePluginConfig {
/** Storage key for saving state */
key: string;
/** Storage implementation to use */
storage: Storage;
/** Optional custom serializer */
serialize?: (data: any) => string;
/** Optional custom deserializer */
deserialize?: (str: string) => any;
/** Optional time-to-live in milliseconds */
ttl?: number;
/** Whether to auto-save on every transition */
autoSave?: boolean;
/** Whether to restore state on engine start */
autoRestore?: boolean;
}
class PersistencePlugin<TStage extends string, TData = unknown> implements Plugin<TStage, TData> {
constructor(config: PersistencePluginConfig);
/** Save current state to storage */
save(): void;
/** Restore state from storage */
restore(): boolean;
/** Clear saved state */
clear(): void;
/** Check if saved state exists */
hasSavedState(): boolean;
}
AnalyticsPlugin
Plugin for tracking user interactions and stage transitions.
import { AnalyticsPlugin } from '@stage-flow/plugins';
interface AnalyticsPluginConfig {
/** Whether to track stage time spent */
trackStageTime?: boolean;
/** Whether to track stage transition durations */
trackStageDurations?: boolean;
/** Custom event handlers */
eventHandlers?: {
onStageEnter?: (stage: string, data: any) => void;
onStageExit?: (stage: string, data: any) => void;
onTransition?: (from: string, to: string, data: any) => void;
onError?: (error: Error, context: any) => void;
};
/** Whether to include stage data in events */
includeData?: boolean;
/** Custom event prefix */
eventPrefix?: string;
}
class AnalyticsPlugin<TStage extends string, TData = unknown> implements Plugin<TStage, TData> {
constructor(config?: AnalyticsPluginConfig);
/** Track a custom event */
track(event: string, data?: any): void;
/** Get analytics data */
getAnalytics(): {
stageTimes: Record<string, number>;
transitionCounts: Record<string, number>;
totalTime: number;
};
/** Reset analytics data */
reset(): void;
}
Usage Examples
Basic Plugin Usage
import { StageFlowEngine } from '@stage-flow/core';
import { LoggingPlugin, PersistencePlugin, AnalyticsPlugin } from '@stage-flow/plugins';
const engine = new StageFlowEngine(config, {
plugins: [
new LoggingPlugin({
level: LogLevel.INFO,
includeContext: true
}),
new PersistencePlugin({
key: 'app-state',
storage: localStorage,
autoSave: true,
autoRestore: true
}),
new AnalyticsPlugin({
trackStageTime: true,
trackStageDurations: true,
eventHandlers: {
onStageEnter: (stage, data) => {
console.log(`Entered stage: ${stage}`, data);
},
onTransition: (from, to, data) => {
console.log(`Transition: ${from} -> ${to}`, data);
}
}
})
]
});
Custom Plugin
import { Plugin, StageFlowEngine } from '@stage-flow/core';
class CustomPlugin<TStage extends string, TData = unknown> implements Plugin<TStage, TData> {
name = 'custom-plugin';
version = '1.0.0';
private engine?: StageFlowEngine<TStage, TData>;
private state: Record<string, unknown> = {};
async install(engine: StageFlowEngine<TStage, TData>): Promise<void> {
this.engine = engine;
// Subscribe to engine events
const unsubscribe = engine.subscribe((stage, data) => {
console.log(`Custom plugin: Stage changed to ${stage}`, data);
this.state.lastStage = stage;
this.state.lastData = data;
});
// Store unsubscribe function for cleanup
this.state.unsubscribe = unsubscribe;
console.log('Custom plugin installed');
}
async uninstall(): Promise<void> {
if (this.state.unsubscribe) {
(this.state.unsubscribe as () => void)();
}
this.engine = undefined;
this.state = {};
console.log('Custom plugin uninstalled');
}
hooks = {
beforeTransition: async (context) => {
console.log(`Custom plugin: Before transition ${context.from} -> ${context.to}`);
},
afterTransition: async (context) => {
console.log(`Custom plugin: After transition ${context.from} -> ${context.to}`);
},
onStageEnter: async (context) => {
console.log(`Custom plugin: Entered stage ${context.current}`);
},
onStageExit: async (context) => {
console.log(`Custom plugin: Exited stage ${context.current}`);
}
};
}
const engine = new StageFlowEngine(config, {
plugins: [new CustomPlugin()]
});
Plugin Configuration
// Logging Plugin with custom configuration
const loggingPlugin = new LoggingPlugin({
level: LogLevel.DEBUG,
includeContext: true,
logTransitions: true,
logStageEvents: true,
prefix: '[MyApp]',
logger: {
debug: (msg) => console.debug(`[DEBUG] ${msg}`),
info: (msg) => console.info(`[INFO] ${msg}`),
warn: (msg) => console.warn(`[WARN] ${msg}`),
error: (msg) => console.error(`[ERROR] ${msg}`)
},
developmentMode: process.env.NODE_ENV === 'development'
});
// Persistence Plugin with custom serialization
const persistencePlugin = new PersistencePlugin({
key: 'my-app-state',
storage: sessionStorage,
serialize: (data) => JSON.stringify(data, null, 2),
deserialize: (str) => JSON.parse(str),
ttl: 24 * 60 * 60 * 1000, // 24 hours
autoSave: true,
autoRestore: true
});
// Analytics Plugin with custom event handlers
const analyticsPlugin = new AnalyticsPlugin({
trackStageTime: true,
trackStageDurations: true,
includeData: true,
eventPrefix: 'stage_flow',
eventHandlers: {
onStageEnter: (stage, data) => {
if (window.gtag) {
window.gtag('event', 'stage_enter', {
stage_name: stage,
stage_data: data
});
}
},
onTransition: (from, to, data) => {
if (window.gtag) {
window.gtag('event', 'stage_transition', {
from_stage: from,
to_stage: to,
transition_data: data
});
}
},
onError: (error, context) => {
if (window.gtag) {
window.gtag('event', 'stage_error', {
error_message: error.message,
error_stack: error.stack,
context: context
});
}
}
}
});
Plugin Lifecycle
class LifecyclePlugin<TStage extends string, TData = unknown> implements Plugin<TStage, TData> {
name = 'lifecycle-plugin';
version = '1.0.0';
private engine?: StageFlowEngine<TStage, TData>;
async install(engine: StageFlowEngine<TStage, TData>): Promise<void> {
this.engine = engine;
console.log('Lifecycle plugin installed');
// Subscribe to engine events
const unsubscribe = engine.subscribe((stage, data) => {
console.log(`Engine state changed: ${stage}`, data);
});
// Store for cleanup
this.state = { unsubscribe };
}
async uninstall(): Promise<void> {
console.log('Lifecycle plugin uninstalling');
if (this.state?.unsubscribe) {
(this.state.unsubscribe as () => void)();
}
this.engine = undefined;
console.log('Lifecycle plugin uninstalled');
}
hooks = {
beforeTransition: async (context) => {
console.log(`[Lifecycle] Before transition: ${context.from} -> ${context.to}`);
},
afterTransition: async (context) => {
console.log(`[Lifecycle] After transition: ${context.from} -> ${context.to}`);
},
onStageEnter: async (context) => {
console.log(`[Lifecycle] Entered stage: ${context.current}`);
},
onStageExit: async (context) => {
console.log(`[Lifecycle] Exited stage: ${context.current}`);
}
};
}
Error Handling in Plugins
class ErrorHandlingPlugin<TStage extends string, TData = unknown> implements Plugin<TStage, TData> {
name = 'error-handling-plugin';
version = '1.0.0';
async install(engine: StageFlowEngine<TStage, TData>): Promise<void> {
// Subscribe to engine errors
engine.on('error', (error) => {
console.error('Stage Flow Error:', error);
// Send error to monitoring service
if (window.gtag) {
window.gtag('event', 'exception', {
description: error.message,
fatal: false
});
}
// Send to custom error tracking
if (window.Sentry) {
window.Sentry.captureException(error);
}
});
}
hooks = {
onStageEnter: async (context) => {
try {
// Validate stage data
if (context.data && typeof context.data === 'object') {
// Perform validation
console.log('Stage data validated:', context.data);
}
} catch (error) {
console.error('Stage validation error:', error);
throw error;
}
}
};
}
Plugin Dependencies
class DependentPlugin<TStage extends string, TData = unknown> implements Plugin<TStage, TData> {
name = 'dependent-plugin';
version = '1.0.0';
dependencies = ['logging', 'persistence']; // Requires logging and persistence plugins
async install(engine: StageFlowEngine<TStage, TData>): Promise<void> {
// Check if required plugins are installed
const installedPlugins = engine.getInstalledPlugins();
for (const dependency of this.dependencies) {
if (!installedPlugins.includes(dependency)) {
throw new Error(`Required plugin '${dependency}' is not installed`);
}
}
console.log('Dependent plugin installed with all dependencies');
}
}