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

# component block

> Use reusable components from the Strata registry or local paths, or author components natively in .iac.

The `component` block references a reusable Pulumi ComponentResource — from the Strata registry, a local npm package, or a local `.iac` directory. Equivalent to Terraform's `module` block.

## Syntax

```hcl theme={"theme":"css-variables"}
component "name" {
  source  = "registry.ubiquex.io/eks-platform"
  version = "2.1.0"
  # component inputs follow
}
```

* **`name`** — unique name within the stack
* **`source`** — registry path or local directory (required)
* **`version`** — semver (registry only, optional, defaults to `"latest"`)

## Registry Component

```hcl theme={"theme":"css-variables"}
component "platform" {
  source     = "registry.ubiquex.io/eks-platform"
  version    = "2.1.0"
  region     = "eu-west-1"
  node_count = 3
}

output "cluster_endpoint" {
  value = ~component.platform.cluster_endpoint
}
```

## Using a Local Component

A short reference form — point `source` at a directory, pass inputs as block attributes:

```hcl theme={"theme":"css-variables"}
component "vpc" {
  source = "./components/vpc"
  cidr   = "10.0.0.0/16"
  region = input.region
}

unit "aws_ec2_subnet" "app" {
  vpc_id = ~component.vpc.vpc_id
}
```

## Local .iac Components

When `source` starts with `./` or `../`, ubx does **not** look for an npm package. Instead it reads the `.iac` files in that directory, compiles them, and emits an inline `class` in `index.ts` — no TypeScript knowledge required.

### How the compiler detects a local component

At codegen time, ubx resolves `source`:

* Starts with `./` or `../` → **local `.iac` component** — compiled inline.
* `registry.ubiquex.io/…` or any other non-relative string → **registry component** — emits `import { ClassName } from "npm-package"`.

The check is purely on the `source` string. No filesystem probe is needed to decide the path.

### Writing a component (`./components/rds/main.iac`)

A local component is a plain `.iac` file that uses `input`, `unit`, `local`, and `output` blocks. The compiler processes it independently from the calling stack.

```hcl theme={"theme":"css-variables"}
# Inputs become typed constructor args.
# Add `default` to make them optional at the call site.
input "engine" {
  type    = "string"
  default = "postgres"
}

input "db_name" {
  type    = "string"
  default = "app"
}

# Units are scoped under the component instance name at deploy time.
unit "aws_rds_instance" "db" {
  engine              = input.engine
  db_name             = input.db_name
  username            = "admin"
  skip_final_snapshot = true
}

# Outputs become public typed properties on the generated class.
output "endpoint" {
  value = ~unit.aws_rds_instance.db.endpoint
}

output "db_name" {
  value = ~unit.aws_rds_instance.db.db_name
}
```

### Supported blocks

| Block    | Behaviour inside a local component                                                                                                                          |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input`  | Becomes a typed arg in `args: { ... }`. `type` sets the TypeScript type; `default` makes it optional (`?`).                                                 |
| `unit`   | Compiled as a scoped resource. The Pulumi name is `` `${pulumiName}-unitname` `` to prevent collisions across instances. `parent: this` is always injected. |
| `local`  | Emitted as a `const` in the constructor body.                                                                                                               |
| `output` | Becomes a `public readonly name: pulumi.Output<string>` property. Accessible via `~component.name.outputname` in the caller.                                |

### How input blocks become constructor args

Each `input` block maps to a field in the TypeScript args object:

| `.iac`                                         | TypeScript args field       | Notes                                     |
| ---------------------------------------------- | --------------------------- | ----------------------------------------- |
| `input "x" { type = "string" }`                | `x: string`                 | Required — no default                     |
| `input "x" { type = "string"; default = "v" }` | `x?: string`                | Optional — default applied in constructor |
| `input "x" { type = "number" }`                | `x: number`                 |                                           |
| `input "x" { type = "bool" }`                  | `x: boolean`                |                                           |
| `input "x" { type = "list(string)" }`          | `x: string[]`               |                                           |
| `input "x" { type = "map(string)" }`           | `x: Record<string, string>` |                                           |

Inside the constructor body, each input becomes a `const`:

```typescript theme={"theme":"css-variables"}
const engine = args.engine ?? "postgres";   // optional input
const db_name = args.db_name ?? "app";
```

The constructor parameter is always named `pulumiName` (not `name`) so that an input named `"name"` never shadows it.

### How output blocks become typed component outputs

Each `output` block produces:

1. A `public readonly` property declaration on the class.
2. An assignment in the constructor: `this.outputName = pulumi.output(value)`.
3. An entry in `this.registerOutputs({...})`.

```typescript theme={"theme":"css-variables"}
public readonly endpoint: pulumi.Output<string>;
public readonly db_name: pulumi.Output<string>;

// inside constructor:
this.endpoint = pulumi.output(db.endpoint);
this.db_name  = pulumi.output(db.db_name);
this.registerOutputs({ endpoint: this.endpoint, db_name: this.db_name });
```

From the caller, these are always `Pending<T>` — they resolve at `ubx apply` time.

### Full worked example: RDS component consumed by ArgoCD sync

This example provisions a database and injects its connection details into a GitOps sync target without any manual copy-paste.

**`./components/rds/main.iac`**

```hcl theme={"theme":"css-variables"}
input "engine" {
  type    = "string"
  default = "postgres"
}

input "db_name" {
  type    = "string"
  default = "app"
}

unit "aws_rds_instance" "db" {
  engine              = input.engine
  db_name             = input.db_name
  username            = "admin"
  skip_final_snapshot = true
}

output "endpoint" {
  value = ~unit.aws_rds_instance.db.endpoint
}

output "db_name" {
  value = ~unit.aws_rds_instance.db.db_name
}
```

**`stack.iac`**

```hcl theme={"theme":"css-variables"}
component "database" {
  source  = "./components/rds"
  engine  = "postgres"
  db_name = "myapp"
}

sync "argocd" "backend" {
  repo    = "https://github.com/my-org/backend"
  path    = "k8s/"
  cluster = ~component.platform.cluster_endpoint
  values  = {
    DB_HOST = ~component.database.endpoint
    DB_NAME = ~component.database.db_name
  }
}
```

Both `~component.database.endpoint` and `~component.database.db_name` are `Pending<T>` — ubx wraps the ArgoCD sync values in `pulumi.all([...]).apply(...)` so the real hostnames are only substituted after the database is live. No manual secret management, no copy-paste.

### What gets generated in `index.ts`

```typescript theme={"theme":"css-variables"}
// Auto-generated from ./components/rds
class RdsComponent extends pulumi.ComponentResource {
  public readonly endpoint: pulumi.Output<string>;
  public readonly db_name: pulumi.Output<string>;

  constructor(pulumiName: string, args: { engine?: string; db_name?: string }, opts?: pulumi.ComponentResourceOptions) {
    super("ubx:component:Rds", pulumiName, {}, opts);

    const engine  = args.engine  ?? "postgres";
    const db_name = args.db_name ?? "app";

    // unit "aws_rds_instance" "db"
    const db = new aws.rds.Instance(`${pulumiName}-db`, {
        engine:             engine,
        dbName:             db_name,
        username:           "admin",
        skipFinalSnapshot:  true,
    }, { parent: this });

    this.endpoint = pulumi.output(db.endpoint);
    this.db_name  = pulumi.output(db.dbName);
    this.registerOutputs({ endpoint: this.endpoint, db_name: this.db_name });
  }
}

// component (local component) "database"
const database = new RdsComponent("database", {
    engine:  "postgres",
    dbName:  "myapp",
});
```

Notable properties:

* **`pulumiName`** — Pulumi stack resource name; `` `${pulumiName}-db` `` gives every instance a unique name even when the same component is used twice.
* **`parent: this`** — internal resources are children of the component in the Pulumi DAG.
* **No `import` statement** — the class is inline; no npm package is needed.
* **Class emitted once** — if `./components/rds` is referenced by multiple `component` blocks, the class definition is de-duplicated in `index.ts`.

### Error cases

| Situation                                                                                           | Error                                                                                                            |
| --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `source = "./components/does-not-exist"`                                                            | `local component "./components/does-not-exist": no such file or directory`                                       |
| `source = "./components/empty"` (no `.iac` files)                                                   | `local component "./components/empty": no .iac files found`                                                      |
| Unknown resource type inside component (`unit "aws_vpc"`)                                           | `local component "…": unknown resource type "aws_vpc": not in the ubx provider registry`                         |
| `~component.mydb.nonexistent` in calling stack                                                      | Type checker error: `unknown reference: component.mydb.nonexistent`                                              |
| `for_each` or `count` on a `unit` inside the component                                              | `local component "…": unit "name": for_each and count are not supported inside local components (v1 limitation)` |
| Component A's `.iac` references `component "b" { source = "./b" }` which references `component "a"` | Parse/codegen error on the nested component; circular component references are not supported in v1               |

## Referencing Outputs

Component outputs are always `Pending<T>`. They can be used anywhere a `Pending<T>` value is accepted:

```hcl theme={"theme":"css-variables"}
deploy "helm" "backend" {
  chart  = "my-backend"
  values = {
    cluster = ~component.platform.cluster_endpoint
  }
}
```

## for\_each

```hcl theme={"theme":"css-variables"}
component "regional_cluster" {
  for_each = { "eu-west-1" = "primary", "us-east-1" = "secondary" }
  source   = "registry.ubiquex.io/eks-platform"
  version  = "1.0.0"
  region   = each.key
  role     = each.value
}
```

## when

```hcl theme={"theme":"css-variables"}
input "enable_monitoring" { default = "true" }

component "monitoring" {
  source  = "registry.ubiquex.io/datadog-agent"
  version = "3.0.0"
  when    = input.enable_monitoring == "true"
}
```

Resolved `when` wraps the constructor in `if (cond) { ... }`. When the condition references a `Pending<T>` value, ubx wraps it in `.apply((_when) => { if (_when) { ... } })` instead.

## count

Create multiple instances of the same component from a single block. Each instance is keyed by its index.

```hcl theme={"theme":"css-variables"}
component "cluster" {
  source  = "registry.ubiquex.io/eks-platform"
  version = "2.1.0"
  count   = 3
  region  = "eu-west-1"
}
```

Generated TypeScript:

```typescript theme={"theme":"css-variables"}
const cluster: {[key: string]: EksPlatform} = {};
[...Array(3)].forEach((_, idx) => {
    cluster[String(idx)] = new EksPlatform(`cluster-${idx}`, {
        region: "eu-west-1",
    } as any);
});
```

Use `count.index` to differentiate instances:

```hcl theme={"theme":"css-variables"}
component "cluster" {
  source      = "registry.ubiquex.io/eks-platform"
  count       = 3
  cluster_tag = "cluster-${count.index}"
}
```

`count` and `for_each` are mutually exclusive on the same block.

## Source Formats

| Format            | Example                                             |
| ----------------- | --------------------------------------------------- |
| Short form        | `eks-platform` → `registry.ubiquex.io/eks-platform` |
| Full registry     | `registry.ubiquex.io/package-name`                  |
| Local `.iac` path | `./components/vpc`                                  |
| Other registry    | `my-registry.io/package-name`                       |

## Generated TypeScript (registry component)

```typescript theme={"theme":"css-variables"}
// ubx:dep @ubiquex/eks-platform 2.1.0
import { EksPlatform } from "@ubiquex/eks-platform";

const platform = new EksPlatform("platform", {
    region: "eu-west-1",
    nodeCount: 3,
});
```
