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

@x12i/client-config

v1.2.0

Published

Framework-agnostic browser runtime config: typed schemas, validation, local persistence, sensitive-field handling, and optional encryption.

Downloads

190

Readme

@x12i/client-config

Client-side runtime configuration for browser apps (especially SPAs): typed schemas, validation, normalization, namespaced persistence, sensitive-field handling, optional encryption, migrations, import/export, .env-style text import, and configuration reporting helpers.

What it is

  • A small, framework-agnostic library for operator-editable settings after deploy (API base URL, tenant id, feature flags, demo tokens, etc.).
  • Schema-first: defaults, validation, and TypeScript inference from field definitions.
  • Storage adapters for localStorage, sessionStorage, and in-memory (tests, SSR fallbacks).
  • Envelope-based persistence (version, namespace, timestamps, encryption metadata) — not a raw JSON blob only.
  • Hooks for load/save/reset/change/migration/validation errors.
  • Pluggable encryption (Web Crypto AES-GCM helpers included) for stored bytes only — not a threat model against XSS.

What it is not

  • Not secure secret storage, a vault, or a substitute for server-side secret management.
  • Not a replacement for backend .env or identity/auth.
  • Not a cloud config service (that can be a future adapter).
  • Not React-specific — use a thin wrapper package later (e.g. @x12i/client-config-react) for providers and hooks.

Real secrets that would cause serious harm if exposed should not rely on browser-only protection.

Installation

npm install @x12i/client-config

Quick start

import {
  createClientConfig,
  createLocalStorageAdapter,
  defineSchema,
  field,
} from "@x12i/client-config";

const schema = defineSchema({
  apiUrl: field.string({
    label: "API base URL",
    default: "",
    required: true,
    trim: true,
  }),
  tenantId: field.string({ default: "", label: "Tenant" }),
  debug: field.boolean({ default: false }),
});

const store = createClientConfig({
  namespace: "x12i.myapp.config",
  schema,
  storage: createLocalStorageAdapter({ fallbackToMemory: true }),
});

await store.load();
store.setField("apiUrl", "https://api.example.com");
await store.save();

Schema definition

Use field.* helpers so TypeScript can infer store.get():

| Kind | Helper | Notes | |------------|---------------|--------| | string | field.string | trim, emptyAsUndefined, ttlMs, persist | | number | field.number | coerceFromString (recommended for .env imports) | | boolean | field.boolean | coerceFromString (recommended for .env imports) | | enum | field.enum | values, caseInsensitive | | string[] | field.stringArray | | | json | field.json | arbitrary JSON-serializable default |

Common options on all fields: label, description, default, required, sensitive, persist (default true), validate, normalize, ttlMs, envKey (explicit env variable name for importEnv / analyzeEnv).

Invalid defaults throw at defineSchema time (INVALID_SCHEMA).

Storage adapters

  • createLocalStorageAdapter({ fallbackToMemory?: boolean })
  • createSessionStorageAdapter({ fallbackToMemory?: boolean })
  • createMemoryAdapter()

If storage is unavailable and you did not enable fallbackToMemory, operations that write will surface structured errors (STORAGE_UNAVAILABLE, STORAGE_QUOTA, etc.).

The storage key is buildStorageKey(namespace) (for example @x12i/client-config:my.app).

Sensitive fields

Mark fields with sensitive: true:

  • store.getRedacted() and safe / default export mask values for display and sharing.
  • Use full export with { mode: "full", includeSensitive: true } only when you explicitly want secrets in the JSON.

This reduces casual exposure; it does not make values secret in the browser.

Encryption caveats

  • Encryption changes the stored representation (full encrypts the inner envelope JSON; field encrypts marked fields with a prefixed ciphertext string).
  • At runtime the app still holds decrypted values in memory.
  • XSS, malicious extensions, or physical access defeat client-only encryption.

Provide an EncryptionProvider or use createAesGcmEncryptionProvider + importAesGcmKey / deriveAesGcmKeyFromPassphrase.

If decryption fails, you get DECRYPTION_FAILED (recoverable); clear() / reset() can recover.

Migration

Pass migrations: [{ toVersion, migrate(config, ctx) }] to createClientConfig. On load, stored schemaVersion is compared to the schema’s version; steps run in order until the config matches the current version.

hooks.onMigration reports success or failure.

Import / export

  • store.exportJson({ mode: "safe" | "full" | "partial", keys?, includeSensitive? })
  • store.importJson(json, { mode: "replace" | "merge" | "mergeKnown", save?: boolean, source?: string })

Import accepts either a full export payload or a bare { config: { ... } } object.

.env-style text

Values in env files are always strings. Map them to schema fields with:

  • store.importEnv(text, options?) — same merge modes as importJson (replace | merge | mergeKnown), plus optional strictParse (default false). Lenient parsing skips bad lines and still applies valid entries; strict mode rejects the whole import if any line fails to parse.
  • store.validateEnv(text, options?) — same merge/parse options but does not mutate the store; returns ValidationResult plus parseErrors and collisionErrors.
  • analyzeEnv(schema, text) — parse and describe mapping: matchedFields, missingInEnv, unknownEnvKeys, collisionErrors, previews (sensitive values masked), without a store.

Variable name resolution (first match wins per field): optional envKey on the field, then the schema key as-is, then a generated alias (apiUrlAPI_URL via schemaKeyToEnvAlias).

Collisions: if one env variable would map to more than one schema field, import/validate fails with ENV_KEY_COLLISION.

Supported in the parser: KEY=value, optional export prefix, # comments, double/single-quoted values (with \\, \", \n, \r, \t in double quotes). Not supported: $VAR expansion, multiline values, line continuations.

Types: json fields accept a JSON object/array string (trimmed, starts with { or [). string[] fields accept comma-separated values.

Runtime reporting: store.getConfigurationReport() returns per-field required, valid, error, isEmpty, satisfied, atDefault for the current in-memory config (independent of .env). Lower-level: buildConfigurationReport(schema, config).

Standalone utilities (also used internally): parseDotenv, mapEnvEntriesToIncoming, mergeImportIncoming, schemaKeyToEnvAlias — useful for tests or custom pipelines without loading the store.

Common patterns

  • Namespaced apps: one namespace per app or tenant slice (x12i.reportix.local).
  • Diagnostics: getClientConfigDiagnostics(adapter) for storage availability and Web Crypto.
  • Change tracking: store.getStatus() exposes dirty, changedKeys, lastSavedAt, lastLoadedAt, lastError, etc.
  • Subscribe: store.subscribe((status) => { ... }) for UI updates.
  • Non-persisted fields: persist: false — not written on save; after a full page reload they come from defaults unless you use session storage or another mechanism.

Security notes

  1. Anything the SPA can read, the user (or attacker with script access) can read.
  2. Do not describe this package as vault-grade or XSS-safe encryption.
  3. Prefer backend-held secrets, short-lived tokens, and proxies for high-value credentials.
  4. Use masking/redacted exports in logs and support tooling.

API overview

| Method | Purpose | |--------|---------| | load() | Read storage, migrate, normalize, validate | | get() / getField / getMany | Read current config | | setField / setMany / replace | Update (returns ValidationResult) | | validate / validateField / validateImport | Validation only | | save() | Persist (optional validateOnSave / per-call validate) | | reset(fields?) | Revert to defaults | | clear() | Remove storage key + defaults | | exportJson / importJson | Portability | | importEnv / validateEnv | .env text → config | | getConfigurationReport | Per-field required/default/valid summary | | getStatus / subscribe / getRedacted | Status and UI |

Constructor options include encryption, migrations, hooks, strictLoad, and validateOnSave.

Development and tests

npm run typecheck   # TypeScript
npm test            # Vitest (core store + `.env` / reporting)
npm run build       # ESM + declarations in dist/

npm publish runs prepublishOnly, which executes typecheck, tests, and build before the tarball is created.

License

MIT