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 ¶
- Constants
- func MarshalGraph(g *dag.DAG) ([]byte, error)
- func MarshalLayout(l Layout) ([]byte, error)
- func ReadGraph(r io.Reader) (*dag.DAG, error)
- func ReadGraphFile(path string) (*dag.DAG, error)
- func ToDAG(gj Graph) (*dag.DAG, error)
- func WriteGraph(g *dag.DAG, w io.Writer) error
- func WriteGraphFile(g *dag.DAG, path string) error
- func WriteLayoutFile(l Layout, path string) error
- type Block
- type BlockMeta
- type Edge
- type Graph
- type Layout
- type NebraskaPackage
- type NebraskaRanking
- type Node
Examples ¶
Constants ¶
const ( VizTypeTower = "tower" VizTypeNodelink = "nodelink" )
Visualization types.
const ( StyleSimple = "simple" StyleHanddrawn = "handdrawn" )
Visual styles for rendering.
const ( KindSubdivider = "subdivider" KindAuxiliary = "auxiliary" )
Node kinds.
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 ¶
MarshalGraph converts a DAG to JSON bytes. Nodes are sorted by ID for deterministic output.
func MarshalLayout ¶
MarshalLayout serializes a Layout to pretty-printed JSON bytes.
func ReadGraph ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ReadLayoutFile reads a Layout from a JSON file.
func UnmarshalLayout ¶
UnmarshalLayout deserializes JSON bytes into a Layout. Validates that required fields are present for the viz type.
func (*Layout) IsNodelink ¶
IsNodelink returns true if this is a nodelink 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 ¶
DisplayLabel returns the label if set, otherwise the ID.
func (*Node) IsAuxiliary ¶
IsAuxiliary returns true if this is an auxiliary dependency.
func (*Node) IsSubdivider ¶
IsSubdivider returns true if this is a subdivider node.