From 8ca1b9ba15f5380d2eb5e88ac2571ea9e89a5474 Mon Sep 17 00:00:00 2001 From: Thuc Pham <51660321+thucpn@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:45:44 +0700 Subject: [PATCH] feat: viz-node (#187) --- .changeset/metal-facts-guess.md | 6 + demo/visualization/README.md | 81 ++++- demo/visualization/index.html | 2 +- demo/visualization/package.json | 4 +- demo/visualization/src/main.ts | 9 - demo/visualization/src/viz-browser.ts | 15 + demo/visualization/src/viz-node.ts | 17 + demo/visualization/src/workflow.ts | 52 ++- packages/graph/biome.json | 7 + packages/graph/package.json | 58 ++++ packages/{viz => graph}/src/graph.ts | 0 packages/graph/src/index.ts | 1 + packages/{viz => graph}/src/parser.ts | 0 .../{viz => graph}/src/tests/graph.test.ts | 0 packages/{viz => graph}/src/types.ts | 0 packages/graph/tsconfig.json | 11 + packages/graph/vitest.config.ts | 9 + packages/viz-node/biome.json | 7 + packages/viz-node/package.json | 63 ++++ packages/viz-node/src/canvas.ts | 22 ++ packages/viz-node/src/drawing.ts | 136 ++++++++ packages/viz-node/src/index.ts | 5 + packages/viz-node/src/tests/canvas.test.ts | 52 +++ packages/viz-node/tsconfig.json | 11 + packages/viz-node/vitest.config.ts | 9 + packages/viz/package.json | 5 +- packages/viz/src/drawing.ts | 2 +- pnpm-lock.yaml | 310 +++++++++++++++++- pnpm-workspace.yaml | 1 + tsconfig.json | 6 + 30 files changed, 840 insertions(+), 61 deletions(-) create mode 100644 .changeset/metal-facts-guess.md delete mode 100644 demo/visualization/src/main.ts create mode 100644 demo/visualization/src/viz-browser.ts create mode 100644 demo/visualization/src/viz-node.ts create mode 100644 packages/graph/biome.json create mode 100644 packages/graph/package.json rename packages/{viz => graph}/src/graph.ts (100%) create mode 100644 packages/graph/src/index.ts rename packages/{viz => graph}/src/parser.ts (100%) rename packages/{viz => graph}/src/tests/graph.test.ts (100%) rename packages/{viz => graph}/src/types.ts (100%) create mode 100644 packages/graph/tsconfig.json create mode 100644 packages/graph/vitest.config.ts create mode 100644 packages/viz-node/biome.json create mode 100644 packages/viz-node/package.json create mode 100644 packages/viz-node/src/canvas.ts create mode 100644 packages/viz-node/src/drawing.ts create mode 100644 packages/viz-node/src/index.ts create mode 100644 packages/viz-node/src/tests/canvas.test.ts create mode 100644 packages/viz-node/tsconfig.json create mode 100644 packages/viz-node/vitest.config.ts diff --git a/.changeset/metal-facts-guess.md b/.changeset/metal-facts-guess.md new file mode 100644 index 0000000..6749e1b --- /dev/null +++ b/.changeset/metal-facts-guess.md @@ -0,0 +1,6 @@ +--- +"@llamaindex/workflow-viz-node": patch +"@llamaindex/workflow-viz": patch +--- + +Generate workflow visualization in Node.js diff --git a/demo/visualization/README.md b/demo/visualization/README.md index d3f1306..2fc9d28 100644 --- a/demo/visualization/README.md +++ b/demo/visualization/README.md @@ -1,13 +1,16 @@ -### Workflow Visualization Demo +# Workflow Visualization Demo -A minimal Vite + TypeScript + React demo that visualizes an example `@llamaindex/workflow-core` graph using `@llamaindex/workflow-viz` and Sigma.js. +This demo showcases two different ways to visualize `@llamaindex/workflow-core` graphs: -### Prerequisites +1. **Browser Visualization**: Interactive web-based graph using Sigma.js +2. **Node.js Image Generation**: Generate static PNG images using node-canvas + +## Prerequisites - **Node.js**: v20 or newer - **npm** (bundled with Node.js) -### Install +## Installation From the repo root: @@ -16,6 +19,12 @@ cd demo/visualization npm install ``` +--- + +## 🌐 Browser Visualization + +Interactive web-based workflow visualization using Sigma.js for real-time graph rendering. + ### Run in development ```bash @@ -38,13 +47,67 @@ npm run preview Open the preview server at [http://localhost:4173](http://localhost:4173) (or the port shown in the terminal). -### What this demo does +### What it does -- Defines a small workflow in `src/workflow.ts` using `createWorkflow` and wraps it with `withDrawing` to add drawing capabilities. -- Use `draw` method of the workflow to render it in a HTML container in `src/main.ts` using force-directed layout. +- Defines a workflow in `src/workflow.ts` using `createWorkflow` and wraps it with `withDrawing` to add drawing capabilities +- Uses the `draw` method to render the workflow in an HTML container using force-directed layout +- Provides interactive graph navigation, zoom, and pan capabilities ### Key files -- `src/workflow.ts`: Example workflow (events, handlers) - adding drawing capabilities -- `src/main.ts`: Renders the workflow in a HTML container using force-directed layout +- `src/workflow.ts`: Example workflow (events, handlers) with drawing capabilities +- `src/viz-browser.ts`: Browser visualization entry point +- `src/style.css`: Styling for the visualization +- `index.html`: HTML container for the visualization - `vite.config.ts`: Vite config with React SWC plugin + +--- + +## 🖼️ Node.js Image Generation + +Generate static PNG images of workflow graphs using node-canvas for documentation, reports, or automated workflows. + +### Generate Graph as PNG image + +```bash +npm run generate +``` + +This will create `workflow.png` in the current directory. + +> **Note:** This uses `node-canvas`, which requires native dependencies. +> +> **On macOS:** +> +> ```bash +> brew install pkg-config cairo pango libpng jpeg giflib librsvg +> ``` +> +> **On Ubuntu/Debian:** +> +> ```bash +> sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev +> ``` +> +> **For pnpm v10+:** Run `pnpm approve-builds` or add `enable-pnpm-unsafe-build-scripts=true` to `.npmrc`. + +### What it does + +- Uses the same workflow definition from `src/workflow.ts` +- Wraps the workflow with `withDrawingNode` for Node.js image generation +- Generates a high-quality PNG image with configurable dimensions and layout +- Perfect for automated documentation, CI/CD pipelines, or batch processing + +### Key files + +- `src/workflow.ts`: Shared workflow definition (events, handlers) +- `src/viz-node.ts`: Node.js image generation entry point +- `package.json`: Contains the `generate` script + +--- + +## Shared Components + +Both examples use the same underlying workflow definition: + +- **`src/workflow.ts`**: Contains the example workflow with events and handlers that both browser and Node.js examples can use diff --git a/demo/visualization/index.html b/demo/visualization/index.html index 0ad9f4d..e500a14 100644 --- a/demo/visualization/index.html +++ b/demo/visualization/index.html @@ -9,6 +9,6 @@
- + diff --git a/demo/visualization/package.json b/demo/visualization/package.json index fe62e9e..04a8d91 100644 --- a/demo/visualization/package.json +++ b/demo/visualization/package.json @@ -6,11 +6,13 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "preview": "vite preview" + "preview": "vite preview", + "generate": "npx tsx src/viz-node.ts" }, "dependencies": { "@llamaindex/workflow-core": "^1.3.3", "@llamaindex/workflow-viz": "^1.0.4", + "@llamaindex/workflow-viz-node": "^1.0.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/demo/visualization/src/main.ts b/demo/visualization/src/main.ts deleted file mode 100644 index d102636..0000000 --- a/demo/visualization/src/main.ts +++ /dev/null @@ -1,9 +0,0 @@ -import "./style.css"; -import { workflow } from "./workflow"; - -const container = document.getElementById("app") as HTMLElement; - -workflow.draw(container, { - defaultEdgeColor: "#999", - layout: "force", -}); diff --git a/demo/visualization/src/viz-browser.ts b/demo/visualization/src/viz-browser.ts new file mode 100644 index 0000000..59b7f35 --- /dev/null +++ b/demo/visualization/src/viz-browser.ts @@ -0,0 +1,15 @@ +import { withDrawing } from "@llamaindex/workflow-viz"; +import "./style.css"; +import { setupWorkflowEvents } from "./workflow"; +import { createWorkflow } from "@llamaindex/workflow-core"; + +const container = document.getElementById("app") as HTMLElement; + +const workflow = withDrawing(createWorkflow()); + +setupWorkflowEvents(workflow); + +workflow.draw(container, { + defaultEdgeColor: "#999", + layout: "force", +}); diff --git a/demo/visualization/src/viz-node.ts b/demo/visualization/src/viz-node.ts new file mode 100644 index 0000000..4690db7 --- /dev/null +++ b/demo/visualization/src/viz-node.ts @@ -0,0 +1,17 @@ +import { withDrawingNode } from "@llamaindex/workflow-viz-node"; +import { setupWorkflowEvents } from "./workflow"; +import { createWorkflow } from "@llamaindex/workflow-core"; + +async function main() { + const workflow = withDrawingNode(createWorkflow()); + setupWorkflowEvents(workflow); + + await workflow.drawToImage({ + layout: "force", + width: 800, + height: 600, + output: "workflow.png", + }); +} + +main().catch(console.error); diff --git a/demo/visualization/src/workflow.ts b/demo/visualization/src/workflow.ts index c8defc9..ead81f4 100644 --- a/demo/visualization/src/workflow.ts +++ b/demo/visualization/src/workflow.ts @@ -1,5 +1,4 @@ -import { createWorkflow, workflowEvent } from "@llamaindex/workflow-core"; -import { withDrawing } from "@llamaindex/workflow-viz"; +import { Workflow, workflowEvent } from "@llamaindex/workflow-core"; //#region define workflow events const startEvent = workflowEvent({ @@ -25,36 +24,35 @@ const stopEvent = workflowEvent({ }); //#endregion -//#region defines workflow -const workflow = withDrawing(createWorkflow()); +export function setupWorkflowEvents(workflow: Workflow) { + workflow.handle([startEvent], async (ctx) => { + const { sendEvent, stream } = ctx; + // emit 3 different events, handled separately -workflow.handle([startEvent], async (ctx) => { - const { sendEvent, stream } = ctx; - // emit 3 different events, handled separately + sendEvent(branchAEvent.with("Branch A")); + sendEvent(branchBEvent.with("Branch B")); + sendEvent(branchCEvent.with("Branch C")); - sendEvent(branchAEvent.with("Branch A")); - sendEvent(branchBEvent.with("Branch B")); - sendEvent(branchCEvent.with("Branch C")); + const results = await stream.filter(branchCompleteEvent).take(3).toArray(); - const results = await stream.filter(branchCompleteEvent).take(3).toArray(); + return allCompleteEvent.with(results.map((e) => e.data).join(", ")); + }); - return allCompleteEvent.with(results.map((e) => e.data).join(", ")); -}); + workflow.handle([branchAEvent], (_context1, branchA) => { + return branchCompleteEvent.with(branchA.data); + }); -workflow.handle([branchAEvent], (_context1, branchA) => { - return branchCompleteEvent.with(branchA.data); -}); + workflow.handle([branchBEvent], (_context2, branchB) => { + return branchCompleteEvent.with(branchB.data); + }); -workflow.handle([branchBEvent], (_context2, branchB) => { - return branchCompleteEvent.with(branchB.data); -}); + workflow.handle([branchCEvent], (_context3, branchC) => { + return branchCompleteEvent.with(branchC.data); + }); -workflow.handle([branchCEvent], (_context3, branchC) => { - return branchCompleteEvent.with(branchC.data); -}); + workflow.handle([allCompleteEvent], (_context4, allComplete) => { + return stopEvent.with(allComplete.data); + }); -workflow.handle([allCompleteEvent], (_context4, allComplete) => { - return stopEvent.with(allComplete.data); -}); - -export { workflow }; + return workflow; +} diff --git a/packages/graph/biome.json b/packages/graph/biome.json new file mode 100644 index 0000000..6b400f8 --- /dev/null +++ b/packages/graph/biome.json @@ -0,0 +1,7 @@ +{ + "root": false, + "extends": "//", + "files": { + "includes": ["src/**", "tests/**"] + } +} diff --git a/packages/graph/package.json b/packages/graph/package.json new file mode 100644 index 0000000..4ce0faf --- /dev/null +++ b/packages/graph/package.json @@ -0,0 +1,58 @@ +{ + "name": "@llamaindex/workflow-graph", + "version": "1.0.0", + "private": true, + "description": "Shared graph utilities", + "type": "module", + "main": "dist/index.cjs", + "types": "dist/index.d.ts", + "module": "dist/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + }, + "default": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "bunchee", + "dev": "bunchee --watch", + "test": "vitest run", + "test:ui": "vitest --ui", + "test:watch": "vitest" + }, + "devDependencies": { + "@babel/types": "^7.27.1", + "@llamaindex/workflow-core": "workspace:*", + "@types/node": "^22.15.19", + "bunchee": "^6.5.1", + "vitest": "^2.0.0", + "@vitest/ui": "^2.0.0" + }, + "peerDependencies": { + "@llamaindex/workflow-core": "workspace:*" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/run-llama/workflow-ts.git", + "directory": "packages/graph" + }, + "dependencies": { + "@babel/parser": "^7.27.2", + "graphology": "^0.26.0", + "graphology-layout-force": "^0.2.4" + } +} diff --git a/packages/viz/src/graph.ts b/packages/graph/src/graph.ts similarity index 100% rename from packages/viz/src/graph.ts rename to packages/graph/src/graph.ts diff --git a/packages/graph/src/index.ts b/packages/graph/src/index.ts new file mode 100644 index 0000000..5897f3f --- /dev/null +++ b/packages/graph/src/index.ts @@ -0,0 +1 @@ +export { withGraph, type WithGraphWorkflow } from "./graph"; diff --git a/packages/viz/src/parser.ts b/packages/graph/src/parser.ts similarity index 100% rename from packages/viz/src/parser.ts rename to packages/graph/src/parser.ts diff --git a/packages/viz/src/tests/graph.test.ts b/packages/graph/src/tests/graph.test.ts similarity index 100% rename from packages/viz/src/tests/graph.test.ts rename to packages/graph/src/tests/graph.test.ts diff --git a/packages/viz/src/types.ts b/packages/graph/src/types.ts similarity index 100% rename from packages/viz/src/types.ts rename to packages/graph/src/types.ts diff --git a/packages/graph/tsconfig.json b/packages/graph/tsconfig.json new file mode 100644 index 0000000..64a0fee --- /dev/null +++ b/packages/graph/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "paths": { + "@llamaindex/workflow-viz": ["./src/index.ts"] + } + }, + "include": ["./src", "./examples"] +} diff --git a/packages/graph/vitest.config.ts b/packages/graph/vitest.config.ts new file mode 100644 index 0000000..9ea3433 --- /dev/null +++ b/packages/graph/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["**/*.test.ts", "**/*.spec.ts"], + }, +}); diff --git a/packages/viz-node/biome.json b/packages/viz-node/biome.json new file mode 100644 index 0000000..6b400f8 --- /dev/null +++ b/packages/viz-node/biome.json @@ -0,0 +1,7 @@ +{ + "root": false, + "extends": "//", + "files": { + "includes": ["src/**", "tests/**"] + } +} diff --git a/packages/viz-node/package.json b/packages/viz-node/package.json new file mode 100644 index 0000000..66f24a2 --- /dev/null +++ b/packages/viz-node/package.json @@ -0,0 +1,63 @@ +{ + "name": "@llamaindex/workflow-viz-node", + "version": "1.0.0", + "description": "Visualization components for drawing LlamaIndex Workflows in Node.js", + "type": "module", + "main": "dist/index.cjs", + "types": "dist/index.d.ts", + "module": "dist/index.js", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + }, + "default": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "bunchee", + "dev": "bunchee --watch", + "test": "vitest run", + "test:ui": "vitest --ui", + "test:watch": "vitest" + }, + "devDependencies": { + "@babel/types": "^7.27.1", + "@llamaindex/workflow-core": "workspace:*", + "@types/node": "^22.15.19", + "bunchee": "^6.5.1", + "vitest": "^2.0.0", + "@vitest/ui": "^2.0.0", + "graphology": "^0.26.0" + }, + "peerDependencies": { + "@llamaindex/workflow-core": "workspace:*" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/run-llama/workflow-ts.git", + "directory": "packages/viz-node" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@llamaindex/workflow-graph": "workspace:*", + "@babel/parser": "^7.27.2", + "graphology-layout-force": "^0.2.4", + "canvas": "^3.2.0", + "graphology-canvas": "^0.4.2" + } +} diff --git a/packages/viz-node/src/canvas.ts b/packages/viz-node/src/canvas.ts new file mode 100644 index 0000000..b9cc73a --- /dev/null +++ b/packages/viz-node/src/canvas.ts @@ -0,0 +1,22 @@ +import type Graph from "graphology"; + +export function toNodeCanvasGraph(graph: Graph): Graph { + const g = graph.copy(); + + g.forEachNode((node, attr) => { + if (attr.type === "handler") { + g.mergeNodeAttributes(node, { size: 20, color: "red" }); + g.removeNodeAttribute(node, "type"); + } + if (attr.type === "event") { + g.mergeNodeAttributes(node, { size: 10, color: "blue" }); + g.removeNodeAttribute(node, "type"); + } + }); + + g.forEachEdge((edge) => { + g.mergeEdgeAttributes(edge, { size: 3, color: "#999" }); + }); + + return g; +} diff --git a/packages/viz-node/src/drawing.ts b/packages/viz-node/src/drawing.ts new file mode 100644 index 0000000..270519b --- /dev/null +++ b/packages/viz-node/src/drawing.ts @@ -0,0 +1,136 @@ +import type { Workflow } from "@llamaindex/workflow-core"; +import { withGraph } from "@llamaindex/workflow-graph"; +import { createCanvas } from "canvas"; +import fs from "fs"; +import forceLayout from "graphology-layout-force"; +import { toNodeCanvasGraph } from "./canvas"; + +export type DrawingOptionsNode = { + layout?: "force" | "none"; + width?: number; + height?: number; + output?: string; // file path to save PNG +}; + +export type WithDrawingWorkflowNode = { + drawToImage(options?: DrawingOptionsNode): Promise; +}; + +export function withDrawingNode( + workflow: WorkflowLike, +): WorkflowLike & WithDrawingWorkflowNode { + const workflowWithGraph = withGraph(workflow); + + return { + ...workflowWithGraph, + async drawToImage(options?: DrawingOptionsNode): Promise { + const width = options?.width ?? 1200; + const height = options?.height ?? 800; + + const graph = toNodeCanvasGraph(workflowWithGraph.getGraph()); + + // Assign default radial positions to avoid overlaps + const nodes = graph.nodes(); + const order = graph.order || nodes.length || 1; + nodes.forEach((node, i) => { + const angle = (i * 2 * Math.PI) / order; + if (graph.getNodeAttribute(node, "x") === undefined) + graph.setNodeAttribute(node, "x", 100 * Math.cos(angle)); + if (graph.getNodeAttribute(node, "y") === undefined) + graph.setNodeAttribute(node, "y", 100 * Math.sin(angle)); + }); + + // Apply force layout if requested + if ((options?.layout ?? "force") === "force") { + await forceLayout.assign(graph, { maxIterations: 200 }); + } + + // Compute bounding box for scaling + let minX = Infinity, + maxX = -Infinity, + minY = Infinity, + maxY = -Infinity; + + graph.forEachNode((_node, attr) => { + const x = attr.x ?? 0; + const y = attr.y ?? 0; + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + }); + + const padding = 50; + const scaleX = (width - padding * 2) / (maxX - minX || 1); + const scaleY = (height - padding * 2) / (maxY - minY || 1); + + const mapX = (x: number) => (x - minX) * scaleX + padding; + const mapY = (y: number) => (y - minY) * scaleY + padding; + + // Create canvas + const canvas = createCanvas(width, height); + const ctx = canvas.getContext("2d"); + + // Background + ctx.fillStyle = "#ffffff"; + ctx.fillRect(0, 0, width, height); + + // Draw edges with arrows + graph.forEachEdge((_edge, attr, source, target) => { + const sx = mapX(graph.getNodeAttribute(source, "x")); + const sy = mapY(graph.getNodeAttribute(source, "y")); + const tx = mapX(graph.getNodeAttribute(target, "x")); + const ty = mapY(graph.getNodeAttribute(target, "y")); + + const angle = Math.atan2(ty - sy, tx - sx); + const arrowLength = 10; + const arrowAngle = Math.PI / 6; + + ctx.strokeStyle = attr.color || "#999"; + ctx.lineWidth = attr.size ?? 2; + ctx.beginPath(); + ctx.moveTo(sx, sy); + ctx.lineTo(tx, ty); + ctx.stroke(); + + // Arrow head + ctx.beginPath(); + ctx.moveTo(tx, ty); + ctx.lineTo( + tx - arrowLength * Math.cos(angle - arrowAngle), + ty - arrowLength * Math.sin(angle - arrowAngle), + ); + ctx.lineTo( + tx - arrowLength * Math.cos(angle + arrowAngle), + ty - arrowLength * Math.sin(angle + arrowAngle), + ); + ctx.closePath(); + ctx.fillStyle = attr.color || "#999"; + ctx.fill(); + }); + + // Draw nodes with labels + graph.forEachNode((_node, attr) => { + const x = mapX(attr.x ?? 0); + const y = mapY(attr.y ?? 0); + + ctx.fillStyle = attr.color; + ctx.beginPath(); + ctx.arc(x, y, attr.size ?? 10, 0, 2 * Math.PI); + ctx.fill(); + + if (attr.label) { + ctx.fillStyle = "#000"; + ctx.font = "14px sans-serif"; + ctx.textAlign = "center"; + ctx.fillText(attr.label, x, y + (attr.size ?? 10) + 12); // below the node + } + }); + + const buffer = canvas.toBuffer("image/png"); + if (options?.output) fs.writeFileSync(options.output, buffer); + + return buffer; + }, + }; +} diff --git a/packages/viz-node/src/index.ts b/packages/viz-node/src/index.ts new file mode 100644 index 0000000..c3a9375 --- /dev/null +++ b/packages/viz-node/src/index.ts @@ -0,0 +1,5 @@ +export { + withDrawingNode, + type WithDrawingWorkflowNode, + type DrawingOptionsNode, +} from "./drawing"; diff --git a/packages/viz-node/src/tests/canvas.test.ts b/packages/viz-node/src/tests/canvas.test.ts new file mode 100644 index 0000000..caa96f3 --- /dev/null +++ b/packages/viz-node/src/tests/canvas.test.ts @@ -0,0 +1,52 @@ +import Graph from "graphology"; +import { describe, expect, it } from "vitest"; +import { toNodeCanvasGraph } from "../canvas"; + +describe("toNodeCanvasGraph", () => { + it("should transform handler nodes with correct attributes", () => { + const graph = new Graph(); + graph.addNode("handler1", { type: "handler", label: "Handler 1" }); + graph.addNode("handler2", { type: "handler", label: "Handler 2" }); + + const result = toNodeCanvasGraph(graph); + + expect(result.getNodeAttribute("handler1", "size")).toBe(20); + expect(result.getNodeAttribute("handler1", "color")).toBe("red"); + expect(result.getNodeAttribute("handler1", "type")).toBeUndefined(); + expect(result.getNodeAttribute("handler1", "label")).toBe("Handler 1"); + + expect(result.getNodeAttribute("handler2", "size")).toBe(20); + expect(result.getNodeAttribute("handler2", "color")).toBe("red"); + expect(result.getNodeAttribute("handler2", "type")).toBeUndefined(); + expect(result.getNodeAttribute("handler2", "label")).toBe("Handler 2"); + }); + + it("should transform event nodes with correct attributes", () => { + const graph = new Graph(); + graph.addNode("event1", { type: "event", label: "Event 1" }); + graph.addNode("event2", { type: "event", label: "Event 2" }); + + const result = toNodeCanvasGraph(graph); + + expect(result.getNodeAttribute("event1", "size")).toBe(10); + expect(result.getNodeAttribute("event1", "color")).toBe("blue"); + expect(result.getNodeAttribute("event1", "type")).toBeUndefined(); + expect(result.getNodeAttribute("event1", "label")).toBe("Event 1"); + + expect(result.getNodeAttribute("event2", "size")).toBe(10); + expect(result.getNodeAttribute("event2", "color")).toBe("blue"); + expect(result.getNodeAttribute("event2", "type")).toBeUndefined(); + expect(result.getNodeAttribute("event2", "label")).toBe("Event 2"); + }); + + it("should transform edges with correct attributes", () => { + const graph = new Graph(); + graph.addNode("node1", { label: "Node 1" }); + graph.addNode("node2", { label: "Node 2" }); + const edge = graph.addEdge("node1", "node2"); + + const result = toNodeCanvasGraph(graph); + + expect(result.getEdgeAttribute(edge, "size")).toBe(3); + }); +}); diff --git a/packages/viz-node/tsconfig.json b/packages/viz-node/tsconfig.json new file mode 100644 index 0000000..64a0fee --- /dev/null +++ b/packages/viz-node/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "paths": { + "@llamaindex/workflow-viz": ["./src/index.ts"] + } + }, + "include": ["./src", "./examples"] +} diff --git a/packages/viz-node/vitest.config.ts b/packages/viz-node/vitest.config.ts new file mode 100644 index 0000000..9ea3433 --- /dev/null +++ b/packages/viz-node/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["**/*.test.ts", "**/*.spec.ts"], + }, +}); diff --git a/packages/viz/package.json b/packages/viz/package.json index 600a7f3..e8d858b 100644 --- a/packages/viz/package.json +++ b/packages/viz/package.json @@ -38,7 +38,8 @@ "@types/node": "^22.15.19", "bunchee": "^6.5.1", "vitest": "^2.0.0", - "@vitest/ui": "^2.0.0" + "@vitest/ui": "^2.0.0", + "graphology": "^0.26.0" }, "peerDependencies": { "@llamaindex/workflow-core": "workspace:*" @@ -53,8 +54,8 @@ "access": "public" }, "dependencies": { + "@llamaindex/workflow-graph": "workspace:*", "@babel/parser": "^7.27.2", - "graphology": "^0.26.0", "graphology-layout-force": "^0.2.4", "sigma": "^3.0.1" } diff --git a/packages/viz/src/drawing.ts b/packages/viz/src/drawing.ts index ffc55bd..5225aa1 100644 --- a/packages/viz/src/drawing.ts +++ b/packages/viz/src/drawing.ts @@ -2,7 +2,7 @@ import type { Workflow } from "@llamaindex/workflow-core"; import ForceSupervisor from "graphology-layout-force/worker"; import Sigma from "sigma"; import type { Settings } from "sigma/settings"; -import { withGraph } from "./graph"; +import { withGraph } from "@llamaindex/workflow-graph"; import { toSigma } from "./sigma"; export type DrawingOptions = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4ea54a..564b3c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -312,6 +312,9 @@ importers: '@llamaindex/workflow-viz': specifier: ^1.0.4 version: link:../../packages/viz + '@llamaindex/workflow-viz-node': + specifier: ^1.0.0 + version: link:../../packages/viz-node react: specifier: ^19.1.0 version: 19.1.0 @@ -401,6 +404,37 @@ importers: specifier: ^3.25.0 || ^4.0.0 version: 3.25.67 + packages/graph: + dependencies: + '@babel/parser': + specifier: ^7.27.2 + version: 7.27.5 + graphology: + specifier: ^0.26.0 + version: 0.26.0(graphology-types@0.24.8) + graphology-layout-force: + specifier: ^0.2.4 + version: 0.2.4(graphology-types@0.24.8) + devDependencies: + '@babel/types': + specifier: ^7.27.1 + version: 7.27.6 + '@llamaindex/workflow-core': + specifier: workspace:* + version: link:../core + '@types/node': + specifier: ^22.15.19 + version: 22.15.33 + '@vitest/ui': + specifier: ^2.0.0 + version: 2.1.9(vitest@2.1.9) + bunchee: + specifier: ^6.5.1 + version: 6.5.4(typescript@5.9.2) + vitest: + specifier: ^2.0.0 + version: 2.1.9(@edge-runtime/vm@5.0.0)(@types/node@22.15.33)(@vitest/ui@2.1.9)(happy-dom@18.0.1)(lightningcss@1.30.1)(terser@5.43.1) + packages/http: devDependencies: '@llamaindex/workflow-core': @@ -434,9 +468,9 @@ importers: '@babel/parser': specifier: ^7.27.2 version: 7.27.5 - graphology: - specifier: ^0.26.0 - version: 0.26.0(graphology-types@0.24.8) + '@llamaindex/workflow-graph': + specifier: workspace:* + version: link:../graph graphology-layout-force: specifier: ^0.2.4 version: 0.2.4(graphology-types@0.24.8) @@ -459,6 +493,49 @@ importers: bunchee: specifier: ^6.5.1 version: 6.5.4(typescript@5.9.2) + graphology: + specifier: ^0.26.0 + version: 0.26.0(graphology-types@0.24.8) + vitest: + specifier: ^2.0.0 + version: 2.1.9(@edge-runtime/vm@5.0.0)(@types/node@22.15.33)(@vitest/ui@2.1.9)(happy-dom@18.0.1)(lightningcss@1.30.1)(terser@5.43.1) + + packages/viz-node: + dependencies: + '@babel/parser': + specifier: ^7.27.2 + version: 7.27.5 + '@llamaindex/workflow-graph': + specifier: workspace:* + version: link:../graph + canvas: + specifier: ^3.2.0 + version: 3.2.0 + graphology-canvas: + specifier: ^0.4.2 + version: 0.4.2(canvas@3.2.0)(graphology-types@0.24.8) + graphology-layout-force: + specifier: ^0.2.4 + version: 0.2.4(graphology-types@0.24.8) + devDependencies: + '@babel/types': + specifier: ^7.27.1 + version: 7.27.6 + '@llamaindex/workflow-core': + specifier: workspace:* + version: link:../core + '@types/node': + specifier: ^22.15.19 + version: 22.15.33 + '@vitest/ui': + specifier: ^2.0.0 + version: 2.1.9(vitest@2.1.9) + bunchee: + specifier: ^6.5.1 + version: 6.5.4(typescript@5.9.2) + graphology: + specifier: ^0.26.0 + version: 0.26.0(graphology-types@0.24.8) vitest: specifier: ^2.0.0 version: 2.1.9(@edge-runtime/vm@5.0.0)(@types/node@22.15.33)(@vitest/ui@2.1.9)(happy-dom@18.0.1)(lightningcss@1.30.1)(terser@5.43.1) @@ -3152,6 +3229,9 @@ packages: bare-url@2.2.2: resolution: {integrity: sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} @@ -3170,6 +3250,9 @@ packages: birpc@2.4.0: resolution: {integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} @@ -3193,6 +3276,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + bunchee@6.5.4: resolution: {integrity: sha512-j8fBYrsLsOB/s7C26omNuWBM9i5ZCQtP4JzZSLARnbMsSl0b6YFMsH5Ou6n++bTrp/X36xWemrM7Xhn8G0PgfA==} engines: {node: '>= 18.0.0'} @@ -3234,6 +3320,10 @@ packages: caniuse-lite@1.0.30001726: resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} + canvas@3.2.0: + resolution: {integrity: sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==} + engines: {node: ^18.12.0 || >= 20.9.0} + caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -3275,6 +3365,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -3493,10 +3586,18 @@ packages: supports-color: optional: true + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -3849,6 +3950,10 @@ packages: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} @@ -3999,6 +4104,9 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@1.0.0: resolution: {integrity: sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==} @@ -4073,6 +4181,9 @@ packages: getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4126,11 +4237,25 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphology-canvas@0.4.2: + resolution: {integrity: sha512-7YJCw/8mMXvFiLP1ujEgpFOM5lmj5/AQG+9H1S/ErFvAQ1eyoLsTK/OzyLJjXzhHsBEl0fF4rN//TDsvuvkfPg==} + peerDependencies: + canvas: '*' + graphology-types: '>=0.20.0' + peerDependenciesMeta: + canvas: + optional: true + graphology-layout-force@0.2.4: resolution: {integrity: sha512-NYZz0YAnDkn5pkm30cvB0IScFoWGtbzJMrqaiH070dYlYJiag12Oc89dbVfaMaVR/w8DMIKxn/ix9Bqj+Umm9Q==} peerDependencies: graphology-types: '>=0.19.0' + graphology-layout@0.6.0: + resolution: {integrity: sha512-QZIminJVOqOFHBOf6qEjcMp0m+BNP4/XqY1jrnJhH+fmUHFtNDdTPSXYhVa8Hr3AN5bOPP1Zte5oY1ZzOyzhvA==} + peerDependencies: + graphology-types: '>=0.19.0' + graphology-types@0.24.8: resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==} @@ -4254,6 +4379,9 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4284,6 +4412,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -4833,6 +4964,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + miniflare@4.20250617.4: resolution: {integrity: sha512-IAoApFKxOJlaaFkym5ETstVX3qWzVt3xyqCDj6vSSTgEH3zxZJ5417jZGg8iQfMHosKCcQH1doPPqqnOZm/yrw==} engines: {node: '>=18.0.0'} @@ -4863,6 +4998,9 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -4906,6 +5044,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + napi-postinstall@0.3.3: resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -4964,6 +5105,13 @@ packages: sass: optional: true + node-abi@3.77.0: + resolution: {integrity: sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==} + engines: {node: '>=10'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -5140,6 +5288,9 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + pandemonium@1.5.0: + resolution: {integrity: sha512-9PU9fy93rJhZHLMjX+4M1RwZPEYl6g7DdWKGmGNhkgBZR5+tOBVExNZc00kzdEGMxbaAvWdQy9MqGAScGwYlcA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -5253,6 +5404,11 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -5347,6 +5503,10 @@ packages: resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} engines: {node: '>= 0.8'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -5628,6 +5788,12 @@ packages: simple-async-context@1.0.4: resolution: {integrity: sha512-OPH9mLzjbamUjRYDf2Xk/KA72nbhzraJrc9NZl0eLC5/npJR2dpC4tGAXrPbZw7WPjoAlv45N27t9uL6WtwV+A==} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -5803,6 +5969,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -5853,9 +6023,16 @@ packages: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + tar-fs@3.1.0: resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -8886,6 +9063,8 @@ snapshots: bare-path: 3.0.0 optional: true + base64-js@1.5.1: {} + basic-ftp@5.0.5: {} bcrypt-pbkdf@1.0.2: @@ -8900,6 +9079,12 @@ snapshots: birpc@2.4.0: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + blake3-wasm@2.1.5: {} body-parser@2.2.0: @@ -8933,6 +9118,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bunchee@6.5.4(typescript@5.9.2): dependencies: '@rollup/plugin-commonjs': 28.0.6(rollup@4.44.0) @@ -8985,6 +9175,11 @@ snapshots: caniuse-lite@1.0.30001726: {} + canvas@3.2.0: + dependencies: + node-addon-api: 7.1.1 + prebuild-install: 7.1.3 + caseless@0.12.0: {} chai@5.2.0: @@ -9038,6 +9233,8 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@1.1.4: {} + chownr@3.0.0: {} chromium-bidi@8.0.0(devtools-protocol@0.0.1495869): @@ -9222,8 +9419,14 @@ snapshots: dependencies: ms: 2.1.3 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -9534,8 +9737,8 @@ snapshots: '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.5.1)) @@ -9554,7 +9757,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -9565,22 +9768,22 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9591,7 +9794,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9747,6 +9950,8 @@ snapshots: exit-hook@2.2.1: {} + expand-template@2.0.3: {} + expect-type@1.2.1: {} express-rate-limit@7.5.1(express@5.1.0): @@ -9941,6 +10146,8 @@ snapshots: fresh@2.0.0: {} + fs-constants@1.0.0: {} + fs-extra@1.0.0: dependencies: graceful-fs: 4.2.11 @@ -10034,6 +10241,8 @@ snapshots: dependencies: assert-plus: 1.0.0 + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -10090,11 +10299,25 @@ snapshots: graphemer@1.4.0: {} + graphology-canvas@0.4.2(canvas@3.2.0)(graphology-types@0.24.8): + dependencies: + graphology-layout: 0.6.0(graphology-types@0.24.8) + graphology-types: 0.24.8 + graphology-utils: 2.5.2(graphology-types@0.24.8) + optionalDependencies: + canvas: 3.2.0 + graphology-layout-force@0.2.4(graphology-types@0.24.8): dependencies: graphology-types: 0.24.8 graphology-utils: 2.5.2(graphology-types@0.24.8) + graphology-layout@0.6.0(graphology-types@0.24.8): + dependencies: + graphology-types: 0.24.8 + graphology-utils: 2.5.2(graphology-types@0.24.8) + pandemonium: 1.5.0 + graphology-types@0.24.8: {} graphology-utils@2.5.2(graphology-types@0.24.8): @@ -10215,6 +10438,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -10242,6 +10467,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -10783,6 +11010,8 @@ snapshots: mimic-function@5.0.1: {} + mimic-response@3.1.0: {} + miniflare@4.20250617.4: dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -10823,6 +11052,8 @@ snapshots: mitt@3.0.1: {} + mkdirp-classic@0.5.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -10847,6 +11078,8 @@ snapshots: nanoid@3.3.11: {} + napi-build-utils@2.0.0: {} + napi-postinstall@0.3.3: {} natural-compare@1.4.0: {} @@ -10903,6 +11136,12 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-abi@3.77.0: + dependencies: + semver: 7.7.2 + + node-addon-api@7.1.1: {} + node-domexception@1.0.0: {} node-fetch@2.7.0: @@ -11085,6 +11324,8 @@ snapshots: dependencies: quansync: 0.2.10 + pandemonium@1.5.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -11177,6 +11418,21 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.4 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.77.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier@2.8.8: {} @@ -11298,6 +11554,13 @@ snapshots: iconv-lite: 0.6.3 unpipe: 1.0.0 + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 @@ -11767,6 +12030,14 @@ snapshots: simple-async-context@1.0.4: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -11986,6 +12257,8 @@ snapshots: strip-bom@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} strip-literal@3.0.0: @@ -12017,6 +12290,13 @@ snapshots: tapable@2.2.2: {} + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + tar-fs@3.1.0: dependencies: pump: 3.0.3 @@ -12028,6 +12308,14 @@ snapshots: - bare-buffer - react-native-b4a + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tar-stream@3.1.7: dependencies: b4a: 1.7.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7371379..b8520cd 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,6 +9,7 @@ ignoredBuiltDependencies: onlyBuiltDependencies: - '@tailwindcss/oxide' + - canvas - esbuild - sharp - workerd diff --git a/tsconfig.json b/tsconfig.json index 86fd4ef..6cc8489 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -52,6 +52,12 @@ { "path": "./packages/viz/tsconfig.json" }, + { + "path": "./packages/viz-node/tsconfig.json" + }, + { + "path": "./packages/graph/tsconfig.json" + }, { "path": "./tests/cjs/tsconfig.json" },