# Realtime streams A realtime stream carries the events routed to one key, to whoever reads it. A route of a component's tree says which key it serves, and whatever authorizes the route authorizes the stream. ## Routes A route says which events go to which keys, and what of them: `key` is the property of the event that is the key, a [literal](#a-literal-key), or a list of them, and `expose` is what the event is streamed with. ```yaml # manifest.toa.yaml name: messages realtime: created: key: [sender, recipient] expose: [id, sender, text] updated: key: room expose: [id, room, text] ``` The event is what the component publishes for its entity — its payload, or `state` for an event that declares none. A key is any value: an identity, a record, a group the application makes up. Where the property holds a list, the event goes to every key in it. A value that begins with `~` is not a key, and the event does not go to it. `expose` is required, and a stream is given nothing it does not name: it is what stands between an event and whoever reads the streams it is routed to. A route without it is refused when the manifest is read. Routes are declared by the component, and only there. ## Serving a stream `realtime:stream` answers with the stream of a key, which a route variable or a [literal](#a-literal-key) names. Where the key comes from is the component's route: below, a message is routed to the stream of its room, and a room's stream is served at the room's route. ```yaml # manifest.toa.yaml namespace: chat name: messages entity: properties: room: type: string sender: type: string text: type: string # every message, as it is created, goes to the stream of its room realtime: created: key: room expose: [id, sender, text] exposition: /rooms/:room/stream: auth:role: moderator GET: realtime:stream: room # the value of :room is the key ``` A message created with `room: general` goes to the stream of the key `general`, which a moderator reads at: ```http GET /chat/messages/rooms/general/stream/ HTTP/1.1 authorization: Token ... accept: application/json ``` ``` {"event":"chat.messages.created","data":{"id":"…","sender":"…","text":"Hello!"}} ``` Who may read the stream is decided by the route's directives, as for anything else it serves: an identity without the `moderator` role is refused, and nothing is opened. ### An identity's own stream An identity's id is a key like any other. An event goes to an identity's stream where a route takes its key from a property that holds identity ids: ```yaml # messages/manifest.toa.yaml realtime: created: key: [sender, recipient] # both hold identity ids expose: [id, sender, recipient, text] ``` A message created with `recipient: 4c4759e6f9c14f3b8b2cc6e6f4c5b3a0` goes to the stream of that identity, which reads it at: ```http GET /realtime/4c4759e6f9c14f3b8b2cc6e6f4c5b3a0/ HTTP/1.1 authorization: Token ... accept: application/json ``` Every event routed to the identity's id comes to this one stream, whichever component routes it. The application declares nothing to serve it: the gateway declares the route itself. ```yaml /realtime/:id: auth:id: id GET: realtime:stream: id ``` ### A literal key A key that begins with `~` is a literal: the key itself, the same for every event it routes, rather than a property of the event. `realtime:stream` names it the same way, so its stream is served at a route that has no variable for it. ```yaml # manifest.toa.yaml namespace: chat name: messages # every message, as it is created, goes to the stream of `~room` realtime: created: key: ~room expose: [id, sender, text] exposition: /rooms/stream: auth:role: moderator GET: realtime:stream: ~room ``` ```http GET /chat/messages/rooms/stream/ HTTP/1.1 authorization: Token ... accept: application/json ``` The key is `~room`, with its `~`. A value is never a literal, so whoever writes the value of a property — a room's name — cannot route an event to a literal's stream, and a route variable that holds one is answered with `404`: a literal's stream is read only at a route that names it. A literal is a key of the context, as every key is: another component that routes to `~room` writes to the same stream. A list of keys may mix literals and properties: `key: [~room, sender]`. ## Pushing to a stream `realtime:push` sends the request body to every reader of a key, as it arrives: nothing is stored and no operation is called. It is for state that changes many times a second and is superseded each time — where a cursor is, where a shape is being dragged — that a reader wants as it is now and never as it was. ```yaml # manifest.toa.yaml namespace: canvas name: boards exposition: /:id/stream: auth:role: editor:{id} GET: realtime:stream: id /:id/cursors: auth:role: editor:{id} POST: realtime:push: key: id # the route variable, or a literal, as `realtime:stream` names it event: cursor # what a reader is sent it as input: # a JSON schema of what may be pushed; required type: object properties: x: { type: number } y: { type: number } tool: { type: string } required: [x, y] additionalProperties: false ``` ```http POST /canvas/boards/42/cursors/ HTTP/1.1 authorization: Token ... content-type: application/json {"x":10.5,"y":3,"tool":"pen"} ``` ``` 204 No Content ``` Every reader of `/canvas/boards/42/stream/` is sent: ``` {"event":"cursor","sender":"4c4759e6f9c14f3b8b2cc6e6f4c5b3a0","data":{"x":10.5,"y":3,"tool":"pen"}} ``` `sender` is the id of the identity that pushed it, absent where the route admits an anonymous caller: a client cannot push as another identity. A body `input` refuses is answered `400`, one past 64 KiB is answered `413`, and nothing is pushed for a request the route's directives refuse. `event` is neither `token` nor a name with a dot, so a reader never takes a pushed event for a token or for a routed event. Whatever `input` admits reaches every reader of the key, so state `additionalProperties: false` where a property the schema does not name should not. A pushed event is for whoever reads the key at the moment it is pushed: - it is not followed by a [token](#reading-a-stream), and a reader that [reconnects](#reconnecting) is not sent it; - a reader whose connection does not keep up loses pushed events, and never a routed one; - it reaches the readers of its region only. So keep the last one per `sender`, and leave to an operation whatever has to survive — a shape drawn, a comment — routing the event it publishes to the same key. A reader that has just opened the stream reads that state, and is sent the next push. ## Reading a stream A stream is a [multipart response](protocol.md#multipart-types). After `ACK`, a part is one of four: - **A routed event**: an object whose `event` is the event's full name and whose `data` is what the route exposes of it. - **A pushed event**: an object whose `event` is the name the [pushing](#pushing-to-a-stream) route gives it, whose `sender` is the identity that pushed it, and whose `data` is what was pushed. - **A token**: an object of the same shape, whose `event` is the word `token` and whose `data` is the token. It follows every routed event, and is sent once when the stream opens. Keep the last one: it is what a reader [reconnects](#reconnecting) with. - **A heartbeat**: a string, `heartbeat