npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@galaxus/chabis-application-config

v0.1.2

Published

Type-safe, layered application configuration implementing the Chabis application-config contract: YAML/TOML files, Google Parameter Manager and Azure Key Vault secrets, schema-driven env overrides, and Standard Schema validation.

Downloads

327

Readme

@galaxus/chabis-application-config

Type-safe application configuration for Node and TypeScript. Describe your config shape with a zod or valibot schema and get back one fully typed object, merged from layered YAML/TOML files and cloud secrets and validated against that schema

It implements the Chabis application-config contract: the same file layering, merge semantics, and secret resolution as the Python chabis-application-config library, so a service keeps one config layout across stacks

What you get:

  • Layered settings, settings.{env}, and .secrets files in YAML or TOML, deep-merged in a fixed priority order
  • Secrets from Google Cloud Parameter Manager or Azure Key Vault, filling placeholders left in those files
  • GPM versions resolve to the latest automatically, no version pin to bump
  • Environment-variable overrides derived from your schema, no magic prefix
  • A single typed object validated against your schema, or a thrown error

Install

pnpm add @galaxus/chabis-application-config

Bring a schema library as a peer dependency, zod or valibot:

pnpm add zod
# or
pnpm add valibot @valibot/to-json-schema

Google Cloud Parameter Manager is included out of the box. Azure Key Vault is optional, install its SDKs only if you use an AzureKeyVault provider instead:

pnpm add @azure/identity @azure/keyvault-secrets

Usage

import { z } from "zod";
import { loadConfig } from "@galaxus/chabis-application-config";

const Schema = z.object({
  LogLevel: 
    z.string().default("INFO"),
  Database: 
    z.object({ Url: z.string() }),
  Auth: 
    z.object({ ClientId: z.string(), TenantId: z.string(), Secret: z.string() }),
});

export const config = await loadConfig(Schema, {
  // directory holding the settings.*.{yaml,toml} files, defaults to process.cwd()
  settingsDir: "./configs",
  // selects settings.{env}.*, defaults to process.env.ENV ?? "dev"
  env: process.env.ENV ?? "dev",
});
// config is fully typed: config.Auth.Secret, config.Database.Url

Typesafe:

loadConfig is async (secret fetches are async) and memoized by (schema, settingsDir, env). Pass { fresh: true } to bypass the cache, or call resetConfigCache()

Options

loadConfig(schema, opts) takes an optional second argument:

| Field | Type | Default | Purpose | | --- | --- | --- | --- | | settingsDir | string | process.cwd() | Directory holding the settings.*.{yaml,toml} files | | env | string | process.env.ENV ?? "dev" | Selects the settings.{env}.* layer | | overrides | Record<string, unknown> | {} | Highest-priority layer, deep-merged on top of everything | | envNestingDelimiter | string | "__" | Separator between schema leaf segments in env-var names | | fresh | boolean | false | Bypass the memoized result and force a fresh load |

overrides and environment variables are two different layers (tiers 1 and 2) with two different shapes:

  • overrides is a nested object passed in code, { Database: { Url: "…" } }. It is the process.env-free way to force a value, e.g. in tests
  • Env vars are flat, __-delimited strings, Database__Url=…. See Environment-variable overrides

Files

loadConfig reads, for a given settingsDir and env, any of these that exist:

| Base name | Purpose | | --- | --- | | settings.{toml,yaml} | Base configuration (lowest priority) | | settings.{env}.{toml,yaml} | Environment-specific overrides | | .secrets.{toml,yaml} | Local secrets, highest-priority file layer |

How it resolves a value (priority, highest to lowest)

The order is 1:1 with the Python settings_customise_sources:

1. opts.overrides
2. env vars (schema leaf)    Database__Url -> { Database: { Url } }
3. Azure Key Vault secrets   (when configured)
4. Google Parameter Manager secrets
5. .secrets.{toml,yaml}
6. settings.{env}.{toml,yaml}
7. settings.{toml,yaml}

Within the file tiers every TOML file outranks every YAML file (*toml_sources before *yaml_sources), and base-name order is .secrets > settings.{env} > settings, so the full file priority, high to low:

.secrets.toml > settings.{env}.toml > settings.toml >
.secrets.yaml > settings.{env}.yaml > settings.yaml

The secret tiers (3 to 4) outrank every settings file (5 to 7), so a "<Configured-in-GoogleParameterManager>" placeholder written in settings.dev.yaml is replaced by the real secret payload

Merge semantics

Applied lowest to highest (pydantic-settings deep_update):

  • Plain objects deep-merge recursively
  • Every other value, including arrays, is replaced wholesale by the higher-priority layer, no array concatenation

Environment-variable overrides

Selection is schema-driven: the loader walks your schema's leaf paths and looks up the env var named by joining each path with the nesting delimiter (__). A Database.Url leaf is overridden by Database__Url, mapped back to { Database: { Url } }. Matching is case-sensitive against your verbatim keys, so there is no prefix and ambient vars like PATH never leak in

Database__Url=postgres://… node app.js

Values stay raw strings, coercion to number or bool is the schema's job (z.coerce.number()). Change the delimiter per call:

await loadConfig(Schema, { envNestingDelimiter: "." }); // Database.Url=…

Only leaves declared in the schema are overridable, and only zod and valibot schemas can be introspected. For any other vendor, env overrides quietly do not apply rather than failing the load

ENV selects the environment and is not itself an override. To force a value from code without touching process.env, pass a nested object to overrides instead (tier 1, outranks env vars)

The Secrets block

Any settings file may carry a top-level Secrets array. It is metadata, stripped before validation, and drives the providers. If more than one file declares it the last in execution order wins (TOML before YAML); in practice exactly one file does

Secrets:
  - Provider: GoogleParameterManager
    GCPProject: my-project-dev
    GCPParameterName: my-app-config
    # GCPParameterVersion: v5   # OPTIONAL, omit for auto-latest
  • Auth via Application Default Credentials
  • Without GCPParameterVersion the provider lists all versions and picks the newest createTime
  • The payload is parsed JSON-first, YAML-fallback, deep-merged whole at tier 4
  • Any failure (no versions, list/fetch error, unparseable payload) throws

Azure:

Secrets:
  - Provider: AzureKeyVault
    AzureKeyVault: my-vault
    Mode: per-leaf        # default, one GET per schema leaf, Auth.Secret -> Auth--Secret
    # Mode: single-blob   # one secret holding a JSON/YAML blob
    # SecretName: config  # single-blob secret name (default "config")
  • per-leaf (default): introspect the schema's leaf paths, GET one secret per leaf with .--, reassemble nested. Missing secrets are skipped (fall through to a lower layer)
  • single-blob: GET one secret whose payload is a JSON/YAML blob (any vendor)
  • Auth via DefaultAzureCredential. SDKs are loaded only when Azure is configured

SecretFiles is reserved, declaring it throws NotImplementedProviderError

Validation

Validation runs through the Standard Schema interface, so zod, valibot, and arktype all work. On issues a ConfigValidationError lists each path: message, on success the typed result.value is returned

Unknown-key handling is your schema's call: zod strips unknown keys unless you use z.looseObject({...}) or .loose(), and any key you want kept must be declared or it is stripped

Azure per-leaf mode reads your schema's leaf paths to derive secret names, which works on zod and valibot only (via their JSON Schema emitters). Any other vendor must use Mode: single-blob

Errors

Everything surfaces fast as a thrown subclass of ChabisConfigError. The library logs nothing

| Error | When | | --- | --- | | SecretsConfigError | Malformed Secrets block | | ProviderError | GPM/Azure auth/list/fetch/parse failure (wraps the cause) | | IntrospectionError | Azure per-leaf with a vendor that has no JSON-Schema emitter | | ConfigValidationError | Standard Schema validation issues | | NotImplementedProviderError | SecretFiles provider requested | | PlaceholderError | assertNoPlaceholders found an unresolved placeholder |

assertNoPlaceholders(config) is an opt-in helper that throws if any leaf string still starts with "<Configured-in-", guarding against a provider key that never matched a field

More

See examples/zod and examples/valibot for runnable setups