@kleb/config (0.6.0)

Published 2026-07-14 11:00:15 +02:00 by kleb

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() and builder.tryLoadAsync()
  • loaded.reloadAsync() and loaded.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.

Details
npm
2026-07-14 11:00:15 +02:00
11
UNLICENSED
14 KiB
Assets (1)
Versions (7) View all
0.7.0 2026-07-20
0.6.0 2026-07-14
0.5.0 2026-07-07
0.4.0 2026-07-04
0.3.0 2026-07-03