# Heroforge API Design
## Current API Issues
The current API requires importing traits to use methods, which is cumbersome:
```rust
use heroforge::{Repository, CommitOperations, FileOperations, BranchOperations};
let repo = Repository::open("project.forge")?;
let tip = repo.trunk_tip()?;
let files = repo.list_files(&tip.hash)?;
```
## Proposed Builder API
A cleaner, more fluent API using the builder pattern.
### 1. Repository as Entry Point
```rust
use heroforge::Repository;
let repo = Repository::open("project.forge")?;
```
### 2. Fluent File Operations
```rust
// List files on trunk
let files = repo.files().on_trunk().list()?;
// List files on a specific branch
let files = repo.files().on_branch("feature-x").list()?;
// List files at a specific tag
let files = repo.files().at_tag("v1.0.0").list()?;
// List files at a specific commit
let files = repo.files().at_commit("abc123").list()?;
// Find files with glob pattern
let rust_files = repo.files().on_trunk().find("**/*.rs")?;
// Read a file
let content = repo.files().on_trunk().read("README.md")?;
// Read file as string (convenience)
let text = repo.files().on_trunk().read_string("README.md")?;
// List directory contents
let items = repo.files().on_trunk().in_dir("src").list()?;
// List subdirectories
let subdirs = repo.files().on_trunk().subdirs("src")?;
```
### 3. Fluent Commit Operations
```rust
// Create a commit using builder
let hash = repo.commit()
.message("Add new feature")
.author("developer")
.parent(&parent_hash) // Optional: defaults to branch tip
.branch("feature-x") // Optional: stays on current branch if omitted
.file("README.md", b"# Hello")
.file("src/main.rs", b"fn main() {}")
.file_from_path("config.json", "/path/to/config.json")? // Read from disk
.execute()?;
// Create initial commit
let hash = repo.commit()
.message("Initial commit")
.author("admin")
.initial() // Marks as initial (no parent)
.execute()?;
// Commit with files from a HashMap or Vec
let files = vec![
("file1.txt", b"content1".as_slice()),
("file2.txt", b"content2".as_slice()),
];
let hash = repo.commit()
.message("Bulk add")
.author("developer")
.parent(&parent)
.files(&files)
.execute()?;
```
### 4. Fluent Branch/Tag Operations
```rust
// List branches
let branches = repo.branches().list()?;
// Get branch tip
let tip = repo.branches().get("feature-x")?.tip()?;
// Create a new branch
repo.branches()
.create("feature-y")
.from_branch("trunk") // or .from_commit("abc123") or .from_tag("v1.0")
.author("developer")
.execute()?;
// List tags
let tags = repo.tags().list()?;
// Create a tag
repo.tags()
.create("v1.0.0")
.at_branch("trunk") // or .at_commit("abc123")
.author("developer")
.execute()?;
// Get commit for a tag
let hash = repo.tags().get("v1.0.0")?.commit_hash()?;
```
### 5. Fluent History Operations
```rust
// Get recent commits
let commits = repo.history().recent(10)?;
// Get commits on a branch
let commits = repo.history().on_branch("trunk").limit(20).list()?;
// Get a specific commit
let commit = repo.history().get("abc123")?;
// Get commit with full details
let commit = repo.history().get("abc123")?.with_files()?;
```
### 6. Fluent User Operations
```rust
// List users
let users = repo.users().list()?;
// Create user
repo.users()
.create("newdev")
.password("secret123")
.capabilities("dehijkmnorstvwz") // Developer caps
.execute()?;
// Update user
repo.users()
.get("developer")?
.set_capabilities("aehijkmnorstvwz")?;
// Get user capabilities
let caps = repo.users().get("developer")?.capabilities()?;
```
### 7. Sync Operations (QUIC & HTTP)
```rust
// Sync over QUIC (default, fastest)
repo.sync()
.to("quic://remote.example.com:4443/repo")
.push()?;
repo.sync()
.from("quic://remote.example.com:4443/repo")
.pull()?;
// Sync over HTTP/HTTPS
repo.sync()
.to("https://remote.example.com/repo")
.auth("user", "password") // Optional for HTTP
.push()?;
repo.sync()
.from("https://remote.example.com/repo")
.pull()?;
// Auto-detect protocol from URL scheme
repo.sync()
.from("quic://server:4443/repo") // Uses QUIC
.pull()?;
repo.sync()
.from("https://server/repo") // Uses HTTP
.pull()?;
// Clone over QUIC
Repository::clone()
.from("quic://remote.example.com:4443/repo")
.to("local.forge")
.execute()?;
// Clone over HTTP
Repository::clone()
.from("https://remote.example.com/repo")
.to("local.forge")
.execute()?;
// Bidirectional sync
repo.sync()
.with("quic://remote.example.com:4443/repo")
.bidirectional() // Push and pull
.execute()?;
// Start a sync server (QUIC)
heroforge::Server::quic()
.bind("0.0.0.0:4443")
.repo(&repo)
.cert_path("/path/to/cert.pem")
.key_path("/path/to/key.pem")
.start()?;
// Start a sync server (HTTP)
heroforge::Server::http()
.bind("0.0.0.0:8080")
.repo(&repo)
.start()?;
```
## Implementation Structure
```
src/
├── lib.rs # Public API exports
├── repo/
│ ├── mod.rs # Repository struct + entry point methods
│ ├── database.rs # Low-level database operations
│ ├── builders/
│ │ ├── mod.rs # Re-exports all builders
│ │ ├── files.rs # FilesBuilder, FileQuery
│ │ ├── commit.rs # CommitBuilder
│ │ ├── branches.rs # BranchesBuilder, BranchBuilder
│ │ ├── tags.rs # TagsBuilder, TagBuilder
│ │ ├── history.rs # HistoryBuilder
│ │ └── users.rs # UsersBuilder, UserBuilder
│ └── types.rs # CheckIn, FileInfo, etc.
├── sync/
│ ├── mod.rs # SyncBuilder, protocol detection
│ ├── protocol.rs # Common sync protocol (messages, artifacts)
│ ├── quic/
│ │ ├── mod.rs # QUIC sync client & server
│ │ ├── client.rs # QuicSyncClient
│ │ └── server.rs # QuicSyncServer
│ └── http/
│ ├── mod.rs # HTTP sync client & server
│ ├── client.rs # HttpSyncClient
│ └── server.rs # HttpSyncServer
└── server/
├── mod.rs # Server builder entry point
├── quic.rs # QUIC server implementation
└── http.rs # HTTP server implementation
```
## Builder Implementation Example
### FilesBuilder
```rust
pub struct FilesBuilder<'a> {
repo: &'a Repository,
}
pub struct FileQuery<'a> {
repo: &'a Repository,
target: QueryTarget,
}
enum QueryTarget {
Trunk,
Branch(String),
Tag(String),
Commit(String),
}
impl<'a> FilesBuilder<'a> {
pub fn on_trunk(self) -> FileQuery<'a> {
FileQuery { repo: self.repo, target: QueryTarget::Trunk }
}
pub fn on_branch(self, name: &str) -> FileQuery<'a> {
FileQuery { repo: self.repo, target: QueryTarget::Branch(name.to_string()) }
}
pub fn at_tag(self, name: &str) -> FileQuery<'a> {
FileQuery { repo: self.repo, target: QueryTarget::Tag(name.to_string()) }
}
pub fn at_commit(self, hash: &str) -> FileQuery<'a> {
FileQuery { repo: self.repo, target: QueryTarget::Commit(hash.to_string()) }
}
}
impl<'a> FileQuery<'a> {
fn resolve_hash(&self) -> Result<String> {
match &self.target {
QueryTarget::Trunk => {
let tip = self.repo.db.get_trunk_tip()?;
self.repo.db.get_hash_by_rid(tip)
}
QueryTarget::Branch(name) => {
let tip = self.repo.db.get_branch_tip(name)?;
self.repo.db.get_hash_by_rid(tip)
}
QueryTarget::Tag(name) => self.repo.get_tag_checkin_internal(name),
QueryTarget::Commit(hash) => Ok(hash.clone()),
}
}
pub fn list(&self) -> Result<Vec<FileInfo>> {
let hash = self.resolve_hash()?;
self.repo.list_files_internal(&hash)
}
pub fn find(&self, pattern: &str) -> Result<Vec<FileInfo>> {
let hash = self.resolve_hash()?;
self.repo.find_files_internal(&hash, pattern)
}
pub fn read(&self, path: &str) -> Result<Vec<u8>> {
let hash = self.resolve_hash()?;
self.repo.read_file_internal(&hash, path)
}
pub fn read_string(&self, path: &str) -> Result<String> {
let content = self.read(path)?;
String::from_utf8(content).map_err(|e| FossilError::InvalidArtifact(e.to_string()))
}
}
```
### CommitBuilder
```rust
pub struct CommitBuilder<'a> {
repo: &'a Repository,
message: Option<String>,
author: Option<String>,
parent: Option<String>,
branch: Option<String>,
files: Vec<(String, Vec<u8>)>,
is_initial: bool,
}
impl<'a> CommitBuilder<'a> {
pub fn new(repo: &'a Repository) -> Self {
Self {
repo,
message: None,
author: None,
parent: None,
branch: None,
files: Vec::new(),
is_initial: false,
}
}
pub fn message(mut self, msg: &str) -> Self {
self.message = Some(msg.to_string());
self
}
pub fn author(mut self, user: &str) -> Self {
self.author = Some(user.to_string());
self
}
pub fn parent(mut self, hash: &str) -> Self {
self.parent = Some(hash.to_string());
self
}
pub fn branch(mut self, name: &str) -> Self {
self.branch = Some(name.to_string());
self
}
pub fn file(mut self, path: &str, content: &[u8]) -> Self {
self.files.push((path.to_string(), content.to_vec()));
self
}
pub fn files(mut self, files: &[(&str, &[u8])]) -> Self {
for (path, content) in files {
self.files.push((path.to_string(), content.to_vec()));
}
self
}
pub fn initial(mut self) -> Self {
self.is_initial = true;
self.branch = Some("trunk".to_string());
self
}
pub fn execute(self) -> Result<String> {
let message = self.message.ok_or(FossilError::InvalidArtifact("message required".into()))?;
let author = self.author.ok_or(FossilError::InvalidArtifact("author required".into()))?;
let files_refs: Vec<(&str, &[u8])> = self.files
.iter()
.map(|(p, c)| (p.as_str(), c.as_slice()))
.collect();
self.repo.commit_internal(
&files_refs,
&message,
&author,
self.parent.as_deref(),
self.branch.as_deref(),
)
}
}
```
## Repository Entry Points
```rust
impl Repository {
/// Access file operations
pub fn files(&self) -> FilesBuilder {
FilesBuilder { repo: self }
}
/// Start building a commit
pub fn commit(&self) -> CommitBuilder {
CommitBuilder::new(self)
}
/// Access branch operations
pub fn branches(&self) -> BranchesBuilder {
BranchesBuilder { repo: self }
}
/// Access tag operations
pub fn tags(&self) -> TagsBuilder {
TagsBuilder { repo: self }
}
/// Access history/commit operations
pub fn history(&self) -> HistoryBuilder {
HistoryBuilder { repo: self }
}
/// Access user operations
pub fn users(&self) -> UsersBuilder {
UsersBuilder { repo: self }
}
/// Access sync operations
pub fn sync(&self) -> SyncBuilder {
SyncBuilder { repo: self }
}
}
```
## Migration Path
1. Keep existing trait-based API for internal use
2. Add builder API as the public-facing interface
3. Builders delegate to internal methods
4. Eventually deprecate direct trait method access
## Benefits
1. **Discoverability**: IDE autocomplete guides users through available options
2. **Type Safety**: Builders ensure required fields are set before execution
3. **Fluent**: Reads like natural language
4. **Composable**: Easy to add new options without breaking existing code
5. **No Trait Imports**: Users only need `use heroforge::Repository`
## Complete Example
```rust
use heroforge::Repository;
fn main() -> heroforge::Result<()> {
// Create repository
let repo = Repository::init("project.forge")?;
// Create initial commit
let init = repo.commit()
.message("Initial commit")
.author("admin")
.initial()
.execute()?;
// Add some files
let v1 = repo.commit()
.message("Add project structure")
.author("developer")
.parent(&init)
.file("README.md", b"# My Project\n")
.file("src/lib.rs", b"pub fn hello() {}\n")
.file("Cargo.toml", b"[package]\nname = \"myproject\"\n")
.execute()?;
// Tag the release
repo.tags()
.create("v1.0.0")
.at_commit(&v1)
.author("developer")
.execute()?;
// Create a feature branch
repo.branches()
.create("feature-auth")
.from_commit(&v1)
.author("developer")
.execute()?;
// Work on the feature branch
let feature = repo.commit()
.message("Add authentication module")
.author("developer")
.branch("feature-auth")
.file("src/auth.rs", b"pub fn login() {}\n")
.execute()?;
// Read files from different locations
let readme = repo.files().on_trunk().read_string("README.md")?;
let auth = repo.files().on_branch("feature-auth").read_string("src/auth.rs")?;
let v1_readme = repo.files().at_tag("v1.0.0").read_string("README.md")?;
// Find all Rust files on trunk
let rust_files = repo.files().on_trunk().find("**/*.rs")?;
// Get recent history
let recent = repo.history().recent(10)?;
// List all branches and tags
let branches = repo.branches().list()?;
let tags = repo.tags().list()?;
// Sync to remote over QUIC
repo.sync()
.to("quic://backup.example.com:4443/project")
.push()?;
// Or sync over HTTP
repo.sync()
.to("https://github-like.example.com/user/project")
.auth("user", "token")
.push()?;
Ok(())
}
```
## Server Example
```rust
use heroforge::{Repository, Server};
fn main() -> heroforge::Result<()> {
let repo = Repository::open("project.forge")?;
// Start QUIC server (recommended for performance)
Server::quic()
.bind("0.0.0.0:4443")
.repo(&repo)
.cert_path("cert.pem")
.key_path("key.pem")
.start_blocking()?;
Ok(())
}
```
```rust
use heroforge::{Repository, Server};
#[tokio::main]
async fn main() -> heroforge::Result<()> {
let repo = Repository::open("project.forge")?;
// Start HTTP server (for compatibility)
Server::http()
.bind("0.0.0.0:8080")
.repo(&repo)
.start()
.await?;
Ok(())
}
```