hinge 0.1.0

SQL-native ELT engine — dependency graph resolved automatically from FROM/JOIN clauses, parallel execution, single binary
Documentation
use crate::application::Engine;
use crate::application::{Executor, ExecutorError};
use crate::domain::{Asset, Graph};
use futures::future::try_join_all;

/// Executes the full upstream subgraph, the target asset, then the full downstream subgraph.
///
/// Combines [`RunUpstream`] and [`RunDownstream`] in a single pass.
/// Use this when you have changed a mid-graph asset and need to rebuild its
/// entire lineage in one operation.
///
/// [`RunUpstream`]: crate::RunUpstream
/// [`RunDownstream`]: crate::RunDownstream
pub struct RunBidirectional<E> {
    engine: Engine,
    executor: E,
}

impl<E: Executor> RunBidirectional<E> {
    pub fn new(graph: Graph, executor: E) -> Self {
        Self { engine: Engine::new(graph), executor }
    }

    /// Run ancestors (parallel waves) → `start` → descendants (parallel waves).
    pub async fn execute(&self, start: &Asset) -> Result<(), ExecutorError> {
        for wave in self.engine.upstream_levels(start) {
            let futs: Vec<_> = wave.iter().map(|asset| self.executor.run(asset)).collect();
            try_join_all(futs).await?;
        }
        self.executor.run(start).await?;
        for wave in self.engine.downstream_levels(start) {
            let futs: Vec<_> = wave.iter().map(|asset| self.executor.run(asset)).collect();
            try_join_all(futs).await?;
        }
        Ok(())
    }

    /// Returns the planned execution order (flat topo, for display / dry-run).
    pub fn plan(&self, start: &Asset) -> Vec<Asset> {
        let mut plan = self.engine.propagate_upstream(start);
        plan.push(start.clone());
        plan.extend(self.engine.propagate_downstream(start));
        plan
    }
}