/** * Descriptions of configuration values that can be read from a * `ConfigProvider`. A `Config` explains which keys to read, how to decode * and validate them, and how to combine defaults, fallbacks, nested paths, and * multiple settings. Configs are also Effects, so they can be yielded in * `Effect.gen` after a provider has been supplied. * * @stability stable * @since 4.0.0 */ import * as Cause from "./Cause.ts" import type { Path, SourceError } from "./ConfigProvider.ts" import * as ConfigProvider from "./ConfigProvider.ts" import * as Effect from "./Effect.ts" import * as Effectable from "./Effectable.ts" import { dual, memoize } from "./Function.ts" import * as InternalRecord from "./internal/record.ts" import * as LogLevel_ from "./LogLevel.ts" import * as Option from "./Option.ts" import * as Predicate from "./Predicate.ts" import * as Rec from "./Record.ts" import * as Result from "./Result.ts" import * as Schema from "./Schema.ts" import * as SchemaAST from "./SchemaAST.ts" import * as SchemaGetter from "./SchemaGetter.ts" import * as SchemaIssue from "./SchemaIssue.ts" import * as SchemaParser from "./SchemaParser.ts" import * as SchemaTransformation from "./SchemaTransformation.ts" const TypeId = "~effect/Config" /** * Returns `true` if `u` is a `Config` instance. * * **When to use** * * Use when you need to distinguish a `Config` from an unknown value before * calling `.parse` or {@link unwrap}. * * **Example** (Checking Config values) * * ```ts import.meta.vitest * import { Config } from "effect" * * Config.isConfig(Config.String("HOST")) // => true * Config.isConfig("not a config") // => false * ``` * * @stability stable * @category guards * @since 2.0.0 */ export const isConfig = (u: unknown): u is Config => Predicate.hasProperty(u, TypeId) /** * Represents the error type produced when config loading or validation fails. * * **When to use** * * Use when you need to inspect config loading or validation failures. * * **Details** * * Wraps either: * - A `SourceError` — the provider could not read data (I/O failure). * - A `SchemaError` — the data was found but did not match the schema * (wrong type, out of range, missing key, etc.). * * @see {@link orElse} – recover from a ConfigError * @see {@link withDefault} – provide a fallback when relevant input is absent * * @stability stable * @category errors * @since 4.0.0 */ export class ConfigError { readonly _tag = "ConfigError" readonly name: string = "ConfigError" readonly cause: SourceError | Schema.SchemaError constructor(cause: SourceError | Schema.SchemaError) { this.cause = cause } get message() { return this.cause.toString() } toString() { return `ConfigError(${this.message})` } } /** * A recipe for extracting a typed value `T` from a `ConfigProvider`. * * **When to use** * * Use to describe typed configuration that can be parsed from a provider or * yielded inside `Effect.gen`. * * **Details** * * Key members: * - `parse(provider)` – runs the config against a specific provider. * - Yieldable – can be yielded inside `Effect.gen`, which automatically * resolves the current `ConfigProvider` from the context. * - Pipeable – supports `.pipe(Config.map(...))` etc. * * @see {@link schema} – the main way to create a Config * * @stability stable * @category models * @since 2.0.0 */ export interface Config extends Effect.Effect { readonly [TypeId]: typeof TypeId readonly parse: (provider: ConfigProvider.ConfigProvider) => Effect.Effect } // Result failure represents recoverable absence; Effect failure represents an // invalid value or a source error. Keep them separate until parsing completes. type Resolution = Result.Result type Evaluator = ( provider: ConfigProvider.ConfigProvider, pathPrefix: Path ) => Effect.Effect, ConfigError> interface ConfigImpl extends Config { readonly evaluator: Evaluator } const Proto = { ...Effectable.Prototype>({ label: "Config", evaluate(fiber) { return this.parse(fiber.getRef(ConfigProvider.ConfigProvider)) } }), [TypeId]: TypeId, toJSON(this: Config) { return { _id: "Config" } } } function make( evaluator: Evaluator ): Config { const self = Object.create(Proto) self.evaluator = evaluator self.parse = (provider: ConfigProvider.ConfigProvider) => Effect.flatMapEager(evaluator(provider, []), Effect.fromResult) return self } const evaluateAt = ( self: Config, provider: ConfigProvider.ConfigProvider, pathPrefix: Path ): Effect.Effect, ConfigError> => (self as ConfigImpl).evaluator(provider, pathPrefix) /** * Transforms the parsed value of a config with a pure function. * * **When to use** * * Use when you need to transform a parsed config value with a function that * cannot fail. * * **Example** (Uppercasing a string config) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const upper = Config.String("name").pipe( * Config.map((s) => s.toUpperCase()) * ) * * const provider = ConfigProvider.fromUnknown({ name: "alice" }) * Effect.runSync(upper.parse(provider)) // => "ALICE" * ``` * * @see {@link mapEffect} – when the transformation can fail * * @stability stable * @category mapping * @since 2.0.0 */ export const map: { /** * Transforms the parsed value of a config with a pure function. * * **When to use** * * Use when you need to transform a parsed config value with a function that * cannot fail. * * **Example** (Uppercasing a string config) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const upper = Config.String("name").pipe( * Config.map((s) => s.toUpperCase()) * ) * * const provider = ConfigProvider.fromUnknown({ name: "alice" }) * Effect.runSync(upper.parse(provider)) // => "ALICE" * ``` * * @see {@link mapEffect} – when the transformation can fail * * @stability stable * @category mapping * @since 2.0.0 */ (f: (a: A) => B): (self: Config) => Config /** * Transforms the parsed value of a config with a pure function. * * **When to use** * * Use when you need to transform a parsed config value with a function that * cannot fail. * * **Example** (Uppercasing a string config) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const upper = Config.String("name").pipe( * Config.map((s) => s.toUpperCase()) * ) * * const provider = ConfigProvider.fromUnknown({ name: "alice" }) * Effect.runSync(upper.parse(provider)) // => "ALICE" * ``` * * @see {@link mapEffect} – when the transformation can fail * * @stability stable * @category mapping * @since 2.0.0 */ (self: Config, f: (a: A) => B): Config } = dual(2, (self: Config, f: (a: A) => B): Config => { return make((provider, pathPrefix) => Effect.map(evaluateAt(self, provider, pathPrefix), Result.map(f))) }) /** * Lets you sequence multiple configs that depend on each other or branch to * multiple different configs depending on the parent. * * **When to use** * * Use when you want to maintain 2 sets of env vars with different names, and switch * between whole sets based on a single dedicated env var. Or when you want to * provide a variety of environment variable names (`PORT`, `BACKEND_PORT`, * `API_PORT`) in order of preference for a specific configuration option, such * as the listening port. * * **Example** (fallback configs and branching) * * ```ts import.meta.vitest * import { Config, Option } from "effect" * * const EnvVar = (name: string) => * Config.Literals([ * ...["prod", "production", "PROD", "PRODUCTION"] as const, * ...["dev", "development", "DEV", "DEVELOPMENT"] as const * ], name) * * const withAbsenceFallback = (fallback: Config.Config) => (self: Config.Config): Config.Config => * Config.flatMap( * Config.option(self), * Option.match({ * onNone: () => fallback, * onSome: Config.succeed * }) * ) * * // const ENV: Config.Config<"dev" | "prod"> * const ENV = EnvVar("ENV").pipe( * withAbsenceFallback(EnvVar("NODE_ENV")), * Config.map((fuzzyEnv) => * fuzzyEnv.toLowerCase().startsWith("dev") ? "dev" : "prod"), * Config.withDefault('dev') * ) * * // const hostConfig: Config.Config * const hostConfig = ENV.pipe( * Config.flatMap((env) => * env === "dev" * // dev is very forgiving * ? Config.NonEmptyString("DEV_HOST").pipe( * Config.orElse(() => Config.NonEmptyString("HOST")), * Config.orElse(() => Config.succeed("localhost")) * ) * // prod is much stricter * : Config.NonEmptyString("PROD_HOST").pipe( * withAbsenceFallback(Config.NonEmptyString("HOST")) * ) * ) * ) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ export const flatMap: { /** * Lets you sequence multiple configs that depend on each other or branch to * multiple different configs depending on the parent. * * **When to use** * * Use when you want to maintain 2 sets of env vars with different names, and switch * between whole sets based on a single dedicated env var. Or when you want to * provide a variety of environment variable names (`PORT`, `BACKEND_PORT`, * `API_PORT`) in order of preference for a specific configuration option, such * as the listening port. * * **Example** (fallback configs and branching) * * ```ts import.meta.vitest * import { Config, Option } from "effect" * * const EnvVar = (name: string) => * Config.Literals([ * ...["prod", "production", "PROD", "PRODUCTION"] as const, * ...["dev", "development", "DEV", "DEVELOPMENT"] as const * ], name) * * const withAbsenceFallback = (fallback: Config.Config) => (self: Config.Config): Config.Config => * Config.flatMap( * Config.option(self), * Option.match({ * onNone: () => fallback, * onSome: Config.succeed * }) * ) * * // const ENV: Config.Config<"dev" | "prod"> * const ENV = EnvVar("ENV").pipe( * withAbsenceFallback(EnvVar("NODE_ENV")), * Config.map((fuzzyEnv) => * fuzzyEnv.toLowerCase().startsWith("dev") ? "dev" : "prod"), * Config.withDefault('dev') * ) * * // const hostConfig: Config.Config * const hostConfig = ENV.pipe( * Config.flatMap((env) => * env === "dev" * // dev is very forgiving * ? Config.NonEmptyString("DEV_HOST").pipe( * Config.orElse(() => Config.NonEmptyString("HOST")), * Config.orElse(() => Config.succeed("localhost")) * ) * // prod is much stricter * : Config.NonEmptyString("PROD_HOST").pipe( * withAbsenceFallback(Config.NonEmptyString("HOST")) * ) * ) * ) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ (f: (a: A) => Config): (self: Config) => Config /** * Lets you sequence multiple configs that depend on each other or branch to * multiple different configs depending on the parent. * * **When to use** * * Use when you want to maintain 2 sets of env vars with different names, and switch * between whole sets based on a single dedicated env var. Or when you want to * provide a variety of environment variable names (`PORT`, `BACKEND_PORT`, * `API_PORT`) in order of preference for a specific configuration option, such * as the listening port. * * **Example** (fallback configs and branching) * * ```ts import.meta.vitest * import { Config, Option } from "effect" * * const EnvVar = (name: string) => * Config.Literals([ * ...["prod", "production", "PROD", "PRODUCTION"] as const, * ...["dev", "development", "DEV", "DEVELOPMENT"] as const * ], name) * * const withAbsenceFallback = (fallback: Config.Config) => (self: Config.Config): Config.Config => * Config.flatMap( * Config.option(self), * Option.match({ * onNone: () => fallback, * onSome: Config.succeed * }) * ) * * // const ENV: Config.Config<"dev" | "prod"> * const ENV = EnvVar("ENV").pipe( * withAbsenceFallback(EnvVar("NODE_ENV")), * Config.map((fuzzyEnv) => * fuzzyEnv.toLowerCase().startsWith("dev") ? "dev" : "prod"), * Config.withDefault('dev') * ) * * // const hostConfig: Config.Config * const hostConfig = ENV.pipe( * Config.flatMap((env) => * env === "dev" * // dev is very forgiving * ? Config.NonEmptyString("DEV_HOST").pipe( * Config.orElse(() => Config.NonEmptyString("HOST")), * Config.orElse(() => Config.succeed("localhost")) * ) * // prod is much stricter * : Config.NonEmptyString("PROD_HOST").pipe( * withAbsenceFallback(Config.NonEmptyString("HOST")) * ) * ) * ) * ``` * * @stability stable * @category mapping * @since 4.0.0 */ (self: Config, f: (a: A) => Config): Config } = dual(2, (self: Config, f: (a: A) => Config): Config => { return make((provider, pathPrefix) => Effect.flatMap( evaluateAt(self, provider, pathPrefix), Result.match({ onSuccess: (success) => evaluateAt(f(success), provider, pathPrefix), onFailure: (error) => Effect.succeed(Result.fail(error)) }) ) ) }) /** * Transforms the parsed value with a function that may fail. * * **When to use** * * Use when you need to transform a parsed config value with a function that can * produce a `ConfigError` (e.g. parsing a URL, checking a range). * * **Example** (Wrapping a value in an effectful transformation) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const trimmed = Config.String("name").pipe( * Config.mapEffect((s) => Effect.succeed(s.trim())) * ) * const provider = ConfigProvider.fromUnknown({ name: " Alice " }) * Effect.runSync(trimmed.parse(provider)) // => "Alice" * ``` * * @see {@link map} – when the transformation cannot fail * * @stability stable * @category mapping * @since 2.0.0 */ export const mapEffect: { /** * Transforms the parsed value with a function that may fail. * * **When to use** * * Use when you need to transform a parsed config value with a function that can * produce a `ConfigError` (e.g. parsing a URL, checking a range). * * **Example** (Wrapping a value in an effectful transformation) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const trimmed = Config.String("name").pipe( * Config.mapEffect((s) => Effect.succeed(s.trim())) * ) * const provider = ConfigProvider.fromUnknown({ name: " Alice " }) * Effect.runSync(trimmed.parse(provider)) // => "Alice" * ``` * * @see {@link map} – when the transformation cannot fail * * @stability stable * @category mapping * @since 2.0.0 */ (f: (a: A) => Effect.Effect): (self: Config) => Config /** * Transforms the parsed value with a function that may fail. * * **When to use** * * Use when you need to transform a parsed config value with a function that can * produce a `ConfigError` (e.g. parsing a URL, checking a range). * * **Example** (Wrapping a value in an effectful transformation) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const trimmed = Config.String("name").pipe( * Config.mapEffect((s) => Effect.succeed(s.trim())) * ) * const provider = ConfigProvider.fromUnknown({ name: " Alice " }) * Effect.runSync(trimmed.parse(provider)) // => "Alice" * ``` * * @see {@link map} – when the transformation cannot fail * * @stability stable * @category mapping * @since 2.0.0 */ (self: Config, f: (a: A) => Effect.Effect): Config } = dual(2, (self: Config, f: (a: A) => Effect.Effect): Config => { return make((provider, pathPrefix) => Effect.flatMap( evaluateAt(self, provider, pathPrefix), (resolution): Effect.Effect, ConfigError> => Result.isSuccess(resolution) ? Effect.mapEager(f(resolution.success), Result.succeed) : Effect.succeed(Result.fail(resolution.failure)) ) ) }) /** * Provides a fallback config when parsing fails with a `ConfigError`. * * **When to use** * * Use when you need to try an alternative config source after the primary one * fails. * * **Details** * * Unlike {@link withDefault}, this handles both semantic absence and **all** * `ConfigError`s. The fallback function receives the error and returns a new * `Config`. * * **Gotchas** * * The fallback's result replaces the original failure. If the fallback is * absent, an outer {@link withDefault} or {@link option} can recover it. If the * fallback fails, only its error propagates. Defects and interruptions never * reach the fallback; they propagate unchanged. * * **Example** (Trying another port before using a default) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const port = Config.Int("PORT").pipe( * Config.orElse(() => Config.Int("BACKUP_PORT")), * Config.withDefault(3000) * ) * const provider = ConfigProvider.fromUnknown({ PORT: "invalid", BACKUP_PORT: "8080" }) * await Effect.runPromise(port.parse(provider)) // => 8080 * * const missingBackup = ConfigProvider.fromUnknown({ PORT: "invalid" }) * await Effect.runPromise(port.parse(missingBackup)) // => 3000 * ``` * * @see {@link withDefault} – fallback only on semantic absence * * @stability stable * @category combinators * @since 2.0.0 */ export const orElse: { /** * Provides a fallback config when parsing fails with a `ConfigError`. * * **When to use** * * Use when you need to try an alternative config source after the primary one * fails. * * **Details** * * Unlike {@link withDefault}, this handles both semantic absence and **all** * `ConfigError`s. The fallback function receives the error and returns a new * `Config`. * * **Gotchas** * * The fallback's result replaces the original failure. If the fallback is * absent, an outer {@link withDefault} or {@link option} can recover it. If the * fallback fails, only its error propagates. Defects and interruptions never * reach the fallback; they propagate unchanged. * * **Example** (Trying another port before using a default) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const port = Config.Int("PORT").pipe( * Config.orElse(() => Config.Int("BACKUP_PORT")), * Config.withDefault(3000) * ) * const provider = ConfigProvider.fromUnknown({ PORT: "invalid", BACKUP_PORT: "8080" }) * await Effect.runPromise(port.parse(provider)) // => 8080 * * const missingBackup = ConfigProvider.fromUnknown({ PORT: "invalid" }) * await Effect.runPromise(port.parse(missingBackup)) // => 3000 * ``` * * @see {@link withDefault} – fallback only on semantic absence * * @stability stable * @category combinators * @since 2.0.0 */ (that: (error: ConfigError) => Config): (self: Config) => Config /** * Provides a fallback config when parsing fails with a `ConfigError`. * * **When to use** * * Use when you need to try an alternative config source after the primary one * fails. * * **Details** * * Unlike {@link withDefault}, this handles both semantic absence and **all** * `ConfigError`s. The fallback function receives the error and returns a new * `Config`. * * **Gotchas** * * The fallback's result replaces the original failure. If the fallback is * absent, an outer {@link withDefault} or {@link option} can recover it. If the * fallback fails, only its error propagates. Defects and interruptions never * reach the fallback; they propagate unchanged. * * **Example** (Trying another port before using a default) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const port = Config.Int("PORT").pipe( * Config.orElse(() => Config.Int("BACKUP_PORT")), * Config.withDefault(3000) * ) * const provider = ConfigProvider.fromUnknown({ PORT: "invalid", BACKUP_PORT: "8080" }) * await Effect.runPromise(port.parse(provider)) // => 8080 * * const missingBackup = ConfigProvider.fromUnknown({ PORT: "invalid" }) * await Effect.runPromise(port.parse(missingBackup)) // => 3000 * ``` * * @see {@link withDefault} – fallback only on semantic absence * * @stability stable * @category combinators * @since 2.0.0 */ (self: Config, that: (error: ConfigError) => Config): Config } = dual(2, (self: Config, that: (error: ConfigError) => Config): Config => { return make((provider, pathPrefix) => Effect.matchCauseEffect(evaluateAt(self, provider, pathPrefix), { // Only plain failures reach the fallback; defects and interruptions propagate unchanged. onFailure: (cause) => { const error = Cause.findErrorOption(cause) return Option.isSome(error) && cause.reasons.every(Cause.isFailReason) ? evaluateAt(that(error.value), provider, pathPrefix) : Effect.failCause(cause) }, onSuccess: (resolution): Effect.Effect, ConfigError> => Result.isFailure(resolution) ? evaluateAt(that(resolution.failure), provider, pathPrefix) : Effect.succeed(resolution) }) ) }) /** * Combines multiple configs into a single config that parses all of them. * * **When to use** * * Use when you need to group related configs into a tuple or named struct. * * **Details** * * Accepts a tuple (preserves positions), an iterable, or a record of configs. * Returns a config whose parsed value mirrors the input shape. * * A combined config is absent when any child is absent and no child fails. * Validation and source errors propagate even when another child is absent. * An outer {@link withDefault} replaces the entire absent group, while * {@link option} returns `None`. Apply defaults to individual children to * preserve the values of other children. * * Unlike a `Schema.Struct` passed to {@link schema}, `all` combines independent * lookups. A struct validates an existing object and fails when required fields * are missing; `all` can recover missing children through a group default. * * **Example** (Defaulting an incomplete config group) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ * host: Config.String("host"), * port: Config.Number("port") * }).pipe(Config.withDefault({ host: "localhost", port: 5432 })) * * const provider = ConfigProvider.fromUnknown({ host: "db.internal", port: 6000 }) * await Effect.runPromise(dbConfig.parse(provider)) // => { host: "db.internal", port: 6000 } * * const missingPort = ConfigProvider.fromUnknown({ host: "db.internal" }) * await Effect.runPromise(dbConfig.parse(missingPort)) // => { host: "localhost", port: 5432 } * ``` * * @stability stable * @category combinators * @since 2.0.0 */ export function all> | Record>>( arg: Arg ): Config< [Arg] extends [ReadonlyArray>] ? { -readonly [K in keyof Arg]: [Arg[K]] extends [Config] ? A : never } : [Arg] extends [Iterable>] ? Array : [Arg] extends [Record>] ? { -readonly [K in keyof Arg]: [Arg[K]] extends [Config] ? A : never } : never > { const configs: Array> | Record> = globalThis.Array.isArray(arg) ? arg : Symbol.iterator in arg ? [...arg as any] : arg return make((provider, pathPrefix) => globalThis.Array.isArray(configs) ? Effect.mapEager( Effect.all(configs.map((config) => evaluateAt(config, provider, pathPrefix))), Result.all ) : Effect.mapEager( Effect.all(Rec.map(configs, (config) => evaluateAt(config, provider, pathPrefix))), Result.all ) ) as any } /** * Provides a fallback value when the config is absent. * * **When to use** * * Use when you need to make a config key optional with a sensible default. * * **Gotchas** * * Validation and source errors still propagate. For an {@link all} group, any * absent child causes the default to replace the entire group unless another * child fails. Apply defaults to individual children to preserve other values. * * A schema that successfully decodes absent input keeps its decoded value * instead of using the default. Schema configs first represent a missing or * incompatible provider shape as `undefined`; the default is used only when * the schema rejects that value and no relevant input was found. * * **Example** (Defaulting a missing port) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const port = Config.Number("port").pipe(Config.withDefault(3000)) * * const provider = ConfigProvider.fromUnknown({}) * Effect.runSync(port.parse(provider)) // => 3000 * ``` * * @see {@link option} – returns `Option` instead of a default value * @see {@link orElse} – catches all errors, not just absent input * * @stability stable * @category combinators * @since 2.0.0 */ export const withDefault: { /** * Provides a fallback value when the config is absent. * * **When to use** * * Use when you need to make a config key optional with a sensible default. * * **Gotchas** * * Validation and source errors still propagate. For an {@link all} group, any * absent child causes the default to replace the entire group unless another * child fails. Apply defaults to individual children to preserve other values. * * A schema that successfully decodes absent input keeps its decoded value * instead of using the default. Schema configs first represent a missing or * incompatible provider shape as `undefined`; the default is used only when * the schema rejects that value and no relevant input was found. * * **Example** (Defaulting a missing port) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const port = Config.Number("port").pipe(Config.withDefault(3000)) * * const provider = ConfigProvider.fromUnknown({}) * Effect.runSync(port.parse(provider)) // => 3000 * ``` * * @see {@link option} – returns `Option` instead of a default value * @see {@link orElse} – catches all errors, not just absent input * * @stability stable * @category combinators * @since 2.0.0 */ (defaultValue: A2): (self: Config) => Config /** * Provides a fallback value when the config is absent. * * **When to use** * * Use when you need to make a config key optional with a sensible default. * * **Gotchas** * * Validation and source errors still propagate. For an {@link all} group, any * absent child causes the default to replace the entire group unless another * child fails. Apply defaults to individual children to preserve other values. * * A schema that successfully decodes absent input keeps its decoded value * instead of using the default. Schema configs first represent a missing or * incompatible provider shape as `undefined`; the default is used only when * the schema rejects that value and no relevant input was found. * * **Example** (Defaulting a missing port) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const port = Config.Number("port").pipe(Config.withDefault(3000)) * * const provider = ConfigProvider.fromUnknown({}) * Effect.runSync(port.parse(provider)) // => 3000 * ``` * * @see {@link option} – returns `Option` instead of a default value * @see {@link orElse} – catches all errors, not just absent input * * @stability stable * @category combinators * @since 2.0.0 */ (self: Config, defaultValue: A2): Config } = dual(2, (self: Config, defaultValue: A2): Config => { return make((provider, pathPrefix) => Effect.mapEager( evaluateAt(self, provider, pathPrefix), (resolution) => Result.isFailure(resolution) ? Result.succeed(defaultValue) : resolution ) ) }) /** * Makes a config optional: returns `Some(value)` on success and `None` when the * config is absent. * * **When to use** * * Use when you need to handle a config key that may or may not be present. * * **Gotchas** * * Validation and source errors still propagate. For an {@link all} group, any * absent child produces `None` unless another child fails. Successful values * are always wrapped in `Some`, including `undefined` when the schema * explicitly accepts it. Schema configs first represent a missing or * incompatible provider shape as `undefined`; `None` is returned only when the * schema rejects that value and no relevant input was found. * * **Example** (Reading optional config) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Option } from "effect" * * const maybePort = Config.option(Config.Number("port")) * * const provider = ConfigProvider.fromUnknown({}) * Effect.runSync(maybePort.parse(provider)) // => Option.none() * ``` * * @see {@link withDefault} – provide a concrete fallback value instead * * @stability stable * @category combinators * @since 2.0.0 */ export const option = (self: Config): Config> => self.pipe(map(Option.some), withDefault(Option.none())) /** * Extracts the successfully parsed value type from a `Config`. * * **When to use** * * Use to derive the parsed value type from an existing `Config` value when * declaring reusable config-driven types. * * @see {@link Config} for the config type whose parsed value is extracted * @see {@link Effect.Success} for extracting the success type from any `Effect` * * @stability stable * @category utility types * @since 2.5.0 */ export type Success = [T] extends [Config] ? A : never /** * Utility type that recursively replaces primitives with `Config` in a nested * structure. * * **When to use** * * Use when typing the input of {@link unwrap} so callers can pass either a `Config` * or a record of `Config`s. * * **Details** * * `Config.Wrap<{ key: string }>` becomes `{ key: Config } | Config<{ key: string }>` * * @see {@link unwrap} – construct a `Config` from a `Wrap` * * @stability stable * @category utility types * @since 2.0.0 */ export type Wrap = [NonNullable] extends [infer T] ? [IsPlainObject] extends [true] ? | { readonly [K in keyof A]: Wrap } | Config : Config : Config type IsPlainObject = [A] extends [Record] ? [keyof A] extends [never] ? false : [keyof A] extends [string] ? true : false : false /** * Constructs a `Config` from a value matching `Wrap`. * * **When to use** * * Use when accepting config from callers who may pass either a single `Config` or a * record of individual `Config`s. * * **Details** * * If the input is already a `Config`, it is returned as-is. Otherwise, each * key is recursively unwrapped and combined. * * **Example** (Unwrapping a record of configs) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * interface Options { * key: string * } * * const makeConfig = (config: Config.Wrap): Config.Config => * Config.unwrap(config) * * const config = makeConfig({ key: Config.String("key") }) * const provider = ConfigProvider.fromUnknown({ key: "value" }) * Effect.runSync(config.parse(provider)) // => { key: "value" } * ``` * * @see {@link Wrap} – the utility type accepted by this function * * @stability stable * @category converting * @since 2.0.0 */ export const unwrap = (wrapped: Wrap): Config => { if (isConfig(wrapped)) return wrapped return all(Rec.map(wrapped as Record>, (config) => unwrap(config))) as Config } // ----------------------------------------------------------------------------- // schema // ----------------------------------------------------------------------------- interface ConfigCursor { readonly provider: ConfigProvider.ConfigProvider readonly path: Path readonly node: ConfigProvider.Node | undefined readonly toString: () => string } const isSourceError = (u: unknown): u is ConfigProvider.SourceError => Predicate.isTagged(u, "SourceError") const cursorToString = (): string => "" // Rewrites each reason of a failure individually, so unrelated defects and // interruptions survive the conversion. const mapReasons = (f: (reason: Cause.Reason) => Cause.Reason) => (self: Effect.Effect): Effect.Effect => Effect.catchCause(self, (cause) => Effect.failCause(Cause.fromReasons(cause.reasons.map(f)))) const loadCursor: ( provider: ConfigProvider.ConfigProvider, path: Path ) => Effect.Effect = (provider, path) => provider.load(path).pipe( // Source errors travel as defects so they bypass schema issue recovery; `schema` converts them back. mapReasons((reason) => Cause.isFailReason(reason) ? Cause.makeDieReason(reason.error).annotate(Cause.reasonAnnotations(reason)) : reason ), Effect.mapEager((node) => ({ provider, path, node, toString: cursorToString })) ) const loadChildCursor = (cursor: ConfigCursor, segment: string | number): Effect.Effect => loadCursor(cursor.provider, [...cursor.path, segment]) const decodeFromCursor = ( ast: SchemaAST.AST, decode: (cursor: ConfigCursor) => Effect.Effect ): SchemaAST.AST => SchemaAST.decodeTo( SchemaAST.unknown, ast, new SchemaTransformation.Transformation( SchemaGetter.transformEffect((input: unknown) => decode(input as ConfigCursor)), SchemaGetter.passthrough() ) ) const isScalarInput = (ast: SchemaAST.AST): boolean => { switch (ast._tag) { case "Union": return ast.types.every(isScalarInput) case "Objects": case "Arrays": case "Suspend": return false default: return true } } const hasProviderInput = ( ast: SchemaAST.AST, node: ConfigProvider.Node | undefined ): boolean => { switch (ast._tag) { case "Objects": return node?._tag === "Record" case "Arrays": return node?._tag === "Array" case "Union": return ast.types.some((ast) => hasProviderInput(ast, node)) case "Suspend": return hasProviderInput(ast.thunk(), node) default: return node?.value !== undefined } } const toConfigCursorAST = memoize((root: SchemaAST.AST): SchemaAST.AST => { const seen = new WeakSet() const recur = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => { seen.add(ast) switch (ast._tag) { case "Objects": { const materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { if (cursor.node?._tag !== "Record") { return undefined } const node = cursor.node const keys = new Set() for (const property of ast.propertySignatures) { if (typeof property.name === "string") keys.add(property.name) } if (ast.indexSignatures.length > 0) { const input: Record = {} for (const key of node.keys) { InternalRecord.assignProperty(input, key, undefined) } for (const is of ast.indexSignatures) { for (const key of SchemaAST.getIndexSignatureKeys(input, is.parameter)) { if (typeof key === "string") keys.add(key) } } } const out: Record = {} for (const key of keys) { const child = yield* loadChildCursor(cursor, key) if (child.node !== undefined) InternalRecord.assignProperty(out, key, child) } return out }) return decodeFromCursor(ast.recur(recur, (ast) => ast), materialize) } case "Arrays": { const materialize = Effect.fnUntraced(function*(cursor: ConfigCursor) { if (cursor.node?._tag !== "Array") { return undefined } const out: Array = [] for (let i = 0; i < cursor.node.length; i++) { out.push(yield* loadChildCursor(cursor, i)) } return out }) return decodeFromCursor(ast.recur(recur), materialize) } case "Union": for (const member of ast.types) { recur(member) } if (!isScalarInput(ast)) return ast.recur(recur) break case "Suspend": { const target = ast.thunk() // Force new branches so opaque encodings fail when the Config is constructed. if (!seen.has(target)) recur(target) return ast.recur(recur) } case "Declaration": case "Any": throw new globalThis.Error("Config.schema does not support opaque StringTree encodings", { cause: ast }) } return decodeFromCursor(ast, (cursor) => Effect.succeed(cursor.node?.value)) }) return recur(root) }) /** * Creates a `Config` from a `Schema.Codec`. * * **When to use** * * Use when you need to read structured or schema-validated configuration. * * **Details** * * The optional `path` sets the local path segment(s) for the config lookup. * It is appended to the logical path prefix accumulated from outer * {@link nested} calls. Pass a single string for a flat key or an array for * nested paths. * * Convenience constructors such as `String`, `Number`, and `Boolean` delegate * to this API. * * The codec is converted to its canonical `StringTree` form. Its encoded shape * determines how provider data is loaded: scalar schemas read a co-located * scalar value, object schemas read declared properties and matching record * keys, and array schemas read indexed children. A mixed-shape union loads each * member according to that member's shape before applying the union's mode and * checks. * * At the config's lookup path, a missing node or a node that cannot provide the * representation required by the schema is decoded as `undefined`. Missing * object properties remain omitted so the schema's property semantics still * apply. Decoding success always wins, even when no provider input was found. * For example, * `Schema.UndefinedOr(Schema.String)` decodes to `undefined` and is not replaced * by {@link withDefault}. If decoding fails and no relevant representation was * found, the config is absent. Invalid data in a relevant representation is a * validation failure. Provider `SourceError`s are always failures. * * **Gotchas** * * Plain `Schema.Array` and `Schema.Record` schemas use structural provider * input. Use {@link Array} or {@link Record} when a flat separated string must * also be accepted. * * `Schema.Struct` and {@link all} describe different lookup models. An * existing object, even an empty one, is validated as a whole by a struct; * missing required fields cause a validation failure. An `all` group combines * independent lookups and is absent if any child is absent and none fail. * * The canonical `StringTree` encoding must expose a concrete scalar, object, * array, or union shape. Opaque encodings such as `Schema.Any`, * `Schema.Unknown`, `Schema.ObjectKeyword`, `Schema.Json`, and * `Schema.MutableJson` are rejected synchronously when this config is * constructed, including when they are nested in another schema. Suspended * recursive schemas remain supported when their eventual shape is concrete. * Declarations such as `Schema.URL` also remain supported when their canonical * encoding has a concrete shape. To read arbitrary JSON from one scalar value, * use `Schema.fromJsonString(Schema.Json)`. * * **Example** (Reading a structured config) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Schema } from "effect" * * const DbConfig = Config.schema( * Schema.Struct({ * host: Schema.String, * port: Schema.Int * }), * "db" * ) * * const provider = ConfigProvider.fromUnknown({ * db: { host: "localhost", port: 5432 } * }) * * Effect.runSync(DbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * @see {@link String} / {@link Number} / {@link Boolean} – shortcuts for * single-value configs * * @stability stable * @category schemas * @since 4.0.0 */ export function schema(codec: Schema.ConstraintCodec, path?: string | ConfigProvider.Path): Config { const codecStringTree = Schema.toCodecStringTree(codec) const encodedAst = SchemaAST.toEncoded(codecStringTree.ast) const decodeCursor = SchemaParser.decodeUnknownEffect( Schema.make>(toConfigCursorAST(codecStringTree.ast)) ) const localPath = typeof path === "string" ? [path] : path ?? [] return make((provider, pathPrefix) => { const fullPath = [...pathPrefix, ...localPath] return loadCursor(provider, fullPath).pipe( Effect.flatMapEager((cursor) => decodeCursor(cursor).pipe( Effect.mapEager(Result.succeed), Effect.catchEager((issue) => { const error = new ConfigError( new Schema.SchemaError(fullPath.length > 0 ? new SchemaIssue.Pointer(fullPath, issue) : issue) ) return hasProviderInput(encodedAst, cursor.node) ? Effect.fail(error) : Effect.succeed(Result.fail(error)) }) ) ), mapReasons((reason) => Cause.isDieReason(reason) && isSourceError(reason.defect) ? Cause.makeFailReason(new ConfigError(reason.defect)).annotate(Cause.reasonAnnotations(reason)) : reason ) ) }) } const PortSchema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })) const LogLevelSchema = Schema.Literals(LogLevel_.values) interface ArrayOptions { readonly separator?: string | undefined } interface RecordOptions { readonly separator?: string | undefined readonly keyValueSeparator?: string | undefined } const isPath = (u: unknown): u is string | Path => Predicate.isString(u) || globalThis.Array.isArray(u) // ----------------------------------------------------------------------------- // constructors // ----------------------------------------------------------------------------- /** * Creates a config that always fails with the given error. * * **When to use** * * Use when you need to re-raise a specific config error, such as inside * {@link orElse}. * * @stability stable * @category constructors * @since 2.0.0 */ export function fail(err: SourceError | Schema.SchemaError): Config { return make(() => Effect.fail(new ConfigError(err))) } /** * Creates a config that always succeeds with the given value, ignoring the * provider entirely. * * **When to use** * * Use when you need a hardcoded config value, such as inside {@link orElse} or * tests. * * **Example** (Returning a constant fallback) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.String("HOST").pipe( * Config.orElse(() => Config.succeed("localhost")) * ) * const provider = ConfigProvider.fromUnknown({}) * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export function succeed(value: T) { return make(() => Effect.succeed(Result.succeed(value))) } /** * Creates a config for a single string value. * * **When to use** * * Use when reading a single string env var or config key. * * **Details** * * Shortcut for `Config.schema(Schema.String, name)`. * * **Example** (Reading a string config) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.String("HOST") * * const provider = ConfigProvider.fromUnknown({ HOST: "localhost" }) * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @see {@link NonEmptyString} – rejects empty strings * @see {@link schema} – for more complex types * * @stability stable * @category constructors * @since 2.0.0 */ export function String(name?: string) { return schema(Schema.String, name) } /** * Creates a config for a non-empty string value. Fails if the value is an * empty string. * * **When to use** * * Use to read a string config value that must contain at least one character. * * **Details** * * Shortcut for `Config.schema(Schema.NonEmptyString, name)`. * * @see {@link String} for allowing empty strings * * @stability stable * @category constructors * @since 3.7.0 */ export function NonEmptyString(name?: string) { return schema(Schema.NonEmptyString, name) } /** * Creates a config for a numeric value (including `NaN`, `Infinity`). * * **When to use** * * Use when you need config input to accept JavaScript's full number domain, * including NaN and infinities, rather than reject non-finite values. * * **Details** * * Shortcut for `Config.schema(Schema.Number, name)`. * * @see {@link Finite} for rejecting `NaN` and `Infinity` * @see {@link Int} for accepting only integers * * @stability stable * @category constructors * @since 2.0.0 */ export function Number(name?: string) { return schema(Schema.Number, name) } /** * Creates a config for a finite number (rejects `NaN` and `Infinity`). * * **When to use** * * Use to read a numeric config value that must be finite. * * **Details** * * Shortcut for `Config.schema(Schema.Finite, name)`. * * @see {@link Number} for accepting `NaN` and `Infinity` * @see {@link Int} for accepting only integers * * @stability stable * @category constructors * @since 4.0.0 */ export function Finite(name?: string) { return schema(Schema.Finite, name) } /** * Creates a config for an integer value. Rejects floats. * * **When to use** * * Use to read a numeric config value that must be an integer. * * **Details** * * Shortcut for `Config.schema(Schema.Int, name)`. * * @see {@link Number} for accepting any number * @see {@link Port} for accepting only integers in `1` through `65535` * * @stability stable * @category constructors * @since 4.0.0 */ export function Int(name?: string) { return schema(Schema.Int, name) } /** * Creates a config that only accepts a specific literal value. * * **When to use** * * Use to restrict a config to a single, specific literal value. * * **Details** * * Shortcut for `Config.schema(Schema.Literal(literal), name)`. * * **Example** (Restricting to a literal) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const env = Config.Literal("production", "ENV") * const provider = ConfigProvider.fromUnknown({ ENV: "production" }) * Effect.runSync(env.parse(provider)) // => "production" * ``` * * @see {@link Literals} – accepts multiple literal values * @stability stable * @category constructors * @since 2.0.0 */ export function Literal(literal: L, name?: string) { return schema(Schema.Literal(literal), name) } /** * Creates a config that only accepts one of the specified literal values. * * **When to use** * * Use to restrict a config to a fixed set of allowed literal values. * * **Details** * * Shortcut for `Config.schema(Schema.Literals(literals), name)`. * * **Example** (Restricting to a set of literals) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const env = Config.Literals(["development", "production"], "ENV") * const provider = ConfigProvider.fromUnknown({ ENV: "development" }) * Effect.runSync(env.parse(provider)) // => "development" * ``` * * @see {@link Literal} for accepting one specific literal value * * @stability stable * @category constructors * @since 4.0.0 */ export function Literals>(literals: L, name?: string) { return schema(Schema.Literals(literals), name) } /** * Creates a config for array values that may also be read from a separated string. * * **When to use** * * Use when you need to read either structural array input or a flat value such as * `"otlp,console"` from an environment variable. * * **Details** * * Pass a string or `ConfigProvider.Path` as the second argument to set the lookup * path. When no path is needed, pass the options object directly. The `separator` * defaults to `","`. * * **Example** (Reading a comma-separated array) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Schema } from "effect" * * const config = Config.Array(Schema.String, "EXPORTERS") * const provider = ConfigProvider.fromEnv({ env: { EXPORTERS: "otlp,console" } }) * * Effect.runSync(config.parse(provider)) // => ["otlp", "console"] * ``` * * @see {@link Record} for key-value input from structural records or separated strings. * @stability stable * @category constructors * @since 4.0.0 */ export function Array>( value: V, options?: ArrayOptions ): Config> export function Array>( value: V, path: string | Path, options?: ArrayOptions ): Config> export function Array>( value: V, pathOrOptions?: string | Path | ArrayOptions, options?: ArrayOptions ) { const hasPath = isPath(pathOrOptions) const resolvedOptions = hasPath ? options : pathOrOptions const array = Schema.Array(value) const separator = resolvedOptions?.separator ?? "," const arrayString = Schema.String.pipe( Schema.decodeTo(Schema.toCodecStringTree(array), { decode: SchemaGetter.split(resolvedOptions), encode: SchemaGetter.compose( SchemaGetter.passthrough, Schema.StringTree>({ strict: false }), SchemaGetter.transform((input) => input.join(separator)) ) }) ) return schema(Schema.Union([arrayString, array]), hasPath ? pathOrOptions : undefined) } /** * Creates a config for record values that may also be read from a separated key-value string. * * **When to use** * * Use when you need to read either structural record input or a flat value such as * `"service.name=my-service,service.version=1.0.0"` from an environment variable. * * **Details** * * Pass a string or `ConfigProvider.Path` as the third argument to set the lookup * path. When no path is needed, pass the options object directly. The `separator` * defaults to `","` and `keyValueSeparator` defaults to `"="`. * * **Example** (Reading a comma-separated record) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect, Schema } from "effect" * * const config = Config.Record(Schema.String, Schema.String, "OTEL_RESOURCE_ATTRIBUTES") * const provider = ConfigProvider.fromEnv({ * env: { * OTEL_RESOURCE_ATTRIBUTES: * "service.name=my-service,service.version=1.0.0,custom.attribute=value" * } * }) * * const result = Effect.runSync(config.parse(provider)) * result["service.name"] // => "my-service" * result["service.version"] // => "1.0.0" * result["custom.attribute"] // => "value" * ``` * * @see {@link Array} for array input from structural arrays or separated strings. * @stability stable * @category constructors * @since 4.0.0 */ export function Record< K extends Schema.Record.Key & Schema.ConstraintCodec, V extends Schema.ConstraintCodec >( key: K, value: V, options?: RecordOptions ): Config> export function Record< K extends Schema.Record.Key & Schema.ConstraintCodec, V extends Schema.ConstraintCodec >( key: K, value: V, path: string | Path, options?: RecordOptions ): Config> export function Record< K extends Schema.Record.Key & Schema.ConstraintCodec, V extends Schema.ConstraintCodec >( key: K, value: V, pathOrOptions?: string | Path | RecordOptions, options?: RecordOptions ) { const hasPath = isPath(pathOrOptions) const record = Schema.Record(key, value) const split = SchemaTransformation.splitKeyValue(hasPath ? options : pathOrOptions) const recordString = Schema.String.pipe( Schema.decodeTo(Schema.toCodecStringTree(record), { decode: split.decode, encode: SchemaGetter.compose( SchemaGetter.passthrough, Schema.StringTree>({ strict: false }), split.encode ) }) ) return schema(Schema.Union([record, recordString]), hasPath ? pathOrOptions : undefined) } /** * Creates a config for a boolean value parsed from common string * representations. * * **When to use** * * Use to read boolean flags from string-like config sources. * * **Details** * * Accepted values: `true`, `false`, `yes`, `no`, `on`, `off`, `1`, `0`, * `y`, `n`. * * **Example** (Reading a boolean flag) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const program = Config.Boolean("FEATURE_FLAG") * * const provider = ConfigProvider.fromEnv({ * env: { * FEATURE_FLAG: "yes" * } * }) * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) * ) // => true * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export function Boolean(name?: string) { return schema(Schema.BooleanLiterals, name) } /** * Creates a config for a `Duration` value parsed from a human-readable * string. * * **When to use** * * Use to read time duration settings such as timeouts, intervals, or TTLs. * * **Details** * * Shortcut for `Config.schema(Schema.DurationFromString, name)`. * * Accepts any string that `Duration.fromInput` can parse (e.g. * `"10 seconds"`, `"500 millis"`, `"Infinity"`, `"-Infinity"`). * * **Example** (Reading a duration) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Duration, Effect } from "effect" * * const program = Config.Duration("DURATION").pipe(Effect.map(Duration.toMillis)) * * const provider = ConfigProvider.fromEnv({ * env: { * DURATION: "10 seconds" * } * }) * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) * ) // => 10000 * ``` * * @see {@link schema} for decoding configuration values with a custom codec * * @stability stable * @category constructors * @since 2.5.0 */ export function Duration(name?: string) { return schema(Schema.DurationFromString, name) } /** * Creates a config for an exact, human-readable byte-size value. * * **Details** * * Decimal symbols such as `kB` use powers of 1,000, while binary symbols such * as `KiB` use powers of 1,024. * * @stability unstable * @category constructors * @since 4.0.0 */ export function ByteSize(name?: string) { return schema(Schema.ByteSize, name) } /** * Creates a config for a port number (integer in 1–65535). * * **When to use** * * Use to read network port settings that must be valid port numbers. * * **Details** * * Accepts integers from `1` through `65535`. * * **Example** (Reading a port) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const program = Config.Port("PORT") * * const provider = ConfigProvider.fromEnv({ * env: { * PORT: "8080" * } * }) * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) * ) // => 8080 * ``` * * @see {@link Int} for integer config values outside the port range * * @stability stable * @category constructors * @since 3.16.0 */ export function Port(name?: string) { return schema(PortSchema, name) } /** * Creates a config for a log level string. * * **When to use** * * Use to read Effect log-level settings from configuration. * * **Details** * * Accepted values: `"All"`, `"Fatal"`, `"Error"`, `"Warn"`, `"Info"`, * `"Debug"`, `"Trace"`, `"None"`. * * **Example** (Reading a log level) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const program = Config.LogLevel("LOG_LEVEL") * * const provider = ConfigProvider.fromEnv({ * env: { * LOG_LEVEL: "Info" * } * }) * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) * ) // => "Info" * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export function LogLevel(name?: string) { return schema(LogLevelSchema, name) } /** * Creates a config for a redacted string value. The parsed result is wrapped * in a `Redacted` container that hides the value from logs and `toString`. * * **When to use** * * Use to read secret string settings that should not be exposed in logs or * string output. * * **Details** * * Shortcut for `Config.schema(Schema.Redacted(Schema.String), name)`. * * **Example** (Reading a secret) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const program = Config.Redacted("API_KEY").pipe(Effect.map(String)) * * const provider = ConfigProvider.fromEnv({ * env: { * API_KEY: "sk-1234567890abcdef" * } * }) * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) * ) // => "" * ``` * * @see {@link String} for non-secret string settings * * @stability stable * @category constructors * @since 2.0.0 */ export function Redacted(name?: string) { return schema(Schema.Redacted(Schema.String), name) } /** * Creates a config for a `URL` value parsed from a string. * * **When to use** * * Use to read configuration values that must be valid URL strings. * * **Details** * * This is a shortcut for `Config.schema(Schema.URL, name)`. * * **Gotchas** * * Fails if the string cannot be parsed by the `URL` constructor. * * **Example** (Reading a URL) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const program = Config.URL("URL").pipe(Effect.map((url) => url.href)) * * const provider = ConfigProvider.fromEnv({ * env: { * URL: "https://example.com" * } * }) * * Effect.runSync( * program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider)) * ) // => "https://example.com/" * ``` * * @see {@link schema} for decoding configuration values with a custom codec * * @stability stable * @category constructors * @since 3.11.0 */ export function URL(name?: string) { return schema(Schema.URL, name) } /** * Creates a config for a `Date` value parsed from a string. * * **When to use** * * Use to read date settings that must parse to valid `Date` values. * * **Details** * * Shortcut for `Config.schema(Schema.Date, name)`. * * **Gotchas** * * Fails with a `SchemaError` if the string produces an invalid `Date`. * * **Example** (Reading a date) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const createdAt = Config.Date("CREATED_AT") * * const provider = ConfigProvider.fromUnknown({ CREATED_AT: "2024-01-15" }) * Effect.runSync(createdAt.parse(provider)).toISOString() // => "2024-01-15T00:00:00.000Z" * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export function Date(name?: string) { return schema(Schema.Date, name) } /** * Scopes a config under a named prefix. * * **When to use** * * Use when you need to group related config keys under a common namespace. * * **Details** * * The prefix is prepended to every key the inner config reads. With * `fromUnknown` this means an extra object level; with `fromEnv` it means * a `_`-separated prefix on env var names. * * Multiple `nested` calls compose: the outermost name becomes the * outermost path segment. * * **Example** (Nesting a struct config under `"database"`) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ * host: Config.String("host"), * port: Config.Number("port") * }).pipe(Config.nested("database")) * * const provider = ConfigProvider.fromUnknown({ * database: { host: "localhost", port: "5432" } * }) * Effect.runSync(dbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * **Example** (Reading env vars with a nested prefix) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.String("host").pipe(Config.nested("database")) * * const provider = ConfigProvider.fromEnv({ * env: { database_host: "localhost" } * }) * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @see {@link all} – combine multiple configs into a struct * @see {@link schema} – read structured config from a schema * * @stability stable * @category combinators * @since 2.0.0 */ export const nested: { /** * Scopes a config under a named prefix. * * **When to use** * * Use when you need to group related config keys under a common namespace. * * **Details** * * The prefix is prepended to every key the inner config reads. With * `fromUnknown` this means an extra object level; with `fromEnv` it means * a `_`-separated prefix on env var names. * * Multiple `nested` calls compose: the outermost name becomes the * outermost path segment. * * **Example** (Nesting a struct config under `"database"`) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ * host: Config.String("host"), * port: Config.Number("port") * }).pipe(Config.nested("database")) * * const provider = ConfigProvider.fromUnknown({ * database: { host: "localhost", port: "5432" } * }) * Effect.runSync(dbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * **Example** (Reading env vars with a nested prefix) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.String("host").pipe(Config.nested("database")) * * const provider = ConfigProvider.fromEnv({ * env: { database_host: "localhost" } * }) * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @see {@link all} – combine multiple configs into a struct * @see {@link schema} – read structured config from a schema * * @stability stable * @category combinators * @since 2.0.0 */ (name: string): (self: Config) => Config /** * Scopes a config under a named prefix. * * **When to use** * * Use when you need to group related config keys under a common namespace. * * **Details** * * The prefix is prepended to every key the inner config reads. With * `fromUnknown` this means an extra object level; with `fromEnv` it means * a `_`-separated prefix on env var names. * * Multiple `nested` calls compose: the outermost name becomes the * outermost path segment. * * **Example** (Nesting a struct config under `"database"`) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const dbConfig = Config.all({ * host: Config.String("host"), * port: Config.Number("port") * }).pipe(Config.nested("database")) * * const provider = ConfigProvider.fromUnknown({ * database: { host: "localhost", port: "5432" } * }) * Effect.runSync(dbConfig.parse(provider)) // => { host: "localhost", port: 5432 } * ``` * * **Example** (Reading env vars with a nested prefix) * * ```ts import.meta.vitest * import { Config, ConfigProvider, Effect } from "effect" * * const host = Config.String("host").pipe(Config.nested("database")) * * const provider = ConfigProvider.fromEnv({ * env: { database_host: "localhost" } * }) * Effect.runSync(host.parse(provider)) // => "localhost" * ``` * * @see {@link all} – combine multiple configs into a struct * @see {@link schema} – read structured config from a schema * * @stability stable * @category combinators * @since 2.0.0 */ (self: Config, name: string): Config } = dual( 2, (self: Config, name: string): Config => make((provider, pathPrefix) => evaluateAt(self, provider, [...pathPrefix, name])) )