# `EctoFoundationDB.Sync`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L1)

This module defines some conventions for integrating with Phoenix LiveView or any other stateful
process. Via EctoFoundationDB watches, your application can automatically be kept up-to-date with
changes to the database.

Simply call one of the provided `sync*` functions in `mount/3` or `handle_params/3`, and this module will do the following:

1. Read from the database and create necessary watches
1. Call `Phoenix.Component.assign/2`, using the provided `label`
1. Call `Phoenix.LiveView.attach_hook/4` to set up a callback as a hook

Then, upon receiving a watch-ready message, the hook calls `Phoenix.Component.assign/2`
again with the updated data, and creates new watches as needed.

Socket requirements:

* For multi-tenant Repos, the tenant must be stored in the `:private` field of the provided `socket`.
* The sync functions will store a key called `:ecto_fdb_sync_data` in the `:private` field.

## Examples

These are quick, short examples. Please see [Sync Engine III](sync_module.html) for end-to-end detail.

### Quick example 1: Syncing a single record

Suppose you have a LiveView that displays a single user. You can use `sync_one/5` to
automatically update the user whenever it is created, updated, or deleted.

```elixir
defmodule MyApp.UserLive do
  use Phoenix.LiveView

  alias EctoFoundationDB.Sync

  alias MyApp.Repo
  alias MyApp.User

  def mount(_params, _session, socket) do
    user_id = "1"
    {:ok,
      socket
      |> put_private(:tenant, open_tenant(socket))
      |> Sync.sync_one(Repo, User, :user, user_id)}
  end
end
```

### Quick example 2: Syncing a list of records

Suppose you have a LiveView that displays a list of users. You can use `sync_all/4` to
automatically update the list whenever a user is created, updated, or deleted.

You must have already defined a `SchemaMetadata` index for the `User` schema for `sync_all/4`
to work.

```elixir
defmodule MyApp.UserLive do
  use Phoenix.LiveView

  alias EctoFoundationDB.Sync

  alias MyApp.Repo
  alias MyApp.User

  def mount(_params, _session, socket) do
    {:ok,
      socket
      |> put_private(:tenant, open_tenant(socket))
      |> Sync.sync_all(Repo, User)}
  end
end
```

### Quick example 3: Syncing a group of records individually

Suppose your page displays several records simultaneously, but you wish to subscribe to change individually.
You can use `sync_many/6`. This integrates nicely with LiveComponents.

```elixir
defmodule MyApp.UserLive do
  use Phoenix.LiveView

  alias EctoFoundationDB.Sync

  alias MyApp.Repo
  alias MyApp.User

  def mount(_params, _session, socket) do
    user_ids = ["1", "2", "3"]
    {:ok,
      socket
      |> put_private(:tenant, open_tenant(socket))
      |> Sync.sync_many(Repo, User, :users, user_ids)}
  end
end
```

## Labels

The `label` argument is used to identify the data being synced. The `label` is
used as the key in the `assigns` map. It can be any term. Usually, you'll use an
atom for compatibility with Phoenix.

For example, you can provide the label `:user` and your assigns map will look like this:

```elixir
iex> assigns
%{user: %User{
  id: 1,
  name: "Alice",
  email: "alice@example.com"
}}
```

## Limitations

The `sync*` functions themselves define FDB transactions. Therefore, if your desired query
is more complex, or if you need to sync multiple collections with internal consistency,
you'll need to write your own logic to create and listen for watches. In these more
sophisticated cases, we encourage you to avoid the Sync module entirely, and instead
handle the `{reference(), :ready}` messages yourself.

# `assign_impl`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L676)

# `assign_map`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L693)

# `attach_callback`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L418)

Attaches a callback to the `:handle_assigns` event.

The callback will be called when the `Sync` module changes your assigns.

## Arguments

- `state`: A map with key `:assigns` and `:private`. `private` must be a map with key `:tenant`
- `repo`: An Ecto repository
- `name`: The name of the callback. Defaults to `:default`.
- `event`: The event to attach the callback to. Only `:handle_assigns` is supported.
- `cb`: The callback function. Arity must be 2, with the first argument being the `state` and
  the second being a map with the old assigns.
- `opts`: Options

## Options

- `:replace`: A boolean indicating whether to replace an existing callback with the same name and event. Defaults to `false`.

# `cancel`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L493)

Cancels syncing for the provided label and, if none are left, detaches the hook.

Refer to `cancel_all/3` for a discussion on when and why to cancel.

## Arguments

- `state`: A map with key `:assigns` and `:private`. `private` must be a map with key `:tenant`
- `repo`: An Ecto repository
- `label`: A label to cancel syncing for
- `opts`: Options

## Options

- `assign`: A boolean indicating whether or not to assign the label to `nil` or `[]`. Defaults to `true`.
- `detach_container_hook`: A function that takes `state, name, repo, opts` and modifies state as needed to detach a container hook.
  When not provided: if `Phoenix.LiveView` is available, we use `Phoenix.LiveView.detach_hook/3`, otherwise we do nothing.

## Return

Returns an updated `state`, with `:private` updated with the following values:

### `private`

- We cancel and clear the futures in `:ecto_fdb_sync_data`.

# `cancel_all`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L450)

Cancels all syncing and detaches the hook.

Canceling syncing is optional. EctoFoundationDB will automatically clean up watches when your process exits.

## Arguments

- `state`: A map with key `:assigns` and `:private`. `private` must be a map with key `:tenant`
- `repo`: An Ecto repository
- `opts`: Options

## Options

- `detach_container_hook`: A function that takes `state, name, repo, opts` and modifies state as needed to detach a container hook.
  When not provided: if `Phoenix.LiveView` is available, we use `Phoenix.LiveView.detach_hook/3`, otherwise we do nothing.

## Return

Returns an updated `state`, with `:private` updated with the following values:

### `private`

- We cancel and clear the futures in `:ecto_fdb_sync_data`.

# `detach_callback`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L423)

Detaches a callback from the `:handle_assigns` event.

# `handle_ready`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L548)

This hook can be attached to a compatible Elixir process to automatically
process handle_info `:ready` messages from EctoFDB.

This hook is designed to be used with LiveView's `attach_hook`. If you're using
one of the `sync_*` function in this module along with LiveView, the hook is
attached automatically. You do not need to call this function.

## Arguments

- `repo`: An Ecto repository.
- `info`: A message received on the process mailbox. We will inspect messages of the form
   `{ref, :ready} when is_reference(ref)`, and ignore all others (returning `{:cont, state}`).
   Or a list of such messages.
- `state`: A map with key `:assigns` and `:private`. `private` must be a map with keys `:tenant` and `:ecto_fdb_sync_data`.
- `opts`: Options

## Options

- `assign`: A function that takes the current socket and new assigns and returns a tuple of new assigns and state.
  By default, we simply update the assigns map with the new labels. The default is not sufficient for LiveView's assign

## Result behavior

Either `{:cont, state}` or `{:halt, state}` is returned.

- `:cont`: Returned when the message was not processed by the Repo.
- `:halt`: Returned when the ready message is relevant to the provided
  `futures`. The `assigns` and `private` are updated accordingly based on the label
  provided to the matching future. The watches are re-initialized so that
  the expected syncing behavior will continue.

# `hook_impl`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L718)

# `sync`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L231)

Initializes a Sync of one or more queryables.

This is to be paired with `handle_ready/3` to provide automatic updating of `assigns` in `state`. If you're using
the default LiveView attach_hook as described in the Options, then `handle_ready/3` will be set up for you
automatically.

## Arguments

- `state`: A map with key `:assigns` and `:private`. For multi-tenant Repos, `private` must be a map with key `:tenant`
- `repo`: An Ecto repository
- `queryable_assigns`: A list of `All`, `One`, or `Many` structs
- `opts`: Options

## Options

- `assign`: A function that takes the current socket and new assigns and returns the updated state.
  When not provided: if `Phoenix.Component` is available, we use `Phoenix.Component.assign/3`, otherwise we use `Map.put/3`.
- `attach_container_hook`: A function that takes `state, name, repo, opts` and modifies state as needed to attach a hook.
  When not provided: if `Phoenix.LiveView` is available, we use `Phoenix.LiveView.attach_hook/4`, otherwise we do nothing.

## Return

Returns an updated `state`, with `:assigns` and `:private` updated with the following values:

### `assigns`

- Provided labels from `queryable_assigns` are used to register the results from the database.

### `private`

- We add or append to the `:ecto_fdb_sync_data` as needed for internal purposes.

# `sync_all`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L176)

Sets up syncing for a list of records in the database.

A watch is created for changes to the list with SchemaMetadata.

See `sync/4` for more.

## Options

- `watch_action`: An atom representing the signal from the `SchemaMetadata` you're interested in syncing. Defaults to `:changes`

# `sync_all_by`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L191)

Sets up syncing for a list of records in the database, constrained by an indexed field.

A watch is created for changes to the list with SchemaMetadata.

See `sync/4` for more.

## Options

- `watch_action`: An atom representing the signal from the `SchemaMetadata` you're interested in syncing. Defaults to `:changes`

# `sync_many`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L155)

Sets up syncing for a list of records in the database, with individual watches.

See `sync/4` for more.

# `sync_one`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L146)

Sets up syncing for a single record in the database.

See `sync/4` for more.

# `watching?`
[🔗](https://github.com/foundationdb-beam/ecto_foundationdb/blob/main/lib/ecto_foundationdb/sync.ex#L392)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
