src/config.ts — Env loader
Zod-parsed environment loader. Single point of failure: anything missing or malformed at boot raises one aggregated error listing every offending key, so operators fix the env file in one pass instead of one variable per restart.
Public surface
| Export | Shape | Purpose |
|---|---|---|
Config | z.infer<typeof ConfigSchema> | Strongly-typed env contract |
loadConfig(env?) | (NodeJS.ProcessEnv) => Config | Parse; throws on failure |
Schema (current)
| Var | Type | Default | Notes |
|---|---|---|---|
NODE_ENV | 'production' | 'development' | 'test' | development | Drives logger format |
PORT | number (coerced) | 3000 | Loopback bind in src/index.ts |
LOG_LEVEL | pino level | info | |
PUBLIC_BASE_URL | URL | — | Used by npm run register-webhook |
TELEGRAM_BOT_TOKEN | string | — | BotFather token |
TELEGRAM_WEBHOOK_SECRET | string | — | Header echoed by Telegram on every webhook delivery |
MCP_AUTH_TOKEN | string | — | Phase 2: bearer required on /mcp |
INNGEST_EVENT_KEY | string | — | Phase 5 |
INNGEST_SIGNING_KEY | string | — | Phase 5 |
DATABASE_URL | string | — | Postgres connection string |
ADMIN_TELEGRAM_USER_ID | number (coerced) | — | Seeded as status=active, role=admin on boot |
Behavior
loadConfig()runs once from src/index.ts before the logger is constructed — so a bad env file produces a clear stderr line- non-zero exit (systemd flags the unit as failed).
- Numeric vars use
z.coerce.number()so the env-file string is converted cleanly. Negative / non-integer values are rejected. LOG_LEVELis constrained to the known pino level set so a typo (e.g.verbose) fails at boot rather than silently falling through toinfo.
Tests
test/unit/config.test.ts — accepts a full
env, applies defaults, coerces numerics, aggregates missing-field errors,
rejects malformed PUBLIC_BASE_URL and unknown LOG_LEVEL.
See also
- docs/plan/data-and-contracts.md — authoritative env contract
- [memory: project-secrets-split] — where the production env file lives