HyperRoute

Plugins & Coprocessors

HyperRoute provides two extensibility mechanisms: plugins for built-in transformations and coprocessors for external HTTP services that hook into the request lifecycle.


Plugin System

Lifecycle Stages

Plugins hook into a defined request lifecycle:

Request In
  → on_router_request     (auth, rate limit, transform)
  → on_subgraph_request   (per-subgraph headers, logging)
  → [upstream call]
  → on_subgraph_response  (transform, cache)
  → on_router_response    (headers, logging, metrics)
Response Out

Plugin Priority

Plugins execute in priority order within each lifecycle stage:

PriorityUse CaseExample
CriticalMust run first, can blockAuthentication
HighImportant, security-relatedRate limiting, validation
NormalStandard processingTransformation, logging
LowCan run lastAnalytics, feature flags

Built-in Plugins

Logging Plugin

Logs request details for debugging and audit:

plugins:
  - name: logging
    enabled: true
    priority: normal
    config:
      log_headers: true
      log_timing: true

Headers Plugin

Add, remove, or transform headers at the router level:

plugins:
  - name: headers
    enabled: true
    priority: low
    config:
      add:
        X-Router-Version: "2.0.0"
        Strict-Transport-Security: "max-age=31536000"
      remove:
        - X-Powered-By

Custom Plugins

Custom plugins follow the same lifecycle interface. Register them in your configuration:

plugins:
  - name: my-custom-plugin
    enabled: true
    priority: high
    config:
      custom_key: custom_value

Coprocessors

Coprocessors are external HTTP services that HyperRoute calls at specific lifecycle stages. They allow you to run arbitrary logic — authentication, audit logging, header injection — in any language, deployed as separate services.

Architecture

Client → HyperRoute Router → [Coprocessor HTTP Call] → Continue/Break
                ↓
         Subgraph Fetch
                ↓
         [Coprocessor HTTP Call] → Continue/Break
                ↓
         Response to Client

Lifecycle Stages

StageWhenUse Case
router_requestBefore any processingAuthentication, rate limiting
router_responseBefore sending to clientAudit logging, response transform
subgraph_requestBefore each subgraph callPer-subgraph auth, header injection
subgraph_responseAfter each subgraph responseResponse validation, caching
execution_requestBefore query executionQuery cost estimation
execution_responseAfter query executionResult transformation
supergraph_requestBefore supergraph processingEarly request validation

Protocol

HyperRoute sends an HTTP POST to your coprocessor URL with a JSON body:

Request from HyperRoute:

{
  "version": 1,
  "stage": "router_request",
  "router": {
    "request_id": "req-abc-123",
    "headers": {
      "authorization": "Bearer eyJhbGci...",
      "content-type": "application/json"
    },
    "body": "{\"query\": \"{ user { name } }\"}",
    "context": {},
    "operation_name": "GetUser"
  }
}

Response — Continue Processing:

{
  "version": 1,
  "stage": "router_request",
  "control": {
    "action": "continue"
  },
  "router": {
    "headers": {
      "x-user-id": "user-789",
      "x-user-roles": "admin,viewer"
    },
    "context": {
      "authenticated": true,
      "user_id": "user-789"
    }
  }
}

Response — Reject Request:

{
  "version": 1,
  "stage": "router_request",
  "control": {
    "action": "break",
    "status_code": 401,
    "message": "Invalid or expired authentication token"
  }
}

Example: Authentication Coprocessor (Node.js)

const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
app.use(express.json());

app.post('/coprocess', async (req, res) => {
  const { stage, router } = req.body;

  if (stage !== 'router_request') {
    return res.json({
      version: 1, stage,
      control: { action: 'continue' }
    });
  }

  const authHeader = router.headers['authorization'];
  if (!authHeader?.startsWith('Bearer ')) {
    return res.json({
      version: 1, stage,
      control: { action: 'break', status_code: 401, message: 'Missing token' }
    });
  }

  try {
    const decoded = jwt.verify(authHeader.slice(7), process.env.JWT_SECRET);
    return res.json({
      version: 1, stage,
      control: { action: 'continue' },
      router: {
        ...router,
        headers: { ...router.headers, 'x-user-id': decoded.sub },
        context: { ...router.context, user: decoded }
      }
    });
  } catch (err) {
    return res.json({
      version: 1, stage,
      control: { action: 'break', status_code: 401, message: 'Invalid token' }
    });
  }
});

app.listen(8080);

Example: Audit Logging Coprocessor (Python)

from flask import Flask, request, jsonify
from datetime import datetime
import logging, json

app = Flask(__name__)
logger = logging.getLogger('audit')

@app.route('/coprocess', methods=['POST'])
def coprocess():
    data = request.json
    stage = data.get('stage')

    if stage == 'router_response':
        audit_log = {
            'request_id': data.get('router', {}).get('request_id'),
            'operation': data.get('router', {}).get('operation_name'),
            'user_id': data.get('router', {}).get('context', {}).get('user_id'),
            'status_code': data.get('response', {}).get('status_code'),
            'timestamp': datetime.utcnow().isoformat()
        }
        logger.info(json.dumps(audit_log))

    return jsonify({
        'version': 1, 'stage': stage,
        'control': {'action': 'continue'}
    })

if __name__ == '__main__':
    app.run(port=8081)

Coprocessor Configuration

coprocessors:
  # Authentication — must succeed
  - name: auth
    url: http://auth-service:8080/coprocess
    timeout: 5s
    stages:
      - router_request
    fail_open: false    # Reject requests if auth service is down
    max_retries: 2
    retry_delay: 100ms

  # Audit logging — best effort
  - name: audit
    url: http://audit-service:8081/coprocess
    timeout: 2s
    stages:
      - router_response
    fail_open: true     # Don't block requests if audit fails

  # Header injection for specific subgraphs
  - name: header-inject
    url: http://header-service:8082/coprocess
    stages:
      - subgraph_request
    subgraphs:          # Only apply to these subgraphs
      - users
      - billing

Best Practices

  1. Authentication: Use fail_open: false — security cannot be bypassed
  2. Audit/Analytics: Use fail_open: true — don't block requests for non-critical processing
  3. Set appropriate timeouts — don't let slow coprocessors impact latency
  4. Order plugins by criticality: Authentication → Rate Limiting → Transformation → Logging → Analytics
  5. Use coprocessors for cross-cutting concerns — keep business logic in subgraphs

Next Steps