Skip to content

Commit 46160bf

Browse files
authored
Merge pull request #2 from electric-sql/thruflo/flesh-out-readme
docs: flesh out the main README.
2 parents aa5de16 + aaa5ca4 commit 46160bf

File tree

2 files changed

+301
-17
lines changed

2 files changed

+301
-17
lines changed

README.md

Lines changed: 301 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,318 @@
1-
<p align="center">
2-
<a href="https://electric-sql.com" target="_blank">
1+
<p>
2+
<br />
3+
<a href="https://hexdocs.pm/phoenix_sync" target="_blank">
34
<picture>
4-
<source media="(prefers-color-scheme: dark)"
5-
srcset="https://raw.githubusercontent.com/electric-sql/meta/main/identity/ElectricSQL-logo-next.svg"
6-
/>
7-
<source media="(prefers-color-scheme: light)"
8-
srcset="https://raw.githubusercontent.com/electric-sql/meta/main/identity/ElectricSQL-logo-black.svg"
9-
/>
10-
<img alt="ElectricSQL logo"
11-
src="https://raw.githubusercontent.com/electric-sql/meta/main/identity/ElectricSQL-logo-black.svg"
5+
<img alt="Phoenix sync illustration"
6+
src="./docs/phoenix-sync.png"
127
/>
138
</picture>
149
</a>
10+
<br />
1511
</p>
1612

1713
# Phoenix.Sync
1814

19-
An adapter to integrate [Electric SQL's sync engine](https://electric-sql.com)
20-
into [Phoenix web applications](https://www.phoenixframework.org/).
15+
[![Hex.pm](https://img.shields.io/hexpm/v/phoenix_sync.svg)](https://hex.pm/packages/phoenix_sync)
16+
[![Documentation](https://img.shields.io/badge/docs-hexdocs-green)](https://hexdocs.pm/phoenix_sync)
17+
[![License](https://img.shields.io/badge/license-Apache_2.0-green)](./LICENSE)
18+
[![Status](https://img.shields.io/badge/status-beta-orange)](https://github.com/electric-sql/phoenix_sync)
19+
[![Discord](https://img.shields.io/discord/933657521581858818?color=5969EA&label=discord)](https://discord.electric-sql.com)
20+
21+
Sync is the best way of building modern apps. Phoenix.Sync enables real-time sync for Postgres-backed [Phoenix](https://www.phoenixframework.org/) applications.
22+
23+
Documentation is available at [hexdocs.pm/phoenix_sync](https://hexdocs.pm/phoenix_sync).
24+
25+
## Build real-time apps on locally synced data
26+
27+
- sync data into Elixir, `LiveView` and frontend web and mobile applications
28+
- integrates with `Plug` and `Phoenix.{Controller, LiveView, Router, Stream}`
29+
- uses [ElectricSQL](https://electric-sql.com) for scalable data delivery and fan out
30+
- maps `Ecto` queries to [Shapes](https://electric-sql.com/docs/guides/shapes) for partial replication
31+
32+
## Usage
33+
34+
There are four key APIs:
35+
36+
- [`Phoenix.Sync.Client.stream/2`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Client.html#stream/2) for low level usage in Elixir
37+
- [`Phoenix.Sync.LiveView.sync_stream/4`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.LiveView.html#sync_stream/4) to sync into a LiveView stream
38+
- [`Phoenix.Sync.Router.sync/2`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Router.html#sync/2) macro to expose a statically defined shape in your Router
39+
- [`Phoenix.Sync.Controller.sync_render/3`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Controller.html#sync_render/3) to expose dynamically constructed shapes from a Controller
40+
41+
### Low level usage in Elixir
42+
43+
Use [`Phoenix.Sync.Client.stream/2`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Client.html#stream/2) to convert an `Ecto.Query` into an Elixir `Stream`:
44+
45+
```elixir
46+
stream = Phoenix.Sync.Client.stream(Todos.Todo)
47+
48+
stream =
49+
Ecto.Query.from(t in Todos.Todo, where: t.completed == false)
50+
|> Phoenix.Sync.Client.stream()
51+
```
52+
53+
### Sync into a LiveView stream
54+
55+
Swap out `Phoenix.LiveView.stream/3` for [`Phoenix.Sync.LiveView.sync_stream/4`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.LiveView.html#sync_stream/4) to automatically keep a LiveView up-to-date with the state of your Postgres database:
56+
57+
```elixir
58+
defmodule MyWeb.MyLive do
59+
use Phoenix.LiveView
60+
import Phoenix.Sync.LiveView
61+
62+
def mount(_params, _session, socket) do
63+
{:ok, sync_stream(socket, :todos, Todos.Todo)}
64+
end
65+
66+
def handle_info({:sync, event}, socket) do
67+
{:noreply, sync_stream_update(socket, event)}
68+
end
69+
end
70+
```
71+
72+
LiveView takes care of automatically keeping the front-end up-to-date with the assigned stream. What Phoenix.Sync does is automatically keep the *stream* up-to-date with the state of the database.
73+
74+
This means you can build fully end-to-end real-time multi-user applications without writing Javascript *and* without worrying about message delivery, reconnections, cache invalidation or polling the database for changes.
75+
76+
### Sync shapes through your Router
77+
78+
Use the [`Phoenix.Sync.Router.sync/2`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Router.html#sync/2) macro to expose statically (compile-time) defined shapes in your Router:
79+
80+
```elixir
81+
defmodule MyWeb.Router do
82+
use Phoenix.Router
83+
import Phoenix.Sync.Router
84+
85+
pipeline :sync do
86+
plug :my_auth
87+
end
88+
89+
scope "/shapes" do
90+
pipe_through :sync
91+
92+
sync "/todos", Todos.Todo
93+
end
94+
end
95+
```
96+
97+
Because the shapes are exposed through your Router, the client connects through your existing Plug middleware. This allows you to do real-time sync straight out of Postgres *without* having to translate your auth logic into complex/fragile database rules.
98+
99+
### Sync dynamic shapes from a Controller
100+
101+
Sync shapes from any standard Controller using the [`Phoenix.Sync.Controller.sync_render/3`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Controller.html#sync_render/3) view function:
102+
103+
```elixir
104+
defmodule Phoenix.Sync.LiveViewTest.TodoController do
105+
use Phoenix.Controller
106+
import Phoenix.Sync.Controller
107+
import Ecto.Query, only: [from: 2]
108+
109+
def show(conn, %{"done" => done} = params) do
110+
sync_render(conn, params, from(t in Todos.Todo, where: t.done == ^done))
111+
end
112+
113+
def show_mine(%{assigns: %{current_user: user_id}} = conn, params) do
114+
sync_render(conn, params, from(t in Todos.Todo, where: t.owner_id == ^user_id))
115+
end
116+
end
117+
```
118+
119+
This allows you to define and personalise the shape definition at runtime using the session and request.
120+
121+
### Consume shapes in the frontend
122+
123+
You can sync *into* any client in any language that [speaks HTTP and JSON](https://electric-sql.com/docs/api/http).
124+
125+
For example, using the Electric [Typescript client](https://electric-sql.com/docs/api/clients/typescript):
126+
127+
```typescript
128+
import { Shape, ShapeStream } from '@electric-sql/client'
129+
130+
const stream = new ShapeStream({
131+
url: `/shapes/todos`
132+
})
133+
const shape = new Shape(stream)
134+
135+
// The callback runs every time the data changes.
136+
shape.subscribe(data => console.log(data))
137+
```
138+
139+
Or binding a shape to a component using the [React bindings](https://electric-sql.com/docs/integrations/react):
140+
141+
```tsx
142+
import { useShape } from '@electric-sql/react'
21143

22-
Documentation available at <https://hexdocs.pm/phoenix_sync/>.
144+
const MyComponent = () => {
145+
const { data } = useShape({
146+
url: `shapes/todos`
147+
})
23148

24-
## Installation
149+
return (
150+
<List todos={data} />
151+
)
152+
}
153+
```
154+
155+
See the Electric [demos](https://electric-sql.com/demos) and [documentation](https://electric-sql.com/demos) for more client-side usage examples.
156+
157+
## Installation and configuration
158+
159+
`Phoenix.Sync` can be used in two modes:
160+
161+
1. `:embedded` where Electric is included as an application dependency and Phoenix.Sync consumes data internally using Elixir APIs
162+
2. `:http` where Electric does *not* need to be included as an application dependency and Phoenix.Sync consumes data from an external Electric service using it's [HTTP API](https://electric-sql.com/docs/api/http)
163+
164+
### Embedded mode
165+
166+
In `:embedded` mode, Electric must be included an application dependency but does not expose an HTTP API (internally or externally). Messages are streamed internally between Electric and Phoenix.Sync using Elixir function APIs. The only HTTP API for sync is that exposed via your Phoenix Router using the `sync/2` macro and `sync_render/3` function.
25167

26-
Install by adding `phoenix_sync` to your list of dependencies in `mix.exs`:
168+
Example config:
27169

28170
```elixir
29-
def deps do
171+
# mix.exs
172+
defp deps do
30173
[
31-
{:phoenix_sync, "~> 0.1.0"}
174+
{:electric, ">= 1.0.0-beta.20"},
175+
{:phoenix_sync, "~> 0.3"}
32176
]
33177
end
178+
179+
# config/config.exs
180+
config :phoenix_sync,
181+
mode: :embedded,
182+
repo: MyApp.Repo
183+
184+
# application.ex
185+
children = [
186+
MyApp.Repo,
187+
# ...
188+
{MyApp.Endpoint, phoenix_sync: Phoenix.Sync.plug_opts()}
189+
]
34190
```
191+
192+
### HTTP
193+
194+
In `:http` mode, Electric does not need to be included as an application dependency. Instead, Phoenix.Sync consumes data from an external Electric service over HTTP.
195+
196+
```elixir
197+
# mix.exs
198+
defp deps do
199+
[
200+
{:phoenix_sync, "~> 0.3"}
201+
]
202+
end
203+
204+
# config/config.exs
205+
config :phoenix_sync,
206+
mode: :http,
207+
url: "https://api.electric-sql.cloud",
208+
credentials: [
209+
secret: "...", # required
210+
source_id: "..." # optional, required for Electric Cloud
211+
]
212+
213+
# application.ex
214+
children = [
215+
MyApp.Repo,
216+
# ...
217+
{MyApp.Endpoint, phoenix_sync: Phoenix.Sync.plug_opts()}
218+
]
219+
```
220+
221+
### Local HTTP services
222+
223+
It is also possible to include Electric as an application dependency and configure it to expose a local HTTP API that's consumed by Phoenix.Sync running in `:http` mode:
224+
225+
```elixir
226+
# mix.exs
227+
defp deps do
228+
[
229+
{:electric, ">= 1.0.0-beta.20"},
230+
{:phoenix_sync, "~> 0.3"}
231+
]
232+
end
233+
234+
# config/config.exs
235+
config :phoenix_sync,
236+
mode: :http,
237+
http: [
238+
# https://hexdocs.pm/bandit/Bandit.html#t:options/0
239+
ip: :loopback,
240+
port: 3000,
241+
],
242+
repo: MyApp.Repo,
243+
url: "http://localhost:3000"
244+
245+
# application.ex
246+
children = [
247+
MyApp.Repo,
248+
# ...
249+
{MyApp.Endpoint, phoenix_sync: Phoenix.Sync.plug_opts()}
250+
]
251+
```
252+
253+
This is less efficient than running in `:embedded` mode but may be useful for testing or when needing to run an HTTP proxy in front of Electric as part of your development stack.
254+
255+
### Different modes for different envs
256+
257+
Apps using `:http` mode in certain environments can exclude `:electric` as a dependency for that environment. The following example shows how to configure:
258+
259+
- `:embedded` mode in `:dev`
260+
- `:http` mode with a local Electric service in `:test`
261+
- `:http` mode with an external Electric service in `:prod`
262+
263+
With Electric only included and compiled as a dependency in `:dev` and `:test`.
264+
265+
```elixir
266+
# mix.exs
267+
defp deps do
268+
[
269+
{:electric, "~> 1.0.0-beta.20", only: [:dev, :test]},
270+
{:phoenix_sync, "~> 0.3"}
271+
]
272+
end
273+
274+
# config/dev.exs
275+
config :phoenix_sync,
276+
mode: :embedded,
277+
repo: MyApp.Repo
278+
279+
# config/test.esx
280+
config :phoenix_sync,
281+
mode: :http,
282+
http: [
283+
ip: :loopback,
284+
port: 3000,
285+
],
286+
repo: MyApp.Repo,
287+
url: "http://localhost:3000"
288+
289+
# config/prod.exs
290+
config :phoenix_sync,
291+
mode: :http,
292+
url: "https://api.electric-sql.cloud",
293+
credentials: [
294+
secret: "...", # required
295+
source_id: "..." # optional, required for Electric Cloud
296+
]
297+
298+
# application.ex
299+
children = [
300+
MyApp.Repo,
301+
# ...
302+
{MyApp.Endpoint, phoenix_sync: Phoenix.Sync.plug_opts()}
303+
]
304+
```
305+
306+
## Notes
307+
308+
### ElectricSQL
309+
310+
ElectricSQL is a [real-time sync engine for Postgres](https://electric-sql.com).
311+
312+
Phoenix.Sync uses Electric to handle the core concerns of partial replication, fan out and data delivery.
313+
314+
### Partial replication
315+
316+
Electric defines partial replication using [Shapes](https://electric-sql.com/docs/guides/shapes).
317+
318+
Phoenix.Sync maps Ecto queries to shape definitions. This allows you to control what data syncs where using Ecto.Schema and Ecto.Query.

docs/phoenix-sync.png

31.4 KB
Loading

0 commit comments

Comments
 (0)