Skip to main content

lib/rpc_elixir/unix_millis.ex

defmodule RpcElixir.UnixMillis do
  @moduledoc """
  Built-in branded-number custom type: a `DateTime` crossing the wire as epoch
  milliseconds. Emits the TypeScript brand `EpochMillis`
  (a `number & { readonly __brand: "EpochMillis" }`), so callers can't
  accidentally pass a bare number where an instant is expected.

  Use per-field as `RpcElixir.UnixMillis.t()`, or map every `DateTime` in a router
  to it via `use RpcElixir.Router, wire_aliases: [{DateTime, RpcElixir.UnixMillis}]`.
  """
  @behaviour RpcElixir.CustomType

  @type t :: DateTime.t()

  @doc "Wire format: a JSON integer (epoch milliseconds)."
  @impl true
  def wire_spec, do: %{kind: "primitive", type: "integer"}

  @doc "Serializes a `DateTime` to integer epoch milliseconds."
  @impl true
  def serialize(%DateTime{} = dt), do: DateTime.to_unix(dt, :millisecond)

  def serialize(other),
    do:
      raise(
        ArgumentError,
        "RpcElixir.UnixMillis can only serialize a DateTime, got: #{inspect(other)}"
      )

  @doc "Deserializes integer epoch milliseconds to `{:ok, DateTime.t()} | {:error, atom}`."
  @impl true
  def deserialize(ms) when is_integer(ms), do: DateTime.from_unix(ms, :millisecond)

  @doc "TypeScript brand name emitted for this type."
  @impl true
  def ts_type, do: "EpochMillis"
end