1426 lines
38 KiB
TypeScript
1426 lines
38 KiB
TypeScript
/**
|
||
* Descriptions of configuration values that can be read from a
|
||
* `ConfigProvider`. A `Config<T>` 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.
|
||
*
|
||
* @since 4.0.0
|
||
*/
|
||
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 } from "./Function.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 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 { Config } from "effect"
|
||
*
|
||
* console.log(Config.isConfig(Config.string("HOST"))) // true
|
||
* console.log(Config.isConfig("not a config")) // false
|
||
* ```
|
||
*
|
||
* @category guards
|
||
* @since 2.0.0
|
||
*/
|
||
export const isConfig = (u: unknown): u is Config<unknown> => 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 for missing-data errors
|
||
*
|
||
* @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, pathPrefix?)` – runs the config against a specific provider.
|
||
* The optional path prefix is the logical scope accumulated from outer
|
||
* `Config.nested` calls.
|
||
* - 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
|
||
*
|
||
* @category models
|
||
* @since 2.0.0
|
||
*/
|
||
export interface Config<out T> extends Effect.Effect<T, ConfigError> {
|
||
readonly [TypeId]: typeof TypeId
|
||
readonly parse: (
|
||
provider: ConfigProvider.ConfigProvider,
|
||
pathPrefix?: Path
|
||
) => Effect.Effect<T, ConfigError>
|
||
}
|
||
|
||
const Proto = {
|
||
...Effectable.Prototype<Config<any>>({
|
||
label: "Config",
|
||
evaluate(fiber) {
|
||
return this.parse(fiber.getRef(ConfigProvider.ConfigProvider))
|
||
}
|
||
}),
|
||
[TypeId]: TypeId,
|
||
toJSON(this: Config<unknown>) {
|
||
return {
|
||
_id: "Config"
|
||
}
|
||
}
|
||
}
|
||
|
||
function make<T>(
|
||
parse: (provider: ConfigProvider.ConfigProvider, pathPrefix: Path) => Effect.Effect<T, ConfigError>
|
||
): Config<T> {
|
||
const self = Object.create(Proto)
|
||
self.parse = (provider: ConfigProvider.ConfigProvider, pathPrefix: Path = []) => parse(provider, pathPrefix)
|
||
return self
|
||
}
|
||
|
||
/**
|
||
* 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 { 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 mapOrFail} – when the transformation can fail
|
||
*
|
||
* @category mapping
|
||
* @since 2.0.0
|
||
*/
|
||
export const map: {
|
||
<A, B>(f: (a: A) => B): (self: Config<A>) => Config<B>
|
||
<A, B>(self: Config<A>, f: (a: A) => B): Config<B>
|
||
} = dual(2, <A, B>(self: Config<A>, f: (a: A) => B): Config<B> => {
|
||
return make((provider, pathPrefix) => Effect.map(self.parse(provider, pathPrefix), f))
|
||
})
|
||
|
||
/**
|
||
* 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 { Config, Effect } from "effect"
|
||
*
|
||
* const trimmed = Config.string("name").pipe(
|
||
* Config.mapOrFail((s) => Effect.succeed(s.trim()))
|
||
* )
|
||
* ```
|
||
*
|
||
* @see {@link map} – when the transformation cannot fail
|
||
*
|
||
* @category mapping
|
||
* @since 2.0.0
|
||
*/
|
||
export const mapOrFail: {
|
||
<A, B>(f: (a: A) => Effect.Effect<B, ConfigError>): (self: Config<A>) => Config<B>
|
||
<A, B>(self: Config<A>, f: (a: A) => Effect.Effect<B, ConfigError>): Config<B>
|
||
} = dual(2, <A, B>(self: Config<A>, f: (a: A) => Effect.Effect<B, ConfigError>): Config<B> => {
|
||
return make((provider, pathPrefix) => Effect.flatMap(self.parse(provider, pathPrefix), f))
|
||
})
|
||
|
||
/**
|
||
* 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 catches **all** `ConfigError`s (not just
|
||
* missing data). The fallback function receives the error and returns a new
|
||
* `Config`.
|
||
*
|
||
* **Example** (Falling back to a literal)
|
||
*
|
||
* ```ts
|
||
* import { Config } from "effect"
|
||
*
|
||
* const hostConfig = Config.string("HOST").pipe(
|
||
* Config.orElse(() => Config.succeed("localhost"))
|
||
* )
|
||
* ```
|
||
*
|
||
* @see {@link withDefault} – fallback only on missing data
|
||
*
|
||
* @category combinators
|
||
* @since 2.0.0
|
||
*/
|
||
export const orElse: {
|
||
<A2>(that: (error: ConfigError) => Config<A2>): <A>(self: Config<A>) => Config<A2 | A>
|
||
<A, A2>(self: Config<A>, that: (error: ConfigError) => Config<A2>): Config<A | A2>
|
||
} = dual(2, <A, A2>(self: Config<A>, that: (error: ConfigError) => Config<A2>): Config<A | A2> => {
|
||
return make((provider, pathPrefix) =>
|
||
Effect.catch(self.parse(provider, pathPrefix), (error) => that(error).parse(provider, pathPrefix))
|
||
)
|
||
})
|
||
|
||
/**
|
||
* 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.
|
||
*
|
||
* **Example** (Combining configs as a struct)
|
||
*
|
||
* ```ts
|
||
* import { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const dbConfig = Config.all({
|
||
* host: Config.string("host"),
|
||
* port: Config.number("port")
|
||
* })
|
||
*
|
||
* const provider = ConfigProvider.fromUnknown({ host: "localhost", port: 5432 })
|
||
* // Effect.runSync(dbConfig.parse(provider))
|
||
* // { host: "localhost", port: 5432 }
|
||
* ```
|
||
*
|
||
* @category combinators
|
||
* @since 2.0.0
|
||
*/
|
||
export function all<const Arg extends Iterable<Config<any>> | Record<string, Config<any>>>(
|
||
arg: Arg
|
||
): Config<
|
||
[Arg] extends [ReadonlyArray<Config<any>>] ? {
|
||
-readonly [K in keyof Arg]: [Arg[K]] extends [Config<infer A>] ? A : never
|
||
}
|
||
: [Arg] extends [Iterable<Config<infer A>>] ? Array<A>
|
||
: [Arg] extends [Record<string, Config<any>>] ? {
|
||
-readonly [K in keyof Arg]: [Arg[K]] extends [Config<infer A>] ? A : never
|
||
}
|
||
: never
|
||
> {
|
||
const configs: Array<Config<any>> | Record<string, Config<any>> = Array.isArray(arg)
|
||
? arg
|
||
: Symbol.iterator in arg
|
||
? [...arg as any]
|
||
: arg
|
||
if (Array.isArray(configs)) {
|
||
return make((provider, pathPrefix) =>
|
||
Effect.all(configs.map((config) => config.parse(provider, pathPrefix)))
|
||
) as any
|
||
} else {
|
||
return make((provider, pathPrefix) =>
|
||
Effect.all(Rec.map(configs, (config) => config.parse(provider, pathPrefix)))
|
||
) as any
|
||
}
|
||
}
|
||
|
||
function isMissingDataOnly(issue: SchemaIssue.Issue): boolean {
|
||
switch (issue._tag) {
|
||
case "MissingKey":
|
||
return true
|
||
case "InvalidType":
|
||
case "InvalidValue":
|
||
return Option.isNone(issue.actual) || (Option.isSome(issue.actual) && issue.actual.value === undefined)
|
||
case "OneOf":
|
||
return issue.actual === undefined
|
||
case "Encoding":
|
||
return Option.isNone(issue.actual) || (Option.isSome(issue.actual) && issue.actual.value === undefined)
|
||
? true
|
||
: isMissingDataOnly(issue.issue)
|
||
case "Pointer":
|
||
return isMissingDataOnly(issue.issue)
|
||
case "Filter":
|
||
case "UnexpectedKey":
|
||
case "Forbidden":
|
||
return false
|
||
case "Composite":
|
||
return issue.issues.every(isMissingDataOnly)
|
||
case "AnyOf":
|
||
if (issue.issues.length === 0) {
|
||
return issue.actual === undefined
|
||
}
|
||
return issue.issues.every(isMissingDataOnly)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Provides a fallback value when the config fails due to missing data.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need to make a config key optional with a sensible default.
|
||
*
|
||
* **Gotchas**
|
||
*
|
||
* Only applies when the error is a `SchemaError` caused exclusively by
|
||
* missing data (missing keys, undefined values). Validation errors (wrong
|
||
* type, out of range) still propagate.
|
||
*
|
||
* **Example** (Defaulting a missing port)
|
||
*
|
||
* ```ts
|
||
* 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 missing data
|
||
*
|
||
* @category combinators
|
||
* @since 2.0.0
|
||
*/
|
||
export const withDefault: {
|
||
<const A2>(defaultValue: A2): <A>(self: Config<A>) => Config<A2 | A>
|
||
<A, const A2>(self: Config<A>, defaultValue: A2): Config<A | A2>
|
||
} = dual(2, <A, const A2>(self: Config<A>, defaultValue: A2): Config<A | A2> => {
|
||
return orElse(self, (err) => {
|
||
if (Schema.isSchemaError(err.cause)) {
|
||
const issue = err.cause.issue
|
||
if (isMissingDataOnly(issue)) {
|
||
return succeed(defaultValue)
|
||
}
|
||
}
|
||
return fail(err.cause)
|
||
})
|
||
})
|
||
|
||
/**
|
||
* Makes a config optional: returns `Some(value)` on success and `None` when
|
||
* data is missing.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need to handle a config key that may or may not be present.
|
||
*
|
||
* **Gotchas**
|
||
*
|
||
* Like {@link withDefault}, only missing-data errors produce `None`.
|
||
* Validation errors still propagate.
|
||
*
|
||
* **Example** (Reading optional config)
|
||
*
|
||
* ```ts
|
||
* import { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const maybePort = Config.option(Config.number("port"))
|
||
*
|
||
* const provider = ConfigProvider.fromUnknown({})
|
||
* // Effect.runSync(maybePort.parse(provider)) // { _tag: "None" }
|
||
* ```
|
||
*
|
||
* @see {@link withDefault} – provide a concrete fallback value instead
|
||
*
|
||
* @category combinators
|
||
* @since 2.0.0
|
||
*/
|
||
export const option = <A>(self: Config<A>): Config<Option.Option<A>> =>
|
||
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`
|
||
*
|
||
* @category utility types
|
||
* @since 2.5.0
|
||
*/
|
||
export type Success<T> = [T] extends [Config<infer A>] ? 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<string> } | Config<{ key: string }>`
|
||
*
|
||
* @see {@link unwrap} – construct a `Config` from a `Wrap<T>`
|
||
*
|
||
* @category Wrap
|
||
* @since 2.0.0
|
||
*/
|
||
export type Wrap<A> = [NonNullable<A>] extends [infer T] ? [IsPlainObject<T>] extends [true] ?
|
||
| { readonly [K in keyof A]: Wrap<A[K]> }
|
||
| Config<A>
|
||
: Config<A>
|
||
: Config<A>
|
||
|
||
type IsPlainObject<A> = [A] extends [Record<string, any>]
|
||
? [keyof A] extends [never] ? false : [keyof A] extends [string] ? true : false
|
||
: false
|
||
|
||
/**
|
||
* Constructs a `Config<T>` from a value matching `Wrap<T>`.
|
||
*
|
||
* **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 { Config } from "effect"
|
||
*
|
||
* interface Options {
|
||
* key: string
|
||
* }
|
||
*
|
||
* const makeConfig = (config: Config.Wrap<Options>): Config.Config<Options> =>
|
||
* Config.unwrap(config)
|
||
* ```
|
||
*
|
||
* @see {@link Wrap} – the utility type accepted by this function
|
||
*
|
||
* @category Wrap
|
||
* @since 2.0.0
|
||
*/
|
||
export const unwrap = <T>(wrapped: Wrap<T>): Config<T> => {
|
||
if (isConfig(wrapped)) return wrapped
|
||
return make((provider, pathPrefix) => {
|
||
const entries = Object.entries(wrapped)
|
||
const configs = entries.map(([key, config]) =>
|
||
unwrap(config as any).parse(provider, pathPrefix).pipe(Effect.map((value) => [key, value] as const))
|
||
)
|
||
return Effect.all(configs).pipe(Effect.map(Object.fromEntries))
|
||
})
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// schema
|
||
// -----------------------------------------------------------------------------
|
||
|
||
const dump: (
|
||
provider: ConfigProvider.ConfigProvider,
|
||
path: Path
|
||
) => Effect.Effect<Schema.StringTree, SourceError> = Effect.fnUntraced(function*(
|
||
provider,
|
||
path
|
||
) {
|
||
const stat = yield* provider.load(path)
|
||
if (stat === undefined) return undefined
|
||
switch (stat._tag) {
|
||
case "Value":
|
||
return stat.value
|
||
case "Record": {
|
||
if (stat.value !== undefined) return stat.value
|
||
const out: Record<string, Schema.StringTree> = {}
|
||
for (const key of stat.keys) {
|
||
const child = yield* dump(provider, [...path, key])
|
||
if (child !== undefined) out[key] = child
|
||
}
|
||
return out
|
||
}
|
||
case "Array": {
|
||
if (stat.value !== undefined) return stat.value
|
||
const out: Array<Schema.StringTree> = []
|
||
for (let i = 0; i < stat.length; i++) {
|
||
out.push(yield* dump(provider, [...path, i]))
|
||
}
|
||
return out
|
||
}
|
||
}
|
||
})
|
||
|
||
const recur: (
|
||
ast: SchemaAST.AST,
|
||
provider: ConfigProvider.ConfigProvider,
|
||
path: Path
|
||
) => Effect.Effect<Schema.StringTree, Schema.SchemaError | SourceError> = Effect.fnUntraced(
|
||
function*(ast, provider, path) {
|
||
switch (ast._tag) {
|
||
case "Objects": {
|
||
const stat = yield* provider.load(path)
|
||
if (stat === undefined && path.length > 0) return undefined
|
||
const out: Record<string, Schema.StringTree> = {}
|
||
for (const ps of ast.propertySignatures) {
|
||
const name = ps.name
|
||
if (typeof name === "string") {
|
||
const value = yield* recur(ps.type, provider, [...path, name])
|
||
if (value !== undefined) out[name] = value
|
||
}
|
||
}
|
||
if (ast.indexSignatures.length > 0) {
|
||
if (stat && stat._tag === "Record") {
|
||
for (const is of ast.indexSignatures) {
|
||
const matches = SchemaParser._is(is.parameter)
|
||
for (const key of stat.keys) {
|
||
if (!Object.hasOwn(out, key) && matches(key)) {
|
||
const value = yield* recur(is.type, provider, [...path, key])
|
||
if (value !== undefined) out[key] = value
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
case "Arrays": {
|
||
const stat = yield* provider.load(path)
|
||
if (stat === undefined) return undefined
|
||
if (stat && stat._tag === "Value") return stat.value === "" ? [] : stat.value.split(",")
|
||
if (stat && stat._tag === "Array" && stat.value !== undefined) {
|
||
return stat.value === "" ? [] : stat.value.split(",")
|
||
}
|
||
const out: Array<Schema.StringTree> = []
|
||
const length = stat && stat._tag === "Array" ? stat.length : ast.elements.length
|
||
for (let i = 0; i < length; i++) {
|
||
const element = ast.elements[i] ?? ast.rest[0]
|
||
if (element !== undefined) {
|
||
out.push(yield* recur(element, provider, [...path, i]))
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
case "Union":
|
||
// Let downstream decoding decide; dump can return a string, object, or array.
|
||
return yield* dump(provider, path)
|
||
case "Suspend":
|
||
return yield* recur(ast.thunk(), provider, path)
|
||
default: {
|
||
// Base primitives / string-like encoded nodes.
|
||
const stat = yield* provider.load(path)
|
||
if (stat === undefined) return undefined
|
||
if (stat._tag === "Value") return stat.value
|
||
if (stat._tag === "Record" && stat.value !== undefined) return stat.value
|
||
if (stat._tag === "Array" && stat.value !== undefined) return stat.value
|
||
// Container without a co-located value cannot satisfy a scalar request.
|
||
return undefined
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
/**
|
||
* Creates a `Config<T>` 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 used to decode the raw `StringTree` produced by the provider
|
||
* into `T`. Schema validation errors are wrapped in `ConfigError`.
|
||
*
|
||
* **Example** (Reading a structured config)
|
||
*
|
||
* ```ts
|
||
* 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
|
||
*
|
||
* @category schemas
|
||
* @since 4.0.0
|
||
*/
|
||
export function schema<T>(codec: Schema.ConstraintCodec<T, unknown>, path?: string | ConfigProvider.Path): Config<T> {
|
||
const codecStringTree = Schema.toCodecStringTree(codec)
|
||
const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(codecStringTree)
|
||
const codecStringTreeEncoded = SchemaAST.toEncoded(codecStringTree.ast)
|
||
const localPath = typeof path === "string" ? [path] : path ?? []
|
||
return make((provider, pathPrefix) => {
|
||
const fullPath = [...pathPrefix, ...localPath]
|
||
return recur(codecStringTreeEncoded, provider, fullPath).pipe(
|
||
Effect.flatMapEager((tree) =>
|
||
decodeUnknownEffect(tree).pipe(
|
||
Effect.mapErrorEager((issue) =>
|
||
new Schema.SchemaError(fullPath.length > 0 ? new SchemaIssue.Pointer(fullPath, issue) : issue)
|
||
)
|
||
)
|
||
),
|
||
Effect.mapErrorEager((cause) => new ConfigError(cause))
|
||
)
|
||
})
|
||
}
|
||
|
||
/** @internal */
|
||
export const TrueValues = Schema.Literals(["true", "yes", "on", "1", "y"])
|
||
|
||
/** @internal */
|
||
export const FalseValues = Schema.Literals(["false", "no", "off", "0", "n"])
|
||
|
||
/**
|
||
* Schema for boolean values encoded as strings.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need the reusable boolean schema value for `Config.schema` with
|
||
* custom paths.
|
||
*
|
||
* **Details**
|
||
*
|
||
* Accepted string values: `true`, `false`, `yes`, `no`, `on`, `off`, `1`,
|
||
* `0`, `y`, `n` (case-sensitive).
|
||
*
|
||
* @see {@link boolean} – convenience constructor
|
||
*
|
||
* @category schemas
|
||
* @since 4.0.0
|
||
*/
|
||
export const Boolean = Schema.Literals([...TrueValues.literals, ...FalseValues.literals]).pipe(
|
||
Schema.decodeTo(
|
||
Schema.Boolean,
|
||
SchemaTransformation.transform({
|
||
decode: (value) => value === "true" || value === "yes" || value === "on" || value === "1" || value === "y",
|
||
encode: (value) => value ? "true" : "false"
|
||
})
|
||
)
|
||
)
|
||
|
||
/**
|
||
* Schema for port numbers (integers in 1–65535).
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need the reusable port schema value for `Config.schema` with
|
||
* custom paths.
|
||
*
|
||
* @see {@link port} – convenience constructor
|
||
*
|
||
* @category schemas
|
||
* @since 4.0.0
|
||
*/
|
||
export const Port = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }))
|
||
|
||
/**
|
||
* Schema for `LogLevel` string literals.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need the reusable log-level schema value for `Config.schema`
|
||
* with custom paths.
|
||
*
|
||
* **Details**
|
||
*
|
||
* Accepted values: `"All"`, `"Fatal"`, `"Error"`, `"Warn"`, `"Info"`,
|
||
* `"Debug"`, `"Trace"`, `"None"`.
|
||
*
|
||
* @see {@link logLevel} – convenience constructor
|
||
*
|
||
* @category schemas
|
||
* @since 4.0.0
|
||
*/
|
||
export const LogLevel = Schema.Literals(LogLevel_.values)
|
||
|
||
/**
|
||
* Schema for key-value record types that can also be parsed from
|
||
* a flat comma-separated string.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when reading key-value maps from a single env var (e.g. OpenTelemetry
|
||
* resource attributes).
|
||
*
|
||
* **Details**
|
||
*
|
||
* Accepts either a JSON-like record from the provider or a flat string like
|
||
* `"key1=val1,key2=val2"`. The `separator` (default `","`) and
|
||
* `keyValueSeparator` (default `"="`) can be customized.
|
||
*
|
||
* **Example** (Parsing a comma-separated record)
|
||
*
|
||
* ```ts
|
||
* import { Config, ConfigProvider, Effect, Schema } from "effect"
|
||
*
|
||
* const schema = Config.Record(Schema.String, Schema.String)
|
||
* const config = Config.schema(schema, "OTEL_RESOURCE_ATTRIBUTES")
|
||
*
|
||
* const provider = ConfigProvider.fromEnv({
|
||
* env: {
|
||
* OTEL_RESOURCE_ATTRIBUTES:
|
||
* "service.name=my-service,service.version=1.0.0,custom.attribute=value"
|
||
* }
|
||
* })
|
||
*
|
||
* console.dir(Effect.runSync(config.parse(provider)))
|
||
* // {
|
||
* // 'service.name': 'my-service',
|
||
* // 'service.version': '1.0.0',
|
||
* // 'custom.attribute': 'value'
|
||
* // }
|
||
* ```
|
||
*
|
||
* @category schemas
|
||
* @since 4.0.0
|
||
*/
|
||
export const Record = <K extends Schema.Record.Key, V extends Schema.Constraint>(key: K, value: V, options?: {
|
||
readonly separator?: string | undefined
|
||
readonly keyValueSeparator?: string | undefined
|
||
}) => {
|
||
const record = Schema.Record(key, value)
|
||
const recordString = Schema.String.pipe(
|
||
Schema.decodeTo(
|
||
Schema.Record(Schema.String, Schema.String),
|
||
SchemaTransformation.splitKeyValue(options)
|
||
),
|
||
Schema.decodeTo(record)
|
||
)
|
||
|
||
return Schema.Union([record, recordString])
|
||
}
|
||
|
||
/**
|
||
* @category schemas
|
||
* @since 4.0.0
|
||
*/
|
||
const ArrayConfig = <V extends Schema.Constraint>(value: V, options?: {
|
||
readonly separator?: string | undefined
|
||
}) => {
|
||
const array = Schema.Array(value)
|
||
const separator = options?.separator ?? ","
|
||
const arrayString = Schema.String.pipe(
|
||
Schema.decodeTo(
|
||
Schema.Array(Schema.String),
|
||
{
|
||
decode: SchemaGetter.split(options),
|
||
encode: SchemaGetter.transform((input: ReadonlyArray<string>) => input.join(separator))
|
||
}
|
||
),
|
||
Schema.decodeTo(array)
|
||
)
|
||
|
||
return Schema.Union([arrayString, array])
|
||
}
|
||
|
||
export {
|
||
/**
|
||
* Schema for array types that can also be parsed from a flat separated string.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when reading array values from a single env var, such as comma-separated
|
||
* exporter names.
|
||
*
|
||
* **Details**
|
||
*
|
||
* Accepts either a JSON-like array from the provider or a flat string like
|
||
* `"a,b,c"`. The `separator` defaults to `","` and can be customized.
|
||
*
|
||
* @category schemas
|
||
* @since 4.0.0
|
||
*/
|
||
ArrayConfig as Array
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// 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}.
|
||
*
|
||
* @category constructors
|
||
* @since 2.0.0
|
||
*/
|
||
export function fail(err: SourceError | Schema.SchemaError) {
|
||
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 { Config } from "effect"
|
||
*
|
||
* const host = Config.string("HOST").pipe(
|
||
* Config.orElse(() => Config.succeed("localhost"))
|
||
* )
|
||
* ```
|
||
*
|
||
* @category constructors
|
||
* @since 2.0.0
|
||
*/
|
||
export function succeed<T>(value: T) {
|
||
return make(() => Effect.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 { 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
|
||
*
|
||
* @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
|
||
*
|
||
* @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
|
||
*
|
||
* @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
|
||
*
|
||
* @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`
|
||
*
|
||
* @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 { Config } from "effect"
|
||
*
|
||
* const env = Config.literal("production", "ENV")
|
||
* ```
|
||
*
|
||
* @see {@link literals} – accepts multiple literal values
|
||
* @category constructors
|
||
* @since 2.0.0
|
||
*/
|
||
export function literal<L extends SchemaAST.LiteralValue>(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 { Config } from "effect"
|
||
*
|
||
* const env = Config.literals(["development", "production"], "ENV")
|
||
* ```
|
||
*
|
||
* @see {@link literal} for accepting one specific literal value
|
||
*
|
||
* @category constructors
|
||
* @since 4.0.0
|
||
*/
|
||
export function literals<const L extends ReadonlyArray<SchemaAST.LiteralValue>>(literals: L, name?: string) {
|
||
return schema(Schema.Literals(literals), name)
|
||
}
|
||
|
||
/**
|
||
* 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**
|
||
*
|
||
* Shortcut for `Config.schema(Config.Boolean, name)`.
|
||
*
|
||
* Accepted values: `true`, `false`, `yes`, `no`, `on`, `off`, `1`, `0`,
|
||
* `y`, `n`.
|
||
*
|
||
* **Example** (Reading a boolean flag)
|
||
*
|
||
* ```ts
|
||
* import { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const program = Effect.gen(function*() {
|
||
* const flag = yield* Config.boolean("FEATURE_FLAG")
|
||
* console.log(flag)
|
||
* })
|
||
*
|
||
* const provider = ConfigProvider.fromEnv({
|
||
* env: {
|
||
* FEATURE_FLAG: "yes"
|
||
* }
|
||
* })
|
||
*
|
||
* Effect.runSync(
|
||
* program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
|
||
* )
|
||
* // Output: true
|
||
* ```
|
||
*
|
||
* @see {@link Boolean} for the underlying boolean codec
|
||
*
|
||
* @category constructors
|
||
* @since 2.0.0
|
||
*/
|
||
export function boolean(name?: string) {
|
||
return schema(Boolean, 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 { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const program = Effect.gen(function*() {
|
||
* const duration = yield* Config.duration("DURATION")
|
||
* console.log(duration)
|
||
* })
|
||
*
|
||
* const provider = ConfigProvider.fromEnv({
|
||
* env: {
|
||
* DURATION: "10 seconds"
|
||
* }
|
||
* })
|
||
*
|
||
* Effect.runSync(
|
||
* program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
|
||
* )
|
||
* // Output: Duration { _tag: "millis", value: 10000 }
|
||
* ```
|
||
*
|
||
* @see {@link schema} for decoding configuration values with a custom codec
|
||
*
|
||
* @category constructors
|
||
* @since 2.5.0
|
||
*/
|
||
export function duration(name?: string) {
|
||
return schema(Schema.DurationFromString, 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**
|
||
*
|
||
* Shortcut for `Config.schema(Config.Port, name)`.
|
||
*
|
||
* **Example** (Reading a port)
|
||
*
|
||
* ```ts
|
||
* import { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const program = Effect.gen(function*() {
|
||
* const port = yield* Config.port("PORT")
|
||
* console.log(port)
|
||
* })
|
||
*
|
||
* const provider = ConfigProvider.fromEnv({
|
||
* env: {
|
||
* PORT: "8080"
|
||
* }
|
||
* })
|
||
*
|
||
* Effect.runSync(
|
||
* program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
|
||
* )
|
||
* // Output: 8080
|
||
* ```
|
||
*
|
||
* @see {@link int} for integer config values outside the port range
|
||
* @see {@link Port} for the underlying port codec
|
||
*
|
||
* @category constructors
|
||
* @since 3.16.0
|
||
*/
|
||
export function port(name?: string) {
|
||
return schema(Port, name)
|
||
}
|
||
|
||
/**
|
||
* Creates a config for a log level string.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use to read Effect log-level settings from configuration.
|
||
*
|
||
* **Details**
|
||
*
|
||
* Shortcut for `Config.schema(Config.LogLevel, name)`.
|
||
*
|
||
* Accepted values: `"All"`, `"Fatal"`, `"Error"`, `"Warn"`, `"Info"`,
|
||
* `"Debug"`, `"Trace"`, `"None"`.
|
||
*
|
||
* **Example** (Reading a log level)
|
||
*
|
||
* ```ts
|
||
* import { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const program = Effect.gen(function*() {
|
||
* const logLevel = yield* Config.logLevel("LOG_LEVEL")
|
||
* console.log(logLevel)
|
||
* })
|
||
*
|
||
* const provider = ConfigProvider.fromEnv({
|
||
* env: {
|
||
* LOG_LEVEL: "Info"
|
||
* }
|
||
* })
|
||
*
|
||
* Effect.runSync(
|
||
* program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
|
||
* )
|
||
* // Output: "Info"
|
||
* ```
|
||
*
|
||
* @see {@link LogLevel} for the underlying log-level codec
|
||
*
|
||
* @category constructors
|
||
* @since 2.0.0
|
||
*/
|
||
export function logLevel(name?: string) {
|
||
return schema(LogLevel, 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 { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const program = Effect.gen(function*() {
|
||
* const apiKey = yield* Config.redacted("API_KEY")
|
||
* console.log(apiKey)
|
||
* })
|
||
*
|
||
* const provider = ConfigProvider.fromEnv({
|
||
* env: {
|
||
* API_KEY: "sk-1234567890abcdef"
|
||
* }
|
||
* })
|
||
*
|
||
* Effect.runSync(
|
||
* program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
|
||
* )
|
||
* // Output: <redacted>
|
||
* ```
|
||
*
|
||
* @see {@link string} for non-secret string settings
|
||
*
|
||
* @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 { Config, ConfigProvider, Effect } from "effect"
|
||
*
|
||
* const program = Effect.gen(function*() {
|
||
* const url = yield* Config.url("URL")
|
||
* console.log(url)
|
||
* })
|
||
*
|
||
* const provider = ConfigProvider.fromEnv({
|
||
* env: {
|
||
* URL: "https://example.com"
|
||
* }
|
||
* })
|
||
*
|
||
* Effect.runSync(
|
||
* program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
|
||
* )
|
||
* // Output:
|
||
* // URL {
|
||
* // href: 'https://example.com/',
|
||
* // origin: 'https://example.com',
|
||
* // protocol: 'https:',
|
||
* // username: '',
|
||
* // password: '',
|
||
* // host: 'example.com',
|
||
* // hostname: 'example.com',
|
||
* // port: '',
|
||
* // pathname: '/',
|
||
* // search: '',
|
||
* // searchParams: URLSearchParams {},
|
||
* // hash: ''
|
||
* // }
|
||
* ```
|
||
*
|
||
* @see {@link schema} for decoding configuration values with a custom codec
|
||
*
|
||
* @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.DateValid, name)`.
|
||
*
|
||
* **Gotchas**
|
||
*
|
||
* Fails with a `SchemaError` if the string produces an invalid `Date`.
|
||
*
|
||
* **Example** (Reading a date)
|
||
*
|
||
* ```ts
|
||
* 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))
|
||
* // Date("2024-01-15T00:00:00.000Z")
|
||
* ```
|
||
*
|
||
* @category constructors
|
||
* @since 2.0.0
|
||
*/
|
||
export function date(name?: string) {
|
||
return schema(Schema.DateValid, 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 { 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 { 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
|
||
*
|
||
* @category combinators
|
||
* @since 2.0.0
|
||
*/
|
||
export const nested: {
|
||
(name: string): <A>(self: Config<A>) => Config<A>
|
||
<A>(self: Config<A>, name: string): Config<A>
|
||
} = dual(
|
||
2,
|
||
<A>(self: Config<A>, name: string): Config<A> =>
|
||
make((provider, pathPrefix) => self.parse(provider, [...pathPrefix, name]))
|
||
)
|