# Session Types as Runtime-Typed Streams
## The Hub as a Self-Describing Type System
The hub isn't just an RPC server—it's a **typed system that exposes its own types at runtime**. Every method's protocol is introspectable: clients can discover not just "what methods exist" but "what is the exact shape of the conversation I'll have with this method."
Session types are the compile-time contract. Streams are the runtime execution. The schema is the bridge that lets external systems understand both.
## The Key Insight: Session Type IS the Stream Definition
A session type like:
```rust
Loop<Choose<(Send<Chunk, Continue>, Send<Done, Done>)>>
```
Doesn't need to be "converted" to a stream. It **defines** the stream's item type:
```rust
Stream<Item = enum { Chunk(Chunk), Done(Done) }>
```
The macro doesn't convert—it **extracts** the implicit enum from the session type.
## Pipeline: Session Type → Schema
The transformation happens in stages, each building on the previous:
### Stage 1: Proc Macro AST Parsing
The `#[hub_method]` macro receives the return type as `syn::Type`. We recursively walk this AST:
```
Input: Loop<Choose<(Send<Chunk, Continue<0>>, Send<Exit, Done>)>>
AST Walk:
TypePath "Loop"
→ generic arg: TypePath "Choose"
→ generic arg: TypeTuple
→ TypePath "Send" [Chunk, Continue<0>]
→ TypePath "Send" [Exit, Done]
```
Output: An intermediate representation (IR) capturing protocol structure and payload type paths.
### Stage 2: IR → Generated Code
From the IR, generate:
```rust
// 1. Protocol schema function (uses ToProtocolSchema trait)
fn method_schema() -> MethodSchema {
MethodSchema {
name: "method",
protocol: <ReturnType as ToProtocolSchema>::protocol_schema(),
server_protocol: <Dual<ReturnType> as ToProtocolSchema>::protocol_schema(),
}
}
// 2. Stream item enum (extracted from Choose branches)
enum MethodStreamItem {
Chunk(Chunk), // from Send<Chunk, Continue>
Exit(Exit), // from Send<Exit, Done>
}
// 3. SessionItem impl
impl SessionItem for MethodStreamItem {
fn is_terminal(&self) -> bool {
matches!(self, Self::Exit(_))
}
}
```
### Stage 3: Runtime Schema Generation
At runtime, `ToProtocolSchema::protocol_schema()` is called. This uses the trait impls we built:
```rust
impl<T: JsonSchema, S: ToProtocolSchema> ToProtocolSchema for Send<T, S> {
fn protocol_schema() -> ProtocolSchema {
ProtocolSchema::Send {
payload_type: type_name::<T>(),
payload_schema: schemars::schema_for!(T),
then: Box::new(S::protocol_schema()),
}
}
}
```
The session type's structure is preserved; payload types get their JSON Schema via schemars.
### Stage 4: Schema Serialization
The `ProtocolSchema` enum serializes to JSON, giving clients the full protocol description:
```json
{
"type": "loop",
"body": {
"type": "choose",
"branches": [
{ "type": "send", "payload_schema": {...}, "then": { "type": "continue" } },
{ "type": "send", "payload_schema": {...}, "then": { "type": "done" } }
]
}
}
```
## The Union Type for Methods
Each method's return type contributes to a plugin-level union:
```rust
// Generated by #[hub_methods]
enum MyPluginEvent {
Execute(ExecuteStreamItem), // from execute() -> Loop<Choose<...>>
Status(StatusResponse), // from status() -> Send<StatusResponse, Done>
}
```
The schema exposes this:
```json
{
"methods": {
"execute": {
"protocol": { "type": "loop", "body": { "type": "choose", ... } },
"stream_item_schema": { "oneOf": [...] }
},
"status": {
"protocol": { "type": "send", "then": "done" },
"response_schema": { ... }
}
}
}
```
## What the Macro Generates
For `#[hub_methods]` on an impl block:
1. **Per-method schema functions**: `fn execute_schema() -> MethodSchema`
2. **Stream item enums**: Generated from session type's `Choose` branches
3. **Method enum**: `enum MyPluginMethod { Execute(Input), Status }` for dispatch
4. **Event union**: `enum MyPluginEvent { ... }` for all possible outputs
5. **RPC trait + impl**: jsonrpsee integration with subscriptions for streams
6. **Activation trait impl**: Plugin system integration
## Summary
| 1. AST Parse | `syn::Type` | Protocol IR |
| 2. Codegen | Protocol IR | Rust code (enums, impls, fns) |
| 3. Runtime | `ToProtocolSchema` call | `ProtocolSchema` value |
| 4. Serialize | `ProtocolSchema` | JSON Schema |
The hub exposes its types because the types ARE the protocol. Session types make the implicit explicit—then we serialize that explicitness as JSON Schema.