WASM SDK
The WASM SDK (@fabric-platform/wasm) is a WebAssembly build of the Rust SDK (fabric-platform on crates.io), published to npm. A single Rust source-of-truth compiled to two artifacts: a native rlib for Rust services, and a wasm-bindgen npm package for browsers, Node, Cloudflare Workers, Deno, and Bun.
Why WASM (and not just the TypeScript SDK)?
Section titled “Why WASM (and not just the TypeScript SDK)?”The TypeScript SDK is the right choice for most browser apps — it’s smaller, faster to load, and exposes every endpoint. Reach for the WASM SDK when you want:
- Type parity across Rust and JavaScript — request/response shapes are defined once, in Rust, and flow out to both sides. No drift.
- Isomorphic Rust — your backend is Rust, and you’d rather ship the same client code into the browser than maintain a parallel hand-written JS wrapper.
- Edge runtime portability — the same
.wasmbinary runs in browsers, Node ≥ 18, Cloudflare Workers, Deno, and Bun with no changes. - First-class SSE streaming — the WASM SDK uses
fetch+ReadableStreamfor Server-Sent Events, which means it can stream authenticated endpoints (unlike browserEventSource, which refuses to attachAuthorizationheaders).
Installation
Section titled “Installation”npm install @fabric-platform/wasm# orpnpm add @fabric-platform/wasm# orbun add @fabric-platform/wasmRequirements: any runtime that supports WebAssembly — modern browsers, Node ≥ 18, Cloudflare Workers, Deno, Bun.
Quick start
Section titled “Quick start”import init, { FabricClient } from "@fabric-platform/wasm";
// Load the .wasm binary (only required in browsers; Node/bundlers skip this)await init();
const client = new FabricClient("https://api.fabric.example", "sk-...");client.setOrganizationId("org_123");
const me = await client.me();console.log(me);const { FabricClient } = require("@fabric-platform/wasm");
const client = new FabricClient("https://api.fabric.example", "sk-...");const me = await client.me();console.log(me);import { FabricClient } from "@fabric-platform/wasm";
export default { async fetch(request, env) { const client = new FabricClient(env.FABRIC_URL, env.FABRIC_API_KEY); const runs = await client.listWorkflows(); return Response.json(runs); },};Run a workflow, stream its events, fetch the output
Section titled “Run a workflow, stream its events, fetch the output”The SDK returns SSE streams as native ReadableStream<SseEvent>, so you can read them with a standard getReader() loop. When the run reaches a terminal state, getRunOutput(runId) gives you the workflow’s final output plus any artifacts it produced (each artifact comes with a signed download URL).
import init, { FabricClient } from "@fabric-platform/wasm";await init();
const client = new FabricClient("https://api.fabric.example", "sk-...");
// 1. Kick off a workflowconst runId = await client.runWorkflow("research/problem-intelligence", { niche: "elderly health",});
// 2. Stream events as the run progressesconst stream = await client.streamWorkflowRun(runId);const reader = stream.getReader();try { while (true) { const { value, done } = await reader.read(); if (done) break; console.log(value.event, value.data); if (value.event === "run.completed" || value.event === "run.failed") break; }} finally { reader.releaseLock();}
// 3. Fetch the final output, artifacts, and per-task timelineconst result = await client.getRunOutput(runId);console.log(result.status); // "completed"console.log(result.variants); // 1 (or N for variant/bundle runs)for (const variant of result.outputs) { console.log(variant.workflow_name, variant.kind, variant.output); for (const a of variant.artifacts) console.log(" →", a.filename, a.download_url);}Variants — N copies of one workflow
Section titled “Variants — N copies of one workflow”runWorkflowWithVariants fans out N parallel runs (1–10) of the same workflow with the same input. The engine spawns one subprocess per variant; the unified outputs array on the run-output response has one entry per variant:
const runId = await client.runWorkflowWithVariants( "video/ai_shorts", { topic: "AI trends" }, 3,);
// Wait for completion (poll, stream, or use the convenience wrapper):const result = await client.runWorkflowAndGetOutput( "video/ai_shorts", { topic: "AI trends", variants: 3 }, // variants can also live in input 3600, // signed URL TTL);result.outputs.length; // 3result.outputs[0].variant_index; // 0Bundle — N different workflows in parallel
Section titled “Bundle — N different workflows in parallel”A bundle runs N independent sub-workflows in parallel from a single submission. Each entry’s output is tagged with its sub-workflow_name and (when the workflow declared one) kind:
const runId = await client.runBundle([ { workflow: "video/ai_shorts", input: { topic: "AI trends" } }, { workflow: "image/generate-post", input: { topic: "AI trends", slides: 6 } }, { workflow: "content/threads-post", input: { topic: "AI trends" } },]);
// Then either stream events as above, or fetch the final shape:const result = await client.getRunOutput(runId);for (const variant of result.outputs) { // variant.kind tells you which card layout to render}bundle and variants are mutually exclusive — bundle is “N different workflows”, variants is “N copies of one”. The server returns 400 if both are set.
Workflow schemas
Section titled “Workflow schemas”Workflows that declare Pydantic types have their input/output schemas automatically discovered at server boot and stored in the registry. The SDK exposes them via getWorkflowSchemas:
const schemas = await client.getWorkflowSchemas("research/problem-intelligence");console.log(schemas.input_schema); // JSON Schema for what the workflow expectsconsole.log(schemas.output_schema); // JSON Schema for what it returnsconsole.log(schemas.task_schemas); // per-task breakdown (array)console.log(schemas.warnings); // e.g. "task X has no Pydantic types"The demo’s input editor uses input_schema to auto-populate the JSON textarea with a stub matching the workflow’s expected shape. The output panel shows output_schema as a collapsible “expected shape” section.
Server-side input validation
Section titled “Server-side input validation”Pass validate = true when submitting a run to have the server validate your input against the workflow’s input_schema before enqueuing:
// Will return 400 with per-field errors if validation failsconst runId = await client.runWorkflow("research/trends", input, true);Workflows without a schema are unaffected — the validation step is a no-op.
Wrapped surface
Section titled “Wrapped surface”The WASM facade currently wraps 21 of the ~60 methods on fabric_sdk::FabricClient. The streaming methods and core workflow APIs are all in:
- System:
health() - Identity:
me() - Orgs:
listOrgs(),createOrg(slug, name),setOrganizationId(id) - API keys:
listApiKeys() - Workflows:
listWorkflows(),upsertWorkflow(name, body),runWorkflow(id, context, validate?),runWorkflowWithVariants(id, context, variants),runBundle(entries),runWorkflowAndGetOutput(id, context, expiresInSecs?),getWorkflowSchemas(name) - Runs:
getRun(id),getRunOutput(id, expiresInSecs?),cancelRun(id) - SSE streams:
streamWorkflowRun(id, includeInternal?),streamJob(id, includeInternal?),streamEvents(includeInternal?),streamProviderExecute(body)
The remaining 42 methods (auth, teams, invitations, webhooks, schedules, usage, audit, secrets, providers, convenience workflows like face_swap/motion_transfer) are tracked in sdks/rust-wasm/MISSING_METHODS.md and wrapped incrementally. Wrapping a new method is mechanical — copy the pattern from runWorkflow in sdks/rust-wasm/src/lib.rs.
Until a method is wrapped, you can still call it from Rust code that depends on fabric-platform and is compiled to wasm yourself.
How it works
Section titled “How it works”Under the hood:
sdks/rust/is the native Rust SDK (fabric-platformon crates.io). It usesreqwest, which natively targetswasm32-unknown-unknownvia the browserfetchAPI.- Two small patches gate
tokio::timeandstd::time::Instantbehindcfg(not(target_arch = "wasm32"))and swap ingloo-timers+web-timeon wasm. Native behaviour is unchanged. sdks/rust-wasm/is a thin#[wasm_bindgen]facade crate that depends onfabric-platformand exposes a JS-ergonomic API. SSE streams flow throughwasm-streamsas native JSReadableStreamvalues.- CI builds three wasm-pack targets (
web,nodejs,bundler) and publishes them as a single npm package with anexportsmap so every runtime loads the right flavour automatically.
Bundle size
Section titled “Bundle size”Release builds with wasm-opt -Oz produce a .wasm binary in the low hundreds of kilobytes — orders of magnitude smaller than PGlite (which ships all of Postgres) and comparable to libSQL’s HTTP-only build. The SDK is a thin REST/SSE wrapper with no embedded database or runtime.
Source
Section titled “Source”- Rust SDK:
sdks/rust/ - WASM facade:
sdks/rust-wasm/ - Plan:
specs/plans/032-wasm-sdk.md