Runtime
Transport, message parsing, and routing a protocol call to its implementation all have to happen at run time. Comline provides a runtime that does this, so each project does not reinvent it.
Per-language runtimes
Each language has its own runtime, a normal dependency built and shipped like
any other library: comline-runtime for Rust, @comline/runtime for
TypeScript. Generated code calls into it; you rarely do.
flowchart TB
H["your handlers and calls"] --> GC["generated code<br/>types, senders, dispatch"]
GC -->|Rust| RS["comline-runtime"]
GC -->|TypeScript| TS["@comline/runtime"]
TS -->|"WebAssembly,<br/>through the C ABI"| RS
RS --> TR["transport<br/>TCP, in-memory, yours"]
TS --> TR
The protocol logic that has to be byte-identical everywhere (framing, request
ids, the handshake's frames) is written once, in Rust: comline-runtime is
the shared runtime core, and
its C ABI exposes it to other languages. The
TypeScript runtime loads it as WebAssembly for its built-in framings, and falls
back to its own TypeScript implementation where the core can't load (a strict
content security policy, say) or for a framing of your own; both paths behave
the same and are tested the same. Codecs, generated types and transports stay
in each language.
Every runtime is checked against the same conformance vectors, so a Rust peer and a TypeScript peer generated from one schema speak the same bytes.
See Language Guides for the practical, per-language next step: spawning a client, serving a protocol, and the concrete framing and wire-format API. This page stays the conceptual overview.