graph

package
v0.20.1 Latest Latest
Warning

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

Go to latest
Published: Apr 11, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package graph provides the core graph data structures and operations for Axon.

The graph consists of nodes and edges. Nodes represent entities (files, directories, git repos, markdown documents, etc.) and edges represent relationships between them (contains, has, links_to, etc.).

The Graph type wraps a Storage implementation and provides high-level operations like AddNode, AddEdge, Neighbors, Children, and Parents. It also handles type validation through a Registry.

Storage implementations (like SQLite) handle persistence and querying. The Storage interface is broken into smaller composable interfaces (NodeReader, NodeWriter, etc.) for flexibility.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnknownEdgeType = errors.New("unknown edge type")

ErrUnknownEdgeType is returned when an edge type is not registered.

View Source
var ErrUnknownNodeType = errors.New("unknown node type")

ErrUnknownNodeType is returned when a node type is not registered.

Functions

func IDFromEdgeKey

func IDFromEdgeKey(edgeType, from, to string) string

IDFromEdgeKey generates a deterministic ID from an edge's natural key. The natural key is (type, from, to). Same key always produces the same ID.

func IDFromURI

func IDFromURI(uri string) string

IDFromURI generates a deterministic ID from a URI. The ID is URL-safe base64 encoded (22 characters) derived from SHA256. Same URI always produces the same ID.

func NewID

func NewID() string

NewID generates a new unique node ID using gonanoid.

func RegisterNodeType

func RegisterNodeType[T any](r *Registry, spec NodeSpec)

RegisterNodeType registers a node type with its data schema. The generic type T specifies what type the node's Data field should hold.

Types

type AQLQuerier

type AQLQuerier interface {
	Query(ctx context.Context, query interface{}) (*QueryResult, error)
	Explain(ctx context.Context, query interface{}) (*QueryPlan, error)
}

AQLQuerier provides AQL (Axon Query Language) query execution.

type CountItem added in v0.2.0

type CountItem struct {
	Name  string `json:"name"`
	Count int    `json:"count"`
}

CountItem represents a single aggregated count result (key + count). Used in QueryResult.Counts for GROUP BY queries, preserving SQLite result order.

type DatabaseInfo

type DatabaseInfo interface {
	GetDatabasePath() string
}

DatabaseInfo provides database metadata.

type Describer added in v0.10.0

type Describer interface {
	DescribeSchema(ctx context.Context, includeFields bool) (*SchemaDescription, error)
}

Describer is an optional interface that storage implementations may satisfy to provide schema introspection. It is intentionally NOT embedded in Storage so that existing test mocks are not affected.

includeFields, when true, causes an additional per-type query to discover the JSON data field names actually stored in nodes of each type. This samples up to 500 nodes per type and may be slightly slower on large graphs.

type Direction

type Direction int

Direction specifies the traversal direction for neighbors.

const (
	// Outgoing follows edges from the node (node -> neighbors).
	Outgoing Direction = iota
	// Incoming follows edges to the node (neighbors -> node).
	Incoming
	// Both follows edges in both directions.
	Both
)

type Edge

type Edge struct {
	ID         string     `json:"id,omitempty"`
	Type       string     `json:"type,omitempty"`
	From       string     `json:"from,omitempty"`
	To         string     `json:"to,omitempty"`
	Data       any        `json:"data,omitempty"`
	Generation string     `json:"generation,omitempty"`
	CreatedAt  *time.Time `json:"created_at,omitempty"`
	ExpiresAt  *time.Time `json:"expires_at,omitempty"` // nil means the edge never expires (immortal)
}

Edge represents a directed relationship between two nodes.

func NewEdge

func NewEdge(edgeType, from, to string) *Edge

NewEdge creates a new edge with the given type and endpoints. The edge ID is deterministic based on (type, from, to), ensuring the same edge always has the same ID.

func (*Edge) WithData

func (e *Edge) WithData(data any) *Edge

WithData sets the edge's data payload and returns the edge for chaining.

func (*Edge) WithGeneration

func (e *Edge) WithGeneration(gen string) *Edge

WithGeneration sets the edge's generation for staleness tracking.

func (*Edge) WithTTL added in v0.20.0

func (e *Edge) WithTTL(d time.Duration) *Edge

WithTTL sets the edge's expiry time to now + d. After d has elapsed, the edge is treated as non-existent by all read paths. Pass 0 to leave the edge immortal (same as not calling WithTTL at all). GC must run (axon gc) to physically remove expired rows.

type EdgeConnection added in v0.10.0

type EdgeConnection struct {
	From  string `json:"from"`
	To    string `json:"to"`
	Count int    `json:"count"`
}

EdgeConnection describes a from→to node-type pair for an edge type.

type EdgeFilter

type EdgeFilter struct {
	Type      string      // Filter by exact edge type (empty = any)
	Types     []string    // Filter by multiple edge types (OR logic, empty = any)
	Direction string      // For traversal: "outgoing", "incoming", "both" (default: "outgoing")
	From      *NodeFilter // Filter by from-node properties (nil = any)
	To        *NodeFilter // Filter by to-node properties (nil = any)
}

EdgeFilter specifies criteria for finding/counting edges.

type EdgeQuerier

type EdgeQuerier interface {
	CountEdges(ctx context.Context, filter EdgeFilter, opts QueryOptions) (map[string]int, error)
}

EdgeQuerier provides edge query capabilities.

type EdgeReader

type EdgeReader interface {
	GetEdge(ctx context.Context, id string) (*Edge, error)
	GetEdgesFrom(ctx context.Context, nodeID string) ([]*Edge, error)
	GetEdgesTo(ctx context.Context, nodeID string) ([]*Edge, error)
}

EdgeReader provides read access to edges.

type EdgeSpec

type EdgeSpec struct {
	Type        string
	Description string
	FromTypes   []string // Allowed source node types (empty = any)
	ToTypes     []string // Allowed target node types (empty = any)
}

EdgeSpec describes a registered edge type.

type EdgeStore

type EdgeStore interface {
	EdgeReader
	EdgeWriter
}

EdgeStore combines read and write access to edges.

type EdgeTypeInfo added in v0.10.0

type EdgeTypeInfo struct {
	Type        string           `json:"type"`
	Count       int              `json:"count"`
	Connections []EdgeConnection `json:"connections"`
}

EdgeTypeInfo describes an edge type in the graph.

type EdgeWriter

type EdgeWriter interface {
	PutEdge(ctx context.Context, edge *Edge) error
	DeleteEdge(ctx context.Context, id string) error
}

EdgeWriter provides write access to edges.

type EmbeddingStore added in v0.5.0

type EmbeddingStore interface {
	PutEmbedding(ctx context.Context, nodeID string, embedding []float32) error
	GetEmbedding(ctx context.Context, nodeID string) ([]float32, error)
	FindSimilar(ctx context.Context, query []float32, limit int, filter *NodeFilter) ([]*NodeWithScore, error)
}

EmbeddingStore provides vector embedding storage and similarity search.

type Flusher

type Flusher interface {
	Flush(ctx context.Context) error
}

Flusher provides buffered write flushing.

type Graph

type Graph struct {
	// contains filtered or unexported fields
}

Graph provides high-level operations over the storage layer.

func New

func New(s Storage, r *Registry) *Graph

New creates a new graph with the given storage and registry.

func (*Graph) AddEdge

func (g *Graph) AddEdge(ctx context.Context, e *Edge) error

AddEdge adds an edge to the graph after validating its type and endpoints.

func (*Graph) AddNode

func (g *Graph) AddNode(ctx context.Context, n *Node) error

AddNode adds a node to the graph after validating its type.

func (*Graph) Children

func (g *Graph) Children(ctx context.Context, nodeID string) ([]*Node, error)

Children returns all nodes that this node contains or has (owns). Filters to only "contains" and "has" edges (parent→child relationships).

func (*Graph) CountEdges

func (g *Graph) CountEdges(ctx context.Context, filter EdgeFilter, opts QueryOptions) (map[string]int, error)

CountEdges returns edge counts grouped by the specified field.

func (*Graph) CountNodes

func (g *Graph) CountNodes(ctx context.Context, filter NodeFilter, opts QueryOptions) (map[string]int, error)

CountNodes returns node counts grouped by the specified field.

func (*Graph) DeleteEdge

func (g *Graph) DeleteEdge(ctx context.Context, id string) error

DeleteEdge removes an edge from the graph.

func (*Graph) DeleteNode

func (g *Graph) DeleteNode(ctx context.Context, id string) error

DeleteNode removes a node from the graph.

func (*Graph) FindNodes

func (g *Graph) FindNodes(ctx context.Context, filter NodeFilter, opts QueryOptions) ([]*Node, error)

FindNodes finds nodes matching the given filter.

func (*Graph) GetEdge

func (g *Graph) GetEdge(ctx context.Context, id string) (*Edge, error)

GetEdge retrieves an edge by ID.

func (*Graph) GetEdgesFrom

func (g *Graph) GetEdgesFrom(ctx context.Context, nodeID string) ([]*Edge, error)

GetEdgesFrom returns all edges originating from the given node.

func (*Graph) GetEdgesTo

func (g *Graph) GetEdgesTo(ctx context.Context, nodeID string) ([]*Edge, error)

GetEdgesTo returns all edges pointing to the given node.

func (*Graph) GetNode

func (g *Graph) GetNode(ctx context.Context, id string) (*Node, error)

GetNode retrieves a node by ID.

func (*Graph) GetNodeByURI

func (g *Graph) GetNodeByURI(ctx context.Context, uri string) (*Node, error)

GetNodeByURI retrieves a node by its URI.

func (*Graph) Neighbors

func (g *Graph) Neighbors(ctx context.Context, nodeID string, dir Direction) ([]*Node, error)

Neighbors returns all nodes connected to the given node in the specified direction.

func (*Graph) Parents

func (g *Graph) Parents(ctx context.Context, nodeID string) ([]*Node, error)

Parents returns all nodes that contain or own this node. Filters to only "contained_by" and "belongs_to" edges (child→parent relationships).

func (*Graph) Registry

func (g *Graph) Registry() *Registry

Registry returns the type registry.

func (*Graph) Storage

func (g *Graph) Storage() Storage

Storage returns the underlying storage.

type IndexRunRecord

type IndexRunRecord struct {
	ID           int64
	StartedAt    time.Time
	FinishedAt   time.Time
	DurationMs   int64
	RootPath     string
	FilesIndexed int
	DirsIndexed  int
	ReposIndexed int
	StaleRemoved int
	Generation   string
}

IndexRunRecord represents a single indexing run for tracking history.

type IndexRunTracker

type IndexRunTracker interface {
	RecordIndexRun(ctx context.Context, run IndexRunRecord) error
	GetLastIndexRun(ctx context.Context) (*IndexRunRecord, error)
}

IndexRunTracker tracks indexing run history.

type Node

type Node struct {
	ID         string     `json:"id,omitempty"`
	Type       string     `json:"type,omitempty"`
	URI        string     `json:"uri,omitempty"`
	Key        string     `json:"key,omitempty"`
	Name       string     `json:"name,omitempty"`   // Human-readable name (filename, branch name, section title)
	Labels     []string   `json:"labels,omitempty"` // Categorical labels (e.g., "ci:config", "agent:instructions")
	Data       any        `json:"data,omitempty"`
	Generation string     `json:"generation,omitempty"`
	CreatedAt  *time.Time `json:"created_at,omitempty"`
	UpdatedAt  *time.Time `json:"updated_at,omitempty"`
	ExpiresAt  *time.Time `json:"expires_at,omitempty"` // nil means the node never expires (immortal)
}

Node represents a vertex in the graph.

func NewNode

func NewNode(nodeType string) *Node

NewNode creates a new node with the given type and a generated ID.

func (*Node) AddLabels

func (n *Node) AddLabels(labels ...string)

AddLabels adds labels to the node, deduplicating.

func (*Node) Clone

func (n *Node) Clone() *Node

Clone returns a shallow copy of the node with a cloned Labels slice. The Data field is shared (not deep-copied) since it's typically read-only.

func (*Node) HasLabel

func (n *Node) HasLabel(label string) bool

HasLabel checks if the node has a specific label.

func (*Node) WithData

func (n *Node) WithData(data any) *Node

WithData sets the node's data payload and returns the node for chaining.

func (*Node) WithGeneration

func (n *Node) WithGeneration(gen string) *Node

WithGeneration sets the node's generation for staleness tracking.

func (*Node) WithKey

func (n *Node) WithKey(key string) *Node

WithKey sets the node's natural key and returns the node for chaining.

func (*Node) WithLabels

func (n *Node) WithLabels(labels ...string) *Node

WithLabels adds labels to the node and returns the node for chaining.

func (*Node) WithName

func (n *Node) WithName(name string) *Node

WithName sets the node's human-readable name and returns the node for chaining.

func (*Node) WithTTL added in v0.20.0

func (n *Node) WithTTL(d time.Duration) *Node

WithTTL sets the node's expiry time to now + d. After d has elapsed, the node is treated as non-existent by all read paths. Pass 0 to leave the node immortal (same as not calling WithTTL at all). GC must run (axon gc) to physically remove expired rows.

func (*Node) WithURI

func (n *Node) WithURI(uri string) *Node

WithURI sets the node's URI and returns the node for chaining. It also sets the node's ID to a deterministic value derived from the URI, ensuring the same URI always produces the same node ID.

type NodeFilter

type NodeFilter struct {
	Type        string   // Filter by exact node type (empty = any)
	TypePattern string   // Filter by node type with glob pattern (empty = any)
	URIPrefix   string   // Filter by URI prefix (empty = any)
	Name        string   // Filter by exact name (empty = any)
	NamePattern string   // Filter by name with glob pattern (empty = any)
	Labels      []string // Filter by labels (OR logic - node must have at least one)
	Extensions  []string // Filter by file extension without dot (OR logic, e.g., "go", "py")
	NodeIDs     []string // Filter to specific node IDs (OR logic)
	Generation  string   // Filter by exact generation ID (empty = any). Pass indexer.Context.Generation
	// to scope a query to only nodes written in the current indexing run.
	Root         bool     // Only nodes with no incoming containment edges (top-level roots)
	ExcludeTypes []string // Exclude nodes whose type matches any of these values (OR logic)
}

NodeFilter specifies criteria for finding nodes.

func (NodeFilter) Normalize added in v0.9.1

func (f NodeFilter) Normalize() NodeFilter

Normalize returns a copy of f with normalized field values. Extensions are stripped of any leading dot so that "go" and ".go" are treated identically — callers need not pre-strip the dot.

type NodeQuerier

type NodeQuerier interface {
	FindNodes(ctx context.Context, filter NodeFilter, opts QueryOptions) ([]*Node, error)
	CountNodes(ctx context.Context, filter NodeFilter, opts QueryOptions) (map[string]int, error)
}

NodeQuerier provides node query capabilities.

type NodeReader

type NodeReader interface {
	GetNode(ctx context.Context, id string) (*Node, error)
	GetNodeByURI(ctx context.Context, uri string) (*Node, error)
	GetNodeByKey(ctx context.Context, nodeType, key string) (*Node, error)
}

NodeReader provides read access to nodes.

type NodeSpec

type NodeSpec struct {
	Type        string
	Description string
	DataType    reflect.Type // The Go type for Data field
}

NodeSpec describes a registered node type.

type NodeStore

type NodeStore interface {
	NodeReader
	NodeWriter
}

NodeStore combines read and write access to nodes.

type NodeTypeInfo added in v0.10.0

type NodeTypeInfo struct {
	Type   string   `json:"type"`
	Count  int      `json:"count"`
	Fields []string `json:"fields,omitempty"`
}

NodeTypeInfo describes a node type in the graph.

type NodeWithScore added in v0.5.0

type NodeWithScore struct {
	*Node
	Score float32
}

NodeWithScore is a node with a similarity score for semantic search results.

type NodeWriter

type NodeWriter interface {
	PutNode(ctx context.Context, node *Node) error
	DeleteNode(ctx context.Context, id string) error
}

NodeWriter provides write access to nodes.

type QueryOptions

type QueryOptions struct {
	GroupBy string // "type", "label", "extension" or "" for no grouping
	OrderBy string // "count", "name" for counts; "name", "updated", "type" for nodes
	Desc    bool   // true for descending order
	Limit   int    // 0 for no limit
}

QueryOptions specifies aggregation, ordering, and limiting for queries.

type QueryPlan

type QueryPlan struct {
	SQL         string // Generated SQL query
	Args        []any  // Query arguments
	SQLitePlan  string // Output of EXPLAIN QUERY PLAN
	EstimatedMs int64  // Estimated execution time (if available)
}

QueryPlan holds the execution plan for an AQL query. Used for debugging and performance analysis.

type QueryResult

type QueryResult struct {
	Type            ResultType
	Nodes           []*Node
	Edges           []*Edge
	Counts          []CountItem      // For GROUP BY queries, in SQLite result order
	SelectedColumns []string         // Column names in SELECT order; nil means SELECT *
	Rows            []map[string]any // For ResultTypeRows: multi-variable pattern results
	GroupingColumn  string           // For ResultTypeCounts GROUP BY: the grouping column name
}

QueryResult holds the results of an AQL query execution. Fields are populated based on the result type: - ResultTypeNodes: Nodes slice is populated - ResultTypeEdges: Edges slice is populated - ResultTypeCounts: Counts slice is populated - ResultTypeRows: Rows slice is populated (multi-variable pattern SELECT)

SelectedColumns is set for non-star SELECT queries, in SELECT order.

func (*QueryResult) Count

func (qr *QueryResult) Count() int

Count returns the scalar count value for SELECT COUNT(*) queries. For scalar COUNT queries (no GROUP BY), looks for the "_count" sentinel item. For GROUP BY queries, returns the sum of all counts. Returns 0 if not a count query or if result is empty.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds registered node and edge type specifications.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new empty registry.

func (*Registry) EdgeSpec

func (r *Registry) EdgeSpec(edgeType string) (EdgeSpec, bool)

EdgeSpec returns the spec for an edge type, if registered.

func (*Registry) EdgeTypes

func (r *Registry) EdgeTypes() []string

EdgeTypes returns all registered edge types.

func (*Registry) NodeSpec

func (r *Registry) NodeSpec(nodeType string) (NodeSpec, bool)

NodeSpec returns the spec for a node type, if registered.

func (*Registry) NodeTypes

func (r *Registry) NodeTypes() []string

NodeTypes returns all registered node types.

func (*Registry) RegisterEdgeType

func (r *Registry) RegisterEdgeType(spec EdgeSpec)

RegisterEdgeType registers an edge type.

func (*Registry) ValidateEdge

func (r *Registry) ValidateEdge(e *Edge, fromNode, toNode *Node) error

ValidateEdge validates that an edge's type is registered and that the from/to node types are allowed (if constraints are specified).

func (*Registry) ValidateNode

func (r *Registry) ValidateNode(n *Node) error

ValidateNode validates that a node's type is registered.

type ResultType

type ResultType int

ResultType indicates the type of query result.

const (
	ResultTypeNodes ResultType = iota
	ResultTypeEdges
	ResultTypeCounts
	ResultTypeRows // Multi-variable or cross-variable field-selector pattern queries
)

type SchemaDescription added in v0.10.0

type SchemaDescription struct {
	NodeTypes []NodeTypeInfo `json:"node_types"`
	EdgeTypes []EdgeTypeInfo `json:"edge_types"`
}

SchemaDescription is the result of schema introspection. It describes all node types and edge types currently present in the graph, along with their counts and (optionally) the data field names available on each node type.

type StalenessManager

type StalenessManager interface {
	FindStaleByURIPrefix(ctx context.Context, uriPrefix, currentGen string) ([]*Node, error)
	DeleteStaleByURIPrefix(ctx context.Context, uriPrefix, currentGen string) (int, error)
	DeleteByURIPrefix(ctx context.Context, uriPrefix string) (int, error)
	DeleteStaleEdges(ctx context.Context, currentGen string) (int, error)
	DeleteOrphanedEdges(ctx context.Context) (int, error)
	CountOrphanedEdges(ctx context.Context) (int, error)
	FindOrphanedEdges(ctx context.Context) ([]*Edge, error)
	// DeleteExpired physically removes all nodes and edges whose ExpiresAt is in
	// the past. Returns (nodesDeleted, edgesDeleted, error). This is called by
	// "axon gc" and the background watch-mode ticker.
	DeleteExpired(ctx context.Context) (int64, int64, error)
	// CountExpired returns the count of expired nodes and edges without deleting
	// them. Used for dry-run reporting in "axon gc --dry-run".
	CountExpired(ctx context.Context) (int64, int64, error)
}

StalenessManager handles generation-based cleanup for indexers.

type Storage

Storage defines the complete interface for graph persistence. It composes all the smaller interfaces for full functionality.

Jump to

Keyboard shortcuts

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