chore: child to parent singleton bleed#14
Draft
brunozoric wants to merge 12 commits into
Draft
Conversation
Describes the singleton bleed-through bug where child container registrations pollute a parent's cached singleton, and the fix: cache singletons per-resolving-container instead of per-owning-container. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds singleton variants of all childContainer.test.ts scenarios to ensure cross-resolution behavior is bulletproof under singleton scoping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add child decorator chain application to the singleton scoping fix - Add ProductRegistry example with parent/child/grandchild showing before/after behavior with 6 products and 2 decorators - Add registry.test.ts to impacted test list (found by code review) - Add 6 additional test scenarios: decorator chain, resolution ordering, singleton dependency chains - Document as breaking semantic change requiring semver bump Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 tasks covering: failing tests, Container.ts fix, existing test updates, singleton cross-resolution tests, decorator chain tests, and documentation updates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cold review findings: falsy singleton cache bug, reflect-metadata
side-effect in types.ts, circular { multiple: true } stack overflow,
dead prettier scripts, undocumented composite/resolveAll behavior.
6 tasks covering: cache fix, reflect-metadata cleanup, prettier
removal, circular depth guard, builder fluency, AGENTS.md docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds LifetimeScope.Global design (owner-context resolution, walk-up cache lookup, downward sharing) and fixes 20 issues found across 4 review passes including incorrect assertions, missing breaking tests, and table inaccuracies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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
Fixes singleton bleed-through in parent/child container hierarchies and introduces a new
GlobalScopelifetime for shared resources.When a singleton registered in a parent container depends on
{ multiple: true }abstractions, child container registrations currently leak into the parent's cached singleton instance — and from there into every other container in the hierarchy.Root cause:
resolveRegistrationcaches singletons inthis.instances(the owning container) but resolves dependencies viaresolveFrom(the requesting container). A child resolving a parent-owned singleton collects child-scoped registrations, then writes the result into the parent's cache — polluting it for all subsequent resolvers.Fix: Two new lifetime scopes replace the broken singleton behavior:
resolveFrom.instancesinstead ofthis.instances, apply decorator chains in parent-to-child order. Each container gets its own instance reflecting its own dependency graph.What changes
Core (
src/Container.ts,src/types.ts)LifetimeScope.Global— new enum value insrc/types.tsRegistrationBuilder.inGlobalScope()— new builder methodresolveFrom.instancesinstead ofthis.instancesresolveFromup tothisand apply each intermediate container's decorators in parent-to-child order. Decorator constructor deps at every level are resolved fromresolveFrom.resolveFromthrough ancestors, stop at owning container. Return first cached instance found.this(owning container) for both dep resolution andapplyDecorators, passingthisasresolveFromso decorator constructor deps also come from the owner.if (existing)toif (existing !== undefined)so singletons resolving to0,false,"", ornullare correctly cachedThree lifetime scopes
Usage
Singleton scope behavior
Global scope behavior
Tests
singletons.test.ts,registry.test.ts: cross-containertoBeidentity assertions updated to assert behavioral equivalence within the same containersingletonBleed.test.ts— bleed-through prevention, child inheritance, sibling isolation, deep hierarchy, same-container identitysingletonCrossResolution.test.ts— singleton variants of allchildContainer.test.tsscenarios (override some/all/no deps, grandchild chain, great-grandchild 4-level hierarchy)singletonDecoratorChain.test.ts— child/grandchild decorator application, intermediate decorator deps from requesting container, parent isolation from child decorators{ multiple: true }registrations ignored, deep hierarchy sharingDocs
AGENTS.mdlifetime scopes section updated to reflect all three scopesBreaking change
This is a breaking semantic change. The singleton contract changes from "one shared instance across the hierarchy" to "one instance per container that resolves it."
child.resolve(X) === parent.resolve(X)(reference identity across containers) will break.inGlobalScope()Design docs (on this branch)
docs/2026-05-26-per-container-singleton-scoping-design.md— full design spec covering both singleton and global scope, with before/after code, decorator ordering, concrete examples, and alternatives considered (reviewed through 4 passes, 20 issues found and fixed)docs/superpowers/plans/2026-05-26-per-container-singleton-scoping.md— task-by-task implementation plandocs/2026-05-26-container-hardening-design.md— cold review findings (falsy cache bug, circular dep check bypass, perf issues)Test plan
toBe-identical instance0,false,"",null) are cached correctly{ multiple: true }registrations have no effect