{"_id":"@0x-config/core","name":"@0x-config/core","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@0x-config/core","version":"1.0.0","description":"Runtime environment variable validation for Node, Bun & Browser. Typed. Zero dependencies. Fail-fast.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","bin":{"oxconfig":"dist/cli.js"},"repository":{"type":"git","url":"git+https://github.com/slotherinee/0x-config.git"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./watch":{"types":"./dist/watch.d.ts","import":"./dist/watch.js","require":"./dist/watch.cjs"}},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage"},"keywords":["env","environment","config","validation","dotenv","typescript","runtime","type-safe","zod","schema"],"author":"","license":"MIT","devDependencies":{"@types/node":"^20.0.0","tsup":"^8.0.0","typescript":"^5.4.0","vitest":"^1.6.0"},"engines":{"node":">=18.0.0"},"sideEffects":false,"_id":"@0x-config/core@1.0.0","gitHead":"8667ce2f8f7f29d7a0298f0bef0d679a259cea50","bugs":{"url":"https://github.com/slotherinee/0x-config/issues"},"homepage":"https://github.com/slotherinee/0x-config#readme","_nodeVersion":"20.20.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-YDA0s7aamr6DxBXrxMhtsbrJ9CLanTNg0/LKMQnL0IjQDONL3UlzL5KiHA6FxqACVcIrYJDYD0Fxr9ZAUKm0gw==","shasum":"08e4c2677af5e0c3949c7f0611a062ea99324e79","tarball":"https://registry.npmjs.org/@0x-config/core/-/core-1.0.0.tgz","fileCount":15,"unpackedSize":72221,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0x-config%2fcore@1.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGvKxE1WOfXw/HR8c1erGFD9VGX+FKp7grg4mdeikv+TAiBoH8iknS8cVj3Ynqvbpv6kXbNJdBEgeHynYt5TQY9y2g=="}]},"_npmUser":{"name":"slotherinee","email":"paveltarasov121@gmail.com"},"directories":{},"maintainers":[{"name":"slotherinee","email":"paveltarasov121@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/core_1.0.0_1774105765053_0.6606530201526923"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-21T15:09:24.952Z","1.0.0":"2026-03-21T15:09:25.198Z","modified":"2026-03-21T15:09:25.617Z"},"maintainers":[{"name":"slotherinee","email":"paveltarasov121@gmail.com"}],"description":"Runtime environment variable validation for Node, Bun & Browser. Typed. Zero dependencies. Fail-fast.","homepage":"https://github.com/slotherinee/0x-config#readme","keywords":["env","environment","config","validation","dotenv","typescript","runtime","type-safe","zod","schema"],"repository":{"type":"git","url":"git+https://github.com/slotherinee/0x-config.git"},"bugs":{"url":"https://github.com/slotherinee/0x-config/issues"},"license":"MIT","readme":"# @0x-config/core\n\n**Runtime environment validation that yells at you before your app blows up in production.**\n\nZero dependencies. Fully typed. Works in Node, Bun, and the browser. Tiny (< 3kb gzipped).\n\n```\nnpm i @0x-config/core\npnpm add @0x-config/core\nyarn add @0x-config/core\nbun add @0x-config/core\n```\n\n---\n\n## The problem\n\nYou've been there. Deploy goes out. Ten minutes later — crash. Somewhere deep in a stack trace:\n\n```\nTypeError: Cannot read properties of undefined (reading 'split')\n```\n\nTurns out `DATABASE_URL` was never set in the prod environment. Your app booted fine, fetched nothing, then exploded the moment it tried to use it.\n\n**@0x-config/core fixes this at startup, not at 3am.**\n\n---\n\n## Quick start\n\n```ts\n// config.ts\nimport { createConfig } from '@0x-config/core'\n\nexport const config = createConfig({\n  PORT:         { type: 'port',    default: 3000 },\n  DATABASE_URL: { type: 'url',     description: 'PostgreSQL connection string',\n                                   example: 'postgresql://user:pass@localhost/mydb' },\n  JWT_SECRET:   { type: 'string',  minLength: 32, sensitive: true },\n  NODE_ENV:     { type: 'string',  oneOf: ['development', 'production', 'test'] as const },\n  DEBUG:        { type: 'boolean', default: false },\n  API_URL:      { type: 'url',     optional: true },\n})\n\n// config.PORT         → number\n// config.DATABASE_URL → string\n// config.JWT_SECRET   → string\n// config.DEBUG        → boolean\n// config.API_URL      → string | undefined\n```\n\nIf anything is missing or invalid when your app starts:\n\n```\n  ✘ 0x-config — environment validation failed\n\n  DATABASE_URL\n    → missing required variable (expected type: url)\n    ℹ  PostgreSQL connection string\n    e.g.  DATABASE_URL=postgresql://user:pass@localhost/mydb\n\n  JWT_SECRET\n    → must be ≥ 32 chars — got 8\n\n  2 problems found. Add the missing variables to your .env and restart.\n```\n\nAll errors at once — not one at a time.\n\n---\n\n## Types\n\n| Type      | Input examples                     | Output type |\n|-----------|------------------------------------|-------------|\n| `string`  | `\"hello\"`, `\"world\"`               | `string`    |\n| `number`  | `\"42\"`, `\"3.14\"`                   | `number`    |\n| `boolean` | `\"true\"`, `\"1\"`, `\"yes\"`, `\"on\"`   | `boolean`   |\n| `port`    | `\"3000\"`, `\"8080\"`                 | `number`    |\n| `url`     | `\"https://api.example.com\"`        | `string`    |\n| `email`   | `\"user@example.com\"`               | `string`    |\n| `json`    | `'{\"retries\":3}'`                  | `unknown`   |\n\n---\n\n## Field options\n\n```ts\n{\n  type?:          EnvType           // default: 'string'\n  optional?:      boolean           // no error if missing\n  default?:       T                 // used when variable is absent\n  description?:   string            // printed in error output and .env.example\n  example?:       string            // shown as e.g. KEY=value in errors\n  oneOf?:         T[]               // allowlist of exact values\n  validate?:      (v: T) => boolean | string        // sync custom validator\n  validateAsync?: (v: T) => Promise<boolean | string> // async validator (createConfigAsync only)\n  min?:           number            // number / port minimum\n  max?:           number            // number / port maximum\n  minLength?:     number            // string / url / email min length\n  maxLength?:     number            // string / url / email max length\n  sensitive?:     boolean           // always mask value in verbose output\n  transform?:     (v: T) => unknown // map coerced value to any shape\n}\n```\n\n---\n\n## Fluent builder API\n\nPrefer a chainable syntax? Use `c()`:\n\n```ts\nimport { createConfig, c } from '@0x-config/core'\n\nexport const config = createConfig({\n  PORT:         c('port').default(3000).build(),\n  DATABASE_URL: c('url')\n                  .describe('PostgreSQL connection string')\n                  .example('postgresql://user:pass@localhost/db')\n                  .build(),\n  JWT_SECRET:   c('string').minLength(32).sensitive().build(),\n  DEBUG:        c('boolean').default(false).build(),\n  NODE_ENV:     c('string').oneOf('development', 'production', 'test').build(),\n  RATE_LIMIT:   c('number').min(1).max(10_000).default(100).build(),\n})\n```\n\nBoth styles produce identical results.\n\n---\n\n## `getOrThrow` — one-off lookups\n\nNeed a single variable without defining a full schema:\n\n```ts\nimport { getOrThrow } from '@0x-config/core'\n\nconst secret = getOrThrow('JWT_SECRET', 'string')\nconst port   = getOrThrow('PORT', 'port')\n```\n\n---\n\n## Custom validators\n\n```ts\nexport const config = createConfig({\n  PASSWORD: {\n    type: 'string',\n    validate: (v) => {\n      if (v.length < 12)    return 'must be at least 12 characters'\n      if (!/[A-Z]/.test(v)) return 'must contain an uppercase letter'\n      if (!/[0-9]/.test(v)) return 'must contain a digit'\n      return true\n    },\n  },\n})\n```\n\n### Async validators\n\nWhen validation requires a network call or external check, use `createConfigAsync` with `validateAsync`:\n\n```ts\nimport { createConfigAsync } from '@0x-config/core'\n\nexport const config = await createConfigAsync({\n  DATABASE_URL: {\n    type: 'url',\n    validateAsync: async (url) => {\n      const ok = await pingDatabase(url)\n      return ok || 'database is unreachable'\n    },\n  },\n  REDIS_URL: {\n    type: 'url',\n    validateAsync: async (url) => checkRedis(url),\n  },\n})\n```\n\nAll async validators run in parallel. Sync errors are reported alongside async errors.\n\n---\n\n## Error handling modes\n\n```ts\ncreateConfig(schema, { onError: 'throw' })   // default — throws ConfigError with all issues\ncreateConfig(schema, { onError: 'warn' })    // prints errors to stderr, continues\ncreateConfig(schema, { onError: 'silent' })  // never throws; result.$errors has the list\n```\n\nCatch programmatically:\n\n```ts\nimport { createConfig, ConfigError } from '@0x-config/core'\n\ntry {\n  const config = createConfig(schema)\n} catch (err) {\n  if (err instanceof ConfigError) {\n    for (const e of err.errors) {\n      console.error(e.key, '→', e.message)\n    }\n  }\n}\n```\n\n---\n\n## Verbose mode\n\n```ts\ncreateConfig(schema, { verbose: true })\n```\n\nPrints a table of all loaded variables on success:\n\n```\n  ✔ 0x-config — all variables loaded\n\n  VARIABLE                         TYPE       VALUE\n  ────────────────────────────────────────────────────────────\n  PORT                             port       3000\n  DATABASE_URL                     url        postgresql://localhost/mydb\n  JWT_SECRET                       string     ●●●●●●●●\n  DEBUG                            boolean    false\n  NODE_ENV                         string     development\n```\n\nValues are masked automatically for keys that start with `secret`, `password`, `api_key`, `auth`, `token` etc. Mark any field explicitly with `sensitive: true` to always mask it regardless of the key name.\n\n---\n\n## `transform` — shape the value at load time\n\nMap a coerced value to any shape right in the schema definition. Runs after all validators pass:\n\n```ts\nexport const config = createConfig({\n  DATABASE_URL: {\n    type: 'url',\n    transform: (v) => new URL(v),\n  },\n  ALLOWED_IPS: {\n    type: 'string',\n    transform: (v) => v.split(',').map(s => s.trim()),\n  },\n  PORT: {\n    type: 'port',\n    transform: (v) => ({ port: v, address: `http://localhost:${v}` }),\n  },\n})\n\nconfig.DATABASE_URL  // URL instance\nconfig.ALLOWED_IPS   // string[]\nconfig.PORT          // { port: number; address: string }\n```\n\n---\n\n## Unused variable warnings\n\nCatch dead env vars before they accumulate:\n\n```ts\nconst source = parseDotEnv(readFileSync('.env', 'utf8'))\n\ncreateConfig(schema, { source, warnUnused: true })\n// ⚠ 0x-config — unused environment variables\n//   ~ LEGACY_STRIPE_KEY\n//   ~ OLD_API_URL\n```\n\nBest used with a parsed `.env` file rather than `process.env` (which contains many system variables).\n\n---\n\n## Watch mode (Node.js)\n\nIn development, re-validate on every `.env` change and see exactly what changed:\n\n```ts\nimport { watchConfig } from '@0x-config/core/watch'\n\nconst handle = watchConfig(schema, {\n  envFile: '.env',\n  onReload: (diff) => console.log('reloaded', diff),\n  onValidationError: (errors) => console.error('invalid env', errors),\n})\n\n// prints on change:\n//   ~ 0x-config — .env changed\n//   + NEW_VAR\n//   - REMOVED_VAR\n//   ~ DATABASE_URL\n//   ✔ re-validated successfully\n\n// stop watching:\nhandle.close()\n```\n\n`watchConfig` is a Node.js-only export — import from `@0x-config/core/watch` so browser bundles are never affected.\n\n---\n\n## CLI\n\nValidate your environment in CI before deploying, or generate a `.env.example` from your schema.\n\nYour schema file must export the raw schema object (not the result of `createConfig`):\n\n```ts\n// config.schema.ts\nexport const schema = {\n  PORT:         { type: 'port',   default: 3000 },\n  DATABASE_URL: { type: 'url',    description: 'PostgreSQL DSN' },\n  JWT_SECRET:   { type: 'string', minLength: 32, sensitive: true },\n} satisfies import('@0x-config/core').Schema\n```\n\n**Check current environment:**\n\n```sh\n# validate process.env against schema\nnpx @0x-config/core --check --schema ./dist/config.schema.js\n\n# validate a specific .env file\nnpx @0x-config/core --check --schema ./dist/config.schema.js --env .env.production\n```\n\nExits with code `1` on failure — drop it into any CI pipeline.\n\n**Generate `.env.example`:**\n\n```sh\nnpx @0x-config/core --generate-example --schema ./dist/config.schema.js\nnpx @0x-config/core --generate-example --schema ./dist/config.schema.js --output .env.example\n```\n\nOutput:\n```\n# Generated by 0x-config\n# Copy this file to .env and fill in the values\n\n# PostgreSQL DSN\n# type: url\nDATABASE_URL=https://example.com\n\n# type: port\n# default: 3000\nPORT=3000\n\n# type: string\nJWT_SECRET=\n```\n\nKeeps your example file in sync with the actual schema automatically.\n\n---\n\n## Using with .env files\n\n`@0x-config/core` does not auto-load `.env` files — pair it with your loader of choice:\n\n```ts\n// With dotenv\nimport 'dotenv/config'\nimport { createConfig } from '@0x-config/core'\nexport const config = createConfig({ /* ... */ })\n\n// With Bun (built-in .env loading)\nexport const config = createConfig({ /* ... */ })\n\n// With Vite — import.meta.env is detected automatically\nexport const config = createConfig({ /* ... */ })\n```\n\nOr use the built-in `parseDotEnv` to handle files yourself:\n\n```ts\nimport { createConfig, parseDotEnv } from '@0x-config/core'\nimport { readFileSync } from 'fs'\n\nconst source = parseDotEnv(readFileSync('.env', 'utf8'))\nexport const config = createConfig(schema, { source })\n```\n\n---\n\n## Prefix support (Vite, Next.js, etc.)\n\n```ts\n// Maps schema key PORT → process.env.VITE_PORT\ncreateConfig(schema, { prefix: 'VITE_' })\n\n// Maps schema key PORT → process.env.NEXT_PUBLIC_PORT\ncreateConfig(schema, { prefix: 'NEXT_PUBLIC_' })\n```\n\n---\n\n## TypeScript\n\nFull type inference — no extra steps needed:\n\n```ts\nconst config = createConfig({\n  PORT:  { type: 'port' },     // → number\n  DEBUG: { type: 'boolean' },  // → boolean\n  NAME:  { optional: true },   // → string | undefined\n})\n\nconfig.PORT   // number  ✓\nconfig.DEBUG  // boolean ✓\nconfig.NAME   // string | undefined ✓\n```\n\n`transform` return types are inferred too:\n\n```ts\nconst config = createConfig({\n  DATABASE_URL: { type: 'url', transform: (v) => new URL(v) },\n})\n\nconfig.DATABASE_URL  // URL ✓\n```\n\nThe returned object is frozen (`Object.freeze`) — no accidental mutation of runtime config.\n\n---\n\n## Why not just use Zod?\n\nZod is fantastic for validating arbitrary data. `@0x-config/core` is specifically built for the env-loading use case:\n\n- No dependency to install alongside it\n- Understands env-specific types (`port`, `url`, `email`)\n- Auto-detects the env source (Node, Bun, Vite)\n- Error messages are written for developers, not machines\n- Watch mode for development\n- CLI for CI validation and example generation\n- The entire library is < 3kb gzipped\n\nUse Zod for your API schemas. Use `@0x-config/core` for your environment.\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-3b205aa191bb75732e6160cd732179f4"}