LiveView Sync Engine


License
Apache-2.0

Documentation

LiveSync

coverbot Hex.pm Documentation

LiveSync allows automatic updating of LiveView assigns by utilizing postgres replication.

Installation

This project builds on top of PostgreSQL replication and it requires PostgreSQL 14+. You must also enable replication in your PostgreSQL instance:

ALTER SYSTEM SET wal_level='logical';
ALTER SYSTEM SET max_wal_senders='64';
ALTER SYSTEM SET max_replication_slots='64';

Then you must restart your database.

Add live_sync to the list of dependencies in mix.exs:

def deps do
  [
    {:live_sync, "~> 0.1.0"}
  ]
end

Add a migration to setup replication (requires superuser permissions to subscribe to all tables):

defmodule MyApp.Repo.Migrations.SetupLiveSync do
  use Ecto.Migration

  def up do
    LiveSync.Migration.up()
    # If you don't have superuser you can pass specific tables
    # LiveSync.Migration.up(["table1", "table2"])
  end

  def down do
    LiveSync.Migration.down()
  end
end

Add LiveSync to your supervision tree:

  • repo (required): The Ecto repo to use for replication.

  • otp_app (required): The OTP app to use to lookup schemas deriving the watch protocol.

    defmodule MyApp.Application do
      @moduledoc false
    
      use Application
    
      @impl true
      def start(_type, _args) do
        children = [
          ...
          {LiveSync, [repo: MyApp.Repo, otp_app: :my_app]}
        ]
    
        opts = [strategy: :one_for_one, name: MyApp.Supervisor]
        Supervisor.start_link(children, opts)
      end
    end

Usage

For any Ecto schemas you want to watch, add the LiveSync.Watch derive:

  • id (optional): The primary key on the schema, defaults to :id.

  • subscription_key (required): The field on the schema that is used to filter messages before sending to the client.

  • table (optional): The table to watch, defaults to the schema's table name, if using a view you need to specify the table name

    defmodule MyApp.MyObject do
      use Ecto.Schema
    
      @derive {LiveSync.Watch,
                [
                  subscription_key: :organization_id,
                  table: "objects"
                ]}
    
      schema "visible_objects" do
        field :name, :string
        field :organization_id, :integer
      end
    
      ...
    end

Add the LiveSync macro to any LiveView module you want to automatically sync data:

  • subscription_key (required): This is the key that MUST exist in the assigns of the LiveView and is used for the subscription. This value must match what is in the schema's @derive attribute.

  • watch (required): A list of keys in assigns that should be watched. You may optionally specify a tuple with options. Schema is required for lists of objects to support inserting.

    use LiveSync,
      subscription_key: :organization_id,
      watch: [
        :single_object,
        list_of_objects: [schema: MyApp.MyObject]
      ]

Handling Sync Events

An optional callback can also be added to the LiveView module to handle the updated data. This callback will be called for each assign key that is watched and changed. It must return the socket with updated assigns.

def sync(:list_of_objects, updated, socket) do
  updates =
    updated
    |> Enum.filter(&is_nil(&1.executed_at))
    |> Enum.sort_by(& &1.name)
    |> Repo.preload([...])

  assign(socket, list_of_objects: updates)
end