defmodule Supabase.Client do
@moduledoc """
A client for interacting with Supabase. This module is responsible for
managing the connection pool and the connection options.
## Usage
Usually you don't need to use this module directly, instead you should
use the `Supabase` module, available on `:supabase_potion` application.
However, if you want to manage clients manually, you can leverage this
module to start and stop clients dynamically. To start a single
client manually, you need to add it to your supervision tree:
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
{Supabase.Client, name: :supabase, client_info: %{connections: %{default: :supabase}}}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Notice that starting a Client in this way, Client options will not be
validated, so you need to make sure that the options are correct. Otherwise
application will crash.
## Examples
iex> Supabase.Client.start_link(name: :supabase, client_info: client_info)
{:ok, #PID<0.123.0>}
iex> Supabase.Client.retrieve_client(:supabase)
%Supabase.Client{
name: :supabase,
connections: %{
default: :supabase
},
db: %Supabase.Client.Db{
schema: "public"
},
global: %Supabase.Client.Global{
headers: %{}
},
auth: %Supabase.Client.Auth{
auto_refresh_token: true,
debug: false,
detect_session_in_url: true,
flow_type: nil,
persist_session: true,
storage: nil,
storage_key: nil
}
}
iex> Supabase.Client.retrieve_connections(:supabase)
%{
default: :supabase
}
"""
use Agent
use Ecto.Schema
import Ecto.Changeset
@type t :: %__MODULE__{
name: atom,
connections: %{atom => atom},
db: db,
global: global,
auth: auth
}
@type db :: %__MODULE__.Db{
schema: String.t()
}
@type global :: %__MODULE__.Global{
headers: Map.t()
}
@type auth :: %__MODULE__.Auth{
auto_refresh_token: boolean(),
debug: boolean(),
detect_session_in_url: boolean(),
flow_type: String.t(),
persist_session: boolean(),
storage: String.t(),
storage_key: String.t()
}
@type params :: [
name: atom,
client_info: %{
connections: %{atom => atom},
db: %{
schema: String.t()
},
global: %{
headers: Map.t()
},
auth: %{
auto_refresh_token: boolean(),
debug: boolean(),
detect_session_in_url: boolean(),
flow_type: String.t(),
persist_session: boolean(),
storage: String.t(),
storage_key: String.t()
}
}
]
@primary_key false
embedded_schema do
field(:name, Supabase.Types.Atom)
field(:connections, {:map, Supabase.Types.Atom})
embeds_one :db, Db, primary_key: false do
field(:schema, :string)
end
embeds_one :global, Global, primary_key: false do
field(:headers, :map)
end
embeds_one :auth, Auth, primary_key: false do
field(:auto_refresh_token, :boolean, default: true)
field(:debug, :boolean, default: false)
field(:detect_session_in_url, :boolean, default: true)
field(:flow_type, :string)
field(:persist_session, :boolean, default: true)
field(:storage, :string)
field(:storage_key, :string)
end
end
@spec parse!(map) :: Supabase.Client.t()
def parse!(attrs) do
%__MODULE__{}
|> cast(attrs, [:name])
|> cast_embed(:db, required: true, with: &db_changeset/2)
|> cast_embed(:global, required: true, with: &global_changeset/2)
|> cast_embed(:auth, required: true, with: &auth_changeset/2)
|> put_change(:connections, attrs[:connections] || %{})
|> validate_required([:name, :connections])
|> apply_action!(:parse)
end
defp db_changeset(schema, params) do
schema
|> cast(params, [:schema])
|> validate_required([:schema])
end
defp global_changeset(schema, params) do
cast(schema, params, [:headers])
end
defp auth_changeset(schema, params) do
schema
|> cast(
params,
~w[auto_refresh_token debug detect_session_in_url persist_session flow_type storage storage_key]a
)
|> validate_required(
~w[auto_refresh_token debug detect_session_in_url persist_session flow_type]a
)
end
def start_link(config) do
name = Keyword.get(config, :name)
client_info = Keyword.get(config, :client_info)
Agent.start_link(fn -> parse!(client_info) end, name: name || __MODULE__)
end
@spec retrieve_client(pid) :: Supabase.Client.t()
def retrieve_client(pid) do
Agent.get(pid, & &1)
end
@spec retrieve_connections(pid) :: %{atom => atom}
def retrieve_connections(pid) do
Agent.get(pid, &Map.get(&1, :connections))
end
end