@agentback/config
v0.13.0
Published
Zod-validated, env-aware config loader (JSONC + YAML) with layered overlays and DI bindings
Readme
@agentback/config
Zod-validated, env-aware config loader — JSONC + YAML files, layered environment overlays, and first-class DI bindings.
Not a port. Written specifically for this monorepo to give services a
single, consistent way to load structured configuration. Files live in a
config/ directory; an environment-specific overlay (config.<NODE_ENV>.jsonc)
is deep-merged on top of the base file before Zod validation runs. String
values can embed ${VAR} and ${VAR:-default} interpolations, which are
resolved from process.env at load time — a missing variable without a
default throws immediately.
What it provides
Loading
loadConfigFile(filename, schema)— load + overlay + env-interpolation + Zod validation; throwsConfigValidationErroron schema failure.loadRawConfigFile(filename)— load + overlay + env-interpolation without schema validation.shallowMergeConfigs(base, overlay)— two-level merge used internally (and available for custom loaders).getConfigDir()— resolves the config directory fromCONFIG_DIR/PROJECT_ROOTenv vars or defaults to{cwd}/config.getEnv()— returnsNODE_ENVor'development'.ConfigValidationError—ZodError-carrying error class thrown on invalid config.
Env-var helpers
resolveEnvVars(str)— resolve${VAR:-default}references in a single string.resolveEnvVarsInObject(obj)— recursively resolve references in any JSON structure.
Dotenv
loadEnvFiles(options?)— cascade-load.env,.env.{NODE_ENV},.env.localintoprocess.envat startup (existing vars win unlessoverride: true).
DI integration
Configuration— DI-friendly service that callsloadConfigFileand binds the result toconfig.<name>in the parentContext, tagged'config'for later discovery.ConfigComponent— plug into anApplicationto registerConfigurationand the resolved config dir asConfigBindings.CONFIGURATION/ConfigBindings.CONFIG_DIR.ConfigBindings— typed binding keys:config.dir,config.service.CONFIG_BINDING_TAG— tag applied to all bindings created viaConfiguration.bind().
Parsers
parseConfigText(text, ext)— parse JSONC or YAML text.SUPPORTED_EXTENSIONS—['.json', '.jsonc', '.yaml', '.yml'].
Usage
import {z} from 'zod';
import {Application} from '@agentback/core';
import {ConfigComponent, ConfigBindings} from '@agentback/config';
const RedisConfig = z.object({
host: z.string(),
port: z.number().int().default(6379),
password: z.string().optional(),
});
const app = new Application();
app.component(ConfigComponent);
const cfg = await app.get(ConfigBindings.CONFIGURATION);
cfg.bind('redis.jsonc', RedisConfig);
// -> other services can now: @inject('config.redis') redis: z.infer<typeof RedisConfig>
await app.start();Without the DI layer (standalone loader):
import {loadEnvFiles, loadConfigFile} from '@agentback/config';
loadEnvFiles(); // load .env cascade before anything reads env
const cfg = loadConfigFile('redis.jsonc', RedisConfig);
// cfg is typed as z.infer<typeof RedisConfig>Config directory resolution order:
$CONFIG_DIR(relative to$PROJECT_ROOTor cwd)$PROJECT_ROOT/config{cwd}/config
Overlay merge order (last wins):
config/redis.jsonc
config/redis.development.jsonc ← only when NODE_ENV=developmentLayering
Depends on: @agentback/context, @agentback/core.
Sit this above core and below your domain services. The ConfigComponent
needs a running Application; standalone loader functions have no framework
dependency.
