Projekte / yerpc

Die Idee

Man schreibt seine API-Methoden einmal in Rust, und yerpc generiert einen typsicheren TypeScript-Client und eine OpenRPC-Spezifikation gratis dazu. Das #[rpc]-Proc-Macro erledigt die Hauptarbeit:

#[rpc(all_positional, ts_outdir = "ts/generated")]
impl Api {
    /// Send a message and get back the uppercased version.
    async fn shout(&self, msg: String) -> String {
        msg.to_uppercase()
    }
    /// Add two numbers together.
    async fn add(&self, a: f32, b: f32) -> f32 {
        a + b
    }
}

Zur Compile-Zeit generiert das einen TypeScript-Client mit passenden Funktionssignaturen:

// AUTO-GENERATED by yerpc-derive
export class RawClient {
    constructor(private _transport: Transport) {}

    public shout(msg: string): Promise<string> {
        return this._transport.request("shout", [msg]);
    }
    public add(a: number, b: number): Promise<number> {
        return this._transport.request("add", [a, b]);
    }
}

Typen plus async-Funktionen, die intern JSON-RPC aufrufen und ein Promise zurückgeben. Probier es aus - tippe client. um Autocomplete zu sehen:

const result = await client.shout("hello");
// result: "HELLO"

const sum = await client.add(1, 2);
// sum: 3

// Try typing: client.

Server und Client bleiben automatisch synchron: Ändere die Rust-API, und die TypeScript-Typen aktualisieren sich beim nächsten Build. Die generierte OpenRPC-Spezifikation kann auch Client-Generierung in anderen Sprachen (Python, Go, Swift, Java) antreiben - daran wird allerdings noch gearbeitet.

Transport-unabhängig

yerpc selbst ist es egal, wie Nachrichten zwischen Server und Client transportiert werden. Es nimmt JSON entgegen und gibt JSON aus. Den Transport wählt man passend zum Anwendungsfall: WebSocket, stdio, ein In-Process-Channel oder etwas Eigenes. Für Delta Chat bedeutete das deutlich mehr Flexibilität als die alte C-FFI, die in den gleichen Prozess gelinkt werden musste.

Entstehung

Die Idee, Delta Chats C-FFI durch eine JSON-RPC-API zu ersetzen, war bei verschiedenen Team-Meetings diskutiert worden. Ich ergriff die Initiative und baute einen Prototyp, um mehrere Schmerzpunkte zu lösen:

Es gab auch ein Prototyp-Projekt für Delta Chat auf KaiOS, wo der Core als separates natives Binary läuft und eine API über einen lokalen WebSocket brauchte.

Ich konnte damals keine geeignete minimale JSON-RPC-Bibliothek finden (das war um 2020, und das Rust-Ökosystem war kleiner als heute), also baute ich einen Prototyp. Frando extrahierte und bereinigte die JSON-RPC-Protokoll- und Typgenerierungsteile zu yerpc als eigenständige Bibliothek. Wir arbeiteten dann zusammen an der Stabilisierung der Delta Chat JSON-RPC-API und ihrer Integration in das offizielle Core-Projekt. Delta Chat Desktop spricht heute ausschließlich über yerpc mit dem Core - die API umfasst rund 170 Methoden (@deltachat/jsonrpc-client). Da die Typen generiert werden, kann man die API schnell ändern und iterieren, ohne Angst vor Brüchen - der Type-Checker fängt Unstimmigkeiten sofort auf.

Die vollständige Geschichte des Übergangs von C-FFI zu JSON-RPC habe ich in einem Blogpost auf delta.chat beschrieben.