mosaik 0.3.13

A Rust runtime for building self-organizing, leaderless distributed systems.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# Collections

The **Collections** subsystem provides replicated, eventually consistent data
structures that feel like local Rust collections but are automatically
synchronized across all participating nodes using Raft consensus.

```text
┌──────────────────────────────────────────────────────────────────────────┐
│                   Collections                                            │
│                                                                          │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ ┌──────┐ │
│  │   Map    │ │   Vec    │ │   Set    │ │  DEPQ  │ │ Cell │ │ Once │ │
│  │ <K, V>   │ │ <T>      │ │ <T>      │ │<P,K,V> │ │  <T>     │ │ <T>  │ │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘ └───┬────┘ └────┬─────┘ └──┬───┘ │
│       └────────────┴────────────┴─┬─────────┴───────────┴──────────┘     │
│                                   │                                      │
│                                   │                                      │
│                                   │                                      │
│                  ┌────────────────▼───────────────────┐                  │
│                  │    Groups (Raft Consensus)         │                  │
│                  │    One group per collection        │                  │
│                  └────────────────────────────────────┘                  │
└──────────────────────────────────────────────────────────────────────────┘
```

Each collection instance creates its own Raft consensus group. Different
collections (or the same type with different `StoreId`s) run as independent
groups and can span different subsets of the network.

## Quick start

```rust,ignore
use mosaik::collections::{Map, StoreId};

// On every node — using the same StoreId joins the same group
let prices = Map::<String, f64>::writer(&network, StoreId::from("prices"));

// Wait until the collection is online
prices.when().online().await;

// Write (only available on writers)
let version = prices.insert("ETH".into(), 3812.50).await?;

// Wait for the write to be committed
prices.when().reaches(version).await;

// Read (available on both writers and readers)
assert_eq!(prices.get(&"ETH".into()), Some(3812.50));
```

## Writer / Reader split

Every collection type offers two modes, distinguished at the **type level**
using a const-generic boolean:

| Mode       | Type alias                                                                  | Can write? | Leadership priority |
| ---------- | --------------------------------------------------------------------------- | ---------- | ------------------- |
| **Writer** | `MapWriter<K,V>`, `VecWriter<T>`, `CellWriter<T>`, `OnceWriter<T>` etc. | Yes        | Normal              |
| **Reader** | `MapReader<K,V>`, `VecReader<T>`, `CellReader<T>`, `OnceReader<T>` etc. | No         | Deprioritized       |

Both modes provide identical read access. Readers automatically use
`deprioritize_leadership()` in their consensus configuration to reduce the
chance of being elected leader, since leaders handle write forwarding.

```rust,ignore
// Writer — can read AND write
let w = Map::<String, u64>::writer(&network, store_id);

// Reader — can only read, lower chance of becoming leader
let r = Map::<String, u64>::reader(&network, store_id);
```

## Available collections

| Collection                                                | Description                         | Backing structure             |
| --------------------------------------------------------- | ----------------------------------- | ----------------------------- |
| [`Map<K, V>`]collections/map.md                         | Unordered key-value map             | `im::HashMap` (deterministic) |
| [`Vec<T>`]collections/vec.md                            | Ordered, index-addressable sequence | `im::Vector`                  |
| [`Set<T>`]collections/set.md                            | Unordered set of unique values      | `im::HashSet` (deterministic) |
| [`Cell<T>`]collections/cell.md                  | Single-value cell               | `Option<T>`                   |
| [`Once<T>`]collections/once.md                          | Write-once cell                 | `Option<T>`                   |
| [`PriorityQueue<P, K, V>`]collections/priority-queue.md | Double-ended priority queue         | `im::HashMap` + `im::OrdMap`  |

Most collections use the [`im` crate](https://docs.rs/im) for their internal
state, which provides **O(1) structural sharing** — cloning a snapshot of the
entire collection is essentially free. This is critical for the snapshot-based
state sync mechanism. `Cell` and `Once` use a plain `Option<T>` since they
hold at most one value.

## Trait requirements

Collections use blanket-implemented marker traits to constrain their type
parameters:

| Trait        | Required bounds                                                                        | Used by                         |
| ------------ | -------------------------------------------------------------------------------------- | ------------------------------- |
| `Value`      | `Clone + Debug + Serialize + DeserializeOwned + Send + Sync + 'static`                 | All element/value types         |
| `Key`        | `Clone + Serialize + DeserializeOwned + Hash + PartialEq + Eq + Send + Sync + 'static` | Map keys, Set elements, PQ keys |
| `OrderedKey` | `Key + Ord`                                                                            | PriorityQueue priorities        |

These traits are automatically implemented for any type that satisfies their
bounds — you never need to implement them manually.

## Ticket-based authentication

Collections support ticket-based peer authentication through `CollectionConfig`.
This adds an authentication requirement to the underlying Raft group — only
peers carrying valid tickets can join and replicate data.

```rust,ignore
use mosaik::CollectionConfig;
use mosaik::collections::{Map, StoreId};
use mosaik::tickets::{Jwt, Hs256};

let validator = Jwt::with_key(Hs256::new(secret))
    .allow_issuer("my-app");

let config = CollectionConfig::default()
    .require_ticket(validator);

let writer = Map::<String, u64>::writer_with_config(&network, store_id, config);
```

Call `require_ticket` multiple times to require multiple types of tickets.
Peers must satisfy **all** configured validators to participate.

> **Important:** Ticket validators affect the derived group ID. All members
> of the collection must use the same validators in the same order, otherwise
> they will derive different group IDs and will not see each other's changes.

The `collection!` macro supports `require_ticket` directly:

```rust,ignore
use mosaik::tickets::{Jwt, Hs256};

mosaik::collection!(
    pub SecureMap = mosaik::collections::Map<String, String>,
    "secure.store",
    require_ticket: Jwt::with_key(Hs256::new(secret))
        .allow_issuer("my-app"),
);
```

See [Auth Tickets](discovery/tickets.md) for the full JWT API and
[TEE > TDX](tee/tdx.md) for TDX-specific validation.

## StoreId and group identity

Each collection derives its Raft group identity from:

1. **A fixed prefix** per collection type (e.g., `"mosaik_collections_map"`,
   `"mosaik_collections_once"`)
2. **The `StoreId`** — a `UniqueId` you provide at construction time
3. **The Rust type names** of the type parameters
4. **Ticket validator signatures** (optional) — if `require_ticket` is configured

This means `Map::<String, u64>::writer(&net, id)` and
`Map::<u32, u64>::writer(&net, id)` will join **different** groups even with
the same `StoreId`, because the key type differs. Likewise, a `Once<String>`
and a `Cell<String>` with the same `StoreId` form separate groups because
the prefix differs.

## The `collection!` macro (recommended)

The `collection!` macro is the recommended way to declare named collection
definitions. It generates a struct with a compile-time `StoreId` and
implements the `CollectionReader` and/or `CollectionWriter` traits, so you
can create readers and writers without repeating the store ID at every call
site.

### Syntax

```rust,ignore

use mosaik::*;

// Full (reader + writer):
declare::collection!(pub Prices = mosaik::collections::Map<String, f64>, "prices");

// Reader only:
declare::collection!(pub reader Prices = mosaik::collections::Map<String, f64>, "prices");

// Writer only:
declare::collection!(pub writer Prices = mosaik::collections::Map<String, f64>, "prices");

// With generics:
declare::collection!(pub Items<T> = mosaik::collections::Vec<T>, "app.items");
```

The three modes control the public API of the generated struct:

| Mode        | Public                                        | `pub(crate)`                                    |
| ----------- | --------------------------------------------- | ----------------------------------------------- |
| *(default)* | `CollectionReader` **and** `CollectionWriter` ||
| `reader`    | `CollectionReader`                            | inherent `writer()` / `online_writer()`         |
| `writer`    | `CollectionWriter`                            | inherent `reader()` / `online_reader()`         |

In `reader` or `writer` mode, the restricted side is still available as
inherent methods with `pub(crate)` visibility. This means the crate that
defines the collection can still instantiate both sides internally, while
downstream consumers only see the publicly exported trait.

### Usage

Call the trait methods on the generated struct to create reader or writer
instances:

```rust,ignore
use mosaik::*;

declare::collection!(pub MyVec = mosaik::collections::Vec<String>, "my.vec");

struct MyType {
	reader: ReaderOf<MyVec>,
}

impl MyType {
	pub fn new(network: &mosaik::Network) -> Self {
		Self { reader: MyVec::reader(network) }
	}
}
```

#### `online_reader` / `online_writer`

Each trait also provides a convenience method that creates the handle and
awaits `.when().online()` in a single call:

```rust,ignore
// These two are equivalent:
let reader = MyVec::reader(&network);
reader.when().online().await;

let reader = MyVec::online_reader(&network).await;
```

This is useful when you don't need to do anything between construction and
the online check — it reduces the common two-step pattern to a single
expression.

The `ReaderOf<C>` and `WriterOf<C>` type aliases (re-exported at the crate
root) resolve to the concrete reader or writer type for a given collection
definition:

| Alias         | Expands to                        |
| ------------- | --------------------------------- |
| `ReaderOf<C>` | `<C as CollectionReader>::Reader` |
| `WriterOf<C>` | `<C as CollectionWriter>::Writer` |

### Store ID derivation

The string literal you pass as the last argument is hashed with blake3 at
compile time to produce the `StoreId`. If the string is exactly 64 hex
characters, it is decoded directly instead of hashed. This matches the
behavior of `unique_id!`.

#### Baked-in configuration

The `collection!` macro supports `require_ticket` for ticket-based
authentication:

```rust,ignore
declare::collection!(
    pub Prices = mosaik::collections::Map<String, f64>, "prices",
    require_ticket: MyValidator::new(),
);
```

Multiple `require_ticket` entries can be specified — peers must satisfy
all of them.

### When to use the macro vs. direct constructors

Use the `collection!` macro when:

- Multiple modules or crates reference the same collection.
- You want a single source of truth for the store ID and auth config.
- You want compile-time checked reader/writer type aliases via
  `ReaderOf<C>` / `WriterOf<C>`.

Use direct constructors (`Map::writer(&network, store_id)`) when:

- You only need the collection in one place.
- The store ID is computed at runtime.

## Collection definitions (`CollectionDef`)

> **Note:** The [`collection!` macro]#the-collection-macro-recommended is
> now the recommended approach for declaring named collection definitions.
> `CollectionDef`, `ReaderDef`, and `WriterDef` still work and are useful
> when you need an explicit value (e.g., passing a definition as a function
> argument), but for most use cases the macro is more ergonomic.

Instead of passing a `StoreId` to each constructor call, you can define a
collection's identity once as a constant and create readers and writers from it.

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

// Define the collection identity as a compile-time constant
const PRICES: CollectionDef<Map<String, f64>> =
	CollectionDef::new(unique_id!("prices"));

// Create writer and reader from the definition
let w = PRICES.writer(&network);
let r = PRICES.reader(&network);
```

The type parameter `T` on `CollectionDef<C, T>` encodes the collection's value
types as a tuple. For single-type collections it is the type itself; for
multi-type collections it is a tuple:

| Collection               | `T` parameter |
| ------------------------ | ------------- |
| `Map<K, V>`              | `(K, V)`      |
| `Vec<T>`                 | `T`           |
| `Set<T>`                 | `T`           |
| `Cell<T>`            | `T`           |
| `Once<T>`                | `T`           |
| `PriorityQueue<P, K, V>` | `(P, K, V)`   |

### Exporting reader-only or writer-only definitions

`CollectionDef` can produce narrowed definitions that expose only one side of
the writer/reader split. This is the recommended pattern when a library owns a
collection and wants to let consumers read from it without granting write
access:

```rust,ignore
// In your library crate:
pub const PRICES: CollectionDef<Map<String, f64>> =
	CollectionDef::new(unique_id!("prices"));

// Export only a reader definition for downstream consumers
pub const PRICES_READER: ReaderDef<Map<String, f64>> = PRICES.as_reader();
```

Downstream code uses `PRICES_READER` without needing the `StoreId`:

```rust,ignore
let reader = my_lib::PRICES_READER.open(&network);
reader.when().online().await;
```

A `WriterDef` works the same way via `as_writer()`.

All three definition types (`CollectionDef`, `ReaderDef`, `WriterDef`) are
`const`-constructible and can be used as top-level constants. See
[Writer/Reader Pattern](collections/writer-reader.md) for more on the
writer/reader split.

## Version tracking

All mutating operations return a `Version`, which wraps the Raft log `Index`
at which the mutation will be committed:

```rust,ignore
let version: Version = map.insert("key".into(), 42).await?;

// Wait until the mutation is committed and visible
map.when().reaches(version).await;
```

## Error handling

All write operations return `Result<Version, Error<T>>`:

| Variant              | Meaning                                                                       |
| -------------------- | ----------------------------------------------------------------------------- |
| `Error::Offline(T)`  | The node is temporarily offline. The value that failed is returned for retry. |
| `Error::NetworkDown` | The network is permanently down. The collection is no longer usable.          |

## SyncConfig

Collections use a snapshot-based state sync mechanism to bring lagging
followers up to date. The `SyncConfig` controls this behavior:

| Parameter                  | Default | Description                                       |
| -------------------------- | ------- | ------------------------------------------------- |
| `fetch_batch_size`         | `2000`  | Items per batch during snapshot transfer          |
| `snapshot_ttl`             | `10s`   | How long a snapshot stays valid after last access |
| `snapshot_request_timeout` | `15s`   | Timeout waiting for a `SnapshotReady` response    |
| `fetch_timeout`            | `5s`    | Timeout per `FetchDataResponse`                   |

> **Important:** Different `SyncConfig` values produce different group
> signatures. Collections using different configs will **not** see each other.

```rust,ignore
use mosaik::collections::{Map, StoreId, SyncConfig};
use std::time::Duration;

let config = SyncConfig::default()
    .with_fetch_batch_size(5000)
    .with_snapshot_ttl(Duration::from_secs(30));

let map = Map::<String, u64>::writer_with_config(&network, store_id, config);
```

## How snapshot sync works

1. A lagging follower sends a `RequestSnapshot` to the leader.
2. The leader wraps the request as a special command and replicates it.
3. When committed, **all** peers create a snapshot at the same log position.
4. The follower fetches snapshot data in batches from available peers.
5. Once complete, the follower installs the snapshot and replays any buffered
   commands received during sync.

Because `im` data structures support O(1) cloning, creating a snapshot is
nearly instant regardless of collection size.

## Deterministic hashing

Map and Set use `BuildHasherDefault<DefaultHasher>` (SipHash-1-3 with a fixed
zero seed) for their internal `im::HashMap` / `im::HashSet`. This ensures
that **iteration order is identical across all nodes** for the same logical
state — a requirement for snapshot sync to produce consistent chunked
transfers.