# Changelog

## v0.5.0 — Render-time transforms + relation attributes

### Added

- **`AttrEngine.Transform`** — render-time value transforms declared via
  `data_config["transform"]` (a string or a list/pipeline). Built-ins: `redact`,
  `redact_secret`. Custom transforms via the `:transforms` application config
  (modules implementing `apply/2`, or 1-/2-arity functions). Transforms now run
  automatically inside `Render.Block.render/4` and `Cascade.build_context/3`.
- **`AttrEngine.Relation`** — relation resolution runtime for reference attributes
  (`data_config.type = "relation"`). Stores an opaque ref in the ASD while the value
  lives in an external store reached through pluggable source adapters
  (`:relation_sources` config). `ref_only` mode keeps raw values out of the ASD;
  ships read (`resolve_relations/3`), write (`process_writes/2`), and cleanup
  (`cleanup/2`) paths.

### Notes

- No schema or migration changes. Backward compatible.

## v0.3.0 — BREAKING: ASA primary key restructure

### What changed

`AttrEngine.Schema.AttributeSetAttribute` (ASA) now uses a surrogate
`bigserial` primary key (`{:id, :id, autogenerate: true}`) instead of
a composite PK on `(attribute_set_id, attribute_id)`.

This allows a single Attribute to appear multiple times within the same
AttributeSet, distinguished by `handle` — enabling structural primitives
(string, integer, currency, etc.) to be reused across named fields.

### New constraints

- **`handle` is required** — `validate_required([:handle])` in changeset,
  `NOT NULL` and `CHECK (handle <> '')` at the database level
- **`UNIQUE(attribute_set_id, handle)`** — each handle appears at most
  once per set (index name: `attribute_set_attributes_unique_set_handle`)

### PK column shape

- Column: `id`, type: `bigserial` (Postgres `bigint` with `GENERATED BY DEFAULT AS IDENTITY`)
- Matches `AttrEngine.Schema.Attribute` and `AttrEngine.Schema.AttributeSet`

### Consumer migration recipes

**Category (a): Consumers with composite PK**

> Before adding NOT NULL / CHECK constraints on `handle`, either backfill
> handles for existing ASA rows or `DELETE FROM attribute_set_attributes
> WHERE handle IS NULL`. For staging consumers where seeds re-create the
> rows with handles, the delete is the right call.

```elixir
def up do
  # 1. Clean up rows without handles (seeds will re-create them)
  execute "DELETE FROM attribute_set_attributes WHERE handle IS NULL OR handle = ''"

  # 2. Drop composite PK
  execute "ALTER TABLE attribute_set_attributes DROP CONSTRAINT attribute_set_attributes_pkey"

  # 3. Add surrogate PK
  execute "ALTER TABLE attribute_set_attributes ADD COLUMN id bigserial PRIMARY KEY"

  # 4. Add unique index on (set, handle)
  create unique_index(:attribute_set_attributes, [:attribute_set_id, :handle],
    name: :attribute_set_attributes_unique_set_handle
  )

  # 5. Add NOT NULL + non-empty constraint on handle
  execute """
  ALTER TABLE attribute_set_attributes
    ALTER COLUMN handle SET NOT NULL,
    ADD CONSTRAINT asa_handle_non_empty CHECK (handle <> '')
  """
end
```

**Category (b): Consumers with surrogate id + old composite UNIQUE**

```elixir
def up do
  # Drop old unique constraint if present
  drop_if_exists unique_index(:attribute_set_attributes, [:attribute_set_id, :attribute_id])

  # Add new unique on (set, handle)
  create unique_index(:attribute_set_attributes, [:attribute_set_id, :handle],
    name: :attribute_set_attributes_unique_set_handle
  )
end
```

**Category (c): Consumers already on this shape**

No migration needed.

## v0.2.2

- Thread `:prefix` through all Repo calls for multi-tenant support.

## v0.2.1

- AttributeSet: conditional `@derive Flop.Schema`, `setup_changeset/3`,
  schema default `"Standalone"`, easy_mode alignment across schema modules.

## v0.2.0

- Attribute: `data_config`/`ui_config` converted to `embeds_one` with
  `Attribute.DataConfig` and `Attribute.UIConfig` embedded schemas.
- Added `draft_config`, virtual fields (`sample`, `data_preview`,
  `ui_preview`, `ui_state`), conditional `@derive Flop.Schema`.
- Aligned easy_mode across schema modules. Added identifier locking.
- Cascade: `normalize_config` handles embedded structs transparently.
