Repository navigation
feat!: add inContainerScope and build singletons from the registering container - #19
Merged
Merged
Conversation
…container Adds a third lifetime, inContainerScope(): one instance per container that resolves it, built from that container's view and cached in it. Registered once in a parent, it gives every child container (one per request, for example) its own instance. Singletons are now built from the view of the container that holds the registration. Before, a parent-registered singleton took its dependencies and decorators from whichever child resolved it first, and that version was cached in the parent for everyone (the "bleed" pinned by #14). Decorators are now collected from the resolving container for transient, container-scoped, instance and factory registrations, so a child's decorators apply to registrations that live in a parent. Singletons keep the registering container's decorators. Tests are adapted from #14: its per-container expectations now use inContainerScope(), and new cases cover the shared singleton. Co-authored-by: Bruno Zorić <bruno@webiny.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Singletons are now built from the registering container's view, and child decorators reach parent registrations. Webiny packages depend on @webiny/di through ^1 ranges, so a minor release would reach existing projects without the fixes they need on their side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A singleton only takes decorators from the container that registered it and its parents, so a decorator registered in a child container for it could never apply. Resolving the singleton from that child now throws instead of skipping the decorator silently. A child that registers its own implementation can still decorate it. Suggested by Bruno Zorić. Co-authored-by: Bruno Zorić <bruno@webiny.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 9, 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.
Implements option B from #14: keep
inSingletonScope(), fix how singletons are built, and add a per-container lifetime as a new method.Changes
New
inContainerScope(). One instance per container that resolves it, built from that container's view and cached in it. Register it once in a parent, and every child container gets its own instance, thrown away with the child. In Webiny that's one instance per request, because every request runs in its own child container.Singletons are built from the registering container's view (the bleed fix). Before, a parent-registered singleton was cached in the parent but took its dependencies and decorators from whichever child resolved it first, and everyone got that child's version. Now its dependencies and decorators come from the container that holds the registration, so it's the same instance no matter who resolves it first.
Decorators follow the resolving container, for transient, container-scoped,
registerInstanceand factory registrations. A child's decorators now apply to registrations that live in a parent. Singletons are the exception: they keep the registering container's decorators, because a child's decorator would otherwise be baked into the instance everyone shares. This is F2 in the chore: child to parent singleton bleed #14 design doc.A child decorator on a parent's singleton throws (Bruno's suggestion). That decorator can never apply, so resolving the singleton from that child now fails with
Cannot apply decorator X to Y, instead of skipping the decorator silently. The check runs at resolve time, so a child that registers its own implementation of the abstraction can still decorate it.In
Container.ts,applyDecoratorsnow collects decorators from the resolving container.resolveRegistrationpicks the container to build from (the registering container for singletons, the resolving one otherwise) and the cache to use (none, the registering container's, or the resolving container's).Breaking changes (major: 2.0.0)
Singletons (change 2). A parent-registered singleton that depends on something registered only in a child container will no longer see it:
undefined;To migrate, register the dependency where the singleton is registered, or switch the singleton to
inContainerScope().Decorators (change 3). A child's decorators now apply to parent-registered transient, instance and factory registrations, where they didn't before.
Child decorators on singletons (change 4). Resolving a parent's singleton from a child that decorated it now throws, where it used to skip the decorator silently.
Why major and not minor: Webiny's packages depend on
@webiny/dithrough^1.1.0ranges. A minor release would reach existing projects on any fresh install or lockfile refresh, without the Webiny-side fix (Cognito, below). A major release means nothing upgrades by accident.Release order:
@webiny/di2.0.0 from this PR.@webiny/dirange to^2.0.0, in one PR.inContainerScope().Tests
The tests are adapted from Bruno's #14:
containerScope.test.tstakes his per-container scenarios (siblings, grandchildren, resolution order, dependency chains) and runs them againstinContainerScope(). New cases cover the shared singleton: no leak when a child resolves first, one instance for siblings and parent, a child's dependency override not reaching it, and a singleton registered in a child.decoratorChain.test.tstakes his decorator-chain scenarios under container scope, plus his transient, instance, factory andresolveAllcases. New cases show that a shared singleton ignores child decorators but keeps ancestor ones, and resolves decorator dependencies from the registering container.Results:
main. The 2 that pass cover decorators registered above the owner, which already worked.pnpm lintandpnpm buildare clean.Checked against Webiny
I put this build into a
webiny-jscheckout on currentnext, which has webiny/webiny-js#5933 merged:api-corepasses (267);api-headless-cmssuite passes (1,041 tests);api-acopasses (124 tests).I reran all of these after adding change 4, and nothing in Webiny trips the new error.
Known follow-up in Webiny:
CognitoIdentityProvideris a root singleton that gets its config from an extension, registered in the request container, as an optional dependency. With change 2, it would silently getundefined. No test covers it, so this needs a Webiny fix before or together with the upgrade.Not in this PR
From the design discussion, still open:
Supersedes the implementation part of #14. Its docs, specs and prior-art write-up stay there, and its tests were adapted here, with Bruno as co-author on the commit.
Workspace: wby-next11 · STORAGE PER REQ
🤖 Generated with Claude Code