View Source Electric.Client (electric_client v0.1.0-dev-4)
An Elixir client for the ElectricSQL synchronisation server.
Subscribing to a particular Shape produce a stream of update messages that will allow you to synchronise your local system to the state the Postgres database.
Quickstart
Start and connect the Electric Sync Service
Follow the quickstart guide to get Electric running and connected to a Postgres database.
Install the Electric Client and Receive sync events
Create a simple script that will subscribe to events from the foo
table you created as part of the Quickstart.
# electric.ex
Mix.install([
:electric_client
])
{:ok, client} = Electric.Client.new(base_url: "http://localhost:3000")
# You can create a stream from a table name or a Shape defined using
# `ShapeDefinition.new/2`
stream = Electric.Client.stream(client, "foo")
for msg <- stream do
IO.inspect(msg, pretty: true, syntax_colors: IO.ANSI.syntax_colors())
end
Then run it:
elixir electric.ex
In a separate terminal window, connect to the Postgres database:
psql "postgresql://postgres:password@localhost:54321/electric"
Now any modifications you make to the data in the foo
table will appear
as messages in the elixir process.
INSERT INTO foo (name, value) VALUES
('josevalim', 4545),
('eksperimental', 966),
('lexmag', 677),
('whatyouhide', 598),
('ericmj', 583),
('alco', 377);
UPDATE foo SET value = value + 1;
Filtering Using WHERE clauses
You can subscribe to subsets of the data in your table using where
clauses.
{:ok, client} = Electric.Client.new(base_url: "http://localhost:3000")
shape = Electric.Client.ShapeDefinition.new("foo", where: "name ILIKE 'a%'")
stream = Electric.Client.stream(client, shape)
for msg <- stream do
# you will now only receive events for database rows matching the `where` clause
end
Configuration
See new/1
for configuration options of the client itself,
and stream/3
for details on configuring the stream itself.
Ecto Integration
If you have Ecto installed then you can define you Shapes using Ecto queries:
# ecto.ex
Mix.install([
:ecto_sql,
:electric_client
])
import Ecto.Query, only: [from: 2]
import Ecto.Query.API, only: [ilike: 2]
defmodule Foo do
use Ecto.Schema
schema "foo" do
field :name, :string
field :value, :float
end
end
{:ok, client} = Electric.Client.new(base_url: "http://localhost:3000")
# Replace the table or `ShapeDefinition` with an `Ecto` query and set
# `update_mode` to `:full` to receive full rows for update messages.
#
# The normal `update_mode: :modified` setting will only send the changed
# columns, so we'd end up with partial `%Foo{}` instances.
stream =
Electric.Client.stream(
client,
from(f in Foo, where: ilike(f.name, "a%")),
update_mode: :full
)
for %{headers: %{operation: operation}, value: value} <- stream do
# The message `value` will now be a `%Foo{}` struct
IO.inspect([{operation, value}], pretty: true, syntax_colors: IO.ANSI.syntax_colors())
end
Custom Values
Electric sends all column values as binaries. The Ecto
integration uses
Ecto
's schema information to turn those into the relevant Elixir terms but
we can provide our own binary() => term()
mapping by implementing the
Electric.Client.ValueMapper
behaviour.
Summary
Functions
Use the given client
to delete the shape
instance from the server.
Create a new client.
Create a new client with the given options or raise if the configuration is invalid.
A shortcut to ShapeDefinition.new/2
.
Use the client
to return a stream of update messages for the given shape
.
Types
@type client_options() :: [client_option()]
@type column() :: %{ :type => String.t(), optional(:pk_index) => non_neg_integer(), optional(:not_null) => boolean(), optional(:max_length) => non_neg_integer(), optional(:length) => non_neg_integer() }
@type cursor() :: integer()
@type message() :: Electric.Client.Message.ControlMessage.t() | Electric.Client.Message.ChangeMessage.t() | Electric.Client.Message.ResumeMessage.t()
@type param() :: :offset | :update_mode | :shape_id | :live | :where | :cursor
@type shape() :: table_name() | Electric.Client.ShapeDefinition.t() | Ecto.Queryable.t()
@type shape_id() :: String.t()
@type shape_option() :: {:parser, {module(), [term()]}} | {:live, boolean()} | {:update_mode, :modified | :full} | {:oneshot, boolean()} | {:resume, Electric.Client.Message.ResumeMessage.t() | nil}
@type shape_options() :: [shape_option()]
@type table_name() :: String.t()
@type update_mode() :: :modified | :full
Functions
Use the given client
to delete the shape
instance from the server.
Delete shape only works if Electric is configured to allow_shape_deletion
.
@spec new(client_options()) :: {:ok, t()} | {:error, term()}
Create a new client.
Options
:base_url
(String.t/0
) - Required. The URL of the electric server, e.g. for local development this would behttp://localhost:3000
.
@spec new!(client_options()) :: t() | no_return()
Create a new client with the given options or raise if the configuration is invalid.
@spec shape(String.t(), Electric.Client.ShapeDefinition.options()) :: Electric.Client.ShapeDefinition.t()
A shortcut to ShapeDefinition.new/2
.
@spec stream(t(), shape(), shape_options()) :: Enumerable.t(message())
Use the client
to return a stream of update messages for the given shape
.
shape
can be a table name, e.g. "my_table"
, a full ShapeDefinition
including a table name and where
clause, or (if Ecto
is installed) an
Ecto.Queryable
instance, such as an Ecto.Query
or a Ecto.Schema
struct.
Options
:parser
- A{module, args}
tuple specifying theElectric.Client.ValueMapper
implementation to use for mapping values from the sync stream into Elixir terms. The default value is{Electric.Client.ValueMapper, []}
.:live
(boolean/0
) - Iftrue
(the default) reads an infinite stream of update messages from the server. The default value istrue
.:update_mode
- Instructs the server to send just the changed columns for an update (:modified
) or the full row (:full
). The default value is:modified
.:oneshot
(boolean/0
) - Only make a single request and then terminate the stream. The default value isfalse
.:resume
- Resume the stream from the given point.Message.ResumeMessage
messages are appended to the change stream if you terminate it early usinglive: false
oroneshot: true