5.1 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 now beta, so treat the API as usable with minor churn possible while it stabilizes.
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.* - host-managed HTTP/WebSocket ingress is mounted through
Parrhesia.Plug
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:
listeners: %{}is the official embedding pattern when your host app owns the HTTPS edge.listeners: %{}disables Parrhesia-managed ingress (/relay,/management,/metrics, etc.).- Mount
Parrhesia.Plugfrom the host app when you still want Parrhesia ingress behind that same HTTPS edge. Parrhesia.Web.*modules are internal runtime wiring. TreatParrhesia.Plugas the stable mount API.- If you prefer Parrhesia-managed ingress instead, 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.
Mounting Parrhesia.Plug from a host app
When listeners: %{} is set, you can still expose Parrhesia ingress by mounting Parrhesia.Plug
in your host endpoint/router and passing an explicit listener config:
forward "/nostr", Parrhesia.Plug,
listener: %{
id: :public,
transport: %{scheme: :https, tls: %{mode: :proxy_terminated}},
proxy: %{trusted_cidrs: ["10.0.0.0/8"], honor_x_forwarded_for: true},
features: %{
nostr: %{enabled: true},
admin: %{enabled: true},
metrics: %{enabled: true, access: %{private_networks_only: true}}
}
}
Use the same listener schema documented in README.md.
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