Schemas
Validate config imports and environment variables with Zod, Valibot, ArkType or Effect Schema.
The content mapper checks types. A schema adds the rules a file can't express (ranges, URLs, enums, required variables) and can transform values. tyfigo accepts any Standard Schema (Zod, Valibot, ArkType, …) and Effect Schema.
import { loadConfig, loadConfigAsync, loadEnv, loadEnvAsync, ConfigValidationError } from 'tyfigo';loadConfig
loadConfig(config, schema) validates a config import (or any value) and returns the schema's output:
import { loadConfig } from 'tyfigo';
import { z } from 'zod';
import config from '../config/app.config.yaml';
const Config = z.object({
name: z.string(),
server: z.object({ host: z.string(), port: z.number().int().positive() }),
database: z.object({
url: z.url({ protocol: /^postgres$/ }),
pool: z.object({ min: z.number().int(), max: z.number().int() }),
}),
features: z.array(z.string()),
});
const settings = loadConfig(config, Config);The config's type has to fit the schema's input type, so a schema that expects a string where the file has a number is a
type error at the call. Keys the schema doesn't list are allowed, and a widened string
fits a schema that expects literals such as z.enum([...]). See
What tsc checks without a schema for the cases a schema covers.
When validation fails it throws a ConfigValidationError that lists every issue, each with the file:line:column the value
came from (the environment file that set it, if one did):
Invalid config /srv/app/config/app.config.yaml:
/srv/app/config/app.config.yaml:2:9 - server.port: Too small: expected number to be >=1
/srv/app/config/app.config.yaml:1:1 - server.host: Invalid input: expected string, received undefinedloadEnv
loadEnv(schema, options?) validates environment variables: .env, .env.local, .env.<environment> and
.env.<environment>.local, with the real environment on top. It never writes process.env.
import { loadEnv } from 'tyfigo';
import { z } from 'zod';
const env = loadEnv(z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development') }));| Option | Default | Meaning |
|---|---|---|
cwd | process.cwd() | Where to look for .env files first, before the project root |
environment | env.NODE_ENV | Picks .env.<environment> and .env.<environment>.local |
env | a copy of process.env | The real environment variables, which win over every file |
All variables are strings, so use a schema that coerces (z.coerce.number(), v.transform(Number), Schema.FiniteFromString)
for numbers.
Async schemas
loadConfig and loadEnv are synchronous. A schema that validates asynchronously (an async refinement, for example) makes them
throw a TypeError. Use loadConfigAsync and loadEnvAsync, which return promises.
Libraries
import { loadConfig, loadEnv } from 'tyfigo';
import { z } from 'zod';
const settings = loadConfig(
config,
z.object({ server: z.object({ host: z.string(), port: z.number().int().min(1) }), tags: z.array(z.string().min(2)) }),
);
const env = loadEnv(z.object({ PORT: z.coerce.number() }));ConfigValidationError
| Property | Type |
|---|---|
_tag | 'ConfigValidationError' |
subject | 'config /path/app.config.yaml' or 'environment variables' |
issues | readonly { path; message; location? }[] |
message | Every issue, one per line |
location is file:line:column for a config import, or the file alone when the key has no position. For loadEnv, a .env
file that can't be read is an issue too.