@codefast/di 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +376 -0
  2. package/README.md +94 -17
  3. package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +7 -2
  4. package/dist/{ambient/active-container.js → ambient-container.js} +6 -1
  5. package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +27 -16
  6. package/dist/container/binding-chain.js +254 -0
  7. package/dist/container/container.d.ts +11 -2
  8. package/dist/container/container.js +293 -73
  9. package/dist/core/binding-builders.d.ts +114 -0
  10. package/dist/core/binding-declaration.d.ts +127 -0
  11. package/dist/core/binding-declaration.js +187 -0
  12. package/dist/core/binding.d.ts +62 -138
  13. package/dist/core/binding.js +54 -17
  14. package/dist/core/module.d.ts +8 -5
  15. package/dist/core/module.js +17 -3
  16. package/dist/core/registry.d.ts +17 -28
  17. package/dist/core/registry.js +92 -133
  18. package/dist/core/state-epoch.d.ts +18 -1
  19. package/dist/core/state-epoch.js +17 -0
  20. package/dist/core/tag.js +1 -1
  21. package/dist/decorators/inject.js +6 -5
  22. package/dist/decorators/injectable.js +4 -2
  23. package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +9 -3
  24. package/dist/decorators/metadata-record.d.ts +9 -0
  25. package/dist/decorators/metadata-record.js +20 -0
  26. package/dist/{errors/errors.d.ts → errors.d.ts} +84 -9
  27. package/dist/{errors/errors.js → errors.js} +101 -17
  28. package/dist/index.d.ts +14 -10
  29. package/dist/index.js +7 -6
  30. package/dist/injection/{resolve-options.js → dependency-slot.js} +6 -4
  31. package/dist/injection/descriptor.d.ts +1 -1
  32. package/dist/injection/descriptor.js +3 -7
  33. package/dist/introspection/dependency-graph.d.ts +8 -3
  34. package/dist/introspection/dependency-graph.js +48 -26
  35. package/dist/introspection/explanation.d.ts +101 -0
  36. package/dist/introspection/explanation.js +151 -0
  37. package/dist/introspection/graph-adapters/reactflow.js +6 -4
  38. package/dist/introspection/inspector.d.ts +9 -2
  39. package/dist/introspection/inspector.js +30 -28
  40. package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
  41. package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +59 -30
  42. package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
  43. package/dist/lifecycle/{scope-manager.js → scopes.js} +36 -29
  44. package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
  45. package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
  46. package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
  47. package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
  48. package/dist/metadata/types.js +1 -0
  49. package/dist/metadata/verify.d.ts +43 -0
  50. package/dist/metadata/verify.js +133 -0
  51. package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +5 -4
  52. package/dist/metadata/verifying-reader.js +53 -0
  53. package/dist/resolution/async-fan-out.d.ts +12 -0
  54. package/dist/resolution/async-fan-out.js +26 -0
  55. package/dist/resolution/cache/activation-need.d.ts +1 -2
  56. package/dist/resolution/cache/activation-need.js +16 -19
  57. package/dist/resolution/cache/class-introspector.d.ts +38 -36
  58. package/dist/resolution/cache/class-introspector.js +79 -159
  59. package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -9
  60. package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +70 -25
  61. package/dist/resolution/context.d.ts +16 -24
  62. package/dist/resolution/context.js +47 -56
  63. package/dist/resolution/path.d.ts +128 -0
  64. package/dist/resolution/path.js +167 -0
  65. package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +5 -4
  66. package/dist/resolution/plan/{plan-codegen.js → codegen.js} +58 -32
  67. package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
  68. package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +64 -24
  69. package/dist/resolution/resolver.d.ts +11 -12
  70. package/dist/resolution/resolver.js +573 -356
  71. package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
  72. package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
  73. package/dist/resolution/select/constraints.js +1 -1
  74. package/package.json +87 -67
  75. package/dist/container/binding-builders.js +0 -305
  76. package/dist/core/binding-scope.d.ts +0 -12
  77. package/dist/core/binding-scope.js +0 -12
  78. package/dist/metadata/verifying-metadata-reader.js +0 -31
  79. package/dist/resolution/path/resolution-path.d.ts +0 -93
  80. package/dist/resolution/path/resolution-path.js +0 -116
  81. /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
  82. /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
  83. /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
  84. /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
  85. /package/dist/{errors → introspection}/diagnostics.js +0 -0
  86. /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
  87. /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
  88. /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
@@ -3,16 +3,18 @@
3
3
  *
4
4
  * @remarks One object plays every role: each builder step, the record the registry stores, and the
5
5
  * binding the resolver reads. The binding fields are declared first and in a fixed order, so every
6
- * binding in the process shares one hidden class; the return type of each step is what pins the
7
- * chain's order, so no runtime check has to.
6
+ * binding in the process shares one hidden class; the return type of each step pins the chain's
7
+ * order, slot before strategy, so the binding registers once and is never re-slotted.
8
8
  */
9
- import type { AliasBindingBuilder, BindingBuilder, BindingSlot, BindToBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, TransientBindingBuilder } from "#core/binding";
9
+ import type { Binding, BindingSlot } from "#core/binding";
10
+ import type { AliasBindingBuilder, BindingBuilder, BindToBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, TransientBindingBuilder } from "#core/binding-builders";
11
+ import type { DeclaredBinding } from "#core/binding-declaration";
10
12
  import type { BindingRegistry } from "#core/registry";
11
13
  import type { BindingTag } from "#core/tag";
12
14
  import type { Token } from "#core/token";
13
15
  import type { ActivationHandler, BindingConstraint, BindingIdentifier, BindingKind, BindingScope, Constructor, DeactivationHandler, ResolutionContext, ResolutionFrame } from "#core/types";
14
16
  import type { InjectableDependency, InjectionDescriptor, ResolvedDependencyValue } from "#injection/descriptor";
15
- import type { ScopeManager } from "#lifecycle/scope-manager";
17
+ import type { ScopeManager } from "#lifecycle/scopes";
16
18
  /**
17
19
  * Where a chain registers, and on whose behalf.
18
20
  *
@@ -25,24 +27,31 @@ export interface BindingRegistration {
25
27
  readonly registry: BindingRegistry;
26
28
  readonly scope: ScopeManager;
27
29
  readonly moduleBindingIds: Array<BindingIdentifier> | undefined;
30
+ /** Runs for a binding this registration displaces, deactivating it on the spot. */
31
+ readonly deactivateDisplaced?: ((binding: Binding) => void) | undefined;
32
+ /** Notes a displaced binding so the container can still tear it down, when nothing deactivates it on the spot. */
33
+ readonly onDisplaced?: ((binding: Binding) => void) | undefined;
28
34
  }
29
35
  /**
30
36
  * The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
31
37
  *
32
38
  * @remarks The binding fields come first, in the order every binding shares, and the chain's own
33
- * bookkeeping follows as private fields; `to*()` fills the fields in place and hands this object to
34
- * the registry, so a plain bind is one allocation. Refinements write the registered object: a scope
35
- * or hook in place, a slot or predicate through the registry so its indexes follow.
39
+ * bookkeeping follows as a private field. The slot steps write the fields before anything registers;
40
+ * `to*()` fills the strategy in place and hands this object to the registry once, so a bind is one
41
+ * allocation and one add. A scope or hook afterwards writes the registered object in place.
36
42
  *
37
43
  * @since 0.5.0-canary.8
38
44
  */
39
- export declare class BindingChain<Value, Names extends string = string> implements AliasBindingBuilder<Names>, BindingBuilder<Value, Names>, BindToBuilder<Value, Names>, ConstantBindingBuilder<Value, Names>, ScopedBindingBuilder<Value>, SingletonBindingBuilder<Value>, SingletonLifecycleBuilder<Value>, TransientBindingBuilder<Value> {
45
+ export declare class BindingChain<Value, Names extends string = string> implements AliasBindingBuilder, BindingBuilder<Value>, BindToBuilder<Value, Names>, ConstantBindingBuilder<Value>, ScopedBindingBuilder<Value>, SingletonBindingBuilder<Value>, SingletonLifecycleBuilder<Value>, TransientBindingBuilder<Value> {
40
46
  #private;
41
47
  kind: BindingKind;
42
48
  readonly identifier: BindingIdentifier;
43
49
  inFlight: boolean;
44
50
  frame: ResolutionFrame | undefined;
51
+ rootContext: ResolutionContext | undefined;
52
+ activationStamp: number;
45
53
  instance: unknown;
54
+ scopedCacheKey: BindingIdentifier;
46
55
  readonly token: Token<Value, Names> | Constructor<Value>;
47
56
  slot: BindingSlot;
48
57
  predicate: BindingConstraint | undefined;
@@ -55,18 +64,20 @@ export declare class BindingChain<Value, Names extends string = string> implemen
55
64
  activationHook: ActivationHandler<Value> | undefined;
56
65
  deactivationHook: DeactivationHandler<Value> | undefined;
57
66
  constructor(token: Token<Value, Names> | Constructor<Value>, registration: BindingRegistration);
58
- to(type: Constructor<Value>): BindingBuilder<Value, Names>;
59
- toSelf(): BindingBuilder<Value, Names>;
60
- toConstantValue(value: Value): ConstantBindingBuilder<Value, Names>;
61
- toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value, Names>;
62
- toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value, Names>;
67
+ /** Registers a declared module's bindings in list order, each a chain of its own, handled as `to*()` handles one. */
68
+ static registerDeclared(declarations: ReadonlyArray<DeclaredBinding>, registration: BindingRegistration): void;
69
+ to(type: Constructor<Value>): BindingBuilder<Value>;
70
+ toSelf(): BindingBuilder<Value>;
71
+ toConstantValue(value: Value): ConstantBindingBuilder<Value>;
72
+ toDynamic(factory: (ctx: ResolutionContext) => Value): BindingBuilder<Value>;
73
+ toDynamicAsync(factory: (ctx: ResolutionContext) => Promise<Value>): BindingBuilder<Value>;
63
74
  toResolved<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
64
75
  [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
65
- }) => Value, deps: Deps): BindingBuilder<Value, Names>;
76
+ }) => Value, deps: Deps): BindingBuilder<Value>;
66
77
  toResolvedAsync<const Deps extends ReadonlyArray<InjectableDependency>>(factory: (...args: {
67
78
  [K in keyof Deps]: ResolvedDependencyValue<NoInfer<Deps>[K]>;
68
- }) => Promise<Value>, deps: Deps): BindingBuilder<Value, Names>;
69
- toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder<Names>;
79
+ }) => Promise<Value>, deps: Deps): BindingBuilder<Value>;
80
+ toAlias(target: Token<Value> | Constructor<Value>): AliasBindingBuilder;
70
81
  when(predicate: BindingConstraint): this;
71
82
  whenNamed(name: Names): this;
72
83
  whenTagged(criterion: BindingTag): this;
@@ -0,0 +1,254 @@
1
+ import { clearBindingFrame, NO_ACTIVATION_STAMP, DEFAULT_BINDING_SLOT, generateBindingId, NO_INSTANCE, withSlotCriterion, } from "#core/binding";
2
+ import { mergingConstraintRequirements } from "#core/constraint-requirement";
3
+ import { slotName } from "#core/tag";
4
+ import { tokenName } from "#core/token";
5
+ import { ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, SelfBindingRequiresClassError, } from "#errors";
6
+ import { normalizeToDescriptor } from "#injection/descriptor";
7
+ // ── BindingChain ─────────────────────────────────────────────────────────────────────────────────────────────────────
8
+ /**
9
+ * The one builder behind `bind()` and every `to*()` return type — and the binding it registers.
10
+ *
11
+ * @remarks The binding fields come first, in the order every binding shares, and the chain's own
12
+ * bookkeeping follows as a private field. The slot steps write the fields before anything registers;
13
+ * `to*()` fills the strategy in place and hands this object to the registry once, so a bind is one
14
+ * allocation and one add. A scope or hook afterwards writes the registered object in place.
15
+ *
16
+ * @since 0.5.0-canary.8
17
+ */
18
+ export class BindingChain {
19
+ // The binding. Every field, every kind, written in this order and nowhere else: one hidden class.
20
+ kind = "constant";
21
+ identifier = generateBindingId();
22
+ inFlight = false;
23
+ frame = undefined;
24
+ rootContext = undefined;
25
+ activationStamp = NO_ACTIVATION_STAMP;
26
+ instance = NO_INSTANCE;
27
+ scopedCacheKey = this.identifier;
28
+ token;
29
+ slot = DEFAULT_BINDING_SLOT;
30
+ predicate = undefined;
31
+ isMany = false;
32
+ scope = "singleton";
33
+ target = undefined;
34
+ factory = undefined;
35
+ deps = undefined;
36
+ value = undefined;
37
+ activationHook = undefined;
38
+ deactivationHook = undefined;
39
+ // The chain. Set by the first `to*()`, which is the only one a chain accepts.
40
+ #isRegistered = false;
41
+ #registration;
42
+ constructor(token, registration) {
43
+ this.token = token;
44
+ this.#registration = registration;
45
+ }
46
+ /** This object as the registry and the resolver see it: the value type erased once, here. */
47
+ get #binding() {
48
+ return this;
49
+ }
50
+ /** Loud failure for a scope, a hook or `id()` before `to*()`. */
51
+ #requireRegistered() {
52
+ if (!this.#isRegistered) {
53
+ throw new ChainNotRegisteredError(tokenName(this.token));
54
+ }
55
+ }
56
+ /** Loud failure for a slot step or a second `to*()` once registered, raised before a field is overwritten. */
57
+ #requireUnregistered() {
58
+ if (this.#isRegistered) {
59
+ throw new ChainAlreadyRegisteredError(tokenName(this.token));
60
+ }
61
+ }
62
+ #register(kind, scope) {
63
+ this.kind = kind;
64
+ this.scope = scope;
65
+ this.#isRegistered = true;
66
+ const registration = this.#registration;
67
+ const displaced = registration.registry.add(this.#binding);
68
+ if (displaced !== undefined) {
69
+ if (registration.deactivateDisplaced !== undefined) {
70
+ registration.deactivateDisplaced(displaced);
71
+ }
72
+ else {
73
+ registration.onDisplaced?.(displaced);
74
+ }
75
+ }
76
+ if (registration.moduleBindingIds !== undefined) {
77
+ registration.moduleBindingIds.push(this.identifier);
78
+ }
79
+ return this;
80
+ }
81
+ /** Registers a declared module's bindings in list order, each a chain of its own, handled as `to*()` handles one. */
82
+ static registerDeclared(declarations, registration) {
83
+ const { registry, moduleBindingIds, deactivateDisplaced, onDisplaced } = registration;
84
+ for (let index = 0; index < declarations.length; index += 1) {
85
+ const declaration = declarations[index];
86
+ const binding = BindingChain.#fromDeclaration(declaration, registration);
87
+ const displaced = registry.add(binding);
88
+ if (displaced !== undefined) {
89
+ if (deactivateDisplaced === undefined) {
90
+ onDisplaced?.(displaced);
91
+ }
92
+ else {
93
+ deactivateDisplaced(displaced);
94
+ }
95
+ }
96
+ moduleBindingIds?.push(binding.identifier);
97
+ }
98
+ }
99
+ /**
100
+ * A registered chain in the shape a declaration's steps would have left.
101
+ *
102
+ * @remarks Typed `unknown` like the declaration it copies, so the value type is never asserted here.
103
+ */
104
+ static #fromDeclaration(declaration, registration) {
105
+ const chain = new BindingChain(declaration.token, registration);
106
+ chain.kind = declaration.kind;
107
+ chain.slot = declaration.slot;
108
+ chain.predicate = declaration.predicate;
109
+ chain.isMany = declaration.isMany;
110
+ chain.scope = declaration.scope;
111
+ chain.target = declaration.target;
112
+ chain.factory = declaration.factory;
113
+ chain.deps = declaration.deps;
114
+ chain.value = declaration.value;
115
+ chain.activationHook = declaration.activationHook;
116
+ chain.deactivationHook = declaration.deactivationHook;
117
+ chain.#isRegistered = true;
118
+ return chain.#binding;
119
+ }
120
+ // ── Registration ───────────────────────────────────────────────────────────────────────────────────────────────────
121
+ to(type) {
122
+ this.#requireUnregistered();
123
+ this.target = type;
124
+ return this.#register("class", "transient");
125
+ }
126
+ toSelf() {
127
+ this.#requireUnregistered();
128
+ if (typeof this.token !== "function") {
129
+ throw new SelfBindingRequiresClassError(tokenName(this.token));
130
+ }
131
+ this.target = this.token;
132
+ return this.#register("class", "transient");
133
+ }
134
+ toConstantValue(value) {
135
+ this.#requireUnregistered();
136
+ this.value = value;
137
+ return this.#register("constant", "singleton");
138
+ }
139
+ toDynamic(factory) {
140
+ this.#requireUnregistered();
141
+ this.factory = factory;
142
+ return this.#register("dynamic", "transient");
143
+ }
144
+ toDynamicAsync(factory) {
145
+ this.#requireUnregistered();
146
+ this.factory = factory;
147
+ return this.#register("dynamic-async", "transient");
148
+ }
149
+ toResolved(factory, deps) {
150
+ this.#requireUnregistered();
151
+ this.factory = factory;
152
+ this.deps = deps.map((dependency) => normalizeToDescriptor(dependency));
153
+ return this.#register("resolved", "transient");
154
+ }
155
+ toResolvedAsync(factory, deps) {
156
+ this.#requireUnregistered();
157
+ this.factory = factory;
158
+ this.deps = deps.map((dependency) => normalizeToDescriptor(dependency));
159
+ return this.#register("resolved-async", "transient");
160
+ }
161
+ toAlias(target) {
162
+ this.#requireUnregistered();
163
+ this.target = target;
164
+ return this.#register("alias", "transient");
165
+ }
166
+ // ── Slot ───────────────────────────────────────────────────────────────────────────────────────────────────────────
167
+ // SPEC calls a candidate a binding that passes *all* of a chain's predicates, so a second `when()`
168
+ // narrows rather than replaces.
169
+ when(predicate) {
170
+ this.#requireUnregistered();
171
+ const previous = this.predicate;
172
+ // The composite carries both sides' requirements, so validate() still sees them.
173
+ this.predicate =
174
+ previous === undefined
175
+ ? predicate
176
+ : mergingConstraintRequirements((ctx) => previous(ctx) && predicate(ctx), previous, predicate);
177
+ return this;
178
+ }
179
+ whenNamed(name) {
180
+ return this.whenTagged(slotName.of(name));
181
+ }
182
+ whenTagged(criterion) {
183
+ this.#requireUnregistered();
184
+ if (this.isMany) {
185
+ throw new ManyBindingSlotError(tokenName(this.token));
186
+ }
187
+ this.slot = withSlotCriterion(this.slot, criterion);
188
+ return this;
189
+ }
190
+ many() {
191
+ this.#requireUnregistered();
192
+ if (this.slot.tags.length !== 0) {
193
+ throw new ManyBindingSlotError(tokenName(this.token));
194
+ }
195
+ this.isMany = true;
196
+ return this;
197
+ }
198
+ whenDefault() {
199
+ // The default slot is what a fresh chain already has; the check keeps it a slot step like the others.
200
+ this.#requireUnregistered();
201
+ return this;
202
+ }
203
+ // ── Scope and lifecycle ────────────────────────────────────────────────────────────────────────────────────────────
204
+ #withScope(scope) {
205
+ this.#requireRegistered();
206
+ // Nothing a cache, a frame or the version answers for depends on a scope that did not change.
207
+ if (this.scope === scope) {
208
+ return this;
209
+ }
210
+ // An instance cached under the old scope must not survive the change — a later flip back to that
211
+ // scope would resurrect it. A child's scoped cache is out of reach here, so leaving `scoped` retires
212
+ // the key every child filed its instance under.
213
+ const registration = this.#registration;
214
+ if (this.instance !== NO_INSTANCE) {
215
+ registration.scope.deleteSingleton(this.#binding);
216
+ }
217
+ if (this.scope === "scoped") {
218
+ registration.scope.deleteScoped(this.scopedCacheKey);
219
+ this.scopedCacheKey = generateBindingId();
220
+ }
221
+ this.scope = scope;
222
+ // The frame reports the scope, so a resolve before this call memoized the previous one.
223
+ if (this.frame !== undefined || this.rootContext !== undefined) {
224
+ clearBindingFrame(this.#binding);
225
+ }
226
+ registration.registry.touch();
227
+ return this;
228
+ }
229
+ singleton() {
230
+ return this.#withScope("singleton");
231
+ }
232
+ transient() {
233
+ return this.#withScope("transient");
234
+ }
235
+ scoped() {
236
+ return this.#withScope("scoped");
237
+ }
238
+ onActivation(fn) {
239
+ this.#requireRegistered();
240
+ this.activationHook = fn;
241
+ this.#registration.registry.touch();
242
+ return this;
243
+ }
244
+ onDeactivation(fn) {
245
+ this.#requireRegistered();
246
+ this.deactivationHook = fn;
247
+ this.#registration.registry.touch();
248
+ return this;
249
+ }
250
+ id() {
251
+ this.#requireRegistered();
252
+ return this.identifier;
253
+ }
254
+ }
@@ -1,11 +1,12 @@
1
- import type { BindToBuilder } from "#core/binding";
1
+ import type { BindToBuilder } from "#core/binding-builders";
2
2
  import type { AsyncModule, SyncModule } from "#core/module";
3
3
  import type { Token } from "#core/token";
4
4
  import type { ActivationHandler, BindingIdentifier, Constructor, DeactivationHandler, ResolveOptions } from "#core/types";
5
5
  import type { AutoRegisterRegistry } from "#decorators/injectable";
6
6
  import type { ContainerGraphJson, GraphOptions } from "#introspection/dependency-graph";
7
+ import type { ExplainOptions, ResolutionExplanation } from "#introspection/explanation";
7
8
  import type { BindingSnapshot, ContainerSnapshot } from "#introspection/inspector";
8
- import type { MetadataReader } from "#metadata/metadata-types";
9
+ import type { MetadataReader } from "#metadata/types";
9
10
  /**
10
11
  * The public surface: binding, resolving, modules, lifecycle hooks, child scopes, and disposal.
11
12
  *
@@ -42,6 +43,14 @@ export interface Container {
42
43
  hasOwn<Names extends string = string>(token: Token<unknown, Names> | Constructor, options?: NoInfer<ResolveOptions<Names>>): boolean;
43
44
  lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot>;
44
45
  inspect(): ContainerSnapshot;
46
+ /**
47
+ * Explains why a request selects the binding it does, reading each registry the way `resolve` does and deciding
48
+ * by the same rules, without instantiating anything.
49
+ *
50
+ * @remarks A `when()` predicate runs here as it would in `resolve`. `options.ancestors` names the resolutions the
51
+ * request is nested in, so a predicate that reads the parent sees the one it would.
52
+ */
53
+ explain<Names extends string = string>(token: Token<unknown, Names> | Constructor, options?: NoInfer<ExplainOptions<Names>>): ResolutionExplanation;
45
54
  generateDependencyGraph(options?: GraphOptions): ContainerGraphJson;
46
55
  }
47
56
  /**