|
1 | 1 | # Phoenix.Sync |
2 | 2 |
|
| 3 | +Real-time sync for Postgres-backed [Phoenix](https://www.phoenixframework.org/) applications. |
| 4 | + |
3 | 5 | <p> |
4 | | - <br /> |
5 | 6 | <a href="https://hexdocs.pm/phoenix_sync" target="_blank"> |
6 | 7 | <picture> |
7 | 8 | <img alt="Phoenix sync illustration" |
8 | 9 | src="https://github.com/electric-sql/phoenix_sync/raw/main/docs/phoenix-sync.png" |
9 | 10 | /> |
10 | 11 | </picture> |
11 | 12 | </a> |
12 | | - <br /> |
13 | 13 | </p> |
14 | 14 |
|
15 | 15 | [](https://hex.pm/packages/phoenix_sync) |
|
18 | 18 | [](https://github.com/electric-sql/phoenix_sync) |
19 | 19 | [](https://discord.electric-sql.com) |
20 | 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 | 21 | Documentation is available at [hexdocs.pm/phoenix_sync](https://hexdocs.pm/phoenix_sync). |
24 | 22 |
|
25 | | -## Build real-time apps on locally synced data |
| 23 | +## Build real-time apps on sync |
26 | 24 |
|
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 |
| 25 | +Phoenix.Sync is a library that adds real-time sync to Postgres-backed [Phoenix](https://www.phoenixframework.org/) applications. Use it to sync data into both LiveView and front-end web and mobile applications. |
31 | 26 |
|
32 | | -## Usage |
| 27 | +- integrates with `Plug` and `Phoenix.{Controller, LiveView, Router, Stream}` |
| 28 | +- uses [ElectricSQL](https://electric-sql.com) for core sync, fan-out and data delivery |
| 29 | +- maps `Ecto.Query`s to [Shapes](https://electric-sql.com/docs/guides/shapes) for partial replication |
33 | 30 |
|
34 | | -There are four key APIs: |
| 31 | +There are four key APIs for [read-path sync](#read-path-sync) out of Postgres: |
35 | 32 |
|
36 | 33 | - [`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 |
| 34 | +- [`Phoenix.Sync.LiveView.sync_stream/4`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.LiveView.html#sync_stream/4) to sync into a LiveView |
| 35 | +- [`Phoenix.Sync.Router.sync/2`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Router.html#sync/2) macro to expose a shape in your Router |
| 36 | +- [`Phoenix.Sync.Controller.sync_render/3`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Controller.html#sync_render/3) to return shapes from a Controller |
| 37 | + |
| 38 | +And a [`Phoenix.Sync.Writer`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Writer.html) module for handling [write-path sync](#write-path-sync) back into Postgres. |
| 39 | + |
| 40 | +## Read-path sync |
40 | 41 |
|
41 | 42 | ### Low level usage in Elixir |
42 | 43 |
|
@@ -152,6 +153,52 @@ const MyComponent = () => { |
152 | 153 |
|
153 | 154 | See the Electric [demos](https://electric-sql.com/demos) and [documentation](https://electric-sql.com/demos) for more client-side usage examples. |
154 | 155 |
|
| 156 | +## Write-path sync |
| 157 | + |
| 158 | +The [`Phoenix.Sync.Writer`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Writer.html) module allows you to ingest batches of writes from the client. |
| 159 | + |
| 160 | +The idea is that the front-end can batch up [local optimistic writes](https://electric-sql.com/docs/guides/writes). For example using a library like [@TanStack/optimistic](https://github.com/TanStack/optimistic) or by [monitoring changes to a local embedded database](https://electric-sql.com/docs/guides/writes#through-the-db). |
| 161 | + |
| 162 | +These changes can be POSTed to a `Phoenix.Controller`, which then constructs a `Phoenix.Sync.Writer` instance. The writer instance authorizes and validates the writes before applying them to the database. Under the hood this uses `Ecto.Multi`, to ensure that transactions (batches of writes) are applied atomically. |
| 163 | + |
| 164 | +For example, the controller below handles local writes made to a project management app. It constructs a writer instance and pipes it through a series of [`Writer.allow/3`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Writer.html#allow/3) calls. These register functions against `Ecto.Schema`s (in this case `Projects.Project` and `Projects.Issue`): |
| 165 | + |
| 166 | +```elixir |
| 167 | +defmodule MutationController do |
| 168 | + use Phoenix.Controller, formats: [:json] |
| 169 | + |
| 170 | + alias Phoenix.Sync.Writer |
| 171 | + alias Phoenix.Sync.Writer.Format |
| 172 | + |
| 173 | + def mutate(conn, %{"transaction" => transaction} = _params) do |
| 174 | + user_id = conn.assigns.user_id |
| 175 | + |
| 176 | + {:ok, txid, _changes} = |
| 177 | + Phoenix.Sync.Writer.new(format: Format.TanstackOptimistic) |
| 178 | + |> Phoenix.Sync.Writer.allow( |
| 179 | + Projects.Project, |
| 180 | + check: reject_invalid_params/2, |
| 181 | + load: &Projects.load_for_user(&1, user_id), |
| 182 | + validate: &Projects.Project.changeset/2 |
| 183 | + ) |
| 184 | + |> Phoenix.Sync.Writer.allow( |
| 185 | + Projects.Issue, |
| 186 | + # Use the sensible defaults: |
| 187 | + # load: Ecto.Repo.get_by(Projects.Issue, id: ^issue_id) |
| 188 | + # validate: Projects.Issue.changeset/2 |
| 189 | + # etc. |
| 190 | + ) |
| 191 | + |> Phoenix.Sync.Writer.apply(transaction, Repo) |
| 192 | + |
| 193 | + render(conn, :mutations, txid: txid) |
| 194 | + end |
| 195 | +end |
| 196 | +``` |
| 197 | + |
| 198 | +This facilitates incrementally adding bi-directional sync support to a Phoenix application, re-using your existing auth and schema/validation logic. |
| 199 | + |
| 200 | +See the [`Phoenix.Sync.Writer`](https://hexdocs.pm/phoenix_sync/Phoenix.Sync.Writer.html) module docs for more information. |
| 201 | + |
155 | 202 | ## Installation and configuration |
156 | 203 |
|
157 | 204 | `Phoenix.Sync` can be used in two modes: |
|
0 commit comments