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

@setu-ts/config-plugin

v0.10.0

Published

Configuration with env loading, variable expansion, and schema validation

Readme

@setu-ts/config-plugin

Configuration management plugin for Setu-TS with strict .env parsing, variable expansion, and structurally compatible schema validation.

Installation

npm install @setu-ts/config-plugin
# Deno: deno add npm:@setu-ts/config-plugin

Quick Start

import { createApplication } from '@setu-ts/kernel';
import { ConfigPlugin } from '@setu-ts/config-plugin';
import { RuntimePlugin } from '@setu-ts/runtime';
import { CAPABILITIES } from '@setu-ts/common';
import type { IConfig } from '@setu-ts/common';

const app = createApplication();

app.register(RuntimePlugin());
app.register(
  ConfigPlugin({
    envFilePath: ['.env.local', '.env'],
  }),
);

await app.start();

// Access configuration
const config = app.services.get<IConfig>(CAPABILITIES.CONFIG);
const port = config.get('PORT', { default: '3000' });

Configuration a plugin needs at construction

Factory arguments are evaluated before app.start() registers ConfigPlugin and loads .env. Load a snapshot first when another plugin needs those values at construction, then register that same snapshot with instance; it is used verbatim without loading again:

import { createApplication } from '@setu-ts/kernel';
import { createRuntimeServices, RuntimePlugin } from '@setu-ts/runtime';
import { ConfigPlugin, loadConfig } from '@setu-ts/config-plugin';
import { AuthPlugin } from '@setu-ts/auth-plugin';

const runtime = createRuntimeServices();
const config = await loadConfig(runtime, { envFilePath: '.env', envFileOptional: true });
const app = createApplication({
  plugins: [
    RuntimePlugin(),
    ConfigPlugin({ instance: config }),
    AuthPlugin({ jwt: { secret: config.getOrThrow<string>('JWT_SECRET') } }),
  ],
});
await app.start();

Options

| Option | Type | Default | Description | | ------------------ | ----------------------------------- | ----------- | ----------------------------------------------------------------------------- | | envFilePath | string \| readonly string[] | undefined | Path(s) to .env files. No file loading when absent. | | envFileOptional | boolean | false | Skip an ABSENT configured path instead of throwing (unreadable still throws). | | validationSchema | StructuralSchema<T> | undefined | Zod-compatible whole-snapshot schema for startup validation. | | sections | readonly ConfigSection<unknown>[] | undefined | Typed, declared-key sections to validate and cache at startup. | | expandVariables | boolean | true | Expand ${NAME} references in values. | | instance | IConfig | undefined | An already-loaded snapshot to register verbatim; nothing is read. | | diagnostics | ConfigDiagnosticsOptions | undefined | Opt-in value-free provenance; absent registers a disabled source. |

Configuration Precedence

Values are merged in the following order (highest precedence first):

  1. Environment variables (runtime.env)
  2. Earlier file paths (.env.local overrides .env)
  3. Later file paths

undefined entries in runtime.env are filtered out.

Dotenv Parsing

Configured files are parsed strictly at startup. Blank lines and comments are ignored; export prefixes, quoted values, common double-quoted escapes, empty values, and whitespace-delimited inline comments are supported. Malformed entries, invalid keys, and unterminated quotes fail startup with a line number. Error messages never include the rejected value.

Variable Expansion

When expandVariables is true (default), ${NAME} references in values are resolved against the final merged configuration:

# .env
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_URL=postgresql://${DATABASE_HOST}:${DATABASE_PORT}/mydb
  • Supports recursive references.
  • Resolves references once, after every file and runtime.env have been merged.
  • Detects cycles spanning any combination of sources and throws with a clear error.
  • Fails for missing variable references.
  • Never uses eval or Function.

Validation with Zod

Pass a Zod schema to validationSchema for type coercion and validation at startup:

import { z } from 'zod';

const AppConfigSchema = z.object({
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  DEBUG: z.coerce.boolean().default(false),
});

app.register(
  ConfigPlugin({
    envFilePath: ['.env.local', '.env'],
    validationSchema: AppConfigSchema,
  }),
);
  • Coercions and defaults are preserved in the stored configuration.
  • The schema's parsed output must be a non-null, non-array object.
  • Validation errors do not disclose secret values.

Typed Configuration Sections

Use a section when related settings should be returned with the type that its schema validated. Sections are validated and cached during startup; getConfigSection never parses or asserts a value at read time.

import { CAPABILITIES, type IConfig } from '@setu-ts/common';
import { ConfigPlugin, defineConfigSection, getConfigSection } from '@setu-ts/config-plugin';
import { z } from 'zod';

const database = defineConfigSection({
  prefix: 'DATABASE_',
  keys: ['URL', 'POOL_SIZE'],
  schema: z.object({
    URL: z.string().url(),
    POOL_SIZE: z.coerce.number().int().positive(),
  }),
});

app.register(ConfigPlugin({ sections: [database] }));

const config = app.services.get<IConfig>(CAPABILITIES.CONFIG);
const settings = getConfigSection(config, database);
// settings.POOL_SIZE is a number, validated before the application started.

keys contains prefix-stripped names: prefix: 'DATABASE_' plus keys: ['URL'] reads the flat DATABASE_URL key and supplies { URL } to the schema. The list is explicit because IConfig supports named reads but deliberately has no key-enumeration method; this also lets sections validate an arbitrary instance snapshot. A missing key is omitted, so the schema decides whether it is optional. A section schema is not interchangeable with validationSchema: the latter receives the whole flat snapshot, while the former receives only its declared, prefix-stripped keys.

Configuration Provenance

diagnostics is the explicit opt-in to value-free provenance for the approved keys only:

app.register(
  ConfigPlugin({
    envFilePath: ['.env.local', '.env'],
    diagnostics: {
      enabled: true, // the literal true: an acknowledgement, not a toggle
      keys: { PORT: 'port', DATABASE_URL: 'database' },
      files: { '.env.local': 'dotenv-local', '.env': 'dotenv' },
    },
  }),
);

During the ONE load already performed — never a second pass — the loader records where each approved key's final value was observed to come from: the source category and approved source aliases, evidenced precedence displacement (overriddenSourceAliases, lowest first), ${NAME} expansion references (expanded + approved referenceAliases), and a schema effect derived only from input/output property presence. introduced reports that a key appeared only after schema parsing; it never names a mechanism, because a default and a transform are indistinguishable by presence. No value, hash, length, raw key name, or file path is ever retained, and unapproved keys are never observed at all.

loadConfig(runtime, { diagnostics }) builds the record with the snapshot; ConfigPlugin({ instance, diagnostics }) adopts that exact instance's record — keeping real origins — or reports an opaque injected instance as unknown with no presence flag and no read of it. An adopted record is served as the loader built it, under the loadConfig call's approvals; the plugin's own keys and files then only enable the source and govern the opaque case, so pass the same policy to both calls. Provenance adds no read to any configuration object. The plugin always registers the source under CAPABILITIES.CONFIG_DIAGNOSTICS: absent diagnostics answers disabled. The Diagnostics Connector serves it at GET /v1/config — see docs/diagnostics-protocol.md.

Hot Reload

Deferred. The current runtime contract has no file-watching abstraction. Configuration is an immutable application-startup snapshot.

Edge Runtimes

On edge platforms (Cloudflare Workers, etc.) where runtime.fs is undefined, envFilePath must not be set. Attempting to do so throws a clear startup error:

ConfigPlugin: envFilePath requires a runtime with filesystem support.

API Reference

ConfigPlugin(options?)

Creates the configuration plugin. Consumes CAPABILITIES.RUNTIME, provides CAPABILITIES.CONFIG.

ConfigPluginOptions

import type { IConfig } from '@setu-ts/common';
import type { ConfigSection, StructuralSchema } from '@setu-ts/config-plugin';

interface ConfigPluginOptions {
  readonly envFilePath?: string | readonly string[];
  readonly envFileOptional?: boolean;
  readonly validationSchema?: StructuralSchema<unknown>;
  readonly sections?: readonly ConfigSection<unknown>[];
  readonly expandVariables?: boolean;
  readonly instance?: IConfig;
}

StructuralSchema<T>

Minimal schema interface compatible with Zod's parse(unknown) API.

ConfigSection<T>

The typed section declaration returned by defineConfigSection. It carries prefix, prefix-stripped keys, and a StructuralSchema<T>. Pass it in ConfigPluginOptions.sections, then retrieve its validated output with getConfigSection(config, section).

IConfig (from @setu-ts/common)

interface IConfig {
  get<T>(key: string): T | undefined;
  get<T>(key: string, options: { readonly default: T }): T;
  getOrThrow<T>(key: string): T;
  has(key: string): boolean;
}

Without Schema

When validationSchema is not provided, all values remain as strings from the environment and .env files.

Dependencies

  • @setu-ts/common (workspace)
  • Consumer-supplied structural schema such as Zod (optional; not a package dependency)

Exports

| Export | Kind | | -------------------------- | --------- | | ConfigPlugin | function | | defineConfigSection | function | | getConfigSection | function | | loadConfig | function | | ConfigDiagnosticsOptions | interface | | ConfigPluginOptions | interface | | ConfigSection | interface | | StructuralSchema | interface |

Generated from the package barrel by pnpm run docs:exports; pnpm run check:docs fails when it drifts.

Full API

Every export and option is documented in PUBLIC_API.md.