A visual media workflow might need a brief and a reference before it creates an image, then the image before it creates a video. The positions of those nodes on a canvas do not define the dependency order. This Node.js example checks a small JSON graph and produces a plan you can inspect.
1. Write down the dependencies
Give each node a unique ID and list its immediate dependencies. A dependency points to work that must finish before that node starts. Save the following fictional example as workflow.json. The review node depends on the video, which depends on the image.
[
{
"id": "brief",
"dependsOn": []
},
{
"id": "reference",
"dependsOn": []
},
{
"id": "image",
"dependsOn": [
"brief",
"reference"
]
},
{
"id": "video",
"dependsOn": [
"image"
]
},
{
"id": "review",
"dependsOn": [
"video"
]
}
]
2. Validate the graph before planning
The code below first checks for duplicate node IDs, repeated dependencies and references to missing nodes. It builds new maps, so calculating the plan leaves the original JSON unchanged.
Save it as order-workflow.mjs. Nodes with no dependencies enter the ready list first. As each ready node is processed, the planner reduces the remaining dependency count for its children. A child enters the list when that count reaches zero.
export function orderWorkflow(nodes) {
if (!Array.isArray(nodes)) throw new Error("Expected a node array");
const byId = new Map();
const remaining = new Map();
const children = new Map();
for (const node of nodes) {
if (!node || typeof node.id !== "string" || !node.id.trim() ||
!Array.isArray(node.dependsOn) ||
!node.dependsOn.every((id) => typeof id === "string" && id.trim())) {
throw new Error("Each node needs an ID and a dependency list");
}
if (byId.has(node.id)) throw new Error(`Duplicate node: ${node.id}`);
if (new Set(node.dependsOn).size !== node.dependsOn.length) {
throw new Error(`Duplicate dependency in ${node.id}`);
}
byId.set(node.id, node);
remaining.set(node.id, node.dependsOn.length);
children.set(node.id, []);
}
for (const node of nodes) {
for (const parent of node.dependsOn) {
if (!byId.has(parent)) throw new Error(`Missing dependency: ${parent}`);
children.get(parent).push(node.id);
}
}
const ready = nodes.filter((node) => remaining.get(node.id) === 0)
.map((node) => node.id);
for (let i = 0; i < ready.length; i++) {
for (const child of children.get(ready[i])) {
const count = remaining.get(child) - 1;
remaining.set(child, count);
if (count === 0) ready.push(child);
}
}
if (ready.length !== nodes.length) {
const blocked = nodes.filter((node) => remaining.get(node.id) > 0)
.map((node) => node.id);
throw new Error(`Cycle blocks: ${blocked.join(", ")}`);
}
return ready;
}
3. Print the execution order
Save plan.mjs and run node plan.mjs workflow.json. The example prints brief -> reference -> image -> video -> review. The wrapper exits with code 2 on invalid JSON, missing files or graph errors.
import { readFile } from "node:fs/promises";
import { orderWorkflow } from "./order-workflow.mjs";
try {
const file = process.argv[2];
if (!file) throw new Error("Usage: node plan.mjs workflow.json");
const nodes = JSON.parse(await readFile(file, "utf8"));
console.log(orderWorkflow(nodes).join(" -> "));
} catch (error) {
console.error(error.message);
process.exitCode = 2;
}
4. Read a blocked result
If image depends on video while video also depends on image, neither can become ready. The error lists unresolved nodes. It can include descendants of a cycle as well as the nodes inside it, so use the dependency records to locate the loop.
Independent nodes retain input order in the initial ready list. Children are discovered in input order too. Reordering the JSON can change the order among independent nodes while still preserving every dependency.
5. Test branches and cycles
Save test.mjs and run node test.mjs. The diamond-shaped fixture sends one source into two branches and then joins them. The tests also include a self-reference, a cycle with a dependent child and a check that planning did not mutate the fixture.
import assert from "node:assert/strict";
import { orderWorkflow } from "./order-workflow.mjs";
const node = (id, ...dependsOn) => ({ id, dependsOn });
const diamond = [node("a"), node("b", "a"), node("c", "a"), node("d", "b", "c")];
assert.deepEqual(orderWorkflow(diamond), ["a", "b", "c", "d"]);
assert.deepEqual(orderWorkflow([...diamond].reverse()), ["a", "c", "b", "d"]);
assert.deepEqual(orderWorkflow([node("x"), node("y")]), ["x", "y"]);
assert.deepEqual(orderWorkflow([]), []);
assert.throws(() => orderWorkflow([node("a"), node("a")]), /Duplicate node/);
assert.throws(() => orderWorkflow([node("a", "missing")]), /Missing dependency/);
assert.throws(() => orderWorkflow([node("a", "a")]), /Cycle blocks: a/);
assert.throws(() => orderWorkflow([node("a", "b"), node("b", "a")]), /Cycle/);
assert.throws(() => orderWorkflow([node("a", "b"), node("b", "a"), node("c", "b")]),
/Cycle blocks: a, b, c/);
assert.throws(() => orderWorkflow([node("a"), node("b", "a", "a")]),
/Duplicate dependency/);
assert.throws(() => orderWorkflow([{ id: "a", dependsOn: null }]), /dependency list/);
const before = JSON.stringify(diamond);
orderWorkflow(diamond);
assert.equal(JSON.stringify(diamond), before);
console.log("12 checks passed");
6. Use the plan in your own runner
This helper returns one sequential order for every node in the supplied graph. Decide which branch you want to run before passing its nodes and dependencies to the helper. For each real task, record its successful output before proceeding to a child. If the task fails, preserve the result and handle that failure before starting dependent work.
The example ran in Node.js 24.20.0 with synthetic node lists and local files. It checks graph structure and order. No image or video task was executed.
AI assistance was used to draft and edit this article.
Source workflow. PixMind AI Infinite Canvas Guide
Technical reference. JavaScript Map
About the author
- The PixMind team shares practical AI image and video workflows. PixMind brings text-to-image, reference-based creation, editing, and video tools into one workspace. Website: https://www.pixmind.io/
Comments
Join the discussionNo comments yet. Be the first to comment.