PhoenixKitSync.SessionStore (PhoenixKitSync v0.2.5)

Copy Markdown View Source

ETS-based session storage for DB Sync module.

Stores sync sessions in ETS for fast, ephemeral access. Sessions remain valid as long as the owning LiveView process is alive. When the LiveView terminates (page closed), the session is automatically deleted.

Architecture

This module uses a GenServer to manage an ETS table. The ETS table provides fast reads while the GenServer handles process monitoring and automatic cleanup when LiveView processes terminate.

Future Migration Path

This module is designed to be easily replaced with database persistence if audit logging or sync history is needed. The public API would remain the same, only the storage backend would change.

Session Structure

%{
  code: "A7X9K2M4",
  direction: :send | :receive,
  status: :pending | :connected | :completed | :failed,
  owner_pid: #PID<0.123.0>,     # Session is deleted when this process dies
  created_at: ~U[2025-12-16 12:15:00Z],
  connected_at: nil | ~U[...],
  sender_info: nil | %{...},
  receiver_info: nil | %{...}
}

Single use

A code is claimed once (claim/2). That it was claimed is kept apart from the session map, in a table of its own that only this process writes, so no later status (:completed, :failed, or :pending written back by update/2 or merge/2) makes the code claimable again. The claim lives as long as the session does.

Summary

Functions

Returns a specification to start this module under a supervisor.

Marks a session as connected, once.

Counts active sessions.

Creates a new session in the store and monitors the owner process.

Deletes a session by code.

Gets a session by its code.

Lists all active sessions. Useful for debugging and admin interfaces.

Merges updates into an existing session, in the store's own process so the merge cannot write back a copy read before a concurrent claim/2.

Starts the SessionStore GenServer.

Updates an existing session.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

claim(code, connected_at)

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

Marks a session as connected, once.

The check and the write happen in the store's own process, so two callers racing on one code cannot both be told it is free. A code is claimed once for the life of its session, whatever its status becomes afterwards.

Returns

  • {:ok, session} - The session, now :connected at connected_at
  • {:error, :already_used} - It was claimed before
  • {:error, :not_found} - No such session

count_active()

@spec count_active() :: non_neg_integer()

Counts active sessions.

All sessions in the store are active (sessions are deleted when owner process terminates).

create(session)

@spec create(map()) :: :ok | {:error, :already_exists}

Creates a new session in the store and monitors the owner process.

Parameters

  • session - Map with at least code and owner_pid fields

Returns

  • :ok on success
  • {:error, :already_exists} if code already exists

delete(code)

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

Deletes a session by code.

get(code)

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

Gets a session by its code.

Returns

  • {:ok, session} if found
  • {:error, :not_found} if not found

list_active()

@spec list_active() :: [map()]

Lists all active sessions. Useful for debugging and admin interfaces.

All sessions in the store are active (sessions are deleted when owner process terminates).

merge(code, updates)

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

Merges updates into an existing session, in the store's own process so the merge cannot write back a copy read before a concurrent claim/2.

Returns

  • {:ok, session} - The merged session
  • {:error, :not_found} - No such session

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the SessionStore GenServer.

update(code, session)

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

Updates an existing session.

Returns

  • :ok on success
  • {:error, :not_found} if session doesn't exist