tinyflow-framework 0.1.0

Streaming runtime engine — checkpoint, chained task execution, and state store
Documentation
//! Linear operator chain DSL (`map`, `filter`, `flat_map`, `sink`, `name`).
//!
//! Build-time errors on the chain **panic** (duplicate source, steps after terminal `sink`).
//! Runtime errors use [`crate::api::error::StreamResult`] in [`crate::stream::chain_execute::execute`].
//!
//! Terminate with [`DataStream::sink`], [`crate::connectors::print::PrintDataStreamExt::print`], or
//! [`crate::connectors::iceberg::data_stream_ext::IcebergDataStreamExt::add_iceberg_sink`],
//! then [`crate::env::stream_env::StreamEnv::execute`].

use std::any::TypeId;
use std::marker::PhantomData;

use crate::env::stream_env::StreamEnv;
use crate::runtime::chain_job::{ChainOperatorKind, ChainStep};
use crate::runtime::chain_segment::ChainSegmentFactory;
use tinyflow_api::functions::*;

pub struct DataStream<T> {
    pub(crate) env: StreamEnv,
    pub(crate) tail_step_index: usize,
    pub(crate) _marker: PhantomData<T>,
}

impl<T: 'static> DataStream<T> {
    pub(crate) fn new(env: StreamEnv, tail_step_index: usize) -> Self {
        Self {
            env,
            tail_step_index,
            _marker: PhantomData,
        }
    }

    /// Sets the user-visible name for the current tail operator.
    pub fn name(self, n: impl Into<String>) -> Self {
        let name = n.into();
        let tail = self.tail_step_index;
        {
            let mut job = self.env.chain_job.lock().unwrap();
            if let Some(job) = job.as_mut()
                && tail < job.steps.len()
            {
                job.steps[tail].user_name = Some(name);
            }
        }
        self
    }

    fn push_step<OUT: Send + 'static>(
        self,
        kind: ChainOperatorKind,
        factory: ChainSegmentFactory,
    ) -> DataStream<OUT> {
        let mut job_guard = self.env.chain_job.lock().unwrap();
        let job = job_guard
            .as_mut()
            .expect("ChainJob missing; call add_source first");
        job.push_step_with_types(
            ChainStep {
                kind,
                user_name: None,
                factory,
            },
            Some(TypeId::of::<T>()),
            TypeId::of::<OUT>(),
        );
        let idx = job.steps.len() - 1;
        DataStream::new(self.env.clone(), idx)
    }
}

impl<T: Send + Clone + 'static> DataStream<T> {
    pub fn map<O: Send + Clone + 'static, F: MapFunction<T, O> + Clone + Send + 'static>(
        self,
        f: F,
    ) -> DataStream<O> {
        let factory = crate::runtime::chain_segment::map_chain_factory(f);
        self.push_step(ChainOperatorKind::Map, factory)
    }

    pub fn filter<F: FilterFunction<T> + Clone + Send + 'static>(self, f: F) -> DataStream<T> {
        let factory = crate::runtime::chain_segment::filter_chain_factory(f);
        self.push_step(ChainOperatorKind::Filter, factory)
    }

    pub fn flat_map<
        O: Send + Clone + 'static,
        F: FlatMapFunction<T, O> + Clone + Send + 'static,
    >(
        self,
        f: F,
    ) -> DataStream<O> {
        let factory = crate::runtime::chain_segment::flat_map_chain_factory(f);
        self.push_step(ChainOperatorKind::FlatMap, factory)
    }

    /// Terminal sink; freezes the chain (no further operators).
    pub fn sink<F>(self, f: F)
    where
        F: SinkFunction<T> + Clone + Send + 'static,
    {
        self.sink_named_impl(None, f);
    }

    /// Terminal sink with a user-visible operator name (for logs and `RuntimeContext::task_name`).
    pub fn sink_named<F>(self, name: impl Into<String>, f: F)
    where
        F: SinkFunction<T> + Clone + Send + 'static,
    {
        self.sink_named_impl(Some(name.into()), f);
    }

    fn sink_named_impl<F>(self, user_name: Option<String>, f: F)
    where
        F: SinkFunction<T> + Clone + Send + 'static,
    {
        let factory = crate::runtime::chain_segment::sink_chain_factory(f);
        let mut job_guard = self.env.chain_job.lock().unwrap();
        let job = job_guard
            .as_mut()
            .expect("ChainJob missing; call add_source first");
        job.push_step_with_types(
            ChainStep {
                kind: ChainOperatorKind::Sink,
                user_name,
                factory,
            },
            Some(TypeId::of::<T>()),
            TypeId::of::<T>(),
        );
        drop(job_guard);
    }
}