fix(config): guard against reading Config singleton before bootstrap#3835
Merged
Conversation
The exported Config singleton is undefined until ConfigService is constructed. Reading it in a class field initializer crashes NestJS bootstrap when the provider is instantiated before ConfigService (provider-ordering / circular dependency) - this previously took the whole API down. Add two guards: - ESLint no-restricted-syntax rule forbidding Config reads in field initializers of @Injectable/@controller classes, and convert four pre-existing latent occurrences (faucet-request, realunit, lnurl x2) to getters so they survive any future instantiation-ordering change. - Regression test that compiles each affected provider in isolation without ConfigService (Config undefined) and asserts construction does not throw.
davidleomay
approved these changes
Jun 8, 2026
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.
Background
A recent incident took the whole API down on DEV:
PricingRealUnitServiceread theConfigsingleton in a class field initializer.Config(export let Configinsrc/config/config.ts) isundefineduntilConfigServiceis constructed, so when that provider was instantiated beforeConfigService,Config.environmentthrew and NestJS bootstrap crashed (all health checks down). That service was fixed separately (#3832); this PR prevents the whole class of bug from recurring.What this adds
A) Static guard + fix latent landmines
no-restricted-syntax) forbidding reads of theConfigsingleton in field initializers of@Injectable/@Controllerclasses. Scoped to those decorators so request DTOs/entities (built at runtime, whenConfigis already set) are not affected.faucet-request.service.ts(faucetBlockchain)realunit.service.ts(tokenBlockchain)lnurl-forward.service.ts(PAYMENT_LINK_PREFIX,PAYMENT_LINK_PAYMENT_PREFIX)B) Regression test
src/config/__tests__/config-bootstrap-order.spec.tscompiles each affected provider in isolation, withoutConfigService(soConfigis undefined during construction) and asserts it does not throw. Verified to fail on the old field-initializer pattern with the exact production error and pass with getters.Why not a full app-bootstrap test
A full
AppModule.compile()smoke test is not viable pre-merge: many blockchain/exchange providers do real I/O in their constructors (Spark wallet init, Scrypt websocket, ethers clients) and need real secrets/URLs. The static rule + isolated regression test cover the failure class cleanly without that; the full boot is already exercised by the DEV deploy.Local checks (npm ci, all green)
lint✅ ·format:check✅ ·build✅ ·test(76 suites / 1014 tests) ✅ ·npm audit --audit-level=critical✅Test plan
@Injectablefield-initializer Config reads, ignores DTOs