Packages
skuld
0.27.2
0.33.1
0.33.0
0.32.1
0.32.0
0.31.2
0.31.1
0.31.0
0.30.0
0.28.0
0.27.3
0.27.2
0.27.1
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.0
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Core effect system for Elixir: write business logic as pure effect descriptions, swap handlers for testing. Provides the Comp engine and foundational effects (State, Reader, Writer, Throw, Yield).
Current section
Files
Jump to
Current section
Files
README.md
# 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 -->