< Adapter | Up: Boundaries | Index | Query System >

Skuld's built-in database contract. Operations dispatch via the Port effect to pluggable handlers — Ecto in production, in-memory maps in tests.

Usage

{:ok, user} <- Repo.insert(User.changeset(%{name: "Alice"}))
{:ok, user} <- Repo.get(User, id)
{:ok, user} <- Repo.get_by(User, email: "alice@example.com")
{:ok, users} <- Repo.all(User)
{:ok, user} <- Repo.one(from u in User, where: u.age > 18)
{:ok, count} <- Repo.aggregate(User, :count, :id)

Bang variants return unwrapped values or throw on error:

user <- Repo.insert!(changeset)
user <- Repo.get!(User, id)

Production

Generate an Ecto adapter and register via Port:

defmodule MyApp.Repo.Port do
  use Skuld.Repo.Ecto, repo: MyApp.Repo
end

computation
|> Port.with_handler(%{Skuld.Repo.Effectful => MyApp.Repo.Port})
|> Throw.with_handler()
|> Comp.run!()

Test

Repo.InMemory — closed-world store with read-after-write consistency:

computation |> Repo.InMemory.with_handler(Repo.InMemory.new())

Records inserted during the test are immediately readable by subsequent Repo.get / Repo.get_by calls — no mocks, no stubs.

Repo.Stub — stateless stub. Writes return results but store nothing. Reads require a fallback function:

fallback = fn
  :get, [User, id], _state -> {:ok, %User{id: id}}
end

computation |> Repo.Stub.with_handler(fallback: fallback)

Operations

CategoryOperations
Writeinsert/1,2, update/1,2, delete/1,2, insert_all/3, insert_or_update/1,2
Read by PKget/2,3, get!/2,3
Read by fieldsget_by/2,3, get_by!/2,3
Queryall/1,2, one/1,2, aggregate/4, query/1,2,3
Reload/statsreload/1,2, preload/2,3, stream/2,3, load/2,3, all_by/3

Bang variants: insert!/1,2, update!/1,2, delete!/1,2, get!/2,3, get_by!/2,3, one!/1,2, query!/1,2,3.


< Adapter | Up: Boundaries | Index | Query System >