Ash DataLayer for Neo4j.
Errors (#372)
Every error the behaviour callbacks (create/update/destroy/run_query/…)
return is a typed struct — never a bare string (a string becomes
Ash.Error.Unknown.UnknownError: unclassified and only substring-matchable).
Pick by meaning:
- Server/Bolt error →
AshNeo4j.Error.Neo4j(classifies by category). - Deliberate refusal ("can't push this down / won't render it") → the
AshNeo4j.Error.Unsupported*/Requires*family (class: :invalid). - The node/edge we expected to act on wasn't there → reuse Ash's own:
Ash.Error.Changes.StaleRecord(the record is gone / a guard no longer holds) orAsh.Error.Invalid.Unavailable(a preservation guard blocked a destroy). - An internal invariant we couldn't satisfy (unexpected input shape, a
relationship/aggregate path that won't resolve) →
AshNeo4j.Error.Internal(class: :unknown).
Two standing rules: reuse an Ash error before inventing an AshNeo4j.Error.*,
and return, never raise, from the behaviour path. Internal sentinels between
helpers (e.g. {:error, :nothing_deleted} from run_expecting_deletions/2) are
atoms, not strings, and are converted to a typed error before escaping. The
contract is enforced by test/error_contract_test.exs.
Ash.Type callbacks (cast/dump/load) are a separate contract — they follow Ash's
type-error convention (message strings), not this one.
Summary
Functions
Read-time polymorphic projection (#329): for each source record, follows
chain to the reached node(s) in one query (related_nodes/4) and projects
each reached node to its concrete world via AshNeo4j.worlds/1.
Functions
@spec project_traversal(module(), [Ash.Resource.Record.t()], list()) :: %{ optional(any()) => any() }
Read-time polymorphic projection (#329): for each source record, follows
chain to the reached node(s) in one query (related_nodes/4) and projects
each reached node to its concrete world via AshNeo4j.worlds/1.
Returns %{source_pk_value => projected} where projected is the concrete
record, an AshNeo4j.Unknown (a node was reached but its labels resolve to no
loaded world), or nil (genuinely nothing reached). v1 is single-valued.