Skip to content

Instantly share code, notes, and snippets.

@jakeisnt
Last active April 23, 2025 23:36
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.

Revisions

  1. jakeisnt revised this gist Apr 23, 2025. 1 changed file with 37 additions and 0 deletions.
    37 changes: 37 additions & 0 deletions createEnv.ts
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,40 @@
    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;
    }
    >;

    // 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().default(5435) },
    * DUMPS_DIR: { type: z.string().default(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 = {
  2. jakeisnt revised this gist Apr 23, 2025. 1 changed file with 8 additions and 53 deletions.
    61 changes: 8 additions & 53 deletions createEnv.ts
    Original file line number Diff line number Diff line change
    @@ -1,41 +1,3 @@
    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 = {
    @@ -44,34 +6,27 @@ const createEnv = <T extends EnvSchema<string>>(schema: T): EnvType<T> => {

    for (const [key, config] of Object.entries(schema) as [
    keyof T,
    T[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 rawValue = process.env[String(key)];
    const parsedValue = config.type.safeParse(rawValue);

    if (!parsedValue.success) {
    console.error(
    `Invalid value for ${key.toString()}:`,
    parsedValue.error,
    console.warn(
    `Invalid or missing value for ${key.toString()}:`,
    parsedValue.error
    );
    return config.fallback;
    }

    this._cache[key] = parsedValue.data;
    this._cache[key] = parsedValue.success
    ? parsedValue.data
    : config.type.parse(undefined);
    return this._cache[key];
    },
    });
  3. jakeisnt renamed this gist Nov 21, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. jakeisnt created this gist Nov 21, 2024.
    83 changes: 83 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    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 };