Skip to content

Latest commit

 

History

History
249 lines (179 loc) · 4.3 KB

File metadata and controls

249 lines (179 loc) · 4.3 KB

Core API

@howells/envy is the implemented foundation of Envy. It defines schemas, validates input objects, and returns typed parsed values.

Define A Schema

import { defineEnv, v } from "@howells/envy";
import { z } from "zod";

export const envSchema = defineEnv({
  server: {
    DATABASE_URL: z.string().url(),
    OPENAI_API_KEY: v(z.string().min(1), {
      deploy: ["preview", "production"],
    }),
  },

  public: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
  },

  system: {
    NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
    CI: z.coerce.boolean().default(false),
  },

  optional: {
    COHERE_API_KEY: z.string().min(1),
  },
});

Raw Zod schemas are enough for most variables. Use v(...) only when a variable needs metadata for tools.

Groups

server

Private values required by server-side code.

server: {
  DATABASE_URL: z.string().url(),
}

Missing values fail validation unless the Zod schema has a default or otherwise accepts undefined.

public

Client-safe values.

public: {
  NEXT_PUBLIC_APP_URL: z.string().url(),
}

Public keys must start with the configured public prefix. The default is NEXT_PUBLIC_.

defineEnv(
  {
    public: {
      PUBLIC_APP_URL: z.string().url(),
    },
  },
  {
    publicPrefix: "PUBLIC_",
  },
);

system

Runtime or provider-owned values such as NODE_ENV, CI, VERCEL_URL, or RAILWAY_*.

system: {
  NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
}

These values parse normally and are excluded from deploy pushes by default.

optional

Optional integration values.

optional: {
  COHERE_API_KEY: z.string().min(1),
}

Missing and empty values parse as undefined. Present values must pass the Zod schema.

Parsing Methods

parse(input)

Parses all groups and returns a frozen object.

const env = envSchema.parse(process.env);

Use this in non-Next server contexts and scripts.

parseServer(input)

Parses server, public, system, and optional.

export const env = envSchema.parseServer(process.env);

Use this for server-side application code.

parseClient(input)

Parses only public.

export const env = envSchema.parseClient({
  NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
});

Use this for client bundle entrypoints.

lazy(input)

Returns a proxy that validates each declared key on access.

const env = envSchema.lazy(process.env);

Lazy mode is for awkward runtimes where validating everything up front is not viable. Prefer explicit parsing otherwise.

Empty Strings

By default, Envy converts "" to undefined before validation.

This makes .env placeholders behave sensibly:

OPTIONAL_KEY=
PORT=
defineEnv({
  optional: {
    OPTIONAL_KEY: z.string().min(1),
  },
  system: {
    PORT: z.coerce.number().default(3000),
  },
});

OPTIONAL_KEY becomes undefined, and PORT uses its Zod default.

Disable this only when empty string is a meaningful value:

defineEnv(
  {
    server: {
      EMPTY_ALLOWED: z.literal(""),
    },
  },
  {
    emptyStringAsUndefined: false,
  },
);

Unknown Keys

Parsed output only contains schema-declared keys.

const env = envSchema.parse({
  DATABASE_URL: "https://db.example.com",
  RANDOM_EXTRA: "ignored",
});

RANDOM_EXTRA is stripped from the returned object.

CLI checks report undeclared keys in env files. Runtime parsing keeps application code focused on the declared typed surface.

Errors

Invalid input throws EnvValidationError.

import { EnvValidationError } from "@howells/envy";

try {
  envSchema.parse(process.env);
} catch (error) {
  if (error instanceof EnvValidationError) {
    console.error(error.issues);
  }
}

Each issue includes:

  • key
  • group
  • message
  • path

Public Prefix Enforcement

This fails immediately when the schema is defined:

defineEnv({
  public: {
    APP_URL: z.string().url(),
  },
});

Use NEXT_PUBLIC_APP_URL or configure a different prefix.

Duplicate Keys

A key may be declared in only one group.

defineEnv({
  server: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
  },
  public: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
  },
});

This fails during schema definition.