Skip to content
Docs menu / core

@konfig.ts/core

@konfig.ts/core is the foundation the other konfig.ts packages are built on. It knows nothing about Kubernetes resource shapes; it provides the machinery that every package shares:

  • the manifest carrier that wraps a value to be rendered (Manifest),
  • the typed needs and provides that drive compile-time dependency tracking (Dep),
  • the module factories that give a build a namespace and a target (Module),
  • Helm chart rendering with digest verification (Helm.release),
  • deterministic YAML serialization and a structural diff,
  • and the render entrypoint the CLI calls.

Most projects work with the higher-level packages (@konfig.ts/k8s, @konfig.ts/env, @konfig.ts/argocd) and reach into core directly only for Module.*, Dep.*, and Helm.release.

Install

Terminal window
bun add @konfig.ts/core

Usage

A reusable module that creates a namespace and pulls a digest-verified Helm chart. Module.fixedNs pins the module to one namespace. Application.target (from @konfig.ts/argocd) turns the module into an Argo CD Application handle.

infra/modules/postgres.ts
export const definePostgres = Application.module({
  namespace: "app",
  annotations: Sync.wave(-1),
  build: ({ name, namespace }, opts: PostgresOpts) => {
    const ns = Namespace.make({ name: namespace })

    const release = Helm.release({
      repo: "https://charts.bitnami.com/bitnami",
      chart: "postgresql",
      releaseName: name,
      version: "16.0.0",
      digest: "sha256:483dc159c5fb377c29026d363153cc904a7f77109d524881eed64098637b9bd4",
      namespace,
      values: {
        auth: {
          database: "app",
          username: "app",
          existingSecret: "db-creds",
          secretKeys: {
            adminPasswordKey: "password",
            userPasswordKey: "password"
          }
        },
        primary: {
          persistence: {
            enabled: true,
            size: `${opts.storageGi}Gi`
          }
        }
      }
    })

    return [ns, release]
  }
})

Dependencies between modules are declared inside the build. Writing yield* Dep.Secret("ghcr-pull") records a typed need for that secret; some other module must satisfy it with Dep.provideSecret("ghcr-pull"). Nothing is checked at that point. The check happens when you compose modules at AppOfApps.fromModules; see Dependency graph.

Surface

ExportPurpose
Manifestmake, combine, concat, whenever, embedYaml: the Manifest<A> carrier and its combinators
render, renderManifest, RenderContextRun a program against a RenderContext (env, cluster, k8s version, flags) and write files
DepNeeds (Secret, SecretValues, ConfigMap, Namespace, ServiceAccount, Pvc, Image, Application, App) and provide* layers
SecretRef, ConfigMapRef, PvcRef, ServiceAccountRef, BuiltImageRefBranded reference types carried by the needs (BuiltImageRef is also a value with of); *RefName, *RefKeys helpers extract the brand parameters
ModulefixedNs, dynamicNs factories; Target, BuildContext, LiteralName types
Bundle, ComposeNon-Argo module composition (Bundle.define, Bundle.target, Bundle.fromModules; Bundle.entrypoint is a deprecated no-op wrapper) and the type-level tracking of unmet needs behind every fromModules (Compose.makeResidualEntrypoint, Compose.NoDuplicateProvides)
HelmHelm.release({ repo, chart, version, digest, values, releaseName?, namespace?, extraOpts?, minVersion? }): chart pull, SHA-256 digest verification, helm template
Yamlserialize (stable key order) and filenameFor
diffFiles, formatDiff, hasDifferences, parseYaml, parseYamlAll, deepEqual, redactStructural YAML diff with Secret data redaction
KonfigConfig, EnvEntry, OutDir, CrdConfig, HelmConfig, DiffConfig, ServicesConfig, ClusterSpec, decodeKonfigConfigSync, decodeKonfigConfigEffectSchemas and decoders for konfig.json; ResolvedKonfigConfig pairs the decoded config with its directory
ImagesConfig, EnvImages, decodeImagesSync, decodeImagesEffect, imagesFor, lookupEnv, lookupEnvEffect, requireImage, requireImageEffectSchema and lookups for images.json
boundary, makeStrictDecoder, brand, unsafeCoerceSchema decode at boundaries; annotated escape hatches
runProcessString, runProcessExit, processDetailSubprocess helpers used by Helm and the secret backends

Errors

Core exports tagged error classes from src/index.ts. The render-time ones are grouped in the AnyRenderError union, which is the error type you see on every manifest’s render and on the fromModules compositions built on top of core.

ErrorMeaningIn AnyRenderError
RenderErrorA render step failed; carries a message and an optional causeyes
EmbedYamlReadErrorManifest.embedYaml could not read the file at pathyes
BoundaryDecodeErrorData at a boundary did not match the named schemayes
HelmVersionTooLowThe installed helm is older than the required versionyes
HelmRenderErrorhelm template failed for the chart and versionyes
HelmDigestMismatchThe pulled chart’s SHA-256 digest did not match the expected oneyes
CrdExtractErrorExtracting CRDs from a chart failedyes
ProcessErrorA subprocess exited non-zero; carries the command, exit code, and stderr tailno
ImagesEnvMissingimages.json has no entry for the requested envno
ImagesAppMissingimages.json has no image for the requested app in that envno

Requirements

  • effect@^4.0.0-rc.111 as a peer dependency (Effect 4, release-candidate line).
  • @effect/platform-node and yaml are regular dependencies: render() needs the Node filesystem and subprocess services, so they install with the package.
  • Runtime: Bun recommended; Node >= 23.6 works; Node 22.6 to 23.5 with --experimental-strip-types; tsx works.

Source and README: packages/core.