> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formae.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Collection semantics

> How updateMethod and indexField control the way formae compares collection properties during apply.

When a resource property contains a collection (list of values or objects), formae needs to know how to compare the current state with the desired state. The `updateMethod` field hint controls this behavior.

## Atomic Fields

Use `updateMethod = "Atomic"` for fields that should be treated as **opaque values** rather than compared recursively. The entire field value is replaced as a single operation.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@FieldHint {
    updateMethod = "Atomic"
}
policyDocument: Dynamic?
```

**Behavior:**

* The field is compared as a whole, with no sub-field diffing
* Any change produces a single `replace` operation for the entire value
* Nested structure is irrelevant to the comparison

**Use for:** Fields where the cloud API expects the entire value as a single update, such as IAM policy documents, SQS queue policies, or any `Dynamic`-typed property with provider-specific structure that formae shouldn't decompose.

***

## The Three Collection Types

### Set (Default)

When no `updateMethod` is specified, collections are treated as **unordered sets**. Elements are compared regardless of their position in the list.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@FieldHint {}
securityGroups: Listing<String>?
```

**Behavior:**

* Order doesn't matter: `["a", "b", "c"]` equals `["c", "a", "b"]`
* Duplicates are ignored
* Adding an element that already exists results in no change
* In reconcile mode, elements not in the desired state are removed

**Use for:** Simple lists where order doesn't matter (security group IDs, availability zones, etc.)

### Array

Use `updateMethod = "Array"` when the **order of elements matters**.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@FieldHint {
    updateMethod = "Array"
}
processingSteps: Listing<String>?
```

**Behavior:**

* Position-based comparison
* `["a", "b"]` does NOT equal `["b", "a"]`
* Elements at the same index are compared directly

**Use for:** Ordered lists where sequence is significant (processing pipelines, priority lists, etc.)

### EntitySet

Use `updateMethod = "EntitySet"` with `indexField` for collections of **objects identified by a key**.

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
@FieldHint {
    updateMethod = "EntitySet"
    indexField = "Key"
}
tags: Listing<Tag>?
```

**Behavior:**

* Objects are matched by their key field (e.g., `Key` for tags)
* If an object with the same key exists, only changed properties are updated
* New keys result in add operations
* Missing keys in reconcile mode result in remove operations

**Use for:** Key-value collections like tags, environment variables, or any list of objects with a unique identifier.

## Example: AWS Tags

The most common use of EntitySet is for resource tags:

```kotlin theme={"languages":{"custom":["/languages/pkl.json"]}}
// Plain class - no FieldHints needed on tag properties
open class Tag {
    hidden key: String
    hidden value: Any

    fixed Key: String = key
    fixed Value: Any = value
}

@ResourceHint {
    type = "AWS::EC2::VPC"
    identifier = "VpcId"
}
open class Vpc extends formae.Resource {
    @FieldHint {
        updateMethod = "EntitySet"
        indexField = "Key"
    }
    tags: Listing<Tag>?
}
```

With this schema, updating a tag value results in a targeted update that only changes the value, not the entire tag.

## How It Works

When formae compares current state with desired state, it uses the `updateMethod` hint to determine the comparison strategy:

1. **No hint or Set**: Compare collections as unordered sets
2. **Array**: Compare collections position-by-position
3. **EntitySet**: Match objects by `indexField`, then compare matched objects property-by-property

## Reconcile vs Patch Mode

The apply mode significantly affects how collections are handled:

**Reconcile mode** (`--mode reconcile`, default): The desired state is the complete truth. Elements not in your forma file are **removed** from the actual resource.

**Patch mode** (`--mode patch`): Collections are **append-only**. Elements in your forma file are added if missing, but existing elements are never removed.

This append-only behavior in patch mode applies to all three collection types:

| Collection Type | Reconcile Mode                            | Patch Mode                                |
| --------------- | ----------------------------------------- | ----------------------------------------- |
| Set             | Elements not in desired state are removed | New elements added, existing preserved    |
| Array           | Elements not in desired state are removed | New elements appended, existing preserved |
| EntitySet       | Keys not in desired state are removed     | New keys added, existing keys preserved   |

**Example:** If the actual resource has tags `[A, B, C]` and your forma file specifies `[B, D]`:

* **Reconcile**: Result is `[B, D]` (A and C removed)
* **Patch**: Result is `[A, B, C, D]` (D added, nothing removed)

## Choosing the Right Semantics

| Scenario                                   | updateMethod | indexField     |
| ------------------------------------------ | ------------ | -------------- |
| List of primitive values, order irrelevant | (none)       | -              |
| List of primitive values, order matters    | `Array`      | -              |
| List of objects with unique key            | `EntitySet`  | Key field name |
| List of objects without unique key         | (none)       | -              |
| Opaque value, no sub-field diffing         | `Atomic`     | -              |

When in doubt, start with the default (Set) semantics. Only specify `Array`, `EntitySet`, or `Atomic` when you have a specific reason.
