11 KiB
11 KiB
Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/config/env.
Tasks: enterprise-env
1. Scaffold
- 1.1 Create
packages/universal/env/withpackage.json,tsconfig.json,tsconfig.lib.json,vite.config.ts,vitest.config.ts,README.mdskeleton - 1.2 Register coverage thresholds at 100/100/100/100 in
vitest.config.ts; provider = istanbul - 1.3 Add cross-cutting guard tests in
tests/: dep-graph, no-disallowed, tree-shake - 1.4 Add package to root
CLAUDE.md,README.md, andSTRUCTURE.yml
2. Constants + types + errors
- 2.1 Implement
src/constants/index.ts—DEFAULT_ARRAY_SEPARATOR,EMAIL_PATTERN,FALSY_VALUES,INTERPOLATION_PATTERN,PORT_MAX,PORT_MIN,TRUTHY_VALUES - 2.2 Implement
src/types/common.ts—AnyField,CreateEnvOptions,EnvError,EnvSchema,ErrorMode,InferEnv,InferFieldType,Loader,LoaderResult,OptionalKeys,RequiredKeys,Simplify,ValidationRule - 2.3 Implement
src/errors/env-error.ts—EnvErrorbase extendsSuaveplanError;EnvValidationError,MissingEnvError,CoercionError,DotenvParseErrorextendEnvError;defineCodesforENV.VALIDATION_FAILED,ENV.MISSING_REQUIRED,ENV.INVALID_FORMAT,ENV.PARSE_ERROR - 2.4 Implement
src/errors/error-codes.tsand auto-register via side-effect import insrc/index.ts - 2.5 Zod schemas:
FieldConfigSchema,ValidationResultSchema,CreateEnvOptionsSchema,DotenvLoaderOptionsSchema,ObjectLoaderOptionsSchema— exported from*.schemas.tssiblings - 2.6 Tests: error construction, code narrowing,
{ key, raw?, source? }context fields on each error subclass
3. Core validator (validate + maskSensitive)
- 3.1 Implement
src/core/validator/validator.types.ts—ValidateInput,ValidationResult - 3.2 Implement
src/core/validator/validator.ts—validate(input): ValidationResult(runs allfield._rules, accumulates failures);maskSensitive(values, schema): Record<string, string>(replaces sensitive values with***) - 3.3 Unit tests: all rules passing, partial failure accumulation, all rules failing, sensitive masking
- 3.4 Property-based tests (fast-check): if N rules fail,
result.errors.length === N;maskSensitivenever leaks a sensitive raw value - 3.5 Co-located
src/core/validator/validator.md
4. BaseField abstract class
- 4.1 Implement
src/core/field/field.types.ts—FieldConfig<T>,RuleOptions - 4.2 Implement
src/core/field/field.ts—BaseField<T>abstract class with_isRequired,_hasDefault,_defaultValue,_isSensitive,_description,_rules; fluent builder methods:optional(),default(v),sensitive(),describe(s),rules(opts); abstractcoerce(raw: string): T - 4.3 Unit tests: builder method chaining,
FieldConfigsnapshot, abstract coerce enforcement - 4.4 Co-located
src/core/field/field.md
5. Fields
- 5.1
src/fields/string-field/string-field.ts—StringFieldextendsBaseField<string>; rules:minLength,maxLength,pattern,nonempty; factorystr() - 5.2
src/fields/number-field/number-field.ts—NumberFieldextendsBaseField<number>; coerces viacoerceNumber; rules:min,max,integer; factorynum() - 5.3
src/fields/boolean-field/boolean-field.ts—BooleanFieldextendsBaseField<boolean>; coerces viacoerceBoolean; factorybool() - 5.4
src/fields/enum-field/enum-field.types.ts—EnumValues;src/fields/enum-field/enum-field.ts—EnumField<V extends EnumValues>extendsBaseField<V[number]>; factoryenums(values) - 5.5
src/fields/url-field/url-field.ts—UrlFieldextendsBaseField<string>; validates vianew URL(); optionalprotocolsallowlist rule; factoryurl() - 5.6
src/fields/email-field/email-field.ts—EmailFieldextendsBaseField<string>; validates againstEMAIL_PATTERN; factoryemail() - 5.7
src/fields/port-field/port-field.ts—PortFieldextendsBaseField<number>; validates integer in[PORT_MIN, PORT_MAX]; factoryport() - 5.8
src/fields/json-field/json-field.types.ts—JsonValidator<T>;src/fields/json-field/json-field.ts—JsonField<T>extendsBaseField<T>; coerces viacoerceJson; accepts optionalJsonValidator<T>; factoryjson<T>(validator?) - 5.9
src/fields/array-field/array-field.types.ts—ItemField<T>;src/fields/array-field/array-field.ts—ArrayField<T>extendsBaseField<T[]>; splits onseparator(defaultDEFAULT_ARRAY_SEPARATOR), maps each item throughitemField; factoryarray(itemField, separator?) - 5.10 Unit tests for each field: valid raw, invalid raw →
CoercionError, all custom rules, default / optional / sensitive combinations - 5.11 Co-located
.mdfor each field file
6. Transforms (coerce utilities)
- 6.1 Implement
src/transforms/coerce/coerce.types.ts—CoerceFn<T>,CoerceEntry<T> - 6.2 Implement
src/transforms/coerce/coerce.ts—coerceString,coerceNumber,coerceInteger,coerceBoolean,coerceArray,coerceJson; each throwsCoercionErrorwithENV.INVALID_FORMATon failure - 6.3 Unit tests: happy-path and all rejection branches for each coerce function
- 6.4 Property-based tests (fast-check):
coerceNumber(raw)round-trips for all finite float strings;coerceBooleanis total over TRUTHY_VALUES ∪ FALSY_VALUES - 6.5 Co-located
src/transforms/coerce/coerce.md
7. Loaders
- 7.1
src/loaders/object/object.ts—objectLoader(obj, options?)→Loader;ObjectLoaderOptions; source label defaults to'object' - 7.2
src/loaders/dotenv/dotenv.ts—parseDotenv(content): Record<string, string>(pure, throwsDotenvParseErroron malformed lines);dotenvLoader(options?)→Loaderreads file fromoptions.path(default.env), callsparseDotenv;DotenvLoaderOptions - 7.3 Unit tests for
objectLoader: wraps object, respects source label - 7.4 Unit tests for
parseDotenv: quoted values, comments, blank lines,=in value, malformed input - 7.5 Unit tests for
dotenvLoader: reads real temp file, file-not-found returns empty gracefully (or throws — match main-branch behaviour),overrideflag - 7.6 Property-based tests (fast-check):
parseDotenvis pure — same input always produces same output - 7.7 Co-located
.mdfor each loader file
8. Utilities — interpolation + error factory
- 8.1
src/utils/interpolation.ts—interpolate(template, values),hasInterpolation(value),extractVariables(template); usesINTERPOLATION_PATTERN - 8.2
src/utils/errors.ts—createEnvError(key, message, value?)factory returningEnvErrorvalue object (not the class — the plain interface shape used byCollectedErrors) - 8.3 Unit tests for all interpolation functions and the error factory
- 8.4 Co-located
.mdfor each utility file
9. createEnv orchestrator
- 9.1 Implement
src/core/create-env/create-env.types.ts—CollectedErrors,EnvResult<S> - 9.2 Implement
src/core/create-env/create-env.ts—createEnv(schema, options?):- Runs each
Loaderinoptions.loadersin order, mergingLoaderResult.values(later loaders win unlessoverride: false) - Expands
${VAR}interpolation tokens in raw values - For each schema key: resolves raw string → calls
coerce→ runsvalidate→ checksisRequired - In
'throw'mode: throwsEnvValidationErrororMissingEnvErroron first failure - In
'collect'mode: accumulates all failures intoCollectedErrors, returns partial result - In
'warn'mode: logs viaconsole.warn, returns partial result - Calls
maskSensitivebefore returning - Returns
EnvResult<S>(typed record)
- Runs each
- 9.3 Unit tests: full schema resolution, missing required, coercion error, collect mode accumulation, throw mode, warn mode, multi-loader merge order, interpolation expansion
- 9.4 Co-located
src/core/create-env/create-env.md
10. Presets
- 10.1
src/presets/common/common.ts—nodeEnvField,portField,hostField,logLevelField,debugField,appNameField,appVersionField,timezoneField,workersField,commonFields - 10.2
src/presets/server/server.ts—databaseUrlField,redisUrlField,sessionSecretField,corsOriginField,rateLimitMaxField,rateLimitWindowField,trustProxyField,smtpHostField,smtpPortField,smtpUserField,smtpPassField,smtpFromField,serverFields - 10.3 Unit tests: each preset field resolves correctly from a matching env object;
commonFieldsandserverFieldscomposites contain all expected keys - 10.4 Co-located
.mdfor each preset file
11. OTel instrumentation
- 11.1 Add
@opentelemetry/apitodependenciesinpackage.json - 11.2 Wire counter
env.validation.total(labeloutcome: 'success' | 'error') increateEnv— incremented once per call - 11.3 Wire counter
env.validation.errors(labelfield) increateEnv— incremented once per field that fails validation - 11.4 Wire histogram
env.load.duration_ms— duration of the fullcreateEnvcall - 11.5 All meters obtained from
@opentelemetry/apigetMeter; no-op without exporter (verified in test with no SDK registered) - 11.6 Unit test: metrics instruments are created without error when no exporter is present
12. Property tests + benchmarks
- 12.1 Property-based tests (fast-check) for field parsers round-trip (valid raw → coerce → stable value)
- 12.2 Property test:
maskSensitivenever leaks raw values for sensitive fields - 12.3 Property test:
validateaccumulates all N failing rules - 12.4 Property test:
parseDotenvis pure and idempotent - 12.5
src/__benchmarks__/create-env.bench.ts—createEnvwith 20-field schema,objectLoader, no errors; throughput via mitata - 12.6
src/__benchmarks__/validate.bench.ts—validatehot path: single field with 5 passing rules; iterations via mitata - 12.7 Publish baseline numbers in README
13. Docs
- 13.1 Co-located
.mdfor every implementation file (≥200 words, simple + advanced examples, API reference, implementation notes) - 13.2 Rewrite
README.mdwith public API table, loader usage, preset reference, error code table, OTel metric names, benchmark numbers, consumer quick-start - 13.3
bun run docs:check --filter=@suaveplan/env≥ 90%
14. Verify and publish
- 14.1
bunx turbo lint typecheck test --filter=@suaveplan/envgreen - 14.2
bunx turbo test:coverage --filter=@suaveplan/envat 100/100/100/100 - 14.3
bunx turbo build --filter=@suaveplan/envgreen - 14.4
bun scripts/publish.ts --package @suaveplan/envto Verdaccio at 0.0.0 - 14.5 Archive OpenSpec change to
openspec/archive/2026-04-19-enterprise-env/