Skip to content

Example: product build-time integration

Build-time TypeScript emits IR. A topology-free binder fills {$param} later. Node is not required in the production worker.

ts
/**
 * Build-time authoring. A non-Node runtime later binds {$param} values in the
 * generated IR / compiled prompt. It does not reimplement the compiler.
 */
import { serializeGraph, workflow } from "@stepupgaming/comfy-workflows";
import { conditioning, image, latent, loaders, sampling } from "@stepupgaming/comfy-workflows/nodes";

export function buildTemplate() {
  const g = workflow("product-demo");
  const prompt = g.param("prompt", { type: "string" });
  const seed = g.param("seed", { type: "int", default: 42n });
  const ckpt = g.add(loaders.CheckpointLoaderSimple, {
    ckpt_name: "v1-5-pruned-emaonly.safetensors",
  });
  const positive = g.add(conditioning.CLIPTextEncode, { text: prompt, clip: ckpt.CLIP });
  const negative = g.add(conditioning.CLIPTextEncode, { text: "", clip: ckpt.CLIP });
  const empty = g.add(latent.EmptyLatentImage, { width: 512, height: 512, batch_size: 1 });
  const sampled = g.add(sampling.KSampler, {
    model: ckpt.MODEL,
    positive: positive.CONDITIONING,
    negative: negative.CONDITIONING,
    latent_image: empty.LATENT,
    seed,
    steps: 8,
    cfg: 7,
    sampler_name: "euler",
    scheduler: "normal",
    denoise: 1,
  });
  const decoded = g.add(latent.VAEDecode, { samples: sampled.LATENT, vae: ckpt.VAE });
  g.add(image.SaveImage, { images: decoded.IMAGE, filename_prefix: "product" });
  return g.toGraph();
}

export function emitArtifacts(): { ir: string } {
  return { ir: serializeGraph(buildTemplate(), { pretty: true }) };
}
js
/**
 * Tiny non-Node-style binder. It only replaces {$param} placeholders in a
 * compiled prompt template. It does not invent nodes or rewrite topology.
 */
/** @param {any} template @param {Record<string, unknown>} params */
export function bindParams(template, params) {
  const walk = (value) => {
    if (Array.isArray(value)) return value.map(walk);
    if (value && typeof value === "object") {
      const keys = Object.keys(value);
      if (keys.length === 1 && keys[0] === "$param") {
        const name = value.$param;
        if (!(name in params)) {
          throw new Error(`unbound parameter: ${name}`);
        }
        return params[name];
      }
      const out = {};
      for (const [k, v] of Object.entries(value)) out[k] = walk(v);
      return out;
    }
    return value;
  };
  return walk(template);
}

Build-time vs runtime · Case study

Released under the MIT License. Unofficial project. Not affiliated with or endorsed by Comfy Org.