Chronicle.Connections.Lifecycle (cratis_chronicle v1.0.3)

Copy Markdown View Source

Tracks the Chronicle connection lifecycle and broadcasts phase changes.

Lifecycle is the single source of truth for whether a client is connected and for the connection_id used on the wire. It mirrors the C# client's IConnectionLifecycle: there is one lifecycle per Chronicle.Client, every artifact that needs the connection (the registration coordinator, reactors, reducers, seeders, webhook and subscription registrars) subscribes to it, and the connection is driven through a small set of phase transitions.

Phases

The connection moves through three ordered phases:

  • :disconnected — no live session with the kernel.
  • :connected — the session handshake completed (the kernel acknowledged the connection_id via its first keepalive). The channel can carry calls.
  • :registered — the registration coordinator has registered the base artifacts (event store, event types, read models, constraints, projections). Observers may now safely register their observation streams, and seeders may run.

Splitting :connected from :registered is what keeps reducers and reactors from racing ahead of their server-side definitions — they wait for :registered, never merely :connected.

connection_id

The lifecycle owns the connection_id. It is adopted on connected/2 and rotated on disconnected/1, so every reconnect uses a fresh id, exactly like the C# client. Because the id lives in one place, the value used in the session handshake and in every reactor/reducer registration is guaranteed consistent for a given connection epoch.

Subscribing

Subscribers call subscribe/1, which returns the current {phase, connection_id} snapshot and delivers it as a message. This closes the race where a subscriber starts after a phase change has already happened: it always learns the current phase immediately, whether it subscribed before or after the transition. Subsequent transitions arrive as messages of the form:

{:chronicle_lifecycle, phase, connection_id}

where phase is :connected, :registered, or :disconnected.

Summary

Functions

Returns a specification to start this module under a supervisor.

Transitions to :connected, adopting the given connection_id.

Returns the current connection id.

Transitions to :disconnected and rotates the connection_id.

Returns the registered lifecycle name for a client.

Returns the current phase.

Transitions to :registered.

Starts a lifecycle process.

Subscribes the calling process to lifecycle phase changes.

Blocks until the connection reaches at least target_phase.

Types

phase()

@type phase() :: :disconnected | :connected | :registered

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

connected(lifecycle, connection_id)

@spec connected(GenServer.server(), String.t()) :: :ok

Transitions to :connected, adopting the given connection_id.

Called by Chronicle.Connections.Session on the first server keepalive.

connection_id(lifecycle)

@spec connection_id(GenServer.server()) :: String.t()

Returns the current connection id.

disconnected(lifecycle)

@spec disconnected(GenServer.server()) :: :ok

Transitions to :disconnected and rotates the connection_id.

Called by Chronicle.Connections.Session when the session is lost.

name_for(client)

@spec name_for(atom()) :: atom()

Returns the registered lifecycle name for a client.

phase(lifecycle)

@spec phase(GenServer.server()) :: phase()

Returns the current phase.

registered(lifecycle)

@spec registered(GenServer.server()) :: :ok

Transitions to :registered.

Called by the registration coordinator once the base artifacts are registered with the kernel.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts a lifecycle process.

Options

  • :client — the owning client name; the process is registered as :"<client>.Lifecycle".
  • :name — an explicit registered name (overrides :client). When neither is given the process is unnamed and callers use the returned pid.

subscribe(lifecycle)

@spec subscribe(GenServer.server()) :: {:ok, phase(), String.t()}

Subscribes the calling process to lifecycle phase changes.

Returns {:ok, phase, connection_id} with the current snapshot and also sends the caller a {:chronicle_lifecycle, phase, connection_id} message so the subscribe-time and transition-time code paths are identical.

wait_until(lifecycle, target_phase, timeout \\ 30000)

@spec wait_until(GenServer.server(), phase(), timeout()) :: :ok | {:error, :timeout}

Blocks until the connection reaches at least target_phase.

Returns :ok immediately if the current phase is already at or beyond the target, otherwise waits up to timeout milliseconds. Returns {:error, :timeout} if the target is not reached in time.