# Primitives Reference
This chapter documents the foundational types and utilities that the rest of
mosaik builds on. These live in the `primitives` module (public types) and
internal helpers.
## `Digest` / `UniqueId` / `Tag`
The core identifier type — a 32-byte blake3 hash.
```rust,ignore
use mosaik::Digest; // re-exported at crate root
```
`Digest`, `UniqueId`, and `Tag` are all the same type, aliased for clarity in
different contexts:
| `Digest` | General-purpose 32-byte identifier |
| `UniqueId` | Derived identity (groups, stores, bonds) |
| `Tag` | Discovery tags for peer classification |
| `NetworkId` | Network identifier (`= Digest`) |
| `GroupId` | Group identifier (`= Digest`) |
| `StreamId` | Stream identifier (`= Digest`) |
| `StoreId` | Collection store identifier (`= UniqueId`) |
### Construction
```rust,ignore
// From a string (hashes the string with blake3)
let tag = Digest::from("validator");
// From a hex string (64 chars = direct decode, otherwise hashed)
let id = Digest::from("a1b2c3d4...");
// From numeric types (little-endian encoded, then hashed)
let id = Digest::from_u64(42);
// From multiple parts (concatenated, then hashed)
let id = Digest::from_parts(&["prefix", "suffix"]);
// Derive a child ID deterministically
let child = parent.derive("child-name");
// Random
let id = Digest::random();
// Zero (sentinel value)
let zero = Digest::zero();
```
### Compile-time construction
Two equivalent macros are available — `id!` is the short alias:
```rust,ignore
use mosaik::{id, unique_id};
const MY_ID: UniqueId = id!("a1b2c3d4e5f6..."); // 64-char hex literal
const MY_ID: UniqueId = unique_id!("a1b2c3d4e5f6..."); // identical, longer name
```
Both accept either a 64-character hex string (decoded directly) or any other
string (hashed with blake3).
#### `id!` vs `unique_id!`
The `id!` macro is a short alias for `unique_id!` — both produce a compile-time
`UniqueId` from a string literal. Use whichever reads more naturally.
### Display
- `Display` — short hex (first 5 bytes): `a1b2c3d4e5`
- `Debug` — full 64-character hex string
- `Short<T>` wrapper — always shows first 5 bytes
- `Abbreviated<const LEN, T>` — shows `first..last` if longer than LEN
### Serialization
- **Human-readable** formats (JSON, TOML): hex string
- **Binary** formats (postcard): raw 32 bytes
---
## `Ticket`
An opaque, typed credential that peers attach to their discovery entry for
authorization purposes. Ticket types live in the `mosaik::tickets` module.
```rust,ignore
use mosaik::{tickets::Ticket, UniqueId, id, Bytes};
const MY_AUTH: UniqueId = id!("my-app.auth");
let ticket = Ticket::new(MY_AUTH, Bytes::from("credential-data"));
let id: UniqueId = ticket.id(); // deterministic, derived from class + data
```
| `class` | `UniqueId` | Identifies the authorization scheme |
| `data` | `Bytes` | Opaque payload; format determined by the class |
Tickets are stored in a peer's `PeerEntry` and propagated via gossip and
catalog sync. The discovery system never interprets ticket data --
validation is done via `TicketValidator` implementations or `require`
closures.
The `mosaik::tickets` module also provides:
- **`Jwt`** — built-in JWT validator supporting HS256/384/512,
ES256/256K/384/512, and EdDSA.
- **`JwtTicketBuilder`** — signs and issues JWT tickets for peers.
- **`TicketValidator`** trait — implement for custom credential schemes.
- Algorithm-specific key types (`Hs256`, `Es256`, `Es256SigningKey`,
`EdDsa`, `EdDsaSigningKey`, etc.).
See [Discovery > Auth Tickets](../subsystems/discovery/tickets.md) for
a full walkthrough.
---
## `PeerId`
```rust,ignore
type PeerId = iroh::EndpointId;
```
A peer's public identity, derived from their Ed25519 public key. This is an
iroh type, not a mosaik `Digest`. It is used in discovery, bonds, and
connection authentication.
## `SecretKey`
Re-exported from iroh. An Ed25519 secret key that determines a node's
`PeerId`. If not provided to `NetworkBuilder`, a random key is generated
automatically — this is the recommended default for regular nodes.
Specifying a fixed secret key is only recommended for **bootstrap nodes**
that need a stable, well-known peer ID across restarts:
```rust,ignore
use mosaik::SecretKey;
// Auto-generated (default) — new identity each run
let network = Network::builder()
.network_id("my-network")
.build().await?;
// Fixed key — stable identity, recommended only for bootstrap nodes
let key = SecretKey::generate(&mut rand::rng());
let bootstrap = Network::builder()
.network_id("my-network")
.secret_key(key)
.build().await?;
```
---
## The `collection!` macro
The recommended way to declare named collection definitions. It generates a
struct with a compile-time `StoreId` and implements `CollectionReader` and/or
`CollectionWriter`.
```rust,ignore
use mosaik::collection;
// Full (reader + writer)
collection!(pub Prices = mosaik::collections::Map<String, f64>, "prices");
// Reader only
collection!(pub reader Prices = mosaik::collections::Map<String, f64>, "prices");
// Writer only
collection!(pub writer Prices = mosaik::collections::Map<String, f64>, "prices");
// With generics
collection!(pub Items<T> = mosaik::collections::Vec<T>, "app.items");
```
The store ID string is **intent-addressed** — hashed with blake3 at compile
time (or decoded directly if it is exactly 64 hex characters). Two nodes that
independently declare the same string converge on the same `StoreId` without
coordination.
See [Collections > collection! macro](../subsystems/collections.md#the-collection-macro-recommended)
for the full syntax reference, mode table, and usage examples.
## Collection traits and type aliases
These traits and aliases work with types generated by the `collection!` macro.
The traits live in `mosaik::collections`; the type aliases are re-exported at
the crate root.
| `CollectionReader` | Trait | Provides `fn reader(&Network)` and `fn online_reader(&Network)` |
| `CollectionWriter` | Trait | Provides `fn writer(&Network)` and `fn online_writer(&Network)` |
| `ReaderOf<C>` | Type alias | `<C as CollectionReader>::Reader` — the concrete reader |
| `WriterOf<C>` | Type alias | `<C as CollectionWriter>::Writer` — the concrete writer |
The `online_reader` and `online_writer` methods create the handle and await
`.when().online()` in a single call, reducing the common two-step pattern.
```rust,ignore
use mosaik::{collection, collections::CollectionReader, ReaderOf};
collection!(pub MyVec = mosaik::collections::Vec<String>, "my.vec");
// ReaderOf<MyVec> resolves to VecReader<String>
let reader: ReaderOf<MyVec> = MyVec::reader(&network);
```
## The `stream!` macro
The recommended way to declare named stream definitions. It generates a
struct with a compile-time `StreamId` and implements `StreamProducer`
and/or `StreamConsumer`.
```rust,ignore
use mosaik::stream;
// Type-derived StreamId (most common)
stream!(pub PriceFeed = PriceUpdate);
// Explicit StreamId
stream!(pub PriceFeed = PriceUpdate, "oracle.price");
// Producer only
stream!(pub producer PriceFeed = PriceUpdate, "oracle.price");
// Consumer only
stream!(pub consumer PriceFeed = PriceUpdate, "oracle.price");
// With configuration
stream!(pub PriceFeed = PriceUpdate, "oracle.price",
producer require: |peer| peer.tags().contains(&tag!("trusted")),
producer online_when: |c| c.minimum_of(2),
consumer require: |peer| true,
);
```
The stream ID string is **intent-addressed** — hashed with blake3 at compile
time (or decoded directly if it is exactly 64 hex characters). When omitted,
the `StreamId` is derived from the datum type name. Two nodes that
independently declare the same string converge on the same `StreamId` without
coordination.
See [Streams > stream! macro](../subsystems/streams.md#the-stream-macro-recommended)
for the full syntax reference, configuration keys, and usage examples.
## Stream traits and type aliases
These traits and aliases work with types generated by the `stream!` macro.
The traits live in `mosaik::streams`; the type aliases are re-exported at
the crate root.
| `StreamProducer` | Trait | Provides `fn producer(&Network)` and `fn online_producer(&Network)` |
| `StreamConsumer` | Trait | Provides `fn consumer(&Network)` and `fn online_consumer(&Network)` |
| `ProducerOf<S>` | Type alias | `<S as StreamProducer>::Producer` — the concrete producer |
| `ConsumerOf<S>` | Type alias | `<S as StreamConsumer>::Consumer` — the concrete consumer |
The `online_producer` and `online_consumer` methods create the handle and
await `.when().online()` in a single call, reducing the common two-step
pattern.
```rust,ignore
use mosaik::{stream, streams::StreamProducer, ProducerOf};
stream!(pub PriceFeed = PriceUpdate, "oracle.price");
// ProducerOf<PriceFeed> resolves to Producer<PriceUpdate>
let producer: ProducerOf<PriceFeed> = PriceFeed::producer(&network);
```
## Stream definitions (`ProducerDef` / `ConsumerDef`)
> **Note:** The [`stream!` macro](#the-stream-macro) is now the
> recommended approach. `ProducerDef` and `ConsumerDef` still work and
> are useful when you need an explicit definition value.
These types allow you to define a stream's identity at compile time and
create pre-configured builders from it. They live in `mosaik::streams`.
| `ProducerDef<T>` | Producer definition — creates a producer builder |
| `ConsumerDef<T>` | Consumer definition — creates a consumer builder |
```rust,ignore
use mosaik::{unique_id, streams::ProducerDef};
const PRICES: ProducerDef<PriceUpdate> =
ProducerDef::new(Some(unique_id!("prices")));
let producer = PRICES.open(&network).build()?;
```
| `const fn new(Option<StreamId>)` | Both types | `Self` |
| `fn open(&self, &Network)` | Both types | Builder |
---
## Collection definitions (`CollectionDef`)
> **Note:** The [`collection!` macro](#the-collection-macro) is now the
> recommended approach. `CollectionDef`, `ReaderDef`, and `WriterDef` still
> work and are useful when you need an explicit definition value.
These types allow you to define a collection's identity at compile time and
create readers or writers from it. They live in `mosaik::collections`.
| `CollectionDef<C>` | Full definition — can create both writers and readers for collection C |
| `ReaderDef<C>` | Reader-only definition — typically exported by library authors |
| `WriterDef<C>` | Writer-only definition |
| `CollectionFromDef` | Trait implemented for all collection types; associates Reader/Writer |
The type parameter `C` is the collection type (e.g., `Map<String, u64>`) and
`T` encodes the value types as a tuple (`(String, u64)` for Map, `T` directly
for single-type collections like `Vec<T>`).
```rust,ignore
use mosaik::{unique_id, collections::{CollectionDef, ReaderDef, Map}};
const MY_MAP: CollectionDef<Map<String, u64>> =
CollectionDef::new(unique_id!("my-map"));
const MY_MAP_READER: ReaderDef<Map<String, u64>> =
MY_MAP.as_reader();
```
| `const fn new(StoreId)` | All three types | `Self` |
| `const fn as_reader(&self)` | `CollectionDef` | `ReaderDef` |
| `const fn as_writer(&self)` | `CollectionDef` | `WriterDef` |
| `fn reader(&self, &Network)` | `CollectionDef`, `ReaderDef` | `C::Reader` |
| `fn writer(&self, &Network)` | `CollectionDef`, `WriterDef` | `C::Writer` |
See [Collections > CollectionDef](../subsystems/collections.md#collection-definitions-collectiondef)
for usage examples and the full type-parameter mapping table.
---
## Wire encoding
All network messages use **postcard** — a compact, `#[no_std]`-compatible
binary format using variable-length integers.
| `serialize<T: Serialize>(&T) -> Bytes` | Serialize to bytes (panics on failure) |
| `deserialize<T: DeserializeOwned>(impl AsRef<[u8]>) -> Result<T>` | Deserialize from bytes |
These are internal crate functions. Application code interacts with them
indirectly through `Link<P>` send/receive and collection operations.
---
## `Bytes` / `BytesMut`
Re-exported from the `bytes` crate. Used throughout mosaik for zero-copy
byte buffers:
```rust,ignore
use mosaik::Bytes;
let data: Bytes = serialize(&my_message);
```
---
## `BackoffFactory`
A factory type for creating retry backoff strategies:
```rust,ignore
type BackoffFactory = Arc<
dyn Fn() -> Box<dyn Backoff + Send + Sync + 'static>
+ Send + Sync + 'static
>;
```
Used in `streams::Config` to configure consumer reconnection. The `backoff`
crate is re-exported at `mosaik::streams::backoff`.
---
## Formatting utilities
Internal helpers for consistent debug output:
| Type | Description |
| --------------------------- | ---------------------------------------------------- |
| `Pretty<T>` | Pass-through wrapper (for trait integration) |
| `Short<T>` | Display first 5 bytes as hex |
| `Abbreviated<const LEN, T>` | Show `first..last` hex if longer than LEN bytes |
| `Redacted<T>` | Always prints `<redacted>` |
| `FmtIter<W, I>` | Format iterator elements comma-separated in brackets |
---
## `IntoIterOrSingle<T>`
An ergonomic trait that lets API methods accept either a single item or an
iterator:
```rust,ignore
// Both work:
discovery.with_tags("validator"); // single tag
discovery.with_tags(["validator", "relay"]); // multiple tags
```
This is implemented via two blanket impls using `Variant<0>` (single item
via `Into<T>`) and `Variant<1>` (iterator of `Into<T>`).
---
## Internal async primitives
These are `pub(crate)` and not part of the public API, but understanding them
helps when reading mosaik's source code.
### `UnboundedChannel<T>`
A wrapper around `tokio::sync::mpsc::unbounded_channel` that keeps both the
sender and receiver in one struct:
```rust,ignore
let channel = UnboundedChannel::new();
channel.send(42);
let val = channel.recv().await;
```
| `sender()` | Get `&UnboundedSender<T>` |
| `receiver()` | Get `&mut UnboundedReceiver<T>` |
| `send(T)` | Send (errors silently ignored) |
| `recv()` | Async receive |
| `poll_recv(cx)` | Poll-based receive |
| `poll_recv_many(cx, buf, limit)` | Batch poll receive |
| `is_empty()` / `len()` | Queue inspection |
### `AsyncWorkQueue<T>`
A `FuturesUnordered` wrapper with a permanently-pending sentinel future so
that polling never returns `None`:
```rust,ignore
let queue = AsyncWorkQueue::new();
queue.enqueue(async { do_work().await });
// Poll via Stream trait — never completes while empty
while let Some(result) = queue.next().await {
handle(result);
}
```
### `BoxPinFut<T>`
Type alias for boxed, pinned, send futures:
```rust,ignore
type BoxPinFut<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
```
The `InternalFutureExt` trait adds a `.pin()` method to any
`Future + Send + 'static` for ergonomic boxing.