Packages
electric_client
0.8.2
0.10.3
0.10.2
0.10.1
0.10.1-beta-1
0.10.0
0.9.5-beta-1
0.9.4
0.9.4-beta-1
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.3-beta-1
0.8.2
0.8.1
0.8.0
0.8.0-beta-1
0.7.3
0.7.2
0.7.1
0.7.0
0.6.5
0.6.5-beta-5
0.6.5-beta-4
0.6.5-beta-3
0.6.5-beta-2
0.6.5-beta-1
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.5.0-beta-1
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.3.0-beta.4
0.3.0-beta.3
0.3.0-beta.2
0.2.6-pre-1
retired
0.2.6-beta.1
0.2.6-beta.0
0.2.5
0.2.4
0.2.4-pre-8
0.2.4-pre-7
0.2.4-pre-6
0.2.4-pre-5
0.2.4-pre-4
0.2.4-pre-3
0.2.4-pre-2
0.2.4-pre-1
0.2.3
0.2.3-rc-1
0.2.2
0.2.2-rc-1
0.2.1
0.2.1-rc-3
0.2.1-rc-2
0.2.1-rc-1
0.2.0
0.1.2
0.1.1
0.1.0
0.1.0-dev-9
0.1.0-dev-8
0.1.0-dev-7
0.1.0-dev-6
0.1.0-dev-5
0.1.0-dev-4
0.1.0-dev-3
0.1.0-dev-2
0.1.0-dev-17
0.1.0-dev-16
0.1.0-dev-15
0.1.0-dev-14
0.1.0-dev-13
0.1.0-dev-12
0.1.0-dev-11
0.1.0-dev-10
0.1.0-dev
Elixir client for ElectricSQL
Current section
Files
Jump to
Current section
Files
lib/electric/client/fetch/request.ex
defmodule Electric.Client.Fetch.Request do
use GenServer
alias Electric.Client
alias Electric.Client.Fetch
alias Electric.Client.Util
require Logger
defstruct [
:stream_id,
:endpoint,
:shape_handle,
:live,
:next_cursor,
# `replica` does not belong here, it's part of the shape definition that we
# receive via the client params. the other keys in this struct are part
# of consuming a stream rather than defining the shape.
# kept for backwards compatibility
replica: :default,
method: :get,
offset: Client.Offset.before_all(),
params: %{},
headers: %{},
authenticated: false
]
@type params :: %{String.t() => String.t()}
@type headers :: %{String.t() => [String.t()] | String.t()}
fields = [
stream_id: quote(do: term()),
method: quote(do: :get | :head | :delete | :options),
endpoint: quote(do: URI.t()),
offset: quote(do: Electric.Client.offset()),
shape_handle: quote(do: Electric.Client.shape_handle() | nil),
replica: quote(do: Electric.Client.replica()),
live: quote(do: boolean()),
next_cursor: quote(do: Electric.Client.cursor()),
params: quote(do: params()),
headers: quote(do: headers())
]
@type unauthenticated :: %__MODULE__{unquote_splicing(fields), authenticated: false}
@type authenticated :: %__MODULE__{unquote_splicing(fields), authenticated: true}
@type t :: unauthenticated() | authenticated()
# the base url should come from the client
attrs = Keyword.delete(fields, :endpoint)
attr_types =
attrs
|> Enum.reduce(nil, fn
{name, spec}, nil -> quote(do: unquote({name, spec}))
{name, spec}, acc -> quote(do: unquote({name, spec}) | unquote(acc))
end)
@type attr :: unquote(attr_types)
@type attrs :: [attr()] | %{unquote_splicing(attrs)}
@doc false
def name(request_id) do
{:via, Registry, {Electric.Client.Registry, {__MODULE__, request_id}}}
end
@doc """
Returns the URL for the Request.
"""
@spec url(t()) :: binary()
def url(%__MODULE__{} = request, opts \\ []) do
request
|> uri(opts)
|> URI.to_string()
end
@doc """
Returns the %URI{} for the Request.
"""
@spec uri(t()) :: URI.t()
def uri(%__MODULE__{} = request, opts \\ []) do
%{endpoint: endpoint} = request
if Keyword.get(opts, :query, true) do
# Convert map to _ordered_ list of query parameters
# to ensure consistent caching
query =
request
|> params()
|> Map.to_list()
|> List.keysort(0)
|> URI.encode_query(:rfc3986)
%{endpoint | query: query}
else
endpoint
end
end
@doc false
@spec params(t()) :: params()
def params(%__MODULE__{} = request) do
%{
replica: replica,
live: live?,
shape_handle: shape_handle,
offset: offset,
next_cursor: cursor,
params: params
} = request
(params || %{})
|> Map.merge(%{"offset" => to_string(offset)})
|> Util.map_put_if("replica", to_string(replica), replica != :default)
|> Util.map_put_if("handle", shape_handle, is_binary(shape_handle))
|> Util.map_put_if("live", "true", live?)
|> Util.map_put_if("cursor", to_string(cursor), !is_nil(cursor))
end
@doc false
def child_spec({request_id, _request, _client, _monitor_pid} = args) do
%{
id: {__MODULE__, request_id},
start: {__MODULE__, :start_link, [args]},
restart: :temporary,
type: :worker
}
end
@doc false
def start_link({request_id, request, client, monitor_pid}) do
GenServer.start_link(__MODULE__, {request_id, request, client, monitor_pid})
end
@impl true
def init({request_id, request, client, monitor_pid}) do
Logger.debug(fn ->
"Starting request for #{inspect(request_id)}"
end)
state = %{
request_id: request_id,
request: request,
client: client,
monitor_pid: monitor_pid
}
{:ok, state, {:continue, :request}}
end
@impl true
def handle_continue(:request, state) do
%{client: client, request: request} = state
%{fetch: {fetcher, fetcher_opts}} = client
authenticated_request = Client.authenticate_request(client, request)
try do
case fetcher.fetch(authenticated_request, fetcher_opts) do
{:ok, %Fetch.Response{status: status} = response} when status in 200..299 ->
reply(response, state)
{:ok, %Fetch.Response{} = response} ->
# Turn HTTP errors into errors
reply({:error, response}, state)
error ->
reply(error, state)
end
rescue
error ->
reply({:error, error}, state)
end
{:stop, :normal, state}
end
defp reply(response, %{monitor_pid: monitor_pid}) do
Fetch.Monitor.reply(monitor_pid, response)
end
end