deno_std icon indicating copy to clipboard operation
deno_std copied to clipboard

feat(dotenv): add the `expectVars` option for typing of return value of `load()`

Open pasha-bolokhov opened this issue 3 months ago • 6 comments

The requested feature that enables you to type the return value of load()

const { SERVER, PORT } = await load({
  envPath: ".env", 
  defaultsPath: ".env.defaults", 
  expectVars: ["SERVER", "PORT"] as const,
});

If set, expectVars does two things:

  • performs the actual check that the variables are there
  • sets the return type to Record<"SERVER" | "PORT", string>

The check is performed regardless of whether the example file has been provided or not in the options — this is for simplicity of the algorithm, and among other things, it also confirms that the example file is up to date with the code.

pasha-bolokhov avatar Mar 06 '24 19:03 pasha-bolokhov

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Mar 06 '24 19:03 CLAassistant

I'm not sure these checks need to be done as part of load. The checks can be done after loading env vars like the below example, which feels more explicit and flexible to me.

const vars = await load({
  envPath: ".env", 
  defaultsPath: ".env.defaults", 
});

assertExists(vars["SERVER"]);
assertExists(vars["PORT"]);

kt3k avatar Mar 07 '24 04:03 kt3k

This is the feature that I would definitely use, to be honest.

This is the reference to one of the original requests, before dotenv was merged into deno_std: https://github.com/pietvanzoen/deno-dotenv/issues/58

I do think that this goes along with idea of automatizing of unpacking of .env variables. Yes, you could explicitly check that certain variables exist, but nobody does

The original idea was to get strong typing, and the only way to get strong typing is by listing the variables that you expect to have. And then if you do get the list, why not check that there are indeed there?

While you can use strong typing (via expectVars), you don't have to, if you omit expectVars option, in which case you get the standard load() behaviour

pasha-bolokhov avatar Mar 07 '24 05:03 pasha-bolokhov

This overlaps with the purpose of LoadOptions.examplePath too much. I agree with Yoshiya.

iuioiua avatar Mar 10 '24 23:03 iuioiua

This overlaps with the purpose of LoadOptions.examplePath too much. I agree with Yoshiya.

Well, the main purpose is the strong typing. The additional check of the existence of variables may be considered as an overlap (although, again the presence of the "example" file does not guarrantee the existence if the "example" file itself is out of date). If you do think that these checks are indeed duplicating the purpose of the "example" file, I can remove them

But again — the original and main purpose is to provide the typing of the return value, which should help avoid typos in variable names. It does seem natural to me that load() knows the return value it returns

Let me know…

pasha-bolokhov avatar Mar 12 '24 22:03 pasha-bolokhov

This kind of type narrowing can be done in a more generic way like zod or valibot.

For example, the below example gives a nice typing to options (which is inferred as { HOST: string; PORT: number }:

import z from "npm:zod";
import { load } from "jsr:@std/dotenv@^0";

const Options = z.object({
  HOST: z.string(),
  PORT: z.coerce.number(),
});

const options = Options.parse(await load());

I think we should rather explore these kinds of solution (or just recommend zod, valibot, etc because these tools tend to have to have an opinionated design)

kt3k avatar Apr 17 '24 07:04 kt3k