Skip to content
Arxo Arxo

Graph Data Structures

:::info For engine extenders and JSON shape When using the closed-source engine via arxo-loader or the FFI, you receive a single JSON string; you do not get direct access to these Rust graph types. This page documents the structure of the data the engine produces (and that may appear in the result JSON or in internal/plugin code). For the public API see Rust API and FFI API. The DataStore page describes how plugins access these graphs inside the engine. :::

The engine produces several graph structures. When extending the engine you access them via the DataStore. All graph types are defined in arxo-types (MIT-licensed).

File-level nodes are identified by NodeId: an interned file path (Arc<str>). Use it as a key for lookups and as source/target in edges.

use arxo_types::core::types::NodeId;
let id = NodeId::from("src/main.ts");
assert_eq!(id.as_str(), "src/main.ts");

ImportGraph represents module/file dependencies: who imports whom.

Each node is a NodeMetadata:

FieldTypeDescription
idNodeIdFile path identifier
pathPathBufFile path
groupOption<String>Group label (from grouping)
layerOption<String>Architecture layer, if configured
sizeusizeSize metric (e.g. lines)
member_idsOption<Vec<NodeId>>Member file IDs when grouped
package_nameOption<String>Package name when in a workspace
package_rootOption<PathBuf>Package root path

Edges carry EdgeData with an EdgeType:

VariantDescription
ImportNormal import
ReexportRe-export from another module
DynamicImportDynamic import (e.g. import())
TypeOnlyType-only import (e.g. import type)
RequireCommonJS-style require()
BarrelImportBarrel file re-export
SideEffectSide-effect-only import

EdgeData also has weight (e.g. for metrics) and optional original_import (raw specifier).

use arxo_types::data::import_graph::{ImportGraph, NodeMetadata};
// Counts
graph.node_count();
graph.edge_count();
// Lookup by NodeId
graph.get_node(&node_id) -> Option<&NodeMetadata>
// Petgraph access (for iteration, algorithms)
graph.inner() -> &Graph<NodeMetadata, EdgeData, Directed>
graph.node_indices() -> Iterator<NodeIndex>
graph.node_weight(idx) -> Option<&NodeMetadata>
graph.neighbors(idx) -> Neighbors
graph.edges(idx) -> Edges

Use graph.inner() with petgraph for algorithms (e.g. DFS, SCC). The engine also exposes a precomputed SCC DAG via the DataStore for cycle analysis.


CallGraph represents file-to-file call relationships: which file’s code calls into which other file.

Each edge has CallEdgeData:

FieldTypeDescription
edge_typeCallEdgeTypeKind of call
is_preciseboolResolved to a single target
confidencef640.0–1.0 resolution confidence
resolution_methodResolutionMethodHow the target was resolved
call_countusizeNumber of call sites
source_fileNodeIdFile where the call appears

CallEdgeType:

VariantDescription
DirectCallDirect function call
MethodCallMethod call on an object
ConstructorConstructor/new call
HigherOrderCallback passed to another function

ResolutionMethod (examples): LocalDefinition, DirectImport, RustAnalyzer, Inheritance, SamePackage, TypeIndex, GlobalNameMatch, etc. Each has a confidence() (e.g. 1.0 for LocalDefinition, lower for heuristics). You can filter edges by min_confidence in configuration.

use arxo_types::data::call_graph::{CallGraph, CallEdgeData, CallEdgeType, ResolutionMethod};
graph.node_count();
graph.edge_count();
graph.get_node(&node_id) -> Option<&NodeMetadata>
graph.inner() -> &Graph<..., CallEdgeData, Directed>

The DataStore also exposes call reachability, call dependencies, and call SCC DAG for transitive and cycle analysis.


EntityGraph is a function/class/variable-level call graph: nodes are entities (e.g. functions, classes), not files.

Entity IDs are strings: "file_id::symbol" (e.g. "src/utils.ts::formatDate"). Use entity_id(file_id, symbol) to build them.

EntityNodeMetadata:

FieldTypeDescription
idEntityIdfile_id::symbol
file_idNodeIdContaining file
symbolStringSymbol name
kindEntityKindEntity kind

EntityKind:

VariantDescription
FunctionFunction or method
ClassClass
VariableVariable
ConstantConstant
ModuleModule-level (e.g. top-level script)
UnknownCould not classify

EntityEdgeData mirrors call graph edge data: edge_type, is_precise, confidence, resolution_method, call_count, source_file.

use arxo_types::data::entity_graph::{
EntityGraph, EntityId, EntityKind, EntityNodeMetadata,
entity_id,
};
let eid = entity_id(&node_id, "formatDate");
graph.get_entity(&eid) -> Option<&EntityNodeMetadata>
graph.node_count();
graph.edge_count();
graph.inner() -> &Graph<EntityNodeMetadata, EntityEdgeData, Directed>

TypeGraph represents type relationships: inheritance, implementations, type aliases.

Type IDs are strings: "file_id::TypeName". Use type_id(file_id, type_name) to build them.

TypeNodeMetadata:

FieldTypeDescription
idTypeIdfile_id::TypeName
file_idNodeIdDeclaring file
nameStringType name
qualified_nameOption<String>Fully qualified (e.g. Java pkg.Class)
kindTypeKindKind of type
is_abstractboolAbstract type

TypeKind:

VariantDescription
ClassClass
InterfaceInterface
TraitTrait (e.g. Rust)
StructStruct
EnumEnum
RecordRecord/object type
TypeAliasType alias
UnknownCould not classify

TypeEdgeData has TypeEdgeType and weight:

VariantDescriptionTypical weight
ExtendsClass extends class1.0
ImplementsClass implements interface0.9
TraitImplimpl Trait for Type (Rust)0.85
TypeAliastype Foo = Bar0.5
GenericBoundT: Trait, T extends Foo0.3
use arxo_types::data::type_graph::{
TypeGraph, TypeId, TypeKind, TypeNodeMetadata, TypeEdgeType,
type_id,
};
graph.get_type(&type_id) -> Option<&TypeNodeMetadata>
graph.node_count();
graph.edge_count();
graph.inner() -> &Graph<TypeNodeMetadata, TypeEdgeData, Directed>

Graphs support serialization for caching and transport. The engine uses to_serializable() / from_serializable() (or equivalent) for bincode cache; the same types are JSON-serializable via serde for the report and FFI output.