Skip to main content

surreal_sync_runtime/pipeline/apply/
feed.rs

1//! Change feed trait: thin adapter over [`crate::pipeline::SourceDriver`] for tests and simple row feeds.
2//!
3//! Prefer [`crate::pipeline::SourceDriver`] + [`crate::pipeline::run_source_runtime`] for production ports.
4//! [`ChangeFeed`] / [`crate::pipeline::run_change_feed`] remain for tests and simple row-CDC sources
5//! that do not need schema refresh, ad-hoc snapshot, or other control hooks.
6
7use crate::pipeline::apply::event::{ApplyEvent, PositionedEvent};
8use anyhow::Result;
9use surreal_sync_core::Change;
10
11/// A CDC / incremental row event plus the source position to advance after sink success.
12///
13/// Prefer [`PositionedEvent`] when the feed may also emit relation changes.
14#[derive(Debug, Clone)]
15pub struct PositionedChange<P> {
16    /// Universal change to transform and apply.
17    pub change: Change,
18    /// Source position associated with this change (checkpoint candidate).
19    pub position: P,
20}
21
22impl<P> PositionedChange<P> {
23    /// Construct a positioned change.
24    pub fn new(change: Change, position: P) -> Self {
25        Self { change, position }
26    }
27
28    /// Convert to the unified [`PositionedEvent`] form.
29    pub fn into_event(self) -> PositionedEvent<P> {
30        PositionedEvent::new(ApplyEvent::Change(self.change), self.position)
31    }
32}
33
34impl<P> From<PositionedChange<P>> for PositionedEvent<P> {
35    fn from(pc: PositionedChange<P>) -> Self {
36        pc.into_event()
37    }
38}
39
40/// Source-facing incremental **row** feed: poll events, advance watermark after durable sink apply.
41///
42/// Sources do **not** batch for transforms — the shared apply loop owns batching,
43/// the in-flight window, ordered sink apply, and contiguous sink-safe watermark.
44///
45/// For sources that also emit relation edges or need DDL / cancel / checkpoint
46/// hooks, implement [`crate::pipeline::SourceDriver`] instead (or wrap this feed with
47/// [`crate::pipeline::ChangeFeedDriver`]).
48#[async_trait::async_trait]
49pub trait ChangeFeed: Send {
50    /// Checkpoint / resume position type.
51    type Position: Clone + Send + Sync + 'static;
52
53    /// Next source events (may be empty on idle).
54    async fn poll_changes(&mut self) -> Result<Vec<PositionedChange<Self::Position>>>;
55
56    /// Mark `position` sink-safe. May be in-memory only, broker offset commit, or
57    /// both — durable store writes belong in
58    /// [`SourceDriver::persist_checkpoint`](crate::pipeline::SourceDriver::persist_checkpoint)
59    /// unless policy is [`CheckpointPolicy::AdvanceOnly`](crate::pipeline::CheckpointPolicy::AdvanceOnly).
60    async fn advance_watermark(&mut self, position: Self::Position) -> Result<()>;
61
62    /// Whether the feed will produce no more changes (EOF).
63    ///
64    /// Default: `false` (endless sources). Scripted / finite feeds override so
65    /// [`crate::pipeline::run_change_feed`] can exit after draining in-flight work.
66    fn is_finished(&self) -> bool {
67        false
68    }
69}