Skip to main content

ComponentGraph

Struct ComponentGraph 

Source
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

Source

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).

Source

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).

Source

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.

Source

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.

Source

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 changes
Source

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.

Source

pub fn instance_id(&self) -> &str

Get the instance ID (root node)

Source

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.

Source

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 lock
Source

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()).

Source

pub fn has_runtime(&self, id: &str) -> bool

Check if a component has a runtime instance stored.

Source

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.

Source

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.

Source

pub fn get_component(&self, id: &str) -> Option<&ComponentNode>

Get a component node by ID.

Source

pub fn get_component_mut(&mut self, id: &str) -> Option<&mut ComponentNode>

Get a mutable reference to a component node by ID.

Source

pub fn contains(&self, id: &str) -> bool

Check if a component exists in the graph.

Source

pub fn list_by_kind( &self, kind: &ComponentKind, ) -> Vec<(String, ComponentStatus)>

List all components of a specific kind with their status.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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 subscribers
  • Ok(None) — same-state no-op (component already in target_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?;
Source

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.

Source

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.

Source

pub fn node_count(&self) -> usize

Get the total number of components (including the instance root).

Source

pub fn edge_count(&self) -> usize

Get the total number of edges.

Source

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.

Source

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
Source

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
Source

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
Source

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
Source

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
Source

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 drop
Source

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.

Source

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.

Source

pub fn get_all_events(&self) -> Vec<ComponentEvent>

Get all lifecycle events across all components.

Returns events sorted by timestamp (oldest first).

Source

pub fn get_last_error(&self, component_id: &str) -> Option<String>

Get the most recent error message for a component.

Source

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.

Trait Implementations§

Source§

impl Debug for ComponentGraph

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more