Plugin System

Complete guide to the plugin system in C15T Backend, including plugin types, lifecycle hooks, context extensions, and best practices.

The C15T Backend plugin system provides a powerful way to extend and customize the functionality of your consent management system. This guide covers everything you need to know about plugins, from basic usage to advanced features.

Overview

Plugins are modular components that can:

  • Extend the context with additional data
  • Modify requests and responses
  • Add new routes and endpoints
  • Provide custom middleware
  • Hook into the request lifecycle

Plugin Types

Core Plugins

Core plugins are essential system components that provide fundamental functionality:

const corePlugin: C15TPlugin = {
  id: 'core-plugin',
  name: 'Core Plugin',
  type: 'core',
  init: () => ({
    context: {
      systemInfo: {
        version: '1.0.0',
        environment: process.env.NODE_ENV,
      },
    },
  }),
};

Feature Plugins

Feature plugins add specific functionality to your system:

const featurePlugin: C15TPlugin = {
  id: 'feature-plugin',
  name: 'Feature Plugin',
  type: 'feature',
  init: () => ({
    routes: [
      {
        path: '/api/custom',
        method: 'GET',
        handler: async (request, ctx) => {
          return new Response(JSON.stringify({ message: 'Custom endpoint' }));
        },
      },
    ],
  }),
};

Middleware Plugins

Middleware plugins process requests and responses:

const middlewarePlugin: C15TPlugin = {
  id: 'middleware-plugin',
  name: 'Middleware Plugin',
  type: 'middleware',
  onRequest: async (request, ctx) => {
    // Add request processing
    return { request };
  },
  onResponse: async (response, ctx) => {
    // Add response processing
    return { response };
  },
};

Plugin Lifecycle

Initialization

Plugins are initialized when the C15T instance is created:

const instance = c15tInstance({
  baseURL: 'http://localhost:3000',
  database: memoryAdapter({}),
  plugins: [
    corePlugin,
    featurePlugin,
    middlewarePlugin,
  ],
});

Request Lifecycle

Plugins can hook into various stages of request processing:

const lifecyclePlugin: C15TPlugin = {
  id: 'lifecycle-plugin',
  name: 'Lifecycle Plugin',
  type: 'middleware',
  onRequest: async (request, ctx) => {
    console.log('Request received');
    return { request };
  },
  onBeforeHandler: async (request, ctx) => {
    console.log('Before handler');
    return { request };
  },
  onAfterHandler: async (response, ctx) => {
    console.log('After handler');
    return { response };
  },
  onResponse: async (response, ctx) => {
    console.log('Response sent');
    return { response };
  },
};

Context Extensions

Plugins can extend the context with additional data:

const contextPlugin: C15TPlugin = {
  id: 'context-plugin',
  name: 'Context Plugin',
  type: 'core',
  init: () => ({
    context: {
      customData: {
        timestamp: Date.now(),
        requestId: generateId(),
      },
    },
  }),
};

Custom Routes

Plugins can add custom routes to your API:

const routePlugin: C15TPlugin = {
  id: 'route-plugin',
  name: 'Route Plugin',
  type: 'feature',
  init: () => ({
    routes: [
      {
        path: '/api/custom',
        method: 'GET',
        handler: async (request, ctx) => {
          const data = await ctx.database.find('custom', {});
          return new Response(JSON.stringify(data));
        },
      },
      {
        path: '/api/custom',
        method: 'POST',
        handler: async (request, ctx) => {
          const data = await request.json();
          const result = await ctx.database.create('custom', data);
          return new Response(JSON.stringify(result));
        },
      },
    ],
  }),
};

Error Handling

Plugins can handle errors at different levels:

const errorPlugin: C15TPlugin = {
  id: 'error-plugin',
  name: 'Error Plugin',
  type: 'middleware',
  onError: async (error, ctx) => {
    console.error('Plugin error:', error);
    return {
      response: new Response(
        JSON.stringify({ error: 'Internal server error' }),
        { status: 500 }
      ),
    };
  },
};

Plugin Dependencies

Plugins can declare dependencies on other plugins:

const dependentPlugin: C15TPlugin = {
  id: 'dependent-plugin',
  name: 'Dependent Plugin',
  type: 'feature',
  dependencies: ['core-plugin', 'auth-plugin'],
  init: (ctx) => {
    // Access dependent plugin data
    const coreData = ctx.plugins['core-plugin'].data;
    const authData = ctx.plugins['auth-plugin'].data;
    
    return {
      context: {
        combinedData: {
          ...coreData,
          ...authData,
        },
      },
    };
  },
};

Best Practices

  1. Plugin Organization

    // plugins/index.ts
    export const plugins: C15TPlugin[] = [
      corePlugin,
      authPlugin,
      loggingPlugin,
      customPlugin,
    ];
  2. Error Handling

    const safePlugin: C15TPlugin = {
      id: 'safe-plugin',
      name: 'Safe Plugin',
      type: 'middleware',
      onRequest: async (request, ctx) => {
        try {
          // Plugin logic
          return { request };
        } catch (error) {
          console.error('Plugin error:', error);
          return { error };
        }
      },
    };
  3. Performance Optimization

    const optimizedPlugin: C15TPlugin = {
      id: 'optimized-plugin',
      name: 'Optimized Plugin',
      type: 'middleware',
      onRequest: async (request, ctx) => {
        // Cache expensive operations
        const cacheKey = request.url;
        const cached = await ctx.cache.get(cacheKey);
        if (cached) {
          return { request, cached };
        }
        
        // Perform operation
        const result = await expensiveOperation();
        await ctx.cache.set(cacheKey, result);
        
        return { request, result };
      },
    };

Testing Plugins

Unit Testing

describe('Custom Plugin', () => {
  it('should extend context', async () => {
    const plugin = customPlugin;
    const ctx = { context: {} };
    
    const result = await plugin.init(ctx);
    expect(result.context).toHaveProperty('customData');
  });
  
  it('should handle requests', async () => {
    const plugin = customPlugin;
    const request = new Request('http://localhost:3000');
    const ctx = { context: {} };
    
    const result = await plugin.onRequest(request, ctx);
    expect(result.request).toBeDefined();
  });
});

Integration Testing

describe('Plugin Integration', () => {
  it('should work with other plugins', async () => {
    const instance = c15tInstance({
      baseURL: 'http://localhost:3000',
      database: memoryAdapter({}),
      plugins: [plugin1, plugin2],
    });
    
    const request = new Request('http://localhost:3000/api/test');
    const response = await instance.handler(request);
    
    expect(response.status).toBe(200);
  });
});

Common Issues

  1. Plugin Order

    • Plugins are executed in the order they are provided
    • Dependencies should be listed first
    • Core plugins should be initialized before feature plugins
  2. Context Access

    • Always check for context existence
    • Use optional chaining for nested properties
    • Provide default values when needed
  3. Error Propagation

    • Handle errors at the appropriate level
    • Log errors for debugging
    • Return appropriate error responses

On this page

c15t.com