feat: add Container.resolveImplementation - #17
Merged
Merged
Conversation
Resolves one implementation without registering it, for when the abstraction
cannot identify which one is wanted: several implementations share an
abstraction, `resolve()` cannot say which, and `resolveAll()` would construct all
of them.
const route = container.resolveImplementation(MatchedRouteImpl);
`resolveWithDependencies` already covered part of this, but it has two problems.
The caller must supply the dependencies, so it reads them out of `Metadata`
itself — plumbing that belongs in the container. And it is the only resolve path
that never calls `applyDecorators`, so anything resolved through it is silently
undecoratable. `resolveImplementation` takes the class alone, reads the
dependencies off its own metadata, and goes through `resolveRegistration` like
every other path, so decorators apply.
Nothing is registered and nothing is cached; the instance is transient, because
there is no registration to key a singleton on.
`register()` and `resolveImplementation()` need the same metadata reads and the
same composite/decorator/missing-abstraction checks, so those move into a shared
`describeImplementation`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The behaviour follows from the method not consulting registrations, but it is the one way resolve() and resolveImplementation() are NOT interchangeable, so it belongs in the doc comment and the changeset rather than being inferred. Adds a test pinning it, so a later change cannot quietly make the two agree without someone deciding to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
adrians5j
marked this pull request as draft
September 9, 2026 20:59
adrians5j
marked this pull request as ready for review
September 10, 2026 09:18
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.
Adds
Container.resolveImplementation(implementation)— resolve one implementation without registering it.Why
When several implementations share an abstraction, there is no way to resolve a specific one.
resolve()cannot say which you mean, andresolveAll()constructs all of them.That is a real shape, not a hypothetical. In
webiny-jsevery HTTP route is registered under oneHttpRouteHandlerabstraction. The router matches a request to a route and then wants to build only that one — building all of them would drag in each route's whole dependency graph, which for a static-asset request means constructing the GraphQL engine, every contextual schema and the AI provider before discovering it wanted none of them.resolveWithDependencieshalf-covers this, but has two problems:webiny-jsreads them out ofMetadataitself and casts toDependencies<T>— container plumbing leaking into a consumer.applyDecorators.resolveInternal,resolveRegistrationandresolveMultipleall decorate; this one does not. So anything resolved through it is silently undecoratable, which cost real time to diagnose downstream.What
Takes the class alone and reads its dependencies from its own metadata. Otherwise identical to
resolve(): dependencies come from the container, and decorators registered for the abstraction are applied, because it goes throughresolveRegistrationlike every other path.Nothing is registered and nothing is cached.
The one gotcha
It does not consult registrations, so a registered singleton is not shared:
That follows from what the method is for — you are asking for this class, not for whatever is registered under its abstraction — and there is no registration to key a singleton on. But it means the two are not interchangeable, so prefer
resolve()whenever the abstraction can identify what you want.It is also why this is a separate method rather than an overload of
resolve(). Discriminating anAbstractionfrom a constructor at runtime is trivial, but folding them together would makecontainer.resolve(X)honour a singleton or not depending on whetherXhappened to be the abstraction or the class — two call sites that read identically, one sharing an instance and one not. The distinct name is doing useful work: it tells you you have left the polymorphic path.A test pins this behaviour, so it cannot quietly change without someone deciding to.
register()andresolveImplementation()need the same metadata reads and the same composite / decorator / missing-abstraction checks, so those moved into a shared privatedescribeImplementation.register()'s behaviour is unchanged; the existing suite covers that.Tests
12 new, covering: resolves without registering, dependencies from metadata, picking one of several implementations sharing an abstraction, building only the requested one, decorators applied (own container and parent container), dependencies resolved from the container it was called on, transient instances, the singleton divergence above, and the three error cases (not an implementation, a decorator, a composite).
Full suite 74 passing, no type errors,
pnpm lintclean, builds.I also ran a throwaway test reproducing the
webiny-jsrouter case end to end — many routes on one abstraction, a decorator registered, resolve the matched one — and confirmed it builds only that route and applies the decorator. Not included here, since it belongs in that repo.Downstream
webiny-jscurrently works around the missing method by resolving in a throwaway child container whose only registration is the wanted route. That works, and its tests pin it, but it allocates a container per matched route per request and leans on child-before-parent lookup order rather than a stated contract. With this method it becomes one line.Note the latest published version is
1.0.2whilemainis 22 commits ahead, so a release is needed before anything downstream can use this.