Skip to content

Instantly share code, notes, and snippets.

@jakeisnt
Created November 21, 2024 14:14
Show Gist options
  • Save jakeisnt/887b00674bc2e09fc24e97f330d9418c to your computer and use it in GitHub Desktop.
Save jakeisnt/887b00674bc2e09fc24e97f330d9418c to your computer and use it in GitHub Desktop.
`createEnv`: statically checked and typed environment variables with Zod
import { z } from 'zod';
// System for creating dynamically accessible environment variables
// from a schema.
// Schema for the environment variables.
// `type` specifies the type of the environment variable.
// `fallback` is the fallback value if the environment variable is not set.
type EnvSchema<T extends string> = Record<
T,
{
type: z.ZodType;
fallback: z.infer<z.ZodType>;
}
>;
// Type for the environment variables.
// We use zod metaprogramming to infer the type of the environment variables
// based on the type specified in the Zod schema.
type EnvType<T extends EnvSchema<string>> = {
[K in keyof T]: T[K]['type'] extends z.ZodType
? z.infer<T[K]['type']>
: never;
};
/**
* Create an environment object from a schema.
* @param schema - The schema to create the environment object from.
* @returns The environment object.
*
* @example
* const env = createEnv({
* PORT: { type: z.coerce.number().int().positive(), fallback: 5435 },
* DUMPS_DIR: { type: z.string(), fallback: path.join(process.env.HOME || '', 'improvin/dumps') },
* });
*
* env.PORT; // 5435
* env.DUMPS_DIR; // /Users/jake/Documents/improvin/dumps
*/
const createEnv = <T extends EnvSchema<string>>(schema: T): EnvType<T> => {
const env = {
_cache: {} as EnvType<T>,
};
for (const [key, config] of Object.entries(schema) as [
keyof T,
T[keyof T],
][]) {
Object.defineProperty(env, key, {
get() {
if (this._cache[key]) {
return this._cache[key];
}
const rawValue =
process.env[String(key)] ||
(() => {
console.warn(
`${String(key)} not provided, defaulting to ${config.fallback}`,
);
return config.fallback.toString();
})();
const parsedValue = config.type.safeParse(rawValue);
if (!parsedValue.success) {
console.error(
`Invalid value for ${key.toString()}:`,
parsedValue.error,
);
return config.fallback;
}
this._cache[key] = parsedValue.data;
return this._cache[key];
},
});
}
return env as EnvType<T>;
};
export { createEnv };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment