From 06130c6449360c6682d81d9595a435b0c8967475 Mon Sep 17 00:00:00 2001 From: Garry Hill Date: Thu, 9 Oct 2025 14:48:10 +0100 Subject: [PATCH 1/3] fix: fetch query params in plug controller wrapper --- lib/phoenix/sync/controller.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/phoenix/sync/controller.ex b/lib/phoenix/sync/controller.ex index caf5a6e..5bcaa60 100644 --- a/lib/phoenix/sync/controller.ex +++ b/lib/phoenix/sync/controller.ex @@ -84,12 +84,16 @@ defmodule Phoenix.Sync.Controller do ) def sync_render(conn, shape_fun) when is_function(shape_fun, 0) do + conn = Plug.Conn.fetch_query_params(conn) + conn |> Phoenix.Sync.Controller.configure_plug_conn!(@plug_assign_opts) |> Phoenix.Sync.Controller.sync_render(conn.params, shape_fun) end def sync_render(conn, shape, shape_opts \\ []) do + conn = Plug.Conn.fetch_query_params(conn) + conn |> Phoenix.Sync.Controller.configure_plug_conn!(@plug_assign_opts) |> Phoenix.Sync.Controller.sync_render(conn.params, shape, shape_opts) From 09025b74fb682258434f7054da5ec59db0943643 Mon Sep 17 00:00:00 2001 From: Garry Hill Date: Mon, 13 Oct 2025 11:21:24 +0100 Subject: [PATCH 2/3] fix: A send plug response within the request process Bandit (and Electric) require responses to be consumed/sent within the original request process. Fixes #110 --- apps/plug_sync/lib/plug_sync/router.ex | 6 +++ lib/phoenix/sync/adapter/plug_api.ex | 8 ++++ lib/phoenix/sync/controller.ex | 6 +-- lib/phoenix/sync/electric.ex | 39 ++++++++++++++++- lib/phoenix/sync/electric/api_adapter.ex | 28 ++++++++++++ lib/phoenix/sync/electric/client_adapter.ex | 48 +++++++++++++++------ lib/phoenix/sync/sandbox/api_adapter.ex | 18 ++++++++ 7 files changed, 136 insertions(+), 17 deletions(-) diff --git a/apps/plug_sync/lib/plug_sync/router.ex b/apps/plug_sync/lib/plug_sync/router.ex index 3208fd6..b12a87b 100644 --- a/apps/plug_sync/lib/plug_sync/router.ex +++ b/apps/plug_sync/lib/plug_sync/router.ex @@ -1,12 +1,18 @@ defmodule PlugSync.Router do use Plug.Router, copy_opts_to_assign: :options use Phoenix.Sync.Router + use Phoenix.Sync.Controller + plug Plug.Logger, log: :debug plug :match plug :dispatch sync "/items-mapped", table: "items", transform: &PlugSync.Router.map_item/1 + get "/items-interruptible" do + sync_render(conn, fn -> [table: "items"] end) + end + match _ do send_resp(conn, 404, "not found") end diff --git a/lib/phoenix/sync/adapter/plug_api.ex b/lib/phoenix/sync/adapter/plug_api.ex index 0ac5ae2..9c1767f 100644 --- a/lib/phoenix/sync/adapter/plug_api.ex +++ b/lib/phoenix/sync/adapter/plug_api.ex @@ -1,9 +1,17 @@ defprotocol Phoenix.Sync.Adapter.PlugApi do @moduledoc false + @type response() :: term() + @spec predefined_shape(t(), Phoenix.Sync.PredefinedShape.t()) :: {:ok, t()} | {:error, term()} def predefined_shape(api, shape) @spec call(t(), Plug.Conn.t(), Plug.Conn.params()) :: Plug.Conn.t() def call(api, conn, params) + + @spec response(t(), Plug.Conn.t(), Plug.Conn.params()) :: response() + def response(api, conn, params) + + @spec send_response(t(), Plug.Conn.t(), response()) :: Plug.Conn.t() + def send_response(api, conn, response) end diff --git a/lib/phoenix/sync/controller.ex b/lib/phoenix/sync/controller.ex index 5bcaa60..0ffd829 100644 --- a/lib/phoenix/sync/controller.ex +++ b/lib/phoenix/sync/controller.ex @@ -252,7 +252,7 @@ defmodule Phoenix.Sync.Controller do {:ok, pid} = Task.start_link(fn -> - send(parent, {:response, self(), Adapter.PlugApi.call(shape_api, conn, params)}) + send(parent, {:response, self(), Adapter.PlugApi.response(shape_api, conn, params)}) end) ref = Process.monitor(pid) @@ -274,10 +274,10 @@ defmodule Phoenix.Sync.Controller do interruptible_call(conn, api, params, shape_fun) - {:response, ^pid, conn} -> + {:response, ^pid, response} -> Process.demonitor(ref, [:flush]) - conn + Adapter.PlugApi.send_response(shape_api, conn, response) {:DOWN, ^ref, :process, _pid, reason} -> Plug.Conn.send_resp(conn, 500, inspect(reason)) diff --git a/lib/phoenix/sync/electric.ex b/lib/phoenix/sync/electric.ex index 63a2e46..40693c2 100644 --- a/lib/phoenix/sync/electric.ex +++ b/lib/phoenix/sync/electric.ex @@ -623,9 +623,25 @@ defmodule Phoenix.Sync.Electric do def map_response_body(msgs, _mapper) do msgs end + + if @electric_available? do + @doc false + # for the embedded api we need to make sure that the response stream is consumed + # in the same process that made the request, in order for cleanups to happen + # so we have to enumerate the body stream immediately before passing the response + # to any other process... + def consume_response_stream(%Electric.Shapes.Api.Response{} = response) do + Map.update!(response, :body, &do_consume_stream(&1)) + end + + defp do_consume_stream(body) do + Enum.to_list(body) + end + end end -if Code.ensure_loaded?(Electric.Shapes.Api) do +if Code.ensure_loaded?(Electric.Shapes.Api) && + Code.ensure_loaded?(Phoenix.Sync.Electric.ApiAdapter) do defimpl Phoenix.Sync.Adapter.PlugApi, for: Electric.Shapes.Api do alias Electric.Shapes @@ -672,6 +688,27 @@ if Code.ensure_loaded?(Electric.Shapes.Api) do Shapes.Api.options(conn) end + def response(api, _conn, params) do + case Shapes.Api.validate(api, params) do + {:ok, request} -> + { + request, + Shapes.Api.serve_shape_log(request) |> Phoenix.Sync.Electric.consume_response_stream() + } + + {:error, response} -> + {nil, response} + end + end + + def send_response(%ApiAdapter{}, conn, {request, response}) do + conn + |> content_type() + |> Plug.Conn.assign(:request, request) + |> Plug.Conn.assign(:response, response) + |> Shapes.Api.Response.send(response) + end + defp content_type(conn) do Plug.Conn.put_resp_content_type(conn, "application/json") end diff --git a/lib/phoenix/sync/electric/api_adapter.ex b/lib/phoenix/sync/electric/api_adapter.ex index 89492f4..84671fd 100644 --- a/lib/phoenix/sync/electric/api_adapter.ex +++ b/lib/phoenix/sync/electric/api_adapter.ex @@ -50,6 +50,34 @@ if Code.ensure_loaded?(Electric.Shapes.Api) do Phoenix.Sync.Adapter.PlugApi.call(api, conn, params) end + # only works if method is GET... + def response(%ApiAdapter{api: api, shape: shape}, %{method: "GET"} = conn, params) do + if transform_fun = PredefinedShape.transform_fun(shape) do + case Shapes.Api.validate(api, params) do + {:ok, request} -> + response = Shapes.Api.serve_shape_log(request) + response = Map.update!(response, :body, &apply_transform(&1, transform_fun)) + {request, response} + + {:error, response} -> + {nil, response} + end + else + Phoenix.Sync.Adapter.PlugApi.response(api, conn, params) + |> then(fn {request, response} -> + {request, Phoenix.Sync.Electric.consume_response_stream(response)} + end) + end + end + + def send_response(%ApiAdapter{}, conn, {request, response}) do + conn + |> content_type() + |> Plug.Conn.assign(:request, request) + |> Plug.Conn.assign(:response, response) + |> Shapes.Api.Response.send(response) + end + defp content_type(conn) do Plug.Conn.put_resp_content_type(conn, "application/json") end diff --git a/lib/phoenix/sync/electric/client_adapter.ex b/lib/phoenix/sync/electric/client_adapter.ex index 44d9d7a..14545e7 100644 --- a/lib/phoenix/sync/electric/client_adapter.ex +++ b/lib/phoenix/sync/electric/client_adapter.ex @@ -18,11 +18,29 @@ defmodule Phoenix.Sync.Electric.ClientAdapter do }} end + def call(sync_client, conn, params) do + {request, shape} = request(sync_client, conn, params) + + fetch_upstream(sync_client, conn, request, shape) + end + + def response(sync_client, %{method: "GET"} = conn, params) do + {request, shape} = request(sync_client, conn, params) + + make_request(sync_client, conn, request, shape) + end + + def send_response(_sync_client, conn, response) do + conn + |> put_resp_headers(response.headers) + |> Plug.Conn.send_resp(response.status, response.body) + end + # this is the server-defined shape route, so we want to only pass on the # per-request/stream position params leaving the shape-definition params # from the configured client. - def call(%{shape_definition: %PredefinedShape{} = shape} = sync_client, conn, params) do - request = + defp request(%{shape_definition: %PredefinedShape{} = shape} = sync_client, _conn, params) do + { Client.request( sync_client.client, method: :get, @@ -30,27 +48,33 @@ defmodule Phoenix.Sync.Electric.ClientAdapter do shape_handle: params["handle"], live: live?(params["live"]), next_cursor: params["cursor"] - ) - - fetch_upstream(sync_client, conn, request, shape) + ), + shape + } end # this version is the pure client-defined shape version - def call(sync_client, %{method: method} = conn, params) do - request = + defp request(sync_client, %{method: method} = _conn, params) do + { Client.request( sync_client.client, method: normalise_method(method), params: params - ) - - fetch_upstream(sync_client, conn, request, nil) + ), + nil + } end defp normalise_method(method), do: method |> String.downcase() |> String.to_atom() defp live?(live), do: live == "true" defp fetch_upstream(sync_client, conn, request, shape) do + response = make_request(sync_client, conn, request, shape) + + send_response(sync_client, conn, response) + end + + defp make_request(sync_client, conn, request, shape) do request = put_req_headers(request, conn.req_headers) response = @@ -69,9 +93,7 @@ defmodule Phoenix.Sync.Electric.ClientAdapter do response.body end - conn - |> put_resp_headers(response.headers) - |> Plug.Conn.send_resp(response.status, body) + %{response | body: body} end defp put_req_headers(request, headers) do diff --git a/lib/phoenix/sync/sandbox/api_adapter.ex b/lib/phoenix/sync/sandbox/api_adapter.ex index 00d62a2..7324734 100644 --- a/lib/phoenix/sync/sandbox/api_adapter.ex +++ b/lib/phoenix/sync/sandbox/api_adapter.ex @@ -26,6 +26,24 @@ if Phoenix.Sync.sandbox_enabled?() do end) end + def response(%{shape: nil} = _adapter, conn, params) do + shape_api = lookup_api!() + PlugApi.response(shape_api, conn, params) + end + + def response(%{shape: shape} = _adapter, conn, params) do + shape_api = lookup_api!() + + Phoenix.Sync.Electric.api_predefined_shape(conn, shape_api, shape, fn conn, shape_api -> + PlugApi.response(shape_api, conn, params) + end) + end + + def send_response(_adapter, conn, response) do + shape_api = lookup_api!() + PlugApi.send_response(shape_api, conn, response) + end + defp lookup_api!() do Phoenix.Sync.Sandbox.retrieve_api!() end From dbb6123a796d77bde456f59c45019fe0beac5509 Mon Sep 17 00:00:00 2001 From: Garry Hill Date: Mon, 13 Oct 2025 11:28:22 +0100 Subject: [PATCH 3/3] Limit to compatible electric versions --- mix.exs | 2 +- mix.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mix.exs b/mix.exs index 9cdcb54..bbfde1d 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule Phoenix.Sync.MixProject do # Remember to update the README when you change the version @version "0.6.0" - @electric_version "~> 1.1.9 and >= 1.1.9" + @electric_version ">= 1.1.9 and <= 1.1.10" def project do [ diff --git a/mix.lock b/mix.lock index 3fc9b55..3be4935 100644 --- a/mix.lock +++ b/mix.lock @@ -7,14 +7,14 @@ "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"}, - "db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"}, + "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, "dotenvy": {:hex, :dotenvy, "1.1.0", "316aee89c11a4ec8be3d74a69d17d17ea2e21e633e0cac9f155cf420e237ccb4", [:mix], [], "hexpm", "0519bda67fdfa1c22279c2654b2f292485f0caae7360fe29205f74f28a93df18"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.13.2", "7d0c0863f3fc8d71d17fc3ad3b9424beae13f02712ad84191a826c7169484f01", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "669d9291370513ff56e7b7e7081b7af3283d02e046cf3d403053c557894a0b3e"}, + "ecto": {:hex, :ecto, "3.13.3", "6a983f0917f8bdc7a89e96f2bf013f220503a0da5d8623224ba987515b3f0d80", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1927db768f53a88843ff25b6ba7946599a8ca8a055f69ad8058a1432a399af94"}, "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, - "electric": {:hex, :electric, "1.1.9", "b0a7774556bf306ffe1b4e4ceb270d0e9dfafdd38044b3ab9f98db8e5ead7bb4", [:mix], [{:backoff, "~> 1.1", [hex: :backoff, repo: "hexpm", optional: false]}, {:bandit, "~> 1.6", [hex: :bandit, repo: "hexpm", optional: false]}, {:dotenvy, "~> 1.1", [hex: :dotenvy, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:electric_cubdb, "~> 2.0", [hex: :electric_cubdb, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5", [hex: :opentelemetry, repo: "hexpm", optional: true]}, {:opentelemetry_exporter, "~> 1.8", [hex: :opentelemetry_exporter, repo: "hexpm", optional: true]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}, {:opentelemetry_telemetry, "~> 1.1", [hex: :opentelemetry_telemetry, repo: "hexpm", optional: false]}, {:otel_metric_exporter, "~> 0.3.11", [hex: :otel_metric_exporter, repo: "hexpm", optional: true]}, {:pg_query_ex, "0.9.0", [hex: :pg_query_ex, repo: "hexpm", optional: false]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.20", [hex: :postgrex, repo: "hexpm", optional: false]}, {:remote_ip, "~> 1.2", [hex: :remote_ip, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:retry, "~> 0.19", [hex: :retry, repo: "hexpm", optional: false]}, {:sentry, "~> 11.0", [hex: :sentry, repo: "hexpm", optional: true]}, {:stream_split, "~> 0.1", [hex: :stream_split, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.1", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: true]}, {:telemetry_metrics_statsd, "~> 0.7", [hex: :telemetry_metrics_statsd, repo: "hexpm", optional: true]}, {:telemetry_poller, "~> 1.2", [hex: :telemetry_poller, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.27", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}, {:tz, "~> 0.28", [hex: :tz, repo: "hexpm", optional: false]}], "hexpm", "cd8beea1d005424fd24aa863e85a707e49b770accd23e66066f6c86524643464"}, + "electric": {:hex, :electric, "1.1.10", "db6a4b714d711b754aff68b456bd4065c83494277cce2143dff9a9b39273c463", [:mix], [{:backoff, "~> 1.1", [hex: :backoff, repo: "hexpm", optional: false]}, {:bandit, "~> 1.6", [hex: :bandit, repo: "hexpm", optional: false]}, {:dotenvy, "~> 1.1", [hex: :dotenvy, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:electric_cubdb, "~> 2.0", [hex: :electric_cubdb, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.5", [hex: :opentelemetry, repo: "hexpm", optional: true]}, {:opentelemetry_exporter, "~> 1.8", [hex: :opentelemetry_exporter, repo: "hexpm", optional: true]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: false]}, {:opentelemetry_telemetry, "~> 1.1", [hex: :opentelemetry_telemetry, repo: "hexpm", optional: false]}, {:otel_metric_exporter, "~> 0.3.11", [hex: :otel_metric_exporter, repo: "hexpm", optional: true]}, {:pg_query_ex, "0.9.0", [hex: :pg_query_ex, repo: "hexpm", optional: false]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.20", [hex: :postgrex, repo: "hexpm", optional: false]}, {:remote_ip, "~> 1.2", [hex: :remote_ip, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:retry, "~> 0.19", [hex: :retry, repo: "hexpm", optional: false]}, {:sentry, "~> 11.0", [hex: :sentry, repo: "hexpm", optional: true]}, {:stream_split, "~> 0.1", [hex: :stream_split, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.1", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: true]}, {:telemetry_metrics_statsd, "~> 0.7", [hex: :telemetry_metrics_statsd, repo: "hexpm", optional: true]}, {:telemetry_poller, "~> 1.2", [hex: :telemetry_poller, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.27", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}, {:tz, "~> 0.28", [hex: :tz, repo: "hexpm", optional: false]}], "hexpm", "e62ba80b4104405490a72d09844fcf040d5da779340e212ac52804ffc1f7b46d"}, "electric_client": {:hex, :electric_client, "0.7.2", "06f221fa7379d41ab4fb771c9cf78f26654d7c265f61faffa8c31e6b73073224", [:mix], [{:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:electric, "~> 1.1.1", [hex: :electric, repo: "hexpm", optional: true]}, {:gen_stage, "~> 1.2", [hex: :gen_stage, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "4036796cc21767917f1c1c72541b0865a5585b9b4a59ccdb15b99af9c457c97e"}, "electric_cubdb": {:hex, :electric_cubdb, "2.0.2", "36f86e3c52dc26f4e077a49fbef813b1a38d3897421cece851f149190b34c16c", [:mix], [], "hexpm", "0c0e24b31fb76ad1b33c5de2ab35c41a4ff9da153f5c1f9b15e2de78575acaf2"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, @@ -29,7 +29,7 @@ "grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"}, "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "igniter": {:hex, :igniter, "0.6.28", "9db10192f19f10b924f14c805f5b2ad992617fccaff9cf9582b7f065d562d4d8", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ad9369d626aeca21079ef17661a2672fb32598610c5e5bccae2537efd36b27d4"}, + "igniter": {:hex, :igniter, "0.6.30", "83a466369ebb8fe009e0823c7bf04314dc545122c2d48f896172fc79df33e99d", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "76a14d5b7f850bb03b5243088c3649d54a2e52e34a2aa1104dee23cf50a8bae0"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "lazy_html": {:hex, :lazy_html, "0.1.3", "8b9c8c135e95f7bc483de6195c4e1c0b2c913a5e2c57353ef4e82703b7ac8bd1", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "5f96f29587dcfed8a22281e8c44c6607e958ba821d90b9dfc003d1ef610f7d07"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, @@ -52,8 +52,8 @@ "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "pg_query_ex": {:hex, :pg_query_ex, "0.9.0", "8e34bd2d0e0eb9e8d621c4697032fad4bfba46826950d3b46904a80ab589b43a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:protox, "~> 2.0", [hex: :protox, repo: "hexpm", optional: false]}], "hexpm", "a3fada1704fa9e2bc11ff846ad545ef9a1d34f46d86206063c37128960f4f5f5"}, "phoenix": {:hex, :phoenix, "1.8.1", "865473a60a979551a4879db79fbfb4503e41cd809e77c85af79716578b6a456d", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "84d77d2b2e77c3c7e7527099bd01ef5c8560cd149c036d6b3a40745f11cd2fb2"}, - "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.10", "491e05d5fdb51ba4ab5b23a5e3e9471d4d5ce9f299a741261db2f47f3f66e582", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c81ef8ef10f14c1e1c7611765a55ca54cd7bd22ab88e5a4677ad7109ebcc31e8"}, + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.14", "cae84abc4cd00dde4bb200b8516db556704c585c267aff9cd4955ff83cceb86c", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b827980e2bc00fddd8674e3b567519a4e855b5de04bf8607140414f1101e2627"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"}, @@ -64,7 +64,7 @@ "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"}, "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "retry": {:hex, :retry, "0.19.0", "aeb326d87f62295d950f41e1255fe6f43280a1b390d36e280b7c9b00601ccbc2", [:mix], [], "hexpm", "85ef376aa60007e7bff565c366310966ec1bd38078765a0e7f20ec8a220d02ca"}, - "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, + "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, "sentry": {:hex, :sentry, "10.8.1", "aa45309785e1521416225adb16e0b4d8b957578804527f3c7babb6fefbc5e456", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0 or ~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "495b3cdadad90ba72eef973aa3dec39b3b8b2a362fe87e2f4ef32133ac3b4097"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, @@ -76,7 +76,7 @@ "telemetry_metrics_statsd": {:hex, :telemetry_metrics_statsd, "0.7.1", "3502235bb5b35ce50d608bf0f34369ef76eb92a4dbc8708c7e8780ca0da2d53e", [:mix], [{:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "06338d9dc3b4a202f11a6e706fd3feba4c46100d0aca23688dea0b8f801c361f"}, "telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, - "thousand_island": {:hex, :thousand_island, "1.4.0", "7189aa9046cb4dc50e5d1d515cd4be6e1c34b6755b2becee624784ab8b07b4df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "686ff52b9e4cf1e16d2bd8ba83360f2357ac6ca304250702ecc3b40000f04015"}, + "thousand_island": {:hex, :thousand_island, "1.4.1", "8df065e627407e281f7935da5ad0f3842d10eb721afa92e760b720d71e2e37aa", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "204a8640e5d2818589b87286ae66160978628d7edf6095181cbe0440765fb6c1"}, "tls_certificate_check": {:hex, :tls_certificate_check, "1.29.0", "4473005eb0bbdad215d7083a230e2e076f538d9ea472c8009fd22006a4cfc5f6", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "5b0d0e5cb0f928bc4f210df667304ed91c5bff2a391ce6bdedfbfe70a8f096c5"}, "tz": {:hex, :tz, "0.28.1", "717f5ffddfd1e475e2a233e221dc0b4b76c35c4b3650b060c8e3ba29dd6632e9", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "bfdca1aa1902643c6c43b77c1fb0cb3d744fd2f09a8a98405468afdee0848c8a"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},