Live events
Instead of polling, subscribe to a context’s real-time events. mero.events(contextIds:)
opens a Server-Sent-Events stream and yields a ContextEvent for each emission —
new messages, application-version flips, and other context activity — so a view
can react as changes arrive.
Subscribe
Section titled “Subscribe”events(contextIds:) returns an AsyncThrowingStream<ContextEvent, Error>.
Consume it with for try await, inside a Task you can cancel:
let task = Task { do { for try await event in mero.events(contextIds: [contextId]) { switch event.kind { case "StateMutation": await reloadMessages() case "SyncStatus": print("syncing: \(event.payload)") default: print("event on \(event.contextId): \(event.kind)") } } } catch { // the stream ended with an error (e.g. auth lost) }}Each event is:
public struct ContextEvent: Sendable { public let contextId: String public let kind: String // "StateMutation" or "SyncStatus" public let payload: JSONValue // raw event JSON}kind is the frame’s result.type, and a node sends exactly two:
StateMutation (the context’s state moved) and SyncStatus
(syncing / waitingForPeers / …). There is no ExecutionEvent — a case for
one never fires.
The contract’s own events are nested a level down, under payload’s
data.events[], each with its own kind and a data byte array carrying
the encoded event. Captured from a live 0.11.0-rc.32 node:
{"result":{"contextId":"…","type":"StateMutation","data":{ "newRoot":"7320c55a…", "events":[{"kind":"Inserted","data":[123,34,107,…],"handler":null}]}}}So the frame tells you that state moved and the nested events[] tell you
what the contract emitted.
Reconnection
Section titled “Reconnection”The stream reconnects automatically ~3 seconds after a drop and re-subscribes to
your context ids (the node persists the session’s subscriptions). You don’t need
to rebuild the stream on a transient network blip — the for try await loop
keeps delivering events across reconnects.
Closing the stream
Section titled “Closing the stream”The stream’s lifetime is owned by the Task consuming it — cancel that task
to close the SSE connection:
task.cancel() // closes the connectionIn SwiftUI
Section titled “In SwiftUI”.task ties the stream to a view’s lifetime — it’s started when the view
appears and cancelled automatically when it disappears:
struct MessagesView: View { let contextId: String @State private var messages: [Message] = []
var body: some View { List(messages) { Text($0.text) } .task { do { for try await _ in mero.events(contextIds: [contextId]) { messages = await load() } } catch { /* stream ended */ } } }}See also
Section titled “See also”- RPC client — trigger the state changes you’re observing.
- Events reference —
SseClientand the full API surface.