XyPriss Plugin Development Guide

This guide covers how to develop, use, and manage plugins in XyPriss.

Plugin System Overview

XyPriss features an extensible plugin system that allows you to:

  • Add custom functionality to your server
  • Extend core features
  • Create reusable components
  • Integrate third-party services
  • Implement custom middleware and routes

Plugin Architecture

Plugin Interface

interface Plugin {
    name: string;
    version: string;
    description?: string;
    dependencies?: string[];
    
    initialize(context: PluginContext): Promise<void>;
    execute(context: PluginContext): Promise<any>;
    cleanup?(): Promise<void>;
}

Plugin Context

interface PluginContext {
    app: Express;
    server: XyPrissServer;
    config: PluginConfig;
    logger: Logger;
    cache: CacheManager;
    metrics: PerformanceManager;
}

Built-in Plugins

XyPriss comes with several built-in plugins:

1. Route Optimization Plugin

Optimizes route handling and caching.

2. Server Maintenance Plugin

Provides health checks and graceful shutdown.

3. Performance Monitoring Plugin

Collects and reports performance metrics.

Creating a Custom Plugin

Basic Plugin Structure

import { Plugin, PluginContext } from "xypriss";

export class MyCustomPlugin implements Plugin {
    name = "my-custom-plugin";
    version = "1.0.0";
    description = "A custom plugin for XyPriss";
    
    async initialize(context: PluginContext): Promise<void> {
        context.logger.info(`Initializing ${this.name} v${this.version}`);
        
        // Plugin initialization logic
        // Set up routes, middleware, etc.
    }
    
    async execute(context: PluginContext): Promise<any> {
        // Main plugin execution logic
        // This is called after initialization
        
        return { status: "success" };
    }
    
    async cleanup(): Promise<void> {
        // Cleanup logic when server shuts down
        console.log(`Cleaning up ${this.name}`);
    }
}

Example: Database Connection Plugin

import { Plugin, PluginContext } from "xypriss";
import { Pool } from "pg";

export class DatabasePlugin implements Plugin {
    name = "database-connection";
    version = "1.0.0";
    description = "PostgreSQL database connection plugin";
    
    private pool: Pool | null = null;
    
    async initialize(context: PluginContext): Promise<void> {
        const { config, logger } = context;
        
        // Create database connection pool
        this.pool = new Pool({
            host: config.options.host || "localhost",
            port: config.options.port || 5432,
            database: config.options.database,
            user: config.options.user,
            password: config.options.password,
            max: config.options.maxConnections || 20
        });
        
        // Test connection
        try {
            const client = await this.pool.connect();
            await client.query("SELECT NOW()");
            client.release();
            logger.info("Database connection established");
        } catch (error) {
            logger.error("Database connection failed:", error);
            throw error;
        }
        
        // Add database to context for other plugins/routes
        (context.app as any).db = this.pool;
    }
    
    async execute(context: PluginContext): Promise<any> {
        // Add health check route
        context.app.get("/health/database", async (req, res) => {
            try {
                const client = await this.pool!.connect();
                const result = await client.query("SELECT NOW() as timestamp");
                client.release();
                
                res.json({
                    status: "healthy",
                    timestamp: result.rows[0].timestamp
                });
            } catch (error) {
                res.status(503).json({
                    status: "unhealthy",
                    error: error.message
                });
            }
        });
        
        return { status: "database plugin active" };
    }
    
    async cleanup(): Promise<void> {
        if (this.pool) {
            await this.pool.end();
            console.log("Database connections closed");
        }
    }
}

Example: Authentication Plugin

import { Plugin, PluginContext } from "xypriss";
import jwt from "jsonwebtoken";

export class AuthenticationPlugin implements Plugin {
    name = "jwt-authentication";
    version = "1.0.0";
    description = "JWT-based authentication plugin";
    
    private jwtSecret: string = "";
    
    async initialize(context: PluginContext): Promise<void> {
        const { config, logger } = context;
        
        this.jwtSecret = config.options.jwtSecret || process.env.JWT_SECRET;
        
        if (!this.jwtSecret) {
            throw new Error("JWT secret is required for authentication plugin");
        }
        
        logger.info("Authentication plugin initialized");
    }
    
    async execute(context: PluginContext): Promise<any> {
        const { app } = context;
        
        // Add authentication middleware to app
        const authenticateToken = (req: any, res: any, next: any) => {
            const authHeader = req.headers["authorization"];
            const token = authHeader && authHeader.split(" ")[1];
            
            if (!token) {
                return res.status(401).json({ error: "Access token required" });
            }
            
            jwt.verify(token, this.jwtSecret, (err: any, user: any) => {
                if (err) {
                    return res.status(403).json({ error: "Invalid token" });
                }
                req.user = user;
                next();
            });
        };
        
        // Make middleware available globally
        (app as any).authenticateToken = authenticateToken;
        
        // Add login route
        app.post("/auth/login", async (req, res) => {
            const { username, password } = req.body;
            
            // Implement your user verification logic here
            const user = await this.verifyUser(username, password);
            
            if (!user) {
                return res.status(401).json({ error: "Invalid credentials" });
            }
            
            const token = jwt.sign(
                { userId: user.id, username: user.username },
                this.jwtSecret,
                { expiresIn: "1h" }
            );
            
            res.json({ token, user: { id: user.id, username: user.username } });
        });
        
        return { status: "authentication routes added" };
    }
    
    private async verifyUser(username: string, password: string): Promise<any> {
        // Implement your user verification logic
        // This is just a placeholder
        if (username === "admin" && password === "password") {
            return { id: 1, username: "admin" };
        }
        return null;
    }
}

Using Plugins

Plugin Configuration

import { createServer } from "xypriss";
import { DatabasePlugin } from "./plugins/DatabasePlugin";
import { AuthenticationPlugin } from "./plugins/AuthenticationPlugin";

const server = createServer({
    plugins: [
        {
            name: "database-connection",
            plugin: new DatabasePlugin(),
            enabled: true,
            options: {
                host: "localhost",
                port: 5432,
                database: "myapp",
                user: "dbuser",
                password: "dbpassword",
                maxConnections: 20
            }
        },
        {
            name: "jwt-authentication",
            plugin: new AuthenticationPlugin(),
            enabled: true,
            options: {
                jwtSecret: process.env.JWT_SECRET
            }
        }
    ]
});

Loading Plugins from Files

import { createServer } from "xypriss";
import path from "path";

const server = createServer({
    plugins: [
        {
            name: "my-plugin",
            path: path.join(__dirname, "plugins", "MyPlugin.js"),
            enabled: true,
            options: {
                customOption: "value"
            }
        }
    ]
});

Plugin Lifecycle

1. Loading Phase

  • Plugin files are loaded
  • Plugin classes are instantiated
  • Dependencies are checked

2. Initialization Phase

  • initialize() method is called
  • Plugin sets up its resources
  • Routes and middleware are registered

3. Execution Phase

  • execute() method is called
  • Plugin becomes active
  • Main functionality is available

4. Cleanup Phase

  • cleanup() method is called (if defined)
  • Resources are released
  • Connections are closed

Plugin Management

Plugin Manager API

// Get plugin manager
const pluginManager = server.getPluginManager();

// List loaded plugins
const plugins = pluginManager.getLoadedPlugins();
console.log(plugins);

// Get specific plugin
const authPlugin = pluginManager.getPlugin("jwt-authentication");

// Enable/disable plugin
await pluginManager.enablePlugin("my-plugin");
await pluginManager.disablePlugin("my-plugin");

// Reload plugin
await pluginManager.reloadPlugin("my-plugin");

Hot Plugin Reloading

// Enable hot reloading in development
const server = createServer({
    plugins: [
        {
            name: "my-plugin",
            path: "./plugins/MyPlugin.js",
            hotReload: process.env.NODE_ENV === "development"
        }
    ]
});

Advanced Plugin Features

Plugin Dependencies

export class AdvancedPlugin implements Plugin {
    name = "advanced-plugin";
    version = "1.0.0";
    dependencies = ["database-connection", "jwt-authentication"];
    
    async initialize(context: PluginContext): Promise<void> {
        // This plugin will only load after its dependencies
        const db = (context.app as any).db;
        const auth = (context.app as any).authenticateToken;
        
        if (!db || !auth) {
            throw new Error("Required dependencies not available");
        }
    }
}

Plugin Communication

export class PublisherPlugin implements Plugin {
    name = "publisher";
    version = "1.0.0";
    
    async execute(context: PluginContext): Promise<any> {
        // Emit events for other plugins
        context.server.emit("custom-event", { data: "hello" });
    }
}

export class SubscriberPlugin implements Plugin {
    name = "subscriber";
    version = "1.0.0";
    
    async initialize(context: PluginContext): Promise<void> {
        // Listen for events from other plugins
        context.server.on("custom-event", (data) => {
            console.log("Received event:", data);
        });
    }
}

Plugin Configuration Validation

import Joi from "joi";

export class ValidatedPlugin implements Plugin {
    name = "validated-plugin";
    version = "1.0.0";
    
    private configSchema = Joi.object({
        apiKey: Joi.string().required(),
        timeout: Joi.number().min(1000).default(5000),
        retries: Joi.number().min(0).max(5).default(3)
    });
    
    async initialize(context: PluginContext): Promise<void> {
        // Validate plugin configuration
        const { error, value } = this.configSchema.validate(context.config.options);
        
        if (error) {
            throw new Error(`Plugin configuration error: ${error.message}`);
        }
        
        // Use validated configuration
        const config = value;
        console.log("Plugin configured with:", config);
    }
}

Testing Plugins

Unit Testing

import { MyCustomPlugin } from "../plugins/MyCustomPlugin";

describe("MyCustomPlugin", () => {
    let plugin: MyCustomPlugin;
    let mockContext: any;
    
    beforeEach(() => {
        plugin = new MyCustomPlugin();
        mockContext = {
            app: {
                get: jest.fn(),
                post: jest.fn(),
                use: jest.fn()
            },
            logger: {
                info: jest.fn(),
                error: jest.fn()
            },
            config: {
                options: {}
            }
        };
    });
    
    it("should initialize successfully", async () => {
        await expect(plugin.initialize(mockContext)).resolves.not.toThrow();
        expect(mockContext.logger.info).toHaveBeenCalled();
    });
    
    it("should execute successfully", async () => {
        await plugin.initialize(mockContext);
        const result = await plugin.execute(mockContext);
        expect(result.status).toBe("success");
    });
});

Integration Testing

import { createServer } from "xypriss";
import request from "supertest";
import { MyCustomPlugin } from "../plugins/MyCustomPlugin";

describe("Plugin Integration", () => {
    let server: any;
    
    beforeAll(async () => {
        server = createServer({
            plugins: [
                {
                    name: "my-custom-plugin",
                    plugin: new MyCustomPlugin(),
                    enabled: true
                }
            ]
        });
        
        // Wait for plugins to initialize
        await new Promise(resolve => setTimeout(resolve, 100));
    });
    
    it("should handle plugin routes", async () => {
        const response = await request(server)
            .get("/plugin-route")
            .expect(200);
            
        expect(response.body).toHaveProperty("message");
    });
});

Best Practices

1. Plugin Structure

  • Keep plugins focused on a single responsibility
  • Use clear, descriptive names
  • Include proper version information
  • Document plugin configuration options

2. Error Handling

  • Handle errors gracefully in all plugin methods
  • Provide meaningful error messages
  • Don't crash the entire server on plugin errors

3. Resource Management

  • Clean up resources in the cleanup() method
  • Close database connections, file handles, etc.
  • Remove event listeners and timers

4. Configuration

  • Validate plugin configuration
  • Provide sensible defaults
  • Use environment variables for sensitive data

5. Testing

  • Write unit tests for plugin logic
  • Test plugin integration with XyPriss
  • Test error conditions and edge cases

6. Documentation

  • Document plugin functionality
  • Provide usage examples
  • Document configuration options

This plugin system allows you to extend XyPriss with custom functionality while maintaining clean separation of concerns and easy maintainability.