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

fullenvstruct

v1.0.0

Published

Read a flat string map (mainly environment variables) into a typed struct from a declarative spec, with required / default / demo fallbacks. One file, zero dependencies.

Readme

fullenvstruct

Read values out of a flat string map into a typed struct, following a declarative spec — and declare which of them are required, which have defaults, and which fall back to a demo value.

The intended use — the one it is named and designed for — is environment variables: renaming SCREAMING_SNAKE_CASE keys into the property names your code actually wants, and getting a precise TypeScript type where required keys are non-optional and the rest are not. Nothing in the implementation is specific to them, though. The source is just a Record<string, string | undefined>, so process.env, import.meta.env, a parsed .env, or any flat string map works the same.

One file, zero dependencies, no globals.

import { parseEnv, type EnvVarSpec } from "fullenvstruct";

const spec = [
  { key: "VITE_FIREBASE_API_KEY", as: "apiKey", require: true },
  { key: "VITE_REGION", as: "region", default: "asia-northeast1" },
  { key: "VITE_FIRESTORE_EMULATOR_HOST", as: "emulatorHost" },
] as const satisfies EnvVarSpec[];

const config = parseEnv(spec, import.meta.env);
//    ^? { apiKey: string; region: string; emulatorHost?: string }

config.apiKey.toUpperCase();   // no undefined check needed — require: true guarantees it
if (config.emulatorHost) { /* ... */ }

The type is the point: require: true and default are what make a property non-optional, so the compiler stops asking you to null-check the things you already declared mandatory.

as const satisfies EnvVarSpec[] is what makes the names survive into the type. Without as const the spec widens to string and you get {}.

Install

npm install fullenvstruct   # or: pnpm add fullenvstruct

API

parseEnv(spec, env, options?)

| Param | Type | Meaning | | --------- | ------------------------------------- | -------------------------------------------------------------- | | spec | readonly EnvVarSpec[] | The declarations (below) | | env | Record<string, string \| undefined> | The source: process.env, import.meta.env, or a plain object | | options | { demoKey?: string } | Which key decides demo mode (see below) |

The source is always an explicit argument — the library never reaches for process.env or import.meta on its own, so the same build runs in Node, in the browser, and in tests where you hand it a fixture object.

EnvVarSpec

type EnvVarSpec = {
  key: string;       // the key to read from the source
  as?: string;       // the property name in the result; defaults to `key`
  require?: boolean; // throw if it cannot be resolved
  default?: string;  // value to use when unset
  onDemo?: string;   // value to use when unset *and* in demo mode
};

Each value is resolved in this order: the source value → onDemo (demo mode only) → default. An unset or empty-string value counts as absent at every step.

  • require: true or a default makes the property non-optional in the result type.
  • Everything else is optional, and is omitted from the result entirely when unresolved.
  • If any require: true entry is unresolved, parseEnv throws — listing all of them at once, so you fix them in one pass instead of one restart per variable.
  • Keys not in the spec are ignored, so unrelated secrets sitting in process.env can't leak into the struct by accident.
  • Values are returned as-is: no trimming, no coercion to number/boolean, no parsing.

Demo mode

Pass demoKey and the value of that key decides it: when it is exactly "demo", unset variables fall back to their onDemo value. It exists so a demo/sandbox build can run with placeholder config without loosening the require rules of the real build.

const spec = [
  { key: "API_KEY", as: "apiKey", require: true, onDemo: "demo-key" },
] as const satisfies EnvVarSpec[];

parseEnv(spec, { MODE: "demo" }, { demoKey: "MODE" }); // { apiKey: "demo-key" }
parseEnv(spec, { MODE: "production" }, { demoKey: "MODE" }); // throws

A real value in the source always wins over onDemo.

EnvStruct<T> / EnvSource / ParseEnvOptions

EnvStruct<typeof spec> is the struct type parseEnv returns — use it when you want to name the config type in a signature.

Smaller sibling

If you only need the renaming and the type, with no required / default / demo declarations, simpleenvstruct is the same spec shape with everything optional and no throwing.

Development

Built with Bun. tsc is used only to emit dist and the .d.ts.

bun install
bun test          # unit tests
bun run typecheck # includes the type-level tests in test/
bun run build     # emit dist/

License

MIT


fullenvstruct(日本語)

宣言的な spec に従って、フラットな文字列マップから値を読み出し、型のついた構造体にして返す。 あわせて「どれが必須か」「どれに既定値があるか」「demo 時に何にフォールバックするか」を spec の中で宣言できる。

主に想定している用途は環境変数で、名前も設計もそこに合わせてある。 VITE_FIREBASE_API_KEY のようなキー名をコード側で使いたいプロパティ名(apiKey)に付け替え、 必須のものは非 optional、それ以外は optional という正確な型を与える。 ただし実装に環境変数固有の処理はない。読み出し元は Record<string, string | undefined> に 過ぎないので、process.env でも import.meta.env でも、パース済みの .env でも、 任意のフラットな文字列マップでも同じように動く。

1ファイル・依存ゼロ・グローバル参照なし。

const spec = [
  { key: "VITE_FIREBASE_API_KEY", as: "apiKey", require: true },
  { key: "VITE_REGION", as: "region", default: "asia-northeast1" },
  { key: "VITE_FIRESTORE_EMULATOR_HOST", as: "emulatorHost" },
] as const satisfies EnvVarSpec[];

const config = parseEnv(spec, import.meta.env);
// 型: { apiKey: string; region: string; emulatorHost?: string }

config.apiKey.toUpperCase(); // require: true なので undefined チェック不要

型がこのライブラリの主眼で、require: truedefault が「値の存在が保証される」ことを 型に伝える。必須だと宣言したものに対してコンパイラが null チェックを要求しなくなる。

振る舞い

  • 解決の優先順は 読み出し元の値 → onDemo(demo モード時のみ)→ default。 未設定・空文字列はどの段階でも「無い」とみなす
  • require: true または default があるプロパティは非 optionalになる
  • それ以外は optional で、解決できなければキーごと省かれる
  • require: true が解決できない場合は throw する。未解決のものを全件まとめて報告するので、 1つ直して再起動、を繰り返さずに済む
  • spec にないキーは無視するので、process.env の無関係な秘密情報が構造体に紛れ込まない
  • 値は加工しない(trim もしないし、number/boolean への変換もしない)
  • 読み出し元は必ず引数で渡す。ライブラリ側からグローバルを触らないため、Node・ブラウザ・ テスト(フィクスチャを渡す)で同じコードが動く

demo モード

demoKey を渡すと、そのキーの値が "demo" のときだけ、未設定の変数が onDemo の値に フォールバックする。本番の require 条件を緩めないまま、demo ビルドだけプレースホルダ設定で 動かすための仕組み。読み出し元に実際の値があれば、常にそちらが優先される。

小さい方

別名付けと型付けだけでよく、必須・既定値・demo の宣言が要らない場合は simpleenvstruct を使う。 spec の形は同じで、全プロパティ optional・throw なしになる。