# petgraph-live — API Design
**Version target:** 0.3.0
## Crate structure
```
petgraph_live
├── cache GenerationCache<G> — hot-reload, generation-keyed
├── metrics Distance-based graph characteristics (weighted + unweighted)
├── connect Articulation points, bridges
├── shortest_path Floyd-Warshall, Seidel, BFS distances, petgraph re-exports
├── mst Prim, Borůvka (Kruskal re-exported from petgraph)
├── snapshot Serde-based disk persistence (feature "snapshot")
└── live GraphState<G> — composes cache + snapshot (feature "snapshot")
```
## Module: `cache`
```rust
use petgraph_live::cache::GenerationCache;
let cache: GenerationCache<MyGraph> = GenerationCache::new();
// Returns cached Arc<MyGraph> if generation matches, else calls build.
let graph: Arc<MyGraph> = cache.get_or_build(current_gen, || build_my_graph())?;
// Force next call to rebuild.
cache.invalidate();
// Currently cached generation (None if empty).
let gen: Option<u64> = cache.current_generation();
```
`get_or_build` takes a `FnOnce() -> Result<G, E>` — the error type is caller-defined.
## Module: `metrics`
All functions work on any graph type satisfying the relevant `petgraph::visit` traits.
### Unweighted
```rust
use petgraph_live::metrics;
metrics::eccentricity(&graph, node) // f32
metrics::radius(&graph) // Option<f32>
metrics::diameter(&graph) // Option<f32>
metrics::center(&graph) // Vec<G::NodeId>
metrics::periphery(&graph) // Vec<G::NodeId>
metrics::girth(&graph) // Option<u32> — None if acyclic
```
Returns `None` on empty or disconnected graph.
### Weighted
All weighted functions take an `edge_cost: F` closure where `F: FnMut(G::EdgeRef) -> K`.
```rust
metrics::weighted_eccentricity(&graph, node, |e| *e.weight()) // Option<K>
metrics::weighted_radius(&graph, |e| *e.weight()) // Option<K>
metrics::weighted_diameter(&graph, |e| *e.weight()) // Option<K>
metrics::weighted_center(&graph, |e| *e.weight()) // Vec<G::NodeId>
metrics::weighted_periphery(&graph, |e| *e.weight()) // Vec<G::NodeId>
```
Returns `None` on empty graph or negative cycle.
## Module: `connect`
Undirected graphs only.
```rust
use petgraph_live::connect;
connect::articulation_points(&graph) // Vec<G::NodeId>
connect::find_bridges(&graph) // Vec<(G::NodeId, G::NodeId)>
```
## Module: `shortest_path`
```rust
use petgraph_live::shortest_path;
shortest_path::shortest_distances(&graph, start) // Vec<f32> — BFS, unreachable = INFINITY
shortest_path::seidel(&graph) // Vec<Vec<u32>> — unweighted APSP, undirected
// Re-exported from petgraph::algo
shortest_path::dijkstra(...)
shortest_path::bellman_ford(...)
shortest_path::astar(...)
shortest_path::spfa(...)
shortest_path::johnson(...)
shortest_path::k_shortest_path(...)
shortest_path::NegativeCycle
```
## Module: `mst`
```rust
use petgraph_live::mst;
`key = None` → load returns most recent by mtime; empty dir → `Ok(None)`.
### API
```rust
use petgraph_live::snapshot::{self, SnapshotConfig, SnapshotError, SnapshotMeta};
snapshot::save(&cfg, &graph) // -> Result<(), SnapshotError>
snapshot::load::<G>(&cfg) // -> Result<Option<G>, SnapshotError>
snapshot::load_or_build::<G, F>(&cfg, build) // -> Result<G, SnapshotError>
snapshot::inspect(&cfg) // -> Result<Option<SnapshotMeta>, SnapshotError>
snapshot::list(&cfg) // -> Result<Vec<(PathBuf, SnapshotMeta)>, SnapshotError>
snapshot::purge(&cfg) // -> Result<usize, SnapshotError>
```
`inspect` reads only the metadata header; for uncompressed bincode, graph bytes are never read from disk. `list` follows the same pattern.
### SnapshotMeta
```rust
pub struct SnapshotMeta {
pub key: String,
pub format: SnapshotFormat,
pub compression: Compression,
pub node_count: usize,
pub edge_count: usize,
pub created_at: u64, // Unix timestamp seconds
pub petgraph_live_version: String,
}
```
### SnapshotError
```rust
pub enum SnapshotError {
Io(std::io::Error),
KeyNotFound { key: String },
ParseError(String),
CompressionError(String),
NoSnapshotFound,
InvalidKey(String),
}
```
### Example
```rust
let cfg = SnapshotConfig {
dir: PathBuf::from("/state/snapshots"),
name: "graph".into(),
key: Some(current_git_sha()),
format: SnapshotFormat::Bincode,
compression: Compression::None,
keep: 3,
};
if let Some(meta) = snapshot::inspect(&cfg)? {
println!("{} nodes, key={}", meta.node_count, meta.key);
}
```
## Module: `live` (feature "snapshot")
Composes `GenerationCache<G>` + `snapshot` into a single managed lifecycle.
Caller provides: how to compute the current key, how to build the graph.
`GraphState` handles: cold start, warm start from snapshot, rebuild on key change, rotation.
### API
```rust
use petgraph_live::live::{GraphState, GraphStateConfig};
let config = GraphStateConfig::new(SnapshotConfig {
dir: "/state".into(), name: "graph".into(), keep: 3,
format: Default::default(), key: None, // must be None — managed internally
..Default::default()
});
let state: GraphState<MyGraph> = GraphState::builder(config)
.key_fn(|| Ok(current_git_sha()))
.build_fn(|| build_graph_from_index())
.current_key(current_git_sha()) // optional — avoids calling key_fn twice at init
.init()?;
let graph = state.get()?; // hot path — no key check
let graph = state.get_fresh()?; // checks key_fn(), rebuilds if stale
let graph = state.rebuild()?; // unconditional rebuild + snapshot save
state.current_key() // -> String
state.generation() // -> u64 (process-lifetime counter)
```
### Concurrency
`build_fn` runs outside any lock. Only the cache-swap step acquires a write lock
(microseconds). Concurrent `get()` callers are never blocked during a rebuild.
Two concurrent `get_fresh()` callers detecting a stale key both call `build_fn`
(last writer wins). Coalescing deferred to a future version.
## Error handling
| `cache` | caller-supplied `E` from the `build` closure |
| `metrics` | no `Result` — `None` for degenerate/empty cases |
| `shortest_path` | `NegativeCycle` (petgraph re-export) |
| `snapshot`, `live` | `SnapshotError` |
## Dependency surface
| default | `petgraph 0.8` only |
| `snapshot` | `serde`, `serde_json`, `bincode`, `petgraph/serde-1` |
| `snapshot-zstd` | `snapshot` + `zstd` |
| `snapshot-lz4` | `snapshot` + `lz4_flex` |
No `nalgebra`, `rand`, `rayon`, or async runtime.