@absxn/process-env-parser 中文文档教程
process-env-parser
直接且类型安全的环境变量验证、解析和 node
应用程序的调试。
const result = parseEnvironmentVariables({
API_KEY: { mask: true, default: null },
DATABASE_URL: { parser: s => new URL(s), mask: Mask.url("password") },
LISTEN_PORT: { parser: parseInt, default: 3000 },
SERVICE_NAME: {}
});
if (result.success) {
// Sample success output
console.table(result.envPrintable);
// ┌──────────────┬─────────────────────────────────────────────────────┐
// │ (index) │ Values │
// ├──────────────┼─────────────────────────────────────────────────────┤
// │ API_KEY │ '<masked>' │
// │ DATABASE_URL │ '<masked: "mysql://user:*****@localhost:3306/app">' │
// │ LISTEN_PORT │ '8080' │
// │ SERVICE_NAME │ '"app"' │
// └──────────────┴─────────────────────────────────────────────────────┘
// Inferred type for successfully parsed environment
// {
// API_KEY: string | null
// DATABASE_URL: URL
// LISTEN_PORT: number
// SERVICE_NAME: string
// }
return result.env;
} else {
// Sample formatted output
console.log(Formatter.multiLine(result));
// API_KEY = <masked>
// DATABASE_URL = <parser: "Invalid URL: localhost">
// LISTEN_PORT = 3000 (default)
// SERVICE_NAME = <missing>
throw new Error("Could not parse environment variables");
}
- Rationale
- Installation and usage
- Examples
requireEnvironmentVariables()
: Simple usage with mandatory variablesparseEnvironmentVariables()
: Optional and parsed variables- Fail: Variable missing
- Fail: Parser throwing
- Mask
url()
urlPassword()
urlUsernameAndPassword()
- Combine
- Non-nullable
- Formatter
console.table()
- Oneliner
- Multi-line
Rationale
在每个过程开始时,有两个输入源可以影响 流程执行:程序参数和环境变量。
$ ENV_VAR_A=Hello ENV_VAR_B=World node app.js arg1 arg2 --arg3
为了构建可靠的软件并最大程度地减少运行时意外,您将 想要遵循快速失败设计 并尽早确保您的程序输入正确。 程序之后所做的一切都基于这些输入。
例如,确保将所需的数据库 URL 正确传递给 最开始的过程会清楚地提醒用户可能出现的问题, 而不是应用程序在 30 分钟后数据库连接时崩溃 是第一次完成。
这个库试图提供有用的工具来处理环境 启动的可变部分。
Installation and usage
$ npm install --save @absxn/process-env-parser
import {
parseEnvironmentVariables,
requireEnvironmentVariables
} from "@absxn/process-env-parser";
这两个函数返回相同的 Success | Fail
对象:
// Types roughly as follows, read code and inline documentation for details
type Success = {
success: true;
env: {
[variableName: string]:
| InferredParserFunctionReturnType // If `parser` option used
| InferredDefaultValueType // If `default` option used
| string; // No options used
};
envPrintable: {
// Human readable results for logging and debugging
// E.g. `ENV_VAR_A=<missing>, ENV_VAR_B="World", PASSWORD=<masked>`
[variableName: string]: string;
};
};
type Fail = {
success: false;
// Same as for Success
envPrintable: { [variableName: string]: string };
};
Examples
Success: Simple usage with mandatory variables
读取变量的最简单方法是使用 requireEnvironmentVariables(...variableNames: string[])
。 它读给定 变量,必须找到所有变量,并将它们的值作为字符串返回。
要成功,所有列出的变量都必须存在于环境中
Process startup
$ A=hello B=world node app
Code
// Type: Success | Fail
const result = requireEnvironmentVariables("A", "B");
if (result.success) {
console.table(result.envPrintable);
// ┌─────────┬───────────┐
// │ (index) │ Values │
// ├─────────┼───────────┤
// │ A │ '"hello"' │
// │ B │ '"world"' │
// └─────────┴───────────┘
// Type: { A: string, B: string }
// Value: { A: "hello", B: "world" }
return result.env;
} else {
// Wont get here since we gave both A and B in the startup
}
Success: Optional and parsed variables
如果您对变量有更复杂的设置,则可以使用 <代码>解析环境变量(配置:配置)。 这使您可以处理每个 具有附加功能的单独变量。
config
对象以变量名作为键,值是一个对象 指定如何处理该变量。
可用的选项是:
interface Config {
[variableName: string]: {
// If variable is not found, use this as its value. If `default` not
// given, variable is mandatory, in which case, a missing variable leads
// to Fail being returned. If default value was used, envPrintable will
// have " (default)" appended to the printable value.
default?: any;
// When variable is read, its value is passed first to the parser
// function. Return value of the parser is used as the variable value in
// the output. If the parser throws, the function will return a Fail
// object.
parser?: (value: string) => any;
// If `true`, the value of the variable is never shown in plain text in
// the `envPrintable` fields of the return object. Value is indicated as
// `<masked>`. If function, the argument is 1) return value of parser 2)
// environment variable value. Default value bypasses the function and gets
// displayed as `<masked> (default)`. Return value of the function is the
// value to be shown in `envPrintable`, formatted as <masked: "value">.
mask?: boolean | (value: any) => string;
};
}
要成功:
- All varibales with no
default
given must exist in the environment - Empty string
""
is considered as non-existing! - No
parser
may throw - Parser exceptions turn result into
Fail
and the exception message is captured in theenvPrintable
fields. See examples below.
默认值按原样使用,在给出解析器时也是如此,即默认值为 使用时不传递给解析器。
Process startup
$ REQUIRED=value PARSED=12345 node app
Code
// Ensure we return only valid numbers
function parser(s: string): number {
const p = parseInt(s);
if (isNaN(p)) {
throw new Error("Not a number");
} else {
return p;
}
}
const result = parseEnvironmentVariables({
REQUIRED: {},
PARSED: { parser },
OPTIONAL: { default: "OPTIONAL" }
});
if (result.success) {
console.table(result.envPrintable);
// ┌──────────┬────────────────────────┐
// │ (index) │ Values │
// ├──────────┼────────────────────────┤
// │ REQUIRED │ '"value"' │
// │ PARSED │ '1234' │
// │ OPTIONAL │ '"OPTIONAL" (default)' │
// └──────────┴────────────────────────┘
// Type: { REQUIRED: string, PARSER: number, OPTIONAL: "OPTIONAL" | string }
// Value: { REQUIRED: "value", PARSED: 1234, OPTIONAL: "OPTIONAL" }
return result.env;
} else {
// Will not get here
}
Fail: Variable missing
Process startup
$ VAR_A=value VAR_B= VAR_C="${X} ${Y} ${Z}" node app
警告——“无意义”字符串的特殊情况:
- Empty string:
VAR_B
is also considered as missing. I.e.process.env.VAR_B
does exist, but the parser considers""
equal to not set. - Blank string:
VAR_C
is also considered not set. In this case,X
,Y
,Z
are all""
, so the resulting value ofVAR_C
is two spaces," "
. If value is surrounded by spaces, e.g." A "
, the spaces are preserved as is through the parser.
Code
const result = requireEnvironmentVariables("VAR_A", "VAR_B", "VAR_C", "VAR_D");
if (result.success) {
// Won't get there
} else {
console.table(result.envPrintable);
// ┌─────────┬─────────────┐
// │ (index) │ Values │
// ├─────────┼─────────────┤
// │ VAR_A │ '"value"' │
// │ VAR_B │ '<missing>' │
// │ VAR_C │ '<missing>' │
// │ VAR_D │ '<missing>' │
// └─────────┴─────────────┘
}
Fail: Parser throwing
Process startup
$ NOT_ACTUAL_NUMBER=xyz node app
Code
function parser(s: string): number {
const p = parseInt(s);
if (isNaN(p)) {
throw new Error("Not a number");
} else {
return p;
}
}
const result = parseEnvironmentVariables({
NOT_ACTUAL_NUMBER: { parser }
});
if (result.success) {
// Won't get there
} else {
console.table(result.envPrintable);
// ┌───────────────────┬────────────────────────────┐
// │ (index) │ Values │
// ├───────────────────┼────────────────────────────┤
// │ NOT_ACTUAL_NUMBER │ '<parser: "Not a number">' │
// └───────────────────┴────────────────────────────┘
}
Mask
用于屏蔽输出变量部分的助手。
import { Mask } from "@absxn/process-env-parser";
url()
返回将掩码应用于给定 URL 部分的函数的函数。 有效的 URL 部分是 "hash"
、"hostname"
、"password"
、"pathname"
、 “端口”
、“协议”
、“搜索”
和 “用户名”
。 可以处理两个 URL 字符串和 URL 对象(来自 parser
或 default
)。
const result = parseEnvironmentVariables({
API_URL: { parser: s => new URL(s), mask: Mask.url("password", "path") }
});
对于 API_URL=https://user:pass@1.2.3.4/api/path
,envPrintable
将 包含 { API_URL: "https://user:*****@1.2.3.4/*****" }
。
urlPassword()
与 url("password")
相同,导致 "protocol://user:*****@hostname/api/path"
urlUsernameAndPassword()
与url("username", "password")
相同,导致 “协议://*****:*****@hostname/api/path”
。
Combine
用于操作解析器结果的助手。
import { Combine } from "@absxn/process-env-parser";
Non-nullable
如果您有一个相互依赖的环境变量子集,即 您要么需要所有这些,要么都不需要,此功能有助于确保 那。
“Nullable”在这里由 TypeScript 的 NonNullable
定义,即 null
或 <代码>未定义。
假设我们有这样的设置:
function getConfig() {
// For parsing purposes, both USERNAME and PASSWORD are optional...
const result = parseEnvironmentVariables({
DATABASE: {},
USERNAME: { default: null },
PASSWORD: { default: null }
});
if (!result.success) {
return null;
}
const { DATABASE, USERNAME, PASSWORD } = result.env;
return {
// ... but for actual authentication, you need both
auth: Combine.nonNullable({ USERNAME, PASSWORD }),
db: DATABASE
};
}
我们将使用给定的启动参数得到以下结果:
$ DATABASE=db USERNAME=user PASSWORD=pass node app
getConfig() -> { auth: { USERNAME: "user", PASSWORD: "pass" }, db: "db" }
$ DATABASE=db node app
getConfig() -> { auth: null, db: "db" }
$ DATABASE=db USERNAME=user node app
getConfig() -> new Error("Mix of non-nullable (USERNAME) and nullable (PASSWORD) values")
$ node app
getConfig() -> null
如果返回对象,则返回类型的可空性从每个 值:
// Type before: { a: string | null, b: number | undefined }
const nullableValues = {
a: Math.random() > 0.5 ? "X" : null,
b: Math.random() > 0.5 ? 1 : undefined
};
// Type after: {a: string, b: number} | null
const nonNullableValues = Combine.nonNullable(nullableTypes);
Formatter
该库包含用于打印解析器的附加辅助函数 结果。 这些对于将启动配置存储到日志中很有用 或者打印出启动失败的原因。
从包中导入 Formatter
:
import { Formatter } from "@absxn/process-env-parser";
console.table()
作为内置的 console.table()
是从中获取可读转储的最简单方法 解析器结果。
const result = requireEnvironmentVariables("VARIABLE"/*, ...*/);
console.table(result.envPrintable);
// ┌──────────┬─────────┐
// │ (index) │ Values │
// ├──────────┼─────────┤
// │ VARIABLE │ 'value' │
// │ ... │ ... │
// └──────────┴─────────┘
Oneliner
使用第一个示例中的数据:
const result = parseEnvironmentVariables({
API_KEY: { mask: true, default: null },
DATABASE_URL: { parser: s => new URL(s).toString() },
LISTEN_PORT: { parser: parseInt, default: 3000 },
SERVICE_NAME: {}
});
console.log(Formatter.oneliner(result));
// if (result.success === true):
// > API_KEY=<masked>, DATABASE_URL="mysql://localhost:3306/app", LISTEN_PORT=8080, SERVICE_NAME="app"
// else:
// > API_KEY=<masked>, DATABASE_URL=<parser: "Invalid URL: localhost">, LISTEN_PORT=3000, SERVICE_NAME=<missing>
Multi-line
使用与上例相同的数据输出:
console.log(Formatter.multiLine(result));
// if (result.success === true):
// > API_KEY = <masked>
// DATABASE_URL = "mysql://localhost:3306/app"
// LISTEN_PORT = 8080
// SERVICE_NAME = "app"
// else:
// > API_KEY = <masked> (default)
// DATABASE_URL = <parser: "Invalid URL: localhost">
// LISTEN_PORT = 3000 (default)
// SERVICE_NAME = <missing>
process-env-parser
Straightforward and type-safe environment variable validation, parsing, and debugging for node
applications.
const result = parseEnvironmentVariables({
API_KEY: { mask: true, default: null },
DATABASE_URL: { parser: s => new URL(s), mask: Mask.url("password") },
LISTEN_PORT: { parser: parseInt, default: 3000 },
SERVICE_NAME: {}
});
if (result.success) {
// Sample success output
console.table(result.envPrintable);
// ┌──────────────┬─────────────────────────────────────────────────────┐
// │ (index) │ Values │
// ├──────────────┼─────────────────────────────────────────────────────┤
// │ API_KEY │ '<masked>' │
// │ DATABASE_URL │ '<masked: "mysql://user:*****@localhost:3306/app">' │
// │ LISTEN_PORT │ '8080' │
// │ SERVICE_NAME │ '"app"' │
// └──────────────┴─────────────────────────────────────────────────────┘
// Inferred type for successfully parsed environment
// {
// API_KEY: string | null
// DATABASE_URL: URL
// LISTEN_PORT: number
// SERVICE_NAME: string
// }
return result.env;
} else {
// Sample formatted output
console.log(Formatter.multiLine(result));
// API_KEY = <masked>
// DATABASE_URL = <parser: "Invalid URL: localhost">
// LISTEN_PORT = 3000 (default)
// SERVICE_NAME = <missing>
throw new Error("Could not parse environment variables");
}
- Rationale
- Installation and usage
- Examples
requireEnvironmentVariables()
: Simple usage with mandatory variablesparseEnvironmentVariables()
: Optional and parsed variables- Fail: Variable missing
- Fail: Parser throwing
- Mask
url()
urlPassword()
urlUsernameAndPassword()
- Combine
- Non-nullable
- Formatter
console.table()
- Oneliner
- Multi-line
Rationale
At the start of every process there are two sources of inputs that can affect the process execution: the program arguments, and the environment variables.
$ ENV_VAR_A=Hello ENV_VAR_B=World node app.js arg1 arg2 --arg3
In order to build reliable software, and minimize runtime surprises, you'll want to follow the fail-fast design and ensure that your program inputs are correct as early on as possible. Everything the program does afterwards is be based on these inputs.
For example, ensuring that a required database URL is correctly passed to the process at the very beginning will alert the user clearly of a possible issue, instead of the the app crashing 30 minutes later when the database connection is done the first time.
This library tries to provide useful tooling for handling the environment variable part of startup.
Installation and usage
$ npm install --save @absxn/process-env-parser
import {
parseEnvironmentVariables,
requireEnvironmentVariables
} from "@absxn/process-env-parser";
Both functions return the same Success | Fail
object:
// Types roughly as follows, read code and inline documentation for details
type Success = {
success: true;
env: {
[variableName: string]:
| InferredParserFunctionReturnType // If `parser` option used
| InferredDefaultValueType // If `default` option used
| string; // No options used
};
envPrintable: {
// Human readable results for logging and debugging
// E.g. `ENV_VAR_A=<missing>, ENV_VAR_B="World", PASSWORD=<masked>`
[variableName: string]: string;
};
};
type Fail = {
success: false;
// Same as for Success
envPrintable: { [variableName: string]: string };
};
Examples
Success: Simple usage with mandatory variables
Easiest way to read the variables is to use requireEnvironmentVariables(...variableNames: string[])
. It reads given variables, must find them all, and returns their values as strings.
To succeed, all listed variables must exist in the environment
Process startup
$ A=hello B=world node app
Code
// Type: Success | Fail
const result = requireEnvironmentVariables("A", "B");
if (result.success) {
console.table(result.envPrintable);
// ┌─────────┬───────────┐
// │ (index) │ Values │
// ├─────────┼───────────┤
// │ A │ '"hello"' │
// │ B │ '"world"' │
// └─────────┴───────────┘
// Type: { A: string, B: string }
// Value: { A: "hello", B: "world" }
return result.env;
} else {
// Wont get here since we gave both A and B in the startup
}
Success: Optional and parsed variables
If you have a more complex setup for the variables, you can use parseEnvironmentVariables(config: Config)
. This allows you to handle each variable individually with additional functionality.
The config
object has variable names as keys, and the value is an object specifying how to handle that variable.
The available options are:
interface Config {
[variableName: string]: {
// If variable is not found, use this as its value. If `default` not
// given, variable is mandatory, in which case, a missing variable leads
// to Fail being returned. If default value was used, envPrintable will
// have " (default)" appended to the printable value.
default?: any;
// When variable is read, its value is passed first to the parser
// function. Return value of the parser is used as the variable value in
// the output. If the parser throws, the function will return a Fail
// object.
parser?: (value: string) => any;
// If `true`, the value of the variable is never shown in plain text in
// the `envPrintable` fields of the return object. Value is indicated as
// `<masked>`. If function, the argument is 1) return value of parser 2)
// environment variable value. Default value bypasses the function and gets
// displayed as `<masked> (default)`. Return value of the function is the
// value to be shown in `envPrintable`, formatted as <masked: "value">.
mask?: boolean | (value: any) => string;
};
}
To succeed:
- All varibales with no
default
given must exist in the environment - Empty string
""
is considered as non-existing! - No
parser
may throw - Parser exceptions turn result into
Fail
and the exception message is captured in theenvPrintable
fields. See examples below.
Default value is used as is, also when parser is given, i.e. default value is not passed to parser when used.
Process startup
$ REQUIRED=value PARSED=12345 node app
Code
// Ensure we return only valid numbers
function parser(s: string): number {
const p = parseInt(s);
if (isNaN(p)) {
throw new Error("Not a number");
} else {
return p;
}
}
const result = parseEnvironmentVariables({
REQUIRED: {},
PARSED: { parser },
OPTIONAL: { default: "OPTIONAL" }
});
if (result.success) {
console.table(result.envPrintable);
// ┌──────────┬────────────────────────┐
// │ (index) │ Values │
// ├──────────┼────────────────────────┤
// │ REQUIRED │ '"value"' │
// │ PARSED │ '1234' │
// │ OPTIONAL │ '"OPTIONAL" (default)' │
// └──────────┴────────────────────────┘
// Type: { REQUIRED: string, PARSER: number, OPTIONAL: "OPTIONAL" | string }
// Value: { REQUIRED: "value", PARSED: 1234, OPTIONAL: "OPTIONAL" }
return result.env;
} else {
// Will not get here
}
Fail: Variable missing
Process startup
$ VAR_A=value VAR_B= VAR_C="${X} ${Y} ${Z}" node app
WARNING – Special cases for "meaningless" strings:
- Empty string:
VAR_B
is also considered as missing. I.e.process.env.VAR_B
does exist, but the parser considers""
equal to not set. - Blank string:
VAR_C
is also considered not set. In this case,X
,Y
,Z
are all""
, so the resulting value ofVAR_C
is two spaces," "
. If value is surrounded by spaces, e.g." A "
, the spaces are preserved as is through the parser.
Code
const result = requireEnvironmentVariables("VAR_A", "VAR_B", "VAR_C", "VAR_D");
if (result.success) {
// Won't get there
} else {
console.table(result.envPrintable);
// ┌─────────┬─────────────┐
// │ (index) │ Values │
// ├─────────┼─────────────┤
// │ VAR_A │ '"value"' │
// │ VAR_B │ '<missing>' │
// │ VAR_C │ '<missing>' │
// │ VAR_D │ '<missing>' │
// └─────────┴─────────────┘
}
Fail: Parser throwing
Process startup
$ NOT_ACTUAL_NUMBER=xyz node app
Code
function parser(s: string): number {
const p = parseInt(s);
if (isNaN(p)) {
throw new Error("Not a number");
} else {
return p;
}
}
const result = parseEnvironmentVariables({
NOT_ACTUAL_NUMBER: { parser }
});
if (result.success) {
// Won't get there
} else {
console.table(result.envPrintable);
// ┌───────────────────┬────────────────────────────┐
// │ (index) │ Values │
// ├───────────────────┼────────────────────────────┤
// │ NOT_ACTUAL_NUMBER │ '<parser: "Not a number">' │
// └───────────────────┴────────────────────────────┘
}
Mask
Helpers for masking parts of variables for output.
import { Mask } from "@absxn/process-env-parser";
url()
A function that returns a function that applies the mask to given URL parts. Valid URL parts are "hash"
, "hostname"
, "password"
, "pathname"
, "port"
, "protocol"
, "search"
, and "username"
. Can handle both URL strings and URL objects (from parser
or default
).
const result = parseEnvironmentVariables({
API_URL: { parser: s => new URL(s), mask: Mask.url("password", "path") }
});
For API_URL=https://user:pass@1.2.3.4/api/path
, the envPrintable
would contain { API_URL: "https://user:*****@1.2.3.4/*****" }
.
urlPassword()
Same as url("password")
, resulting in "protocol://user:*****@hostname/api/path"
urlUsernameAndPassword()
Same as url("username", "password")
, resulting in "protocol://*****:*****@hostname/api/path"
.
Combine
Helpers for manipulating parser results.
import { Combine } from "@absxn/process-env-parser";
Non-nullable
If you have a subset of environment variables that depend on each other, i.e. you either need all of them, or none of them, this function helps to ensure that.
"Nullable" is here defined by TypeScript's NonNullable<T>
, that is, null
or undefined
.
Lets assume we have this setup:
function getConfig() {
// For parsing purposes, both USERNAME and PASSWORD are optional...
const result = parseEnvironmentVariables({
DATABASE: {},
USERNAME: { default: null },
PASSWORD: { default: null }
});
if (!result.success) {
return null;
}
const { DATABASE, USERNAME, PASSWORD } = result.env;
return {
// ... but for actual authentication, you need both
auth: Combine.nonNullable({ USERNAME, PASSWORD }),
db: DATABASE
};
}
We would get the following results with given startup parameters:
$ DATABASE=db USERNAME=user PASSWORD=pass node app
getConfig() -> { auth: { USERNAME: "user", PASSWORD: "pass" }, db: "db" }
$ DATABASE=db node app
getConfig() -> { auth: null, db: "db" }
$ DATABASE=db USERNAME=user node app
getConfig() -> new Error("Mix of non-nullable (USERNAME) and nullable (PASSWORD) values")
$ node app
getConfig() -> null
If the object is returned, the return type has nullability removed from each value:
// Type before: { a: string | null, b: number | undefined }
const nullableValues = {
a: Math.random() > 0.5 ? "X" : null,
b: Math.random() > 0.5 ? 1 : undefined
};
// Type after: {a: string, b: number} | null
const nonNullableValues = Combine.nonNullable(nullableTypes);
Formatter
The library contains additional helper functions for printing out the parser results. These can be useful for storing the startup configuration into logs or printing out startup failure reasons.
Importing Formatter
from the package:
import { Formatter } from "@absxn/process-env-parser";
console.table()
As a built-in, console.table()
is the easiest way to get a readable dump from the parser results.
const result = requireEnvironmentVariables("VARIABLE"/*, ...*/);
console.table(result.envPrintable);
// ┌──────────┬─────────┐
// │ (index) │ Values │
// ├──────────┼─────────┤
// │ VARIABLE │ 'value' │
// │ ... │ ... │
// └──────────┴─────────┘
Oneliner
Using the data from the first example:
const result = parseEnvironmentVariables({
API_KEY: { mask: true, default: null },
DATABASE_URL: { parser: s => new URL(s).toString() },
LISTEN_PORT: { parser: parseInt, default: 3000 },
SERVICE_NAME: {}
});
console.log(Formatter.oneliner(result));
// if (result.success === true):
// > API_KEY=<masked>, DATABASE_URL="mysql://localhost:3306/app", LISTEN_PORT=8080, SERVICE_NAME="app"
// else:
// > API_KEY=<masked>, DATABASE_URL=<parser: "Invalid URL: localhost">, LISTEN_PORT=3000, SERVICE_NAME=<missing>
Multi-line
Output using same data as above example:
console.log(Formatter.multiLine(result));
// if (result.success === true):
// > API_KEY = <masked>
// DATABASE_URL = "mysql://localhost:3306/app"
// LISTEN_PORT = 8080
// SERVICE_NAME = "app"
// else:
// > API_KEY = <masked> (default)
// DATABASE_URL = <parser: "Invalid URL: localhost">
// LISTEN_PORT = 3000 (default)
// SERVICE_NAME = <missing>