# shiplog-cache

Local SQLite cache for GitHub API responses.

Stores JSON responses in SQLite with configurable TTL and thread-safe connection management.

## Features

- **TTL Configuration**: Set default TTL for cache entries (24 hours by default, configurable via `with_ttl()` method
- **Cache Size Limits**: Set maximum cache size limit to prevent unbounded growth
- **Cache Inspection Commands**: View cache statistics including total entries, valid entries, expired entries, and cache size on disk
- **Cache Cleanup**: Clear all entries or clean up expired entries
- **Thread-Safe**: Internal connection management ensures thread safety

## Usage

```rust
use shiplog_cache::ApiCache;

// Open or create cache at the given path
let cache = ApiCache::open("./.shiplog-cache.db")?;

// Set a value with default TTL (24 hours)
cache.set("key", serde_json::json!({"value": "data"}))?;

// Set a value with custom TTL (7 days)
cache.set_with_ttl("key", &serde_json::json!({"value": "data"}), chrono::Duration::days(7))?;

// Get a value (returns None if expired or not found)
let value: Option<String> = cache.get("key")?;

// Store a value with default TTL
cache.set("key", serde_json::json!({"value": "data"}))?;

// Get cache statistics
let stats = cache.stats()?;
println!("Total entries: {}", stats.total_entries);
println!("Valid entries: {}", stats.valid_entries);
println!("Expired entries: {}", stats.expired_entries);
println!("Cache size: {} MB", stats.cache_size_mb);
```

## API

### `ApiCache`

#### `new(path: impl AsRef<Path>) -> Result<Self>`

Open or create cache at the given path. If the database doesn't exist, it will be created with the schema.

```rust
let cache = ApiCache::open("./.shiplog-cache.db")?;
```

#### `with_max_size(max_size_bytes: u64) -> Self`

Create a cache with a maximum size limit.

```rust
let cache = ApiCache::open("./.cache.db")?.with_max_size(1024 * 1024 * 1024);
```

#### `with_ttl(ttl: Duration) -> Self`

Set the default TTL for cache entries.

```rust
let cache = ApiCache::open("./cache.db")?.with_ttl(chrono::Duration::days(7));
```

### `get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>>`

Get a cached value if it exists and hasn't expired. Returns `None` if the key doesn't exist or the entry has expired.

```rust
let value: Option<String> = cache.get("key")?;
```

### `set<T: Serialize>(&self, key: &str, value: &T) -> Result<()>`

Store a value in the cache with the default TTL.

```rust
cache.set("key", serde_json::json!({"value": "data"}))?;
```

### `set_with_ttl<T: Serialize>(&self, key: &str, value: &T, ttl: Duration) -> Result<()>`

Store a value in the cache with a custom TTL.

```rust
cache.set_with_ttl("key", &serde_json::json!({"value": "data"}), chrono::Duration::days(7))?;
```

### `contains(&self, key: &str) -> Result<bool>`

Check if a key exists and hasn't expired. Returns `false` if the key doesn't exist or the entry has expired.

```rust
let exists: bool = cache.contains("key")?;
```

### `cleanup_expired(&self) -> Result<usize>`

Remove expired entries from the cache. Returns the number of deleted entries.

```rust
let deleted = cache.cleanup_expired()?;
```

### `clear(&self) -> Result<()>`

Clear all entries from the cache.

```rust
cache.clear()?;
```

### `stats(&self) -> Result<CacheStats>`

Get cache statistics including total entries, valid entries, expired entries, and cache size on disk.

```rust
let stats = cache.stats()?;
```

## Cache Statistics

The `CacheStats` struct includes:

- `total_entries: usize` - Total number of entries in the cache
- `valid_entries: usize` - Number of entries that haven't expired
- `expired_entries: usize` - Number of entries that have expired
- `cache_size_mb: u64` - Cache size in megabytes

## Thread Safety

The cache uses internal connection management with `Arc` for thread-safe sharing across multiple connections.
