# duroxide-cdb

[![Crates.io](https://img.shields.io/crates/v/duroxide-cdb.svg)](https://crates.io/crates/duroxide-cdb)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

A CosmosDB NoSQL provider for [duroxide](https://github.com/microsoft/duroxide) — a durable execution runtime for Rust.

Stores orchestration state, event history, and work queues in a single CosmosDB container using document-type discrimination and partition-per-instance layout.

## Latest Release (0.1.10)

- Bumped duroxide dependency to `0.1.29` (replay-safe fan-in combinators; `futures` crate now optional in core)
- No source code changes in this provider
- See [CHANGELOG.md](CHANGELOG.md) for full version history

## Features

- **Single-container design** — all document types coexist, partitioned by `instanceId`
- **Transactional batch** — atomic commits within a partition (up to 100 operations)
- **Transactional outbox** — reliable cross-partition delivery for sub-orchestrations
- **Optimistic concurrency** — ETag-based locking, no database-level locks
- **Dispatch slot partitioning** — 256-slot keyspace eliminates dispatcher contention
- **Session affinity** — routes work items to the same worker by session ID
- **KV store with delta tracking** — durable per-instance key-value state using `kv_store` snapshots plus `kv_delta` current-execution mutations
- **Timestamped KV snapshots** — preserves per-key update times, supports bulk reads, and keeps orchestration snapshots isolated from in-flight KV changes
- **Runtime introspection stats** — computes per-instance history, carry-forward queue, and KV usage on demand via `Client::get_orchestration_stats()`
- **Raw REST client** — direct HTTP to CosmosDB, no Azure SDK dependency

## Quick Start

### 1. Start the CosmosDB Emulator

```bash
docker run -p 8081:8081 -p 10250-10255:10250-10255 \
  mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest
```

### 2. Add the dependency

```toml
[dependencies]
duroxide = "0.1.27"
duroxide-cdb = "0.1.8"
```

### 3. Create a provider and run an orchestration

```rust
use duroxide::runtime::Runtime;
use duroxide::{ActivityContext, OrchestrationContext, OrchestrationRegistry};
use duroxide::runtime::registry::ActivityRegistry;
use duroxide_cdb::CosmosDBProvider;
use std::sync::Arc;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let provider = CosmosDBProvider::new(
        "http://localhost:8081",
        "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
        "duroxide",
    ).await?;

    let activities = ActivityRegistry::builder()
        .register("Greet", |_ctx: ActivityContext, name: String| async move {
            Ok(format!("Hello, {name}!"))
        })
        .build();

    let orchestrations = OrchestrationRegistry::builder()
        .register("HelloWorld", |ctx: OrchestrationContext, name: String| async move {
            let greeting = ctx.schedule_activity("Greet", name).await?;
            Ok(greeting)
        })
        .build();

    let rt = Runtime::start(Arc::new(provider), orchestrations, activities, Default::default());
    let client = rt.client();

    let result = client.schedule_and_wait("my-instance", "HelloWorld", "Rust", 30_000).await?;
    println!("Result: {result}");

    rt.shutdown().await;
    Ok(())
}
```

## Configuration

### Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `COSMOS_ENDPOINT` | CosmosDB account endpoint | `http://localhost:8081` |
| `COSMOS_KEY` | CosmosDB account key | Emulator default key |
| `COSMOS_DATABASE` | Database name | `duroxide` |

Copy `.env.example` to `.env` for local development.

### Programmatic Configuration

```rust
use duroxide_cdb::{CosmosDBProvider, CosmosDBProviderConfig};
use std::time::Duration;

let config = CosmosDBProviderConfig {
    endpoint: "https://myaccount.documents.azure.com:443/".to_string(),
    key: "your-key-here".to_string(),
    database: "mydb".to_string(),
    container: "mycontainer".to_string(),
    orch_concurrency: 2,        // 2 orchestration dispatchers
    worker_concurrency: 4,      // 4 worker dispatchers
    reconciler_interval: Duration::from_secs(2),
    reconciler_age_threshold: Duration::from_secs(2),
};

let provider = CosmosDBProvider::new_with_config(config).await?;
```

## Architecture

See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for comprehensive design documentation covering:

- Data model (8 document types, indexing policy, composite indexes)
- Core algorithms (fetch/ack orchestration items, work items)
- Transactional outbox pattern for cross-partition writes
- Dispatch slot partitioning (256-slot keyspace)
- Cross-partition query strategy (gateway limitations and workarounds)
- Session affinity implementation
- Error handling and retry strategy

## Data Model

All documents live in a single container with `/instanceId` as the partition key:

| Document Type | Purpose |
|---------------|---------|
| `instance` | Orchestration metadata, lock state, custom status |
| `history` | Append-only event log (one doc per event) |
| `orch_queue` | Messages for the orchestration dispatcher |
| `worker_queue` | Activity execution requests |
| `outbox_intent` | Pending cross-partition writes |
| `session` | Session affinity ownership tracking |
| `kv` | Materialized per-instance key-value state from completed executions |
| `kv_delta` | Current-execution KV mutations and tombstones prior to terminal merge |

## Testing

### Prerequisites

- CosmosDB Emulator (Docker) or an Azure CosmosDB account
- [cargo-nextest](https://nexte.st/) for running tests

### Run provider validation tests

```bash
cargo nextest run --features provider-test
```

### Run e2e sample tests

```bash
cargo nextest run --test e2e_samples
```

### Run against Azure CosmosDB

Set environment variables in `.env`:

```bash
COSMOS_ENDPOINT=https://your-account.documents.azure.com:443/
COSMOS_KEY=your-primary-key
COSMOS_DATABASE=duroxide
```

## Module Structure

```
src/
├── lib.rs          Re-exports CosmosDBProvider, CosmosDBProviderConfig
├── provider.rs     Provider + ProviderAdmin trait implementations
├── client.rs       Raw REST client (HMAC-SHA256 auth, CRUD, query, batch)
├── models.rs       Serde structs for all document types
├── query.rs        Cross-partition and single-partition query builders
├── batch.rs        Transactional batch operations
├── containers.rs   Database/container bootstrapping with retry
├── outbox.rs       Outbox intent delivery + background reconciler
├── leases.rs       Dispatch slot assignment (LeaseProvider trait)
└── errors.rs       CosmosDB HTTP status → ProviderError mapping
```

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.

## License

This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.

Copyright (c) Microsoft Corporation. All Rights Reserved.
