@kleb/config (0.6.0)
Installation
@kleb:registry=npm install @kleb/config@0.6.0"@kleb/config": "0.6.0"About this package
@kleb/config
Typed configuration loading for TypeScript projects. The default @kleb/config
entrypoint is browser-safe. File, environment, CLI, secret, and builder APIs are
available from @kleb/config/server.
Builder-managed configuration
Use the server builder when the library should own creation, loading, updates, reloads, and saves:
import { field, kConfig } from "@kleb/config/server";
const appConfig = kConfig("server.json")
.fields({
port: field.number().int().min(1).max(65535).default(3000).env("PORT"),
mode: field.enum(["dev", "prod"] as const).default("dev"),
})
.section(
"http",
kConfig("http.json").fields({
host: field.string().default("127.0.0.1"),
enabled: field.boolean().default(true),
}),
);
const config = appConfig.load({ env: process.env, logger });
config.value.port;
config.update((draft) => {
draft.http.host = "0.0.0.0";
});
config.save();
config.reload();
JSON files are strict: a number field must be a JSON number. Environment and CLI
values are parsed only for fields that declare .env() or .cli(). Unknown
file keys are warnings, and saves write the declared config shape. Defaults are
validated with the same output validators used by update() and set().
Reversible codecs
Use .codec({ decode, encode }) when the in-memory output type differs from the
raw JSON representation. Loading and reloading decode raw values; updates and
set() validate the output type without decoding it again; saving encodes the
output back to JSON.
const date = field.string().codec({
decode: (value) => new Date(value),
encode: (value: Date) => value.toISOString(),
});
const config = await kConfig("server.json").field("startedAt", date).loadAsync();
config.update({ startedAt: new Date() });
await config.saveAsync();
await config.reloadAsync();
Codecs compose through objects, arrays, tuples, records, unions, and sections. The encoder must return the preceding field output type, so codec chains remain reversible and type-safe.
transform() remains available for one compatibility release, but is
deprecated because a one-way transform cannot safely round-trip through JSON.
It can still load and reload existing configurations. Before writing, a save
encodes and JSON-serializes every parent and file-backed section; a codec error
or one-way transformed field therefore fails without updating any target file.
Replace transform() with a codec before relying on save.
Each target file is then replaced atomically, but a multi-file save is not a cross-file transaction. A filesystem failure while replacing a later target can leave targets written earlier in that save updated.
Async builder APIs
All synchronous builder APIs remain available. Promise-based counterparts use
node:fs/promises for file I/O:
builder.loadAsync()andbuilder.tryLoadAsync()loaded.reloadAsync()andloaded.tryReloadAsync()loaded.saveAsync()
Failed reloads preserve the last valid value for both sync and async APIs.
.env()/.cli() overrides only apply to fields declared directly on a
builder via .field()/.fields() — at the top level or inside a .section().
They are never applied to fields nested inside a composite validator such as
field.object(...), field.array(...), field.record(...), field.tuple(...),
or field.union(...); those inner fields are always sourced from the JSON file
(or an in-memory update/set), regardless of any .env()/.cli() calls on the
inner field definitions.
Boolean fields parsed from an env or CLI source accept a small set of
case-insensitive tokens in addition to true/false: 1/0, yes/no, and
on/off. Any other string throws a ConfigError. Boolean values loaded from
a JSON file must still be a JSON true/false.
Standard Schema configuration
Validate caller-provided configuration with any Standard Schema-compatible schema from the browser-safe entrypoint:
import { loadConfig } from "@kleb/config";
const config = loadConfig(schema, {
PORT: "3000",
LOG_LEVEL: "info",
});
Use the server entrypoint to read process.env:
import { loadEnvConfig } from "@kleb/config/server";
const config = loadEnvConfig(schema, {
prefix: "APP_",
});
Prefix filtering strips the prefix by default before validation:
const config = loadEnvConfig(schema, {
prefix: "APP_",
keys: ["PORT", "LOG_LEVEL"],
});
Server file and secret APIs
Sync and genuine async variants share the same parsers and behavior:
readJsonConfigFile()/readJsonConfigFileAsync()readDotEnvFile()/readDotEnvFileAsync()buildLayeredConfigSource()/buildLayeredConfigSourceAsync()loadLayeredConfig()/loadLayeredConfigAsync()resolveSecretRef()/resolveSecretRefAsync()
Optional files handle ENOENT from the read itself rather than checking for
existence first, avoiding an existence/read race. Layer precedence is defaults,
JSON files, dotenv files, environment, then CLI.
Validation errors keep schema-provided messages on ConfigError.issues, but the
thrown ConfigError.message only reports paths and issue counts. This keeps
application logs from leaking raw configuration values if a schema includes
them in its issue text.
Config sources reject unsafe object keys such as __proto__, constructor, and
prototype when loading, merging, parsing CLI/JSON sources, or saving
builder-managed configuration.