PhoenixKit.Modules.Sync (phoenix_kit v1.7.63)

Copy Markdown View Source

Main context for Sync module.

Provides peer-to-peer data sync between PhoenixKit instances. Supports sync between dev↔prod, dev↔dev, or even different websites.

Programmatic API

This module provides a complete API for programmatic data sync, suitable for use from code, scripts, or AI agents.

System Control

Session Management (for LiveView UI)

Local Database Inspection

Data Import

Remote Operations (via Client)

For connecting to a remote sender and fetching data, use PhoenixKit.Modules.Sync.Client:

{:ok, client} = PhoenixKit.Modules.Sync.Client.connect("https://example.com", "ABC12345")
{:ok, tables} = PhoenixKit.Modules.Sync.Client.list_tables(client)
{:ok, records} = PhoenixKit.Modules.Sync.Client.fetch_records(client, "users")
PhoenixKit.Modules.Sync.Client.disconnect(client)

Usage Examples

# List local tables
{:ok, tables} = PhoenixKit.Modules.Sync.list_tables()
# => [{name: "users", estimated_count: 150}, ...]

# Get table schema
{:ok, schema} = PhoenixKit.Modules.Sync.get_schema("users")
# => %{table: "users", columns: [...], primary_key: ["id"]}

# Export records with pagination
{:ok, records} = PhoenixKit.Modules.Sync.export_records("users", limit: 100, offset: 0)

# Import records with conflict strategy
{:ok, result} = PhoenixKit.Modules.Sync.import_records("users", records, :skip)
# => %{created: 5, updated: 0, skipped: 3, errors: []}

# Full sync workflow
{:ok, client} = PhoenixKit.Modules.Sync.Client.connect(url, code)
{:ok, tables} = PhoenixKit.Modules.Sync.Client.list_tables(client)
{:ok, result} = PhoenixKit.Modules.Sync.Client.transfer(client, "users", strategy: :skip)

Summary

Functions

Creates a new transfer session tied to the calling process.

Creates a table from a schema definition.

Deletes a session by code.

Disables the Sync module.

Enables the Sync module.

Checks if the Sync module is enabled.

Exports records from a table with pagination.

Gets the Sync module configuration with statistics.

Gets the exact row count for a table.

Gets the current incoming connection mode.

Gets the incoming connection password (if set). Returns nil if not set.

Gets the schema (columns, types, constraints) for a specific table.

Gets a session by its connection code.

Imports records into a table with conflict resolution.

Checks if an incoming connection password is set.

Lists all transferable tables in the local database with row counts.

Sets the incoming connection mode.

Sets the incoming connection password. Pass nil or empty string to clear the password.

Checks if a table exists in the local database.

Updates session status and metadata.

Validates a connection code and marks it as used if valid.

Validates an incoming connection password.

Functions

create_session(direction, owner_pid \\ self())

@spec create_session(:send | :receive, pid()) :: {:ok, map()} | {:error, any()}

Creates a new transfer session tied to the calling process.

The session remains valid as long as the owner process (typically a LiveView) is alive. When the process terminates, the session is automatically deleted.

Parameters

  • direction - Either :send or :receive
  • owner_pid - The PID of the owning process (defaults to self())

Returns

  • {:ok, session} - Session with code, direction, status, owner_pid
  • {:error, reason} - If creation failed

Examples

{:ok, session} = PhoenixKit.Modules.Sync.create_session(:receive)
# => %{
#   code: "A7X9K2M4",
#   direction: :receive,
#   status: :pending,
#   owner_pid: #PID<0.123.0>,
#   created_at: ~U[2025-12-16 12:15:00Z]
# }

create_table(table_name, schema_def, opts \\ [])

@spec create_table(String.t(), map(), keyword()) :: :ok | {:error, any()}

Creates a table from a schema definition.

Used when receiving data for a table that doesn't exist locally. The schema definition should match the format returned by get_schema/1.

Examples

schema = %{
  "columns" => [
    %{"name" => "id", "type" => "bigint", "nullable" => false, "primary_key" => true},
    %{"name" => "email", "type" => "character varying", "nullable" => false}
  ],
  "primary_key" => ["id"]
}
:ok = PhoenixKit.Modules.Sync.create_table("users", schema)

delete_session(code)

@spec delete_session(String.t()) :: :ok

Deletes a session by code.

disable_system()

@spec disable_system() :: {:ok, any()} | {:error, any()}

Disables the Sync module.

enable_system()

@spec enable_system() :: {:ok, any()} | {:error, any()}

Enables the Sync module.

enabled?()

@spec enabled?() :: boolean()

Checks if the Sync module is enabled.

export_records(table_name, opts \\ [])

@spec export_records(
  String.t(),
  keyword()
) :: {:ok, [map()]} | {:error, any()}

Exports records from a table with pagination.

Options

  • :offset - Number of records to skip (default: 0)
  • :limit - Maximum records to return (default: 100)

Examples

{:ok, records} = PhoenixKit.Modules.Sync.export_records("users", limit: 50, offset: 0)
# => [%{"id" => 1, "email" => "user@example.com", ...}, ...]

get_config()

@spec get_config() :: map()

Gets the Sync module configuration with statistics.

Returns a map with:

  • enabled - Whether the module is enabled
  • active_sessions - Number of active sessions (sessions tied to LiveView processes)
  • incoming_mode - How incoming connections are handled
  • incoming_password_set - Whether a password is set for incoming connections

get_count(table_name, opts \\ [])

@spec get_count(
  String.t(),
  keyword()
) :: {:ok, non_neg_integer()} | {:error, any()}

Gets the exact row count for a table.

Examples

{:ok, count} = PhoenixKit.Modules.Sync.get_count("users")
# => 150

get_incoming_mode()

@spec get_incoming_mode() :: String.t()

Gets the current incoming connection mode.

Modes:

  • "auto_accept" - Automatically accept and activate incoming connections
  • "require_approval" - Accept but set as pending, requires manual approval
  • "require_password" - Require password before accepting
  • "deny_all" - Reject all incoming connection requests

Default is "require_approval".

get_incoming_password()

@spec get_incoming_password() :: String.t() | nil

Gets the incoming connection password (if set). Returns nil if not set.

get_schema(table_name, opts \\ [])

@spec get_schema(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, any()}

Gets the schema (columns, types, constraints) for a specific table.

Examples

{:ok, schema} = PhoenixKit.Modules.Sync.get_schema("users")
# => %{
#   table: "users",
#   columns: [
#     %{name: "id", type: "bigint", nullable: false, primary_key: true},
#     %{name: "email", type: "character varying", nullable: false},
#     ...
#   ],
#   primary_key: ["id"]
# }

get_session(code)

@spec get_session(String.t()) :: {:ok, map()} | {:error, :not_found}

Gets a session by its connection code.

Sessions remain valid as long as the owning LiveView process is alive. When the page is closed, the session is automatically deleted.

Returns {:ok, session} if found, {:error, :not_found} otherwise.

import_records(table_name, records, strategy \\ :skip)

@spec import_records(String.t(), [map()], atom()) ::
  {:ok, PhoenixKit.Modules.Sync.DataImporter.import_result()} | {:error, any()}

Imports records into a table with conflict resolution.

Conflict Strategies

  • :skip - Skip if record with same primary key exists (default)
  • :overwrite - Replace existing record with imported data
  • :merge - Merge imported data with existing (keeps existing where new is nil)
  • :append - Always insert as new record with auto-generated ID

Examples

records = [%{"email" => "user@example.com", "name" => "John"}, ...]
{:ok, result} = PhoenixKit.Modules.Sync.import_records("users", records, :skip)
# => %{created: 5, updated: 0, skipped: 3, errors: []}

# Append mode (ignores primary keys, creates new records)
{:ok, result} = PhoenixKit.Modules.Sync.import_records("users", records, :append)

incoming_password_set?()

@spec incoming_password_set?() :: boolean()

Checks if an incoming connection password is set.

list_tables(opts \\ [])

@spec list_tables(keyword()) :: {:ok, [map()]} | {:error, any()}

Lists all transferable tables in the local database with row counts.

Returns tables from the public schema, excluding system tables and security-sensitive tables (like session tokens).

Options

  • :include_phoenix_kit - Include phoenixkit* tables (default: true)
  • :exact_counts - Use exact COUNT(*) instead of estimates (default: true)

Examples

{:ok, tables} = PhoenixKit.Modules.Sync.list_tables()
# => [%{name: "users", estimated_count: 150}, %{name: "posts", estimated_count: 1200}]

set_incoming_mode(mode)

@spec set_incoming_mode(String.t()) :: {:ok, any()} | {:error, any()}

Sets the incoming connection mode.

set_incoming_password(password)

@spec set_incoming_password(String.t() | nil) :: {:ok, any()} | {:error, any()}

Sets the incoming connection password. Pass nil or empty string to clear the password.

table_exists?(table_name, opts \\ [])

@spec table_exists?(
  String.t(),
  keyword()
) :: boolean()

Checks if a table exists in the local database.

Examples

PhoenixKit.Modules.Sync.table_exists?("users")
# => true

update_session(code, updates)

@spec update_session(String.t(), map()) :: {:ok, map()} | {:error, :not_found}

Updates session status and metadata.

validate_code(code)

@spec validate_code(String.t()) :: {:ok, map()} | {:error, atom()}

Validates a connection code and marks it as used if valid.

This is called when a sender connects to a receiver's session. Sessions remain valid as long as the owner's LiveView process is alive.

Returns

  • {:ok, session} - Code is valid, session is now marked as connected
  • {:error, :invalid_code} - Code doesn't exist (or owner closed the page)
  • {:error, :already_used} - Code was already used for a connection

validate_incoming_password(provided_password)

@spec validate_incoming_password(String.t() | nil) :: boolean()

Validates an incoming connection password.