graph

package
v1.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package graph provides serialization types for dependency graphs and layouts.

This package defines the canonical wire format for Stacktower's graph data, used for JSON files, API responses, caching, and cross-tool interoperability.

Architecture

The package sits at the serialization boundary between internal representations and external formats:

  • Graph, Layout: Serialization types (this package)
  • pkg/core/dag.DAG: Internal graph representation
  • pkg/core/render/tower/layout.Layout: Internal layout (positions, metadata)

Use FromDAG/ToDAG and Export/Parse methods to convert between them.

Core Types

  • Graph: Node-link format for dependency graphs
  • Layout: Unified format for visualization layouts (tower or nodelink)
  • Node, Edge: Shared structural types
  • Block: Positioned element in tower visualizations

Constants

This package is the single source of truth for visualization constants:

graph.VizTypeTower      // "tower"
graph.VizTypeNodelink   // "nodelink"
graph.StyleSimple       // "simple"
graph.StyleHanddrawn    // "handdrawn"

Graph Serialization

Graphs use a simple node-link JSON format:

{
  "nodes": [{"id": "app"}, {"id": "lib-a"}],
  "edges": [{"from": "app", "to": "lib-a"}]
}

Common operations:

g, _ := graph.ReadGraphFile("deps.json")    // File → DAG
graph.WriteGraphFile(dag, "output.json")    // DAG → File
data, _ := graph.MarshalGraph(dag)          // DAG → []byte
parsed, _ := graph.UnmarshalGraph(data)     // []byte → Graph

Layout Serialization

Layouts are discriminated by VizType:

layout, _ := graph.UnmarshalLayout(data)
if layout.IsTower() {
    // Use layout.Blocks for positioned blocks
} else {
    // Use layout.DOT for Graphviz rendering
}

Converting Between Types

For tower layouts:

// Internal → Serialized (for JSON/API/cache)
serialized, err := internalLayout.Export(dag)

// Serialized → Internal (from JSON/API/cache)
internal, err := layout.Parse(serializedLayout)

Node Metadata

The meta object supports arbitrary key-value data. Recognized keys:

repo_url          Repository URL (clickable blocks)
repo_stars        Star count (popups)
repo_owner        Owner for Nebraska ranking
repo_maintainers  Maintainer list
repo_last_commit  Staleness detection
repo_archived     Archived flag
description       Popup content

Concurrency

All functions are safe for concurrent reads but not concurrent writes.

Index

Examples

Constants

View Source
const (
	VizTypeTower    = "tower"
	VizTypeNodelink = "nodelink"
)

Visualization types.

View Source
const (
	StyleSimple    = "simple"
	StyleHanddrawn = "handdrawn"
)

Visual styles for rendering.

View Source
const (
	KindSubdivider = "subdivider"
	KindAuxiliary  = "auxiliary"
)

Node kinds.

View Source
const ProjectRootNodeID = dag.ProjectRootNodeID

ProjectRootNodeID is an alias for dag.ProjectRootNodeID so that callers importing pkg/graph can use the short name without importing pkg/core/dag.

Variables

This section is empty.

Functions

func MarshalGraph

func MarshalGraph(g *dag.DAG) ([]byte, error)

MarshalGraph converts a DAG to JSON bytes. Nodes are sorted by ID for deterministic output.

func MarshalLayout

func MarshalLayout(l Layout) ([]byte, error)

MarshalLayout serializes a Layout to pretty-printed JSON bytes.

func ReadGraph

func ReadGraph(r io.Reader) (*dag.DAG, error)

ReadGraph decodes a JSON graph from an io.Reader into a DAG. Use ReadGraphFile for files or pass bytes.NewReader for in-memory data.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/stacktower-io/stacktower/pkg/graph"
)

func main() {
	// JSON input representing a dependency graph
	jsonData := `{
		"nodes": [
			{"id": "app"},
			{"id": "lib", "row": 1}
		],
		"edges": [
			{"from": "app", "to": "lib"}
		]
	}`

	// Parse the JSON
	g, err := graph.ReadGraph(bytes.NewReader([]byte(jsonData)))
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("Nodes:", g.NodeCount())
	fmt.Println("Edges:", g.EdgeCount())
	fmt.Println("Children of app:", g.Children("app"))
}
Output:
Nodes: 2
Edges: 1
Children of app: [lib]
Example (WithMetadata)
package main

import (
	"bytes"
	"fmt"

	"github.com/stacktower-io/stacktower/pkg/graph"
)

func main() {
	// JSON with package metadata (as produced by dependency parsing)
	jsonData := `{
		"nodes": [
			{
				"id": "fastapi",
				"meta": {
					"version": "1.0.0",
					"description": "FastAPI framework",
					"repo_stars": 70000
				}
			},
			{
				"id": "pydantic",
				"row": 1,
				"meta": {
					"version": "2.0.0"
				}
			}
		],
		"edges": [
			{"from": "fastapi", "to": "pydantic"}
		]
	}`

	g, _ := graph.ReadGraph(bytes.NewReader([]byte(jsonData)))
	node, _ := g.Node("fastapi")

	fmt.Println("Package:", node.ID)
	fmt.Println("Version:", node.Meta["version"])
	fmt.Println("Stars:", node.Meta["repo_stars"])
}
Output:
Package: fastapi
Version: 1.0.0
Stars: 70000

func ReadGraphFile

func ReadGraphFile(path string) (*dag.DAG, error)

ReadGraphFile reads a JSON file and returns the decoded DAG. Returns validation errors for malformed graphs or DAG constraint violations.

Example
package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/stacktower-io/stacktower/pkg/graph"
)

func main() {
	// Create a temporary JSON file
	tmpDir := os.TempDir()
	path := filepath.Join(tmpDir, "example-graph.json")

	jsonData := []byte(`{
		"nodes": [
			{"id": "root"},
			{"id": "child-a", "row": 1},
			{"id": "child-b", "row": 1}
		],
		"edges": [
			{"from": "root", "to": "child-a"},
			{"from": "root", "to": "child-b"}
		]
	}`)

	if err := os.WriteFile(path, jsonData, 0644); err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer os.Remove(path)

	// Import the graph
	g, err := graph.ReadGraphFile(path)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("Imported", g.NodeCount(), "nodes")
	fmt.Println("Root has", g.OutDegree("root"), "children")
}
Output:
Imported 3 nodes
Root has 2 children

func ToDAG

func ToDAG(gj Graph) (*dag.DAG, error)

ToDAG converts a Graph to a DAG. Returns an error if the structure violates DAG constraints. Label is stored in metadata for round-trip fidelity when non-empty. Constraint is stored in edge metadata for round-trip fidelity when non-empty.

func WriteGraph

func WriteGraph(g *dag.DAG, w io.Writer) error

WriteGraph writes a DAG as JSON to an io.Writer. Use MarshalGraph for in-memory serialization or WriteGraphFile for files.

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/stacktower-io/stacktower/pkg/core/dag"
	"github.com/stacktower-io/stacktower/pkg/graph"
)

func main() {
	// Create a simple dependency graph
	g := dag.New(nil)
	_ = g.AddNode(dag.Node{ID: "app", Row: 0})
	_ = g.AddNode(dag.Node{ID: "lib", Row: 1, Meta: dag.Metadata{"version": "1.0.0"}})
	_ = g.AddEdge(dag.Edge{From: "app", To: "lib"})

	// Write to a buffer (or any io.Writer)
	var buf bytes.Buffer
	if err := graph.WriteGraph(g, &buf); err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("JSON output:")
	fmt.Println(buf.String())
}
Output:
JSON output:
{
  "nodes": [
    {
      "id": "app"
    },
    {
      "id": "lib",
      "row": 1,
      "meta": {
        "version": "1.0.0"
      }
    }
  ],
  "edges": [
    {
      "from": "app",
      "to": "lib"
    }
  ]
}

func WriteGraphFile

func WriteGraphFile(g *dag.DAG, path string) error

WriteGraphFile writes a DAG to a JSON file. The file is created with 0644 permissions.

Example
package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/stacktower-io/stacktower/pkg/core/dag"
	"github.com/stacktower-io/stacktower/pkg/graph"
)

func main() {
	// Build a simple graph
	g := dag.New(nil)
	_ = g.AddNode(dag.Node{ID: "server"})
	_ = g.AddNode(dag.Node{ID: "database", Row: 1})
	_ = g.AddEdge(dag.Edge{From: "server", To: "database"})

	// Export to a file
	tmpDir := os.TempDir()
	path := filepath.Join(tmpDir, "exported-graph.json")
	defer os.Remove(path)

	if err := graph.WriteGraphFile(g, path); err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Verify the file was created
	if _, err := os.Stat(path); err == nil {
		fmt.Println("Graph exported successfully")
	}
}
Output:
Graph exported successfully

func WriteLayoutFile

func WriteLayoutFile(l Layout, path string) error

WriteLayoutFile writes a Layout to a JSON file.

Types

type Block

type Block struct {
	ID     string  `json:"id" bson:"id"`
	Label  string  `json:"label" bson:"label"`
	X      float64 `json:"x" bson:"x"`
	Y      float64 `json:"y" bson:"y"`
	Width  float64 `json:"width" bson:"width"`
	Height float64 `json:"height" bson:"height"`

	// Metadata
	URL          string     `json:"url,omitempty" bson:"url,omitempty"`
	Brittle      bool       `json:"brittle,omitempty" bson:"brittle,omitempty"`
	VulnSeverity string     `json:"vuln_severity,omitempty" bson:"vuln_severity,omitempty"` // Max vulnerability severity
	Auxiliary    bool       `json:"auxiliary,omitempty" bson:"auxiliary,omitempty"`
	Synthetic    bool       `json:"synthetic,omitempty" bson:"synthetic,omitempty"`
	Meta         *BlockMeta `json:"meta,omitempty" bson:"meta,omitempty"`
}

Block represents a positioned block in a tower layout.

type BlockMeta

type BlockMeta struct {
	Description string `json:"description,omitempty" bson:"description,omitempty"`
	Stars       int    `json:"stars,omitempty" bson:"stars,omitempty"`
	LastCommit  string `json:"last_commit,omitempty" bson:"last_commit,omitempty"`
	LastRelease string `json:"last_release,omitempty" bson:"last_release,omitempty"`
	Archived    bool   `json:"archived,omitempty" bson:"archived,omitempty"`
}

BlockMeta contains enriched metadata (from GitHub, registries).

type Edge

type Edge struct {
	From       string `json:"from" bson:"from"`
	To         string `json:"to" bson:"to"`
	Constraint string `json:"constraint,omitempty" bson:"constraint,omitempty"` // Version constraint (e.g., "^4.17.0", ">=2.0")
}

Edge represents a directed edge in the dependency graph.

type Graph

type Graph struct {
	Meta  map[string]any `json:"meta,omitempty" bson:"meta,omitempty"` // Graph-level metadata (runtime version, dependency scope, etc.)
	Nodes []Node         `json:"nodes" bson:"nodes"`
	Edges []Edge         `json:"edges" bson:"edges"`
}

Graph is the canonical serialization format for dependency graphs. Used for API responses, storage, caching, and cross-tool compatibility.

The format is human-readable and designed for round-trip fidelity: import → transform → export → re-import produces identical results.

func FromDAG

func FromDAG(g *dag.DAG) Graph

FromDAG converts a DAG to its serialization format. Nodes are sorted by ID for deterministic output. Extracts repository URL and computes brittle flag from metadata.

func UnmarshalGraph

func UnmarshalGraph(data []byte) (Graph, error)

UnmarshalGraph deserializes JSON bytes to a Graph.

type Layout

type Layout struct {
	// Discriminator
	VizType string `json:"viz_type" bson:"viz_type"`

	// Common dimensions and style
	Width  float64 `json:"width" bson:"width"`
	Height float64 `json:"height" bson:"height"`
	Style  string  `json:"style,omitempty" bson:"style,omitempty"`

	// Graph structure (shared)
	Nodes    []Node            `json:"nodes,omitempty" bson:"nodes,omitempty"`
	Edges    []Edge            `json:"edges,omitempty" bson:"edges,omitempty"`
	Rows     map[int][]string  `json:"rows,omitempty" bson:"rows,omitempty"`
	Nebraska []NebraskaRanking `json:"nebraska,omitempty" bson:"nebraska,omitempty"`

	// Crossings is the number of edge crossings produced by Rows.
	// Populated by the ordering stage; persisted so cached layouts don't
	// have to recompute it. A negative value means "not computed yet".
	Crossings int `json:"crossings,omitempty" bson:"crossings,omitempty"`

	// Tower-specific
	Blocks    []Block `json:"blocks,omitempty" bson:"blocks,omitempty"`
	MarginX   float64 `json:"margin_x,omitempty" bson:"margin_x,omitempty"`
	MarginY   float64 `json:"margin_y,omitempty" bson:"margin_y,omitempty"`
	Seed      uint64  `json:"seed,omitempty" bson:"seed,omitempty"`
	Randomize bool    `json:"randomize,omitempty" bson:"randomize,omitempty"`
	Merged    bool    `json:"merged,omitempty" bson:"merged,omitempty"`

	// Nodelink-specific
	DOT    string `json:"dot,omitempty" bson:"dot,omitempty"`
	Engine string `json:"engine,omitempty" bson:"engine,omitempty"`
}

Layout is the unified serialization format for all visualizations.

This is a discriminated union type - check VizType to determine which fields are populated:

Tower ("tower"):
  - Blocks: positioned blocks with coordinates
  - MarginX/Y, Seed, Randomize, Merged: tower-specific render options

Nodelink ("nodelink"):
  - DOT: Graphviz DOT string for rendering
  - Engine: Graphviz layout engine (e.g., "dot")

Shared fields (both types):

  • Width, Height: frame dimensions
  • Style: visual style ("handdrawn", "simple")
  • Nodes: structured node metadata
  • Edges: dependency edges
  • Rows: layer assignments (row → node IDs)
  • Nebraska: maintainer rankings (optional)

For tower layouts, there is also an internal representation (pkg/core/render/tower/layout.Layout) optimized for computation. Use Export()/Parse() methods to convert between them.

func ReadLayoutFile

func ReadLayoutFile(path string) (Layout, error)

ReadLayoutFile reads a Layout from a JSON file.

func UnmarshalLayout

func UnmarshalLayout(data []byte) (Layout, error)

UnmarshalLayout deserializes JSON bytes into a Layout. Validates that required fields are present for the viz type.

func (l *Layout) IsNodelink() bool

IsNodelink returns true if this is a nodelink layout.

func (*Layout) IsTower

func (l *Layout) IsTower() bool

IsTower returns true if this is a tower layout.

type NebraskaPackage

type NebraskaPackage struct {
	Package string `json:"package" bson:"package"`
	Role    string `json:"role" bson:"role"` // "owner", "lead", "maintainer"
	URL     string `json:"url,omitempty" bson:"url,omitempty"`
}

NebraskaPackage represents a package maintained by someone.

type NebraskaRanking

type NebraskaRanking struct {
	Maintainer string            `json:"maintainer" bson:"maintainer"`
	Score      float64           `json:"score" bson:"score"`
	Packages   []NebraskaPackage `json:"packages" bson:"packages"`
}

NebraskaRanking contains maintainer ranking information.

type Node

type Node struct {
	ID           string         `json:"id" bson:"id"`
	Label        string         `json:"label,omitempty" bson:"label,omitempty"`                 // Display label (defaults to ID)
	Row          int            `json:"row,omitempty" bson:"row,omitempty"`                     // Layer/rank assignment
	Kind         string         `json:"kind,omitempty" bson:"kind,omitempty"`                   // "subdivider", "auxiliary", or empty
	Brittle      bool           `json:"brittle,omitempty" bson:"brittle,omitempty"`             // At-risk package flag
	VulnSeverity string         `json:"vuln_severity,omitempty" bson:"vuln_severity,omitempty"` // Max vulnerability severity ("critical","high","medium","low")
	LicenseRisk  string         `json:"license_risk,omitempty" bson:"license_risk,omitempty"`   // License risk classification ("copyleft","weak-copyleft","unknown","proprietary")
	License      string         `json:"license,omitempty" bson:"license,omitempty"`             // License identifier/SPDX (e.g., "MIT", "Apache-2.0")
	LicenseText  string         `json:"license_text,omitempty" bson:"license_text,omitempty"`   // Full license text for custom/non-standard licenses (for LLM analysis)
	MasterID     string         `json:"master_id,omitempty" bson:"master_id,omitempty"`
	URL          string         `json:"url,omitempty" bson:"url,omitempty"` // Repository URL
	Meta         map[string]any `json:"meta,omitempty" bson:"meta,omitempty"`
}

Node is the unified node type for all serialization contexts. Used in both Graph and Layout types for consistency.

func (*Node) DisplayLabel

func (n *Node) DisplayLabel() string

DisplayLabel returns the label if set, otherwise the ID.

func (*Node) IsAuxiliary

func (n *Node) IsAuxiliary() bool

IsAuxiliary returns true if this is an auxiliary dependency.

func (*Node) IsSubdivider

func (n *Node) IsSubdivider() bool

IsSubdivider returns true if this is a subdivider node.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL