ReactiveDag.Node.Payload (reactive_dag v0.17.0-rc)

Copy Markdown View Source

Closes the payload loop for a resource-backed node: writes a combinator's output row into the node's OWN resource (cell.meta.resource), keyed by the cell key.

This is what makes "the resource IS the node, and its rows ARE its payload" true in the code, not just the docs. A reduce/join whose into returns a row and that omits an explicit upsert: has its row written HERE — an Ash upsert into the node's resource, with change-detection — so the common case needs no host write callback, and writing into a different resource becomes the explicit deviation (a custom upsert:), not the default.

The key attribute

The cell key (a string) maps to one resource attribute — the payload key. It defaults to :key; a resource whose primary key is named otherwise declares payload_key :flow_key in its reactive block. The row is written with that attribute set to the cell key; a :key field on the row itself is dropped (it's the coordination key, not a payload column).

Change detection

upsert/5 reads the existing row first and compares the writable attributes; it returns :changed only when a create or a real value change happened, so the drain's Op.put/parent-dirty only fires for genuine changes (a no-op recompute stays a no-op). Requires an upsert action on the resource — by default the action named :upsert with upsert?: true; override with payload_action.

Summary

Functions

RETIRE a vanished unit's row: the unit's input rows are gone, so the derived row must go too — a derived table whose whole point is that you query it cannot keep rows for units that no longer exist (a stale row is indistinguishable from a live one).

Upsert row into resource under cell_key (written to key_attr), via action. Returns :changed (created, or a writable attr differs) or :unchanged. row's :key field is dropped before writing.

Upsert an IDENTITY-KEYED row (a composite-primary-key node): the row carries its identity fields, the upsert conflicts on the primary key (Ash's default for upsert? true), and no key column exists — the cell key is the identity's serialization, derived elsewhere. Returns :changed | :unchanged with the same read-compare change detection as upsert/5.

Functions

retire(resource, key_attr, identity_fields, keys, action \\ :destroy)

@spec retire(module(), atom() | nil, [atom()] | nil, [String.t()], atom()) :: [
  String.t()
]

RETIRE a vanished unit's row: the unit's input rows are gone, so the derived row must go too — a derived table whose whole point is that you query it cannot keep rows for units that no longer exist (a stale row is indistinguishable from a live one).

keys are cell keys; for an identity-keyed node each is the identity's "|" serialization, split back into its fields. Rows already absent are skipped, so this is idempotent. Returns the keys whose row was actually destroyed.

Requires a destroy action (default :destroy); a resource without one raises with the fix, since silently keeping the row would defeat the reconcile.

upsert(resource, key_attr, cell_key, row, action \\ :upsert)

@spec upsert(module(), atom(), String.t(), map(), atom()) :: :changed | :unchanged

Upsert row into resource under cell_key (written to key_attr), via action. Returns :changed (created, or a writable attr differs) or :unchanged. row's :key field is dropped before writing.

upsert_identity(resource, identity_fields, row, action \\ :upsert)

@spec upsert_identity(module(), [atom()], map(), atom()) :: :changed | :unchanged

Upsert an IDENTITY-KEYED row (a composite-primary-key node): the row carries its identity fields, the upsert conflicts on the primary key (Ash's default for upsert? true), and no key column exists — the cell key is the identity's serialization, derived elsewhere. Returns :changed | :unchanged with the same read-compare change detection as upsert/5.