Skip to content

Architecture

Transit is a polyglot interop system built around three core principles:

  1. No glue layers — functions call each other directly across language boundaries
  2. Zero source changes for basics — the scanner discovers existing public functions automatically
  3. Optimal transport per pair — each language pair uses the fastest available mechanism
┌──────────────────────────────────────────────────────────────┐
│ Your JavaScript Code │
│ │
│ const rs = transit.rust("./rust") │
│ const jv = transit.java("./java") │
│ const py = transit.python("./python") │
│ await rs.processFile(job) │
│ await jv.processSpecialized(result) │
│ await py.processData(transformed) │
└─────────────┬────────────────────────────────┬───────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────────┐
│ transit-scanner │ │ transit-js │
│ (Rust native) │ │ (Public API + Proxy) │
│ │ │ │
│ • tree-sitter parse │◄─────────│ scanDirectorySync() │
│ • manifest output │ │ createLanguageHandle() │
│ • tier detection │ │ FunctionProxy (Proxy) │
└─────────────────────┘ └──────┬──────────────┬───────┘
│ │
┌─────────────┘ └─────────────┐
▼ ▼
┌──────────────────────────────────┐ ┌─────────────────────────────────┐
│ RustDevBridge │ │ JavaDevBridge │
│ │ │ │
│ Loads .node native addon │ │ Manages JVM process lifecycle │
│ Calls #[napi] functions │ │ TCP binary protocol client │
│ In-process, zero-copy │ │ Health checks + auto-restart │
└──────────────────┬───────────────┘ └──────────────┬──────────────────┘
│ │
▼ ▼
┌──────────────────────────────────┐ ┌─────────────────────────────────┐
│ Your Rust Codebase │ │ TransitServer (Java) │
│ (compiled .node addon) │ │ (resident JVM process) │
└──────────────────────────────────┘ └─────────────────────────────────┘

The scanner is written in Rust for performance. It’s compiled to a native addon (.node) and loaded by transit-js at module init.

Capabilities:

  • Fast directory walk with .gitignore awareness (via the ignore crate, same as ripgrep)
  • Tree-sitter parsing for Rust, JavaScript, Python, and Java grammars
  • Text-based fallback for Rust (pub fn) and Java (public methods)
  • Comment marker detection: transit:file (Tier 2) and transit:function (Tier 3)
  • Outputs a JSON manifest: { language, sourceFile, functionName, signature, exportTier }[]

Why Rust: Directory walking + tree-sitter parsing is CPU-bound work where Rust’s speed matters. The scanner runs on every dev startup and will eventually run on file-save in watch mode.

The public API surface. Exports the transit singleton and the FunctionProxy type.

Key classes:

  • Transit — factory that creates and caches language handles
  • RustDevBridge — loads the native addon, dispatches calls
  • JavaDevBridge — manages the JVM process, dispatches calls via TCP
  • PythonDevBridge — manages the Python process, dispatches calls via TCP
  • createLanguageHandle() — builds a Proxy that resolves names against the manifest

A minimal TCP server (TransitServer.java) and example service (TransitService.java). No external dependencies.

  • Binds to 127.0.0.1 on an ephemeral port
  • Prints PORT=<port> to stdout for the parent process to read
  • Handles CALL_REQUEST and HEALTH_PING messages
  • Functions are registered via server.registerFunction(name, fn)

The Node.js client for the Java TCP bridge.

  • JavaProcessManager — manages JVM lifecycle (spawn, port detection, connect, health checks, auto-restart)
  • Binary protocol encoder/decoder
  • Pending call tracking with timeouts

A minimal TCP server (transit_server.py) for Python function hosting. No external dependencies — uses only stdlib (socket, struct, json).

  • Binds to 127.0.0.1 on an ephemeral port
  • Prints PORT=<port> to stdout on startup
  • Handles CALL_REQUEST, HEALTH_PING messages
  • Functions registered via register_function(name, fn)
  • Same binary protocol as Java

The Node.js client for the Python TCP bridge.

  • PythonProcessManager — manages Python process lifecycle (same pattern as JavaProcessManager)
  • Binary protocol encoder/decoder (identical to Java client)
  • Pending call tracking with timeouts

Shared type definitions for the Transit IDL, manifest, and configuration.

Code generation from scanner manifests. Produces typed stubs and glue code.

Generators:

  • generateTypeScript(manifest) — produces transit.gen.ts with typed function interfaces and a createTransit() factory
  • generateJavaGlue(manifest) — produces TransitService.gen.java with function registration stubs
  • generatePythonGlue(manifest) — produces transit_service.gen.py with function stubs and camelCase registrations

Command-line interface for Transit development workflow.

Commands:

  • transit init — Scans source files for transit.rust(), transit.java(), transit.python() calls, detects languages, and writes transit.config.json
  • transit dev — Live development mode with file watching, automatic re-scanning, and function change logging
  • transit build — Build pipeline that orchestrates scan → codegen → compile. Generates typed stubs and compiles Rust/Java code
  • transit start — Production mode with built artifacts, resident processes, and signal forwarding
Pair Mechanism Serialization Latency
JS ↔ Rust In-process native addon N/A (direct memory) ~ns
JS ↔ Java TCP socket, binary protocol Custom binary (10-byte header + payload) ~ms
JS ↔ Python TCP socket, binary protocol Custom binary (10-byte header + payload) ~ms
Rust ↔ Java Socket (planned) Binary ~ms

V8 (JavaScript) and native Rust code can share a process via N-API — this is how Node.js native addons work. A Rust function compiled with napi-rs is loaded directly into the Node.js process, giving true zero-copy interop.

The JVM cannot share a process with V8. Java runs as a separate process, connected via TCP. To avoid the latency of per-request process startup, Transit keeps the JVM alive as a resident process.

See Binary Protocol for the wire format specification.

Key design decisions:

  • Little-endian, fixed 10-byte header (version, type, request ID, payload length)
  • Function names sent as length-prefixed UTF-8 strings
  • Arguments and results are JSON-encoded strings (not binary-encoded — simplicity over raw performance for v0.1)
  • Health checks keep the connection alive and detect crashed processes
  • TCP on loopback only (never exposed to the network)
1. User calls: rs.processGeneral(job)
2. Proxy intercepts property access "processGeneral"
3. flatMap lookup finds the entry in the manifest
4. Proxy returns a function that calls target._bridge.call("processGeneral", [job])
5. RustDevBridge.call() looks up "processGeneral" (or "process_general") in the loaded addon
6. The #[napi] function executes in-process
7. Result is returned directly (no serialization for Rust→JS via N-API)
1. User calls: jv.processSpecialized(data)
2. Proxy intercepts, flatMap lookup succeeds
3. JavaDevBridge.call() triggers doStart() if not started
4. JVM is spawned, PORT=<port> is read from stdout
5. TCP connection is established to 127.0.0.1:<port>
6. Binary protocol: CALL_REQUEST message sent
7. TransitServer dispatches to the registered function
8. Function returns a JSON string
9. CALL_RESPONSE message received by JavaProcessManager
10. Result string is returned to the caller
1. User calls: py.processData(data)
2. Proxy intercepts, flatMap lookup succeeds
3. PythonDevBridge.call() triggers doStart() if not started
4. Python process is spawned, PORT=<port> is read from stdout
5. TCP connection is established to 127.0.0.1:<port>
6. Binary protocol: CALL_REQUEST message sent
7. transit_server.py dispatches to the registered function
8. Function returns a JSON string
9. CALL_RESPONSE message received by PythonProcessManager
10. Result string is returned to the caller
  • transit dev starts a live development server
  • Scanner runs on startup, builds a manifest
  • FunctionProxy resolves names dynamically via Proxy
  • File watcher re-scans directories on changes
  • Rust: loads the .node addon directly
  • Java: spawns a resident JVM process on first call
  • Python: spawns a resident Python process on first call
  • Optimized for iteration speed, not raw throughput
  • transit build runs the scan → codegen → compile pipeline
  • Codegen replaces dynamic Proxy with generated typed stubs (transit.gen.ts)
  • Rust: cargo build --release produces an optimized .node addon
  • Java: javac compiles classes
  • dist/transit-manifest.json records all discovered functions and compilation status
  • transit start loads the manifest and runs in production mode
  • No runtime scanning overhead in production

The scanner skips these directories by default:

  • node_modules, target, __pycache__, .git, dist, build, .next, vendor

Hidden files are included (.gitignore-style hidden files are read).

Rust errors: N-API propagates Rust Result::Err as JavaScript exceptions. The error message from Rust is included in the exception.

Java errors: If a Java function throws an exception, the server catches it and returns a CALL_RESPONSE with STATUS_ERROR and a JSON error message: {"error": "..."}.

Python errors: If a Python function raises an exception, the server catches it and returns a CALL_RESPONSE with STATUS_ERROR and a JSON error message: {"error": "..."}.

Scanner errors: If a file fails to parse, it’s silently skipped. The scanner continues with the rest of the directory.

Bridge errors: If the Java process crashes, it auto-restarts up to 3 times with exponential backoff. In-flight calls are rejected with an error.