feat(config): typed ConfigService with dot-path inference - #8
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a typed, injectable ConfigService<T> as the primary consumption API for validated configuration, including compile-time namespace.leaf dot-path inference via new Path<T> / PathValue<T, P> utilities, and wires the service into the module’s provider/exports surface.
Changes:
- Introduces
Path<T>/PathValue<T, P>template-literal type utilities (two-level only) with compile-time type tests. - Adds
ConfigService<T>(get,getAll,has) and registers/exports it fromBymaxConfigModule, including global-injection integration coverage. - Updates public exports and phase/dashboard documentation to mark Phase 4 complete.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types.ts | Adds Path / PathValue type utilities for two-level dot-path inference. |
| src/types.spec.ts | Adds compile-time and runtime assertions for Path / PathValue. |
| src/index.ts | Exports ConfigService, Path, and PathValue from the public surface. |
| src/config.service.ts | Introduces injectable typed accessor over BYMAX_CONFIG. |
| src/config.service.spec.ts | Unit coverage for ConfigService behavior and type inference. |
| src/config.module.ts | Registers and exports ConfigService alongside BYMAX_CONFIG. |
| src/config.module.spec.ts | Verifies module provides/exports both config surfaces. |
| src/config.module.integration.spec.ts | Proves global injection of ConfigService<AppConfig> from a non-importing module. |
| docs/tasks/README.md | Updates phase progress table for Phase 4 completion. |
| docs/tasks/phase-04-typed-accessor.md | Marks Phase 4 tasks complete and logs completion details. |
| docs/development_plan.md | Updates overall dashboard progress and Phase 4 status. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…th namespace (4.4)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 4 (typed-accessor) ships
ConfigService<T>, the recommended consumption surface over the frozenBYMAX_CONFIGobject, with compile-time dot-path inference limited to the two-levelnamespace.leafconvention. Consumers read validated values with the leaf type inferred from the path and no cast, while invalid paths fail to compile.What changed
Path<T>andPathValue<T, P>type utilities (src/types.ts): template-literal utilities scoped to exactly two levels (namespace and leaf), with no recursive conditional types, so compiler cost stays flat for large schemas. Deeper nesting stays typed throughgetAll()and is documented as a known limitation.ConfigService<T>(src/config.service.ts):@Injectable()accessor injecting the frozen config via explicit@Inject(BYMAX_CONFIG)(no decorator metadata; the build has noemitDecoratorMetadata). API:get<P extends Path<T>>(path)returnsPathValue<T, P>, resolving the single-dot path across the two fixed levels. Never throws for a declared path because validation completed at bootstrap.getAll()returns the same deep-frozen root by reference (the escape hatch for arbitrary-depth typing).has(path)reports declared-path presence, returningfalsewhen the resolved value isundefined.src/config.module.ts):BymaxConfigModulenow provides and exportsConfigServicealongsideBYMAX_CONFIG, for bothforRootandforRootAsync.src/index.ts): exportsConfigService,Path, andPathValue.Verification
All gates green:
pnpm typecheck— clean (bothtsconfig.jsonandtsconfig.build.json), including the compile-time type-test assertions.pnpm lint— clean (zero warnings).pnpm build— emits.mjs/.cjs/.d.tsfor both subpaths (.and./testing).pnpm test:cov:all— 95/95 tests pass, 100% statements / branches / functions / lines on every runtime file, including the newconfig.service.ts(type-only files carry no runtime code).Type-inference evidence (pinned by compile-time assertions):
get('database.url')types asstring;get('server.port')asnumber;get('server.env')as the declared literal union.PathValue<AppConfig, 'server.port'>resolves tonumber.@ts-expect-errorinside the type-test specs (a positive compile-time assertion: an unused directive fails the build): namespace-only ('database'), undeclared leaf ('database.missing'), and arbitrary strings ('not.a.path').Global typed injection is proven end-to-end in the integration suite: a feature provider in a nested module injects
ConfigService<AppConfig>without importingBymaxConfigModuleand reads typed values, sharing the single frozen root.Definition of Done (P4)
get('database.url')type-checks asstringandget('server.port')asnumber; an invalid path fails compilation. Done.getAllreturns the frozen root;hasreports declared-path presence. Done.Follow-ups
createTestConfig,configTestingModule) land in Phase 5.getAll()is the typed escape hatch.