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.
Maintainers
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 fullenvstructAPI
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: trueor adefaultmakes the property non-optional in the result type.- Everything else is optional, and is omitted from the result entirely when unresolved.
- If any
require: trueentry is unresolved,parseEnvthrows — 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.envcan'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" }); // throwsA 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: true と default が「値の存在が保証される」ことを
型に伝える。必須だと宣言したものに対してコンパイラが 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 なしになる。
