defmodule SpacetimeDB.Types do @moduledoc "Structs representing SpacetimeDB WebSocket protocol types." # --------------------------------------------------------------------------- # Identifiers # --------------------------------------------------------------------------- defmodule Identity do @moduledoc "A 256-bit public key identifying a SpacetimeDB user or module." @enforce_keys [:hex] defstruct [:hex] @type t :: %__MODULE__{hex: String.t()} end defmodule ConnectionId do @moduledoc "An opaque identifier for a single client WebSocket connection." @enforce_keys [:hex] defstruct [:hex] @type t :: %__MODULE__{hex: String.t()} end defmodule Timestamp do @moduledoc "A point in time, in microseconds since the Unix epoch." @enforce_keys [:microseconds_since_epoch] defstruct [:microseconds_since_epoch] @type t :: %__MODULE__{microseconds_since_epoch: integer()} @doc "Convert to an Elixir `DateTime`." @spec to_datetime(t()) :: DateTime.t() def to_datetime(%__MODULE__{microseconds_since_epoch: us}), do: DateTime.from_unix!(us, :microsecond) end # --------------------------------------------------------------------------- # Table / row data # --------------------------------------------------------------------------- defmodule TableUpdate do @moduledoc """ Incremental row changes for a single table, as reported in a transaction or initial-subscription response. In the JSON protocol rows are plain JSON values (maps, lists, etc.) decoded directly from the server's response. """ defstruct table_id: nil, table_name: nil, inserts: [], deletes: [] @type t :: %__MODULE__{ table_id: non_neg_integer() | nil, table_name: String.t() | nil, inserts: [term()], deletes: [term()] } end # --------------------------------------------------------------------------- # Server → Client messages # --------------------------------------------------------------------------- defmodule IdentityToken do @moduledoc "First message after connecting — contains the client's identity and session token." @enforce_keys [:identity, :token, :connection_id] defstruct [:identity, :token, :connection_id] @type t :: %__MODULE__{ identity: String.t(), token: String.t(), connection_id: String.t() } end defmodule InitialSubscription do @moduledoc "Initial rows for a legacy multi-query `Subscribe` request." defstruct request_id: nil, tables: [], execution_time_micros: 0 @type t :: %__MODULE__{ request_id: non_neg_integer() | nil, tables: [TableUpdate.t()], execution_time_micros: non_neg_integer() } end defmodule SubscribeApplied do @moduledoc "Confirmation + initial rows for a `SubscribeSingle` or `SubscribeMulti` request." defstruct request_id: nil, query_id: nil, tables: [] @type t :: %__MODULE__{ request_id: non_neg_integer() | nil, query_id: non_neg_integer() | nil, tables: [TableUpdate.t()] } end defmodule UnsubscribeApplied do @moduledoc "Confirmation that a `Unsubscribe` was processed." defstruct request_id: nil, query_id: nil, tables: [] @type t :: %__MODULE__{ request_id: non_neg_integer() | nil, query_id: non_neg_integer() | nil, tables: [TableUpdate.t()] } end defmodule SubscriptionError do @moduledoc "The server rejected or invalidated a subscription." defstruct request_id: nil, query_id: nil, error: nil @type t :: %__MODULE__{ request_id: non_neg_integer() | nil, query_id: non_neg_integer() | nil, error: String.t() } end defmodule ReducerCallInfo do @moduledoc "Metadata about the reducer that produced a `TransactionUpdate`." defstruct reducer_name: nil, request_id: nil, status: nil @type t :: %__MODULE__{ reducer_name: String.t() | nil, request_id: non_neg_integer() | nil, status: :committed | {:failed, String.t()} | :out_of_energy | nil } end defmodule TransactionUpdate do @moduledoc """ Pushed to all subscribers whose queries match rows touched by a committed reducer call. Also sent back to the caller of the reducer (even without a matching subscription). """ defstruct [ :caller_identity, :caller_connection_id, :reducer_call, :timestamp, tables: [], status: :committed, energy_consumed: 0 ] @type status :: :committed | {:failed, String.t()} | :out_of_energy @type t :: %__MODULE__{ caller_identity: String.t() | nil, caller_connection_id: String.t() | nil, reducer_call: ReducerCallInfo.t() | nil, timestamp: Timestamp.t() | nil, tables: [TableUpdate.t()], status: status(), energy_consumed: non_neg_integer() } end defmodule OneOffQueryResponse do @moduledoc "Response to a `OneOffQuery` request." defstruct message_id: nil, error: nil, tables: [], execution_time_micros: 0 @type t :: %__MODULE__{ message_id: binary(), error: String.t() | nil, tables: [TableUpdate.t()], execution_time_micros: non_neg_integer() } end end