Roux.Entity (roux v0.2.2)

Copy Markdown View Source

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]
end

This generates a struct with all fields as enforced keys and __entity__/1 callbacks that return schema metadata.

Entity lifecycle

  1. create/4 interns the identity key and inserts or updates the entity.
  2. field/4 reads a single field value.
  3. field_changed_at/4 reads the revision at which a field last changed.
  4. lookup/3 performs 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

entity_id()

@type entity_id() :: Roux.Intern.id()

field_entry()

@type field_entry() :: %{
  value: term(),
  hash: integer(),
  changed_at: Roux.Revision.revision()
}

Functions

alive?(db, module, entity_id)

@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.

create(db, module, attrs, revision)

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.

decrement_refcount(db, module, entity_id)

@spec decrement_refcount(Roux.Database.t(), module(), entity_id()) ::
  non_neg_integer()

Decrements the reference count for an entity by 1, clamped at 0.

delete(db, module, entity_id)

@spec delete(Roux.Database.t(), module(), entity_id()) :: :ok

Removes an entity from the ETS table.

No-op if the entity does not exist.

field(db, module, entity_id, field_name)

@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.

field_changed_at(db, module, entity_id, field_name)

@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.

get_fields(db, module, entity_id)

@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.

increment_refcount(db, module, entity_id)

@spec increment_refcount(Roux.Database.t(), module(), entity_id()) ::
  non_neg_integer()

Increments the reference count for an entity by 1.

lookup(db, module, identity_key)

@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.

refcount(db, module, entity_id)

@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.

restore(db, module, rows)

@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.

snapshot(db, module)

@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.