Skip to content

hypermemetic/plexus-rpc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

plexus-rpc

One dependency. Schema-driven RPC for distributed systems in Rust + Haskell.

plexus-rpc is the umbrella crate for the Plexus RPC framework. Add one line to your Cargo.toml and you get the verified-compatible set: dispatch core, procedural macros, sealed identity / credential / tenant / audit primitives, and the WebSocket / HTTP / stdio server runtime.

[dependencies]
plexus-rpc = "0.1"

Define methods in plain Rust. The macro extracts JSON Schema from your signatures and rustdoc, registers handlers, and lets synapse discover the whole surface at runtime. No route tables, no schema files, no codegen step.

Hello, plexus

A working server in one file. Run it; talk to it from the synapse CLI.

// examples/echo.rs
use async_stream::stream;
use futures::Stream;
use plexus_rpc::core::plexus::DynamicHub;
use plexus_rpc::transport::TransportServer;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum EchoEvent {
    Echo { message: String, count: u32 },
}

pub struct Echo;

#[plexus_rpc::macros::activation(
    namespace = "echo",
    version = "1.0.0",
    description = "Echo messages back"
)]
impl Echo {
    /// Echo a message back the specified number of times.
    #[plexus_rpc::macros::method]
    async fn echo(
        &self,
        /// The message to echo
        message: String,
        /// Number of times to repeat
        count: u32,
    ) -> impl Stream<Item = EchoEvent> + Send + 'static {
        stream! {
            for i in 0..count {
                yield EchoEvent::Echo { message: message.clone(), count: i + 1 };
            }
        }
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let hub = Arc::new(DynamicHub::new("echo").register(Echo));

    let rpc_converter = |arc| {
        DynamicHub::arc_into_rpc_module(arc)
            .map_err(|e| anyhow::anyhow!("rpc module: {e}"))
    };

    println!("listening on ws://127.0.0.1:4444");
    TransportServer::builder(hub, rpc_converter)
        .with_websocket(4444)
        .build().await?
        .serve().await
}
$ cargo run --example echo
listening on ws://127.0.0.1:4444

In another terminal:

$ synapse echo echo --message "hello" --count 3
message: hello
count: 1

message: hello
count: 2

message: hello
count: 3

The default synapse registry sits at localhost:4444 — start your backend on that port and the CLI discovers it automatically.

Installing synapse

Synapse is the CLI client that derives commands, help, and validation from your backend's schema. Install it from Hackage:

cabal update
cabal install plexus-synapse

Or build from source:

git clone https://github.com/hypermemetic/plexus-synapse
cd plexus-synapse && cabal install

With the binary on your PATH:

$ synapse                # lists registered backends at localhost:4444
$ synapse echo           # lists the methods on the echo activation
$ synapse --schema echo  # raw JSON Schema for the activation
$ synapse --emit-ir echo > echo.ir.json   # IR for codegen

What's in the box

The umbrella re-exports four crates as namespaced modules:

Re-export Source crate Contents
plexus_rpc::auth_core plexus-auth-core AuthContext, Principal, sealed Credential<T>, Tenant + TenantResolver, ForwardPolicy, AuditRecord + AuditSink, BackendAuthCapabilities
plexus_rpc::core plexus-core DynamicHub, Activation trait, MethodSchema, credential wire envelope, ChildRouter, hub builders (with_auth_capabilities, with_forward_policy)
plexus_rpc::macros plexus-macros #[activation], #[method], #[child], #[derive(Credentials)], #[from_auth]
plexus_rpc::transport plexus-transport (default-on feature transport) TransportServer, WebSocket / HTTP / stdio server runtime

Drop the transport feature when you're building ahead-of-time codegen / embedded / WASM consumers that only need the type and dispatch surface:

plexus-rpc = { version = "0.1", default-features = false }

The doc-comment-first method shape

Doc comments on the function and each parameter feed the JSON Schema description automatically:

#[plexus_rpc::macros::activation(namespace = "calc", version = "1.0.0")]
impl Calc {
    /// Add two integers.
    #[plexus_rpc::macros::method]
    async fn add(
        &self,
        /// Left-hand operand
        a: i64,
        /// Right-hand operand
        b: i64,
    ) -> impl Stream<Item = CalcEvent> + Send + 'static {
        stream! { yield CalcEvent::Result { value: a + b } }
    }
}

If you need to override (e.g. to differ from the rustdoc you want shown to library users), the explicit attribute syntax still works and wins when set:

#[plexus_rpc::macros::method(
    description = "Wire description, separate from the rustdoc",
    params(a = "Override of the param doc")
)]

Auth, credentials, tenancy (one-line summary each)

The umbrella ships the AUTHZ wave-2 primitives. Each is opt-in — your existing backends keep working unchanged.

  • Auth capability advertisementDynamicHub::with_auth_capabilities(...) makes _info describe your supported mechanisms (Bearer / Cookie / OIDC / Anonymous) so generic clients can discover them.
  • Sealed identityAuthContext, Principal, VerifiedUser are sealed at the crate boundary; activation code receives references, never constructs them.
  • Credentials as return values — a method can return Credential<T> from any response struct. The framework intercepts at the serialization boundary, replaces the value with a {"$credential": "<id>"} sentinel, routes the real value through a sidecar, and (with attach_as(cookie = "...")) projects it to a Set-Cookie header. Generated TypeScript/Rust clients auto-store and auto-attach.
  • Tenant isolationTenant is a sealed identity primitive; ClaimTenantResolver derives it from a JWT claim; Tenanted<S> + Scoped<'a, S> wrap your storage in a tenant boundary the type system enforces.
  • Forwarding policyDynamicHub::with_forward_policy(namespace, policy) declares how the caller's identity propagates on cross-boundary calls (IdentityOnly, PassThrough, Anonymous, or your own).
  • AuditAuditRecord + AuditSink trait + TracingAuditSink default sink emits to the plexus::audit tracing target.

Capability manifest

Backends embed plexus_rpc::CAPABILITIES in their _info response so generic clients negotiate features instead of guessing from version strings.

let info = serde_json::json!({
    "backend": "my-backend",
    "capabilities": plexus_rpc::CAPABILITIES,
});

CAPABILITIES.features is a stable list of named flags ("credentials", "forward_policy", "tenant", etc.). Tooling branches on flags, not versions.

Code generation

synapse --emit-ir <backend> produces a structured IR JSON. hub-codegen consumes it to produce typed TypeScript and Rust clients with:

  • Method signatures derived 1:1 from the backend
  • Auto-storage of returned credentials on a SessionRegistry
  • Auto-attach on methods declaring requires_credential
  • Streaming responses surfaced as AsyncIterable (TS) / impl Stream (Rust)
  • /// doc comments threaded into the generated docstrings

See synapse-cc for the orchestrator that wires synapse + hub-codegen into one cargo-style build flow.

Examples

The examples/ directory has a small set of runnable servers:

  • examples/echo.rs — the minimal hello-world above
  • examples/calculator.rs — multi-method activation with doc-comment-derived params
  • examples/credentials.rs — a login method returning Credential<SessionToken> end-to-end

Each binds to 127.0.0.1:4444 by default so the bare synapse invocation finds it.

cargo run --example echo
cargo run --example calculator
cargo run --example credentials

What plexus-rpc is NOT

  • Not a routing framework for HTTP REST. This is JSON-RPC streaming over WebSocket / stdio / MCP HTTP. The optional REST gateway in plexus-transport is for adapting REST callers; the canonical surface is JSON-RPC.
  • Not a code generator for the server side. Server code IS the schema. The codegen story is on the client side (TypeScript, Rust SDK consumers).
  • Not an auth provider. The framework defines the primitives (AuthContext, Principal, Credential<T>, Tenant) and the wire envelopes; you bring your own JWT validator, OIDC client, or whatever — and it plugs in as a SessionValidator implementation.

License

MIT. See LICENSE.

Status

Pre-1.0. APIs are stable in shape but subject to additive change. Each subcrate carries its own changelog; the umbrella's Capabilities.features is the canonical "what shipped in this release" reference.

About

Umbrella crate for Plexus RPC. Schema-driven streaming JSON-RPC for distributed systems in Rust + Haskell.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors