4.0 KiB
Parrhesia Local API
Parrhesia can run as a normal standalone relay application, but it also exposes a stable in-process API for Elixir callers that want to embed the relay inside a larger OTP system.
This document describes that embedding surface. The runtime is still alpha, so treat the API as usable but not yet frozen.
What embedding means today
Embedding currently means:
- the host app adds
:parrhesiaas a dependency and OTP application - the host app provides
config :parrhesia, ...explicitly - the host app migrates the Parrhesia database schema
- callers interact with the relay through
Parrhesia.API.*
Current operational assumptions:
- Parrhesia runs one runtime per BEAM node
- core processes use global module names such as
Parrhesia.ConfigandParrhesia.Web.Endpoint - the config defaults in this repo's
config/*.exsare not imported automatically by a host app
If you want multiple isolated relay instances inside one VM, Parrhesia does not support that cleanly yet.
Minimal host setup
Add the dependency in your host app:
defp deps do
[
{:parrhesia, path: "../parrhesia"}
]
end
Configure the runtime in your host app. At minimum you should carry over:
import Config
config :postgrex, :json_library, JSON
config :parrhesia,
relay_url: "wss://relay.example.com/relay",
listeners: %{},
storage: [backend: :postgres]
config :parrhesia, Parrhesia.Repo,
url: System.fetch_env!("DATABASE_URL"),
pool_size: 10,
types: Parrhesia.PostgresTypes
config :parrhesia, Parrhesia.ReadRepo,
url: System.fetch_env!("DATABASE_URL"),
pool_size: 10,
types: Parrhesia.PostgresTypes
config :parrhesia, ecto_repos: [Parrhesia.Repo]
Notes:
- Set
listeners: %{}if you only want the in-process API and no HTTP/WebSocket ingress. - If you do want ingress, copy the listener shape from the config reference in README.md.
- Production runtime overrides still use the
PARRHESIA_*environment variables described in README.md.
Migrate before serving traffic:
Parrhesia.Release.migrate()
In development, mix ecto.migrate -r Parrhesia.Repo works too.
Starting the runtime
In the common case, letting OTP start the :parrhesia application is enough.
If you need to start the runtime explicitly under your own supervision tree, use
Parrhesia.Runtime:
children = [
{Parrhesia.Runtime, name: Parrhesia.Supervisor}
]
Primary modules
The in-process surface is centered on these modules:
Parrhesia.API.Eventsfor publish, query, and countParrhesia.API.Streamfor REQ-like local subscriptionsParrhesia.API.Authfor event validation and NIP-98 auth parsingParrhesia.API.Adminfor management operationsParrhesia.API.Identityfor relay-owned key managementParrhesia.API.ACLfor protected sync ACLsParrhesia.API.Syncfor outbound relay sync management
Generated ExDoc groups these modules under Embedded API.
Request context
Most calls take a Parrhesia.API.RequestContext. This carries authenticated pubkeys and
caller metadata through policy checks.
%Parrhesia.API.RequestContext{
caller: :local,
authenticated_pubkeys: MapSet.new()
}
If your host app has already authenticated a user or peer, put that pubkey into
authenticated_pubkeys before calling the API.
Example
alias Parrhesia.API.Events
alias Parrhesia.API.RequestContext
alias Parrhesia.API.Stream
context = %RequestContext{caller: :local}
{:ok, publish_result} = Events.publish(event, context: context)
{:ok, events} = Events.query([%{"kinds" => [1]}], context: context)
{:ok, ref} = Stream.subscribe(self(), "local-sub", [%{"kinds" => [1]}], context: context)
receive do
{:parrhesia, :event, ^ref, "local-sub", event} -> event
{:parrhesia, :eose, ^ref, "local-sub"} -> :ok
end
:ok = Stream.unsubscribe(ref)
Where to look next
- README.md for setup and the full config reference
- docs/SYNC.md for relay-to-relay sync semantics
- module docs under
Parrhesia.API.*for per-function behavior