Threadline.ChangeDiff (Threadline v1.0.0)

Copy Markdown View Source

Threadline.ChangeDiff projects one captured row change into deterministic, JSON-friendly maps.

Authority

from_audit_change/2 is a pass-through of persisted audit_changes columns only. It does not query the database, re-apply capture-time redaction, or invent values that capture did not store. Low-information rows (masked columns, sparse changed_from) are expected and honest.

Relationship to Threadline.Export: the :export_compat format mirrors the base string-key map produced by export's internal change_map/1 ("op", "data_after", "changed_fields", "changed_from", identifiers). omits nested "transaction" and "action" objects; full export rows may still include those when preloaded. Use export for CSV/NDJSON documents; use this module when you need field_changes or the same triple without join metadata.

schema_version

The primary wire map includes "schema_version" => 1. Future additive fields may bump this integer; consumers should treat unknown keys as forward-compatible.

INSERT / UPDATE / DELETE matrix

Opdata_afterchanged_fields / changed_fromDefault field_changes
INSERTrow snapshotN/A for delta semantics[] (row snapshot is authoritative via "data_after")
UPDATErow after imagecapture-driven deltaOne entry per name in changed_fields (only), sorted by "name"
DELETEnilN/A[] — no synthetic per-column removals without a stored pre-image

before_values and per-field prior epistemics

  • "none" — changed_from is nil: integrator did not store before-values. UPDATE field entries omit "before" / "prior" keys entirely (only "after").
  • "sparse" — changed_from is a map (including %{}): prior values may be incomplete. If a column in changed_fields has no key in changed_from, the field object includes "prior_state" => "omitted" instead of inventing a scalar. When the key exists, "before" reflects JSON truth (including JSON null).

except_columns and masking

UPDATE projection iterates only changed_fields (nil-safe), not Map.keys(data_after), so columns present in data_after but excluded from capture's delta lists do not appear as false positives.

Values in data_after / changed_from pass through unchanged—including stable placeholders such as :mask where capture stored them (same class of values as export JSON).

Options

  • :format — set to :export_compat for the export-aligned flat map (see from_audit_change/2 @doc).
  • :expand_insert_fields — default false; when true on INSERT, derives "kind" => "set" field rows from keys in data_after only (presentation-only; not per-field capture facts).

Summary

Types

A JSON-bound row-change projection. The primary form includes schema_version, before_values, op, id, transaction_id, table_schema, table_name, table_pk, captured_at, data_after, and field_changes. The :export_compat form includes id, transaction_id, table_schema, table_name, op, captured_at, table_pk, data_after, changed_fields, and changed_from. Future additive keys are allowed.

Functions

Returns a deterministic string-keyed JSON projection for one audit_change.

Types

change_diff_result()

@type change_diff_result() :: Threadline.json_map()

A JSON-bound row-change projection. The primary form includes schema_version, before_values, op, id, transaction_id, table_schema, table_name, table_pk, captured_at, data_after, and field_changes. The :export_compat form includes id, transaction_id, table_schema, table_name, op, captured_at, table_pk, data_after, changed_fields, and changed_from. Future additive keys are allowed.

Functions

from_audit_change(ch, opts \\ [])

Returns a deterministic string-keyed JSON projection for one audit_change.

Primary format (default)

String keys throughout, including "schema_version", "before_values" ("none" or "sparse"), "field_changes" (lexicographically sorted by "name"), and core row identifiers compatible with integrator expectations ("op", "id", "transaction_id", "table_schema", "table_name", "table_pk", "captured_at" as ISO-8601 UTC, "data_after").

:export_compat

When opts contains format: :export_compat, returns a single flat string-key map aligned with Threadline.Export change_map/1 base fields: "id", "transaction_id", "table_schema", "table_name", "op", "captured_at", "table_pk", "data_after", "changed_fields", "changed_from". IDs are coerced with to_string/1; table_pk defaults to %{}, changed_fields to [], changed_from to %{} when nil. Nested "transaction" and "action" are not included unless future versions add optional preload parameters.

Options

  • :format — :primary or :export_compat. Defaults to :primary.
  • :expand_insert_fields — boolean. Defaults to false; adds presentation-only field rows for INSERT changes.

Unknown option keys are ignored.

Returns

  • change_diff_result() — a string-keyed map in the selected format.
  • Raises ArgumentError when the captured operation is unsupported.
  • Raises FunctionClauseError when audit_change is not an %AuditChange{}.

Results contain column values as captured; redaction is applied when triggers are generated, not on read. Authorize reads with :scope_query_fn.