Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.6.2] - 2025-11-05

### Changed

- Bump allowed `electric` to version `1.2.2` [#103](https://github.com/electric-sql/phoenix_sync/pull/116)

## [0.6.1] - 2025-10-13

### Fixed
Expand Down
75 changes: 0 additions & 75 deletions lib/phoenix/sync/electric.ex
Original file line number Diff line number Diff line change
Expand Up @@ -639,78 +639,3 @@ defmodule Phoenix.Sync.Electric do
end
end
end

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

alias Phoenix.Sync.PredefinedShape
alias Phoenix.Sync.Electric.ApiAdapter

def predefined_shape(api, %PredefinedShape{} = shape) do
ApiAdapter.new(api, shape)
end

def call(api, %{method: "GET"} = conn, params) do
case Shapes.Api.validate(api, params) do
{:ok, request} ->
conn
|> content_type()
|> Plug.Conn.assign(:request, request)
|> Shapes.Api.serve_shape_log(request)

{:error, response} ->
conn
|> content_type()
|> Shapes.Api.Response.send(response)
|> Plug.Conn.halt()
end
end

def call(api, %{method: "DELETE"} = conn, params) do
case Shapes.Api.validate_for_delete(api, params) do
{:ok, request} ->
conn
|> content_type()
|> Plug.Conn.assign(:request, request)
|> Shapes.Api.delete_shape(request)

{:error, response} ->
conn
|> content_type()
|> Shapes.Api.Response.send(response)
|> Plug.Conn.halt()
end
end

def call(_api, %{method: "OPTIONS"} = conn, _params) 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
end
end
72 changes: 72 additions & 0 deletions lib/phoenix/sync/electric/api_adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,76 @@ if Code.ensure_loaded?(Electric.Shapes.Api) do
end
end
end

defimpl Phoenix.Sync.Adapter.PlugApi, for: Electric.Shapes.Api do
alias Electric.Shapes

alias Phoenix.Sync.PredefinedShape
alias Phoenix.Sync.Electric.ApiAdapter

def predefined_shape(api, %PredefinedShape{} = shape) do
ApiAdapter.new(api, shape)
end

def call(api, %{method: "GET"} = conn, params) do
case Shapes.Api.validate(api, params) do
{:ok, request} ->
conn
|> content_type()
|> Plug.Conn.assign(:request, request)
|> Shapes.Api.serve_shape_log(request)

{:error, response} ->
conn
|> content_type()
|> Shapes.Api.Response.send(response)
|> Plug.Conn.halt()
end
end

def call(api, %{method: "DELETE"} = conn, params) do
case Shapes.Api.validate_for_delete(api, params) do
{:ok, request} ->
conn
|> content_type()
|> Plug.Conn.assign(:request, request)
|> Shapes.Api.delete_shape(request)

{:error, response} ->
conn
|> content_type()
|> Shapes.Api.Response.send(response)
|> Plug.Conn.halt()
end
end

def call(_api, %{method: "OPTIONS"} = conn, _params) 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(_api, 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
end
end
11 changes: 10 additions & 1 deletion lib/phoenix/sync/sandbox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ if Phoenix.Sync.sandbox_enabled?() do
# mark the stack as ready
Electric.StatusMonitor.mark_pg_lock_acquired(stack_id, owner)
Electric.StatusMonitor.mark_replication_client_ready(stack_id, owner)
Electric.StatusMonitor.mark_connection_pool_ready(stack_id, owner)
mark_connection_pool_ready(stack_id, owner)

api_config = Sandbox.Stack.config(stack_id, repo)
api = Electric.Application.api(api_config)
Expand Down Expand Up @@ -476,5 +476,14 @@ if Phoenix.Sync.sandbox_enabled?() do
end
end
end

# Handle both Electric API versions - older uses /2, newer uses /3
defp mark_connection_pool_ready(stack_id, owner) do
if function_exported?(Electric.StatusMonitor, :mark_connection_pool_ready, 3) do
apply(Electric.StatusMonitor, :mark_connection_pool_ready, [stack_id, owner, 1])
else
apply(Electric.StatusMonitor, :mark_connection_pool_ready, [stack_id, owner])
end
end
end
end
3 changes: 3 additions & 0 deletions lib/phoenix/sync/sandbox/inspector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ if Phoenix.Sync.sandbox_enabled?() do
@impl Electric.Postgres.Inspector
def list_relations_with_stale_cache(_), do: {:ok, []}

@impl Electric.Postgres.Inspector
def load_supported_features(_opts), do: {:ok, []}

def start_link(args) do
GenServer.start_link(__MODULE__, args, name: name(args[:stack_id]))
end
Expand Down
4 changes: 4 additions & 0 deletions lib/phoenix/sync/sandbox/publication_manager.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,9 @@ if Phoenix.Sync.sandbox_enabled?() do
def refresh_publication(_opts) do
:ok
end

def wait_for_restore(_opts) do
:ok
end
end
end
13 changes: 7 additions & 6 deletions lib/phoenix/sync/sandbox/stack.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,17 @@ if Phoenix.Sync.sandbox_enabled?() do
%{stack_id: stack_id, table_base_name: :"#{stack_id}"}
}

shape_status_config =
struct!(Electric.ShapeCache.ShapeStatus,
shape_meta_table: Electric.ShapeCache.ShapeStatus.shape_meta_table(stack_id),
storage: storage
)

[
purge_all_shapes?: false,
stack_id: stack_id,
storage: storage,
shape_status:
{Electric.ShapeCache.ShapeStatus,
Electric.ShapeCache.ShapeStatus.opts(
shape_meta_table: Electric.ShapeCache.ShapeStatus.shape_meta_table(stack_id),
storage: storage
)},
shape_status: {Electric.ShapeCache.ShapeStatus, shape_status_config},
inspector: inspector,
publication_manager: publication_manager_spec,
chunk_bytes_threshold: 10_485_760,
Expand Down
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ defmodule Phoenix.Sync.MixProject do
use Mix.Project

# Remember to update the README when you change the version
@version "0.6.1"
@electric_version ">= 1.1.9 and <= 1.1.10"
@version "0.6.2"
@electric_version ">= 1.1.9 and <= 1.2.4"

def project do
[
Expand Down