Alchemy Integration
TypeKro integrates with Alchemy to deploy your TypeKro resources through Alchemy's declarative, stateful runtime — so they get per-resource state, dependency-ordered deployment, idempotent reconcile, and reverse-topological teardown alongside the rest of your Alchemy-managed infrastructure.
Alchemy v2. This integration targets Alchemy v2 (the Effect-based
2.0.0-betaline). It is declarative: TypeKro emits resource declarations, and your Alchemy runtime materializes them as Alchemy resources. The older v1 imperative model (graph.deployWithAlchemy(...), thealchemyScopefactory option, the globalalchemy(...)scope-driven deploy) has been removed.
What is Alchemy?
Alchemy is an infrastructure-as-TypeScript tool with a stateful runtime: it tracks every resource it manages in a state store, deploys them in dependency order, reconciles them idempotently, and tears them down in reverse-topological order. TypeKro's integration represents each TypeKro KRO resource as an Alchemy resource, so a TypeKro deployment becomes a first-class part of an Alchemy stack.
The v2 model
TypeKro exports a declarative Alchemy v2 integration from typekro/alchemy:
KroResource— a declarative Alchemy v2Resourcerepresenting one TypeKro KRO resource. That single resource can be an RGD (ResourceGraphDefinition), a CR instance, or a direct-mode Kubernetes resource.kroProvider— the AlchemyProvider(an EffectLayer) that backsKroResource. Merge it into your Alchemy runtime's providers.materializeAlchemyResources(KroResource, declarations)— a helper that returns an Effect. Run it inside an AlchemyStackbody to instantiate a list of declarations asKroResources. It wires each declaration'sdependsOninto AlchemyOutputdependencies, so resources deploy in dependency order and direct-mode cross-resource references resolve against their dependencies' live state.AlchemyResourceDeclaration—{ id: string; props; dependsOn: string[] }. This is whattoAlchemyResourcesreturns.
Both DirectResourceFactory and KroResourceFactory expose:
toAlchemyResources(spec, opts?): Promise<AlchemyResourceDeclaration[]>It emits the resource(s) as declarations:
- KRO mode → a declaration for each discovered singleton owner (its own RGD + CR instance), then the composition's RGD, then its CR instance. The instance
dependsOnthe RGD and any singleton instances, so a deployment with no singletons is just two declarations (RGD + instance), and one that depends on shared singletons emits those first. - Direct mode → one declaration per resolved Kubernetes resource, topologically ordered, with
dependsOntaken from the resource dependency graph.
The result is the same per-resource state granularity as the old v1 integration — one Alchemy state entry per resource, reverse-topological teardown, idempotent reconcile — but expressed declaratively.
Canonical Usage
This is the verified pattern (see test/integration/alchemy/direct-fan-out-e2e.test.ts):
import { Cel, simple, toResourceGraph } from 'typekro';
import { KroResource, kroProvider, materializeAlchemyResources } from 'typekro/alchemy';
// + your Alchemy v2 runtime — its `providers` must include `kroProvider`, plus a state backend.
// 1. Build the factory as usual.
const factory = await graph.factory('direct', { namespace: 'apps', waitForReady: true });
// 2. Emit per-resource declarations (topologically ordered, dependsOn wired).
const decls = await factory.toAlchemyResources(spec);
// 3. Inside an Alchemy Stack body (an Effect generator), with kroProvider in the runtime:
const outputs = yield* materializeAlchemyResources(KroResource, decls);Once deployed, each TypeKro resource is a per-resource entry in Alchemy's state: Alchemy reconciles them idempotently and tears them down in reverse-topological order.
The Alchemy runtime itself — how you construct the runtime, which providers and state backend you supply — is part of your own Alchemy v2 setup and is not provided by TypeKro. The only TypeKro requirement is that kroProvider is merged into the runtime's providers, and that a state backend is configured. Everything above the toAlchemyResources / materializeAlchemyResources calls is TypeKro-side and is what this page documents.
Direct mode: per-resource fan-out
In direct mode, toAlchemyResources returns one declaration per resolved Kubernetes resource, ordered so that dependencies come first:
import { Cel, simple, toResourceGraph } from 'typekro';
import { KroResource, kroProvider, materializeAlchemyResources } from 'typekro/alchemy';
import { type } from 'arktype';
const graph = toResourceGraph(
{
name: 'fanoutapp',
apiVersion: 'v1alpha1',
kind: 'FanoutApp',
spec: type({ name: 'string', image: 'string', replicas: 'number%1' }),
status: type({ readyReplicas: 'number%1' }),
},
(schema) => {
const deployment = simple.Deployment({
name: schema.spec.name,
image: schema.spec.image,
replicas: schema.spec.replicas,
id: 'appDeployment',
});
return {
deployment,
// Reads the Deployment's LIVE status → a genuine cross-resource dependency.
config: simple.ConfigMap({
name: Cel.template('%s-cfg', schema.spec.name),
data: { readyReplicas: Cel.template('%s', deployment.status.readyReplicas) },
id: 'appConfig',
}),
};
},
(_schema, resources) => ({ readyReplicas: resources.deployment?.status.readyReplicas })
);
const factory = await graph.factory('direct', { namespace: 'apps', waitForReady: true });
// One declaration per resource; the ConfigMap dependsOn the Deployment.
const decls = await factory.toAlchemyResources({ name: 'fanapp', image: 'nginx', replicas: 1 });
// In the Stack body, with kroProvider in the runtime's providers:
const outputs = yield* materializeAlchemyResources(KroResource, decls);Because the ConfigMap reads the Deployment's live status.readyReplicas, its declaration dependsOn the Deployment. Alchemy therefore deploys the Deployment first, captures its live status, and only then deploys the ConfigMap — resolving the cross-resource reference against real cluster state.
Kro mode: RGD + instance (+ singleton owners)
In KRO mode, toAlchemyResources returns the composition's RGD and a CR instance that dependsOn it — preceded by a declaration for each singleton owner the composition depends on (each its own RGD + instance). A composition with no singletons therefore yields exactly two declarations:
const factory = await graph.factory('kro', { namespace: 'apps' });
const decls = await factory.toAlchemyResources({ name: 'web', image: 'nginx', replicas: 3 });
// (any singleton owners' RGD + instance come first, deps-first)
// decls[-2] → the composition's RGD
// decls[-1] → its CR instance (dependsOn the RGD + any singleton instances)
const outputs = yield* materializeAlchemyResources(KroResource, decls);Alchemy applies singleton owners and the RGD first, then the instance, and the Kro controller reconciles the rest at runtime — each piece tracked as its own state entry. Singleton owners use deterministic ids, so a singleton shared across compositions is deduplicated to one state entry. Singleton spec-drift protection is enforced at reconcile time: deploying a singleton identity whose live spec fingerprint differs from the one being applied fails rather than silently clobbering the shared owner.
Security: kubeconfig in Alchemy state
toAlchemyResources captures the factory's kubeconfig into each declaration's kubeConfigOptions, and Alchemy persists that to its state store — so that a later state-driven delete can reconnect to the cluster to remove the resource.
This means: if the kubeconfig uses static credentials (token, certData, keyData), those credentials land in Alchemy's state store. To avoid persisting long-lived secrets:
- Prefer re-derived auth — an
execcredential plugin (e.g.aws eks get-token) or anauthProvider— so each operation mints fresh, short-lived credentials instead of storing static ones. - Use a secured state backend for your Alchemy runtime regardless, since the state store may hold connection details.
Without Alchemy
If you don't need Alchemy's state and lifecycle management, TypeKro deploys standalone — just call the factory directly:
const factory = await graph.factory('direct', { namespace: 'default' });
await factory.deploy({ name: 'app', image: 'nginx' });Next Steps
- Deployment Modes - Direct vs Kro deployment
- Custom Integrations - Create custom factories
- Examples - See more patterns