Skuld.Repo.InMemory (skuld v0.27.1)

View Source

Closed-world stateful in-memory Repo handler for tests. Recommended default.

Thin wrapper around DoubleDown.Repo.InMemory that provides skuld's effect-based with_handler/3 API. The state is the complete truth — if a record isn't in the store, it doesn't exist. This makes the adapter authoritative for all bare-schema operations without needing a fallback function.

Usage

alias Skuld.Repo

# Basic — all bare-schema reads work without fallback:
comp
|> Repo.InMemory.with_handler(Repo.InMemory.new())
|> Comp.run!()

# With seed data:
state = Repo.InMemory.new(seed: [%User{id: 1, name: "Alice"}])
comp
|> Repo.InMemory.with_handler(state)
|> Comp.run!()

Authoritative operations (bare schema queryables)

CategoryOperationsBehaviour
Writesinsert, update, delete, bang variantsStore in state
PK readsget, get!nil/raise on miss (no fallback)
Clause readsget_by, get_by!Scan and filter
Collectionall, one/one!, exists?Scan state
AggregatesaggregateCompute from state
Bulk writesinsert_all, delete_all, update_allModify state
PreloadpreloadResolve from store
Reloadreload, reload!Re-fetch from store
Transactionstransact, rollbackSnapshot + restore on rollback

Ecto.Query fallback

Operations with Ecto.Query queryables (containing where, join, select etc.) cannot be evaluated in-memory. These fall through to the fallback function, or raise with a clear error.

Extracting Final State

Use the :output option to access the final handler state:

{result, final_store} =
  comp
  |> Repo.InMemory.with_handler(Repo.InMemory.new(),
    output: fn result, state -> {result, state.handler_state} end
  )
  |> Comp.run!()

Fallback function

The optional fallback function handles operations the closed-world store cannot service (e.g. Ecto.Query queryables). It receives (operation, args, state) — the skuld-idiomatic 3-arity convention.

When to use which Repo fake

FakeStateBest for
Repo.InMemoryComplete storeAll bare-schema reads; ExMachina
Repo.OpenInMemoryPartial storePK reads in state, fallback for rest
Repo.StubNoneFire-and-forget writes, canned reads

Summary

Functions

Returns the stateful handler function.

Create a new InMemory state map.

Convert a list of structs into the nested state map for seeding.

Install the closed-world in-memory Repo handler for a computation.

Types

store()

@type store() :: DoubleDown.Repo.InMemory.store()

Functions

handler()

Returns the stateful handler function.

The function has the signature (contract, operation, args, state) -> {result, new_state}.

new(opts \\ [])

@spec new(keyword()) :: store()

Create a new InMemory state map.

Options

  • :seed - a list of structs to pre-populate the store
  • :fallback_fn - a 3-arity function (operation, args, state) -> result for operations the closed-world store cannot service (e.g. Ecto.Query).

Examples

Repo.InMemory.new()
Repo.InMemory.new(seed: [%User{id: 1, name: "Alice"}])
Repo.InMemory.new(
  seed: [%User{id: 1, name: "Alice"}],
  fallback_fn: fn
    :all, [%Ecto.Query{}], _state -> []
  end
)

seed(records)

@spec seed([struct()]) :: store()

Convert a list of structs into the nested state map for seeding.

with_handler(comp, initial_store \\ %{}, opts \\ [])

Install the closed-world in-memory Repo handler for a computation.

Options

All options from Port.with_stateful_handler/4 are supported:

  • :log — enable dispatch logging
  • :output — transform (result, %Port.State{}) -> output on scope exit.