---
title: Core API Reference
description: Complete API reference for @envlock/core – schema builders, validation, typing, and utilities
url: https://pr-1-8289d63b6330.thally.app/envlock/core-api
---

# Core API Reference

Complete API reference for @envlock/core – schema builders, validation, typing, and utilities

`@envlock/core` is a zero-dependency library providing schema builders, validation, typing, and environment variable utilities. This page documents all exported functions and types.

## Schema Definition

### `defineEnv(shape)`

Creates an environment variable schema. The `shape` is a record mapping variable names to field builders.

```ts
const schema = defineEnv({
  PORT: env.port().default(3000),
  DATABASE_URL: env.url({ protocols: ["postgres:"] }).secret(),
  DEBUG: env.boolean().optional(),
});
```

**Signature:**
```ts
defineEnv<const Shape extends Record<string, AnyField>>(
  shape: Shape
): EnvSchema<Shape>
```

**Throws:** `TypeError` if any key does not match `/^[A-Za-z_][A-Za-z0-9_]*$/`
- Error message: `"Invalid environment variable name "<key>": use letters, digits and underscores, not starting with a digit"`

**Returns:** A frozen `EnvSchema<Shape>` object.

---

### `EnvSchema<Shape>`

The schema object returned by `defineEnv`. Contains the shape and metadata.

```ts
interface EnvSchema<Shape extends Record<string, AnyField> = Record<string, AnyField>> {
  readonly kind: "envlock.schema";  // SCHEMA_KIND constant
  readonly shape: Shape;
  readonly keys: readonly string[];  // keys in declaration order
}
```

---

### `isEnvSchema(value: unknown): value is EnvSchema`

Type guard to check if a value is an `EnvSchema`. Works correctly even if package copies are present.

---

### `SCHEMA_KIND`

The sentinel string for schema objects.

```ts
const SCHEMA_KIND = "envlock.schema";
```

---

## Field Builders

The `env` namespace provides 10 builder functions, each returning a required, non-secret field.

### `env.string()`

Accepts any string value as-is (no trimming or transformation).

```ts
const field: Field<string, true> = env.string();
```

---

### `env.number()`

Parses a finite number. Rejects blank input, NaN, and Infinity.

```ts
const field: Field<number, true> = env.number();
// Constraints: "finite number"
```

---

### `env.integer()`

Parses a safe integer (within ±2⁵³−1). Rejects decimals.

```ts
const field: Field<number, true> = env.integer();
// Constraints: "whole number"
```

---

### `env.boolean()`

Parses true/false, 1/0, yes/no, on/off (case-insensitive).

```ts
const field: Field<boolean, true> = env.boolean();
// Accepts: "true", "false", "1", "0", "yes", "no", "on", "off" (any case)
```

---

### `env.port()`

Parses an integer in the range 1–65535.

```ts
const field: Field<number, true> = env.port();
// Constraints: "integer 1-65535"
```

---

### `env.url(options?)`

Parses an absolute URL using `new URL()`. Optionally restrict to specific protocols.

```ts
const field1: Field<string, true> = env.url();
const field2: Field<string, true> = env.url({ protocols: ["https:"] });
const field3: Field<string, true> = env.url({
  protocols: ["postgres:", "postgresql:"]
});
```

**Options:**
```ts
interface UrlOptions {
  readonly protocols?: readonly string[];  // e.g., ["https:", "postgres:"]
}
```

**Constraints:** `"absolute URL"` or `"absolute URL with protocol X or Y"`

---

### `env.enum(values)`

Restricts values to a fixed set of literal strings. The output type is a literal union.

```ts
const field: Field<"dev" | "prod", true> = env.enum(["dev", "prod"]);
// Constraints: "one of: dev, prod"
```

**Signature:**
```ts
env.enum<const V extends readonly [string, ...string[]]>(
  values: V
): Field<V[number], true>
```

---

### `env.json<T>()`

Parses a JSON document and optionally types the result.

```ts
const field1: Field<unknown, true> = env.json();
const field2: Field<{ x: number }, true> = env.json<{ x: number }>();
```

**Constraints:** `"JSON document"`

---

### `env.duration()`

Parses a duration string into milliseconds. Supports `250ms`, `30s`, `5m`, `2h`, `1d`, or a bare number (interpreted as milliseconds).

```ts
const field: Field<number, true> = env.duration();
// "30s" → 30000
// "5m" → 300000
// "2h" → 7200000
// "1d" → 86400000
// "5000" → 5000
```

**Constraints:** `"duration like 30s, 5m, 2h, stored as milliseconds"`

---

### `env.list(options?)`

Parses a comma-separated list into a string array. Items are trimmed; empty items are dropped.

```ts
const field1: Field<string[], true> = env.list();
const field2: Field<string[], true> = env.list({ separator: ":" });
```

**Options:**
```ts
interface ListOptions {
  readonly separator?: string;  // defaults to ","
}
```

**Example:** `"a, b, c"` → `["a", "b", "c"]`

---

## Field Type and Methods

### `Field<T, Required>`

A field represents a single environment variable with validation rules and metadata.

**Properties:**
```ts
interface Field<T, Required extends boolean> {
  readonly kind: FieldKind;
  readonly isOptional: boolean;        // !Required
  readonly hasDefault: boolean;
  readonly defaultValue?: T;           // present when hasDefault is true
  readonly isSecret: boolean;
  readonly description?: string;
  readonly exampleValue?: string;
  readonly constraints?: string;
  readonly parse: (raw: string) => ParseOutcome<T>;
}
```

All fields are frozen objects. Chain methods return new fields.

---

### Field Chain Methods

#### `.optional()` → `Field<T, false>`

Makes the field optional. Absent values (undefined or empty string) produce no validation issue.

```ts
env.port().optional()  // present: validated; absent: ok
```

---

#### `.default(value: T)` → `Field<T, true>`

Provides a default value for absent inputs. Clears the optional flag (making the field required in the output type).

```ts
env.port().default(3000)  // absent: 3000; present: validated
```

---

#### `.secret()` → `Field<T, Required>`

Marks the field as a secret. Secret values are:
- Masked as `"••••••"` in validation issues
- Redacted by the `redact()` function
- Shown as `KEY=` with no value in `.env.example`

```ts
env.url({ protocols: ["postgres:"] }).secret()
```

---

#### `.describe(text: string)` → `Field<T, Required>`

Adds a human-readable description. Used in `.env.example` headers and schema inspection.

```ts
env.port().describe("HTTP server listen port")
```

---

#### `.example(text: string)` → `Field<T, Required>`

Sets an example value for `.env.example` generation. Ignored for secret fields.

```ts
env.url().example("https://api.example.com")
```

---

### `AnyField`

Type alias for any field.

```ts
type AnyField = Field<unknown, boolean>;
```

---

### `FieldKind`

The union of all 10 builder names.

```ts
type FieldKind =
  | "string" | "number" | "integer" | "boolean"
  | "port" | "url" | "enum" | "json"
  | "duration" | "list";
```

---

### `FIELD_KINDS`

A record of all field kind constants.

```ts
const FIELD_KINDS = {
  string: "string",
  number: "number",
  integer: "integer",
  boolean: "boolean",
  port: "port",
  url: "url",
  enum: "enum",
  json: "json",
  duration: "duration",
  list: "list",
};
```

---

### `ParseOutcome<T>`

The result of parsing a single field value.

```ts
type ParseOutcome<T> =
  | { readonly ok: true; readonly value: T }
  | { readonly ok: false; readonly message: string };
```

---

### `FieldValue<F extends AnyField>`

Extracts the value type from a field.

```ts
type T = FieldValue<Field<string, true>>;  // string
```

---

## Validation and Parsing

### `parseEnv(schema, source, options?)`

Validates an environment source against a schema. Never throws; returns a result object.

```ts
const result = parseEnv(schema, process.env);
const result2 = parseEnv(schema, { PORT: "3000", DEBUG: "true" }, { strict: true });
```

**Signature:**
```ts
parseEnv<S extends EnvSchema>(
  schema: S,
  source: EnvSource,
  options?: ParseOptions,
): ParseResult<S>
```

**Parameters:**
- `schema`: An `EnvSchema` from `defineEnv`
- `source`: A record of strings or undefined
- `options.strict`: If `true`, report unknown keys as issues (files only; ignored for process.env)

**Returns:** A `ParseResult<S>` — either success with typed values, or failure with issues.

**Validation rules:**
- `undefined` and empty string `""` both count as absent
- Absent + `.default()` → use default value
- Absent + `.optional()` → skip (no issue)
- Absent + required → `missing` issue
- Present value → parse through field's `parse` function; invalid → `invalid` issue
- `strict: true` → undeclared keys → `unknown` issues (sorted alphabetically after declared keys)
- Issues are always ordered by declaration order, then unknown keys alphabetically

---

### `EnvSource`

A record of environment variables.

```ts
type EnvSource = Readonly<Record<string, string | undefined>>;
```

---

### `ParseOptions`

Options for `parseEnv`.

```ts
interface ParseOptions {
  readonly strict?: boolean;  // report undeclared keys; for bounded sources only
}
```

---

### `ParseResult<S extends EnvSchema>`

The result of parsing a schema against an environment source.

```ts
type ParseResult<S extends EnvSchema> =
  | { readonly ok: true; readonly values: Infer<S>; readonly issues: readonly [] }
  | { readonly ok: false; readonly issues: readonly EnvIssue[] };
```

Check `result.ok` to determine success or failure.

---

### `EnvIssue`

A single validation problem.

```ts
interface EnvIssue {
  readonly key: string;
  readonly code: IssueCode;
  readonly message: string;
  readonly received?: string;  // masked as "••••••" for secret fields
}
```

**Example issue:**
```ts
{
  key: "PORT",
  code: "invalid",
  message: "expected a finite number",
  received: "abc"
}
```

---

### `IssueCode`

The type of validation problem.

```ts
type IssueCode = "missing" | "invalid" | "unknown";
```

| Code | Meaning |
|---|---|
| `missing` | Required variable is absent (undefined or empty string) |
| `invalid` | Variable is present but failed parsing |
| `unknown` | Variable is present in source but not declared in schema (strict mode only) |

---

### `ISSUE_CODES`

A record of all issue code constants.

```ts
const ISSUE_CODES = {
  missing: "missing",
  invalid: "invalid",
  unknown: "unknown",
};
```

---

### `loadEnv(schema, source?, options?)`

A convenience function: calls `parseEnv` and either returns typed values or throws `EnvValidationError`.

```ts
const config = loadEnv(schema);
const config2 = loadEnv(schema, { PORT: "3000" });
```

**Signature:**
```ts
loadEnv<S extends EnvSchema>(
  schema: S,
  source?: EnvSource,        // defaults to process.env
  options?: ParseOptions,
): Infer<S>
```

**Throws:** `EnvValidationError` if validation fails.

---

### `EnvValidationError`

Thrown by `loadEnv` when validation fails.

```ts
class EnvValidationError extends Error {
  readonly issues: readonly EnvIssue[];
  name = "EnvValidationError";
}
```

**Message format:**
```
Environment validation failed (N issue(s)):
  - KEY: message (received "value")
  - KEY2: message
```

Secrets are masked in the message.

---

### `formatIssues(issues: readonly EnvIssue[]): string`

Renders issues as a bullet list, one per line.

```ts
const text = formatIssues([
  { key: "PORT", code: "invalid", message: "expected a number", received: "abc" },
]);
// Output:
//   - PORT: expected a number (received "abc")
```

---

## Type Inference

### `Infer<S extends EnvSchema>`

Maps a schema to its fully typed values object.

```ts
const schema = defineEnv({
  PORT: env.port().default(3000),           // required
  DATABASE_URL: env.url().secret(),         // required
  DEBUG: env.boolean().optional(),          // optional
});

type Config = Infer<typeof schema>;
// {
//   PORT: number;
//   DATABASE_URL: string;
//   DEBUG?: boolean;
// }
```

---

## Dotenv Utilities

### `parseDotenv(text: string): Record<string, string>`

Parses a `.env` file into a record. Supports comments, quotes, escape sequences, and multi-line values.

```ts
const env = parseDotenv(`
  PORT=3000
  DATABASE_URL="postgres://localhost/myapp"
  # debug mode
  DEBUG=true
`);
// { PORT: "3000", DATABASE_URL: "postgres://localhost/myapp", DEBUG: "true" }
```

**Supports:**
- `KEY=value` and `export KEY=value`
- `# comment` lines
- Single quotes (literal strings, no escapes)
- Double quotes (with escapes: `\n`, `\r`, `\t`, `\"`, `\\`, `\$`)
- Multi-line quoted values
- Inline comments (` #` preceded by whitespace)
- Empty values (`KEY=`)
- CRLF line endings (normalized to LF)
- Malformed lines (silently skipped)
- Duplicate keys (later values win)

Never throws.

---

### `formatDotenv(record: Readonly<Record<string, string>>): string`

Serializes a record back to `.env` format. Quotes only when needed (spaces, special chars, or leading/trailing whitespace).

```ts
const text = formatDotenv({
  PORT: "3000",
  MESSAGE: "hello world",  // quoted (space)
  SIMPLE: "value",          // bare
});
```

**Property:**
- `parseDotenv(formatDotenv(record))` equals `record` (round-trips correctly)

---

## Schema Documentation

### `renderExample(schema, options?)`

Generates a `.env.example` file from a schema, with descriptions, types, defaults, and constraints.

```ts
const example = renderExample(schema);
console.log(example);  // writes to stdout or file
```

**Signature:**
```ts
renderExample(
  schema: EnvSchema,
  options?: RenderExampleOptions,
): string
```

**Options:**
```ts
interface RenderExampleOptions {
  readonly header?: readonly string[];  // comment lines at top; pass [] to omit
}
```

**Default header:**
```
Environment contract rendered by envlock.
Copy to .env and fill in the values; never commit real secrets here.
```

**Format per variable:**
```
# <description>             (only if described)
# <type> · required|optional · default: VALUE · <constraints> · secret
KEY=<example or default>    (bare KEY= for secrets; default value for others)
```

**Example output:**
```
# Environment contract rendered by envlock.
# Copy to .env and fill in the values; never commit real secrets here.

# HTTP server listen port
# port · required · default: 3000 · integer 1-65535
PORT=3000

# Database connection string
# url · required · absolute URL with protocol postgres: · secret
DATABASE_URL=

# Debug mode
# boolean · optional
DEBUG=
```

---

### `describeSchema(schema): SchemaDescription[]`

Extracts structured metadata about the schema for inspection, rendering, or documentation generation.

```ts
const descriptions = describeSchema(schema);
// [
//   {
//     key: "PORT",
//     type: "port",
//     required: true,
//     hasDefault: true,
//     default: 3000,
//     secret: false,
//     description: "HTTP listen port",
//     constraints: "integer 1-65535",
//   },
//   ...
// ]
```

**Returns:** An array of `SchemaDescription` objects (declaration order).

---

### `SchemaDescription`

Metadata about a single field in the schema.

```ts
interface SchemaDescription {
  readonly key: string;
  readonly type: FieldKind;
  readonly required: boolean;           // !isOptional && !hasDefault
  readonly hasDefault: boolean;
  readonly default?: unknown;           // present when hasDefault; masked for secrets
  readonly secret: boolean;
  readonly description?: string;
  readonly example?: string;            // omitted for secrets
  readonly constraints?: string;
}
```

---

## Schema Diffing

### `diffEnv(schema, source)`

Compares an environment source against a schema and returns structured differences.

```ts
const diff = diffEnv(schema, process.env);
if (!diff.ok) {
  console.log(`Missing: ${diff.missing}`);
  console.log(`Unknown: ${diff.unknown}`);
  console.log(`Invalid:`, diff.invalid);
}
```

**Signature:**
```ts
diffEnv(schema: EnvSchema, source: EnvSource): EnvDiff
```

**Returns:** An `EnvDiff` object with categorized issues.

---

### `EnvDiff`

The result of diffing an environment against a schema.

```ts
interface EnvDiff {
  readonly ok: boolean;                      // true if no issues
  readonly missing: readonly string[];       // required variables absent
  readonly unknown: readonly string[];       // undeclared variables present (sorted)
  readonly invalid: readonly EnvIssue[];     // variables present but invalid
}
```

**Example:**
```ts
{
  ok: false,
  missing: ["DATABASE_URL"],
  unknown: ["EXTRA_VAR"],
  invalid: [
    { key: "PORT", code: "invalid", message: "expected a number", received: "abc" },
  ],
}
```

---

## Value Utilities

### `redact(values, schema)`

Creates a shallow copy of a values object, masking all secret fields.

```ts
const config = loadEnv(schema);
const safe = redact(config, schema);
// config.DATABASE_URL is "postgres://..."
// safe.DATABASE_URL is "••••••"
```

**Signature:**
```ts
redact<T extends Readonly<Record<string, unknown>>>(
  values: T,
  schema: EnvSchema,
): { readonly [K in keyof T]: T[K] | string }
```

**Behavior:**
- Copies all properties from input
- Replaces secret field values with `REDACTED_VALUE` (`"••••••"`)
- Input object is not mutated
- Safe to log, send to error tracking, or pass to external services

---

### `REDACTED_VALUE`

The constant string used for redacted secrets.

```ts
const REDACTED_VALUE = "••••••";
```

---

## Example

A complete example combining schema definition, parsing, and utilities:

```ts
import {
  defineEnv,
  env,
  loadEnv,
  parseEnv,
  renderExample,
  redact,
  Infer,
} from "@envlock/core";

const schema = defineEnv({
  NODE_ENV: env.enum(["development", "test", "production"]).default("development"),
  PORT: env.port().default(3000).describe("HTTP server port"),
  DATABASE_URL: env.url({ protocols: ["postgres:"] }).secret(),
  REDIS_URL: env.url({ protocols: ["redis:"] }).optional(),
  FEATURES: env.json<Record<string, boolean>>().optional(),
});

// Type inference
type Config = Infer<typeof schema>;

// Load from process.env (throws on invalid)
const config = loadEnv(schema);

// Or parse without throwing
const result = parseEnv(schema, process.env);
if (!result.ok) {
  console.error("Validation failed:", result.issues);
} else {
  const config = result.values;
}

// Redact before logging
const safe = redact(config, schema);
console.log(safe);  // DATABASE_URL is "••••••"

// Generate example file
const exampleContent = renderExample(schema);
```

---

## Constants Summary

| Constant | Value | Purpose |
|---|---|---|
| `SCHEMA_KIND` | `"envlock.schema"` | Sentinel for schema objects |
| `FIELD_KINDS` | `{ string, number, ... }` | All 10 builder names |
| `ISSUE_CODES` | `{ missing, invalid, unknown }` | All issue types |
| `REDACTED_VALUE` | `"••••••"` | Placeholder for secrets |