Skip to main content

Plugin Host

How Race Platform actually executes a plugin's TypeScript bundle in two completely different runtimes — and keeps the outputs identical.

Two runtimes, one bundle

RuntimeEngineWhere
flutter_js (QuickJS)QuickJSFlutter client (in-process)
NodeRuntime (V8)V8 isolatesAPI server (Node locally, Cloudflare Workers in prod)

Both consume the same module.exports-shaped bundle produced by the SDK's esbuild wrapper.

The PluginHost abstraction

The host is a small object the plugin can call:

interface PluginHost {
log(...args: unknown[]): void;
hasCapability(name: string): boolean;
fetch?(req: FetchRequest): Promise<FetchResponse>; // if granted
storage?: { // if granted
get(key: string): Promise<string | null>;
put(key: string, value: string): Promise<void>;
list(prefix?: string): Promise<string[]>;
delete(key: string): Promise<void>;
};
}

The host is constructed per invocation based on the plugin's declared capabilities and the account's capability_grants rows. A plugin that asked for fetch but didn't get it will see host.fetch === undefined.

Server: NodeRuntime

@race/plugin-sdk ships NodeRuntime, the implementation used by apps/api:

  1. Loads the bundle into a fresh V8 isolate
  2. Injects module, exports, and a sandboxed host
  3. Evaluates the bundle (CommonJS evaluation)
  4. Reads module.exports.math, kpiProcessor, etc.
  5. Calls the requested handler with typed args
  6. Returns the typed result

The same NodeRuntime is used by the parity tests, so production code paths are exactly what the tests exercise.

Client: flutter_js adapter (scaffolded)

apps/client/lib/src/plugins/plugin_host.dart wires:

  • A _ServerAdapter (default for v0.1.0) that POSTs to /plugins/:id/invoke/{math,kpi}
  • A _NativeAdapter (scaffolded) that loads the bundle into a QuickJS context via flutter_js

The native adapter is structured to slot in without touching call sites — the wiring needs a deferred import so the web bundle stays clean:

import 'package:flutter_js/flutter_js.dart' deferred as fjs;

Parity

packages/plugin-sdk/tests/parity.test.ts:

  1. Loads rolling-avg-rpm's bundle
  2. Runs it under NodeRuntime with a synthetic lap
  3. Runs it under a QuickJS shim with the same input
  4. Asserts the outputs are bit-identical

The test is part of pnpm test and breaks the build on divergence.

What you can do today

  • Invoke any plugin's math or kpiProcessor interface via the Plugin Runner or the REST API
  • See parity-tested behaviour across V8 (server) and QuickJS (test shim)
  • Use host.log() and host.hasCapability() from any plugin

What's coming

  • Wire flutter_js natively for native platforms
  • Inject host.fetch and host.storage server-side
  • Add the other four interfaces (runPlan, uiCell, transform, liveHook) to the host's switch
  • Move plugin invocation to Cloudflare Queues for KPI jobs triggered by attachment uploads
  • Plugin cold-start optimisations — keep recently-used bundles warm in the isolate pool