View Source Electric.Phoenix.Plug (Electric Phoenix v0.1.3)
Provides an configuration endpoint for use in your Phoenix applications.
Rather than configuring your Electric Typescript
client directly, you
instead configure a route in your application with a pre-configured
Electric.Client.ShapeDefinition
and then retreive the URL and other
configuration for that shape from your client via a request to your Phoenix
application.
In your Phoenix application, add a route to
Electric.Phoenix.Plug
specifying a particular shape:
defmodule MyAppWeb.Router do
scope "/shapes" do
pipe_through :browser
get "/todos", Electric.Phoenix.Plug,
shape: Electric.Client.shape!("todos", where: "visible = true")
end
end
Then in your client code, you retrieve the shape configuration directly from the Phoenix endpoint:
import { ShapeStream } from '@electric-sql/client'
const endpoint = `https://localhost:4000/shapes/todos`
const response = await fetch(endpoint)
const config = await response.json()
// The returned `config` has all the information you need to subscribe to
// your shape
const stream = new ShapeStream(config)
stream.subscribe(messages => {
// ...
})
You can add additional authentication/authorization for shapes using
Phoenix's
pipelines or
other plug
calls.
Plug.Router
For pure Plug
-based applications, you can use Plug.Router.forward/2
:
defmodule MyRouter do
use Plug.Router
plug :match
plug :dispatch
forward "/shapes/items",
to: Electric.Phoenix.Plug,
shape: Electric.Client.shape!("items")
match _ do
send_resp(conn, 404, "oops")
end
end
Parameter-based shapes
As well as defining fixed-shapes for a particular url, you can request shape configuration using parameters in your request:
defmodule MyAppWeb.Router do
scope "/" do
pipe_through :browser
get "/shape", Electric.Phoenix.Plug, []
end
end
import { ShapeStream } from '@electric-sql/client'
const endpoint = `https://localhost:4000/shape?table=items&namespace=public&where=visible%20%3D%20true`
const response = await fetch(endpoint)
const config = await response.json()
// as before
The parameters are:
table
- The Postgres table name (required).namespace
- The Postgres schema if not specified defaults topublic
.where
- The where clause to filter items from the shape.
Summary
Functions
Callback implementation for Plug.call/2
.
Send the client configuration for a given shape to the browser.
Defines a shape based on a root Ecto
query plus some filters based on the
current request.
Types
@type conn_param_spec() :: param_name() | [{op(), param_name()}]
@type dynamic_shape_param() :: {table_column(), conn_param_spec()} | table_column()
@type dynamic_shape_params() :: [dynamic_shape_param()]
@type op() :: :== | :!= | :> | :< | :>= | :<=
@type param_name() :: atom()
@type table_column() :: atom()
Functions
Callback implementation for Plug.call/2
.
send_configuration(conn, shape_or_queryable, client \\ Electric.Phoenix.client!())
View Source@spec send_configuration( Plug.Conn.t(), Electric.Phoenix.shape_definition(), Client.t() ) :: Plug.Conn.t()
Send the client configuration for a given shape to the browser.
Example
get "/my-shapes/messages" do
user_id = get_session(conn, :user_id)
shape = from(m in Message, where: m.user_id == ^user_id)
Electric.Phoenix.Plug.send_configuration(conn, shape)
end
get "/my-shapes/tasks/:project_id" do
project_id = conn.params["project_id"]
if user_has_access_to_project?(project_id) do
shape = where(Task, project_id: ^project_id)
Electric.Phoenix.Plug.send_configuration(conn, shape)
else
send_resp(conn, :forbidden, "You do not have permission to view project #{project_id}")
end
end
@spec shape!(Electric.Phoenix.shape_definition(), dynamic_shape_params()) :: term()
Defines a shape based on a root Ecto
query plus some filters based on the
current request.
forward "/shapes/tasks/:project_id",
to: Electric.Plug,
shape: Electric.Phoenix.Plug.shape!(
from(t in Task, where: t.active == true),
project_id: :project_id
)
The params
describe the way to build the where
clause on the shape from
the request parameters.
For example, [id: :user_id]
means that the id
column on the table should
match the value of the user_id
parameter, equivalent to:
from(
t in Table,
where: t.id == ^conn.params["user_id"]
)
If both the table column and the request parameter have the same name, then you can just pass the name, so:
Electric.Phoenix.Plug.shape!(Table, [:visible])
is equivalent to:
from(
t in Table,
where: t.visible == ^conn.params["visible"]
)
If you need to match on something other than ==
then you can pass the operator in the params:
Electric.Phoenix.Plug.shape!(Table, size: [>=: :size])
is equivalent to:
from(
t in Table,
where: t.size >= ^conn.params["size"]
)
Instead of calling shape!/2
directly in your route definition, you can just
pass a list of [query | params]
to do the same thing:
forward "/shapes/tasks/:project_id",
to: Electric.Plug,
shape: [
from(t in Task, where: t.active == true),
project_id: :project_id
]