Tracked structs with identity that persists across revisions.
Rather than queries returning opaque values compared by structural equality, they create entities whose fields are individually tracked for changes. This enables finer-grained invalidation than opaque value comparison.
Defining an entity
defmodule MyLang.Function do
use Roux.Entity,
identity: [:name],
tracked: [:body, :return_type]
endThis generates a struct with all fields as enforced keys and __entity__/1
callbacks that return schema metadata.
Entity lifecycle
create/4interns the identity key and inserts or updates the entity.field/4reads a single field value.field_changed_at/4reads the revision at which a field last changed.lookup/3performs a non-interning lookup by identity key.
Field-level dependency tracking is deferred to Roux.Runtime.
Summary
Functions
Returns true if the entity has a positive reference count.
Creates or updates an entity, returning its interned ID.
Decrements the reference count for an entity by 1, clamped at 0.
Removes an entity from the ETS table.
Reads a single field value from an entity.
Returns the revision at which a field last changed.
Returns the full fields map for an entity.
Increments the reference count for an entity by 1.
Non-interning lookup of an entity by its identity key.
Returns the current reference count for an entity.
Bulk-inserts entity rows for a registered entity type.
Returns all entity rows for a registered entity type.
Types
@type entity_id() :: Roux.Intern.id()
@type field_entry() :: %{ value: term(), hash: integer(), changed_at: Roux.Revision.revision() }
Functions
@spec alive?(Roux.Database.t(), module(), entity_id()) :: boolean()
Returns true if the entity has a positive reference count.
Raises ArgumentError if the entity does not exist.
@spec create(Roux.Database.t(), module(), map(), Roux.Revision.revision()) :: entity_id()
Creates or updates an entity, returning its interned ID.
Interns the identity key from attrs to produce a stable entity_id. If
the entity is new, all fields are inserted with changed_at set to
revision. If the entity already exists, only tracked fields whose values
have actually changed get their changed_at updated.
Uses a hash pre-check (D8) to avoid structural comparison when values differ.
Raises ArgumentError if module is not registered as an entity type.
Raises KeyError if attrs is missing a required field.
@spec decrement_refcount(Roux.Database.t(), module(), entity_id()) :: non_neg_integer()
Decrements the reference count for an entity by 1, clamped at 0.
@spec delete(Roux.Database.t(), module(), entity_id()) :: :ok
Removes an entity from the ETS table.
No-op if the entity does not exist.
@spec field(Roux.Database.t(), module(), entity_id(), atom()) :: term()
Reads a single field value from an entity.
Raises ArgumentError if the entity does not exist or the field is unknown.
@spec field_changed_at(Roux.Database.t(), module(), entity_id(), atom()) :: Roux.Revision.revision()
Returns the revision at which a field last changed.
Raises ArgumentError if the entity does not exist or the field is unknown.
@spec get_fields(Roux.Database.t(), module(), entity_id()) :: {:ok, %{required(atom()) => field_entry()}} | :error
Returns the full fields map for an entity.
Returns {:ok, fields_map} if the entity exists, :error otherwise.
@spec increment_refcount(Roux.Database.t(), module(), entity_id()) :: non_neg_integer()
Increments the reference count for an entity by 1.
@spec lookup(Roux.Database.t(), module(), tuple()) :: {:ok, entity_id()} | :error
Non-interning lookup of an entity by its identity key.
Returns {:ok, entity_id} if the identity key has been interned,
:error otherwise. Does not create the identity mapping.
@spec refcount(Roux.Database.t(), module(), entity_id()) :: non_neg_integer()
Returns the current reference count for an entity.
Raises ArgumentError if the entity does not exist.
@spec restore(Roux.Database.t(), module(), list()) :: :ok
Bulk-inserts entity rows for a registered entity type.
Used by manifest restore. Registers the entity type if not already registered, then inserts all rows.
@spec snapshot(Roux.Database.t(), module()) :: list()
Returns all entity rows for a registered entity type.
Used by manifest serialization. Returns raw ETS rows as a list of
{entity_id, fields_map, refcount} tuples.