Skip to main content

Overview

A listener is a filter on one session’s event stream that pushes each match to the connection that added it. It lasts as long as that connection and is gone when the connection closes. Declared listeners, which act inside the session instead of pushing, are on Listeners.

Add a listener

listener/add starts a listener on a session of a revision. The caller needs READ on the revision’s blob.
The server answers with the id of the new listener:
A connection holds at most 32 listeners (workflow.listeners_per_connection). Frames name their listener by listener_id, which is how one connection tells its listeners apart.

Remove a listener

listener/remove ends a listener of this connection.
The server answers {} in result. A listener_id this connection does not hold, whether it never existed, was already removed or belongs to another connection, is answered with a 400 invalid_params error and the connection stays open.

Receive events

Without batch, each matching event arrives in its own frame. The frame is a notification, so it has no id.
event is the event record that get_session_event returns.

Batch

With batch: {window_seconds?, max_events?, distinct_by?} events are collected and arrive together:
  • window_seconds (default 0) opens at the first pending event’s own time.
  • max_events (default 100 with a batch) counts entries after distinct_by.
  • distinct_by is up to 8 dotted paths. Events that agree on every path are one entry, which keeps the newest event.
  • count is the number of entries in events; first_id and last_id are the ids of the first and last of them.
  • A frame is cut before it passes 120 KiB. An event that alone passes 120 KiB is never pushed; read it with get_session_event.
An invalid batch is answered invalid_params and no listener is added.

Resume

Pushes are at least once and can be lost, and a connection lasts at most two hours, so a client that must not miss an event pairs the listener with the listing:
  1. Connect, send status/initialize, and listener/add first, so nothing that happens next is missed.
  2. Read what came before with list_session_events in ascending order, with after: <last id held> when there is one.
  3. Read again from the boundary id of a timestamp a minute older than the last event seen, because a row can commit after a row with a larger id. This is the settle time recipe.
  4. Drop every id already held, from pushes and listing alike. An id is the event’s identity.
  5. After a reconnect, repeat from step 1 with after set to the last id held. Listeners do not survive a connection, so add them again.
The worker is a client of this kind: it follows a session’s events and reacts to the thread items posted in it.

Keep the connection

status/initialize must be the first request on a connection; any other method but status/ping before it is a 405 and the server closes the connection. status/ping answers {} in every phase and writes nothing. API Gateway closes a connection idle for 10 minutes, so ping at least that often. It also closes every connection after 2 hours, whatever its traffic. See Lifecycle.

Errors

An error answers the request that caused it, in the error shape. A message that is not valid JSON-RPC, or that names an internal method, closes the connection without a reply. A 403 does not say whether the revision or session exists.