Skip to main content

Running on Client vs Server

A Race Platform plugin is a single TypeScript bundle that runs identically in two runtimes. This page explains how that's guaranteed, what's wired today, and what's coming.

The two runtimes

RuntimeWhereEngineAdapter
Serverapps/api (Node locally / Cloudflare Workers in prod)V8 isolatesNodeRuntime in @race/plugin-sdk
Clientapps/client (Flutter on any platform)QuickJSflutter_js package

Both runtimes consume the same module.exports-shaped bundle.

How parity is enforced

packages/plugin-sdk/tests/parity.test.ts is a vitest suite that:

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

The parity test is part of pnpm test and runs on every change to the SDK or the example plugins. If a divergence creeps in, the test breaks the build.

What's wired today

  • Server adapter: fully wired. The API's POST /plugins/:id/invoke/{math,kpi} routes load the bundle into a NodeRuntime, build a sandboxed PluginHost, call the requested handler, return the typed PluginValue / KpiResult.
  • Client server-fallback adapter: every Flutter platform (web, macOS, iOS, Android, Windows, Linux) currently invokes plugins by POSTing to the server adapter. This is what the Plugin Runner screen uses.
  • Client native adapter (scaffolded): PluginHost is structured so a _NativeAdapter using flutter_js slots in without touching call sites. The wiring needs a deferred import (import 'package:flutter_js/flutter_js.dart' deferred as fjs;) so the web bundle stays clean.

When to prefer which

Client-side (in-browser via QuickJS):

  • Low-latency math functions invoked from CID cells
  • Interactive UI feedback while the user is editing
  • Working offline (when we get offline mode)

Server-side (Node / Workers V8):

  • Heavy KPI processing over telemetry blobs
  • Anything that needs the fetch or storage capability
  • Bulk re-processing historical data

What you can do today

  • Run any plugin server-side and get correct results
  • Run the parity test to confirm bit-identical behaviour
  • See the Plugin Runner's history card with timestamps and results from the server adapter

What's coming

  • Wiring the flutter_js native adapter — once flutter_js's deferred-import pattern lands, web stays server-side and native targets become in-process
  • Auto-routing — given a plugin invocation, choose client-side if it's a pure math fn under N µs, server-side otherwise
  • Background invocation — schedule KPI re-runs as Cloudflare Queue jobs once an attachment finishes uploading