Skip to main content
The Pending<T> type system is the core innovation of ubx. It solves a fundamental problem: some values only exist after infrastructure is deployed.

The Problem

When you provision a database, you don’t know its endpoint until after pulumi up runs. But your Helm chart needs that endpoint. Traditional tools handle this externally:
terraform apply
 endpoint = "db.xxxx.eu-west-1.rds.amazonaws.com"
 manually paste into Helm values.yaml
 git commit, push, wait for ArgoCD

The Solution

ubx makes this a compile-time concern. Any value prefixed with ~ is Pending<T>:
# Resolved<string> — known at compile time
engine = "postgres"

# Pending<string> — known only after pulumi up
host = ~unit.aws_rds_instance.db.endpoint

Propagation Rules

Pending&lt;T&gt; propagates through the dependency graph:
  • If X depends on a Pending&lt;T&gt; value, X is also Pending&lt;T&gt;
  • Plain values are Resolved&lt;T&gt;
  • The compiler tracks propagation through every expression, function call, and interpolation

What Produces Pending&lt;T&gt;

SourceExample
Unit output attribute~unit.aws_rds_instance.db.endpoint
Component output~component.eks.cluster_endpoint
Data source attribute~data.aws_vpc.main.id
Remote stack output~@networking.vpc_id
secret("aws_secrets_manager", ...)AWS Secrets Manager
secret("vault", ...)HashiCorp Vault
secret("gcp_secret_manager", ...)GCP Secret Manager
secret("azure_key_vault", ...)Azure Key Vault
Ephemeral inputsinput "x" { ephemeral = true }

What is Resolved&lt;T&gt;

SourceExample
String literals"postgres"
Number literals5432
Boolean literalstrue
Input references (non-ephemeral)input.env
Local blocks (if value is resolved)local.prefix
secret("env", ...)process.env["KEY"]

Generated TypeScript

Simple reference:
host = ~unit.aws_rds_instance.db.endpoint
host: db.endpoint,   // Output<string>
String interpolation:
conn = "postgres://${~unit.aws_rds_instance.db.endpoint}:5432/app"
conn: db.endpoint.apply(e => `postgres://${e}:5432/app`),
Multiple pending values:
label = "${~unit.aws_s3_bucket_v2.a.bucket}-${~unit.aws_s3_bucket_v2.b.bucket}"
label: pulumi.all([a.bucket, b.bucket]).apply(([a, b]) => `${a}-${b}`),

In Function Calls

# Resolved — emitted inline
bucket = lower("PROD")
# → ("PROD").toLowerCase()

# Pending — wraps in .apply()
bucket = lower(~unit.aws_s3_bucket_v2.src.bucket)
# → src.bucket.apply(_dep0 => (_dep0).toLowerCase())

# Multiple pending — uses pulumi.all()
bucket = join("-", [~unit.x.y.name, ~unit.z.w.id])
# → pulumi.all([x.name, z.id]).apply(([_dep0, _dep1]) => [_dep0, _dep1].join("-"))