pub struct ComponentGraph { /* private fields */ }Expand description
Central component dependency graph — the single source of truth.
Backed by petgraph::stable_graph::StableGraph which keeps node/edge indices
stable across removals (critical for a graph that changes at runtime).
§Error Handling
Mutation methods on this struct return anyhow::Result, following the Layer 2
(internal module) error convention. The public API boundary in DrasiLib and
lib_core_ops wraps these into DrasiError variants before returning to callers.
External consumers should use DrasiLib methods rather than calling graph
mutations directly.
§Event Emission
The graph emits ComponentEvents via a built-in broadcast channel whenever
components are added, removed, or change status. Call subscribe()
to receive events.
§Thread Safety
ComponentGraph is NOT Send/Sync by itself due to the underlying StableGraph.
It must be wrapped in Arc<RwLock<ComponentGraph>> for multi-threaded access.
All public APIs in DrasiLib handle this wrapping automatically.
§Instance Root
The graph always has the DrasiLib instance as its root node. All other components are connected to it via Owns/OwnedBy edges.
Implementations§
Source§impl ComponentGraph
impl ComponentGraph
Sourcepub fn new(instance_id: &str) -> (Self, ComponentUpdateReceiver)
pub fn new(instance_id: &str) -> (Self, ComponentUpdateReceiver)
Create a new component graph with the given instance as root node.
Returns the graph and a ComponentUpdateReceiver that must be consumed by
a graph update loop task (see Self::apply_update).
Sourcepub fn subscribe(&self) -> ComponentEventBroadcastReceiver
pub fn subscribe(&self) -> ComponentEventBroadcastReceiver
Subscribe to component lifecycle events.
Returns a broadcast receiver that gets a copy of every ComponentEvent
emitted by graph mutations (add_component, remove_component, update_status).
Sourcepub fn event_sender(&self) -> &Sender<ComponentEvent>
pub fn event_sender(&self) -> &Sender<ComponentEvent>
Get a reference to the broadcast sender for component events.
This allows callers to clone the sender before the graph is wrapped in
Arc<RwLock<>>, enabling subscription without needing to acquire the lock.
The returned sender is the same one used by the graph for event emission.
Sourcepub fn update_sender(&self) -> ComponentUpdateSender
pub fn update_sender(&self) -> ComponentUpdateSender
Get a clone of the mpsc update sender.
This is the sender that components use to report status changes without
acquiring any graph lock. Clone this and pass to SourceBase/ReactionBase.
Sourcepub fn status_notifier(&self) -> Arc<Notify>
pub fn status_notifier(&self) -> Arc<Notify>
Get a clone of the status change notifier.
This Notify is signalled whenever any component’s status changes in the graph.
Use it with [wait_for_status] or build custom wait loops that avoid polling
with sleep.
§Pattern
let notify = graph.status_notifier();
// Register interest BEFORE releasing the graph lock
let notified = notify.notified();
drop(graph); // release lock
notified.await; // woken when any status changesSourcepub fn apply_update(
&mut self,
update: ComponentUpdate,
) -> Option<ComponentEvent>
pub fn apply_update( &mut self, update: ComponentUpdate, ) -> Option<ComponentEvent>
Apply a ComponentUpdate received from the mpsc channel.
Called by the graph update loop task. Updates the graph, emits a broadcast event, and records the event in the centralized event history. Returns the event for external logging/processing.
Sourcepub fn instance_id(&self) -> &str
pub fn instance_id(&self) -> &str
Get the instance ID (root node)
Sourcepub fn set_runtime(
&mut self,
id: &str,
runtime: Box<dyn Any + Send + Sync>,
) -> Result<()>
pub fn set_runtime( &mut self, id: &str, runtime: Box<dyn Any + Send + Sync>, ) -> Result<()>
Store a runtime instance for a component.
The component must already exist in the graph (registered via add_component
or one of the register_* methods). The runtime instance is stored in a
type-erased map keyed by component ID.
Managers call this during provisioning after creating the runtime instance:
let source: Arc<dyn Source> = Arc::new(my_source);
source.initialize(context).await;
graph.set_runtime("source-1", Box::new(source))?;§Errors
Returns an error if the component ID is not present in the graph.
Sourcepub fn get_runtime<T: 'static>(&self, id: &str) -> Option<&T>
pub fn get_runtime<T: 'static>(&self, id: &str) -> Option<&T>
Retrieve a reference to a component’s runtime instance, downcasting to T.
Returns None if the component has no runtime instance or if the stored
type doesn’t match T.
§Type Parameter
T is typically Arc<dyn Source>, Arc<dyn Query>, or Arc<dyn Reaction>.
§Example
let source: &Arc<dyn Source> = graph.get_runtime::<Arc<dyn Source>>("source-1")
.ok_or_else(|| anyhow!("Source not found"))?;
let source = source.clone(); // Clone the Arc for use outside the lockSourcepub fn take_runtime<T: 'static>(&mut self, id: &str) -> Option<T>
pub fn take_runtime<T: 'static>(&mut self, id: &str) -> Option<T>
Remove and return a component’s runtime instance, downcasting to T.
Returns None if the component has no runtime instance or if the stored
type doesn’t match T. On type mismatch, the runtime is put back into
the store (not lost) and an error is logged.
Used during teardown when the manager needs ownership of the instance
(e.g., to call deprovision()).
Sourcepub fn has_runtime(&self, id: &str) -> bool
pub fn has_runtime(&self, id: &str) -> bool
Check if a component has a runtime instance stored.
Sourcepub fn add_component(&mut self, node: ComponentNode) -> Result<NodeIndex>
pub fn add_component(&mut self, node: ComponentNode) -> Result<NodeIndex>
Add a component node to the graph.
Automatically creates bidirectional Owns/OwnedBy edges between the
instance root and the new component. Emits a ComponentEvent with
the node’s current status to all subscribers.
§Errors
Returns an error if a component with the same ID already exists.
Sourcepub fn remove_component(&mut self, id: &str) -> Result<ComponentNode>
pub fn remove_component(&mut self, id: &str) -> Result<ComponentNode>
Remove a component node and all its edges from the graph.
Emits a ComponentEvent with status ComponentStatus::Stopped to all
subscribers. Returns the removed node data, or an error if the component
doesn’t exist. The instance root node cannot be removed.
Sourcepub fn get_component(&self, id: &str) -> Option<&ComponentNode>
pub fn get_component(&self, id: &str) -> Option<&ComponentNode>
Get a component node by ID.
Sourcepub fn get_component_mut(&mut self, id: &str) -> Option<&mut ComponentNode>
pub fn get_component_mut(&mut self, id: &str) -> Option<&mut ComponentNode>
Get a mutable reference to a component node by ID.
Sourcepub fn list_by_kind(
&self,
kind: &ComponentKind,
) -> Vec<(String, ComponentStatus)>
pub fn list_by_kind( &self, kind: &ComponentKind, ) -> Vec<(String, ComponentStatus)>
List all components of a specific kind with their status.
Sourcepub fn add_relationship(
&mut self,
from_id: &str,
to_id: &str,
forward: RelationshipKind,
) -> Result<()>
pub fn add_relationship( &mut self, from_id: &str, to_id: &str, forward: RelationshipKind, ) -> Result<()>
Add a bidirectional relationship between two components (idempotent).
Creates both the forward edge (from → to with forward relationship) and
the reverse edge (to → from with the reverse of forward).
If the relationship already exists, this is a no-op and returns Ok(()).
§Errors
Returns an error if either component doesn’t exist.
Sourcepub fn remove_relationship(
&mut self,
from_id: &str,
to_id: &str,
forward: RelationshipKind,
) -> Result<()>
pub fn remove_relationship( &mut self, from_id: &str, to_id: &str, forward: RelationshipKind, ) -> Result<()>
Remove a bidirectional relationship between two components.
Removes both the forward edge (from → to with forward relationship) and
the reverse edge (to → from with the reverse of forward).
If the relationship doesn’t exist, this is a no-op and returns Ok(()).
§Errors
Returns an error if either component doesn’t exist in the graph.
Sourcepub fn get_neighbors(
&self,
id: &str,
relationship: &RelationshipKind,
) -> Vec<&ComponentNode>
pub fn get_neighbors( &self, id: &str, relationship: &RelationshipKind, ) -> Vec<&ComponentNode>
Get all components that this component has outgoing edges to, filtered by relationship kind.
Sourcepub fn get_dependents(&self, id: &str) -> Vec<&ComponentNode>
pub fn get_dependents(&self, id: &str) -> Vec<&ComponentNode>
Get all components that depend on the given component.
“Dependents” are components that would be affected if this component were removed or stopped. This follows Feeds edges (outgoing).
Sourcepub fn get_dependencies(&self, id: &str) -> Vec<&ComponentNode>
pub fn get_dependencies(&self, id: &str) -> Vec<&ComponentNode>
Get all components that this component depends on.
“Dependencies” are components that this component needs to function. This follows SubscribesTo edges (outgoing).
Sourcepub fn can_remove(&self, id: &str) -> Result<(), Vec<String>>
pub fn can_remove(&self, id: &str) -> Result<(), Vec<String>>
Check if a component can be safely removed (no dependents that would break).
Returns Ok(()) if safe, or Err with the list of dependent component IDs.
Sourcepub fn validate_and_transition(
&mut self,
id: &str,
target_status: ComponentStatus,
message: Option<String>,
) -> Result<Option<ComponentEvent>>
pub fn validate_and_transition( &mut self, id: &str, target_status: ComponentStatus, message: Option<String>, ) -> Result<Option<ComponentEvent>>
Atomically validate and apply a commanded status transition.
This is the single canonical way for managers to change a component’s
status for command-initiated transitions (Starting, Stopping,
Reconfiguring). It combines validation and mutation under a single
&mut self borrow, eliminating the TOCTOU gap between checking status
and updating it.
Components still report runtime-initiated transitions (Running,
Stopped, Error) via the mpsc channel → [apply_update].
§Returns
Ok(Some(event))— transition applied, event emitted to broadcast subscribersOk(None)— same-state no-op (component already intarget_status)Err(...)— component not found or transition not valid from current state
§Example
let mut graph = self.graph.write().await;
graph.validate_and_transition("source-1", ComponentStatus::Starting, Some("Starting source"))?;
drop(graph); // release lock before calling source.start()
source.start().await?;Sourcepub fn topological_order(&self) -> Result<Vec<&ComponentNode>>
pub fn topological_order(&self) -> Result<Vec<&ComponentNode>>
Get a topological ordering of components for lifecycle operations.
Returns components in dependency order: sources first, then queries, then reactions. Only follows Feeds edges for ordering (other edge types don’t affect lifecycle order).
The instance root node is excluded from the result.
Sourcepub fn snapshot(&self) -> GraphSnapshot
pub fn snapshot(&self) -> GraphSnapshot
Create a serializable snapshot of the entire graph.
The snapshot includes the instance root node and all components with their relationships. Used for API responses and UI visualization.
Sourcepub fn node_count(&self) -> usize
pub fn node_count(&self) -> usize
Get the total number of components (including the instance root).
Sourcepub fn edge_count(&self) -> usize
pub fn edge_count(&self) -> usize
Get the total number of edges.
Sourcepub fn register_source(
&mut self,
id: &str,
metadata: HashMap<String, String>,
) -> Result<()>
pub fn register_source( &mut self, id: &str, metadata: HashMap<String, String>, ) -> Result<()>
Register a source component in the graph.
Creates the node and bidirectional ownership edges transactionally. Events are emitted only on successful commit.
§Errors
Returns an error if a component with the same ID already exists.
Sourcepub fn register_query(
&mut self,
id: &str,
metadata: HashMap<String, String>,
source_ids: &[String],
) -> Result<()>
pub fn register_query( &mut self, id: &str, metadata: HashMap<String, String>, source_ids: &[String], ) -> Result<()>
Register a query component with its source dependencies.
Creates the node, ownership edges, and Feeds edges from each source.
All operations are transactional — if any dependency is missing or any
step fails, the entire registration is rolled back.
§Errors
Returns an error if:
- A component with the same ID already exists
- Any referenced source does not exist in the graph
Sourcepub fn register_reaction(
&mut self,
id: &str,
metadata: HashMap<String, String>,
query_ids: &[String],
) -> Result<()>
pub fn register_reaction( &mut self, id: &str, metadata: HashMap<String, String>, query_ids: &[String], ) -> Result<()>
Register a reaction component with its query dependencies.
Creates the node, ownership edges, and Feeds edges from each query.
All operations are transactional — if any dependency is missing or any
step fails, the entire registration is rolled back.
§Errors
Returns an error if:
- A component with the same ID already exists
- Any referenced query does not exist in the graph
Sourcepub fn deregister(&mut self, id: &str) -> Result<ComponentNode>
pub fn deregister(&mut self, id: &str) -> Result<ComponentNode>
Deregister a component and all its edges from the graph.
Validates that the component exists and has no dependents before removal. The instance root node cannot be deregistered.
§Errors
Returns an error if:
- The component does not exist
- The component has dependents (use
can_remove()to check first) - The component is the instance root node
Sourcepub fn register_bootstrap_provider(
&mut self,
id: &str,
metadata: HashMap<String, String>,
source_ids: &[String],
) -> Result<()>
pub fn register_bootstrap_provider( &mut self, id: &str, metadata: HashMap<String, String>, source_ids: &[String], ) -> Result<()>
Register a bootstrap provider in the graph for topology visibility.
Creates the node and bidirectional ownership edges transactionally.
Optionally links the provider to its target source via Bootstraps edges.
§Usage
Bootstrap providers are managed internally by source plugins (set via
Source::set_bootstrap_provider()). Call this method to make a bootstrap
provider visible in the component graph for topology visualization and
dependency tracking.
// After adding a source with a bootstrap provider:
let mut graph = core.component_graph().write().await;
graph.register_bootstrap_provider("my-bootstrap", metadata, &["my-source".into()])?;§Errors
Returns an error if:
- A component with the same ID already exists
- Any referenced source does not exist in the graph
Sourcepub fn register_identity_provider(
&mut self,
id: &str,
metadata: HashMap<String, String>,
component_ids: &[String],
) -> Result<()>
pub fn register_identity_provider( &mut self, id: &str, metadata: HashMap<String, String>, component_ids: &[String], ) -> Result<()>
Register an identity provider in the graph for topology visibility.
Creates the node and bidirectional ownership edges transactionally.
Optionally links the provider to components it authenticates via
Authenticates edges.
§Current Status
This method is reserved for future use. Identity provider support is not yet implemented in the component lifecycle. The registration infrastructure is in place for when authentication integration is added.
§Errors
Returns an error if:
- A component with the same ID already exists
- Any referenced component does not exist in the graph
Sourcepub fn begin(&mut self) -> GraphTransaction<'_>
pub fn begin(&mut self) -> GraphTransaction<'_>
Begin a transactional mutation of the graph.
Returns a GraphTransaction that collects mutations (nodes, edges)
and defers event emission until commit().
If the transaction is dropped without being committed, all added nodes
and edges are rolled back automatically.
The &mut self borrow ensures compile-time exclusivity — no other code
can access the graph while a transaction is in progress.
§Example
let mut graph = self.graph.write().await;
let mut txn = graph.begin();
txn.add_component(source_node)?;
txn.add_relationship("source-1", "query-1", RelationshipKind::Feeds)?;
txn.commit(); // events emitted here; if this line is not reached, rollback on dropSourcepub fn record_event(&mut self, event: ComponentEvent)
pub fn record_event(&mut self, event: ComponentEvent)
Record a component event in the centralized history.
Called internally by [apply_update()]. Managers should NOT call this
directly — status updates flow through the mpsc channel and are recorded
automatically.
Sourcepub fn get_events(&self, component_id: &str) -> Vec<ComponentEvent>
pub fn get_events(&self, component_id: &str) -> Vec<ComponentEvent>
Get all lifecycle events for a specific component.
Returns events in chronological order (oldest first). Up to 100 most recent events are retained per component.
Sourcepub fn get_all_events(&self) -> Vec<ComponentEvent>
pub fn get_all_events(&self) -> Vec<ComponentEvent>
Get all lifecycle events across all components.
Returns events sorted by timestamp (oldest first).
Sourcepub fn get_last_error(&self, component_id: &str) -> Option<String>
pub fn get_last_error(&self, component_id: &str) -> Option<String>
Get the most recent error message for a component.
Sourcepub fn subscribe_events(
&mut self,
component_id: &str,
) -> (Vec<ComponentEvent>, Receiver<ComponentEvent>)
pub fn subscribe_events( &mut self, component_id: &str, ) -> (Vec<ComponentEvent>, Receiver<ComponentEvent>)
Subscribe to live lifecycle events for a component.
Returns the current history and a broadcast receiver for new events. Creates the component’s event channel if it doesn’t exist.