Code review fixes: registration, lazy proxies, build task, diagnostics - #2
Open
autonomous-low-noise wants to merge 16 commits into
Open
autonomous-low-noise wants to merge 16 commits into
autonomous-low-noise wants to merge 16 commits into
Conversation
register() recursed into every design:paramtypes entry, so a constructor param typed as an interface (emitted as Object), a primitive, or any undecorated class crashed at import time with E_REFLECT_UTIL_NO_DI_TOKEN — even when the param carried an explicit @Inject token that settle() would resolve via container lookup. Skip recursion for params with an explicit token, mirroring the settlement logic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
register() built a fresh proxy + context on every registration. When a token was overridden after settleLazy() had handed out a proxy, the old context was never hydrated: the settler callback settled the new wraplet while the held proxy stayed dead forever. Overrides now reuse the existing proxy & context, establishing the invariant that token -> proxy is stable for the container's lifetime and only the context's instance pointer changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
The proxy trapped only `get` and passed the proxy itself as receiver:
- writes/`in`/`delete`/`Object.keys` hit the internal proxy context
(a pre-hydration write silently vanished after hydration and
Object.keys leaked ['dependencyInstance'])
- getters and methods touching #private state threw ("Cannot read
private member") even after hydration, because `this` was the proxy
The proxy now behaves as an empty object before hydration (reads yield
undefined, writes are ignored) and as a transparent view afterwards:
set/has/deleteProperty/ownKeys/getOwnPropertyDescriptor/getPrototypeOf
forward to the instance, getters receive the instance as receiver, and
methods are bound to the instance (with a cache to keep function
identity stable). The __isUI5DependencyProxy brand now also exists at
runtime, matching the ClassInstanceProxy type.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
…ee token The token-preservation task used String.replace with a string pattern, which rewrites only the FIRST occurrence: a file with two or more polymorphic injectables shipped the rest with class-name tokens that terser then mangled — silent, production-only token collisions. The token was also Symbol.for(<basename>), so same-named files in different folders collided in the global symbol registry. Match all @Injectable() occurrences (the @ anchor also stops accidental hits on identifiers ending in "Injectable()"), and derive the token from the full resource path plus an occurrence index so every class in every file gets a distinct, minification-stable token. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
All three error constants were thrown bare, so every failure looked identical regardless of which token or class caused it. The container miss now names the token (via a new stringifyToken helper in misc), the missing-metadata error names the class, and the factory-only violation names the class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
A constructor cycle (A needs B, B needs A) recursed in settle() until a bare RangeError with no diagnostic. settle() now tracks in-flight tokens on a settlement stack and reports the full chain on re-entry, e.g. "Dependency cycle detected: CYCLE:A -> CYCLE:B -> CYCLE:A". The stack unwinds in a finally block so failed settlements leave no residue. Also guard register() against a paramtype of undefined (the class reference was not initialized when the decorator ran, typically a circular import) with a descriptive error naming the class and parameter index, instead of a cryptic TypeError from the Reflect API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
settle(token, factory) on a non-factory-only class still settled every constructor dependency, then discarded the results because the factory branch never consumes them — instantiating and caching singletons nobody asked for. Nested settlement now only runs when the constructor path will actually receive the instances. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
Precedence was snapshotted into the wraplet at registration, while the seal & factory flags are read live from metadata. Since decorators apply bottom-up, @precedence written above @Injectable ran after registration: it updated the metadata but the container kept comparing against the stale snapshot, silently ignoring the declared precedence. Precedence is now read from metadata during override comparison like the other flags, making all class-level decorators order-insensitive; the redundant wraplet field is dropped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
An unsealed, settled token could be silently overridden by an equal-or- higher-precedence registration, splitting the singleton: consumers that had already settled kept the old instance while later settles produced a new one, with no trace of why. The override still proceeds (that is the precedence design; @seal is the opt-out), but it now logs a warning naming the token and both classes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
Applied to a method parameter (possible via a type cast, or from plain JavaScript), @Inject wrote the token into the CONSTRUCTOR parameter metadata for that index, silently corrupting injection for the class. The decorator now throws a descriptive error when propertyKey is defined, i.e. whenever it does not decorate a constructor parameter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
Precedence.FINAL was 999, so any @precedence(1000) silently out-ranked a @Final() class. FINAL is now Number.MAX_SAFE_INTEGER: no user-supplied precedence can exceed it (an equal value still cannot displace a settled + sealed dependency, and ties intentionally follow last-registration semantics like any equal-precedence pair). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
The Injector is a static global, so consumers had no way to isolate unit tests: registrations and settled singletons leaked across test cases. reset() clears the container and the in-flight settlement stack; class metadata is untouched, so re-decorating or re-importing registers anew. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
dumpContainerInfo is a diagnostic aid, not an error condition; logging it via console.error polluted error monitoring and test output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
- InjectorUtil: injecitonToken -> injectionToken - README (both copies): the BusinessService example declared utliService but used this.utilService (broken as written), and the controller snippet used settle without importing it Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
The deprecated prepublish script has not run during "npm publish" since npm 7, so a publish could silently ship a stale dist and README. prepublishOnly is the supported hook for exactly this purpose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
One-shot runner for all sandbox scenarios (the pre-existing ones plus the regression tests added alongside each fix), reporting pass/fail per file. Scenarios are self-verifying via node:assert. Usage: cd test/sandbox && npm run sync && npm i && ./run-regression.sh Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc
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.
Fixes for the findings of a full code review of the library. One commit per fix; every behavioral fix ships with a self-verifying regression scenario in
test/sandbox(run via the newrun-regression.sh). All 18 scenarios (7 pre-existing + 11 new) pass, andtsc --noEmitis clean over the sandbox + synced lib.Correctness fixes (each was empirically reproduced first)
@Injecttokens — a constructor param typed as an interface/primitive/undecorated class crashed at import time even when@Inject('TOKEN')made it resolvable at settlement.settleLazy; the existing proxy & context are reused, so held proxies keep following the token.set/has/deleteProperty/ownKeys/getOwnPropertyDescriptor/getPrototypeOftraps, correct receiver, and instance-bound methods (with identity cache). Getters/methods using#privatestate no longer throw;Object.keysno longer leaksdependencyInstance; the__isUI5DependencyProxybrand now exists at runtime.@Injectable()occurrence (was: first per file) with path+index-derived tokens (was: basename, colliding across folders in the global symbol registry).Dependency cycle detected: A -> B -> Ainstead of a bare stack overflow;undefinedparamtypes (circular imports) get a descriptive error.settle(token, factory)no longer eagerly settles constructor deps whose instances the factory branch discarded.Semantics & diagnostics
@Precedenceabove@Injectablewas silently ignored (stale snapshot in the wraplet); all class-level decorators are now order-insensitive, matching the seal/factory flags.@Sealopts out) but now logs which token/classes are involved.@Injecton method params throws instead of corrupting constructor token metadata.Finalis actually final —Precedence.FINALwas 999, beatable by@Precedence(1000); nowNumber.MAX_SAFE_INTEGER.reset()exported for consumer test isolation.info(waserror); typo fixes (injecitonToken, README's brokenutliServiceexample, missingsettleimport);prepublish→prepublishOnly(the former hasn't run onnpm publishsince npm 7).Notes for the maintainer
package/distis committed but stale relative to these fixes — worth a rebuild (npm run build) before the next publish;prepublishOnlynow guarantees it going forward.package.json's"types": "dist/index.d.ts"still looks stale given the README wires consumers tosrc/viapaths— left untouched deliberately.eslintand the karma suite (nonode_modulesfor the library package in this environment). The sandbox suite + typecheck cover all changed code paths.🤖 Generated with Claude Code
https://claude.ai/code/session_01YCrV3GrDLmJHehiNuKYByc