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.xJson({
          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.xJson({ 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.