Engine
The KykuEngine is the core orchestrator that drives the entire infrastructure lifecycle. It coordinates the dependency graph, diff engine, planner, state manager, crypto, and provider calls.
Lifecycle
Section titled “Lifecycle”Config → Inject Auto-Subnets → Build DAG → Load State → Query Cloud → Diff → Plan → Apply → Persist StatePhase 1: Load Config
Section titled “Phase 1: Load Config”const engine = new KykuEngine({ stateDir: './.kyku' });const plan = await engine.plan(config, { provider: awsProvider });The config is a TypeScript module that exports resource instances. The engine receives an array of BaseResource objects.
Phase 2: Inject Auto-Subnets
Section titled “Phase 2: Inject Auto-Subnets”Before building the graph, the engine scans for Vpc resources with distributeAcrossAzs > 0 and injects synthetic Subnet resources:
// Vpc with distributeAcrossAzs: 2// Injects: vpc-main-subnet-public-0, vpc-main-subnet-public-1// vpc-main-subnet-private-0, vpc-main-subnet-private-1These subnets appear in the dependency graph, state file, and outputs like any other resource.
Phase 3: Build Dependency Graph
Section titled “Phase 3: Build Dependency Graph”The GraphBuilder scans every resource property for BaseResource instances. A reference like vm.network = myVpc creates a Vm → Vpc edge. The graph assigns levels via topological sort.
Phase 4: Load State
Section titled “Phase 4: Load State”Reads .kyku/state.<env>.json for last known cloud provider IDs and configs. If no state file exists, all resources are considered new.
Phase 5: Discover Cloud State
Section titled “Phase 5: Discover Cloud State”For resources with a providerId in state, calls provider.readState(resource, providerId) to detect drift. Resources without a providerId skip this step.
Phase 6: Diff
Section titled “Phase 6: Diff”The DiffEngine compares desired config (from resourceToConfig()) vs actual config (from readState). Changes are categorized:
| Change Type | Description |
|---|---|
create |
Resource exists in config but not in state |
update |
Config differs from cloud state |
replace |
Immutable property changed (destroy + recreate) |
destroy |
Resource exists in state but not in config |
no-op |
Config matches cloud state |
Phase 7: Plan
Section titled “Phase 7: Plan”The planner produces an ordered list of changes grouped by graph level. Resources at the same level run in parallel.
Phase 8: Apply
Section titled “Phase 8: Apply”Executes changes in dependency order, reporting progress in real-time. Each change calls the appropriate provider method:
create→provider.createResource(resource)update→provider.updateResource(resource)replace→provider.destroyResource()+provider.createResource()destroy→provider.destroyResource()
Phase 9: Persist State
Section titled “Phase 9: Persist State”After applying, the new provider IDs, configs, and outputs are written to the state file. A timestamped backup of the previous state is saved before every apply.
Engine API
Section titled “Engine API”class KykuEngine { constructor(options: EngineOptions);
// Lifecycle methods plan(config: ResourceConfig[], options: PlanOptions): Promise<Plan>; apply(config: ResourceConfig[], options: ApplyOptions, plan?: Plan): Promise<ApplyResult>; destroy(options: DestroyOptions): Promise<DestroyResult>; refresh(options: RefreshOptions): Promise<RefreshResult>;
// State access readState(env?: string): Promise<StateFile | null>; getOutputs(options: OutputOptions): Promise<Record<string, any>>;
// Graph buildGraph(config: ResourceConfig[]): Graph;}EngineOptions
Section titled “EngineOptions”| Option | Type | Default | Description |
|---|---|---|---|
stateDir |
string |
./.kyku |
Directory for local state files |
backend |
StateBackendConfig |
— | Optional remote backend (s3, gcs, spaces, or http) |
deployPrefix |
string |
random 8-char hex | Prefix for resource names |
PlanOptions
Section titled “PlanOptions”| Option | Type | Default | Description |
|---|---|---|---|
provider |
Provider |
required | Provider implementation |
env |
string |
'' |
Environment workspace |
ApplyOptions
Section titled “ApplyOptions”| Option | Type | Default | Description |
|---|---|---|---|
provider |
Provider |
required | Provider implementation |
autoApprove |
boolean |
false |
Skip confirmation prompt |
parallelism |
number |
10 |
Max concurrent operations |
env |
string |
'' |
Environment workspace |
Deploy Prefix
Section titled “Deploy Prefix”Every deployment gets a unique 8-char hex prefix, auto-generated on first plan/apply and stored in StateFile.metadata.prefix. Resource names are wrapped with a deep Proxy that prepends prefix- to name for new resources while keeping existing resource names unchanged.
State Locking
Section titled “State Locking”Local backend: file lock at {stateDir}/.kyku.lock with PID + timestamp. S3 backend: lock object at ${key}.lock in the configured bucket. Locks are auto-released on success/error via try/finally. A 60s heartbeat refreshes the timestamp; stale locks (>5 minutes) are broken only when a re-read still shows the same token. Default 30s timeout for acquiring lock.
Error Handling
Section titled “Error Handling”| Phase | Behavior |
|---|---|
| Validation | Config errors surface immediately with file/line info |
| Plan | Cloud API errors during discovery abort the plan |
| Apply | On failure, partial state is preserved. Resources that succeeded remain applied. |
| Destroy | Retry with backoff (5 attempts, 2^n × 2s). Verify deletion with poll (10 attempts, 2s). |
Progress Display
Section titled “Progress Display”Kyku uses ANSI cursor control for in-place terminal updates:
\x1b[1A(cursor up) +\x1b[2K(clear line) for redraw- Tracks exact
linesPrintedcount - Spinner animation at ~80ms via
setInterval - No leading/trailing newlines in render to avoid scroll accumulation