Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
# OpenSpec Change: enterprise-env
- **Change ID**: enterprise-env
- **Package**: `@suaveplan/env`
- **Category**: universal
- **Target path**: `packages/universal/env`
- **Status**: PROPOSED
- **Date**: 2026-04-19
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/env.md`
## Summary
Greenfield implementation of `@suaveplan/env` for the genesis branch. The existing main-branch env package provides the baseline public-API surface to preserve; this change delivers enterprise-grade type-safe environment variable loading with schema-first validation, a rich set of typed field builders (string, number, boolean, enum, URL, email, port, JSON, array), pluggable loaders (dotenv file, plain object), common presets for server apps, structured errors with `ENV.*` codes, Zod schemas, OTel instrumentation, property-based tests via fast-check, and benchmarks.
## Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build.
## Available workspace deps on genesis
| Package | Available |
|---------|-----------|
| `@suaveplan/error` | ✅ |
| `@suaveplan/types` | ✅ |
| `@suaveplan/logger` | ✅ |
| `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly |
| `@suaveplan/schema-registry` | ❌ not yet built — export Zod schemas directly |
External deps (via catalog):
- `zod` → `catalog:forms`
- `@opentelemetry/api` → `catalog:observability`
- `tslib` → `catalog:build`
Dev deps (via catalog):
- `vitest`, `@vitest/coverage-istanbul` → `catalog:testing`
- `fast-check` → `catalog:testing`
- `mitata` → `catalog:testing`
## Preserved public API surface (from main branch)
### Constants
- `DEFAULT_ARRAY_SEPARATOR` — default delimiter used by `ArrayField`
- `EMAIL_PATTERN` — regex for RFC-5322-compatible email validation
- `FALSY_VALUES` — string values coerced to `false` by `BooleanField`
- `INTERPOLATION_PATTERN` — regex matching `${VAR}` tokens in raw values
- `PORT_MAX` — upper bound for valid TCP port (65535)
- `PORT_MIN` — lower bound for valid TCP port (1)
- `TRUTHY_VALUES` — string values coerced to `true` by `BooleanField`
### Core
- `createEnv(schema, options?)` — loads env vars through configured loaders, validates all fields, collects or throws errors, returns typed record
- `CollectedErrors` — map of field key → array of error messages produced during a collect-mode run
- `EnvResult<S>` — the resolved typed record produced by `createEnv`
- `BaseField<T>` — abstract base class for all field builders; holds rules, default, sensitive flag, description
- `FieldConfig<T>` — runtime configuration snapshot extracted from any `BaseField`
- `RuleOptions` — options accepted by `BaseField.rules()` for adding a named validation rule
- `maskSensitive(values, schema)` — replaces sensitive field values with `***` in the result map
- `validate(input)` → `ValidationResult` — runs all rules for a single field, accumulating failures
- `ValidateInput` — `{ key, raw, field, coerced? }` fed into `validate`
- `ValidationResult` — `{ ok, errors }` where `errors` is an array of `{ rule, message }` pairs
### Types
- `AnyField<T, R>` — minimal interface every field builder satisfies; used for schema maps
- `CreateEnvOptions` — options accepted by `createEnv`: `loaders`, `errorMode`, `signal?`
- `EnvError` — `{ key, message, value? }` shape for a single validation failure
- `EnvSchema` — `Record<string, AnyField>` used as the schema argument to `createEnv`
- `ErrorMode` — `'throw' | 'collect' | 'warn'`
- `InferEnv<S>` — maps an `EnvSchema` to the resulting typed record type
- `InferFieldType<F>` — extracts the value type from a field builder
- `Loader` — `{ name: string; load(): LoaderResult }` interface for env sources
- `LoaderResult` — `{ values: Record<string, string>; source: string }`
- `OptionalKeys<S>` — keys whose fields may produce `undefined`
- `RequiredKeys<S>` — keys whose fields always produce a value
- `Simplify<T>` — intersection flattener utility type
- `ValidationRule<T>` — `{ name, message, validate(value): boolean }`
### Fields
- `StringField` / `str` — string field builder; rules: `minLength`, `maxLength`, `pattern`, `nonempty`
- `NumberField` / `num` — number field; rules: `min`, `max`, `integer`
- `BooleanField` / `bool` — boolean field; coerces TRUTHY_VALUES / FALSY_VALUES strings
- `EnumField<V>` / `enums` — enum field locked to a declared `EnumValues` tuple
- `EnumValues` — string-tuple type constraint for `EnumField`
- `UrlField` / `url` — URL field; validates via `URL` constructor, optional protocol restriction
- `EmailField` / `email` — email field; validates against `EMAIL_PATTERN`
- `PortField` / `port` — port field; integer in `[PORT_MIN, PORT_MAX]`
- `JsonField<T>` / `json` — JSON field; accepts a `JsonValidator<T>` for runtime shape checking
- `JsonValidator<T>` — `(parsed: unknown) => T` used with `JsonField`
- `ArrayField<T>` / `array` — array field; splits by `DEFAULT_ARRAY_SEPARATOR` (or custom), maps each item through an `ItemField`
- `ItemField<T>` — per-element field descriptor for `ArrayField`
### Loaders
- `dotenvLoader(options?)` — reads a `.env` file from disk and returns a `LoaderResult`
- `parseDotenv(content)` — pure function: parses a dotenv-format string into a `Record<string, string>`
- `DotenvLoaderOptions` — `{ path?, encoding?, override? }`
- `objectLoader(obj, options?)` — wraps an in-memory object as a loader
- `ObjectLoaderOptions` — `{ source? }` (source label for diagnostics)
### Presets — common
- `nodeEnvField` — `NODE_ENV` enum field locked to `'development' | 'test' | 'production' | 'staging'`
- `portField` — `PORT` with default 3000
- `hostField` — `HOST` with default `'0.0.0.0'`
- `logLevelField` — `LOG_LEVEL` enum locked to standard logger levels
- `debugField` — `DEBUG` boolean with default `false`
- `appNameField` — `APP_NAME` required string
- `appVersionField` — `APP_VERSION` string with default `'0.0.0'`
- `timezoneField` — `TZ` string with default `'UTC'`
- `workersField` — `WORKERS` integer number
- `commonFields` — composite preset: `nodeEnvField + portField + hostField + logLevelField + debugField`
### Presets — server
- `databaseUrlField` — `DATABASE_URL` sensitive required URL
- `redisUrlField` — `REDIS_URL` sensitive URL
- `sessionSecretField` — `SESSION_SECRET` sensitive required string with `minLength: 32`
- `corsOriginField` — `CORS_ORIGIN` string or array
- `rateLimitMaxField` — `RATE_LIMIT_MAX` integer number
- `rateLimitWindowField` — `RATE_LIMIT_WINDOW_MS` integer number
- `trustProxyField` — `TRUST_PROXY` boolean with default `false`
- `smtpHostField` — `SMTP_HOST` string
- `smtpPortField` — `SMTP_PORT` port field
- `smtpUserField` — `SMTP_USER` string
- `smtpPassField` — `SMTP_PASS` sensitive string
- `smtpFromField` — `SMTP_FROM` email field
- `serverFields` — composite: common server connection + SMTP fields
### Transforms (coerce utilities)
- `coerceString(raw)` — identity / trim
- `coerceNumber(raw)` — parses float, rejects NaN
- `coerceInteger(raw)` — parses integer, rejects non-integer
- `coerceBoolean(raw)` — maps truthy/falsy string sets
- `coerceArray(raw, separator?)` — splits and trims
- `coerceJson(raw)` — `JSON.parse` with structured error on failure
- `CoerceFn<T>` — `(raw: string) => T`
- `CoerceEntry<T>` — `{ coerce: CoerceFn<T> }` mixin used internally
### Utilities
- `EnvValidationError` extends `SuaveplanError` — thrown in `'throw'` error mode
- `MissingEnvError` extends `SuaveplanError` — thrown when a required key is absent
- `CoercionError` extends `SuaveplanError` — thrown when coercion fails for a raw value
- `DotenvParseError` extends `SuaveplanError` — thrown by `parseDotenv` on malformed input
- `createEnvError(key, message, value?)` — factory for `EnvError` value objects
- `interpolate(template, values)` — resolves `${VAR}` tokens in a string from a values map
- `hasInterpolation(value)` — returns `true` if the string contains any `${VAR}` tokens
- `extractVariables(template)` — returns the list of variable names referenced in a template string
## New genesis additions
### Structured errors
- `EnvError` base class extends `SuaveplanError` with codes under the `ENV.*` namespace registered via `defineCodes`
- Codes:
- `ENV.VALIDATION_FAILED` — one or more fields failed validation rules
- `ENV.MISSING_REQUIRED` — a required field has no value and no default
- `ENV.INVALID_FORMAT` — coercion of a raw string to the target type failed
- `ENV.PARSE_ERROR` — dotenv file parse failed or JSON parse failed
- `EnvValidationError`, `MissingEnvError`, `CoercionError`, `DotenvParseError` all extend `EnvError` using the appropriate code
- All errors carry `{ key, raw?, source? }` context for diagnostics
### Zod schemas
- `FieldConfigSchema` — validates the `FieldConfig<unknown>` shape at runtime
- `ValidationResultSchema` — validates `{ ok, errors }` produced by `validate`
- `EnvResultSchema` — validates the merged output record from `createEnv`
- `CreateEnvOptionsSchema` — validates options passed to `createEnv`
- `DotenvLoaderOptionsSchema`, `ObjectLoaderOptionsSchema` — validates loader options at the boundary
- All schemas exported from `*.schemas.ts` sibling files
### OTel instrumentation on createEnv
- Counter `env.validation.total` (labels: `outcome: 'success' | 'error'`) — incremented once per `createEnv` call
- Counter `env.validation.errors` (label: `field`) — incremented once per field that fails validation
- Histogram `env.load.duration_ms` — duration of the full `createEnv` call including all loaders
- All meters via `@opentelemetry/api`; no-op without exporter
### Property-based tests (fast-check)
- Field parsers round-trip: `coerce(raw)` produces a stable value for all valid `raw` strings
- `maskSensitive` never leaks values: for any schema with sensitive fields, the masked output never contains the original raw value at that key
- `validate` accumulates all errors: if N rules fail, `ValidationResult.errors.length === N`
- `parseDotenv` is pure: same input always yields same output
### Benchmarks (mitata)
- `createEnv` with a 20-field schema, object loader, no errors — hot-path throughput
- `validate` hot path — single field with 5 rules, all passing
## Impact
- Adds one new package: `packages/universal/env`
- Adds one new spec capability: `env`
- No existing package consumers on genesis yet