radicle-feed 0.1.1

A Radicle feed library for implementing Radicle COB feeds
Documentation
# rad-feed

A flexible Rust library for processing Radicle collaborative objects (patches and issues) with pluggable storage backends.

## Overview

`rad-feed` processes operations from patches and issues across Radicle repositories and stores them using your choice of storage backend. Instead of being tied to a specific database, the library uses a trait-based approach that allows you to implement any storage system you need.

## Features

- **Storage Agnostic**: Implement the `FeedStorage` trait to use any storage backend
- **Incremental Processing**: Only processes new operations since the last run
- **Multi-repository Support**: Process COB operations across all delegate repositories
- **Action Deduplication**: Built-in mechanisms to prevent duplicate processing
- **Author Resolution**: Automatic author information resolution using Radicle aliases

## Implementing Custom Storage

The core of the library is the `FeedStorage` trait. Implement this trait to use any storage system:

```rust
use std::collections::HashMap;

use radicle::{cob::ObjectId, prelude::RepoId, git::Oid};

use rad_feed::storage::{FeedStorage, StorageError, StorageStats};
use rad_feed::entry::ActionEntry;

pub struct MyCustomStorage {
    // Your storage implementation
    data: HashMap<String, ActionEntry>,
    tracking: HashMap<String, Oid>,
}

impl FeedStorage for MyCustomStorage {
    type Error = StorageError;

    fn initialize(&mut self) -> Result<(), Self::Error> {
        // Initialize your storage (create tables, connections, etc.)
        Ok(())
    }

    fn get_last_processed_operation(
        &self,
        rid: &RepoId,
        cob_id: &ObjectId,
        cob_type: &str,
    ) -> Result<Option<Oid>, Self::Error> {
        // Return the last processed operation ID for incremental updates
        let key = format!("{}:{}:{}", rid, cob_id, cob_type);
        Ok(self.tracking.get(&key).copied())
    }

    fn update_last_processed_operation(
        &self,
        rid: &RepoId,
        cob_id: &ObjectId,
        cob_type: &str,
        last_operation_id: &Oid,
    ) -> Result<(), Self::Error> {
        // Update the tracking information
        let key = format!("{}:{}:{}", rid, cob_id, cob_type);
        // Note: In a real implementation, you'd want &mut self or interior mutability
        Ok(())
    }

    fn operation_exists(&self, operation_id: &Oid) -> Result<bool, Self::Error> {
        // Check if action already exists (for duplicate prevention)
        Ok(self.data.contains_key(&operation_id.to_string()))
    }

    fn insert_batch(&mut self, entries: &[ActionEntry]) -> Result<(), Self::Error> {
        // Insert new operations into your storage
        for entry in entries {
            self.data.insert(entry.operation_id.to_string(), entry.clone());
        }
        Ok(())
    }

    // Optional methods with default implementations
    fn get_stats(&self) -> Result<StorageStats, Self::Error> {
        let mut stats = StorageStats::default();
        stats.total_actions = self.data.len() as u64;

        for entry in self.data.values() {
            *stats.actions_by_type.entry(entry.kind.clone()).or_insert(0) += 1;
        }

        Ok(stats)
    }
}
```

## Architecture

### Core Components

1. **`FeedStorage` Trait** (`src/storage/mod.rs`): The main abstraction for storage backends
2. **`FeedProcessor`** (`src/feed.rs`): Processes repositories and manages the storage lifecycle
3. **`ActionEntry`** (`src/entry.rs`): The core data structure representing COB operations
4. **Built-in Storages**: SQLite and in-memory implementations

## Action Data Structure

Each `ActionEntry` contains:

- `operation_id`: Unique identifier of the COB operation (Git OID)
- `cob_id`: Collaborative Object ID (patch or issue ID)
- `rid`: Repository ID where the action occurred
- `timestamp`: When the action was performed
- `action`: Serialized action data (JSON string)
- `author`: Author information with DID and optional alias
- `typename`: Typename of the COB (e.g. "xyz.radicle.patch" or "xyz.radicle.issue")