openclaw-node (npm) is the bridge between the Rust core and the Node ecosystem. Existing Node apps don’t have to choose between “Rust runtime” and “stays in JS.” They can have both.

What napi-rs actually is

napi-rs is a Rust framework that compiles your Rust functions into a .node native module that Node.js loads like any other addon. It handles the JavaScript ↔ Rust value conversion, generates TypeScript definitions, and produces pre-built binaries on CI so end users never need a Rust toolchain.

The developer experience is roughly: write a Rust function, slap a #[napi] attribute on it, and call it from JavaScript with full types.

#[napi]
pub async fn complete(req: CompletionRequest) -> Result<CompletionResponse> {
    let provider = AnthropicProvider::new(...);
    provider.complete(req.into()).await
        .map(Into::into)
        .map_err(napi_err)
}
// Auto-generated types
import { complete } from "openclaw-node";
const res = await complete({ model: "claude-3-5-sonnet-20241022", messages: [...] });

What openclaw-node exposes

  • ProvidersAnthropicProvider, OpenAIProvider with .complete() and .completeStream().
  • AuthNodeApiKey, CredentialStore (AES-256-GCM, file-backed).
  • EventsNodeEventStore with append, query, project.
  • ValidationvalidateMessage(), validatePath().
  • ConfigloadConfig(), loadDefaultConfig(), validateConfig().
  • SessionsbuildSessionKey().
  • ToolsToolRegistry with register() and execute().

Full TypeScript definitions ship in openclaw-node/index.d.ts. Your IDE knows the types.

A real example

import { AnthropicProvider, CredentialStore } from "openclaw-node";

// Load encrypted credential from disk
const store = new CredentialStore("~/.openclaw/credentials/");
const apiKey = await store.get("anthropic");

// Use it
const provider = new AnthropicProvider(apiKey);

// Non-streaming
const response = await provider.complete({
  model: "claude-3-5-sonnet-20241022",
  messages: [{ role: "user", content: "Hello!" }],
  maxTokens: 1024,
});
console.log(response.content);

// Streaming
provider.completeStream(request, (err, chunk) => {
  if (err) throw err;
  if (chunk.delta) process.stdout.write(chunk.delta);
  if (chunk.done) process.stdout.write("\n");
});

The CredentialStore here is the same AES-256-GCM-backed store the Rust core uses. The Node code never touches plaintext on disk.

Pre-built binaries

openclaw-node publishes platform-specific packages:

  • @openclaw-node/linux-x64-gnu
  • @openclaw-node/linux-arm64-gnu
  • @openclaw-node/darwin-x64
  • @openclaw-node/darwin-arm64
  • @openclaw-node/win32-x64-msvc

The main openclaw-node package depends on the right platform package via optionalDependencies. On install, npm picks exactly one. No node-gyp dance, no C++ toolchain on the install host.

Where this fits

Three concrete use cases:

1. Existing Node app, want the Rust providers

Drop in openclaw-node, swap your provider client. The rest of your app is unchanged. Use the Rust streaming for free.

2. Want the Rust event store from Node

import { NodeEventStore } from "openclaw-node";

const store = new NodeEventStore("./events.db");
await store.append(sessionKey, { kind: "MessageReceived", content: "hi" });
const events = await store.query(sessionKey, { from: 0, to: 100 });

You get sled-backed durability without managing a database from JS.

3. Bridge to a Rust gateway

If you’ve moved your runtime to the Rust gateway (openclaw gateway run), you might still have Node services that need to call into it. openclaw-node can drive JSON-RPC over HTTP/WS from the Node side, sharing types with the Rust core.

The overhead question

napi-rs call overhead is typically in the nanoseconds. For LLM workloads where each call is hundreds of milliseconds of network I/O, the napi boundary cost is rounding error. Where it matters more is in tight allocation loops — and openclaw-node is designed not to have those, since LLM streams and event appends are coarse-grained operations.

When not to use openclaw-node

If you’re starting from scratch and you want a Rust core, just use the Rust crates directly. The Node bindings are for teams that:

  • Have a large existing Node codebase to integrate with.
  • Want the Rust runtime properties without rewriting the orchestration layer.
  • Need to expose openclaw-rs primitives to a non-Rust team incrementally.

For greenfield Rust services, openclaw-core, openclaw-providers, and openclaw-agents are the direct path.