Build·JavaScript SDK

Build

Build with the JavaScript SDK

The JavaScript SDK lets you parse, identify, and process Blue documents from your own code. You are not calling isolated features — you are loading documents, computing BlueIds, building timeline entries, and processing them through the official Blue document processor.

What you are doing with the SDK

An important mental shift: you are working with Blue documents and interactions, not just "resources" in the generic SaaS sense. Common SDK actions:

  • parse YAML or JSON into typed Blue nodes
  • compute the canonical BlueId of a node
  • initialize a document and run the lifecycle marker
  • process timeline entries and observe state, gas, and triggered events
  • build new entries with a hash-linked prevEntry.blueId

Install

bash
npm install @blue-labs/language @blue-labs/document-processor @blue-repository/types

The processor pulls in a QuickJS WebAssembly runtime so it can execute deterministic JavaScript inside workflow steps. That makes browser bundling non-trivial; the SDK is most ergonomic when run in Node.js (a serverless function, an API route, a worker, a CLI).

Parse a document and compute its BlueId

javascript
import { Blue, BlueIdCalculator } from "@blue-labs/language";
import { createDefaultMergingProcessor } from "@blue-labs/document-processor";
import { repository } from "@blue-repository/types";

const blue = new Blue({
  repositories: [repository],
  mergingProcessor: createDefaultMergingProcessor(),
});

const node = blue.yamlToNode(`
name: Demo Counter
counter: 0
`);

const blueId = await BlueIdCalculator.calculateBlueId(node);
// e.g. "67WudUZqbHHoVkDhWiX1r7NQtP8igMCan2kQ2qDSGDDZ"

Process a Counter end-to-end

javascript
import { Blue } from "@blue-labs/language";
import {
  DocumentProcessor,
  ContractProcessorRegistryBuilder,
  createDefaultMergingProcessor,
} from "@blue-labs/document-processor";
import { repository } from "@blue-repository/types";

const blue = new Blue({
  repositories: [repository],
  mergingProcessor: createDefaultMergingProcessor(),
});

const registry = ContractProcessorRegistryBuilder.create()
  .registerDefaults()
  .build();

const processor = new DocumentProcessor({ blue, registry });

const document = blue.yamlToNode(`
name: Demo Counter
counter: 0
contracts:
  ownerChannel:
    type: Conversation/Timeline Channel
    timelineId: owner-1
  increment:
    type: Conversation/Operation
    channel: ownerChannel
    request:
      type: Integer
  incrementHandler:
    type: Conversation/Sequential Workflow Operation
    operation: increment
    steps:
      - name: ApplyIncrement
        type: Conversation/Update Document
        changeset:
          - op: replace
            path: /counter
            val: ${event.message.request + document('/counter')}
`);

const init = await processor.initializeDocument(document);
// init.document, init.totalGas, init.triggeredEvents, init.capabilityFailure

const event = blue.jsonValueToNode({
  type: "Conversation/Timeline Entry",
  timeline: { timelineId: "owner-1" },
  message: {
    type: "Conversation/Operation Request",
    operation: "increment",
    request: 5,
    allowNewerVersion: true,
  },
});

const result = await processor.processDocument(init.document, event);
// result.document    → the next document state
// result.totalGas    → gas consumed by this processing pass
// result.triggeredEvents → events emitted during processing (often empty)
// result.capabilityFailure / result.failureReason → set when a contract is unsupported

What the result tells you

  • Next state. result.document is the new BlueNode. Use blue.nodeToJson(...) to serialize it.
  • Gas. result.totalGas is a real number — what the processor actually consumed. The document stepper shows it directly. See Gas and JS processing.
  • Triggered events. Workflow steps may emit events that other contracts can react to.
  • Capability failure. If a contract type is not understood by the registry, the result is returned with capabilityFailure: true and a failureReason. The processor does not pretend it can handle what it cannot.

Building hash-linked timeline entries

If you are stepping a document yourself, set prevEntry.blueId on each new entry to the BlueId of the previous entry on the same timeline:

javascript
const previousEntryNode = blue.jsonValueToNode(previousEntryJson);
const previousBlueId = await BlueIdCalculator.calculateBlueId(previousEntryNode);

const nextEntry = {
  type: "Conversation/Timeline Entry",
  timeline: { timelineId: "owner-1" },
  prevEntry: { blueId: previousBlueId },
  message: { /* ... */ },
};
The tutorial keeps reinforcing: you are processing real Blue documents — not calling generic REST endpoints.

Where this powers the rest of the docs

The document stepper uses these exact packages. Open it, load Counter, and you are watching the same code paths run on the same processor.