Skuld.Query.Contract (skuld v0.3.0)
View SourceMacro for defining typed batchable fetch contracts with deffetch declarations.
A Query contract defines a set of fetch operations, generating:
- Operation structs — nested struct modules per fetch (e.g.,
MyContract.GetUser) - Caller functions — typed public API returning
computation(return_type), which suspend the current fiber for batched execution - Executor behaviour (
__MODULE__.Executor) — typed callbacks for batch execution, one per fetch - Dispatch function —
__dispatch__/3routes from batch key to executor callback - Wiring function —
with_executor/2installs an executor module for all fetches - Bang variants — unwrap
{:ok, v}or dispatch Throw (when applicable) - Introspection —
__query_operations__/0
Example
defmodule MyApp.Queries.Users do
use Skuld.Query.Contract
deffetch get_user(id :: String.t()) :: User.t() | nil
deffetch get_users_by_org(org_id :: String.t()) :: [User.t()]
deffetch get_user_count(org_id :: String.t()) :: non_neg_integer()
endThis generates:
# Operation struct
MyApp.Queries.Users.GetUser # defstruct [:id]
MyApp.Queries.Users.GetUsersByOrg # defstruct [:org_id]
# Executor behaviour
MyApp.Queries.Users.Executor
@callback get_user(ops :: [{reference(), GetUser.t()}]) :: computation(...)
# Caller (suspends fiber for batching)
@spec get_user(String.t()) :: Types.computation(User.t() | nil)
def get_user(id)
# Wiring
@spec with_executor(computation(), module()) :: computation()
def with_executor(comp, executor_module)Executor Implementation
defmodule MyApp.Queries.Users.EctoExecutor do
@behaviour MyApp.Queries.Users.Executor
@impl true
def get_user(ops) do
ids = Enum.map(ops, fn {_ref, %GetUser{id: id}} -> id end) |> Enum.uniq()
Comp.bind(Reader.ask(:repo), fn repo ->
results = repo.all(User, ids)
by_id = Map.new(results, &{&1.id, &1})
Comp.pure(Map.new(ops, fn {ref, %GetUser{id: id}} -> {ref, Map.get(by_id, id)} end))
end)
end
endWiring
my_comp
|> MyApp.Queries.Users.with_executor(MyApp.Queries.Users.EctoExecutor)
|> FiberPool.with_handler()
|> Comp.run()Bulk Wiring
my_comp
|> Skuld.Query.Contract.with_executors([
{MyApp.Queries.Users, MyApp.Queries.Users.EctoExecutor},
{MyApp.Queries.Orders, MyApp.Queries.Orders.EctoExecutor}
])Bang Variant Generation
Same rules as Port.Contract:
- Auto-detect (default): If the return type contains
{:ok, T}, a bang variant is generated that unwraps{:ok, value}or dispatchesThrowon{:error, reason}. bang: true: Force standard unwrapping.bang: false: Suppress bang generation.bang: unwrap_fn: Custom unwrap function.
Summary
Functions
Define a typed fetch operation.
Deprecated: use deffetch instead.
Install multiple contract/executor pairs in one call.
Functions
Define a typed fetch operation.
Syntax
deffetch function_name(param :: type(), ...) :: return_type()
deffetch function_name(param :: type(), ...) :: return_type(), bang: optionEach deffetch declaration generates an operation struct, caller function,
executor callback, dispatch clause, and optionally a bang variant.
The bang option follows the same rules as Port.Contract.defport:
- omitted -- auto-detect: generate bang only if return type contains
{:ok, T} true-- force standard{:ok, v}/{:error, r}unwrappingfalse-- suppress bang generationunwrap_fn-- custom unwrap function
Deprecated: use deffetch instead.
defquery is a deprecated alias for deffetch. It will be removed in a future release.
@spec with_executors( Skuld.Comp.Types.computation(), [{module(), module()}] | %{required(module()) => module()} ) :: Skuld.Comp.Types.computation()
Install multiple contract/executor pairs in one call.
Accepts either a list of {contract_module, executor_module} tuples or a map.
Examples
comp
|> Skuld.Query.Contract.with_executors([
{MyApp.Queries.Users, MyApp.Queries.Users.EctoExecutor},
{MyApp.Queries.Orders, MyApp.Queries.Orders.EctoExecutor}
])
comp
|> Skuld.Query.Contract.with_executors(%{
MyApp.Queries.Users => MyApp.Queries.Users.EctoExecutor,
MyApp.Queries.Orders => MyApp.Queries.Orders.EctoExecutor
})