@codefast/di 0.10.0 → 0.11.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 (81) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/README.md +71 -7
  3. package/dist/ambient/active-container.d.ts +13 -11
  4. package/dist/ambient/active-container.js +8 -6
  5. package/dist/container/binding-builders.d.ts +25 -7
  6. package/dist/container/binding-builders.js +110 -42
  7. package/dist/container/container.d.ts +8 -8
  8. package/dist/container/container.js +244 -67
  9. package/dist/core/binding-declaration.d.ts +120 -0
  10. package/dist/core/binding-declaration.js +186 -0
  11. package/dist/core/binding-scope.d.ts +2 -2
  12. package/dist/core/binding.d.ts +61 -9
  13. package/dist/core/binding.js +49 -2
  14. package/dist/core/constraint-requirement.d.ts +1 -1
  15. package/dist/core/module.d.ts +10 -7
  16. package/dist/core/module.js +17 -3
  17. package/dist/core/registry.d.ts +17 -13
  18. package/dist/core/registry.js +155 -59
  19. package/dist/core/state-epoch.d.ts +18 -1
  20. package/dist/core/state-epoch.js +17 -0
  21. package/dist/core/tag.js +1 -1
  22. package/dist/core/token.d.ts +1 -1
  23. package/dist/core/types.d.ts +5 -5
  24. package/dist/decorators/decorator-metadata.d.ts +9 -0
  25. package/dist/decorators/decorator-metadata.js +20 -0
  26. package/dist/decorators/inject.d.ts +3 -3
  27. package/dist/decorators/inject.js +7 -6
  28. package/dist/decorators/injectable.d.ts +2 -2
  29. package/dist/decorators/injectable.js +5 -3
  30. package/dist/decorators/lifecycle-decorators.js +9 -3
  31. package/dist/errors/errors.d.ts +85 -14
  32. package/dist/errors/errors.js +100 -20
  33. package/dist/index.d.ts +37 -35
  34. package/dist/index.js +20 -19
  35. package/dist/injection/descriptor.d.ts +3 -3
  36. package/dist/injection/descriptor.js +3 -7
  37. package/dist/injection/resolve-options.d.ts +3 -3
  38. package/dist/injection/resolve-options.js +7 -5
  39. package/dist/introspection/dependency-graph.d.ts +10 -5
  40. package/dist/introspection/dependency-graph.js +51 -28
  41. package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
  42. package/dist/introspection/graph-adapters/dot.d.ts +1 -1
  43. package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
  44. package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
  45. package/dist/introspection/graph-adapters/reactflow.js +6 -4
  46. package/dist/introspection/inspector.d.ts +4 -4
  47. package/dist/introspection/inspector.js +9 -12
  48. package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
  49. package/dist/lifecycle/lifecycle-manager.js +18 -6
  50. package/dist/lifecycle/scope-manager.d.ts +2 -2
  51. package/dist/lifecycle/scope-manager.js +31 -13
  52. package/dist/metadata/metadata-reader-token.d.ts +2 -2
  53. package/dist/metadata/metadata-reader-token.js +1 -1
  54. package/dist/metadata/metadata-types.d.ts +3 -3
  55. package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
  56. package/dist/metadata/symbol-metadata-reader.js +1 -1
  57. package/dist/metadata/verifying-metadata-reader.d.ts +5 -4
  58. package/dist/metadata/verifying-metadata-reader.js +30 -8
  59. package/dist/resolution/async-fan-out.d.ts +12 -0
  60. package/dist/resolution/async-fan-out.js +26 -0
  61. package/dist/resolution/cache/activation-need.d.ts +4 -5
  62. package/dist/resolution/cache/activation-need.js +11 -18
  63. package/dist/resolution/cache/binding-lookup-cache.d.ts +5 -12
  64. package/dist/resolution/cache/binding-lookup-cache.js +32 -19
  65. package/dist/resolution/cache/class-introspector.d.ts +16 -5
  66. package/dist/resolution/cache/class-introspector.js +73 -56
  67. package/dist/resolution/context.d.ts +17 -25
  68. package/dist/resolution/context.js +47 -56
  69. package/dist/resolution/path/resolution-path.d.ts +48 -13
  70. package/dist/resolution/path/resolution-path.js +90 -39
  71. package/dist/resolution/plan/instantiation-plan.d.ts +5 -5
  72. package/dist/resolution/plan/instantiation-plan.js +66 -26
  73. package/dist/resolution/plan/plan-codegen.d.ts +10 -7
  74. package/dist/resolution/plan/plan-codegen.js +62 -34
  75. package/dist/resolution/resolver.d.ts +16 -17
  76. package/dist/resolution/resolver.js +316 -273
  77. package/dist/resolution/select/binding-select.d.ts +6 -5
  78. package/dist/resolution/select/binding-select.js +5 -4
  79. package/dist/resolution/select/constraints.d.ts +3 -3
  80. package/dist/resolution/select/constraints.js +4 -4
  81. package/package.json +14 -2
package/dist/index.d.ts CHANGED
@@ -1,35 +1,37 @@
1
- export type { ActivationHandler, BindingConstraint, BindingIdentifier, BindingKind, BindingScope, BindingTag, ConstraintContext, Constructor, DependencyKey, DeactivationHandler, ResolutionFrame, ResolveOptions, ResolutionContext, TokenValue, } from "#/core/types";
2
- export { token, tokenName } from "#/core/token";
3
- export type { SlotNamesOf, Token } from "#/core/token";
4
- export { coversTagKeys, NO_TAG_KEYS, slotName, tag, tagKeyMaskOf } from "#/core/tag";
5
- export type { TagKey, TagKeyMask } from "#/core/tag";
6
- export type { AliasBindingBuilder, BindToBuilder, BindingBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, SlotConstrainedBuilder, TransientBindingBuilder, } from "#/core/binding";
7
- export { Container } from "#/container/container";
8
- export type { Container as ContainerInterface, ContainerOptions, ContainerStatic } from "#/container/container";
9
- export { getActiveContainer, runWithContainer } from "#/ambient/active-container";
10
- export { bindingSlotToResolveOptions, injectionSlotToResolveOptions, resolveOptionsForSlot, } from "#/injection/resolve-options";
11
- export type { DependencySlot } from "#/injection/resolve-options";
12
- export type { BindingSnapshot, ContainerSnapshot } from "#/introspection/inspector";
13
- export type { ContainerGraphJson, GraphEdge, GraphNode, GraphOptions } from "#/introspection/dependency-graph";
14
- export { AsyncModule, isSyncModule, Module, SyncModule } from "#/core/module";
15
- export type { AsyncModuleBuilder, ModuleBuilder } from "#/core/module";
16
- export { inject } from "#/decorators/inject";
17
- export { injectAll, isInjectionDescriptor, optional } from "#/injection/descriptor";
18
- export type { InjectionDescriptor, InjectOptions } from "#/injection/descriptor";
19
- export { injectable } from "#/decorators/injectable";
20
- export type { InjectableDependency, InjectableOptions } from "#/decorators/injectable";
21
- export { postConstruct, preDestroy } from "#/decorators/lifecycle-decorators";
22
- export { createAutoRegisterRegistry } from "#/decorators/injectable";
23
- export type { AutoRegisterRegistry } from "#/decorators/injectable";
24
- export { MetadataReaderToken } from "#/metadata/metadata-reader-token";
25
- export type { ConstructorMetadata, LifecycleMetadata, MetadataReader, MutableLifecycleMetadata, ParamMetadata, } from "#/metadata/metadata-types";
26
- export { defaultMetadataReader, SymbolMetadataReader } from "#/metadata/symbol-metadata-reader";
27
- export { whenAnyAncestorIs, whenAnyAncestorNamed, whenAnyAncestorTagged, whenAnyAncestorTaggedAll, whenNoAncestorIs, whenNoParentIs, whenParentIs, whenParentNamed, whenParentTagged, whenParentTaggedAll, } from "#/resolution/select/constraints";
28
- export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidMetadataError, MissingContainerContextError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#/errors/errors";
29
- export type { ScopeViolationDetails } from "#/errors/errors";
30
- export { toDotGraph } from "#/introspection/graph-adapters/dot";
31
- export { toCytoscapeGraph } from "#/introspection/graph-adapters/cytoscape";
32
- export type { CytoscapeEdge, CytoscapeElements, CytoscapeNode } from "#/introspection/graph-adapters/cytoscape";
33
- export { toReactFlowGraph } from "#/introspection/graph-adapters/reactflow";
34
- export type { ReactFlowEdge, ReactFlowGraph, ReactFlowNode } from "#/introspection/graph-adapters/reactflow";
35
- export { toMermaidGraph } from "#/introspection/graph-adapters/mermaid";
1
+ export type { ActivationHandler, BindingConstraint, BindingIdentifier, BindingKind, BindingScope, BindingTag, ConstraintContext, Constructor, DependencyKey, DeactivationHandler, ResolutionFrame, ResolveOptions, ResolutionContext, TokenValue, } from "#core/types";
2
+ export { token, tokenName } from "#core/token";
3
+ export type { SlotNamesOf, Token } from "#core/token";
4
+ export { coversTagKeys, NO_TAG_KEYS, slotName, tag, tagKeyMaskOf } from "#core/tag";
5
+ export type { TagKey, TagKeyMask } from "#core/tag";
6
+ export type { AliasBindingBuilder, BindToBuilder, BindingBuilder, ConstantBindingBuilder, ScopedBindingBuilder, SingletonBindingBuilder, SingletonLifecycleBuilder, SlotConstrainedBuilder, TransientBindingBuilder, } from "#core/binding";
7
+ export { Container } from "#container/container";
8
+ export type { Container as ContainerInterface, ContainerOptions, ContainerStatic } from "#container/container";
9
+ export { getActiveContainer, runWithContainer } from "#ambient/active-container";
10
+ export { bindingSlotToResolveOptions, injectionSlotToResolveOptions, resolveOptionsForSlot, } from "#injection/resolve-options";
11
+ export type { DependencySlot } from "#injection/resolve-options";
12
+ export type { BindingSnapshot, ContainerSnapshot } from "#introspection/inspector";
13
+ export type { ContainerGraphJson, GraphEdge, GraphNode, GraphOptions } from "#introspection/dependency-graph";
14
+ export { AsyncModule, isSyncModule, Module, SyncModule } from "#core/module";
15
+ export type { AsyncModuleBuilder, ModuleBuilder } from "#core/module";
16
+ export { binding } from "#core/binding-declaration";
17
+ export type { BindingDeclaration, BindingDefinition } from "#core/binding-declaration";
18
+ export { inject } from "#decorators/inject";
19
+ export { injectAll, isInjectionDescriptor, optional } from "#injection/descriptor";
20
+ export type { InjectionDescriptor, InjectOptions } from "#injection/descriptor";
21
+ export { injectable } from "#decorators/injectable";
22
+ export type { InjectableDependency, InjectableOptions } from "#decorators/injectable";
23
+ export { postConstruct, preDestroy } from "#decorators/lifecycle-decorators";
24
+ export { createAutoRegisterRegistry } from "#decorators/injectable";
25
+ export type { AutoRegisterRegistry } from "#decorators/injectable";
26
+ export { MetadataReaderToken } from "#metadata/metadata-reader-token";
27
+ export type { ConstructorMetadata, LifecycleMetadata, MetadataReader, MutableLifecycleMetadata, ParamMetadata, } from "#metadata/metadata-types";
28
+ export { defaultMetadataReader, SymbolMetadataReader } from "#metadata/symbol-metadata-reader";
29
+ export { whenAnyAncestorIs, whenAnyAncestorNamed, whenAnyAncestorTagged, whenAnyAncestorTaggedAll, whenNoAncestorIs, whenNoParentIs, whenParentIs, whenParentNamed, whenParentTagged, whenParentTaggedAll, } from "#resolution/select/constraints";
30
+ export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidBindingDeclarationError, InvalidMetadataError, MissingContainerContextError, MissingDecoratorMetadataError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SymbolKeyedLifecycleError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
31
+ export type { ScopeViolationDetails } from "#errors/errors";
32
+ export { toDotGraph } from "#introspection/graph-adapters/dot";
33
+ export { toCytoscapeGraph } from "#introspection/graph-adapters/cytoscape";
34
+ export type { CytoscapeEdge, CytoscapeElements, CytoscapeNode } from "#introspection/graph-adapters/cytoscape";
35
+ export { toReactFlowGraph } from "#introspection/graph-adapters/reactflow";
36
+ export type { ReactFlowEdge, ReactFlowGraph, ReactFlowNode } from "#introspection/graph-adapters/reactflow";
37
+ export { toMermaidGraph } from "#introspection/graph-adapters/mermaid";
package/dist/index.js CHANGED
@@ -1,33 +1,34 @@
1
1
  // Token
2
- export { token, tokenName } from "#/core/token";
2
+ export { token, tokenName } from "#core/token";
3
3
  // Tag — the interned slot criteria a `whenTagged` and a resolve both take
4
- export { coversTagKeys, NO_TAG_KEYS, slotName, tag, tagKeyMaskOf } from "#/core/tag";
4
+ export { coversTagKeys, NO_TAG_KEYS, slotName, tag, tagKeyMaskOf } from "#core/tag";
5
5
  // Container
6
- export { Container } from "#/container/container";
6
+ export { Container } from "#container/container";
7
7
  // Ambient container — the context an `@inject` accessor initializer resolves from. `resolution/context`
8
8
  // stays internal: it hands out resolver callbacks, not public values.
9
- export { getActiveContainer, runWithContainer } from "#/ambient/active-container";
9
+ export { getActiveContainer, runWithContainer } from "#ambient/active-container";
10
10
  // `effectiveBindingScope` is deliberately absent: it reads a `Binding`, which is internal, and no
11
11
  // public API hands one out. `BindingSnapshot.scope` and `GraphNode.scope` are the public answers.
12
- export { bindingSlotToResolveOptions, injectionSlotToResolveOptions, resolveOptionsForSlot, } from "#/injection/resolve-options";
12
+ export { bindingSlotToResolveOptions, injectionSlotToResolveOptions, resolveOptionsForSlot, } from "#injection/resolve-options";
13
13
  // Module
14
- export { AsyncModule, isSyncModule, Module, SyncModule } from "#/core/module";
14
+ export { AsyncModule, isSyncModule, Module, SyncModule } from "#core/module";
15
+ export { binding } from "#core/binding-declaration";
15
16
  // Decorators
16
- export { inject } from "#/decorators/inject";
17
- export { injectAll, isInjectionDescriptor, optional } from "#/injection/descriptor";
18
- export { injectable } from "#/decorators/injectable";
19
- export { postConstruct, preDestroy } from "#/decorators/lifecycle-decorators";
17
+ export { inject } from "#decorators/inject";
18
+ export { injectAll, isInjectionDescriptor, optional } from "#injection/descriptor";
19
+ export { injectable } from "#decorators/injectable";
20
+ export { postConstruct, preDestroy } from "#decorators/lifecycle-decorators";
20
21
  // Auto-register
21
- export { createAutoRegisterRegistry } from "#/decorators/injectable";
22
+ export { createAutoRegisterRegistry } from "#decorators/injectable";
22
23
  // MetadataReader — everything a consumer needs to write one and pass it to Container.create()
23
- export { MetadataReaderToken } from "#/metadata/metadata-reader-token";
24
- export { defaultMetadataReader, SymbolMetadataReader } from "#/metadata/symbol-metadata-reader";
24
+ export { MetadataReaderToken } from "#metadata/metadata-reader-token";
25
+ export { defaultMetadataReader, SymbolMetadataReader } from "#metadata/symbol-metadata-reader";
25
26
  // Constraints — contextual injection predicates for .when()
26
- export { whenAnyAncestorIs, whenAnyAncestorNamed, whenAnyAncestorTagged, whenAnyAncestorTaggedAll, whenNoAncestorIs, whenNoParentIs, whenParentIs, whenParentNamed, whenParentTagged, whenParentTaggedAll, } from "#/resolution/select/constraints";
27
+ export { whenAnyAncestorIs, whenAnyAncestorNamed, whenAnyAncestorTagged, whenAnyAncestorTaggedAll, whenNoAncestorIs, whenNoParentIs, whenParentIs, whenParentNamed, whenParentTagged, whenParentTaggedAll, } from "#resolution/select/constraints";
27
28
  // Errors
28
- export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidMetadataError, MissingContainerContextError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#/errors/errors";
29
+ export { AmbiguousBindingError, AsyncActivationError, AsyncDeactivationError, AsyncModuleLoadError, AsyncResolutionError, ChainAlreadyRegisteredError, ChainNotRegisteredError, ManyBindingSlotError, CircularDependencyError, DiError, DisposedContainerError, InternalError, InvalidBindingDeclarationError, InvalidMetadataError, MissingContainerContextError, MissingDecoratorMetadataError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, RebindUnboundTokenError, ScopeViolationError, SelfBindingRequiresClassError, StaticMemberDecoratorError, SymbolKeyedLifecycleError, SyncDisposalNotSupportedError, EmptyTagCriteriaError, TokenNotBoundError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#errors/errors";
29
30
  // Graph adapters — render `generateDependencyGraph()` output for common viewers
30
- export { toDotGraph } from "#/introspection/graph-adapters/dot";
31
- export { toCytoscapeGraph } from "#/introspection/graph-adapters/cytoscape";
32
- export { toReactFlowGraph } from "#/introspection/graph-adapters/reactflow";
33
- export { toMermaidGraph } from "#/introspection/graph-adapters/mermaid";
31
+ export { toDotGraph } from "#introspection/graph-adapters/dot";
32
+ export { toCytoscapeGraph } from "#introspection/graph-adapters/cytoscape";
33
+ export { toReactFlowGraph } from "#introspection/graph-adapters/reactflow";
34
+ export { toMermaidGraph } from "#introspection/graph-adapters/mermaid";
@@ -1,7 +1,7 @@
1
1
  /** The one shape every declared dependency is normalised to, whatever channel declared it. */
2
- import type { Token } from "#/core/token";
3
- import type { BindingTag, Constructor, TokenValue } from "#/core/types";
4
- import type { DependencySlot } from "#/injection/resolve-options";
2
+ import type { Token } from "#core/token";
3
+ import type { BindingTag, Constructor, TokenValue } from "#core/types";
4
+ import type { DependencySlot } from "#injection/resolve-options";
5
5
  /**
6
6
  * Slot-selection options — a name and tags — a declared dependency narrows its binding with.
7
7
  *
@@ -4,12 +4,8 @@
4
4
  * @since 0.3.16-canary.0
5
5
  */
6
6
  export function isInjectionDescriptor(value) {
7
- if (value === null || value === undefined) {
8
- return false;
9
- }
10
- const type = typeof value;
11
- // inject() returns a function (dual-role), so must check both object and function
12
- if (type !== "object" && type !== "function") {
7
+ // inject() returns a function (dual-role), so both an object and a function qualify.
8
+ if ((typeof value !== "object" && typeof value !== "function") || value === null) {
13
9
  return false;
14
10
  }
15
11
  return ("token" in value &&
@@ -106,7 +102,7 @@ export function buildInjectionDescriptor(token, options) {
106
102
  */
107
103
  export function optional(token, options) {
108
104
  return withOptions({
109
- token: token,
105
+ token,
110
106
  optional: true,
111
107
  multi: false,
112
108
  }, options);
@@ -1,6 +1,6 @@
1
- import type { BindingTag } from "#/core/tag";
2
- import type { Token } from "#/core/token";
3
- import type { Constructor, ResolveOptions } from "#/core/types";
1
+ import type { BindingTag } from "#core/tag";
2
+ import type { Token } from "#core/token";
3
+ import type { Constructor, ResolveOptions } from "#core/types";
4
4
  /**
5
5
  * What one resolvable dependency declares.
6
6
  *
@@ -1,4 +1,4 @@
1
- import { slotName, slotNameCriterionOf } from "#/core/tag";
1
+ import { slotName, slotNameCriterionOf } from "#core/tag";
2
2
  /**
3
3
  * The lone criterion of a request that carries exactly one, whatever its spelling — the shape the
4
4
  * registry has a direct index for.
@@ -49,15 +49,17 @@ function loneNameCriterionOf(options) {
49
49
  }
50
50
  /** Shared core: build a ResolveOptions from already-normalised name + tags. */
51
51
  function buildOptions(name, tags) {
52
- if (name === undefined && tags === undefined) {
52
+ // An empty criterion list selects nothing, so it states no criterion at all.
53
+ const criteria = tags !== undefined && tags.length > 0 ? tags : undefined;
54
+ if (name === undefined && criteria === undefined) {
53
55
  return undefined;
54
56
  }
55
57
  const options = {};
56
58
  if (name !== undefined) {
57
59
  options.name = name;
58
60
  }
59
- if (tags !== undefined) {
60
- options.tags = tags;
61
+ if (criteria !== undefined) {
62
+ options.tags = criteria;
61
63
  }
62
64
  return options;
63
65
  }
@@ -85,7 +87,7 @@ const MEMOIZED_SINGLE_CRITERION = Symbol("di:single-criterion");
85
87
  */
86
88
  export function resolveOptionsForSlot(injectionSlot) {
87
89
  const { name, tags } = injectionSlot;
88
- if (name === undefined && tags === undefined) {
90
+ if (name === undefined && (tags === undefined || tags.length === 0)) {
89
91
  return undefined;
90
92
  }
91
93
  const slot = injectionSlot;
@@ -1,6 +1,6 @@
1
- import type { BindingRegistry } from "#/core/registry";
2
- import type { BindingKind, BindingScope } from "#/core/types";
3
- import type { MetadataReader } from "#/metadata/metadata-types";
1
+ import type { BindingRegistry } from "#core/registry";
2
+ import type { BindingKind, BindingScope } from "#core/types";
3
+ import type { MetadataReader } from "#metadata/metadata-types";
4
4
  /**
5
5
  * @remarks `kind`/`scope` are `"unbound"` for the placeholder node an optional, currently
6
6
  * unsatisfied dependency points at.
@@ -49,8 +49,13 @@ export interface GraphOptions {
49
49
  readonly includeParent?: boolean | undefined;
50
50
  }
51
51
  /**
52
- * Builds the JSON dependency graph of a registry's bindings, optionally including the parent's.
52
+ * Builds the JSON dependency graph of a registry's bindings, optionally including its ancestors'.
53
+ *
54
+ * @param registry - The registry whose bindings the graph is for.
55
+ * @param metadataReader - The reader class dependencies are read through.
56
+ * @param options - Whether the ancestors' bindings join the graph.
57
+ * @param ancestorRegistries - The ancestor containers' registries, nearest first.
53
58
  *
54
59
  * @since 0.3.16-canary.0
55
60
  */
56
- export declare function buildDependencyGraph(registry: BindingRegistry, metadataReader: MetadataReader, options: GraphOptions | undefined, parentRegistry?: BindingRegistry): ContainerGraphJson;
61
+ export declare function buildDependencyGraph(registry: BindingRegistry, metadataReader: MetadataReader, options: GraphOptions | undefined, ancestorRegistries?: ReadonlyArray<BindingRegistry>): ContainerGraphJson;
@@ -1,8 +1,9 @@
1
- import { effectiveBindingScope } from "#/core/binding-scope";
2
- import { slotName } from "#/core/tag";
3
- import { tokenName } from "#/core/token";
4
- import { bindingSlotToResolveOptions } from "#/injection/resolve-options";
5
- import { matchesSlot } from "#/resolution/select/binding-select";
1
+ import { stringifyTagValue } from "#core/binding";
2
+ import { effectiveBindingScope } from "#core/binding-scope";
3
+ import { slotName } from "#core/tag";
4
+ import { tokenName } from "#core/token";
5
+ import { bindingSlotToResolveOptions } from "#injection/resolve-options";
6
+ import { matchesSlot } from "#resolution/select/binding-select";
6
7
  // ── Builder ──────────────────────────────────────────────────────────────────────────────────────────────────────────
7
8
  // Tokens are compared by object identity, and a name is free to repeat, so the graph mints its
8
9
  // own per-process key. Weakly held: a discarded token takes its key with it.
@@ -41,18 +42,31 @@ function edgeLabel(ref, index) {
41
42
  const criterion = name !== undefined
42
43
  ? `name:${name}`
43
44
  : firstPlainTag !== undefined
44
- ? `tag:${firstPlainTag.key.name}=${String(firstPlainTag.value)}`
45
+ ? `tag:${firstPlainTag.key.name}=${stringifyTagValue(firstPlainTag.value)}`
45
46
  : `[${index}]`;
46
47
  return ref.optional ? `${criterion} optional` : criterion;
47
48
  }
48
- function bindingLookup(sourceRegistry, fallbackRegistry) {
49
- return (token) => {
50
- const own = sourceRegistry.getAll(token);
51
- if (own.length > 0 || fallbackRegistry === undefined) {
52
- return own;
49
+ /**
50
+ * The bindings a dependency reaches, found the way resolution finds them.
51
+ *
52
+ * @remarks A single dependency stops at the nearest registry holding a binding its slot matches, and
53
+ * a collection gathers every registry's matches — `resolve` and `resolveAll` respectively.
54
+ */
55
+ function dependencyTargets(chain, ref) {
56
+ if (ref.multi) {
57
+ const gathered = [];
58
+ for (const registry of chain) {
59
+ gathered.push(...matchingTargets(registry.getAll(ref.token), ref));
60
+ }
61
+ return gathered;
62
+ }
63
+ for (const registry of chain) {
64
+ const targets = matchingTargets(registry.getAll(ref.token), ref);
65
+ if (targets.length > 0) {
66
+ return targets;
53
67
  }
54
- return fallbackRegistry.getAll(token);
55
- };
68
+ }
69
+ return [];
56
70
  }
57
71
  /** The placeholder node an optional-but-unbound dependency points at, minted once per token. */
58
72
  function unboundNodeIdFor(accumulator, dependency) {
@@ -73,8 +87,8 @@ function unboundNodeIdFor(accumulator, dependency) {
73
87
  });
74
88
  return id;
75
89
  }
76
- function addDependencyEdges(accumulator, from, ref, index, lookup) {
77
- const targets = matchingTargets(lookup(ref.token), ref);
90
+ function addDependencyEdges(accumulator, from, ref, index, chain) {
91
+ const targets = dependencyTargets(chain, ref);
78
92
  const label = edgeLabel(ref, index);
79
93
  if (targets.length === 0) {
80
94
  // A required-but-unbound dependency is validate()'s story, not the graph's.
@@ -108,25 +122,25 @@ function addDependencyEdges(accumulator, from, ref, index, lookup) {
108
122
  }
109
123
  }
110
124
  /** What one binding declares up front — a class's params, a factory's descriptors, an alias's target. */
111
- function addBindingEdges(accumulator, binding, metadataReader, lookup) {
125
+ function addBindingEdges(accumulator, binding, metadataReader, chain) {
112
126
  if (binding.kind === "class") {
113
127
  const meta = metadataReader.getConstructorMetadata(binding.target);
114
128
  if (meta !== undefined) {
115
129
  for (const [index, param] of meta.params.entries()) {
116
- addDependencyEdges(accumulator, String(binding.identifier), param, index, lookup);
130
+ addDependencyEdges(accumulator, String(binding.identifier), param, index, chain);
117
131
  }
118
132
  }
119
133
  return;
120
134
  }
121
135
  if (binding.kind === "resolved" || binding.kind === "resolved-async") {
122
136
  for (const [index, dependency] of binding.deps.entries()) {
123
- addDependencyEdges(accumulator, String(binding.identifier), dependency, index, lookup);
137
+ addDependencyEdges(accumulator, String(binding.identifier), dependency, index, chain);
124
138
  }
125
139
  return;
126
140
  }
127
141
  if (binding.kind === "alias") {
128
142
  const aliasRef = { token: binding.target, optional: false, multi: false };
129
- for (const target of matchingTargets(lookup(binding.target), aliasRef)) {
143
+ for (const target of dependencyTargets(chain, aliasRef)) {
130
144
  accumulator.edges.push({
131
145
  from: String(binding.identifier),
132
146
  to: String(target.identifier),
@@ -136,9 +150,9 @@ function addBindingEdges(accumulator, binding, metadataReader, lookup) {
136
150
  }
137
151
  }
138
152
  }
139
- function addRegistryBindings(accumulator, sourceRegistry, metadataReader, fromParent, fallbackRegistry) {
140
- const lookup = bindingLookup(sourceRegistry, fallbackRegistry);
141
- for (const binding of sourceRegistry.allBindings()) {
153
+ /** Adds the nodes of `chain[0]`, the registry the chain starts at, and the edges each of its bindings declares. */
154
+ function addRegistryBindings(accumulator, chain, metadataReader, fromParent) {
155
+ for (const binding of chain[0].allBindings()) {
142
156
  accumulator.nodes.push({
143
157
  id: String(binding.identifier),
144
158
  tokenName: tokenName(binding.token),
@@ -147,20 +161,29 @@ function addRegistryBindings(accumulator, sourceRegistry, metadataReader, fromPa
147
161
  scope: effectiveBindingScope(binding),
148
162
  fromParent,
149
163
  });
150
- addBindingEdges(accumulator, binding, metadataReader, lookup);
164
+ addBindingEdges(accumulator, binding, metadataReader, chain);
151
165
  }
152
166
  }
153
167
  /**
154
- * Builds the JSON dependency graph of a registry's bindings, optionally including the parent's.
168
+ * Builds the JSON dependency graph of a registry's bindings, optionally including its ancestors'.
169
+ *
170
+ * @param registry - The registry whose bindings the graph is for.
171
+ * @param metadataReader - The reader class dependencies are read through.
172
+ * @param options - Whether the ancestors' bindings join the graph.
173
+ * @param ancestorRegistries - The ancestor containers' registries, nearest first.
155
174
  *
156
175
  * @since 0.3.16-canary.0
157
176
  */
158
- export function buildDependencyGraph(registry, metadataReader, options, parentRegistry) {
177
+ export function buildDependencyGraph(registry, metadataReader, options, ancestorRegistries = []) {
159
178
  const accumulator = { nodes: [], edges: [], unboundNodeIds: new Map() };
160
179
  const includesParent = options?.includeParent === true;
161
- addRegistryBindings(accumulator, registry, metadataReader, false, includesParent ? parentRegistry : undefined);
162
- if (includesParent && parentRegistry !== undefined) {
163
- addRegistryBindings(accumulator, parentRegistry, metadataReader, true);
180
+ if (!includesParent) {
181
+ addRegistryBindings(accumulator, [registry], metadataReader, false);
182
+ return { nodes: accumulator.nodes, edges: accumulator.edges, includesParent };
183
+ }
184
+ const chain = [registry, ...ancestorRegistries];
185
+ for (let depth = 0; depth < chain.length; depth += 1) {
186
+ addRegistryBindings(accumulator, chain.slice(depth), metadataReader, depth > 0);
164
187
  }
165
188
  return { nodes: accumulator.nodes, edges: accumulator.edges, includesParent };
166
189
  }
@@ -1,4 +1,4 @@
1
- import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
1
+ import type { ContainerGraphJson, GraphNode } from "#introspection/dependency-graph";
2
2
  /**
3
3
  * A dependency-graph node in Cytoscape's element format.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { ContainerGraphJson } from "#/introspection/dependency-graph";
1
+ import type { ContainerGraphJson } from "#introspection/dependency-graph";
2
2
  /**
3
3
  * Renders a container's dependency graph as Graphviz DOT source.
4
4
  *
@@ -1,4 +1,4 @@
1
- import type { ContainerGraphJson } from "#/introspection/dependency-graph";
1
+ import type { ContainerGraphJson } from "#introspection/dependency-graph";
2
2
  /**
3
3
  * Mermaid `flowchart TD` source for a container graph — renders anywhere Mermaid does
4
4
  * (GitHub markdown, docs tooling, mermaid.live) with no extra library.
@@ -1,4 +1,4 @@
1
- import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
1
+ import type { ContainerGraphJson, GraphNode } from "#introspection/dependency-graph";
2
2
  /**
3
3
  * A dependency-graph node in React Flow's node format.
4
4
  *
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Initial grid layout: React Flow expects concrete positions; viewers re-layout anyway.
2
+ * Initial grid cell, in the pixels React Flow positions in: a starting layout viewers re-lay out,
3
+ * fixed by no contract.
3
4
  *
4
5
  * @since 0.3.16-canary.0
5
6
  */
6
- const GRID_COLUMN_COUNT = 5;
7
7
  const GRID_CELL_WIDTH_PX = 200;
8
8
  const GRID_CELL_HEIGHT_PX = 100;
9
9
  /**
@@ -12,6 +12,8 @@ const GRID_CELL_HEIGHT_PX = 100;
12
12
  * @since 0.5.0-canary.7
13
13
  */
14
14
  export function toReactFlowGraph(graph) {
15
+ // A square grid over the graph: as many columns as rows, derived from the node count.
16
+ const columnCount = Math.max(1, Math.ceil(Math.sqrt(graph.nodes.length)));
15
17
  const nodes = graph.nodes.map((node, idx) => ({
16
18
  id: node.id,
17
19
  data: {
@@ -22,8 +24,8 @@ export function toReactFlowGraph(graph) {
22
24
  fromParent: node.fromParent,
23
25
  },
24
26
  position: {
25
- x: (idx % GRID_COLUMN_COUNT) * GRID_CELL_WIDTH_PX,
26
- y: Math.floor(idx / GRID_COLUMN_COUNT) * GRID_CELL_HEIGHT_PX,
27
+ x: (idx % columnCount) * GRID_CELL_WIDTH_PX,
28
+ y: Math.floor(idx / columnCount) * GRID_CELL_HEIGHT_PX,
27
29
  },
28
30
  }));
29
31
  const edges = graph.edges.map((edge, idx) => ({
@@ -1,7 +1,7 @@
1
- import type { BindingRegistry } from "#/core/registry";
2
- import type { Token } from "#/core/token";
3
- import type { BindingIdentifier, BindingKind, BindingScope, BindingTag, Constructor, ResolveOptions } from "#/core/types";
4
- import type { ScopeManager } from "#/lifecycle/scope-manager";
1
+ import type { BindingRegistry } from "#core/registry";
2
+ import type { Token } from "#core/token";
3
+ import type { BindingIdentifier, BindingKind, BindingScope, BindingTag, Constructor, ResolveOptions } from "#core/types";
4
+ import type { ScopeManager } from "#lifecycle/scope-manager";
5
5
  /**
6
6
  * A read-only view of one binding: token, kind, scope, slot, and id.
7
7
  *
@@ -1,6 +1,7 @@
1
- import { effectiveBindingScope } from "#/core/binding-scope";
2
- import { tokenName } from "#/core/token";
3
- import { selectAllBindings } from "#/resolution/select/binding-select";
1
+ import { effectiveBindingScope } from "#core/binding-scope";
2
+ import { tokenName } from "#core/token";
3
+ import { DefaultConstraintContext } from "#resolution/context";
4
+ import { selectAllBindings } from "#resolution/select/binding-select";
4
5
  // ── Inspector ────────────────────────────────────────────────────────────────────────────────────────────────────────
5
6
  /**
6
7
  * The read-only introspection surface behind a container's `inspect()`.
@@ -38,17 +39,13 @@ export class Inspector {
38
39
  }
39
40
  const bindings = this.#registry.getAll(token);
40
41
  // An existence probe answers ambiguity with `true` — several matches still exist; only
41
- // resolution has to pick one.
42
- return bindings.length > 0 && selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0;
42
+ // resolution has to pick one. A default-slot alias answers any criteria by forwarding them.
43
+ return (bindings.length > 0 &&
44
+ (selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0 ||
45
+ this.#registry.getDefaultSlotBinding(token)?.kind === "alias"));
43
46
  }
44
47
  #makeConstraintContext(options) {
45
- return {
46
- resolutionPath: [],
47
- resolutionStack: [],
48
- parent: undefined,
49
- ancestors: [],
50
- currentResolveOptions: options,
51
- };
48
+ return new DefaultConstraintContext([], options);
52
49
  }
53
50
  #toSnapshot(binding) {
54
51
  // Aliased, not copied: slot tags are frozen where they are built, so a caller's write throws
@@ -1,7 +1,7 @@
1
- import type { Binding } from "#/core/binding";
2
- import type { Token } from "#/core/token";
3
- import type { ActivationHandler, Constructor, DeactivationHandler, DependencyKey, ResolutionContext } from "#/core/types";
4
- import type { MetadataReader } from "#/metadata/metadata-types";
1
+ import type { Binding } from "#core/binding";
2
+ import type { Token } from "#core/token";
3
+ import type { ActivationHandler, Constructor, DeactivationHandler, DependencyKey, ResolutionContext } from "#core/types";
4
+ import type { MetadataReader } from "#metadata/metadata-types";
5
5
  /**
6
6
  * One container's registry of container-level activation and deactivation hooks, keyed by token.
7
7
  *
@@ -1,7 +1,7 @@
1
- import { getOrInsert } from "#/core/map-upsert";
2
- import { advanceStateEpoch } from "#/core/state-epoch";
3
- import { tokenName } from "#/core/token";
4
- import { AsyncActivationError, AsyncDeactivationError, InvalidMetadataError } from "#/errors/errors";
1
+ import { getOrInsert } from "#core/map-upsert";
2
+ import { advanceStateEpoch } from "#core/state-epoch";
3
+ import { tokenName } from "#core/token";
4
+ import { AsyncActivationError, AsyncDeactivationError, InvalidMetadataError } from "#errors/errors";
5
5
  /**
6
6
  * One container's registry of container-level activation and deactivation hooks, keyed by token.
7
7
  *
@@ -97,7 +97,11 @@ export class LifecycleManager {
97
97
  let activatedInstance = instance;
98
98
  // 1. @postConstruct() — must be sync (instance fully constructed per TC39 order)
99
99
  for (const methodName of lifecycleMethods(binding, metadataReader, "postConstruct")) {
100
- if (callHook(activatedInstance, methodName) instanceof Promise) {
100
+ const hookResult = callHook(activatedInstance, methodName);
101
+ if (hookResult instanceof Promise) {
102
+ // The hook has already run; adopt its rejection so a failing async hook cannot become an
103
+ // unhandled rejection that ends the process, then report the sync-lane violation.
104
+ void hookResult.catch(() => { });
101
105
  throw new AsyncActivationError(tokenName(binding.token), "postConstruct", methodName);
102
106
  }
103
107
  }
@@ -105,6 +109,7 @@ export class LifecycleManager {
105
109
  if (binding.kind !== "alias" && binding.activationHook !== undefined) {
106
110
  const activationResult = binding.activationHook(resolutionContext, activatedInstance);
107
111
  if (activationResult instanceof Promise) {
112
+ void activationResult.catch(() => { });
108
113
  throw new AsyncActivationError(tokenName(binding.token), "onActivation");
109
114
  }
110
115
  activatedInstance = activationResult;
@@ -116,6 +121,7 @@ export class LifecycleManager {
116
121
  for (const hook of containerHooks) {
117
122
  const activationResult = hook(resolutionContext, activatedInstance);
118
123
  if (activationResult instanceof Promise) {
124
+ void activationResult.catch(() => { });
119
125
  throw new AsyncActivationError(tokenDisplayName, "onActivation");
120
126
  }
121
127
  activatedInstance = activationResult;
@@ -159,6 +165,9 @@ export class LifecycleManager {
159
165
  for (const hook of containerHooks) {
160
166
  const hookResult = hook(instance);
161
167
  if (hookResult instanceof Promise) {
168
+ // The hook has already run; adopt its rejection so a failing async hook cannot become an
169
+ // unhandled rejection that ends the process, then report the sync-lane violation.
170
+ void hookResult.catch(() => { });
162
171
  throw new AsyncDeactivationError(tokenDisplayName);
163
172
  }
164
173
  }
@@ -167,12 +176,15 @@ export class LifecycleManager {
167
176
  if (binding.kind !== "alias" && binding.deactivationHook !== undefined) {
168
177
  const hookResult = binding.deactivationHook(instance);
169
178
  if (hookResult instanceof Promise) {
179
+ void hookResult.catch(() => { });
170
180
  throw new AsyncDeactivationError(tokenDisplayName);
171
181
  }
172
182
  }
173
183
  // 3. @preDestroy()
174
184
  for (const methodName of lifecycleMethods(binding, metadataReader, "preDestroy")) {
175
- if (callHook(instance, methodName) instanceof Promise) {
185
+ const hookResult = callHook(instance, methodName);
186
+ if (hookResult instanceof Promise) {
187
+ void hookResult.catch(() => { });
176
188
  throw new AsyncDeactivationError(tokenDisplayName);
177
189
  }
178
190
  }
@@ -1,5 +1,5 @@
1
- import type { Binding } from "#/core/binding";
2
- import type { BindingIdentifier } from "#/core/types";
1
+ import type { Binding } from "#core/binding";
2
+ import type { BindingIdentifier } from "#core/types";
3
3
  /**
4
4
  * One container's instance caches — singletons, in-flight async creations, and the scoped cache.
5
5
  *