WASM Workflows

Run Blazen workflows entirely in WebAssembly

Overview

Blazen workflows run natively inside the WASM module. Steps, events, and the context store all execute locally — no server round-trips for orchestration. Step handlers are plain JavaScript async functions that the WASM runtime calls back into.

Creating a workflow

import init, { Workflow, ChatMessage, CompletionModel } from '@blazen/sdk';

await init();

const wf = new Workflow('summarizer');

wf.addStep('fetch_text', ['blazen::StartEvent'], async (event, ctx) => {
  ctx.set('source', event.url);
  const text = await fetch(event.url).then(r => r.text());
  return { type: 'SummarizeEvent', text };
});

wf.addStep('summarize', ['SummarizeEvent'], async (event, ctx) => {
  const model = CompletionModel.openrouter(event.apiKey);
  const response = await model.complete([
    ChatMessage.system('Summarize the following text in 2-3 sentences.'),
    ChatMessage.user(event.text),
  ]);
  return {
    type: 'blazen::StopEvent',
    result: { summary: response.content },
  };
});

const result = await wf.run({ url: 'https://example.com/article', apiKey: 'your-key' });
console.log(result.data.summary);

Event-driven architecture

Events are plain objects with a type field. The WASM event router dispatches each event to the step whose eventTypes list includes that type — identical to the Rust and Node.js SDKs.

Built-in event types:

  • "blazen::StartEvent" — emitted when the workflow begins. The object passed to wf.run() is merged onto it.
  • "blazen::StopEvent" — returning this from a step ends the workflow. Attach your output to the result property.

Context

The ctx parameter is a WasmContext instance — a real object with methods for sharing state, emitting events, and inspecting the current run. All methods are synchronous (unlike the Node.js SDK, which is async).

Storing and retrieving values

wf.addStep('store', ['blazen::StartEvent'], async (event, ctx) => {
  ctx.set('user', event.name);
  ctx.set('count', 42);
  ctx.set('tags', ['intro', 'demo']);
  return { type: 'NextEvent' };
});

wf.addStep('read', ['NextEvent'], async (event, ctx) => {
  const user = ctx.get('user');   // 'Alice'
  const count = ctx.get('count'); // 42
  const missing = ctx.get('nope'); // null
  return { type: 'blazen::StopEvent', result: { user, count } };
});

ctx.set(key, value) auto-detects Uint8Array values and stores them as binary. Everything else is stored as-is. ctx.get(key) returns the original value, or null if the key is missing.

Values can be any StateValue:

type StateValue = string | number | boolean | null | Uint8Array | StateValue[] | { [key: string]: StateValue };

Binary data

For explicit binary storage, use ctx.setBytes() and ctx.getBytes():

wf.addStep('binary', ['blazen::StartEvent'], async (event, ctx) => {
  const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);

  // Either approach works for storing binary:
  ctx.set('via_set', data);          // auto-detected as binary
  ctx.setBytes('via_explicit', data); // explicit binary storage

  // Both return Uint8Array on read:
  const a = ctx.get('via_set');          // Uint8Array
  const b = ctx.getBytes('via_explicit'); // Uint8Array | null

  return { type: 'blazen::StopEvent', result: { ok: true } };
});

Emitting events

ctx.sendEvent(event) queues an event into the workflow’s event loop, allowing a step to trigger other steps mid-execution:

wf.addStep('kickoff', ['blazen::StartEvent'], async (event, ctx) => {
  ctx.sendEvent({ type: 'SideTask', payload: 'extra work' });
  return { type: 'MainTask' };
});

Run ID

Each workflow run is assigned a unique UUID v4. Access it with ctx.runId():

wf.addStep('log', ['blazen::StartEvent'], async (event, ctx) => {
  console.log('Run:', ctx.runId());
  return { type: 'blazen::StopEvent', result: {} };
});

Workflow name

The workflow name is available as a getter property:

console.log(ctx.workflowName); // 'summarizer'

Method summary

MethodReturn typeDescription
ctx.set(key, value)voidStore a value; auto-detects Uint8Array for binary
ctx.get(key)StateValue | nullRetrieve a value, or null if missing
ctx.setBytes(key, data)voidExplicitly store binary data (Uint8Array)
ctx.getBytes(key)Uint8Array | nullRetrieve binary data
ctx.sendEvent(event)voidQueue an event into the workflow event loop
ctx.writeEventToStream(event)voidNo-op in WASM (present for API compatibility)
ctx.runId()stringUnique UUID v4 for the current run
ctx.workflowNamestringGetter property for the workflow name

Streaming events

In the WASM SDK, ctx.writeEventToStream() is a no-op — it exists for API compatibility with the Node.js and Rust SDKs but does not emit events to an external stream. You can still use wf.runStreaming() to receive events routed via ctx.sendEvent():

wf.addStep('process', ['blazen::StartEvent'], async (event, ctx) => {
  for (let i = 0; i < 5; i++) {
    ctx.sendEvent({ type: 'Progress', step: i });
  }
  return { type: 'blazen::StopEvent', result: { done: true } };
});

const result = await wf.runStreaming({}, (event) => {
  console.log(`Progress: step ${event.step}`);
});

Branching

Return an array of events to fan out to multiple steps:

wf.addStep('classify', ['blazen::StartEvent'], async (event, ctx) => {
  return [
    { type: 'PositiveEvent', text: event.text },
    { type: 'NegativeEvent', text: event.text },
  ];
});

Timeouts

Set a maximum execution time with setTimeout():

wf.setTimeout(30); // 30 seconds

Next steps

  • Add tool-calling agents to your workflows with the WASM Agent guide.
  • Deploy workflows to the edge with the Edge Deployment guide.