# `Parrhesia.API.Events`

Canonical event publish, query, and count API.

This is the main in-process API for working with Nostr events. It applies the same core
validation and policy checks used by the relay edge, but without going through a socket or
HTTP transport.

All public functions expect `opts[:context]` to contain a `Parrhesia.API.RequestContext`.
That context drives authorization, caller attribution, and downstream policy behavior.

`publish/2` intentionally returns `{:ok, %PublishResult{accepted: false}}` for policy and
storage rejections so callers can mirror relay `OK` semantics without treating a rejected
event as a process error.

# `count`

```elixir
@spec count(
  [map()],
  keyword()
) :: {:ok, non_neg_integer() | map()} | {:error, term()}
```

Counts events matching the given filters.

Required options:

- `:context` - a `Parrhesia.API.RequestContext`

Supported options:

- `:validate_filters?` - skips filter validation when `false`
- `:authorize_read?` - skips read policy checks when `false`
- `:options` - when set to a map, returns a NIP-45-style payload instead of a bare integer

When `opts[:options]` is a map, the result shape is `%{"count" => count, "approximate" => false}`.
If `opts[:options]["hll"]` is `true` and the feature is enabled, an `"hll"` field is included.

# `paginate`

```elixir
@spec paginate(
  map(),
  keyword()
) :: {:ok, [map()]} | {:error, term()}
```

Paginates stored events for a single filter using keyset cursors.

Unlike `query/2`, this bypasses NIP-01 newest-first ordering and any dynamic NIP-43
expansion; it returns the persisted-event slice that strictly satisfies the keyset
bounds in the requested order. Intended for the Parrhesia `SYNC-PAGE` wire frame.

Required options:

- `:context` - a `Parrhesia.API.RequestContext`

Supported options:

- `:order` - `:asc` (default) or `:desc`
- `:limit` - max events returned (caller is responsible for capping)
- `:after_key`, `:until_key` - `{created_at, event_id_hex}` keyset bounds
- `:validate_filters?`, `:authorize_read?` - same semantics as `query/2`

# `publish`

```elixir
@spec publish(
  map(),
  keyword()
) :: {:ok, Parrhesia.API.Events.PublishResult.t()} | {:error, term()}
```

Validates, authorizes, persists, and fans out an event.

Required options:

- `:context` - a `Parrhesia.API.RequestContext`

Supported options:

- `:max_event_bytes` - overrides the configured max encoded event size
- `:role`, `:path`, `:private_key`, `:configured_private_key` - forwarded to the NIP-43 helper flow

Return semantics:

- `{:ok, %PublishResult{accepted: true}}` for accepted events
- `{:ok, %PublishResult{accepted: false}}` for rejected or duplicate events
- `{:error, :invalid_context}` only when the call itself is malformed

# `publish_many`

```elixir
@spec publish_many(
  [map()],
  keyword()
) :: {:ok, [Parrhesia.API.Events.PublishResult.t()]} | {:error, term()}
```

Batch variant of `publish/2` for trusted sync paths that have already received a page of
remote events in one transport frame.

Untrusted callers still run full event validation. Trusted sync callers skip duplicate
signature/hash/future-timestamp validation because the source Parrhesia node already
validated before storage. Regular persisted events are inserted with a storage-level batch
operation and dispatched as a batch. Control and ephemeral events fall back to `publish/2`
because their side effects are not bulk-safe.

# `query`

```elixir
@spec query(
  [map()],
  keyword()
) :: {:ok, [map()]} | {:error, term()}
```

Queries stored events plus any dynamic NIP-43 events visible to the caller.

Required options:

- `:context` - a `Parrhesia.API.RequestContext`

Supported options:

- `:max_filter_limit` - overrides the configured per-filter limit
- `:validate_filters?` - skips filter validation when `false`
- `:authorize_read?` - skips read policy checks when `false`

The skip flags are primarily for internal composition, such as `Parrhesia.API.Stream`.
External callers should normally leave them enabled.

# `query_with_eose_hints`

```elixir
@spec query_with_eose_hints(
  [map()],
  keyword()
) :: {:ok, [map()], [String.t()]} | {:error, term()}
```

Queries events and returns NIP-67 EOSE completeness hints for the same visible result set.

The hint check widens each effective limit by one and compares the visible result count. If
widening reveals more events than were sent, the relay advertises `"more"`; otherwise it
advertises `"finish"`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
