Port.EffectfulFacade

View Source

< Port | Up: Boundaries | Index | Adapter >

Generates typed effectful caller functions from defcallback declarations. The callers return computation(return_type) values that dispatch via the Port effect.

Single-module (simplest)

Define callbacks directly in the facade module:

defmodule MyApp.Todos do
  use Skuld.Effects.Port.EffectfulFacade

  defcallback get_todo(id :: String.t()) :: {:ok, Todo.t()} | {:error, term()}
  defcallback list_todos() :: [Todo.t()]
end

MyApp.Todos now has effectful @callbacks, __callbacks__/0, __port_effectful__?/0, and dispatch functions:

comp do
  todo <- MyApp.Todos.get_todo("42")    # returns computation
  todo
end
|> Port.with_handler(%{MyApp.Todos => MyApp.Todos.Ecto})
|> Throw.with_handler()
|> Comp.run!()

From a DoubleDown contract

When the contract and facade are separate:

defmodule MyApp.Todos.Contract do
  use DoubleDown.Contract
  defcallback get_todo(id :: String.t()) :: {:ok, Todo.t()} | {:error, term()}
end

defmodule MyApp.Todos do
  use Skuld.Effects.Port.EffectfulFacade,
    double_down_contract: MyApp.Todos.Contract
end

Test stub keys

The __key__ helpers generate canonical keys for Port.with_test_handler:

responses = %{
  MyApp.Todos.__key__(:get_todo, "42") => {:ok, %Todo{id: "42"}}
}

computation |> Port.with_test_handler(responses) |> Comp.run!()
OptionPurpose
(none)Single-module: callbacks + facade in one module
:double_down_contractCombine effectful contract + facade from a DD contract
:contractSeparate effectful contract module (used with Adapter.EffectfulContract)

Handler wiring

Register the facade module as an effectful resolver:

Port.with_handler(%{MyApp.Todos => MyApp.Todos.Ecto})

If the implementation's functions return computations (via __port_effectful__?/0), Port auto-detects and inlines them into the current effect context.


< Port | Up: Boundaries | Index | Adapter >