Skuld.Query.Cache (skuld v0.3.0)

View Source

Composable caching layer for Query.Contract queries.

Provides:

  • Cross-batch result caching — identical queries across batch rounds return cached results without re-executing
  • Within-batch request deduplication — identical queries in the same batch round are sent to the executor only once, with the result fanned out to all requesting fibers

Usage

comp
|> QueryCache.with_executor(Users, Users.EctoExecutor)
|> FiberPool.with_handler()
|> Comp.run()

# Multiple contracts (shared cache scope):
comp
|> QueryCache.with_executors([
  {Users, Users.EctoExecutor},
  {Orders, Orders.EctoExecutor}
])
|> FiberPool.with_handler()
|> Comp.run()

Cache Scope

The cache is scoped per computation run. It's initialised as an empty map by Comp.scoped and cleaned up on scope exit. No TTL, no eviction.

Multiple with_executors/with_executor calls share a single cache scope. The first call creates the cache; subsequent calls reuse the existing cache and register their executors against it. Only the outermost scope performs cleanup.

Cache Key

{batch_key, op_struct} where:

  • batch_key is {ContractModule, :query_name}
  • op_struct is the operation struct (e.g. %Users.GetUser{id: "123"})

Structural equality on Elixir structs provides correct comparison.

Per-Query Opt-Out

Queries declared with cache: false bypass caching entirely:

deffetch get_random_user() :: User.t(), cache: false

Error Handling

Executor failures are not cached. When an executor produces a Throw, the error propagates normally and the cache is not polluted — subsequent requests for the same query go to the executor again.

Summary

Functions

Install a caching-wrapped executor for a single contract.

Install caching-wrapped executors for multiple contracts.

Functions

with_executor(comp, contract_module, executor_module)

Install a caching-wrapped executor for a single contract.

Shorthand for with_executors(comp, [{contract_module, executor_module}]).

with_executors(comp, contract_executor_pairs)

Install caching-wrapped executors for multiple contracts.

Initialises a scoped cache and registers caching-wrapped executors for all query operations in each contract. Queries with cacheable: false (from deffetch ..., cache: false) are registered with raw dispatch that bypasses the cache entirely.

Example

comp
|> QueryCache.with_executors([
  {Users, Users.EctoExecutor},
  {Orders, Orders.EctoExecutor}
])
|> FiberPool.with_handler()
|> Comp.run()