@codefast/di 0.8.0 → 0.8.1

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 (218) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +242 -159
  3. package/dist/ambient/active-container.d.ts +1 -2
  4. package/dist/ambient/active-container.js +1 -2
  5. package/dist/container/binding-builders.d.ts +1 -2
  6. package/dist/container/binding-builders.js +1 -2
  7. package/dist/container/container.d.ts +1 -2
  8. package/dist/container/container.js +1 -2
  9. package/dist/core/binding-scope.d.ts +1 -2
  10. package/dist/core/binding-scope.js +1 -2
  11. package/dist/core/binding.d.ts +1 -2
  12. package/dist/core/binding.js +1 -2
  13. package/dist/core/constraint-requirement.d.ts +1 -2
  14. package/dist/core/constraint-requirement.js +1 -2
  15. package/dist/core/constructor-type.d.ts +1 -2
  16. package/dist/core/constructor-type.js +1 -2
  17. package/dist/core/map-upsert.d.ts +1 -2
  18. package/dist/core/map-upsert.js +1 -2
  19. package/dist/core/module.d.ts +1 -2
  20. package/dist/core/module.js +1 -2
  21. package/dist/core/registry.d.ts +1 -2
  22. package/dist/core/registry.js +1 -2
  23. package/dist/core/tag.d.ts +1 -2
  24. package/dist/core/tag.js +1 -2
  25. package/dist/core/token.d.ts +1 -2
  26. package/dist/core/token.js +1 -2
  27. package/dist/core/types.d.ts +1 -2
  28. package/dist/core/types.js +1 -2
  29. package/dist/decorators/inject.d.ts +1 -2
  30. package/dist/decorators/inject.js +1 -2
  31. package/dist/decorators/injectable.d.ts +1 -2
  32. package/dist/decorators/injectable.js +1 -2
  33. package/dist/decorators/lifecycle-decorators.d.ts +1 -2
  34. package/dist/decorators/lifecycle-decorators.js +1 -2
  35. package/dist/errors/diagnostics.d.ts +1 -2
  36. package/dist/errors/diagnostics.js +1 -2
  37. package/dist/errors/errors.d.ts +1 -2
  38. package/dist/errors/errors.js +1 -2
  39. package/dist/index.d.ts +1 -2
  40. package/dist/index.js +1 -2
  41. package/dist/injection/descriptor.d.ts +1 -2
  42. package/dist/injection/descriptor.js +1 -2
  43. package/dist/injection/resolve-options.d.ts +1 -2
  44. package/dist/injection/resolve-options.js +1 -2
  45. package/dist/introspection/dependency-graph.d.ts +1 -2
  46. package/dist/introspection/dependency-graph.js +1 -2
  47. package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -2
  48. package/dist/introspection/graph-adapters/cytoscape.js +1 -2
  49. package/dist/introspection/graph-adapters/dot.d.ts +1 -2
  50. package/dist/introspection/graph-adapters/dot.js +1 -2
  51. package/dist/introspection/graph-adapters/mermaid.d.ts +1 -2
  52. package/dist/introspection/graph-adapters/mermaid.js +1 -2
  53. package/dist/introspection/graph-adapters/reactflow.d.ts +1 -2
  54. package/dist/introspection/graph-adapters/reactflow.js +1 -2
  55. package/dist/introspection/inspector.d.ts +1 -2
  56. package/dist/introspection/inspector.js +1 -2
  57. package/dist/lifecycle/lifecycle-manager.d.ts +1 -2
  58. package/dist/lifecycle/lifecycle-manager.js +1 -2
  59. package/dist/lifecycle/scope-manager.d.ts +1 -2
  60. package/dist/lifecycle/scope-manager.js +1 -2
  61. package/dist/metadata/metadata-keys.d.ts +1 -2
  62. package/dist/metadata/metadata-keys.js +1 -2
  63. package/dist/metadata/metadata-reader-token.d.ts +1 -2
  64. package/dist/metadata/metadata-reader-token.js +1 -2
  65. package/dist/metadata/metadata-types.d.ts +1 -2
  66. package/dist/metadata/metadata-types.js +1 -2
  67. package/dist/metadata/symbol-metadata-reader.d.ts +1 -2
  68. package/dist/metadata/symbol-metadata-reader.js +1 -2
  69. package/dist/metadata/verifying-metadata-reader.d.ts +1 -2
  70. package/dist/metadata/verifying-metadata-reader.js +1 -2
  71. package/dist/resolution/cache/activation-need.d.ts +1 -2
  72. package/dist/resolution/cache/activation-need.js +1 -2
  73. package/dist/resolution/cache/binding-lookup-cache.d.ts +1 -2
  74. package/dist/resolution/cache/binding-lookup-cache.js +1 -2
  75. package/dist/resolution/cache/class-introspector.d.ts +1 -2
  76. package/dist/resolution/cache/class-introspector.js +1 -2
  77. package/dist/resolution/context.d.ts +1 -2
  78. package/dist/resolution/context.js +1 -2
  79. package/dist/resolution/path/resolution-path.d.ts +1 -2
  80. package/dist/resolution/path/resolution-path.js +1 -2
  81. package/dist/resolution/plan/instantiation-plan.d.ts +1 -2
  82. package/dist/resolution/plan/instantiation-plan.js +1 -2
  83. package/dist/resolution/resolver.d.ts +1 -2
  84. package/dist/resolution/resolver.js +1 -2
  85. package/dist/resolution/select/binding-select.d.ts +1 -2
  86. package/dist/resolution/select/binding-select.js +1 -2
  87. package/dist/resolution/select/constraints.d.ts +1 -2
  88. package/dist/resolution/select/constraints.js +1 -2
  89. package/package.json +6 -58
  90. package/dist/ambient/active-container.d.ts.map +0 -1
  91. package/dist/ambient/active-container.js.map +0 -1
  92. package/dist/container/binding-builders.d.ts.map +0 -1
  93. package/dist/container/binding-builders.js.map +0 -1
  94. package/dist/container/container.d.ts.map +0 -1
  95. package/dist/container/container.js.map +0 -1
  96. package/dist/core/binding-scope.d.ts.map +0 -1
  97. package/dist/core/binding-scope.js.map +0 -1
  98. package/dist/core/binding.d.ts.map +0 -1
  99. package/dist/core/binding.js.map +0 -1
  100. package/dist/core/constraint-requirement.d.ts.map +0 -1
  101. package/dist/core/constraint-requirement.js.map +0 -1
  102. package/dist/core/constructor-type.d.ts.map +0 -1
  103. package/dist/core/constructor-type.js.map +0 -1
  104. package/dist/core/map-upsert.d.ts.map +0 -1
  105. package/dist/core/map-upsert.js.map +0 -1
  106. package/dist/core/module.d.ts.map +0 -1
  107. package/dist/core/module.js.map +0 -1
  108. package/dist/core/registry.d.ts.map +0 -1
  109. package/dist/core/registry.js.map +0 -1
  110. package/dist/core/tag.d.ts.map +0 -1
  111. package/dist/core/tag.js.map +0 -1
  112. package/dist/core/token.d.ts.map +0 -1
  113. package/dist/core/token.js.map +0 -1
  114. package/dist/core/types.d.ts.map +0 -1
  115. package/dist/core/types.js.map +0 -1
  116. package/dist/decorators/inject.d.ts.map +0 -1
  117. package/dist/decorators/inject.js.map +0 -1
  118. package/dist/decorators/injectable.d.ts.map +0 -1
  119. package/dist/decorators/injectable.js.map +0 -1
  120. package/dist/decorators/lifecycle-decorators.d.ts.map +0 -1
  121. package/dist/decorators/lifecycle-decorators.js.map +0 -1
  122. package/dist/errors/diagnostics.d.ts.map +0 -1
  123. package/dist/errors/diagnostics.js.map +0 -1
  124. package/dist/errors/errors.d.ts.map +0 -1
  125. package/dist/errors/errors.js.map +0 -1
  126. package/dist/index.d.ts.map +0 -1
  127. package/dist/index.js.map +0 -1
  128. package/dist/injection/descriptor.d.ts.map +0 -1
  129. package/dist/injection/descriptor.js.map +0 -1
  130. package/dist/injection/resolve-options.d.ts.map +0 -1
  131. package/dist/injection/resolve-options.js.map +0 -1
  132. package/dist/introspection/dependency-graph.d.ts.map +0 -1
  133. package/dist/introspection/dependency-graph.js.map +0 -1
  134. package/dist/introspection/graph-adapters/cytoscape.d.ts.map +0 -1
  135. package/dist/introspection/graph-adapters/cytoscape.js.map +0 -1
  136. package/dist/introspection/graph-adapters/dot.d.ts.map +0 -1
  137. package/dist/introspection/graph-adapters/dot.js.map +0 -1
  138. package/dist/introspection/graph-adapters/mermaid.d.ts.map +0 -1
  139. package/dist/introspection/graph-adapters/mermaid.js.map +0 -1
  140. package/dist/introspection/graph-adapters/reactflow.d.ts.map +0 -1
  141. package/dist/introspection/graph-adapters/reactflow.js.map +0 -1
  142. package/dist/introspection/inspector.d.ts.map +0 -1
  143. package/dist/introspection/inspector.js.map +0 -1
  144. package/dist/lifecycle/lifecycle-manager.d.ts.map +0 -1
  145. package/dist/lifecycle/lifecycle-manager.js.map +0 -1
  146. package/dist/lifecycle/scope-manager.d.ts.map +0 -1
  147. package/dist/lifecycle/scope-manager.js.map +0 -1
  148. package/dist/metadata/metadata-keys.d.ts.map +0 -1
  149. package/dist/metadata/metadata-keys.js.map +0 -1
  150. package/dist/metadata/metadata-reader-token.d.ts.map +0 -1
  151. package/dist/metadata/metadata-reader-token.js.map +0 -1
  152. package/dist/metadata/metadata-types.d.ts.map +0 -1
  153. package/dist/metadata/metadata-types.js.map +0 -1
  154. package/dist/metadata/symbol-metadata-reader.d.ts.map +0 -1
  155. package/dist/metadata/symbol-metadata-reader.js.map +0 -1
  156. package/dist/metadata/verifying-metadata-reader.d.ts.map +0 -1
  157. package/dist/metadata/verifying-metadata-reader.js.map +0 -1
  158. package/dist/resolution/cache/activation-need.d.ts.map +0 -1
  159. package/dist/resolution/cache/activation-need.js.map +0 -1
  160. package/dist/resolution/cache/binding-lookup-cache.d.ts.map +0 -1
  161. package/dist/resolution/cache/binding-lookup-cache.js.map +0 -1
  162. package/dist/resolution/cache/class-introspector.d.ts.map +0 -1
  163. package/dist/resolution/cache/class-introspector.js.map +0 -1
  164. package/dist/resolution/context.d.ts.map +0 -1
  165. package/dist/resolution/context.js.map +0 -1
  166. package/dist/resolution/path/resolution-path.d.ts.map +0 -1
  167. package/dist/resolution/path/resolution-path.js.map +0 -1
  168. package/dist/resolution/plan/instantiation-plan.d.ts.map +0 -1
  169. package/dist/resolution/plan/instantiation-plan.js.map +0 -1
  170. package/dist/resolution/resolver.d.ts.map +0 -1
  171. package/dist/resolution/resolver.js.map +0 -1
  172. package/dist/resolution/select/binding-select.d.ts.map +0 -1
  173. package/dist/resolution/select/binding-select.js.map +0 -1
  174. package/dist/resolution/select/constraints.d.ts.map +0 -1
  175. package/dist/resolution/select/constraints.js.map +0 -1
  176. package/src/ambient/active-container.ts +0 -69
  177. package/src/container/binding-builders.ts +0 -337
  178. package/src/container/container.ts +0 -972
  179. package/src/core/binding-scope.ts +0 -15
  180. package/src/core/binding.ts +0 -505
  181. package/src/core/constraint-requirement.ts +0 -94
  182. package/src/core/constructor-type.ts +0 -18
  183. package/src/core/map-upsert.ts +0 -53
  184. package/src/core/module.ts +0 -117
  185. package/src/core/registry.ts +0 -319
  186. package/src/core/tag.ts +0 -212
  187. package/src/core/token.ts +0 -31
  188. package/src/core/types.ts +0 -149
  189. package/src/decorators/inject.ts +0 -101
  190. package/src/decorators/injectable.ts +0 -134
  191. package/src/decorators/lifecycle-decorators.ts +0 -43
  192. package/src/errors/diagnostics.ts +0 -45
  193. package/src/errors/errors.ts +0 -499
  194. package/src/index.ts +0 -139
  195. package/src/injection/descriptor.ts +0 -218
  196. package/src/injection/resolve-options.ts +0 -201
  197. package/src/introspection/dependency-graph.ts +0 -296
  198. package/src/introspection/graph-adapters/cytoscape.ts +0 -78
  199. package/src/introspection/graph-adapters/dot.ts +0 -32
  200. package/src/introspection/graph-adapters/mermaid.ts +0 -61
  201. package/src/introspection/graph-adapters/reactflow.ts +0 -84
  202. package/src/introspection/inspector.ts +0 -134
  203. package/src/lifecycle/lifecycle-manager.ts +0 -272
  204. package/src/lifecycle/scope-manager.ts +0 -158
  205. package/src/metadata/metadata-keys.ts +0 -28
  206. package/src/metadata/metadata-reader-token.ts +0 -10
  207. package/src/metadata/metadata-types.ts +0 -57
  208. package/src/metadata/symbol-metadata-reader.ts +0 -126
  209. package/src/metadata/verifying-metadata-reader.ts +0 -41
  210. package/src/resolution/cache/activation-need.ts +0 -87
  211. package/src/resolution/cache/binding-lookup-cache.ts +0 -169
  212. package/src/resolution/cache/class-introspector.ts +0 -256
  213. package/src/resolution/context.ts +0 -331
  214. package/src/resolution/path/resolution-path.ts +0 -167
  215. package/src/resolution/plan/instantiation-plan.ts +0 -592
  216. package/src/resolution/resolver.ts +0 -1654
  217. package/src/resolution/select/binding-select.ts +0 -207
  218. package/src/resolution/select/constraints.ts +0 -146
@@ -1,87 +0,0 @@
1
- /**
2
- * Per binding: does resolving it have to go through the activation pipeline?
3
- *
4
- * @remarks Versioned on the lifecycle manager plus the own registry, since `onActivation` can be
5
- * registered at any time and a rebind mints binding ids the memo must not keep forever.
6
- */
7
- import type { Binding } from "#/core/binding";
8
- import type { BindingRegistry } from "#/core/registry";
9
- import type { BindingIdentifier } from "#/core/types";
10
- import type { LifecycleManager } from "#/lifecycle/lifecycle-manager";
11
- import type { ClassIntrospector } from "#/resolution/cache/class-introspector";
12
-
13
- /**
14
- * A per-binding cache of whether activation work — hooks or `@postConstruct` — is needed on resolve.
15
- *
16
- * @since 0.5.0-canary.8
17
- */
18
- export class ActivationNeedCache {
19
- readonly #needByBindingId = new Map<BindingIdentifier, boolean>();
20
- #version = -1;
21
- readonly #lifecycle: LifecycleManager;
22
- readonly #classes: ClassIntrospector;
23
- readonly #registry: BindingRegistry;
24
-
25
- constructor(lifecycle: LifecycleManager, classes: ClassIntrospector, registry: BindingRegistry) {
26
- this.#lifecycle = lifecycle;
27
- this.#classes = classes;
28
- this.#registry = registry;
29
- }
30
-
31
- needsActivation<Value>(binding: Binding<Value>): boolean {
32
- // The chain writes a binding's own hook in place with no version anything here can see, so it
33
- // is read fresh on every call; the memo covers only container hooks and lifecycle metadata.
34
- if (binding.kind !== "alias" && binding.onActivation !== undefined) {
35
- return true;
36
- }
37
- const lifecycleVersion = this.#lifecycle.activationVersion;
38
- // No hooks registered anywhere and none on the binding: only classes can still surprise us,
39
- // via a @postConstruct we have not looked for yet.
40
- if (lifecycleVersion === 0 && binding.kind !== "class" && binding.kind !== "alias") {
41
- return false;
42
- }
43
- // The registry version evicts entries for binding ids a rebind has retired.
44
- const version = lifecycleVersion + this.#registry.version;
45
- if (this.#version !== version) {
46
- this.#needByBindingId.clear();
47
- this.#version = version;
48
- }
49
- const cached = this.#needByBindingId.get(binding.id);
50
- if (cached !== undefined) {
51
- return cached;
52
- }
53
- const needsActivation =
54
- binding.kind === "class" ? this.#classNeedsActivation(binding) : this.#nonClassNeedsActivation(binding);
55
- this.#needByBindingId.set(binding.id, needsActivation);
56
- return needsActivation;
57
- }
58
-
59
- /**
60
- * Settles a class binding's answer once its lifecycle metadata has actually been read, which
61
- * only happens on the first instantiation.
62
- *
63
- * @returns the answer to use for this resolve — possibly now `false` where it was a
64
- * conservative `true`.
65
- */
66
- refreshAfterFirstInstantiation<Value>(binding: Binding<Value>, needsActivation: boolean): boolean {
67
- if (binding.kind !== "class" || this.#classes.knownPostConstruct(binding.target) !== undefined) {
68
- return needsActivation;
69
- }
70
- this.#classes.discoverPostConstruct(binding.target);
71
- this.#needByBindingId.delete(binding.id);
72
- return this.needsActivation(binding);
73
- }
74
-
75
- // Own hooks are answered before the memo, so both computations cover the memoizable rest only.
76
- #classNeedsActivation<Value>(binding: Binding<Value> & { kind: "class" }): boolean {
77
- if (this.#lifecycle.hasActivationHandlers(binding.token)) {
78
- return true;
79
- }
80
- // Unknown lifecycle metadata: activate once so the first instantiation can settle it.
81
- return this.#classes.knownPostConstruct(binding.target) !== false;
82
- }
83
-
84
- #nonClassNeedsActivation<Value>(binding: Binding<Value>): boolean {
85
- return this.#lifecycle.hasActivationHandlers(binding.token);
86
- }
87
- }
@@ -1,169 +0,0 @@
1
- /**
2
- * Options-less token → terminal binding, memoized per container chain.
3
- *
4
- * @remarks Each cache links to its parent's, so a child answers from its own chain without walking
5
- * the container hierarchy again.
6
- */
7
- import type { Binding } from "#/core/binding";
8
- import { getOrInsertComputed } from "#/core/map-upsert";
9
- import type { BindingRegistry } from "#/core/registry";
10
- import type { BindingTag } from "#/core/tag";
11
- import type { Token } from "#/core/token";
12
- import type { Constructor } from "#/core/types";
13
-
14
- /**
15
- * A token's terminal binding with alias hops already folded, plus the container that owns it.
16
- *
17
- * @typeParam Owner - the resolver type, kept generic so this module stays free of resolver internals
18
- *
19
- * @since 0.5.0-canary.8
20
- */
21
- export interface DefaultLookupEntry<Owner> {
22
- readonly binding: Binding;
23
- readonly owner: Owner;
24
- }
25
-
26
- /**
27
- * Alias folding gives up past this many hops and defers to the full resolve loop, whose
28
- * Set-based traversal detects genuine cycles exactly rather than by an arbitrary cap.
29
- *
30
- * @since 0.5.0-canary.8
31
- */
32
- export const ALIAS_HOP_LIMIT = 32;
33
-
34
- const newTagToEntryMap = <Owner>(): Map<BindingTag, DefaultLookupEntry<Owner> | null> => new Map();
35
-
36
- /**
37
- * A version-stamped cache of binding lookups by token and criterion across the container chain.
38
- *
39
- * @since 0.5.0-canary.9
40
- */
41
- export class BindingLookupCache<Owner> {
42
- readonly #byToken = new Map<Token<unknown> | Constructor, DefaultLookupEntry<Owner> | null>();
43
- #version = -1;
44
- // One entry in front of the map: the two shapes that reach here — an alias, and a token owned by
45
- // a parent — are both resolved in a loop over the same token. `null` is a real answer, so absence
46
- // is tracked by the token slot rather than by the entry.
47
- #lastToken: Token<unknown> | Constructor | undefined;
48
- #lastEntry: DefaultLookupEntry<Owner> | null = null;
49
- readonly #byTokenAndTag = new Map<Token<unknown> | Constructor, Map<BindingTag, DefaultLookupEntry<Owner> | null>>();
50
- #taggedVersion = -1;
51
- // One entry in front of the tag map, and the map is not written until a second distinct request
52
- // shape appears: a per-request child usually asks one (token, tag) once, and the inner-map
53
- // allocation was that shape's whole regression when this memo landed.
54
- #lastTagToken: Token<unknown> | Constructor | undefined;
55
- #lastTag: BindingTag | undefined;
56
- #lastTaggedEntry: DefaultLookupEntry<Owner> | null = null;
57
-
58
- readonly #registry: BindingRegistry;
59
- readonly #owner: Owner;
60
- readonly #parent: BindingLookupCache<Owner> | undefined;
61
-
62
- constructor(registry: BindingRegistry, owner: Owner, parent: BindingLookupCache<Owner> | undefined) {
63
- this.#registry = registry;
64
- this.#owner = owner;
65
- this.#parent = parent;
66
- }
67
-
68
- /** Summed registry versions of this cache's whole chain — the memo stamp. */
69
- chainVersion(): number {
70
- let version = this.#registry.version;
71
- for (let cache = this.#parent; cache !== undefined; cache = cache.#parent) {
72
- version += cache.#registry.version;
73
- }
74
- return version;
75
- }
76
-
77
- /** `null` when the token's shape needs the full selection path. */
78
- defaultEntry(token: Token<unknown> | Constructor): DefaultLookupEntry<Owner> | null {
79
- const version = this.chainVersion();
80
- if (version !== this.#version) {
81
- this.#byToken.clear();
82
- this.#version = version;
83
- this.#lastToken = undefined;
84
- } else if (token === this.#lastToken) {
85
- return this.#lastEntry;
86
- }
87
- let entry = this.#byToken.get(token);
88
- if (entry === undefined) {
89
- entry = this.#foldAliases(token);
90
- this.#byToken.set(token, entry);
91
- }
92
- this.#lastToken = token;
93
- this.#lastEntry = entry;
94
- return entry;
95
- }
96
-
97
- /** `null` when the tag's shape needs the full selection path. */
98
- taggedEntry(token: Token<unknown> | Constructor, tag: BindingTag): DefaultLookupEntry<Owner> | null {
99
- const version = this.chainVersion();
100
- if (version !== this.#taggedVersion) {
101
- this.#byTokenAndTag.clear();
102
- this.#taggedVersion = version;
103
- this.#lastTagToken = undefined;
104
- this.#lastTag = undefined;
105
- } else if (token === this.#lastTagToken && tag === this.#lastTag) {
106
- return this.#lastTaggedEntry;
107
- }
108
- let entry: DefaultLookupEntry<Owner> | null | undefined;
109
- if (this.#lastTagToken === undefined) {
110
- // First shape this cache generation sees: answer from the walk and defer the map entirely.
111
- entry = this.#findTaggedInChain(token, tag);
112
- } else {
113
- // Keyed by the criterion object itself: criteria are interned, so identity is the slot
114
- // contract's own `Object.is` — the same exactness the registry's tagged index relies on.
115
- const byTag = getOrInsertComputed(this.#byTokenAndTag, token, newTagToEntryMap);
116
- entry = byTag.get(tag);
117
- if (entry === undefined) {
118
- entry = this.#findTaggedInChain(token, tag);
119
- byTag.set(tag, entry);
120
- }
121
- }
122
- this.#lastTagToken = token;
123
- this.#lastTag = tag;
124
- this.#lastTaggedEntry = entry;
125
- return entry;
126
- }
127
-
128
- #foldAliases(token: Token<unknown> | Constructor): DefaultLookupEntry<Owner> | null {
129
- let current = token;
130
- for (let hop = 0; hop < ALIAS_HOP_LIMIT; hop += 1) {
131
- const entry = this.#findDefaultInChain(current);
132
- if (entry === null) {
133
- return null;
134
- }
135
- if (entry.binding.kind !== "alias") {
136
- return entry;
137
- }
138
- current = entry.binding.target;
139
- }
140
- return null;
141
- }
142
-
143
- #findDefaultInChain(token: Token<unknown> | Constructor): DefaultLookupEntry<Owner> | null {
144
- const fast = this.#registry.getFastDefault(token);
145
- if (fast !== undefined) {
146
- return { binding: fast, owner: this.#owner };
147
- }
148
- // A level with non-fast bindings (multi-slot / predicate) needs full selection — bail.
149
- if (this.#registry.has(token)) {
150
- return null;
151
- }
152
- return this.#parent === undefined ? null : this.#parent.#findDefaultInChain(token);
153
- }
154
-
155
- #findTaggedInChain(token: Token<unknown> | Constructor, tag: BindingTag): DefaultLookupEntry<Owner> | null {
156
- const tagged = this.#registry.getSimpleTagged(token, tag);
157
- if (tagged !== undefined) {
158
- // Predicates need a live context; aliases carry options through the full path.
159
- if (tagged.predicate !== undefined || tagged.kind === "alias") {
160
- return null;
161
- }
162
- return { binding: tagged, owner: this.#owner };
163
- }
164
- if (this.#registry.has(token)) {
165
- return null;
166
- }
167
- return this.#parent === undefined ? null : this.#parent.#findTaggedInChain(token, tag);
168
- }
169
- }
@@ -1,256 +0,0 @@
1
- /**
2
- * Per-class decorator metadata, cached by constructor.
3
- *
4
- * @remarks Metadata cannot change once a class is defined, so nothing here needs version stamping.
5
- */
6
-
7
- import { runWithAmbientResolution } from "#/ambient/active-container";
8
- import type { AmbientResolution } from "#/ambient/active-container";
9
- import type { Container } from "#/container/container";
10
- import type { ConstructorInvocation } from "#/core/constructor-type";
11
- import type { Constructor } from "#/core/types";
12
- import { InvalidMetadataError } from "#/errors/errors";
13
- import type { InjectionDescriptor } from "#/injection/descriptor";
14
- import type { ConstructorMetadata, LifecycleMetadata, MetadataReader } from "#/metadata/metadata-types";
15
-
16
- // Verified pairs, not verified classes: two readers may disagree about the same class, and a reader
17
- // that goes out of scope takes its record with it.
18
- const verifiedTargets = new WeakMap<MetadataReader, WeakSet<Constructor>>();
19
- const verifiedLifecycleTargets = new WeakMap<MetadataReader, WeakSet<Constructor>>();
20
- const verifiedAccessorTargets = new WeakMap<MetadataReader, WeakSet<Constructor>>();
21
-
22
- function isVerified(
23
- cache: WeakMap<MetadataReader, WeakSet<Constructor>>,
24
- reader: MetadataReader,
25
- target: Constructor,
26
- ): boolean {
27
- return cache.get(reader)?.has(target) ?? false;
28
- }
29
-
30
- function markVerified(
31
- cache: WeakMap<MetadataReader, WeakSet<Constructor>>,
32
- reader: MetadataReader,
33
- target: Constructor,
34
- ): void {
35
- let verified = cache.get(reader);
36
- if (verified === undefined) {
37
- verified = new WeakSet();
38
- cache.set(reader, verified);
39
- }
40
- verified.add(target);
41
- }
42
-
43
- /**
44
- * A reader's constructor metadata for a class, verified the first time this process asks.
45
- *
46
- * @remarks Metadata cannot change once a class is defined, so re-checking per container would charge
47
- * every fresh container for a fact already established — a per-request child or a cold boot pays
48
- * that repeatedly.
49
- *
50
- * @since 0.6.0
51
- */
52
- export function verifyConstructorMetadata(
53
- reader: MetadataReader,
54
- target: Constructor,
55
- ): ConstructorMetadata | undefined {
56
- const metadata = reader.getConstructorMetadata(target);
57
- if (metadata === undefined) {
58
- return undefined;
59
- }
60
- if (isVerified(verifiedTargets, reader, target)) {
61
- return metadata;
62
- }
63
- assertConstructorMetadata(metadata, target);
64
- markVerified(verifiedTargets, reader, target);
65
-
66
- return metadata;
67
- }
68
-
69
- /**
70
- * Verifies what a reader claims about a class, since a `MetadataReader` is a public seam.
71
- *
72
- * @remarks Every path that reads constructor metadata comes through here, so a reader that answers
73
- * wrongly is a named error at the class it described rather than a `TypeError` raised later inside a
74
- * resolve. Only what a consumer dereferences is checked: `params` and each entry's `token`.
75
- * `optional`/`multi` degrade to falsy without crashing, and `index` is decorative — dependencies are
76
- * consumed positionally.
77
- *
78
- * @since 0.6.0
79
- */
80
- export function assertConstructorMetadata(metadata: unknown, target: Constructor): ConstructorMetadata | undefined {
81
- if (metadata === undefined) {
82
- return undefined;
83
- }
84
- if (typeof metadata !== "object" || metadata === null) {
85
- throw new InvalidMetadataError(
86
- target.name,
87
- `constructor metadata: expected an object, received ${typeof metadata}`,
88
- );
89
- }
90
- const params: unknown = Reflect.get(metadata, "params");
91
- if (!Array.isArray(params)) {
92
- throw new InvalidMetadataError(target.name, "constructor metadata: params is not an array");
93
- }
94
- for (const [position, param] of params.entries()) {
95
- if (typeof param !== "object" || param === null) {
96
- throw new InvalidMetadataError(target.name, `constructor metadata: params[${String(position)}] is not an object`);
97
- }
98
- const dependency: unknown = Reflect.get(param, "token");
99
- if (typeof dependency !== "object" && typeof dependency !== "function") {
100
- throw new InvalidMetadataError(
101
- target.name,
102
- `constructor metadata: params[${String(position)}].token is not a token or a class`,
103
- );
104
- }
105
- }
106
-
107
- return metadata as ConstructorMetadata;
108
- }
109
-
110
- /**
111
- * A reader's lifecycle metadata for a class, verified the first time this process asks.
112
- *
113
- * @since 0.6.0
114
- */
115
- export function verifyLifecycleMetadata(reader: MetadataReader, target: Constructor): LifecycleMetadata | undefined {
116
- const metadata = reader.getLifecycleMetadata(target);
117
- if (metadata === undefined || isVerified(verifiedLifecycleTargets, reader, target)) {
118
- return metadata;
119
- }
120
- if (typeof metadata !== "object" || metadata === null) {
121
- throw new InvalidMetadataError(target.name, `lifecycle metadata: expected an object, received ${typeof metadata}`);
122
- }
123
- for (const phase of ["postConstruct", "preDestroy"] as const) {
124
- const methods: unknown = Reflect.get(metadata, phase);
125
- if (methods === undefined) {
126
- continue;
127
- }
128
- if (!Array.isArray(methods)) {
129
- throw new InvalidMetadataError(target.name, `lifecycle metadata: ${phase} is not an array`);
130
- }
131
- for (const [position, name] of methods.entries()) {
132
- if (typeof name !== "string") {
133
- throw new InvalidMetadataError(
134
- target.name,
135
- `lifecycle metadata: ${phase}[${String(position)}] is not a string`,
136
- );
137
- }
138
- }
139
- }
140
- markVerified(verifiedLifecycleTargets, reader, target);
141
- return metadata;
142
- }
143
-
144
- /**
145
- * A reader's accessor metadata for a class, verified the first time this process asks.
146
- *
147
- * @since 0.6.0
148
- */
149
- export function verifyAccessorMetadata(
150
- reader: MetadataReader,
151
- target: Constructor,
152
- ): ReadonlyArray<{ readonly key: string | symbol; readonly descriptor: InjectionDescriptor }> | undefined {
153
- const metadata = reader.getAccessorMetadata?.(target);
154
- if (metadata === undefined || isVerified(verifiedAccessorTargets, reader, target)) {
155
- return metadata;
156
- }
157
- if (!Array.isArray(metadata)) {
158
- throw new InvalidMetadataError(target.name, "accessor metadata: expected an array");
159
- }
160
- for (const [position, entry] of metadata.entries()) {
161
- if (typeof entry !== "object" || entry === null) {
162
- throw new InvalidMetadataError(target.name, `accessor metadata: [${String(position)}] is not an object`);
163
- }
164
- const key: unknown = Reflect.get(entry, "key");
165
- if (typeof key !== "string" && typeof key !== "symbol") {
166
- throw new InvalidMetadataError(
167
- target.name,
168
- `accessor metadata: [${String(position)}].key is not a string or symbol`,
169
- );
170
- }
171
- const descriptor: unknown = Reflect.get(entry, "descriptor");
172
- if (typeof descriptor !== "object" || descriptor === null) {
173
- throw new InvalidMetadataError(
174
- target.name,
175
- `accessor metadata: [${String(position)}].descriptor is not an object`,
176
- );
177
- }
178
- const dependency: unknown = Reflect.get(descriptor, "token");
179
- if (typeof dependency !== "object" && typeof dependency !== "function") {
180
- throw new InvalidMetadataError(
181
- target.name,
182
- `accessor metadata: [${String(position)}].descriptor.token is not a token or a class`,
183
- );
184
- }
185
- }
186
- markVerified(verifiedAccessorTargets, reader, target);
187
- return metadata;
188
- }
189
-
190
- /**
191
- * A per-class cache of constructor metadata and the activation facts derived from it.
192
- *
193
- * @since 0.5.0-canary.8
194
- */
195
- export class ClassIntrospector {
196
- // Unallocated until the container resolves its first class binding — a container bound entirely
197
- // to constants, factories or aliases never introspects one.
198
- #constructorMetadata: WeakMap<Constructor, ConstructorMetadata | null> | undefined;
199
- #hasPostConstruct: WeakMap<Constructor, boolean> | undefined;
200
- #needsActiveContainer: WeakMap<Constructor, boolean> | undefined;
201
- readonly #reader: MetadataReader;
202
- readonly #container: Container;
203
-
204
- constructor(reader: MetadataReader, container: Container) {
205
- this.#reader = reader;
206
- this.#container = container;
207
- }
208
-
209
- constructorMetadata(target: Constructor): ConstructorMetadata | undefined {
210
- const cached = this.#constructorMetadata?.get(target);
211
- if (cached !== undefined) {
212
- return cached === null ? undefined : cached;
213
- }
214
- const metadata = this.#reader.getConstructorMetadata(target);
215
- (this.#constructorMetadata ??= new WeakMap<Constructor, ConstructorMetadata | null>()).set(
216
- target,
217
- metadata ?? null,
218
- );
219
- return metadata;
220
- }
221
-
222
- /**
223
- * Whether the class has a `@postConstruct` hook, or `undefined` until {@link discoverPostConstruct}.
224
- *
225
- * @remarks Callers treat unknown as "assume it does", so the first activation settles it.
226
- */
227
- knownPostConstruct(target: Constructor): boolean | undefined {
228
- return this.#hasPostConstruct?.get(target);
229
- }
230
-
231
- discoverPostConstruct(target: Constructor): void {
232
- const lifecycle = this.#reader.getLifecycleMetadata(target);
233
- (this.#hasPostConstruct ??= new WeakMap<Constructor, boolean>()).set(
234
- target,
235
- lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0,
236
- );
237
- }
238
-
239
- /** True when the class has accessor injection, which reads the container during construction. */
240
- needsActiveContainer(target: Constructor): boolean {
241
- let needsActiveContainer = this.#needsActiveContainer?.get(target);
242
- if (needsActiveContainer === undefined) {
243
- needsActiveContainer = (this.#reader.getAccessorMetadata?.(target)?.length ?? 0) > 0;
244
- (this.#needsActiveContainer ??= new WeakMap<Constructor, boolean>()).set(target, needsActiveContainer);
245
- }
246
- return needsActiveContainer;
247
- }
248
-
249
- instantiate(target: Constructor, deps: Array<unknown>, resolution?: AmbientResolution): unknown {
250
- const invokable = target as ConstructorInvocation;
251
- if (!this.needsActiveContainer(target)) {
252
- return new invokable(...deps);
253
- }
254
- return runWithAmbientResolution(this.#container, resolution, () => new invokable(...deps));
255
- }
256
- }