thread_flow/incremental/mod.rs
1// SPDX-FileCopyrightText: 2025 Knitli Inc. <knitli@knit.li>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! # Incremental Update System
5//!
6//! This module implements Thread's incremental update system for dependency-aware
7//! invalidation and targeted re-analysis. It adapts patterns from ReCoco's
8//! `FieldDefFingerprint` design to Thread's AST analysis domain.
9//!
10//! ## Architecture
11//!
12//! The system consists of four integrated subsystems:
13//!
14//! - **Types** ([`types`]): Core data structures for fingerprints, dependency edges,
15//! and the dependency graph.
16//! - **Graph** ([`graph`]): Dependency graph traversal algorithms including BFS
17//! affected-file detection, topological sort, and cycle detection.
18//! - **Storage** ([`storage`]): Trait definitions for persisting dependency graphs
19//! and fingerprints across sessions.
20//! - **Backends** ([`backends`]): Concrete storage implementations (Postgres, D1, InMemory)
21//! with factory pattern for runtime backend selection.
22//!
23//! ## Design Pattern
24//!
25//! Adapted from ReCoco's `FieldDefFingerprint` (analyzer.rs:69-84):
26//! - **Source tracking**: Identifies which files contribute to each analysis result
27//! - **Fingerprint composition**: Detects content AND logic changes via Blake3 hashing
28//! - **Dependency graph**: Maintains import/export relationships for cascading invalidation
29//!
30//! ## Examples
31//!
32//! ### Basic Dependency Graph Operations
33//!
34//! ```rust
35//! use thread_flow::incremental::types::{
36//! AnalysisDefFingerprint, DependencyEdge, DependencyType,
37//! };
38//! use thread_flow::incremental::graph::DependencyGraph;
39//! use std::path::PathBuf;
40//! use std::collections::HashSet;
41//!
42//! // Create a dependency graph
43//! let mut graph = DependencyGraph::new();
44//!
45//! // Add a dependency edge: main.rs imports utils.rs
46//! graph.add_edge(DependencyEdge {
47//! from: PathBuf::from("src/main.rs"),
48//! to: PathBuf::from("src/utils.rs"),
49//! dep_type: DependencyType::Import,
50//! symbol: None,
51//! });
52//!
53//! // Find files affected by a change to utils.rs
54//! let changed = HashSet::from([PathBuf::from("src/utils.rs")]);
55//! let affected = graph.find_affected_files(&changed);
56//! assert!(affected.contains(&PathBuf::from("src/main.rs")));
57//! ```
58//!
59//! ### Runtime Backend Selection
60//!
61//! ```rust
62//! use thread_flow::incremental::{create_backend, BackendType, BackendConfig};
63//!
64//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
65//! // Select backend based on deployment environment
66//! let backend = if cfg!(feature = "postgres-backend") {
67//! create_backend(
68//! BackendType::Postgres,
69//! BackendConfig::Postgres {
70//! database_url: std::env::var("DATABASE_URL")?,
71//! },
72//! ).await?
73//! } else if cfg!(feature = "d1-backend") {
74//! create_backend(
75//! BackendType::D1,
76//! BackendConfig::D1 {
77//! account_id: std::env::var("CF_ACCOUNT_ID")?,
78//! database_id: std::env::var("CF_DATABASE_ID")?,
79//! api_token: std::env::var("CF_API_TOKEN")?,
80//! },
81//! ).await?
82//! } else {
83//! // Fallback to in-memory for testing
84//! create_backend(BackendType::InMemory, BackendConfig::InMemory).await?
85//! };
86//! # Ok(())
87//! # }
88//! ```
89//!
90//! ### Persistent Storage with Incremental Updates
91//!
92//! ```rust,ignore
93//! use thread_flow::incremental::{
94//! create_backend, BackendType, BackendConfig,
95//! StorageBackend, AnalysisDefFingerprint, DependencyGraph,
96//! };
97//! use std::path::Path;
98//!
99//! async fn incremental_analysis(backend: &dyn StorageBackend) -> Result<(), Box<dyn std::error::Error>> {
100//! // Load previous dependency graph
101//! let mut graph = backend.load_full_graph().await?;
102//!
103//! // Check if file changed
104//! let file_path = Path::new("src/main.rs");
105//! let new_fp = AnalysisDefFingerprint::new(b"new content");
106//!
107//! if let Some(old_fp) = backend.load_fingerprint(file_path).await? {
108//! if !old_fp.content_matches(b"new content") {
109//! // File changed - invalidate and re-analyze
110//! let affected = graph.find_affected_files(&[file_path.to_path_buf()].into());
111//! for affected_file in affected {
112//! // Re-analyze affected files...
113//! }
114//! }
115//! }
116//!
117//! // Save updated state
118//! backend.save_fingerprint(file_path, &new_fp).await?;
119//! backend.save_full_graph(&graph).await?;
120//! Ok(())
121//! }
122//! ```
123//!
124//! ## Migration Guide
125//!
126//! ### From Direct Storage Usage to Backend Factory
127//!
128//! **Before (direct backend instantiation):**
129//! ```rust,ignore
130//! #[cfg(feature = "postgres-backend")]
131//! use thread_flow::incremental::backends::postgres::PostgresIncrementalBackend;
132//!
133//! let backend = PostgresIncrementalBackend::new(database_url).await?;
134//! ```
135//!
136//! **After (factory pattern):**
137//! ```rust,ignore
138//! use thread_flow::incremental::{create_backend, BackendType, BackendConfig};
139//!
140//! let backend = create_backend(
141//! BackendType::Postgres,
142//! BackendConfig::Postgres { database_url },
143//! ).await?;
144//! ```
145//!
146//! ### Feature Flag Configuration
147//!
148//! **CLI deployment (Postgres):**
149//! ```toml
150//! [dependencies]
151//! thread-flow = { version = "*", features = ["postgres-backend", "parallel"] }
152//! ```
153//!
154//! **Edge deployment (D1):**
155//! ```toml
156//! [dependencies]
157//! thread-flow = { version = "*", features = ["d1-backend", "worker"] }
158//! ```
159//!
160//! **Testing (InMemory):**
161//! ```toml
162//! [dev-dependencies]
163//! thread-flow = { version = "*" } # InMemory always available
164//! ```
165
166pub mod analyzer;
167pub mod backends;
168pub mod concurrency;
169pub mod dependency_builder;
170pub mod extractors;
171pub mod graph;
172pub mod invalidation;
173pub mod storage;
174pub mod types;
175
176// Re-export core types for ergonomic use
177pub use analyzer::{AnalysisResult, AnalyzerError, IncrementalAnalyzer};
178pub use graph::DependencyGraph;
179pub use invalidation::{InvalidationDetector, InvalidationError, InvalidationResult};
180pub use types::{
181 AnalysisDefFingerprint, DependencyEdge, DependencyStrength, DependencyType, SymbolDependency,
182 SymbolKind,
183};
184
185// Re-export backend factory and configuration for runtime backend selection
186pub use backends::{BackendConfig, BackendType, IncrementalError, create_backend};
187
188// Re-export storage trait for custom backend implementations
189pub use storage::{InMemoryStorage, StorageBackend, StorageError};
190
191// Re-export concurrency layer for parallel execution - TODO: Phase 4.3
192// pub use concurrency::{create_executor, ConcurrencyMode, ExecutionError, Executor};
193
194// Feature-gated backend re-exports
195#[cfg(feature = "postgres-backend")]
196pub use backends::PostgresIncrementalBackend;
197
198#[cfg(feature = "d1-backend")]
199pub use backends::D1IncrementalBackend;