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 pre-beta, 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.* - 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