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

@streetjs/config

v1.1.0

Published

StreetJS configuration foundation: typed, schema-validated, immutable configuration with pluggable sources (env, JSON, YAML, TOML, custom providers), namespaces, secret masking, descriptive startup validation, and a secret store (pluggable providers, cach

Readme

@streetjs/config

The configuration foundation for StreetJS: typed, schema-validated, immutable configuration with pluggable sources (environment variables, JSON, YAML, TOML, and custom providers), namespaces, secret masking, and descriptive startup validation.

Zero runtime dependencies. Built on Node.js core only, matching the StreetJS minimal, carefully curated dependency footprint. Generic and reusable by any application — not tied to any particular StreetJS package.

npm install @streetjs/config

Why

Every StreetJS package (runtime-http, auth, database, cache, jobs, metrics, …) and every application needs the same thing: load configuration from the environment and files, validate it once at startup with clear errors, expose it as a typed and immutable object, and never leak secrets. @streetjs/config provides exactly that, once, so each package can declare its own schema and consume a validated result.

Quick start

import { createConfig, s, type Infer } from '@streetjs/config';

const config = await createConfig()
  .schema({
    port: s.number({ integer: true, min: 1, max: 65535 }).default(3000),
    logLevel: s.enum(['debug', 'info', 'warn', 'error'] as const).default('info'),
    database: {
      url: s.url({ protocols: ['postgres', 'postgresql'] }).secret(),
      poolSize: s.number({ integer: true, min: 1 }).default(10),
    },
    corsOrigins: s.array(s.url({ protocols: ['https'] })).default([]),
    requestTimeout: s.duration().default(30_000), // milliseconds
  })
  // Sources are applied in order; later sources override earlier ones.
  .json('config.json', { optional: true })
  .env({ prefix: 'APP_' })   // APP_PORT, APP_DATABASE__URL, APP_DATABASE__POOL_SIZE
  .load();                    // validates everything; throws one aggregated error on failure

config.get('port');                          // number (typed)
config.get('database.url');                  // string (dotted path)
config.namespace('database').get('poolSize'); // number
config.serialize({ format: 'flat' });        // secrets masked

Features

  • Typed access — get() is strongly typed from the schema; nested/dotted paths supported.
  • Schema validation with defaults, required, optional, nested, and custom checks.
  • Immutable after load — the resolved config is deep-frozen. Opt-in reload().
  • Sources / providers — env, JSON, YAML, TOML, in-memory objects, and your own.
  • Deep merging + precedence — later providers override earlier; nested objects merge.
  • Namespaces — config.namespace('database') scopes reads without copying.
  • Secret handling — secret values are masked in serialize()/toJSON() and redacted in errors; never logged.
  • Environment detection — NODE_ENV normalized to development | test | staging | production.
  • Descriptive errors — every failing field reports key, source, invalid value, expected type, and a human-readable explanation, aggregated into one error.
  • Value transformation — .transform() post-validation mapping.
  • Metadata — config.metadata(path) reports type, source, secret, required, defaulted.

Validation types

s.string, s.number, s.boolean, s.enum, s.array, s.object, s.duration, s.url, s.path, s.hostname, s.ip, s.email, and s.custom.

| Field | Coercion / notes | |---|---| | s.string({ minLength, maxLength, pattern, trim }) | trims by default | | s.number({ min, max, integer }) | coerces numeric strings | | s.boolean() | accepts true/false/1/0/yes/no/on/off | | s.enum([...] as const) | typed literal union | | s.array(itemField, { delimiter, minItems, maxItems }) | splits "a,b,c" from env | | s.object() | opaque object (use a nested shape for typed nesting) | | s.duration() | "500ms", "2s", "5m", "1h", "1d", or a number → milliseconds | | s.url({ protocols }) | validates + optional protocol allowlist | | s.path() | non-empty, no null byte | | s.hostname() | RFC-1123 | | s.ip(4 \| 6?) | IPv4/IPv6 (via node:net) | | s.email() | pragmatic RFC check | | s.custom(validate) | supply (raw) => Outcome<T> |

Chainable on every field: .default(v), .optional(), .secret(), .describe(text), .check(fn), .transform(fn).

Configuration sources

createConfig()
  .schema(schema)
  .object({ port: 3000 }, 'defaults') // in-memory
  .file('config.toml', { optional: true }) // dispatch by extension
  .json('config.json') .yaml('config.yaml') .toml('config.toml')
  .env({ prefix: 'APP_', nestingDelimiter: '__', map: { DATABASE_URL: 'database.url' } })
  .provider(myCustomProvider) // anything implementing { name, load(): object }
  .load();

Environment convention: APP_DATABASE__POOL_SIZE with prefix APP_ and delimiter __ maps to database.poolSize — each segment is camelCased, so POOL_SIZE matches a camelCase schema key. An explicit map entry overrides the convention.

Custom providers implement a two-member interface; no core changes required:

import type { Provider } from '@streetjs/config';
const remoteProvider: Provider = {
  name: 'remote',
  async load() { return await fetchConfigFromVault(); }, // returns a nested object
};

Supported file syntax (honest subset)

JSON is parsed natively. YAML and TOML are parsed by dependency-free parsers covering the common configuration subset — documented here so there are no surprises:

  • YAML: comments, --- document start, indentation-based nested maps, block sequences (- item, incl. - key: value maps), scalars (string/number/bool/null), single/double-quoted strings, and inline flow ([a, b], {a: 1}). Not supported: anchors/aliases, multiline block scalars (|, >), and multiple documents.
  • TOML: comments, [tables] and [a.b] nesting, dotted keys, basic/literal strings, integers/floats (with _), booleans, single-line arrays, and inline tables. Not supported: [[array-of-tables]], multiline strings/arrays, and datetimes.

For configuration beyond these subsets, supply the parsed object through a custom provider (using your preferred parser).

Secrets

Mark a field .secret(). Its value is:

  • masked in serialize() and toJSON() (default mask ********),
  • redacted (<redacted>) in validation errors — the value never appears in a thrown error or its message,
  • never logged — the package performs no logging of values.

serialize({ includeSecrets: true }) opts out of masking for trusted local inspection.

Errors

load() throws a single ConfigValidationError aggregating all failures. Each issue carries { key, source, invalidValue, expectedType, message, secret }. Use .validate() for a non-throwing { ok } | { ok: false, issues } result.

Configuration validation failed (2 issues):
  • database.url — required configuration value is missing
      expected: url
      received: (absent)
      source:   default/absent
  • port — value 99999 is above maximum 65535
      expected: number <= 65535
      received: "99999"
      source:   env:port

Public API

createConfig() · .schema() · .provider() / .env() / .file() / .object() · .load() · .validate() · then on the result: .get() · .has() · .keys() · .namespace() · .metadata() · .serialize() · .toJSON() · .freeze() · .reload() (when enabled).

See ARCHITECTURE.md for module layout and design notes, and src/examples/integration.ts for a package-integration example.

License

MIT © street contributors