README.md

# Synq

[![CI](https://github.com/guess/synq/actions/workflows/ci.yml/badge.svg)](https://github.com/guess/synq/actions/workflows/ci.yml)
[![License](https://img.shields.io/hexpm/l/synq.svg)](https://github.com/guess/synq/blob/main/LICENSE.md)
[![Version](https://img.shields.io/hexpm/v/synq.svg)](https://hex.pm/packages/synq)
[![Hex Docs](https://img.shields.io/badge/documentation-gray.svg)](https://hexdocs.pm/synq)

Synq is an Elixir library for building servers that power interactive web and mobile applications. It offers an alternative approach to state management and client-server communication, particularly suited for applications that don't rely on server-side HTML rendering.

## Key Concepts

- **Centralized State Management**: Application state is maintained on the server, reducing complexity in state synchronization.
- **Event-Driven Architecture**: Clients dispatch events to the server, which handles them and updates the state accordingly.
- **Real-Time Updates**: The server pushes state changes to clients, facilitating real-time interactivity.
- **Simplified Client Logic**: Client-side code primarily focuses on rendering state and dispatching events.
- **Platform Agnostic**: Suitable for web applications and mobile apps that manage their own UI rendering.

## How It Works

1. Clients send events to the server
2. The server processes events and updates the application state
3. Updated state is sent back to clients for rendering

This approach aims to reduce the complexity often found in managing state across both client and server in traditional web and mobile applications.

## Use Cases

Synq is particularly well-suited for:

- Mobile applications that handle their own UI rendering
- Web applications requiring real-time updates
- Scenarios where a unified backend can serve multiple client types (web, mobile, etc.)

## Comparison to LiveView

While Synq shares similar goals with Phoenix LiveView, it takes a different approach:

- LiveView manages both server logic and view presentation in Elixir, primarily for web applications
- Synq handles server logic in Elixir but relies on client-side code for rendering, making it adaptable for both web and mobile platforms

This distinction allows Synq to be used in scenarios where full server-side rendering is not possible or desirable, such as in native mobile applications.

## Installation

This package can be installed
by adding `synq` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:synq, "~> 0.1"},
    {:cors_plug, "~> 3.0"}
  ]
end
```

While `cors_plug` is not strictly required, you will very likely want it to be able to add to your endpoint so that
clients cannot connect to your channel.

## Usage

First you need to set up a socket as you would with other normal [Phoenix Channels](https://hexdocs.pm/phoenix/channels.html)

1. On your Endpoint module, set up a socket for your channel:

```elixir
defmodule DemoWeb.Endpoint do
  socket "/synq", DemoWeb.Socket
...
```

2. Then create the socket module with the topic to listen to:

```elixir
defmodule DemoWeb.Socket do
  use Phoenix.Socket

  channel "topic", DemoWeb.Channel
  @impl true
  def connect(_params, socket), do: {:ok, socket}

  @impl true
  def id(_), do: "random_id"
end
```

3. Create your channel using the `Synq.Channel` behaviour:

```elixir
defmodule DemoWeb.Channel do
  use Synq.Channel, web_module: DemoWeb
...
```

4. Define your channel bindings and initial state:

```elixir
defmodule DemoWeb.Channel do
  use Synq.Channel,
    web_module: DemoWeb,
    track: [:inventory, :energy] # State that can manifest in the mortal realm

  @impl true
  def init(_topic, _payload, socket) do
    {:ok, assign(socket, inventory: [], energy: 100)}
  end
end
```

State must be a map. It will be sent down as JSON, so anything in it
must have a `Jason.Encoder` implementation.

State assigned to the socket will only manifest in clients if included in the `track` option.

## Events

Handle events from clients using `handle_event/3`:

```elixir
  @impl true
  def handle_event("gather_energy", %{"amount" => amount}, socket) do
    socket = update(socket, :energy, &(&1 + amount))
    {:noreply, socket}
  end

  @impl true
  def handle_event("summon_item", %{"item" => item}, socket) do
    case ItemRegistry.summon(item) do
      {:ok, item} ->
        socket = update(socket, :inventory, &[item | &1])
        {:reply, {:ok, %{item: item}}, socket}

      {:error, reason} ->
        {:error, reason}
    end
  end
```

`c:Synq.Channel.handle_event/3` receives the following arguments

- event name
- payload
- current

And returns a tuple whose last element is the new state. It can also return
one or many events to dispatch on the calling DOM Element:

```elixir
  def handle_event("add_todo_with_one_reply", todo, %{todos: todos}) do
    {:reply, %Event{name: "reply_event", detail: %{foo: "bar"}}, %{todos: [todo | todos]}}
  end

  def handle_event("add_todo_with_two_replies", todo, %{todos: todos}) do
    {:reply,
     [
       %Event{name: "reply_event1", detail: %{foo: "bar"}},
       %Event{name: "reply_event2", detail: %{bing: "baz"}}
     ], %{todos: [todo | todos]}}
  end
```

## Documentation

- [API Docs](https://hexdocs.pm/synq/) are available in the [hex package](https://hex.pm/packages/synq).

## Inspriation

This library was adapted from [live_state](https://github.com/launchscout/live_state)