Config
Hierarchical config discovery, merge and edit across a directory chain.
@ozaco/std/configConfig is a plugin that discovers <name>.<ext> files from cwd up to home, resolves each file's extends, and merges the whole chain — plus the active-variant overlay, the config-directory files, and an environment overlay — into one view.
Precedence within a level is variant → dir → base; inner directory levels win over outer ones; and the ENV overlay wins over everything. Which layers run is controlled by Features bitflags (all enabled by default). It requires an IO implementation and a codec (TOML by default) installed first.
Config.actions.* operate on the scope-installed singleton config, covering the full lifecycle: load/refresh/save, read with get/has/keys/search, edit with set/remove/clear/delete, inspect provenance with tree/origin/explain, and react to changes with watch. Config.actions.open(options) mints extra, fully independent instances for managing several configs at once.
Plugin
The config plugin and its actions.
Config
The config plugin — discovers, merges and edits a hierarchy of config files.
const Config: Plugin<
ConfigDef.Context,
[options?: ConfigDef.Options],
ConfigDef.Actions
>
// Config.actions operate on the scope-installed (singleton) config:
Config.actions.load(cwd?: string): Future<void> // (re)discover + merge
Config.actions.refresh(): Future<void> // re-run discovery in place
Config.actions.save(path?: string): Future<void> // flush edited files
Config.actions.get<T>(key?: string): Future<T> // read dotted key (or all)
Config.actions.set(key: string, value: unknown): Future<void>
Config.actions.remove(key: string): Future<void>
Config.actions.clear(): Future<void> // empty the working file
Config.actions.delete(path?: string): Future<void> // delete a file, re-discover
Config.actions.search(query: string): Future<ConfigDef.Match[]>
Config.actions.tree(): Future<ConfigDef.Source[]>
Config.actions.has(key: string): Future<boolean>
Config.actions.keys(): Future<string[]>
Config.actions.origin(key: string): Future<string | undefined>
Config.actions.explain(key: string): Future<ConfigDef.Origin[]>
Config.actions.watch(
listener: ConfigDef.Watcher,
options?: ConfigDef.WatchOptions,
): Future<Task<void>>
Config.actions.open(options?: ConfigDef.Options): Future<ConfigDef.Instance>The plugin is a scope singleton, so a second install overwrites the first — use open to run several independent configs side by side. Edits from set/remove/clear are applied to the merged view immediately and in memory; call save to persist them to disk.
import { install } from '@ozaco/std/plugin'
import { Config } from '@ozaco/std/config'
yield* install(Config, { name: 'ozaco' })
yield* Config.actions.load()
const port = yield* Config.actions.get<number>('server.port')
yield* Config.actions.set('server.port', 8080)
yield* Config.actions.save()
// Trace where a key comes from.
const where = yield* Config.actions.explain('server.port')
// An independent second config with its own discovery + working file.
const other = yield* Config.actions.open({ name: 'tsconfig', ext: 'json' })
const strict = yield* other.get<boolean>('compilerOptions.strict')Types
ConfigDef
The config plugin shape and its associated value types.
type ConfigDef = Plugin<ConfigDef.Context, [options?: ConfigDef.Options], ConfigDef.Actions>The ConfigDef namespace also exposes:
- •
Object—Record<string, unknown>, a codec-parsed config value. - •
Match—{ key: string; value: unknown }, asearchhit. - •
Origin—{ path: string; value: unknown }, one provenance entry ('<env>'= overlay). - •
Source— a discovered file: itspath,data, resolvedextends, and rawextendsSpec. - •
Watcher—(merged: ConfigDef.Object) => void, thewatchchange listener. - •
WatchOptions—{ debounce?: number }for coalescing filesystem events. - •
Context— the internal merged state (chain, env overlay, merged view, working file, dirty set). - •
Instance/Actions— the method surface (see below).
ConfigDef.Options
Discovery and merge options passed on install or to `open`.
interface Options {
codec?: CodecDef // target codec (default TomlCodec)
name?: string // config name (default 'ozaco')
cwd?: string // discovery start dir (default process.cwd())
variant?: string // active variant overlay (default STD_CONFIG env)
home?: string // discovery stop dir, inclusive (default home)
dot?: boolean // dir/file should start with a dot (default true)
ext?: string // file extension w/o dot (default derived from codec)
features?: Features // enabled features (default Features.ALL)
}ConfigDef.Instance
A self-contained config with its own discovery, merge and working file.
interface Instance {
load(cwd?: string): Future<void>
refresh(): Future<void>
save(path?: string): Future<void>
get<T>(key?: string): Future<T>
set(key: string, value: unknown): Future<void>
remove(key: string): Future<void>
clear(): Future<void>
delete(path?: string): Future<void>
search(query: string): Future<ConfigDef.Match[]>
tree(): Future<ConfigDef.Source[]>
has(key: string): Future<boolean>
keys(): Future<string[]>
origin(key: string): Future<string | undefined>
explain(key: string): Future<ConfigDef.Origin[]>
watch(
listener: ConfigDef.Watcher,
options?: ConfigDef.WatchOptions,
): Future<Task<void>>
}Config's own actions are the default instance; ConfigDef.Actions extends this with open, which returns a fresh, independent Instance. watch returns the background Task — halt() it to stop watching.
Constants
Feature flags and discovery defaults.
Features
Bitflags toggling each config discovery/merge layer. Combine with `|`.
enum Features {
NONE = 0,
FILE = 1 << 2, // read the base <name>.<ext> at each level
CHAIN = 1 << 3, // walk parent dirs cwd -> home, merging outer -> inner
VARIANT = 1 << 4, // apply the <variant>.<name>.<ext> overlay per level
ENV = 1 << 5, // read STD_CONFIG + overlay <NAME>_A_B env vars on top
DIR = 1 << 6, // also merge a <name>/ directory's *.<ext> files
ALL = FILE | CHAIN | VARIANT | ENV | DIR,
}Each flag toggles one discovery layer independently; Features.ALL (the default) enables every layer.
import { Features } from '@ozaco/std/config'
import { Config } from '@ozaco/std/config'
import { install } from '@ozaco/std/plugin'
// Single file only, no chain walking or env overlay.
yield* install(Config, { features: Features.FILE })DEFAULT_NAME
Default config name (basename of the discovered files).
const DEFAULT_NAME = 'ozaco'EXTENDS_KEY
Key inside a config file listing other files to inherit from.
const EXTENDS_KEY = 'extends'Extended paths are resolved relative to the file that declares them.
VARIANT_ENV_KEY
Env var naming the active variant when the `ENV` feature is on.
const VARIANT_ENV_KEY = 'STD_CONFIG'