Feature/compt 51 config module and service#3
Conversation
- Add defineConfig(schema) returning ConfigDefinition<T> - Add ConfigDefinition.parse() — validates process.env synchronously, returns frozen fully-typed config, throws ConfigValidationError on failure - Add ConfigValidationError extending Error with fields: ZodIssue[] - Add zod as peerDependency (^3 || ^4) - Install zod as devDependency for local development - Update src/index.ts to export defineConfig, ConfigDefinition, ConfigValidationError - Add node to tsconfig types array for process.env access
- Add ConfigModule with register() sync, registerAsync() async factory, and forRoot() global registration - Add ConfigService<TDef> with typed get<K>(key) — return type inferred directly from Zod schema output, no casting needed - Add CONFIG_VALUES_TOKEN internal DI token in constants.ts - Validation runs before any provider resolves in all three registration modes - App fails to boot when validation fails (ConfigValidationError thrown) - Update src/index.ts to export ConfigModule, ConfigService, ConfigModuleAsyncOptions
There was a problem hiding this comment.
Pull request overview
Adds the NestJS integration layer for @ciscode/config-kit, exposing a dynamic ConfigModule and injectable ConfigService so consumers can validate process.env at startup and retrieve strongly-typed config values throughout an app.
Changes:
- Export
ConfigModule,ConfigService, andConfigModuleAsyncOptionsfrom the public entrypoint. - Introduce internal DI token(s) (
CONFIG_VALUES_TOKEN) for wiring parsed config into Nest providers. - Implement
ConfigModule.register/registerAsync/forRootplus typedConfigService.get()backed by the validated config object.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/index.ts | Exposes the new NestJS module/service types as public API exports. |
| src/constants.ts | Defines internal DI token used to provide the parsed config object. |
| src/config.service.ts | Implements typed ConfigService<TDef>.get(key) via injected parsed config. |
| src/config.module.ts | Implements dynamic module registration APIs (sync/async/global) and providers wiring. |
| @Global() // Applied only when used via forRoot(); register() and registerAsync() are non-global | ||
| @Module({}) | ||
| export class ConfigModule { |
There was a problem hiding this comment.
ConfigModule is decorated with @Global(), which makes it global for all usages (including register() and registerAsync()), contradicting the doc comment and the returned DynamicModule objects that set global: false. This changes DI scope unexpectedly for consumers. Remove the @Global() decorator and rely solely on returning global: true from forRoot() (or split into separate global/non-global modules).
| imports?: DynamicModule["imports"]; | ||
|
|
||
| /** | ||
| * Tokens to inject into `useFactory` as positional arguments. | ||
| * Same semantics as `inject` in a standard NestJS `useFactory` provider. | ||
| */ | ||
| inject?: unknown[]; | ||
|
|
||
| /** | ||
| * Factory function that returns the ConfigDefinition to validate. | ||
| * May be synchronous or async. | ||
| */ | ||
| useFactory: ( | ||
| ...args: unknown[] | ||
| ) => Promise<ConfigDefinition<T>> | ConfigDefinition<T>; | ||
| } |
There was a problem hiding this comment.
ConfigModuleAsyncOptions types inject and useFactory args as unknown[], which gives consumers little compile-time help and makes it easy to pass invalid injection tokens/argument shapes. Consider typing inject as Nest’s injection token list type (e.g., FactoryProvider['inject'] / InjectionToken[]) and using generics/tuples to type the useFactory parameters accordingly.
| return definition.parse(process.env); | ||
| }, | ||
| inject: (options.inject ?? []) as never[], | ||
| }; |
There was a problem hiding this comment.
inject: (options.inject ?? []) as never[] is an unsafe assertion that disables type checking and can hide mistakes in options.inject. Prefer passing inject: options.inject ?? [] with the correct type on ConfigModuleAsyncOptions.inject so this provider stays type-safe without assertions.
Summary
Why
Checklist
npm run lintpassesnpm run typecheckpassesnpm testpassesnpm run buildpassesnpx changeset) if this affects consumersNotes