IPC/RPC framework that bridges processes across language boundaries. A C++ server and a JavaScript client talk to each other — or any future language — using the same wire protocol: Unix domain sockets + Protocol Buffers, no shared memory, no native bindings.
┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ C++ SDK │ │ JS/TS SDK │ │ Go, Python, … │
│ sdk/cpp/ │ │ sdk/js/ │ │ (your SDK) │
└────────┬─────────┘ └────────┬─────────┘ └───────┬─────────┘
│ │ │
│ Protobuf Envelope over framed IPC │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ Unix Domain Socket / Named Pipe (Windows) │
└──────────────────────────────────────────────────────────────┘
npm install @lambertse/ibridger// server.ts
import { IBridgerServer, typedMethod } from '@lambertse/ibridger';
import { ibridger } from '@lambertse/ibridger';
const server = new IBridgerServer({ endpoint: '/tmp/my.sock' });
server.register('EchoService', {
Echo: typedMethod(
ibridger.examples.EchoRequest,
ibridger.examples.EchoResponse,
async (req) => ({ message: req.message.toUpperCase() }),
),
});
await server.start();// client.ts
import { IBridgerClient } from '@lambertse/ibridger';
import { ibridger } from '@lambertse/ibridger';
const client = new IBridgerClient({ endpoint: '/tmp/my.sock' });
await client.connect();
const resp = await client.call(
'EchoService', 'Echo',
{ message: 'hello' },
ibridger.examples.EchoRequest,
ibridger.examples.EchoResponse,
);
console.log(resp.message); // "HELLO"
client.disconnect();Server — define a service by subclassing ServiceBase, then wire it up with ServerBuilder:
// my_service.h
#include "ibridger/sdk/service_base.h"
#include "my_service.pb.h" // your protobuf-generated types
class GreetService : public ibridger::sdk::ServiceBase {
public:
GreetService() : ServiceBase("GreetService") {
register_method<GreetRequest, GreetResponse>(
"Hello",
[](const GreetRequest& req) {
GreetResponse resp;
resp.set_message("Hello, " + req.name() + "!");
return resp;
});
}
};// server_main.cpp
#include "ibridger/sdk/server_builder.h"
#include "my_service.h"
int main() {
auto server = ibridger::sdk::ServerBuilder()
.set_endpoint("/tmp/my.sock")
.add_service(std::make_shared<GreetService>())
.build();
server->start();
::pause(); // wait for SIGINT
server->stop();
}Client — connect with ClientStub and call with typed proto messages. Pass a ReconnectConfig to auto-reconnect if the server restarts:
#include "ibridger/sdk/client_stub.h"
#include "my_service.pb.h"
int main() {
ibridger::rpc::ClientConfig cfg;
cfg.endpoint = "/tmp/my.sock";
// Optional: reconnect automatically if the server restarts.
ibridger::rpc::ReconnectConfig rc;
rc.base_delay = std::chrono::milliseconds(200);
rc.max_delay = std::chrono::milliseconds(10'000);
rc.on_reconnect = [] { std::cout << "reconnected\n"; };
cfg.reconnect = rc;
// Optional: react to unexpected disconnection.
cfg.on_disconnect = [] { std::cerr << "server lost\n"; };
ibridger::sdk::ClientStub stub(cfg);
stub.connect();
GreetRequest req;
req.set_name("world");
auto [resp, err] = stub.call<GreetRequest, GreetResponse>(
"GreetService", "Hello", req);
if (!err) std::cout << resp.message() << "\n"; // "Hello, world!"
}CMakeLists.txt for a consumer project:
find_package(ibridger CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE ibridger::sdk::cpp)# Build C++
cmake -B build -DIBRIDGER_BUILD_EXAMPLES=ON && cmake --build build
# Terminal 1 — C++ echo server
./build/sdk/cpp/echo_server /tmp/ibridger.sock
# Terminal 2 — JS client (from sdk/js/)
npx ts-node examples/echo-client.ts /tmp/ibridger.sockconst pong = await client.ping();
console.log(pong.serverId, Number(pong.timestampMs));Both SDKs detect server disconnection immediately and expose hooks to react to it.
// Notified when the server dies — isConnected becomes false instantly.
client.onDisconnect = () => console.log('server lost');
// Auto-reconnect: call() blocks with exponential backoff until the server recovers.
const client = new IBridgerClient(
{ endpoint: '/tmp/my.sock' },
{ baseDelayMs: 200, maxDelayMs: 10_000, maxAttempts: Infinity,
onReconnect: () => console.log('reconnected') },
);cfg.on_disconnect = [] { std::cerr << "server lost\n"; };
ibridger::rpc::ReconnectConfig rc;
rc.base_delay = std::chrono::milliseconds(200);
rc.max_delay = std::chrono::milliseconds(10'000);
rc.max_attempts = -1; // -1 = unlimited
rc.on_reconnect = [] { std::cout << "reconnected\n"; };
cfg.reconnect = rc;When reconnect is set, call() / client.call() blocks transparently while the server is down — the caller sees no error as long as the server recovers within the backoff budget. on_disconnect / onDisconnect fires regardless of whether auto-reconnect is enabled.
Retry safety: only the
send()path is retried (server never received the request). Ifrecv()fails the call returns an error — the server may have already processed it.
Four-layer stack (bottom-up):
| Layer | Responsibility | C++ | JavaScript/TypeScript |
|---|---|---|---|
| Transport | Platform IPC | UnixSocketTransport |
UnixSocketConnection |
| Protocol | Framing + serialization | FramedConnection, EnvelopeCodec |
FramedConnection, EnvelopeCodec |
| RPC | Dispatch + correlation | Server, Client |
IBridgerServer, IBridgerClient |
| SDK | Ergonomic public API | ServerBuilder, ClientStub |
same as RPC layer |
Wire format: [4-byte big-endian length][protobuf Envelope], max 16 MB per frame. Documented in docs/WIRE_PROTOCOL.md.
cmake -B build -DIBRIDGER_BUILD_BENCHMARKS=ON
cmake --build build --target ibridger_benchmarks
./build/core/benchmarks/ibridger_benchmarksOutput JSON for CI storage:
./build/core/benchmarks/ibridger_benchmarks \
--benchmark_format=json --benchmark_out=benchmark_results.json| chmark | Payload | Wall time/op | Throughput |
|---|---|---|---|
| PingLatency | — | ~16 μs | ~133 k calls/s |
| EchoLatency | 64 B | ~15 μs | ~130 k calls/s |
| EchoLatency | 1 KB | ~17 μs | ~122 k calls/s |
| EchoLatency | 64 KB | ~140 μs | ~14 k calls/s |
| EchoLatency | 256 KB | ~456 μs | ~4.3 k calls/s |
| SequentialThroughput | 64 B | ~16 μs | ~130 k calls/s |
| ConcurrentThroughput | 64 B, 1 thread | ~16 μs | ~128 k calls/s |
| ConcurrentThroughput | 64 B, 4 threads | ~33 μs | ~60 k calls/s (agg.) |
| ConnectionSetup | — | ~12 μs | ~90 k conn/s |
Numbers from a Debug build; a Release build (
-DCMAKE_BUILD_TYPE=Release) will be significantly faster. Run on your own hardware for accurate figures.
cmake -B build -DIBRIDGER_BUILD_TESTS=ON -DIBRIDGER_BUILD_EXAMPLES=ON
cmake --build build
cd build && ctest --output-on-failure # all C++ tests
cd build && ctest -R <TestName> # single testOn macOS, brew install protobuf is recommended so CMake uses the system library instead of compiling protobuf from source.
cd sdk/js
npm install
npm test # all JS tests
npx jest --testPathPattern=<pattern> # specific test
npm run build # compile TypeScript → dist/cd tests/integration && npm install && npm testRequires the C++ build to be complete (build/sdk/cpp/echo_server).
proto/ibridger/ Wire protocol .proto definitions
core/ C++ transport / protocol / RPC core (ibridger::core)
sdk/cpp/ C++ SDK wrapper — ServerBuilder, ClientStub
sdk/js/ TypeScript SDK — pure Node.js, no native bindings
tests/integration/ Cross-language integration tests (C++ ↔ JS)
docs/ Wire protocol spec, roadmap, adding-a-language guide
Any language that can open a Unix domain socket and encode/decode protobuf messages can implement an SDK — the C++ core is not required. See docs/adding-a-language.md for the four-component checklist and a Go SDK outline.
| Document | Description |
|---|---|
docs/WIRE_PROTOCOL.md |
Authoritative wire format spec |
docs/adding-a-language.md |
Guide for new language SDKs |
docs/ROADMAP.md |
25-phase implementation plan |
| npm package README | JS/TS SDK API reference |
MIT