aster_forge_cloud_files_core/
change.rs

1//! Incremental backend changes, deletion tombstones, and explicit cursor resets.
2
3use crate::{ChangeCursor, CloudItem, CloudItemId, CloudItemKey, MetadataRevision};
4
5/// One product-neutral remote change.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum CloudChange {
8    /// Creates an item or replaces its known metadata/content state.
9    Upsert {
10        /// Complete current item state.
11        item: CloudItem,
12    },
13    /// Removes an item using a durable deletion tombstone.
14    Delete {
15        /// Fully scoped stable identity of the deleted item.
16        key: CloudItemKey,
17        /// Last known parent identity, when supplied by the backend or local baseline.
18        previous_parent_id: Option<CloudItemId>,
19        /// Deletion/metadata revision, when the backend exposes one.
20        metadata_revision: Option<MetadataRevision>,
21    },
22}
23
24impl CloudChange {
25    /// Returns the fully scoped item identity affected by this change.
26    #[must_use]
27    pub const fn key(&self) -> &CloudItemKey {
28        match self {
29            Self::Upsert { item } => item.key(),
30            Self::Delete { key, .. } => key,
31        }
32    }
33}
34
35/// One durable batch from an anchored backend change stream.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct ChangeBatch {
38    changes: Vec<CloudChange>,
39    next_cursor: ChangeCursor,
40    has_more: bool,
41}
42
43impl ChangeBatch {
44    /// Creates a change batch. The cursor becomes active only after effects are durably replayable.
45    #[must_use]
46    pub const fn new(changes: Vec<CloudChange>, next_cursor: ChangeCursor, has_more: bool) -> Self {
47        Self {
48            changes,
49            next_cursor,
50            has_more,
51        }
52    }
53
54    /// Returns changes in backend-defined application order.
55    #[must_use]
56    pub fn changes(&self) -> &[CloudChange] {
57        &self.changes
58    }
59
60    /// Returns the checkpoint following this batch.
61    #[must_use]
62    pub const fn next_cursor(&self) -> &ChangeCursor {
63        &self.next_cursor
64    }
65
66    /// Returns whether another batch is immediately available.
67    #[must_use]
68    pub const fn has_more(&self) -> bool {
69        self.has_more
70    }
71
72    /// Consumes the batch and returns its changes, next cursor, and continuation flag.
73    #[must_use]
74    pub fn into_parts(self) -> (Vec<CloudChange>, ChangeCursor, bool) {
75        (self.changes, self.next_cursor, self.has_more)
76    }
77}
78
79/// Reason an existing change checkpoint can no longer continue incrementally.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
81pub enum ChangeResetReason {
82    /// The backend no longer retains changes after the supplied cursor.
83    CursorExpired,
84    /// The backend rebuilt or replaced its change-tracking state.
85    BackendStateRebuilt,
86    /// Stable item identifiers or their mapping can no longer be trusted.
87    IdentityMappingInvalidated,
88}
89
90/// Result of requesting the next anchored change page.
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum ChangePage {
93    /// A replayable batch and its following checkpoint.
94    Batch(ChangeBatch),
95    /// Incremental continuation stopped and requires reconciliation or reimport.
96    ResetRequired {
97        /// Product-neutral reset classification.
98        reason: ChangeResetReason,
99    },
100}