mosaik 0.3.13

A Rust runtime for building self-organizing, leaderless distributed systems.
Documentation
# Writer/Reader Pattern

All mosaik collections share a common access-control pattern: every collection
type is parameterized by a const-generic boolean (`IS_WRITER`) that determines
whether the instance has write access.

```text
                 ┌──────────────────┐
                 │  Collection<T>   │
                 │  const IS_WRITER │
                 └────────┬─────────┘
              ┌───────────┴───────────┐
              │                       │
       IS_WRITER = true        IS_WRITER = false
       ┌──────────────┐       ┌──────────────┐
       │    Writer    │       │    Reader    │
       │ read + write │       │  read-only   │
       │ normal leader│       │ deprioritized│
       │   priority   │       │   leader     │
       └──────────────┘       └──────────────┘
```

## Type aliases

Each collection provides convenient type aliases:

| Collection               | Writer type                    | Reader type                    |
| ------------------------ | ------------------------------ | ------------------------------ |
| `Map<K, V>`              | `MapWriter<K, V>`              | `MapReader<K, V>`              |
| `Vec<T>`                 | `VecWriter<T>`                 | `VecReader<T>`                 |
| `Set<T>`                 | `SetWriter<T>`                 | `SetReader<T>`                 |
| `Cell<T>`            | `CellWriter<T>`            | `CellReader<T>`            |
| `Once<T>`                | `OnceWriter<T>`                | `OnceReader<T>`                |
| `PriorityQueue<P, K, V>` | `PriorityQueueWriter<P, K, V>` | `PriorityQueueReader<P, K, V>` |

## Construction

Every collection offers the same set of constructors:

```rust,ignore
// Writer (default)
Collection::writer(&network, store_id)
Collection::writer_with_config(&network, store_id, sync_config)

// Convenience aliases for writer
Collection::new(&network, store_id)
Collection::new_with_config(&network, store_id, sync_config)

// Reader
Collection::reader(&network, store_id)
Collection::reader_with_config(&network, store_id, sync_config)
```

The `StoreId` and `SyncConfig` must match between writers and readers for them
to join the same consensus group.

### Construction from the `collection!` macro (recommended)

The `collection!` macro declares a named collection definition with a
compile-time `StoreId`. It generates a struct and implements
`CollectionReader` and/or `CollectionWriter`, letting you create readers
and writers without repeating the store ID.

```rust,ignore
use mosaik::{collection, collections::CollectionWriter, WriterOf};

// Full — implements both CollectionReader and CollectionWriter
collection!(pub Tags = mosaik::collections::Set<String>, "tags");

let writer: WriterOf<Tags> = Tags::writer(&network);

// Reader-only — implements CollectionReader only
collection!(pub reader TagsReader = mosaik::collections::Set<String>, "tags");

// Writer-only — implements CollectionWriter only
collection!(pub writer TagsWriter = mosaik::collections::Set<String>, "tags");
```

Use `ReaderOf<C>` and `WriterOf<C>` (re-exported at `mosaik::ReaderOf` /
`mosaik::WriterOf`) to refer to the concrete reader or writer types:

```rust,ignore
use mosaik::{collection, collections::CollectionReader, ReaderOf};

collection!(pub reader Logs = mosaik::collections::Vec<String>, "app.logs");

fn subscribe(network: &mosaik::Network) -> ReaderOf<Logs> {
	Logs::reader(network)
}
```

See the [collection! macro section](../collections.md#the-collection-macro-recommended)
for the full syntax reference, mode table, and guidance on when to use the
macro vs. direct constructors.

### Construction from a `CollectionDef`

As an alternative, you can define a collection's identity with `CollectionDef`
and derive readers and writers from it. This is useful when you need an explicit
definition value (e.g., passing it as a function argument).

```rust,ignore
use mosaik::{unique_id, collections::{CollectionDef, ReaderDef, WriterDef, Set}};

// Full definition — can create both writers and readers
const TAGS: CollectionDef<Set<String>> =
	CollectionDef::new(unique_id!("tags"));

let writer = TAGS.writer(&network);
let reader = TAGS.reader(&network);

// Narrowed definitions — expose only one side
const TAGS_READER: ReaderDef<Set<String>> = TAGS.as_reader();
const TAGS_WRITER: WriterDef<Set<String>> = TAGS.as_writer();

let r = TAGS_READER.open(&network);
let w = TAGS_WRITER.open(&network);
```

This pattern is especially useful for libraries that own a collection and want
to export a `ReaderDef` so downstream code can subscribe without knowing the
`StoreId`. See the [CollectionDef section](../collections.md#collection-definitions-collectiondef)
for the full type-parameter mapping.

## How it works

Internally, the const-generic boolean controls two things:

1. **Method availability** — Write methods (`insert`, `push_back`, `remove`,
   etc.) are only implemented for `IS_WRITER = true`. This is enforced at
   compile time.

1. **Leadership priority** — Readers return a `ConsensusConfig` with
   `deprioritize_leadership()`, which increases their election timeout. This
   makes it less likely for a reader to become the Raft leader, keeping
   leadership on writer nodes where write operations are handled directly
   rather than being forwarded.

```rust,ignore
// Inside every collection's StateMachine impl:
fn consensus_config(&self) -> Option<ConsensusConfig> {
    (!self.is_writer)
        .then(|| ConsensusConfig::default().deprioritize_leadership())
}
```

## Shared read API

Both writers and readers have identical read access. The read methods are
implemented on `Collection<T, IS_WRITER>` without constraining `IS_WRITER`:

```rust,ignore
// Works on both MapWriter and MapReader
impl<K: Key, V: Value, const IS_WRITER: bool> Map<K, V, IS_WRITER> {
    pub fn len(&self) -> usize { ... }
    pub fn get(&self, key: &K) -> Option<V> { ... }
    pub fn iter(&self) -> impl Iterator<Item = (K, V)> { ... }
    pub fn when(&self) -> &When { ... }
    pub fn version(&self) -> Version { ... }
}
```

## Trait requirements

The type parameters for collection elements must satisfy blanket-implemented
trait bounds:

### Value

Required for all element and value types:

```rust,ignore
pub trait Value:
    Clone + Debug + Serialize + DeserializeOwned + Send + Sync + 'static
{}

// Blanket impl — any conforming type is a Value
impl<T> Value for T where T: Clone + Debug + Serialize + ... {}
```

### Key

Required for map keys, set elements, and priority queue keys:

```rust,ignore
pub trait Key:
    Clone + Serialize + DeserializeOwned
    + Hash + PartialEq + Eq + Send + Sync + 'static
{}
```

Note that `Key` does not require `Debug` (unlike `Value`).

### OrderedKey

Required for priority queue priorities:

```rust,ignore
pub trait OrderedKey: Key + Ord {}
impl<T: Key + Ord> OrderedKey for T {}
```

## Version

All write operations return `Version`, which wraps the Raft log `Index` where
the mutation will be committed. Use it with the `When` API to synchronize:

```rust,ignore
let version = map.insert("key".into(), value).await?;
map.when().reaches(version).await;
// Now the insert is guaranteed to be committed
```

`Version` implements `Deref<Target = Index>`, `PartialOrd`, `Ord`, `Display`,
and `Copy`.

## When (collections)

The collections `When` is a thin wrapper around the groups `When` that exposes
collection-relevant observers:

| Method             | Description                                                       |
| ------------------ | ----------------------------------------------------------------- |
| `online()`         | Resolves when the collection has joined and synced with the group |
| `offline()`        | Resolves when the collection loses sync or leadership             |
| `updated()`        | Resolves when any new state version is committed                  |
| `reaches(Version)` | Resolves when committed state reaches at least the given version  |

```rust,ignore
// Typical lifecycle pattern
loop {
    collection.when().online().await;
    println!("online, version = {}", collection.version());

    // ... do work ...

    collection.when().offline().await;
    println!("went offline, waiting to reconnect...");
}
```

## Multiple writers

Multiple nodes can be writers for the same collection simultaneously. All
writes are funneled through Raft consensus, so there is no conflict — every
write is serialized in the log and applied in the same order on all nodes.

```rust,ignore
// Node A
let map = Map::<String, u64>::writer(&network, store_id);
map.insert("from-a".into(), 1).await?;

// Node B (same StoreId)
let map = Map::<String, u64>::writer(&network, store_id);
map.insert("from-b".into(), 2).await?;

// Both nodes see both entries after sync
```

## Choosing writer vs. reader

| Use a Writer when…                           | Use a Reader when…                      |
| -------------------------------------------- | --------------------------------------- |
| The node needs to modify the collection      | The node only observes state            |
| You want normal leadership election priority | You want to reduce leadership overhead  |
| The node is in the "hot path" for writes     | The node is a monitoring/dashboard node |