Skip to content

Architecture

This page explains how Transit works under the hood. You do not need to read this to use Transit, but it helps if you are curious or debugging issues.

When you write this code:

const rs = transit.rust("./rust");
const c = transit.c("./c");
const greeting = await rs.greet("World");
const chunk = await c.processChunk({ data: [1, 2] });

Transit does three things:

  1. Scans your Rust/C/C++ files to find functions
  2. Loads the compiled native code
  3. Calls the function directly in-process and returns the result

Here is a simplified diagram of what happens:

Your JavaScript code
|
v
transit.rust("./rust") or transit.c("./c")
|
v
Scanner finds: greet, add (from pub fn or generated headers)
|
v
Loads the compiled native code (index.node or .so file)
|
v
Returns a "proxy" object that looks like your functions
|
v
rs.greet("World") -> calls the actual native function -> returns result

Transit has several parts that work together:

The scanner is a tool written in Rust that reads your source files and finds functions. It uses a technology called “tree-sitter” to understand the structure of your code.

What it does:

  • Reads your .rs, .py, .java, .c, .cpp, .js, .ts files
  • Finds functions that are public
  • Detects special comments like // transit:file
  • Produces a list of discovered functions (called a “manifest”)

Why it is fast: The scanner is written in Rust, which is very fast at reading and parsing files. It also caches results so it does not re-scan files that have not changed.

This is the code you interact with. It provides the transit.rust(), transit.java(), transit.python(), transit.c(), and transit.cpp() functions.

What it does:

  • Creates language handles (the rs, jv, py, c, cpp objects)
  • Manages communication with Rust, Java, Python, C, and C++
  • Handles errors and timeouts
  • Caches language handles so you do not create duplicates

When you call a Rust function, Transit loads a compiled Rust file (called a “native addon”) directly into your JavaScript process. The same bridge is shared for C and C++ functions — RustDevBridge detects the language and loads the appropriate compiled addon.

How it works:

  • Your Rust/C/C++ code is compiled into a .node or .so file
  • Transit loads this file using Node.js’s native addon system
  • Function calls go directly to the native code — no network, no serialization
  • This is very fast (nanosecond-level latency)

What you need:

  • Rust: #[napi] annotated pub fn functions with crate-type = ["cdylib"] in Cargo.toml
  • C: Functions declared via #include <transit_c_glue.gen.h> (generated from your function signatures)
  • C++: Functions declared via #include <transit_cpp_glue.gen.h> (generated from your function signatures)
  • The compiled .node file must be in your project directory

When you call a Python function, Transit starts a Python process in the background and communicates with it over a network connection.

How it works:

  • Transit starts your Python script as a separate process
  • The Python script prints its port number to the console
  • Transit connects to that port using TCP
  • Function calls are sent as binary messages
  • The Python script processes the call and sends back a result

Why a separate process? JavaScript and Python cannot share a process directly. They communicate over a network connection (on your computer only, not the internet).

Why it is fast after the first call: The Python process stays running. Subsequent calls skip the startup time.

Works the same way as the Python bridge — starts a Java process and communicates over TCP.

What you need:

  • Java code compiled with javac
  • A TransitServer class that handles communication
  • Your functions registered with server.registerFunction()
JavaScript -> Loads .node file -> Calls Rust/C/C++ function directly -> Returns result

No network involved. The native code runs inside your JavaScript process. This is the fastest option.

JavaScript -> Sends message to localhost:PORT -> Python/Java processes it -> Sends result back

The message is small (just the function name and arguments as JSON). Communication happens over your computer’s internal network (loopback), which is very fast.

When something goes wrong, Transit gives you useful error messages:

Function not found:

Function "nonexistent" not found in rust. Available: greet, add

Python/Java error:

[python] processData: Division by zero

Process crashed:

Python process exited (code=1, signal=null)

For Python and Java, if the process crashes, Transit automatically restarts it (up to 3 times).

Language pair Latency Why
JavaScript <-> Rust ~nanoseconds Runs in the same process
JavaScript <-> C ~nanoseconds Runs in the same process
JavaScript <-> C++ ~nanoseconds Runs in the same process
JavaScript <-> Python ~milliseconds Network communication
JavaScript <-> Java ~milliseconds Network communication

The first call to Python or Java is slower because it includes process startup time. After that, all calls are fast because the process stays running.

  • Python and Java processes only listen on 127.0.0.1 (your computer only)
  • They reject connections from outside your computer
  • Each request has a unique ID to prevent confusion
  • No sensitive data leaves your machine
  1. You write a new pub fn in Rust (or def in Python, public method in Java, or a C/C++ function with the right signature)
  2. You recompile if needed (Rust: cargo build --release, C/C++: npm run build, Java: javac)
  3. The next time Transit scans your code, it finds the new function
  4. You can call it immediately — no restart needed

In development mode with transit dev, the scanner watches for file changes and updates automatically.