# Skuld
<!-- nav:header:start -->
[Why Effects? >](docs/why.md)
<!-- nav:header:end -->
[](https://github.com/mccraigmccraig/skuld/actions/workflows/test.yml)
[](https://hex.pm/packages/skuld)
[](https://hexdocs.pm/skuld/)
An effectful programming framework for Elixir.
```
Comp
(lazy computation,
evidence-passing,
scoped handlers)
│
┌─────────────────────┼───────────────────────────┐
│ │ │
//Foundational //Coroutines & //Boundaries
//Effects //Concurrency │
│ │ │
│ Coroutine ┌──────┼────┐
│ │ │ │ │
State, Reader, ┌────────┼─────────┐ │ │ Port
Writer, Throw, │ │ │ │ │ Port.EffectfulFacade
Bracket, Fresh, │ Serializable- │ │ │ Repo
Random, FxList, │ Coroutine │ │ │
Yield, │ │ │ │
EffectLogger, AsyncCoroutine FiberPool │ Adapter
Parallel, │ │ Adapter.EffectfulContract
AtomicState, ├─────────┐│
Transaction, │ ││
Command │ ││
┌────┴────┐ ││
Channel Task ││
│ ││
Brook ││
││
Query.Contract
QueryBlock
(Haxl-like: auto-batches fetches
via Coroutine fibers)
```
## The old problem
Between pure business logic and side-effecting infrastructure
sits the orchestration layer — "fetch the user, check permissions, load
their subscription, hit some APIs, compute a price, write an invoice."
This code encodes your most important business rules, but it's tangled with
databases, APIs, and randomness — making it hard to test, hard to
refactor, and often — impossible to property-test.
## Another way
Skuld lets you write orchestration code that *describes* side effects
without performing them — then handlers decide what those descriptions mean.
The exact same "effectful" code runs with side-effecting handlers in production
and pure in-memory handlers in tests — fully deterministic, fully pure, and
straightforwardly property-testable.
Because effects are first-class data, Skuld can do more — batch
independent queries automatically, serialise partially complete computations
for later resumption.
## Quick example
```elixir
defmodule Onboarding do
use Skuld.Syntax
alias Skuld.Repo
alias Skuld.Effects.{Fresh, Reader, Writer}
defcomp register(params) do
config <- Reader.ask()
id <- Fresh.fresh_uuid()
{:ok, user} <- Repo.insert(User.changeset(%{id: id, name: params.name, tier: config.default_tier}))
_ <- Writer.tell(:events, %UserRegistered{user_id: id})
{:ok, user}
end
end
```
Run with production handlers:
```elixir
# One-time setup: generate an Ecto adapter for the Repo contract
defmodule MyApp.Repo.Port do
use Skuld.Repo.Ecto, repo: MyApp.Repo
end
# Wire everything up:
Onboarding.register(%{name: "Alice"})
|> Reader.with_handler(%{default_tier: :free})
|> Fresh.with_uuid7_handler()
|> Port.with_handler(%{Skuld.Repo.Effectful => MyApp.Repo.Port})
|> Writer.with_handler([], tag: :events, output: fn r, raw ->
MyApp.EventBus.publish(Enum.reverse(raw))
r
end)
|> Throw.with_handler()
|> Comp.run!()
```
Run with test handlers — same code, fully deterministic, no database:
```elixir
Onboarding.register(%{name: "Alice"})
|> Reader.with_handler(%{default_tier: :free})
|> Fresh.with_test_handler()
|> Repo.InMemory.with_handler(Repo.InMemory.new())
|> Writer.with_handler([], tag: :events, output: fn r, raw -> {r, Enum.reverse(raw)} end)
|> Throw.with_handler()
|> Comp.run!()
```
`Repo.InMemory` is a closed-world in-memory store with read-after-write
consistency. Records created during the test are immediately readable by
subsequent `Repo.get` / `Repo.get_by` calls — no mocks, no stubs.
## Installation
```elixir
def deps do
[
{:skuld, "~> 0.27"}
]
end
```
## Where next?
| If you want to... | Read |
|---|---|
| Understand the problem effects solve | [Why Effects?](docs/why.md) |
| See how effects and handlers work | [How It Works](docs/what.md) |
| Write your first computation | [Getting Started](docs/getting-started.md) |
| State, Reader, Writer, Throw, Fresh, Random | [Foundational Effects](docs/effects/state-reader-writer.md) |
| Yield, Coroutines, FiberPool, Channels, Async | [Coroutines & Concurrency](docs/effects/yield.md) |
| Port, Repo, Hexagonal Architecture | [Boundaries](docs/effects/port.md) |
| Eliminate N+1 queries | [Query System](docs/effects/query.md) |
| Handler-swapping for deterministic testing | [Testing](docs/testing.md) |
| Full effect and API reference | [Reference](docs/reference.md) |
## License
MIT License — see [LICENSE](LICENSE) for details.
<!-- nav:footer:start -->
---
[Why Effects? >](docs/why.md)
<!-- nav:footer:end -->