Compiler

Compiler

Print an IGraph as a standalone ES module, and migrate from compiler 1.x.

@hlang-org/compiler turns an IGraph into an ES module. It plans the graph with planGraph, builds an ESTree program from the plan and prints it. The module exports run(options).

JavaScript
import { mkdir, copyFile, writeFile } from 'node:fs/promises';
import { compile } from '@hlang-org/compiler';
import { graph } from './lib/graph.mjs';

// Each node code becomes an import from a module you choose.
const { code, imports } = compile(graph, {
  nodes: {
    greeting: { from: './nodes.mjs', name: 'Greeting' },
    uppercase: { from: './nodes.mjs', name: 'Uppercase' },
    print: { from: './nodes.mjs', name: 'Print' },
  },
});
console.log(code);
console.log('imports:', imports.join(', '));

// Write the module next to the node classes and run it.
const dir = new URL('../../node_modules/.cache/hlang-examples/', import.meta.url);
await mkdir(dir, { recursive: true });
await copyFile(new URL('./lib/nodes.mjs', import.meta.url), new URL('nodes.mjs', dir));
await writeFile(new URL('program.mjs', dir), code);
const { run } = await import(new URL('program.mjs', dir).href);
const { dispose } = run();
dispose();

The output below is the printed module, then the module's own output when the example imports and runs it:

Output
// Generated by @hlang-org/compiler. Do not edit.
import {Flow} from "@hlang-org/runtime";
import {Subject} from "rxjs";
import {Greeting as Node0} from "./nodes.mjs";
import {Uppercase as Node1} from "./nodes.mjs";
import {Print as Node2} from "./nodes.mjs";
export function run(options = {}) {
  const flowId = options.flowId ?? "flow-" + Date.now().toString(36);
  const env = options.env ?? "prod";
  const middleware = options.middleware ?? ({});
  const stop = new Subject();
  const stopCallbacks = [];
  const signal = {
    observable: stop.asObservable(),
    onStop: callback => {
      stopCallbacks.push(callback);
    }
  };
  const nodes = {
    a: new Node0({
      nodeId: "a",
      flowId,
      env,
      middleware,
      config: {
        params: {}
      },
      signal
    }),
    b: new Node1({
      nodeId: "b",
      flowId,
      env,
      middleware,
      config: {
        params: {}
      },
      signal
    }),
    c: new Node2({
      nodeId: "c",
      flowId,
      env,
      middleware,
      config: {
        params: {}
      },
      signal
    })
  };
  if (options.onError) {
    const onError = options.onError;
    for (const nodeId of ["a", "b"]) {
      nodes[nodeId].O("@node/error").receive(error => onError({
        nodeId,
        error
      }));
    }
  }
  nodes["a"].O("out").connect(nodes["b"].I("in"));
  nodes["b"].O("out").connect(nodes["c"].I("in"));
  const flow = new Flow();
  flow.run(nodes["a"]);
  return {
    flowId,
    nodes,
    dispose: () => {
      stop.next();
      stop.complete();
      for (const callback of stopCallbacks) {
        callback();
      }
    }
  };
}

imports: @hlang-org/runtime, rxjs, ./nodes.mjs, ./nodes.mjs, ./nodes.mjs
HELLO, HLANG

compile(graph, options)

OptionContents
nodesFor every node code: { from, name? }, the module to import it from and its export name (the default export when name is omitted).
runtimeThe module that exports Flow. Defaults to @hlang-org/runtime.
rxjsThe module that exports Subject. Defaults to rxjs.

It returns { code, imports }: the module source and every module specifier it imports, in order.

The compiled run(options) accepts flowId, env (default 'prod'), middleware and onError, and returns { flowId, nodes, dispose }.

Errors

compile throws a GraphError for an invalid graph, the same as the interpreter, and a CompileError with one of these codes:

CodeMeaning
unresolved-nodeA node code in the graph has no entry in options.nodes.
invalid-importAn export name is not a valid JavaScript identifier.
unserializable-configA node's configuration holds a value that is not JSON (null, boolean, finite number, string, array or plain object), such as a function or a class instance.

Migrating from 1.x

Version 2 is a rewrite. 1.x remains on npm, and ^1.0.0 ranges do not resolve to 2.x.

1.x2.x
The default export build(graph) wrote a project to the temp directory and ran npm install.compile(graph, options) returns { code, imports }. Write and install them yourself.
Node modules came from each node's runtimeResource.Map each code in options.nodes.
The output depended on @hset/xlang-runtime-middleware, lodash and child-process IPC.The output depends only on the runtime, RxJS and your node modules. Pass services as middleware.
The complier export.Removed. Use compile.

The full API is in the compiler reference.

The examples on this page run in CI. Edit this page on GitHub