Skip to main content

README.md

# skuld_port

<!-- nav:header:start -->
[Port >](docs/effects/port.md) | [Umbrella →](https://hexdocs.pm/skuld/architecture.html)
<!-- nav:header:end -->

Port/adapter boundaries for Skuld — typed contracts, pluggable backends,
and bridges between effectful and plain Elixir code. This is the
hexagonal architecture layer: define interfaces, swap implementations.

## What's included

- **`Skuld.Effects.Port`** — dispatches function calls to pluggable
  backends via a registry. Supports plain resolvers (`:direct`, function
  handlers) and effectful resolvers (computation-returning modules with
  compile-time auto-detection via `__port_effectful__?/0`). Merges
  nested registries, supports dispatch logging.
- **`Skuld.Effects.Port.EffectfulFacade`** — generates typed effectful
  callers from `defcallback` declarations. Single-module pattern for
  purely effectful boundaries; three-module pattern for mixed
  Plain/Effectful systems.
- **`Skuld.Adapter`** — bridges an effectful implementation (one whose
  functions return `computation()`) to a plain Elixir interface. Takes
  a contract, an impl module, and a handler stack — generates a module
  that satisfies the plain behaviour.
- **`Skuld.Adapter.EffectfulContract`** — generates effectful `@callback`
  declarations from a DoubleDown contract. The contract module doubles
  as a `__using__` helper so implementations get `@behaviour` and the
  `__port_effectful__?/0` marker in one line.

## The four directions

| Caller | Implementation | Mechanism |
|--------|---------------|-----------|
| Plain Elixir | Plain Elixir | `DoubleDown.ContractFacade` |
| Plain Elixir | Effectful | `Skuld.Adapter` |
| Effectful | Plain Elixir | `Port.with_handler` + `:direct` |
| Effectful | Effectful | `Port.with_handler` + effectful module (auto-detected) |

## Installation

```elixir
def deps do
  [
    {:skuld_port, "~> 0.32"}
  ]
end
```

## Example: single-module effectful boundary

```elixir
# Contract + Facade in one module
defmodule MyApp.Payments do
  use Skuld.Effects.Port.EffectfulFacade
  defcallback charge(amount :: integer()) :: {:ok, receipt()} | {:error, term()}
end

# Implementation — one-liner with compile-time behaviour
defmodule MyApp.Payments.Stripe do
  use Skuld.Syntax
  use MyApp.Payments

  @impl MyApp.Payments
  defcomp charge(amount) do
    {:ok, _} <- StripeAPI.create_charge(amount)
    charge_id <- Fresh.uuid4()
    {:ok, %{charge_id: charge_id}}
  end
end

# Wire at runtime — auto-detected as effectful
MyApp.Payments.charge(99)
|> Port.with_handler(%{MyApp.Payments => MyApp.Payments.Stripe})
|> Throw.with_handler()
|> Comp.run!()
```

## Further reading

- [Port](https://hexdocs.pm/skuld_port/port.html) — resolvers, logging, key-based stubs
- [EffectfulFacade](https://hexdocs.pm/skuld_port/effectful-facade.html) — single-module and three-module patterns
- [Adapter](https://hexdocs.pm/skuld_port/adapter.html) — bridging effectful → plain
- [Hexagonal Architecture](https://hexdocs.pm/skuld/hexagonal-architecture.html) — full recipe with testing

See the [architecture guide](https://hexdocs.pm/skuld/architecture.html) for how this fits into the Skuld ecosystem.

<!-- nav:footer:start -->

---

[Port >](docs/effects/port.md) | [Umbrella →](https://hexdocs.pm/skuld/architecture.html)
<!-- nav:footer:end -->