config
js/config.ts
fino:config — explicit ordered config loading over fino:validate.
Config loading is intentionally explicit. Callers provide a sources list,
and that list is both the set of enabled source types and the precedence
order. Earlier sources are lower precedence; later sources override them.
The final merged value is validated through fino:validate, so config can
use fluent builders or raw JSON Schema loaded from disk. Environment and argv
sources produce strings by default, then the loader coerces scalar values
according to the schema before validating.
import { loadConfig } from 'fino:config';
import { v } from 'fino:validate';
const loaded = await loadConfig({
schema: v.object({
server: v.object({ port: v.integer().default(3000) }),
}),
sources: [
{ type: 'defaults', value: { server: { port: 3000 } } },
{ type: 'file', path: './app.toml' },
{ type: 'env', prefix: 'APP_' },
{ type: 'argv', args: ['--server.port', '8080'] },
],
});
loaded.value.server.port; // 8080
JSON Schema specification: https://json-schema.org/specification
Types
type ConfigValue = Record<string, unknown>
Plain object value produced by config sources before schema validation.
Config sources merge object-shaped data. Nested values may be strings, numbers, booleans, arrays, nulls, or other objects at runtime, but the top-level source value must be an object so precedence merging can apply.
type ConfigSource = {
type: 'defaults';
value: ConfigValue;
} | {
type: 'file';
path: string;
format?: 'json' | 'toml';
} | {
type: 'dotenv';
path: string;
map?: Record<string, string>;
prefix?: string;
} | {
type: 'env';
values?: Record<string, string>;
map?: Record<string, string>;
prefix?: string;
} | {
type: 'argv';
args?: string[];
map?: Record<string, string>;
} | {
type: 'override';
value: ConfigValue;
}
One config input source.
Sources are loaded in array order. The merged result from each source overrides values from all earlier sources.
Each union arm is selected by its type field. File-backed sources throw
when the file cannot be read or parsed. Env-like sources produce string
values first; loadConfig() performs schema-guided scalar coercion before
validation.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [
{ type: 'defaults', value: { server: { port: 3000 } } },
{ type: 'env', values: { APP_SERVER_PORT: '8080' }, prefix: 'APP_' },
],
});
loaded.value;
Interfaces
interface LoadConfigOptions<T = unknown> {
Options for loadConfig().
Provide a validation schema and the ordered list of enabled sources. The
loader rejects with ConfigError when validation fails.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [{ type: 'defaults', value: {} }],
});
loaded.sources;
Properties
schema: unknown
Fluent builder or raw JSON Schema object used for final validation.
Builders with toJSON() are converted before scalar coercion. Schema
defaults are applied by fino:validate when supported by the provided
schema.
import { loadConfig } from 'fino:config';
await loadConfig({
schema: {
type: 'object',
properties: { port: { type: 'integer' } },
},
sources: [{ type: 'defaults', value: { port: 3000 } }],
});
sources: ConfigSource[]
Ordered source list. Later entries override earlier entries.
The array also controls which source types are enabled; there is no implicit loading from files, env, or argv.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [
{ type: 'defaults', value: { debug: false } },
{ type: 'override', value: { debug: true } },
],
});
loaded.value;
secrets?: string[]
Config paths whose received values should be redacted in validation errors.
Paths use the same dotted form as source mappings. Redaction affects the
rendered error message; the original ValidationIssue objects are still
exposed on ConfigError.issues.
import { ConfigError, loadConfig } from 'fino:config';
try {
await loadConfig({
schema: { type: 'object', required: ['database'] },
sources: [{ type: 'defaults', value: { database: { password: 'secret' } } }],
secrets: ['database.password'],
});
} catch (error) {
if (error instanceof ConfigError) error.message;
}
interface ConfigSourceReport {
Metadata describing values loaded from a source.
Reports are returned in the same order as the input sources array. They
include source type, optional file path, and flattened keys produced by the
source after mapping.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [{ type: 'env', values: { APP_SERVER_PORT: '8080' }, prefix: 'APP_' }],
});
loaded.sources[0].keys;
Properties
type: string
Source type, matching the input source discriminant.
This is a string so reports can describe future source types without a type change.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [{ type: 'env', values: { APP_DEBUG: 'true' }, prefix: 'APP_' }],
});
loaded.sources.find((source) => source.type === 'env');
path?: string
File path for file-backed sources.
This is present for file and dotenv reports and omitted for inline,
environment, and argv sources.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [{ type: 'file', path: './app.toml' }],
});
loaded.sources[0].path;
keys: string[]
Flattened config paths produced by this source.
Nested objects are represented as dotted paths. Empty sources produce an empty array.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [{ type: 'defaults', value: { server: { port: 3000 } } }],
});
loaded.sources[0].keys.includes('server.port');
interface LoadedConfig<T = unknown> {
Result returned from loadConfig().
The value has already been merged, coerced, and validated. Source reports describe what each source contributed.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig<{ debug: boolean }>({
schema: { type: 'object', properties: { debug: { type: 'boolean' } } },
sources: [{ type: 'override', value: { debug: true } }],
});
loaded.value.debug;
Properties
value: T
Validated config value, including defaults applied by the schema.
Its type is the generic T supplied to loadConfig<T>().
import { loadConfig } from 'fino:config';
const loaded = await loadConfig<{ port: number }>({
schema: { type: 'object', properties: { port: { type: 'integer' } } },
sources: [{ type: 'defaults', value: { port: 3000 } }],
});
loaded.value.port;
sources: ConfigSourceReport[]
Per-source load metadata in the same order as sources.
Reports are useful for diagnostics and for explaining where configuration came from. They do not include secret redaction metadata.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [
{ type: 'defaults', value: { port: 3000 } },
{ type: 'argv', args: ['--port', '8080'] },
],
});
loaded.sources.map((source) => source.type);
Methods
get(path: string): unknown
Read a dotted path from the validated config value.
Missing paths return undefined. Only plain objects are traversed:
a path segment that lands on an array, scalar, or null resolves to
undefined rather than throwing.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig({
schema: { type: 'object' },
sources: [{ type: 'defaults', value: { server: { port: 3000 } } }],
});
loaded.get('server.port');
Classes
class ConfigError extends Error {
Error thrown when loading or validating config fails.
Validation failures include the original ValidationIssue objects in
issues. Source parsing and unknown-source errors may throw ConfigError
without issues.
import { ConfigError, loadConfig } from 'fino:config';
try {
await loadConfig({
schema: { type: 'object', required: ['port'] },
sources: [{ type: 'defaults', value: {} }],
});
} catch (error) {
if (error instanceof ConfigError) error.issues;
}
Properties
issues: ValidationIssue[]
Validation issues when the failure came from fino:validate.
This array is empty for loader errors that are not validation failures. Secret redaction applies to the error message, not to issue objects.
import { ConfigError, loadConfig } from 'fino:config';
try {
await loadConfig({
schema: { type: 'object', required: ['port'] },
sources: [{ type: 'defaults', value: {} }],
});
} catch (error) {
if (error instanceof ConfigError) error.issues.map((issue) => String(issue));
}
Constructors
constructor(message: string, issues: ValidationIssue[] = [])
Create a config error.
When the failure came from validation, pass the ValidationIssue list as
the second argument; it defaults to an empty array. The name property is
set to 'ConfigError' for callers that distinguish config failures from
other exceptions.
import { ConfigError } from 'fino:config';
throw new ConfigError('Invalid config');
Functions
async function loadConfig<T = unknown>(options: LoadConfigOptions<T>): Promise<LoadedConfig<T>>
Load, merge, coerce, and validate config from an explicit source list.
Source order is the precedence model: later sources override earlier sources.
The returned value is the post-validation object.
The loader reads each source in order, deep-merges object values, performs
conservative schema-guided coercion for strings from env and argv sources,
and validates through fino:validate. Validation failures throw
ConfigError with issues; file parse errors and unsupported sources also
reject. There are no implicit defaults beyond what you provide in sources
or the validation schema. The resolved LoadedConfig carries the validated
value, per-source reports, and a dotted-path get() accessor.
import { loadConfig } from 'fino:config';
const loaded = await loadConfig<{ server: { port: number } }>({
schema: {
type: 'object',
properties: {
server: {
type: 'object',
properties: { port: { type: 'integer' } },
},
},
},
sources: [
{ type: 'defaults', value: { server: { port: 3000 } } },
{ type: 'argv', args: ['--server.port', '8080'] },
],
});
loaded.value.server.port;