Comparing version
@@ -1,4 +0,4 @@ | ||
export * as z from "zod"; | ||
export { z } from "zod"; | ||
export * from "./parse-env"; | ||
export * from "./preprocessors"; | ||
export * from "./extra-schemas"; |
@@ -9,14 +9,2 @@ "use strict"; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
@@ -27,3 +15,4 @@ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
exports.z = void 0; | ||
exports.z = __importStar(require("zod")); | ||
var zod_1 = require("zod"); | ||
Object.defineProperty(exports, "z", { enumerable: true, get: function () { return zod_1.z; } }); | ||
__exportStar(require("./parse-env"), exports); | ||
@@ -30,0 +19,0 @@ __exportStar(require("./preprocessors"), exports); |
@@ -162,7 +162,12 @@ "use strict"; | ||
throw new Error(`Zod type not yet supported: "${typeName}" (PRs welcome)`); | ||
case TypeName.ZodAny: | ||
case TypeName.ZodUnknown: | ||
throw new Error([ | ||
`Zod type not supported: ${typeName}`, | ||
"You can use `z.string()` or `z.string().optional()` instead of the above type.", | ||
"(Environment variables are already constrained to `string | undefined`.)", | ||
].join("\n")); | ||
// some of these types could maybe be supported (if only via the identity | ||
// function), but don't necessarily represent something meaningful as a | ||
// top-level schema passed to znv. | ||
case TypeName.ZodAny: | ||
case TypeName.ZodUnknown: | ||
case TypeName.ZodVoid: | ||
@@ -169,0 +174,0 @@ case TypeName.ZodNever: |
{ | ||
"name": "znv", | ||
"version": "0.3.0", | ||
"version": "0.3.1", | ||
"description": "Parse your environment with Zod schemas", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
105
README.md
# znv | ||
<p align="center"> | ||
<img src="logo.svg" height="90"> | ||
<img src="logo.svg" height="90" alt="znv logo"> | ||
</p> | ||
@@ -43,18 +43,45 @@ | ||
import { parseEnv } from "znv"; | ||
import * as z from "zod"; | ||
import { z } from "zod"; | ||
export const { LLAMA_COUNT, COLOR } = parseEnv(process.env, { | ||
export const { NICKNAME, LLAMA_COUNT, COLOR, SHINY } = parseEnv(process.env, { | ||
NICKNAME: z.string().nonempty(), | ||
LLAMA_COUNT: z.number().int().positive(), | ||
COLOR: z.enum(["red", "blue"]), | ||
SHINY: z.boolean().default(true), | ||
}); | ||
console.log([NICKNAME, LLAMA_COUNT, COLOR, SHINY].join(", ")); | ||
``` | ||
In the above example, the exported values `LLAMA_COUNT` and `COLOR` are | ||
guaranteed to be defined and will be typed as `number` and `'red' | 'blue'` | ||
respectively. | ||
Let's run this with [ts-node](https://github.com/TypeStrong/ts-node): | ||
``` | ||
$ LLAMA_COUNT=huge COLOR=cyan ts-node env.ts | ||
``` | ||
<img src="example.png" width="658" alt="A screenshot showing error output, with parsing errors aggregated and grouped by env var."> | ||
Oops! Let's fix those issues: | ||
``` | ||
$ LLAMA_COUNT=24 COLOR=red NICKNAME=coolguy ts-node env.ts | ||
``` | ||
Now we see the expected output: | ||
``` | ||
coolguy, 24, red, true | ||
``` | ||
Since `parseEnv` didn't throw, our exported values are guaranteed to be defined. | ||
Their TypeScript types will be inferred based on the schemas we used — `COLOR` | ||
will be even be typed to the union of literal strings `'red' | 'blue'` rather | ||
than just `string`. | ||
--- | ||
A more elaborate example: | ||
```ts | ||
// znv re-exports zod as 'z' to save you a few keystrokes. | ||
// znv re-exports zod as 'z' to save a few keystrokes. | ||
import { parseEnv, z, port } from "znv"; | ||
@@ -93,6 +120,4 @@ | ||
// using zod arrays or objects as a spec will attempt to `JSON.parse` the | ||
// env var if it's present. remember, with great power comes great | ||
// responsibility! if you're passing large amounts of data in as an env var, | ||
// you may be doing something wrong. | ||
// using a zod `array()` or `object()` as a spec will make znv attempt to | ||
// `JSON.parse` the env var if it's present. | ||
EDITORS: z.array(z.string().nonempty()), | ||
@@ -228,3 +253,3 @@ | ||
`defaults` accepts a special token as a key: `_`. This is like the `default` | ||
clause in a `switch` case — its value will be used it `NODE_ENV` doesn't match | ||
clause in a `switch` case — its value will be used if `NODE_ENV` doesn't match | ||
any other key in `defaults`. | ||
@@ -246,3 +271,3 @@ | ||
However, `_` still lets you express a few interesting scenarios: | ||
Caveats aside, `_` lets you express a few interesting scenarios: | ||
@@ -254,7 +279,9 @@ ```ts | ||
// default for all environments, but require the var to be passed in in prod. | ||
// default for all non-production environments, but require the var to be | ||
// passed in for production. | ||
{ production: undefined, _: "dev default" } | ||
// unconditional default. equivalent to adding `.default("some default")` | ||
// to the zod schema. | ||
// to the zod schema, but this might be more stylistically consistent with | ||
// your other specs if they use the `defaults` field. | ||
{ _: "unconditional default" } | ||
@@ -264,3 +291,3 @@ ``` | ||
Some testing tools like [Jest](https://jestjs.io/) set `NODE_ENV` to `test`, | ||
so you can also use `defaults` to override env vars for testing. | ||
so you can also use `defaults` to provide default env vars for testing. | ||
@@ -289,8 +316,17 @@ `parseEnv` doesn't restrict or validate `NODE_ENV` to any particular values, | ||
znv tries to do as little work as possible to coerce env vars to the input types | ||
of your schemas. If a string value doesn't look like the input type, znv will | ||
pass it to the validator as-is, with the assumption that the validator will | ||
throw. For example, if your schema is `z.number()` and you pass in "banana", znv | ||
won't coerce it to `NaN`, it'll let your schema say "hey, this is a banana!" | ||
znv tries to do as little work as possible to coerce env vars (which are always | ||
strings when they're present) to the [input | ||
types](https://github.com/colinhacks/zod#what-about-transforms) of your schemas. | ||
If the env var doesn't look like the input type, znv will pass it to the | ||
validator as-is with the assumption that the validator will throw. For example, | ||
if your schema is `z.number()`, znv will test it against a numeric regex first, | ||
rather than unconditionally wrap it in `Number()` or `parseFloat()` (and thus | ||
coerce it to `NaN`). | ||
By modifying as little as possible, znv tries to get out of Zod's way and let it | ||
do the heavy lifting of validation. This also lets us produce less confusing | ||
error messages: if you pass the string "banana" to your number schema, it should | ||
be able to say "you gave me 'banana' instead of a number!" rather than "you gave | ||
me NaN instead of a number!" | ||
**Coercions only happen at the top level of a schema**. If you define an object | ||
@@ -304,4 +340,22 @@ with nested schemas, no coercions will be applied to the keys. | ||
will be passed through. | ||
> Some CLI tool conventions dictate that a variable simply being present in | ||
> the environment (even with no value, eg. setting `MY_VALUE=` with no | ||
> right-hand side) should be interpreted as `true`. However, this convention | ||
> doesn't seem to be in widespread use in Node, probably because it causes the | ||
> var to evaluate to the empty string (which is falsy). znv demands a little | ||
> more specificity by default, while still hedging a bit for some common | ||
> true/false equivalents. If you want the "any defined value" behaviour, you | ||
> can use | ||
> `z.string().optional().transform(v => v === undefined ? false : true)`. | ||
- If your schema's input is an object or array (or record or tuple), znv will | ||
attempt to `JSON.parse` the input value if it's not `undefined` or empty. | ||
> **Remember, with great power comes great responsibility!** If you're using | ||
> an object or array schema to pass in dozens or hundreds of kilobytes of data | ||
> as an env var, you may be doing something wrong. (Certain platforms also | ||
> [impose limits on environment variable | ||
> length](https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083).) | ||
- If your schema's input is a Date, znv will call `new Date()` with the input | ||
@@ -313,6 +367,9 @@ value. This has a number of pitfalls, since the `Date()` constructor is | ||
`invalid date` or a completely nonsensical date.) _You should only pass in ISO | ||
8601 date strings_, such as those returned by `Date.prototype.toISOString()`. | ||
8601 date strings_, such as those returned by | ||
[`Date.prototype.toISOString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString). | ||
Improved validation for Date schemas could be added in a future version. | ||
- Zod defines "nullable" as distinct from "optional". If your schema is | ||
nullable, znv will coerce `undefined` to `null`. Generally it's preferred to | ||
simply use optional. | ||
`nullable`, znv will coerce `undefined` to `null`. Generally it's preferred to | ||
simply use `optional`. | ||
@@ -319,0 +376,0 @@ ## Comparison to other libraries |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
52551
4.6%408
16.24%509
-1.17%