Scripting

RelayCore includes a Deno/V8 runtime for dynamic request and response modification using JavaScript or TypeScript.

Overview

Scripts can intercept and modify flows at various stages:

  • onRequest — Before the request is forwarded
  • onResponse — After the response is received
  • onWebSocket — For WebSocket messages

Basic Script

import { Flow, FlowAction } from "relay:script";

async function onRequest(flow: Flow): Promise<FlowAction> {
  console.log(`Request: ${flow.method} ${flow.url}`);

  // Add custom header
  flow.request.headers["X-Script-Ran"] = "true";

  // Log and continue
  return FlowAction.Continue;
}

async function onResponse(flow: Flow): Promise<FlowAction> {
  console.log(`Response: ${flow.status} for ${flow.url}`);

  // Return mock response for specific URLs
  if (flow.path === "/api/mocked") {
    return FlowAction.Mock({
      status: 200,
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ mocked: true }),
    });
  }

  return FlowAction.Continue;
}

Flow Object

interface Flow {
  id: string;
  timestamp: Date;
  method: string;
  url: string;
  host: string;
  path: string;
  protocol: string;
  status?: number;

  request: {
    headers: Record<string, string>;
    body?: Uint8Array;
    bodyText?: string;
    bodyJson?: unknown;
  };

  response?: {
    headers: Record<string, string>;
    body?: Uint8Array;
    bodyText?: string;
    bodyJson?: unknown;
  };

  duration_ms: number;
  size: number;
}

FlowAction

enum FlowAction {
  Continue = "continue",      // Proceed normally
  Drop = "drop",              // Drop the flow
  Mock = "mock",              // Return mock response
  Intercept = "intercept",    // Pause for inspection
  Redirect = "redirect",      // Redirect to URL
}

Advanced Example

import { Flow, FlowAction } from "relay:script";

// Mock API responses for testing
const mocks = new Map([
  ["/api/users", { users: [{ id: 1, name: "Alice" }] }],
  ["/api/products", { products: [{ id: 1, name: "Widget" }] }],
]);

async function onRequest(flow: Flow): Promise<FlowAction> {
  // Inject auth token if missing
  if (flow.path.startsWith("/api/") && !flow.request.headers["authorization"]) {
    flow.request.headers["authorization"] = "Bearer test-token-123";
  }
  return FlowAction.Continue;
}

async function onResponse(flow: Flow): Promise<FlowAction> {
  if (flow.response && flow.response.bodyJson) {
    // Log all API responses
    console.log(`API Response for ${flow.path}:`, flow.response.bodyJson);

    // Inject metadata into responses
    if (Array.isArray(flow.response.bodyJson)) {
      flow.response.bodyJson._meta = {
        mocked: true,
        timestamp: new Date().toISOString(),
      };
    }
  }
  return FlowAction.Continue;
}

Loading Scripts

# Load a script
relay-core-cli scripts load ./path/to/script.ts

# List loaded scripts
relay-core-cli scripts list

# Unload a script
relay-core-cli scripts unload my-script

Script Configuration

# In config.toml
[scripting]
enabled = true
deno_dir = "~/.relay-core/deno"
auto_reload = true