~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs

0
0
main
156 lines · 5.7 KB Raw History

Rust servers

rust/host (crate atlibs-host) runs the same modules, step protocol, compositions and state dir as the JS hosts, with wasmtime. The atlibs CLI (rust/cli) is built on it, and atlibs serve is a ready-made server; this page shows the pieces to embed in your own.

Serve modules without writing a server

atlibs serve examples/rust/compose --port 8791 &
curl -s -X POST localhost:8791/run/lookup -H 'content-type: application/json' -d '{"n":5}'; echo
		"doubled": 10

Sign-in, origins and reverse proxies: Server sessions.

Add the crate

rust/cli/Cargo.toml

[dependencies]
atlibs-host = { path = "../host" }
atlibs-iroh = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs-iroh", optional = true }

atlibs-host has no transport in it; atlibs-iroh is one, from its own repo behind a cargo feature (Transports). Outside this repo, take both by git:

[dependencies]
atlibs-host = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs" }
atlibs-iroh = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs-iroh" }

Build and test everything in rust/:

cargo host        # the CLI, into rust/host/target
cargo host-test   # atlibs-host's and the CLI's tests

Choose who runs act as

SessionConfig is explicit: an app password or a saved OAuth session. Nothing is read from the environment, and no config means signed out.

rust/host/src/host.rs

/// Who a session signs in as: an explicit choice, never read from the environment.
/// No config, no session.
#[derive(Clone, Debug)]
pub enum SessionConfig {
    /// The simple / testing case: an app password at `service` (an entryway or PDS). The password may
    /// be None when the state dir holds this identifier's saved session.
    AppPassword { identifier: String, password: Option<String>, service: String },
    /// A user's OAuth session, saved in the state dir by `atlibs login` or a server's /oauth/callback.
    OAuth { did: String },
}

Run a module

A module's call is HostCall (XRPC with the session) wrapped in ScopeCall when signed in, so the run makes only the calls its own session record's scope allows.

rust/host/src/registry.rs

    let mut call: Arc<dyn Call> = Arc::new(HostCall { fetch: fetch.clone(), session: session.clone(), attest });
    if session.is_some() {
        let key = module.key.clone().unwrap_or_else(|| name.to_string());
        call = Arc::new(ScopeCall { module: key, session: module.scope.clone(), inner: call });
    }

Ctx holds the wasm runner and the records ledger; run_module drives the step loop and returns the same { entry, result, error?, events, calls } JSON as the JS host.

rust/cli/src/run.rs

                let call = self.call_for(module, module.id.as_deref().unwrap_or_default()).await?;
                let ctx = Ctx { runner: Arc::new(Bound { wasm: self.wasm.clone(), bytes: module.bytes.clone(), sha256: module.sha256.clone() }), ledger, now: None };
                let options = Options { load: Some(self.hook_loader(module, pinned.clone())), ..options };
                Ok(run_module(&ctx, module, entry, &with_did, call, options).await)

Compositions go through compose::run_compose the same way, with a call_for per instance; compose::check_specs runs the load checks from module.json fields alone (atlibs compose check).

Serve client metadata

ClientMetadataHandler::from_stage(&stage) takes an atlibs stage --target server stage.json and answers GET /oauth/<session>/client-metadata.json with any http::Request; anything else is None.

rust/host/src/oauth.rs

/// Serves `<path><session>/client-metadata.json` for each record, the client_id being that URL on
/// `origin` (on a loopback Host, the request's origin when `origin` is unset). With `name`,
/// `<path><name>/client-metadata.json` is the composite of all of them.
#[derive(Clone, Debug, Default)]
pub struct ClientMetadataHandler {

rust/cli/src/serve.rs

        if let Some(res) = self.oauth.handle(&req) {

One session per signed-in user

Each request carries its user's session handle; the server opens that user's session and runs with it alone.

rust/cli/src/serve.rs

        // who this run acts as: the request's signed-in user (their session alone), else the server's own (--as / --app-password)
        let host = match handle_of(&req) {
            None => self.host.clone(),
            Some(handle) => {
                let did = self.cli.store().and_then(|store| oauth_client::did_of_handle(store, handle));
                let session = match did {
                    Ok(Some(did)) => self.session(&did).await,
                    Ok(None) => Err("unknown or expired session: sign in again (GET /oauth/login?handle=...)".into()),
                    Err(e) => Err(e),
                };
                match session {
                    Ok(session) => Host { session: Some(session), ..self.host.clone() },
                    Err(e) => return json_reply(401, &json!({ "error": "AuthRequired", "message": e })),
                }
            }
        };

Same results as the JS host

A conformance suite runs the same modules, inputs and call stubs through both hosts and compares results, call logs and ledgers. It runs JS tests against the Rust host too:

node --test rust/host/conformance.test.mjs
node --import ./rust/host/swap.mjs --test javascript/src/run-module/*.test.mjs examples/rust/*/test.mjs javascript/src/compose/compose.test.mjs javascript/src/server/server.test.mjs

Previous: Node servers | Next: Server sessions