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
| Runtime | Engine | Where |
|---|---|---|
flutter_js (QuickJS) | QuickJS | Flutter client (in-process) |
NodeRuntime (V8) | V8 isolates | API 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:
- Loads the bundle into a fresh V8 isolate
- Injects
module,exports, and a sandboxedhost - Evaluates the bundle (CommonJS evaluation)
- Reads
module.exports.math,kpiProcessor, etc. - Calls the requested handler with typed args
- 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 viaflutter_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:
- Loads
rolling-avg-rpm's bundle - Runs it under
NodeRuntimewith a synthetic lap - Runs it under a QuickJS shim with the same input
- 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
mathorkpiProcessorinterface via the Plugin Runner or the REST API - See parity-tested behaviour across V8 (server) and QuickJS (test shim)
- Use
host.log()andhost.hasCapability()from any plugin
What's coming
- Wire
flutter_jsnatively for native platforms - Inject
host.fetchandhost.storageserver-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