Document embedded API surface
This commit is contained in:
34
README.md
34
README.md
@@ -45,7 +45,7 @@ Current `supported_nips` list:
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Elixir `~> 1.19`
|
- Elixir `~> 1.18`
|
||||||
- Erlang/OTP 28
|
- Erlang/OTP 28
|
||||||
- PostgreSQL (18 used in the dev environment; 16+ recommended)
|
- PostgreSQL (18 used in the dev environment; 16+ recommended)
|
||||||
- Docker or Podman plus Docker Compose support if you want to run the published container image
|
- Docker or Podman plus Docker Compose support if you want to run the published container image
|
||||||
@@ -114,6 +114,38 @@ GitHub CI currently runs the non-Docker node-sync e2e on the main Linux matrix j
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Embedding in another Elixir app
|
||||||
|
|
||||||
|
Parrhesia is usable as an embedded OTP dependency, not just as a standalone relay process.
|
||||||
|
The intended in-process surface is `Parrhesia.API.*`, especially:
|
||||||
|
|
||||||
|
- `Parrhesia.API.Events` for publish, query, and count
|
||||||
|
- `Parrhesia.API.Stream` for local REQ-like subscriptions
|
||||||
|
- `Parrhesia.API.Admin` for management operations
|
||||||
|
- `Parrhesia.API.Identity`, `Parrhesia.API.ACL`, and `Parrhesia.API.Sync` for relay identity, protected sync ACLs, and outbound relay sync
|
||||||
|
|
||||||
|
Start with:
|
||||||
|
|
||||||
|
- [`docs/LOCAL_API.md`](./docs/LOCAL_API.md) for the embedding model and a minimal host setup
|
||||||
|
- generated ExDoc for the `Embedded API` module group when running `mix docs`
|
||||||
|
|
||||||
|
Important caveats for host applications:
|
||||||
|
|
||||||
|
- Parrhesia is still alpha; expect some public API and config churn.
|
||||||
|
- Parrhesia currently assumes a single runtime per BEAM node and uses globally registered process names.
|
||||||
|
- The defaults in this repo's `config/*.exs` are not imported automatically when Parrhesia is used as a dependency. A host app must set `config :parrhesia, ...` explicitly.
|
||||||
|
- The host app is responsible for migrating Parrhesia's schema, for example with `Parrhesia.Release.migrate()` or `mix ecto.migrate -r Parrhesia.Repo`.
|
||||||
|
|
||||||
|
If you only want the in-process API and not the HTTP/WebSocket edge, configure:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :parrhesia, :listeners, %{}
|
||||||
|
```
|
||||||
|
|
||||||
|
The config reference below still applies when embedded. That is the primary place to document basic setup and runtime configuration changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Production configuration
|
## Production configuration
|
||||||
|
|
||||||
### Minimal setup
|
### Minimal setup
|
||||||
|
|||||||
20
docs/ARCH.md
20
docs/ARCH.md
@@ -82,16 +82,20 @@ Configured WS/HTTP Listeners (Bandit/Plug)
|
|||||||
|
|
||||||
## 4) OTP supervision design
|
## 4) OTP supervision design
|
||||||
|
|
||||||
`Parrhesia.Application` children (top-level):
|
`Parrhesia.Runtime` children (top-level):
|
||||||
|
|
||||||
1. `Parrhesia.Telemetry` – metric definitions/reporters
|
1. `Parrhesia.Telemetry` – metric definitions/reporters
|
||||||
2. `Parrhesia.Config` – runtime config cache (ETS-backed)
|
2. `Parrhesia.ConnectionStats` – per-listener connection/subscription counters
|
||||||
3. `Parrhesia.Storage.Supervisor` – adapter processes (`Repo`, pools)
|
3. `Parrhesia.Config` – runtime config cache (ETS-backed)
|
||||||
4. `Parrhesia.Subscriptions.Supervisor` – subscription index + fanout workers
|
4. `Parrhesia.Web.EventIngestLimiter` – relay-wide event ingest rate limiter
|
||||||
5. `Parrhesia.Auth.Supervisor` – AUTH challenge/session tracking
|
5. `Parrhesia.Web.IPEventIngestLimiter` – per-IP event ingest rate limiter
|
||||||
6. `Parrhesia.Policy.Supervisor` – rate limiters / ACL caches
|
6. `Parrhesia.Storage.Supervisor` – adapter processes (`Repo`, pools)
|
||||||
7. `Parrhesia.Web.Endpoint` – supervises configured WS + HTTP listeners
|
7. `Parrhesia.Subscriptions.Supervisor` – subscription index + fanout workers
|
||||||
8. `Parrhesia.Tasks.Supervisor` – background jobs (expiry purge, maintenance)
|
8. `Parrhesia.Auth.Supervisor` – AUTH challenge/session tracking
|
||||||
|
9. `Parrhesia.Sync.Supervisor` – outbound relay sync workers
|
||||||
|
10. `Parrhesia.Policy.Supervisor` – rate limiters / ACL caches
|
||||||
|
11. `Parrhesia.Web.Endpoint` – supervises configured WS + HTTP listeners
|
||||||
|
12. `Parrhesia.Tasks.Supervisor` – background jobs (expiry purge, maintenance)
|
||||||
|
|
||||||
Failure model:
|
Failure model:
|
||||||
|
|
||||||
|
|||||||
140
docs/KHATRU.md
140
docs/KHATRU.md
@@ -1,140 +0,0 @@
|
|||||||
# Khatru-Inspired Runtime Improvements
|
|
||||||
|
|
||||||
This document collects refactoring and extension ideas learned from studying Khatru-style relay design.
|
|
||||||
|
|
||||||
It is intentionally **not** about the new public API surface or the sync ACL model. Those live in `docs/slop/LOCAL_API.md` and `docs/SYNC.md`.
|
|
||||||
|
|
||||||
The focus here is runtime shape, protocol behavior, and operator-visible relay features.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Why This Matters
|
|
||||||
|
|
||||||
Khatru appears mature mainly because it exposes clearer relay pipeline stages.
|
|
||||||
|
|
||||||
That gives three practical benefits:
|
|
||||||
|
|
||||||
- less policy drift between storage, websocket, and management code,
|
|
||||||
- easier feature addition without hard-coding more branches into one connection module,
|
|
||||||
- better composability for relay profiles with different trust and traffic models.
|
|
||||||
|
|
||||||
Parrhesia should borrow that clarity without copying Khatru's code-first hook model wholesale.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Proposed Runtime Refactors
|
|
||||||
|
|
||||||
### 2.1 Staged policy pipeline
|
|
||||||
|
|
||||||
Parrhesia should stop treating policy as one coarse `EventPolicy` module plus scattered special cases.
|
|
||||||
|
|
||||||
Recommended internal stages:
|
|
||||||
|
|
||||||
1. connection admission
|
|
||||||
2. authentication challenge and validation
|
|
||||||
3. publish/write authorization
|
|
||||||
4. query/count authorization
|
|
||||||
5. stream subscription authorization
|
|
||||||
6. negentropy authorization
|
|
||||||
7. response shaping
|
|
||||||
8. broadcast/fanout suppression
|
|
||||||
|
|
||||||
This is an internal runtime refactor. It does not imply a new public API.
|
|
||||||
|
|
||||||
### 2.2 Richer internal request context
|
|
||||||
|
|
||||||
The runtime should carry a structured request context through all stages.
|
|
||||||
|
|
||||||
Useful fields:
|
|
||||||
|
|
||||||
- authenticated pubkeys
|
|
||||||
- caller kind
|
|
||||||
- remote IP
|
|
||||||
- subscription id
|
|
||||||
- peer id
|
|
||||||
- negentropy session flag
|
|
||||||
- internal-call flag
|
|
||||||
|
|
||||||
This reduces ad-hoc branching and makes audit/telemetry more coherent.
|
|
||||||
|
|
||||||
### 2.3 Separate policy from storage presence tables
|
|
||||||
|
|
||||||
Moderation state should remain data.
|
|
||||||
|
|
||||||
Runtime enforcement should be a first-class layer that consumes that data, not a side effect of whether a table exists.
|
|
||||||
|
|
||||||
This is especially important for:
|
|
||||||
|
|
||||||
- blocked IP enforcement,
|
|
||||||
- pubkey allowlists,
|
|
||||||
- future kind- or tag-scoped restrictions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Protocol and Relay Features
|
|
||||||
|
|
||||||
### 3.1 Real COUNT sketches
|
|
||||||
|
|
||||||
Parrhesia currently returns a synthetic `hll` payload for NIP-45-style count responses.
|
|
||||||
|
|
||||||
If approximate count exchange matters, implement a real reusable HLL sketch path instead of hashing `filters + count`.
|
|
||||||
|
|
||||||
### 3.2 Relay identity in NIP-11
|
|
||||||
|
|
||||||
Once Parrhesia owns a stable server identity, NIP-11 should expose the relay pubkey instead of returning `nil`.
|
|
||||||
|
|
||||||
This is useful beyond sync:
|
|
||||||
|
|
||||||
- operator visibility,
|
|
||||||
- relay fingerprinting,
|
|
||||||
- future trust tooling.
|
|
||||||
|
|
||||||
### 3.3 Connection-level IP enforcement
|
|
||||||
|
|
||||||
Blocked IP support should be enforced on actual connection admission, not only stored in management tables.
|
|
||||||
|
|
||||||
This should happen early, before expensive protocol handling.
|
|
||||||
|
|
||||||
### 3.4 Better response shaping
|
|
||||||
|
|
||||||
Introduce a narrow internal response shaping layer for cases where returned events or counts need controlled rewriting or suppression.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- hide fields for specific relay profiles,
|
|
||||||
- suppress rebroadcast of locally-ingested remote sync traffic,
|
|
||||||
- shape relay notices consistently.
|
|
||||||
|
|
||||||
This should stay narrow and deterministic. It should not become arbitrary app semantics.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Suggested Extension Points
|
|
||||||
|
|
||||||
These should be internal runtime seams, not necessarily public interfaces:
|
|
||||||
|
|
||||||
- `ConnectionPolicy`
|
|
||||||
- `AuthPolicy`
|
|
||||||
- `ReadPolicy`
|
|
||||||
- `WritePolicy`
|
|
||||||
- `NegentropyPolicy`
|
|
||||||
- `ResponsePolicy`
|
|
||||||
- `BroadcastPolicy`
|
|
||||||
|
|
||||||
They may initially be plain modules with well-defined callbacks or functions.
|
|
||||||
|
|
||||||
The point is not pluggability for its own sake. The point is to make policy stages explicit and testable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Near-Term Priority
|
|
||||||
|
|
||||||
Recommended order:
|
|
||||||
|
|
||||||
1. enforce blocked IPs and any future connection-gating on the real connection path
|
|
||||||
2. split the current websocket flow into explicit read/write/negentropy policy stages
|
|
||||||
3. enrich runtime request context and telemetry metadata
|
|
||||||
4. expose relay pubkey in NIP-11 once identity lands
|
|
||||||
5. replace fake HLL payloads with a real approximate-count implementation if NIP-45 support matters operationally
|
|
||||||
|
|
||||||
This keeps the runtime improvements incremental and independent from the ongoing API and ACL implementation.
|
|
||||||
147
docs/LOCAL_API.md
Normal file
147
docs/LOCAL_API.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# 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 `:parrhesia` as 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.Config` and `Parrhesia.Web.Endpoint`
|
||||||
|
- the config defaults in this repo's `config/*.exs` are 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:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
{:parrhesia, path: "../parrhesia"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure the runtime in your host app. At minimum you should carry over:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
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](../README.md).
|
||||||
|
- Production runtime overrides still use the `PARRHESIA_*` environment variables described in
|
||||||
|
[README.md](../README.md).
|
||||||
|
|
||||||
|
Migrate before serving traffic:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
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`:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
children = [
|
||||||
|
{Parrhesia.Runtime, name: Parrhesia.Supervisor}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Primary modules
|
||||||
|
|
||||||
|
The in-process surface is centered on these modules:
|
||||||
|
|
||||||
|
- `Parrhesia.API.Events` for publish, query, and count
|
||||||
|
- `Parrhesia.API.Stream` for REQ-like local subscriptions
|
||||||
|
- `Parrhesia.API.Auth` for event validation and NIP-98 auth parsing
|
||||||
|
- `Parrhesia.API.Admin` for management operations
|
||||||
|
- `Parrhesia.API.Identity` for relay-owned key management
|
||||||
|
- `Parrhesia.API.ACL` for protected sync ACLs
|
||||||
|
- `Parrhesia.API.Sync` for 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.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
%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
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
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](../README.md) for setup and the full config reference
|
||||||
|
- [docs/SYNC.md](./SYNC.md) for relay-to-relay sync semantics
|
||||||
|
- module docs under `Parrhesia.API.*` for per-function behavior
|
||||||
@@ -1,17 +1,27 @@
|
|||||||
defmodule Parrhesia do
|
defmodule Parrhesia do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Documentation for `Parrhesia`.
|
Parrhesia is a Nostr relay runtime that can run standalone or as an embedded OTP service.
|
||||||
|
|
||||||
|
For embedded use, the main developer-facing surface is `Parrhesia.API.*`.
|
||||||
|
Start with:
|
||||||
|
|
||||||
|
- `Parrhesia.API.Events`
|
||||||
|
- `Parrhesia.API.Stream`
|
||||||
|
- `Parrhesia.API.Admin`
|
||||||
|
- `Parrhesia.API.Identity`
|
||||||
|
- `Parrhesia.API.ACL`
|
||||||
|
- `Parrhesia.API.Sync`
|
||||||
|
|
||||||
|
The host application is responsible for:
|
||||||
|
|
||||||
|
- setting `config :parrhesia, ...`
|
||||||
|
- migrating the configured Parrhesia repos
|
||||||
|
- deciding whether to expose listeners or use only the in-process API
|
||||||
|
|
||||||
|
See `README.md` and `docs/LOCAL_API.md` for the embedding model and configuration guide.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@doc """
|
@doc false
|
||||||
Hello world.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
iex> Parrhesia.hello()
|
|
||||||
:world
|
|
||||||
|
|
||||||
"""
|
|
||||||
def hello do
|
def hello do
|
||||||
:world
|
:world
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
defmodule Parrhesia.ConnectionStats do
|
defmodule Parrhesia.ConnectionStats do
|
||||||
@moduledoc false
|
@moduledoc """
|
||||||
|
Per-listener connection and subscription counters.
|
||||||
|
|
||||||
|
Tracks active connection and subscription counts per listener and emits
|
||||||
|
`[:parrhesia, :listener, :population]` telemetry events on each change.
|
||||||
|
"""
|
||||||
|
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
Postgrex.Types.define(Parrhesia.PostgresTypes, [], json: JSON)
|
Postgrex.Types.define(Parrhesia.PostgresTypes, [],
|
||||||
|
json: JSON,
|
||||||
|
moduledoc: "Custom Postgrex type definitions used by `Parrhesia.Repo` and `Parrhesia.ReadRepo`."
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
defmodule Parrhesia.Release do
|
defmodule Parrhesia.Release do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Helpers for running Ecto tasks from a production release.
|
Helpers for running Ecto tasks from a production release.
|
||||||
|
|
||||||
|
Intended for use from a release `eval` command where Mix is not available:
|
||||||
|
|
||||||
|
bin/parrhesia eval "Parrhesia.Release.migrate()"
|
||||||
|
bin/parrhesia eval "Parrhesia.Release.rollback(Parrhesia.Repo, 20260101000000)"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@app :parrhesia
|
@app :parrhesia
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Runs all pending Ecto migrations for every configured repo.
|
||||||
|
"""
|
||||||
def migrate do
|
def migrate do
|
||||||
load_app()
|
load_app()
|
||||||
|
|
||||||
@@ -16,6 +24,9 @@ defmodule Parrhesia.Release do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Rolls back the given `repo` to the specified migration `version`.
|
||||||
|
"""
|
||||||
def rollback(repo, version) when is_atom(repo) and is_integer(version) do
|
def rollback(repo, version) when is_atom(repo) and is_integer(version) do
|
||||||
load_app()
|
load_app()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
defmodule Parrhesia.Repo do
|
defmodule Parrhesia.Repo do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
PostgreSQL repository for storage adapter persistence.
|
PostgreSQL repository for write traffic and storage adapter persistence.
|
||||||
|
|
||||||
|
Separated from `Parrhesia.ReadRepo` so that ingest writes and read-heavy
|
||||||
|
queries use independent connection pools.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Ecto.Repo,
|
use Ecto.Repo,
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
defmodule Parrhesia.Runtime do
|
defmodule Parrhesia.Runtime do
|
||||||
@moduledoc false
|
@moduledoc """
|
||||||
|
Top-level Parrhesia supervisor.
|
||||||
|
|
||||||
|
In normal standalone use, the `:parrhesia` application starts this supervisor automatically.
|
||||||
|
Host applications can also embed it directly under their own supervision tree:
|
||||||
|
|
||||||
|
children = [
|
||||||
|
{Parrhesia.Runtime, name: Parrhesia.Supervisor}
|
||||||
|
]
|
||||||
|
|
||||||
|
Parrhesia currently assumes a single runtime per BEAM node and uses globally registered
|
||||||
|
process names for core services.
|
||||||
|
"""
|
||||||
|
|
||||||
use Supervisor
|
use Supervisor
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Starts the Parrhesia runtime supervisor.
|
||||||
|
|
||||||
|
Accepts a `:name` option (defaults to `Parrhesia.Supervisor`).
|
||||||
|
"""
|
||||||
def start_link(opts \\ []) do
|
def start_link(opts \\ []) do
|
||||||
name = Keyword.get(opts, :name, Parrhesia.Supervisor)
|
name = Keyword.get(opts, :name, Parrhesia.Supervisor)
|
||||||
Supervisor.start_link(__MODULE__, opts, name: name)
|
Supervisor.start_link(__MODULE__, opts, name: name)
|
||||||
@@ -13,6 +30,9 @@ defmodule Parrhesia.Runtime do
|
|||||||
Supervisor.init(children(), strategy: :one_for_one)
|
Supervisor.init(children(), strategy: :one_for_one)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of child specifications started by the runtime supervisor.
|
||||||
|
"""
|
||||||
def children do
|
def children do
|
||||||
[
|
[
|
||||||
Parrhesia.Telemetry,
|
Parrhesia.Telemetry,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
defmodule Parrhesia.Telemetry do
|
defmodule Parrhesia.Telemetry do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Supervision entrypoint and helpers for relay telemetry.
|
Supervision entrypoint and helpers for relay telemetry.
|
||||||
|
|
||||||
|
Starts the Prometheus reporter and telemetry poller as supervised children.
|
||||||
|
All relay metrics are namespaced under `parrhesia.*` and exposed through the
|
||||||
|
`/metrics` endpoint in Prometheus exposition format.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Supervisor
|
use Supervisor
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
defmodule Parrhesia.Web.RelayInfo do
|
defmodule Parrhesia.Web.RelayInfo do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
NIP-11 relay information document.
|
NIP-11 relay information document.
|
||||||
|
|
||||||
|
`document/1` builds the JSON-serialisable relay info map served on
|
||||||
|
`GET /relay` with `Accept: application/nostr+json`, including supported NIPs,
|
||||||
|
limitations, and the relay's advertised public key.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Parrhesia.API.Identity
|
alias Parrhesia.API.Identity
|
||||||
@@ -8,7 +12,7 @@ defmodule Parrhesia.Web.RelayInfo do
|
|||||||
alias Parrhesia.NIP43
|
alias Parrhesia.NIP43
|
||||||
alias Parrhesia.Web.Listener
|
alias Parrhesia.Web.Listener
|
||||||
|
|
||||||
@spec document(Listener.t()) :: map()
|
@spec document(map()) :: map()
|
||||||
def document(listener) do
|
def document(listener) do
|
||||||
document = %{
|
document = %{
|
||||||
"name" => Metadata.name(),
|
"name" => Metadata.name(),
|
||||||
|
|||||||
39
mix.exs
39
mix.exs
@@ -9,7 +9,8 @@ defmodule Parrhesia.MixProject do
|
|||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
deps: deps(),
|
deps: deps(),
|
||||||
aliases: aliases()
|
aliases: aliases(),
|
||||||
|
docs: docs()
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ defmodule Parrhesia.MixProject do
|
|||||||
|
|
||||||
# Project tooling
|
# Project tooling
|
||||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||||
|
{:ex_doc, "~> 0.34", only: :dev, runtime: false},
|
||||||
{:deps_changelog, "~> 0.3"},
|
{:deps_changelog, "~> 0.3"},
|
||||||
{:igniter, "~> 0.6", only: [:dev, :test]}
|
{:igniter, "~> 0.6", only: [:dev, :test]}
|
||||||
]
|
]
|
||||||
@@ -82,4 +84,39 @@ defmodule Parrhesia.MixProject do
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp docs do
|
||||||
|
[
|
||||||
|
main: "readme",
|
||||||
|
output: "_build/doc",
|
||||||
|
extras: [
|
||||||
|
"README.md",
|
||||||
|
"docs/LOCAL_API.md",
|
||||||
|
"docs/SYNC.md",
|
||||||
|
"docs/ARCH.md",
|
||||||
|
"docs/CLUSTER.md",
|
||||||
|
"BENCHMARK.md"
|
||||||
|
],
|
||||||
|
groups_for_modules: [
|
||||||
|
"Embedded API": [
|
||||||
|
Parrhesia.API.ACL,
|
||||||
|
Parrhesia.API.Admin,
|
||||||
|
Parrhesia.API.Auth,
|
||||||
|
Parrhesia.API.Auth.Context,
|
||||||
|
Parrhesia.API.Events,
|
||||||
|
Parrhesia.API.Events.PublishResult,
|
||||||
|
Parrhesia.API.Identity,
|
||||||
|
Parrhesia.API.RequestContext,
|
||||||
|
Parrhesia.API.Stream,
|
||||||
|
Parrhesia.API.Sync
|
||||||
|
],
|
||||||
|
Runtime: [
|
||||||
|
Parrhesia,
|
||||||
|
Parrhesia.Release,
|
||||||
|
Parrhesia.Runtime
|
||||||
|
]
|
||||||
|
],
|
||||||
|
nest_modules_by_prefix: [Parrhesia.API]
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
6
mix.lock
6
mix.lock
@@ -8,9 +8,11 @@
|
|||||||
"db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"},
|
"db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"},
|
||||||
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
||||||
"deps_changelog": {:hex, :deps_changelog, "0.3.5", "65981997d9bc893b8027a0c03da093a4083328c00b17f562df269c2b61d44073", [:mix], [], "hexpm", "298fcd7794395d8e61dba8d29ce8fcee09f1df4d48adb273a41e8f4a1736491e"},
|
"deps_changelog": {:hex, :deps_changelog, "0.3.5", "65981997d9bc893b8027a0c03da093a4083328c00b17f562df269c2b61d44073", [:mix], [], "hexpm", "298fcd7794395d8e61dba8d29ce8fcee09f1df4d48adb273a41e8f4a1736491e"},
|
||||||
|
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
|
||||||
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
|
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
|
"ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
||||||
|
"ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"},
|
||||||
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
|
||||||
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
|
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
|
||||||
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
|
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
|
||||||
@@ -18,9 +20,13 @@
|
|||||||
"igniter": {:hex, :igniter, "0.7.4", "b5f9dd512eb1e672f1c141b523142b5b4602fcca231df5b4e362999df4b88e14", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "971b240ee916a06b1af56381a262d9eeaff9610eddc299d61a213cd7a9d79efd"},
|
"igniter": {:hex, :igniter, "0.7.4", "b5f9dd512eb1e672f1c141b523142b5b4602fcca231df5b4e362999df4b88e14", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "971b240ee916a06b1af56381a262d9eeaff9610eddc299d61a213cd7a9d79efd"},
|
||||||
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||||
"lib_secp256k1": {:hex, :lib_secp256k1, "0.7.1", "53cad778b8da3a29e453a7a477517d99fb5f13f615c8050eb2db8fd1dce7a1db", [:make, :mix], [{:elixir_make, "~> 0.9", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "78bdd3661a17448aff5aeec5ca74c8ddbc09b01f0ecfa3ba1aba3e8ae47ab2b3"},
|
"lib_secp256k1": {:hex, :lib_secp256k1, "0.7.1", "53cad778b8da3a29e453a7a477517d99fb5f13f615c8050eb2db8fd1dce7a1db", [:make, :mix], [{:elixir_make, "~> 0.9", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "78bdd3661a17448aff5aeec5ca74c8ddbc09b01f0ecfa3ba1aba3e8ae47ab2b3"},
|
||||||
|
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
|
||||||
|
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
|
||||||
|
"makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"},
|
||||||
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
|
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
|
||||||
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
|
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
|
||||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||||
|
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||||
"owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"},
|
"owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"},
|
||||||
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
|
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
|
||||||
|
|||||||
Reference in New Issue
Block a user