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);
}