@konfig.ts/env
@konfig.ts/env lets you declare a pod’s environment once and use that one declaration from both sides of the wire. On the manifest side, the declaration produces the Deployment’s env block (through Environment.bind in @konfig.ts/k8s). On the process side, the same declaration decodes the real environment at startup (through Environment.runtime, an Effect Config over the whole tree). Because both sides share one value, renaming DATABASE_URL in one place makes the typechecker flag every consumer that still uses the old name.
Install
bun add @konfig.ts/envUsage
The API contract from the full-stack example. Its secret entries are Secret.define values shared with the worker, and its literal entries may carry a Config schema so they decode into typed values.
export const apiEnv = Environment.define({
db: dbCreds,
s3: s3Creds,
jwt: jwtKey,
http: Environment.define({
port: Literal.define({
envName: "HTTP_PORT",
value: 8080,
schema: Config.Number("HTTP_PORT").pipe(Config.withDefault(8080))
}),
logLevel: Literal.define({
envName: "LOG_LEVEL",
value: "info",
schema: Config.String("LOG_LEVEL").pipe(Config.withDefault("info"))
})
}),
runtime: Environment.define({
nodeEnv: Literal.define({ envName: "NODE_ENV", value: "production" }),
podName: Downward.define({ envName: "POD_NAME", fieldPath: "metadata.name" })
})
})On the process side, Environment.runtime(apiEnv) yields a typed record: config.http.port is a number and config.db.password is Redacted. On the manifest side, Environment.bind({ env: apiEnv, namespace, secrets: { db: { backend } } }) returns the envVars for the container plus the manifests the chosen secret backend emits.
Surface
| Export | Purpose |
|---|---|
Secret.define | A secret contract { name, namespace, env }; bound to a backend at compose time |
Literal.define | A constant { envName, value, schema?, serialize? } baked into the manifest; serialize is required for non-primitive values |
Downward.define | A downward-API field { envName, fieldPath } |
Environment.define | A nestable bundle of the above; the single source of truth |
runtime | Decode an Environment from the process env into an EnvironmentShape |
environmentLayer | Wrap the decoded shape as a Layer for a service |
SecretSource | Plaintext sources for backends: fromConfig({ keys, envName? }), literal({ data }), fromCommand({ keys, run }) |
EnvClaim, HasEnvClaims, EntryKind, EntryMarker | Metadata types used to detect env-name collisions |
AnyEnvironment, EnvMember, MemberValue, EnvironmentShape, SecretEntry, LiteralEntry, DownwardEntry, ResolvedSecretValues | Types for generic helpers over contracts |
Errors
| Error | When it is raised |
|---|---|
EnvNameCollision | Two members of one bundle claim the same envName. Environment.define catches most collisions at compile time; the remaining cases throw this tagged error synchronously when the bundle is defined (at module load), not inside an Effect |
SecretSourceError | A SecretSource failed to resolve: the fromConfig env var is missing, the fromCommand process exited with a failure, or Sops.source could not decrypt or extract. Carries source, key, and cause |
Requirements
effect@^4.0.0-rc.111as a peer dependency (Effect 4, release-candidate line).- Depends on
@konfig.ts/core. - Runtime: Bun recommended; Node >= 23.6 works; Node 22.6 to 23.5 with
--experimental-strip-types;tsxworks.
Source and README: packages/env.