Skip to content

Composition APIs

TypeKro provides two composition APIs for creating typed resource graphs. Both produce the same TypedResourceGraph output and support the same deployment modes.

Choosing Your API

kubernetesCompositiontoResourceGraph
StyleImperative — single function creates resources and returns statusDeclarative — separate resource builder and status builder
Status expressionsJavaScript expressions auto-converted to CELExplicit Cel.expr() / Cel.template() / resource references
Cross-resource refsAutomatic via magic proxy (deploy.status.readyReplicas)Explicit via resources.deploy.status.readyReplicas
Nested compositionsCall compositions as functions inside the composition fnSame
Best forMost applications — natural JS, less boilerplateAdvanced users who want full CEL control or complex status logic

Decision Flowchart

Rule of thumb: Start with kubernetesComposition. Switch to toResourceGraph if you need explicit Cel.expr() or find the single-function pattern limiting.


kubernetesComposition API

The primary API for creating typed resource graphs with full type safety and automatic CEL generation.

Syntax

typescript
function kubernetesComposition<TSpec, TStatus>(
  definition: {
    name: string;
    apiVersion: string;
    kind: string;
    spec: ArkTypeSchema<TSpec>;
    status: ArkTypeSchema<TStatus>;
  },
  compositionFunction: (spec: MagicProxy<TSpec>) => TStatus
): ResourceGraph<TSpec, TStatus>

Parameters

definition

PropertyTypeDescription
namestringUnique name for the resource graph
apiVersionstringKubernetes API version (e.g., example.com/v1alpha1)
kindstringKubernetes resource kind (e.g., WebApp)
specArkTypeSchemaArkType schema defining the spec structure
statusArkTypeSchemaArkType schema defining the status structure

compositionFunction

Function that receives a magic proxy of the spec and:

  1. Creates resources (automatically registered)
  2. Returns status object with JavaScript expressions (auto-converted to CEL)

Returns

A ResourceGraph instance with methods:

MethodDescription
factory(mode, options)Create deployment factory ('direct' or 'kro')
toYaml(spec?)Generate YAML representation

Basic Example

typescript
import { type } from 'arktype';
import { kubernetesComposition } from 'typekro';
import { Deployment, Service } from 'typekro/simple';

const webApp = kubernetesComposition(
  {
    name: 'webapp',
    apiVersion: 'example.com/v1alpha1',
    kind: 'WebApp',
    spec: type({ name: 'string', image: 'string', replicas: 'number' }),
    status: type({ ready: 'boolean', url: 'string' })
  },
  (spec) => {
    const deploy = Deployment({
      id: 'deploy',
      name: spec.name,
      image: spec.image,
      replicas: spec.replicas
    });
    
    const svc = Service({
      id: 'svc',
      name: `${spec.name}-svc`,
      selector: { app: spec.name },
      ports: [{ port: 80 }]
    });

    return {
      ready: deploy.status.readyReplicas >= spec.replicas,
      url: `http://${svc.status.clusterIP}`
    };
  }
);

Cross-Resource References

Resources can reference each other's fields:

typescript
import { kubernetesComposition } from 'typekro';
import { Deployment, Service } from 'typekro/simple';

const app = kubernetesComposition(definition, (spec) => {
  const db = Deployment({ id: 'db', name: 'db', image: 'postgres' });
  const dbService = Service({
    id: 'dbSvc',
    name: 'db-svc',
    selector: { app: 'db' },
    ports: [{ port: 5432 }]
  });
  
  const api = Deployment({
    id: 'api',
    name: 'api',
    image: spec.image,
    env: {
      DATABASE_HOST: dbService.status.clusterIP,  // Reference service's status
      DATABASE_PORT: '5432'
    }
  });

  return { ready: api.status.readyReplicas > 0 };
});

Status Expressions

JavaScript expressions in the return object are automatically converted to CEL:

typescript
return {
  // Boolean expressions
  ready: deploy.status.readyReplicas >= spec.replicas,
  
  // String templates
  url: `https://${ingress.status.loadBalancer.ingress[0].hostname}`,
  
  // Conditionals
  phase: deploy.status.readyReplicas > 0 ? 'running' : 'pending',
  
  // Use Cel.expr for conditional status — JavaScript operators like || are not supported in status builders
  endpoint: Cel.expr<string>('has(svc.status.loadBalancer) && size(svc.status.loadBalancer.ingress) > 0 ? svc.status.loadBalancer.ingress[0].ip : "pending"')
};

Deployment

typescript
// Direct deployment (immediate, no Kro controller)
const factory = webApp.factory('direct', { namespace: 'production' });
await factory.deploy({ name: 'my-app', image: 'nginx', replicas: 3 });

// Kro deployment (creates ResourceGraphDefinition)
const kroFactory = webApp.factory('kro', { namespace: 'production' });
await kroFactory.deploy({ name: 'my-app', image: 'nginx', replicas: 3 });

// Generate YAML for GitOps
const yaml = webApp.toYaml({ name: 'my-app', image: 'nginx', replicas: 3 });

Factory Options

typescript
interface FactoryOptions {
  namespace?: string;           // Target namespace
  timeout?: number;             // Deployment timeout (ms)
  waitForReady?: boolean;       // Wait for resources to be ready
  
  // Event monitoring - stream control plane logs
  eventMonitoring?: {
    enabled?: boolean;
    eventTypes?: ('Normal' | 'Warning' | 'Error')[];
    includeChildResources?: boolean;
  };
  
  // Debug logging
  debugLogging?: {
    enabled?: boolean;
    statusPolling?: boolean;
    readinessEvaluation?: boolean;
    verboseMode?: boolean;
  };
  
  // Progress callback for custom handling
  progressCallback?: (event: DeploymentEvent) => void;
}

The id Parameter

Every resource needs an id for cross-resource references:

typescript
import { Deployment } from 'typekro/simple';

const deploy = Deployment({
  id: 'webDeploy',  // Required for references
  name: spec.name,
  image: spec.image
});

// Now you can reference it
return { replicas: deploy.status.readyReplicas };

toResourceGraph API

The declarative API for creating typed resource graphs with explicit CEL expressions and separated concerns.

Syntax

typescript
function toResourceGraph<TSpec, TStatus>(
  definition: {
    name: string;
    apiVersion: string;
    kind: string;
    spec: ArkTypeSchema<TSpec>;
    status: ArkTypeSchema<TStatus>;
  },
  resourceBuilder: (schema: SchemaProxy<TSpec, TStatus>) => Record<string, KubernetesResource>,
  statusBuilder: (
    schema: SchemaProxy<TSpec, TStatus>,
    resources: EnhancedResources
  ) => TStatus
): TypedResourceGraph<TSpec, TStatus>

Parameters

definition

Same as kubernetesComposition — name, apiVersion, kind, spec schema, status schema.

resourceBuilder

Function that receives the schema proxy and returns a record of named resources. Each key becomes the resource identifier used in the status builder.

statusBuilder

Function that receives both the schema proxy and the deployed resources, returning the status object. Use Cel.expr(), Cel.template(), or direct resource references.

Basic Example

typescript
import { type } from 'arktype';
import { toResourceGraph, Cel } from 'typekro';
import { createDeployment, createService } from 'typekro';

const webApp = toResourceGraph(
  {
    name: 'webapp',
    apiVersion: 'example.com/v1alpha1',
    kind: 'WebApp',
    spec: type({ name: 'string', image: 'string', replicas: 'number' }),
    status: type({ ready: 'boolean', url: 'string' })
  },
  // Resource builder — creates resources keyed by identifier
  (schema) => ({
    deploy: createDeployment({
      name: schema.spec.name,
      image: schema.spec.image,
      replicas: schema.spec.replicas,
    }),
    svc: createService({
      name: schema.spec.name,
      selector: { app: schema.spec.name },
      ports: [{ port: 80 }],
    }),
  }),
  // Status builder — maps resource status to composition status
  (schema, resources) => ({
    ready: Cel.expr<boolean>(resources.deploy.status.readyReplicas, ' > 0'),
    url: Cel.template('http://%s', resources.svc.status.clusterIP),
  })
);

When to Use toResourceGraph

  • You want explicit CEL expressions rather than auto-converted JavaScript
  • You prefer separated concerns — resource creation distinct from status mapping
  • You need complex CEL patterns that JavaScript auto-conversion doesn't support
  • You're building reusable library compositions where explicitness aids maintainability

Next Steps

Released under the Apache 2.0 License.