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 errorAshNeo4j.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) or Ash.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.

neo4j

Examples

neo4j do
  label :Comment
  relate [{:post, :BELONGS_TO, :outgoing, :Post}]
end

Options

NameTypeDefaultDocs
labelatomOptional node label
relatelist({atom, atom, atom, atom})[]Optional list of relationships, as tuples of {relationship_name, edge_label, edge_direction, destination_label}
guardlist({atom, atom, atom})[]Optional list of node relationships, as tuples of {edge_label, edge_direction, destination_label}
skiplist(atom)[]Optional list of attributes not to be stored directly as node properties