Packages
skuld
0.7.0
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/)Evidence-passing Algebraic Effects for Elixir.## The problemBetween your pure business logic and your side-effecting infrastructuresits the orchestration layer: "fetch the user, check permissions, loadtheir subscription, compute the price, write the invoice." This codeencodes your most important business rules, but it's tangled withdatabases, APIs, and randomness - making it hard to test, hard torefactor, and impossible to property-test.## The insightSkuld adds a layer between pure and side-effecting code: **effectful**code. Domain logic *requests* effects (database access, randomness, errorhandling) without performing them. Handlers decide what those requestsmean. The same orchestration code runs with real handlers in productionand pure in-memory handlers in tests - fully deterministic, triviallytestable.## What Skuld solves| Pain point | What Skuld does | More ||----------------------------------------|----------------------------------------------------------------------------|----------------------------------------------------------------------------|| Orchestration code is untestable | Swap handlers: same code runs in-memory with no DB, no network | [Testing](docs/pain-points.md#testing-orchestration-code) || Mox boilerplate for multiple stubs | Handler scoping replaces Mox - five stubs compose as cleanly as one | [No more Mox](docs/pain-points.md#no-more-mox-boilerplate) || Non-deterministic UUIDs / randomness | Fresh and Random have deterministic test handlers - same test, same values | [Determinism](docs/pain-points.md#deterministic-uuids-randomness-and-time) || N+1 queries | `deffetch` + `query` batch independent loads automatically | [Batching](docs/pain-points.md#automatic-query-batching) || Long-running workflows across restarts | EffectLogger serialises progress; resume from where you left off | [Durable workflows](docs/pain-points.md#long-running-computations) || LiveView multi-step operations | AsyncComputation bridges effects into LiveView's process model | [LiveView](docs/pain-points.md#liveview-multi-step-operations) || Hexagonal architecture plumbing | Port.Contract / Port.Provider - typed boundaries, no parameter threading | [Hex arch](docs/pain-points.md#clean-architecture-boundaries) |See [What Skuld Solves](docs/pain-points.md) for worked examples of each.## Quick example```elixirdefmodule Onboarding do use Skuld.Syntax defcomp register(params) do # Read configuration from the environment config <- Reader.ask() # Generate a deterministic ID id <- Fresh.fresh_uuid() # Use a port for the database call user <- UserRepo.create_user!(%{id: id, name: params.name, tier: config.default_tier}) # Accumulate domain events _ <- EventAccumulator.emit(%UserRegistered{user_id: id}) {:ok, user} endend```Run with production handlers:```elixirOnboarding.register(%{name: "Alice"})|> Reader.with_handler(%{default_tier: :free})|> Fresh.with_uuid7_handler()|> Port.with_handler(%{UserRepo => UserRepo.Ecto})|> EventAccumulator.with_handler(output: fn r, events -> MyApp.EventBus.publish(events) rend)|> Throw.with_handler()|> Comp.run!()```Run with test handlers - same code, no database, fully deterministic:```elixirOnboarding.register(%{name: "Alice"})|> Reader.with_handler(%{default_tier: :free})|> Fresh.with_test_handler()|> Port.with_test_handler(%{ UserRepo.key(:create_user, %{id: _, name: "Alice", tier: :free}) => {:ok, %User{id: "test-uuid", name: "Alice", tier: :free}}})|> EventAccumulator.with_handler(output: fn r, events -> {r, events} end)|> Throw.with_handler()|> Comp.run!()```## InstallationAdd `skuld` to your dependencies in `mix.exs` (see [Hex](https://hex.pm/packages/skuld) for the current version):```elixirdef deps do [ {:skuld, "~> 0.3"} ]end```## What can it do?### Foundational effectsEffects that solve problems every Elixir developer recognises. They feellike well-structured Elixir code with better testability.| Side-effecting operation | Effectful equivalent ||---------------------------------|------------------------------|| Configuration / environment | Reader || Process dictionary / state | State, Writer || Random values | Random || Generating IDs (UUIDs) | Fresh || Transactions | Transaction || Blocking calls to external code | Port, Port.Contract || Effectful code from plain code | Port.Provider || Mutation dispatch | Command || Domain event collection | EventAccumulator || Fork-join concurrency | Parallel || Thread-safe state | AtomicState || Effects from LiveView | AsyncComputation || Raising exceptions | Throw || Resource cleanup (try/finally) | Bracket || Effectful list operations | FxList, FxFasterList |### Advanced effectsEffects that use cooperative fibers and continuations to do things thataren't possible with standard BEAM patterns. You don't need these toget value from Skuld - the foundational effects stand on their own.| Pattern | Effectful equivalent ||---------------------------------|------------------------------|| Coroutines / suspend-resume | Yield || Cooperative fibers | FiberPool || Bounded channels | Channel || Streaming with backpressure | Brook || Automatic N+1 query batching | query, deffetch, Query.Cache || Serializable coroutines | EffectLogger |## Documentation**New to algebraic effects?** Start with[Why Effects?](docs/why.md) - the problem, explained without jargon.**Ready to code?** Jump to[Getting Started](docs/getting-started.md) for your first computation.### Full documentation| Layer | Topic | Description ||-------|-------|-------------|| 1 | [Why Effects?](docs/why.md) | The problem effects solve || 2 | [The Concept](docs/what.md) | How algebraic effects work || | [What Skuld Solves](docs/pain-points.md) | Concrete problems, worked examples || 3 | [Getting Started](docs/getting-started.md) | Your first computation || 4 | [Syntax In Depth](docs/syntax.md) | `comp`, `else`, `catch`, `defcomp` || 5 | **Foundational Effects** | || | [State & Environment](docs/effects/state-environment.md) | State, Reader, Writer || | [Error Handling](docs/effects/error-handling.md) | Throw, Bracket || | [Value Generation](docs/effects/value-generation.md) | Fresh, Random || | [Collections](docs/effects/collections.md) | FxList, FxFasterList || | [Concurrency](docs/effects/concurrency.md) | Parallel, AtomicState, AsyncComputation || | [Persistence](docs/effects/persistence.md) | Transaction, Command, EventAccumulator || | [External Integration](docs/effects/external-integration.md) | Port, Port.Contract, Port.Provider || 6 | **Advanced Effects** | || | [Yield](docs/advanced/yield.md) | Coroutines || | [Fibers & Concurrency](docs/advanced/fibers-concurrency.md) | FiberPool, Channel, Brook || | [Query & Batching](docs/advanced/query-batching.md) | Automatic N+1 prevention || | [EffectLogger](docs/advanced/effect-logger.md) | Serializable coroutines || 7 | **Recipes** | || | [Testing](docs/recipes/testing.md) | Property-based testing with effects || | [Hexagonal Architecture](docs/recipes/hexagonal-architecture.md) | Port.Contract + Port.Provider || | [Decider Pattern](docs/recipes/decider-pattern.md) | Event-sourced domain logic || | [Handler Stacks](docs/recipes/handler-stacks.md) | Composing production & test stacks || | [LiveView](docs/recipes/liveview.md) | Multi-step wizards || | [Durable Workflows](docs/recipes/durable-workflows.md) | Persist-and-resume with EffectLogger || | [Data Pipelines](docs/recipes/data-pipelines.md) | Streaming with Brook || | [Batch Loading](docs/recipes/batch-loading.md) | N+1-free data access |## Demo ApplicationSee [TodosMcp](https://github.com/mccraigmccraig/todos_mcp) - avoice-controllable todo application built with Skuld. It demonstratescommand/query structs with algebraic effects for LLM integration andproperty-based testing. Try it live athttps://todos-mcp-lu6h.onrender.com/## LicenseMIT License - see [LICENSE](LICENSE) for details.<!-- nav:footer:start -->---[Why Effects? >](docs/why.md)<!-- nav:footer:end -->