Hyperlinkv0.8.0-beta.28

ConfigProvider

ConfigProvider.fromDirconsteffect/ConfigProvider.ts:1166
(options?: {
  readonly rootPath?: string | undefined
  readonly preserveEmptyStrings?: boolean | undefined
}): Effect.Effect<
  ConfigProvider,
  never,
  Path_.Path | FileSystem.FileSystem
>

Creates a ConfigProvider that reads configuration from a directory tree on disk, where each file is a leaf value and each directory is a container.

When to use

Use when you expose each config key as a file under a directory, such as Kubernetes ConfigMap or Secret volume mounts.

Details

Resolution tries a regular file first and returns a Value node for non-empty trimmed file contents. If the file read fails, it tries a directory and returns a Record node with immediate child names as keys. If both fail with NotFound, it returns undefined. Other platform failures return SourceError.

Requires Path and FileSystem in the Effect context. Defaults to root path /; override with { rootPath: "/etc/config" }.

Literal empty strings are treated as missing values by default after file contents are trimmed. Pass { preserveEmptyStrings: true } to keep empty strings as explicit values. Directory listings still reflect the file names present on disk.

Example (Reading config from a directory)

import { ConfigProvider, Effect } from "effect"

const program = Effect.gen(function*() {
  const provider = yield* ConfigProvider.fromDir({
    rootPath: "/etc/myapp"
  })
  return provider
})
ConfigProvidersfromEnvfromDotEnv
export const fromDir: (options?: {
  readonly rootPath?: string | undefined
  readonly preserveEmptyStrings?: boolean | undefined
}) => Effect.Effect<
  ConfigProvider,
  never,
  Path_.Path | FileSystem.FileSystem
> = Effect.fnUntraced(function*(options) {
  const platformPath = yield* Path_.Path
  const fs = yield* FileSystem.FileSystem
  const rootPath = options?.rootPath ?? "/"
  const preserveEmptyStrings = options?.preserveEmptyStrings === true

  return make((path) => {
    const fullPath = platformPath.join(rootPath, ...path.map(String))

    // Try reading as a *file*
    const asFile = fs.readFileString(fullPath).pipe(
      Effect.map((content) => stringNode(content.trim(), preserveEmptyStrings))
    )

    // If not a file, try reading as a *directory*
    const asDirectory = fs.readDirectory(fullPath).pipe(
      Effect.map((entries) => makeRecord(new Set(entries.map((entry) => platformPath.basename(entry)))))
    )

    return asFile.pipe(
      Effect.catch((fileCause) =>
        asDirectory.pipe(
          Effect.catch((dirCause) =>
            isNotFound(fileCause) && isNotFound(dirCause)
              ? Effect.succeed(undefined)
              : Effect.fail(isNotFound(fileCause) ? dirCause : fileCause)
          )
        )
      ),
      Effect.mapError((cause: PlatformError) =>
        new SourceError({
          message: `Failed to read file at ${platformPath.join(rootPath, ...path.map(String))}`,
          cause
        })
      )
    )
  })
})