Skip to content

config API

Everything the package exports, by the path you import it from. Open an export for its signature and options.

@evolve-framework/config

BaseConfig, the envfield field types, the sources (envSource, fileSource, documentSource), createIndexedSource for writing a source of your own, and the key transformers that match a store's key spelling to a property name

Classes

BaseConfigA service's configuration: extend it, declare each setting as a property whose initializer is its default, and describe how to read each one in config().
import { BaseConfig } from "@evolve-framework/config";
new BaseConfig()
MethodDescription
load(options?: LoadOptions | undefined): Promise<void>Fills every field declared in config() from the sources, then coerces and validates it.

Functions

anyKeysReads any conventional spelling, including redisUrl and RedisUrl.
import { anyKeys } from "@evolve-framework/config";

Deliberately one-directional. Camel and Pascal do not round-trip: MyAPIKey folds to MY_API_KEY, which spells back as MyApiKey. A reverse mapping that is wrong on exactly the keys containing an acronym would pass every test and then miss one key in production, so those spellings are read and never written.

function anyKeys(): KeyTransformer

Returns KeyTransformer

createIndexedSourceBuilds a source from a way to list a namespace and a way to read one key.
import { createIndexedSource } from "@evolve-framework/config";

Every source shares the same prefix fallback, the same key folding and the same collision reporting, so a cloud adapter only has to say how to talk to its store.

function createIndexedSource(options: IndexedSourceOptions): ConfigSource
OptionTypeDescription
namestring
namespace?string

Describes the namespace when it turns out to hold nothing.

keys?KeyTransformer
prefix?string
prefixFallback?boolean
lookup?"index" | "direct"

index lists the namespace once and looks keys up in the result, which turns a thirty-field config into one request. direct reads one key at a time, which needs no permission to list and so suits a least-privilege grant of read-on-key.

list?(() => Promise<NativeEntry[]>)

Lists the namespace. Required for lookup: "index".

read?((native: string) => Promise<string | undefined>)

Reads one native key. Required for lookup: "direct".

remove?((native: string) => void)

Drops a native key, backing envfield.secret().

Returns ConfigSource

documentSourceReads one file holding the entire configuration.
import { documentSource } from "@evolve-framework/config";

The cloud-agnostic option, and the reason it earns a place beside the others: mount a config.json into a container and every key is present, with no SDK, no credentials, no network call and no cloud to be on. The same file works on Docker, Kubernetes, ECS and a plain virtual machine. It also covers a ConfigMap mounted as a single config.yaml, which is at least as common as the key-per-file form.

function documentSource(options: DocumentSourceOptions): ConfigSource
OptionTypeDescription
pathstring

The file holding the whole configuration.

parse?((raw: string) => unknown)

Parses the file. JSON by default; pass YAML.parse for YAML rather than having this package depend on a parser nobody may need.

prefix?string
prefixFallback?boolean
keys?KeyTransformer

Returns ConfigSource

dottedKeysKeys are lowercase dotted: redis.url.
import { dottedKeys } from "@evolve-framework/config";
function dottedKeys(): KeyTransformer

Returns KeyTransformer

envSourceReads process.env.
import { envSource } from "@evolve-framework/config";

Exact matching rather than folding: environment variables are uppercase by convention, so there is nothing to reconcile, and PATH against Path on Windows is a trap. Reading one variable is free, so there is nothing to index.

function envSource(options?: EnvSourceOptions | undefined): ConfigSource
OptionTypeDescription
prefix?string

Tried before the bare name, so PURCHASE_ finds PURCHASE_REDIS_URL.

prefixFallback?boolean

When false, only the prefixed name is tried. Sealing a service into its own namespace stops a generic name in a shared environment from leaking into a service that meant to read its own.

keys?KeyTransformer

Returns ConfigSource

exactKeysKeys are spelled exactly as the field declares them: REDIS_URL.
import { exactKeys } from "@evolve-framework/config";
function exactKeys(): KeyTransformer

Returns KeyTransformer

fileSourceReads a directory holding one file per key.
import { fileSource } from "@evolve-framework/config";

This is the shape of Docker and Compose secrets under /run/secrets, of a Kubernetes Secret or ConfigMap mounted as a volume, and of a projected volume.

function fileSource(options: FileSourceOptions): ConfigSource
OptionTypeDescription
pathstring

The directory holding one file per key.

prefix?string
prefixFallback?boolean
keys?KeyTransformer
lookup?"index" | "direct"
trim?boolean

Strips one trailing newline, which is what echo secret > file leaves behind. Set false for a value where a final newline is significant.

Returns ConfigSource

foldKeyThe permissive fold: separators become underscores and camel humps become boundaries, so every conventional spelling of one key lands on one name.
import { foldKey } from "@evolve-framework/config";
function foldKey(key: string): string

Returns string

indexDocumentTurns a parsed document into entries, one per key at every depth.
import { indexDocument } from "@evolve-framework/config";

Every level is indexed rather than only the leaves or only the top, because both readings are legitimate: a field may want the whole subtree as an envfield.object(), or one value inside it. The document is already in memory, so offering both costs nothing.

{ "store-config": { "evolve": { "domain": "https://example.test" } } }

store-config -> {"evolve":{"domain":"https://example.test"}} store-config.evolve -> {"domain":"https://example.test"} store-config.evolve.domain -> https://example.test

Segments are joined with a dot so that a folding transformer reduces the path and the segments together: the last of those resolves the field STORE_CONFIG_EVOLVE_DOMAIN.

This only ever runs in this direction. Nothing rebuilds a document from an index, because configuration here is read-only.

function indexDocument(document: unknown): NativeEntry[]

Returns NativeEntry[]

kebabKeysKeys are lowercase kebab-case: redis-url.
import { kebabKeys } from "@evolve-framework/config";
function kebabKeys(): KeyTransformer

Returns KeyTransformer

prefixedKeyNormalizes a lookup prefix to exactly one trailing underscore, so that NL, NL_, nl/ and nl: all describe the same lookup.
import { prefixedKey } from "@evolve-framework/config";
function prefixedKey(prefix: string, key: string): string

Returns string

snakeKeysKeys are lowercase snake_case: redis_url.
import { snakeKeys } from "@evolve-framework/config";
function snakeKeys(): KeyTransformer

Returns KeyTransformer

Constants

envfield
import { envfield } from "@evolve-framework/config";
const envfield: { number: (options?: FieldOptions | undefined) => PropertyOptions; string: (options?: FieldOptions | undefined) => PropertyOptions; object: (options?: FieldOpti…
silentLoggerThe default: a library nobody asked to be loud stays silent.
import { silentLogger } from "@evolve-framework/config";
const silentLogger: ConfigLogger

Types

ConfigLoggerThe logging surface config needs, kept structural so that a pino logger satisfies it without this package depending on pino — or on core, which is loaded after config and therefore cannot be loaded before it.
import { type ConfigLogger } from "@evolve-framework/config";

trace is optional so that a bare console also satisfies the type.

export type ConfigLogger = {
debug: LogFn;
warn: LogFn;
trace?: LogFn;
};
ConfigSource
import { type ConfigSource } from "@evolve-framework/config";
export type ConfigSource = {
readonly name: string;
get(key: string): Promise<string | undefined>;
prefetch?(): Promise<void>;
unset?(key: string): void;
describe?(key: string): string;
applyLoadOptions?(options: SourceLoadOptions): void;
};
EnviConfig
import { type EnviConfig } from "@evolve-framework/config";
export type EnviConfig = EnvVariableDict;
EnvVariableDict
import { type EnvVariableDict } from "@evolve-framework/config";
export type EnvVariableDict = Record<string, PropertyOptions>;
FieldOptions
import { type FieldOptions } from "@evolve-framework/config";
export type FieldOptions = {
optional?: boolean;
validator?: Validator;
unset?: boolean;
envName?: string;
envSeparator?: string;
};
KeyTransformerReconciles the name a config class declares with the name one store uses.
import { type KeyTransformer } from "@evolve-framework/config";

The two directions are not equally knowable, which is why only one of them is required. Folding redis-url onto REDIS_URL is deterministic. Going back is a guess unless the store's convention has been declared: it could as easily be redis_url, RedisUrl, or REDIS_URL unchanged.

export type KeyTransformer = {
readonly name: string;
toCanonical(native: string): string | undefined;
toNative?(key: string): string;
};
KnownTypes
import { type KnownTypes } from "@evolve-framework/config";
export type KnownTypes = "string" | "number" | "boolean" | "object";
LoadOptions
import { type LoadOptions } from "@evolve-framework/config";
export type LoadOptions = {
prefix?: string;
prefixFallback?: boolean;
disableUnset?: boolean;
sources?: ConfigSource[];
logger?: ConfigLogger;
};
NativeEntryOne entry as the store spells it, with a way to read its value.
import { type NativeEntry } from "@evolve-framework/config";
export type NativeEntry = {
native: string;
read(): Promise<string | undefined>;
};
PropertyOptions
import { type PropertyOptions } from "@evolve-framework/config";
export type PropertyOptions = {
type: KnownTypes;
} & FieldOptions;
SourceLoadOptionsDefaults the load call hands to every source it was given.
import { type SourceLoadOptions } from "@evolve-framework/config";
export type SourceLoadOptions = {
prefix?: string;
prefixFallback?: boolean;
logger: ConfigLogger;
};

@evolve-framework/config/testing

memorySource, a plain record as a source, for testing a config class without the environment or a cloud

Functions

memorySourceA plain record as a source, so a config class can be exercised across the overlay without a cloud, a filesystem or the process environment.
import { memorySource } from "@evolve-framework/config/testing";

Keys are written the way the store being stood in for would write them, so { "redis-url": "..." } exercises the same folding a real store would.

function memorySource(
values: Record<string, string>,
options?: MemorySourceOptions,
): ConfigSource
OptionTypeDescription
name?string

Names the source in errors and log lines.

namespace?string

Stands in for a real store's namespace in errors and log lines.

prefix?string
prefixFallback?boolean
keys?KeyTransformer

Returns ConfigSource

Not listed: 5 options types. They appear as the tables under the constructors and functions they type.