@codefast/di 0.5.0 → 0.6.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 (266) hide show
  1. package/CHANGELOG.md +615 -0
  2. package/README.md +119 -30
  3. package/dist/ambient/active-container.d.ts +37 -0
  4. package/dist/ambient/active-container.d.ts.map +1 -0
  5. package/dist/ambient/active-container.js +41 -0
  6. package/dist/ambient/active-container.js.map +1 -0
  7. package/dist/container/binding-builders.d.ts +12 -8
  8. package/dist/container/binding-builders.d.ts.map +1 -1
  9. package/dist/container/binding-builders.js +56 -31
  10. package/dist/container/binding-builders.js.map +1 -1
  11. package/dist/container/container.d.ts +33 -19
  12. package/dist/container/container.d.ts.map +1 -1
  13. package/dist/container/container.js +279 -118
  14. package/dist/container/container.js.map +1 -1
  15. package/dist/{resolution → core}/binding-scope.d.ts +2 -2
  16. package/dist/core/binding-scope.d.ts.map +1 -0
  17. package/dist/core/binding-scope.js.map +1 -0
  18. package/dist/{binding.d.ts → core/binding.d.ts} +70 -15
  19. package/dist/core/binding.d.ts.map +1 -0
  20. package/dist/{binding.js → core/binding.js} +27 -12
  21. package/dist/core/binding.js.map +1 -0
  22. package/dist/core/constraint-requirement.d.ts +38 -0
  23. package/dist/core/constraint-requirement.d.ts.map +1 -0
  24. package/dist/core/constraint-requirement.js +28 -0
  25. package/dist/core/constraint-requirement.js.map +1 -0
  26. package/dist/{constructor-type.d.ts → core/constructor-type.d.ts} +1 -1
  27. package/dist/core/constructor-type.d.ts.map +1 -0
  28. package/dist/core/constructor-type.js.map +1 -0
  29. package/dist/{module.d.ts → core/module.d.ts} +5 -5
  30. package/dist/core/module.d.ts.map +1 -0
  31. package/dist/{module.js → core/module.js} +4 -4
  32. package/dist/core/module.js.map +1 -0
  33. package/dist/{registry.d.ts → core/registry.d.ts} +27 -7
  34. package/dist/core/registry.d.ts.map +1 -0
  35. package/dist/{registry.js → core/registry.js} +125 -52
  36. package/dist/core/registry.js.map +1 -0
  37. package/dist/core/tag.d.ts +80 -0
  38. package/dist/core/tag.d.ts.map +1 -0
  39. package/dist/core/tag.js +84 -0
  40. package/dist/core/tag.js.map +1 -0
  41. package/dist/{token.d.ts → core/token.d.ts} +2 -6
  42. package/dist/core/token.d.ts.map +1 -0
  43. package/dist/core/token.js +13 -0
  44. package/dist/core/token.js.map +1 -0
  45. package/dist/{types.d.ts → core/types.d.ts} +27 -21
  46. package/dist/core/types.d.ts.map +1 -0
  47. package/dist/core/types.js.map +1 -0
  48. package/dist/decorators/inject.d.ts +5 -49
  49. package/dist/decorators/inject.d.ts.map +1 -1
  50. package/dist/decorators/inject.js +34 -104
  51. package/dist/decorators/inject.js.map +1 -1
  52. package/dist/decorators/injectable.d.ts +25 -5
  53. package/dist/decorators/injectable.d.ts.map +1 -1
  54. package/dist/decorators/injectable.js +2 -3
  55. package/dist/decorators/injectable.js.map +1 -1
  56. package/dist/decorators/lifecycle-decorators.d.ts +4 -2
  57. package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
  58. package/dist/decorators/lifecycle-decorators.js +19 -25
  59. package/dist/decorators/lifecycle-decorators.js.map +1 -1
  60. package/dist/{resolution → errors}/diagnostics.d.ts +2 -0
  61. package/dist/errors/diagnostics.d.ts.map +1 -0
  62. package/dist/errors/diagnostics.js.map +1 -0
  63. package/dist/{errors.d.ts → errors/errors.d.ts} +89 -4
  64. package/dist/errors/errors.d.ts.map +1 -0
  65. package/dist/{errors.js → errors/errors.js} +129 -7
  66. package/dist/errors/errors.js.map +1 -0
  67. package/dist/index.d.ts +21 -14
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +15 -7
  70. package/dist/index.js.map +1 -1
  71. package/dist/injection/descriptor.d.ts +79 -0
  72. package/dist/injection/descriptor.d.ts.map +1 -0
  73. package/dist/injection/descriptor.js +118 -0
  74. package/dist/injection/descriptor.js.map +1 -0
  75. package/dist/{resolution → injection}/resolve-options.d.ts +21 -11
  76. package/dist/injection/resolve-options.d.ts.map +1 -0
  77. package/dist/injection/resolve-options.js +101 -0
  78. package/dist/injection/resolve-options.js.map +1 -0
  79. package/dist/introspection/dependency-graph.d.ts +16 -5
  80. package/dist/introspection/dependency-graph.d.ts.map +1 -1
  81. package/dist/introspection/dependency-graph.js +138 -57
  82. package/dist/introspection/dependency-graph.js.map +1 -1
  83. package/dist/introspection/graph-adapters/cytoscape.d.ts +6 -3
  84. package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
  85. package/dist/introspection/graph-adapters/cytoscape.js +3 -0
  86. package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
  87. package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
  88. package/dist/introspection/graph-adapters/dot.js +12 -5
  89. package/dist/introspection/graph-adapters/dot.js.map +1 -1
  90. package/dist/introspection/graph-adapters/mermaid.d.ts +9 -0
  91. package/dist/introspection/graph-adapters/mermaid.d.ts.map +1 -0
  92. package/dist/introspection/graph-adapters/mermaid.js +47 -0
  93. package/dist/introspection/graph-adapters/mermaid.js.map +1 -0
  94. package/dist/introspection/graph-adapters/reactflow.d.ts +6 -3
  95. package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
  96. package/dist/introspection/graph-adapters/reactflow.js +5 -1
  97. package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
  98. package/dist/introspection/inspector.d.ts +4 -4
  99. package/dist/introspection/inspector.d.ts.map +1 -1
  100. package/dist/introspection/inspector.js +10 -6
  101. package/dist/introspection/inspector.js.map +1 -1
  102. package/dist/lifecycle/lifecycle-manager.d.ts +27 -0
  103. package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
  104. package/dist/{resolution/lifecycle.js → lifecycle/lifecycle-manager.js} +39 -9
  105. package/dist/lifecycle/lifecycle-manager.js.map +1 -0
  106. package/dist/lifecycle/scope-manager.d.ts +48 -0
  107. package/dist/lifecycle/scope-manager.d.ts.map +1 -0
  108. package/dist/lifecycle/scope-manager.js +135 -0
  109. package/dist/lifecycle/scope-manager.js.map +1 -0
  110. package/dist/metadata/metadata-reader-token.d.ts +1 -1
  111. package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
  112. package/dist/metadata/metadata-reader-token.js +1 -1
  113. package/dist/metadata/metadata-reader-token.js.map +1 -1
  114. package/dist/metadata/metadata-types.d.ts +9 -9
  115. package/dist/metadata/metadata-types.d.ts.map +1 -1
  116. package/dist/metadata/symbol-metadata-reader.d.ts +19 -6
  117. package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
  118. package/dist/metadata/symbol-metadata-reader.js +89 -9
  119. package/dist/metadata/symbol-metadata-reader.js.map +1 -1
  120. package/dist/metadata/verifying-metadata-reader.d.ts +16 -0
  121. package/dist/metadata/verifying-metadata-reader.d.ts.map +1 -0
  122. package/dist/metadata/verifying-metadata-reader.js +32 -0
  123. package/dist/metadata/verifying-metadata-reader.js.map +1 -0
  124. package/dist/resolution/{activation-need.d.ts → cache/activation-need.d.ts} +5 -5
  125. package/dist/resolution/cache/activation-need.d.ts.map +1 -0
  126. package/dist/resolution/cache/activation-need.js.map +1 -0
  127. package/dist/resolution/{binding-lookup-cache.d.ts → cache/binding-lookup-cache.d.ts} +6 -5
  128. package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
  129. package/dist/resolution/cache/binding-lookup-cache.js.map +1 -0
  130. package/dist/resolution/cache/class-introspector.d.ts +66 -0
  131. package/dist/resolution/cache/class-introspector.d.ts.map +1 -0
  132. package/dist/resolution/cache/class-introspector.js +194 -0
  133. package/dist/resolution/cache/class-introspector.js.map +1 -0
  134. package/dist/resolution/context.d.ts +84 -0
  135. package/dist/resolution/context.d.ts.map +1 -0
  136. package/dist/resolution/{environment.js → context.js} +54 -80
  137. package/dist/resolution/context.js.map +1 -0
  138. package/dist/resolution/path/resolution-path.d.ts +94 -0
  139. package/dist/resolution/path/resolution-path.d.ts.map +1 -0
  140. package/dist/resolution/path/resolution-path.js +117 -0
  141. package/dist/resolution/path/resolution-path.js.map +1 -0
  142. package/dist/resolution/{instantiation-plan.d.ts → plan/instantiation-plan.d.ts} +30 -7
  143. package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
  144. package/dist/resolution/plan/instantiation-plan.js +390 -0
  145. package/dist/resolution/plan/instantiation-plan.js.map +1 -0
  146. package/dist/resolution/resolver.d.ts +28 -28
  147. package/dist/resolution/resolver.d.ts.map +1 -1
  148. package/dist/resolution/resolver.js +552 -239
  149. package/dist/resolution/resolver.js.map +1 -1
  150. package/dist/resolution/select/binding-select.d.ts +34 -0
  151. package/dist/resolution/select/binding-select.d.ts.map +1 -0
  152. package/dist/resolution/select/binding-select.js +175 -0
  153. package/dist/resolution/select/binding-select.js.map +1 -0
  154. package/dist/resolution/{constraints.d.ts → select/constraints.d.ts} +13 -12
  155. package/dist/resolution/select/constraints.d.ts.map +1 -0
  156. package/dist/resolution/{constraints.js → select/constraints.js} +33 -22
  157. package/dist/resolution/select/constraints.js.map +1 -0
  158. package/package.json +160 -20
  159. package/src/ambient/active-container.ts +65 -0
  160. package/src/container/binding-builders.ts +72 -44
  161. package/src/container/container.ts +355 -170
  162. package/src/{resolution → core}/binding-scope.ts +2 -2
  163. package/src/{binding.ts → core/binding.ts} +101 -39
  164. package/src/core/constraint-requirement.ts +51 -0
  165. package/src/{constructor-type.ts → core/constructor-type.ts} +1 -1
  166. package/src/{module.ts → core/module.ts} +10 -10
  167. package/src/core/registry.ts +365 -0
  168. package/src/core/tag.ts +144 -0
  169. package/src/{token.ts → core/token.ts} +2 -14
  170. package/src/{types.ts → core/types.ts} +37 -34
  171. package/src/decorators/inject.ts +39 -175
  172. package/src/decorators/injectable.ts +40 -10
  173. package/src/decorators/lifecycle-decorators.ts +22 -34
  174. package/src/{resolution → errors}/diagnostics.ts +2 -0
  175. package/src/{errors.ts → errors/errors.ts} +152 -8
  176. package/src/index.ts +43 -15
  177. package/src/injection/descriptor.ts +206 -0
  178. package/src/injection/resolve-options.ts +144 -0
  179. package/src/introspection/dependency-graph.ts +223 -63
  180. package/src/introspection/graph-adapters/cytoscape.ts +9 -3
  181. package/src/introspection/graph-adapters/dot.ts +13 -5
  182. package/src/introspection/graph-adapters/mermaid.ts +61 -0
  183. package/src/introspection/graph-adapters/reactflow.ts +11 -4
  184. package/src/introspection/inspector.ts +16 -12
  185. package/src/{resolution/lifecycle.ts → lifecycle/lifecycle-manager.ts} +68 -32
  186. package/src/lifecycle/scope-manager.ts +156 -0
  187. package/src/metadata/metadata-reader-token.ts +2 -2
  188. package/src/metadata/metadata-types.ts +9 -9
  189. package/src/metadata/symbol-metadata-reader.ts +95 -18
  190. package/src/metadata/verifying-metadata-reader.ts +41 -0
  191. package/src/resolution/{activation-need.ts → cache/activation-need.ts} +8 -8
  192. package/src/resolution/{binding-lookup-cache.ts → cache/binding-lookup-cache.ts} +6 -5
  193. package/src/resolution/cache/class-introspector.ts +251 -0
  194. package/src/resolution/{environment.ts → context.ts} +84 -145
  195. package/src/resolution/path/resolution-path.ts +167 -0
  196. package/src/resolution/plan/instantiation-plan.ts +579 -0
  197. package/src/resolution/resolver.ts +635 -313
  198. package/src/resolution/select/binding-select.ts +210 -0
  199. package/src/resolution/select/constraints.ts +130 -0
  200. package/dist/binding.d.ts.map +0 -1
  201. package/dist/binding.js.map +0 -1
  202. package/dist/constructor-type.d.ts.map +0 -1
  203. package/dist/constructor-type.js.map +0 -1
  204. package/dist/errors.d.ts.map +0 -1
  205. package/dist/errors.js.map +0 -1
  206. package/dist/module.d.ts.map +0 -1
  207. package/dist/module.js.map +0 -1
  208. package/dist/registry.d.ts.map +0 -1
  209. package/dist/registry.js.map +0 -1
  210. package/dist/resolution/activation-need.d.ts.map +0 -1
  211. package/dist/resolution/activation-need.js.map +0 -1
  212. package/dist/resolution/binding-lookup-cache.d.ts.map +0 -1
  213. package/dist/resolution/binding-lookup-cache.js.map +0 -1
  214. package/dist/resolution/binding-scope.d.ts.map +0 -1
  215. package/dist/resolution/binding-scope.js.map +0 -1
  216. package/dist/resolution/binding-select.d.ts +0 -23
  217. package/dist/resolution/binding-select.d.ts.map +0 -1
  218. package/dist/resolution/binding-select.js +0 -121
  219. package/dist/resolution/binding-select.js.map +0 -1
  220. package/dist/resolution/class-introspector.d.ts +0 -27
  221. package/dist/resolution/class-introspector.d.ts.map +0 -1
  222. package/dist/resolution/class-introspector.js +0 -60
  223. package/dist/resolution/class-introspector.js.map +0 -1
  224. package/dist/resolution/constraints.d.ts.map +0 -1
  225. package/dist/resolution/constraints.js.map +0 -1
  226. package/dist/resolution/diagnostics.d.ts.map +0 -1
  227. package/dist/resolution/diagnostics.js.map +0 -1
  228. package/dist/resolution/environment.d.ts +0 -94
  229. package/dist/resolution/environment.d.ts.map +0 -1
  230. package/dist/resolution/environment.js.map +0 -1
  231. package/dist/resolution/instantiation-plan.d.ts.map +0 -1
  232. package/dist/resolution/instantiation-plan.js +0 -183
  233. package/dist/resolution/instantiation-plan.js.map +0 -1
  234. package/dist/resolution/lifecycle.d.ts +0 -23
  235. package/dist/resolution/lifecycle.d.ts.map +0 -1
  236. package/dist/resolution/lifecycle.js.map +0 -1
  237. package/dist/resolution/resolution-path.d.ts +0 -98
  238. package/dist/resolution/resolution-path.d.ts.map +0 -1
  239. package/dist/resolution/resolution-path.js +0 -98
  240. package/dist/resolution/resolution-path.js.map +0 -1
  241. package/dist/resolution/resolve-options.d.ts.map +0 -1
  242. package/dist/resolution/resolve-options.js +0 -56
  243. package/dist/resolution/resolve-options.js.map +0 -1
  244. package/dist/resolution/scope.d.ts +0 -28
  245. package/dist/resolution/scope.d.ts.map +0 -1
  246. package/dist/resolution/scope.js +0 -88
  247. package/dist/resolution/scope.js.map +0 -1
  248. package/dist/token.d.ts.map +0 -1
  249. package/dist/token.js +0 -22
  250. package/dist/token.js.map +0 -1
  251. package/dist/types.d.ts.map +0 -1
  252. package/dist/types.js.map +0 -1
  253. package/src/registry.ts +0 -282
  254. package/src/resolution/binding-select.ts +0 -154
  255. package/src/resolution/class-introspector.ts +0 -74
  256. package/src/resolution/constraints.ts +0 -121
  257. package/src/resolution/instantiation-plan.ts +0 -292
  258. package/src/resolution/resolution-path.ts +0 -156
  259. package/src/resolution/resolve-options.ts +0 -89
  260. package/src/resolution/scope.ts +0 -105
  261. /package/dist/{resolution → core}/binding-scope.js +0 -0
  262. /package/dist/{constructor-type.js → core/constructor-type.js} +0 -0
  263. /package/dist/{types.js → core/types.js} +0 -0
  264. /package/dist/{resolution → errors}/diagnostics.js +0 -0
  265. /package/dist/resolution/{activation-need.js → cache/activation-need.js} +0 -0
  266. /package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +0 -0
@@ -1,154 +0,0 @@
1
- import type { Binding, BindingSlot } from "#/binding";
2
- import { AmbiguousBindingError } from "#/errors";
3
- import type { BindingTag, ConstraintContext, ResolveOptions } from "#/types";
4
-
5
- /**
6
- * Select a single candidate from a list of bindings using slot matching + predicates.
7
- * Returns undefined if no match, throws AmbiguousBindingError if multiple match.
8
- *
9
- * @since 0.3.16-canary.0
10
- */
11
- export function selectBinding(
12
- bindings: ReadonlyArray<Binding>,
13
- options: ResolveOptions | undefined,
14
- ctx: ConstraintContext,
15
- tokenDisplayName: string,
16
- ): Binding | undefined {
17
- const candidates = filterBindings(bindings, options, ctx);
18
- if (candidates.length === 0) {
19
- return undefined;
20
- }
21
- if (candidates.length === 1) {
22
- return candidates[0];
23
- }
24
- // Most specific wins: a single matching predicate-carrying candidate beats
25
- // predicate-less ones (a predicate is a deliberate specialization of the
26
- // default). Two matching predicates are genuinely ambiguous.
27
- let predicatedCandidate: Binding | undefined;
28
- for (const candidate of candidates) {
29
- if (candidate.predicate !== undefined) {
30
- if (predicatedCandidate !== undefined) {
31
- predicatedCandidate = undefined;
32
- break;
33
- }
34
- predicatedCandidate = candidate;
35
- }
36
- }
37
- if (predicatedCandidate !== undefined) {
38
- return predicatedCandidate;
39
- }
40
- throw new AmbiguousBindingError(
41
- tokenDisplayName,
42
- candidates.map((c) => c.id),
43
- );
44
- }
45
-
46
- /**
47
- * Select all candidates matching options + predicates.
48
- *
49
- * @since 0.3.16-canary.0
50
- */
51
- export function selectAllBindings(
52
- bindings: ReadonlyArray<Binding>,
53
- options: ResolveOptions | undefined,
54
- ctx: ConstraintContext,
55
- ): Array<Binding> {
56
- return filterBindings(bindings, options, ctx, "all");
57
- }
58
-
59
- function filterBindings(
60
- bindings: ReadonlyArray<Binding>,
61
- options: ResolveOptions | undefined,
62
- ctx: ConstraintContext,
63
- selectionMode: "single" | "all" = "single",
64
- ): Array<Binding> {
65
- // `resolveAll` with no slot criterion takes every binding; `resolve` always matches the slot,
66
- // where an absent criterion means "the default slot".
67
- const requiresSlotMatch = selectionMode === "single" || (options !== undefined && hasSlotCriterion(options));
68
- const result: Array<Binding> = [];
69
- for (const binding of bindings) {
70
- if ((!requiresSlotMatch || matchesSlot(binding.slot, options)) && matchesPredicate(binding, ctx)) {
71
- result.push(binding);
72
- }
73
- }
74
- return result;
75
- }
76
-
77
- function hasSlotCriterion(options: ResolveOptions): boolean {
78
- return options.name !== undefined || options.tag !== undefined || (options.tags?.length ?? 0) > 0;
79
- }
80
-
81
- /**
82
- * Whether a binding's slot satisfies a request: names must be equal, and every tag the slot
83
- * declares must be among the tags requested (SPEC §6.9).
84
- *
85
- * @since 0.5.0-canary.9
86
- */
87
- export function matchesSlot(slot: BindingSlot, options: ResolveOptions | undefined): boolean {
88
- const requestedName = options?.name;
89
- const requestedTags = options?.tags;
90
- const singleRequestedTag = options?.tag;
91
- const hasRequestedTags = (requestedTags?.length ?? 0) > 0 || singleRequestedTag !== undefined;
92
-
93
- // Match by name
94
- if (slot.name !== undefined) {
95
- if (requestedName === undefined) {
96
- return false;
97
- }
98
- if (slot.name !== requestedName) {
99
- return false;
100
- }
101
- } else if (requestedName !== undefined) {
102
- // Binding has no name but options requests a specific name — no match
103
- return false;
104
- }
105
-
106
- // Match by tags — binding's tags must all be present in options
107
- if (slot.tags.length > 0) {
108
- if (!hasRequestedTags) {
109
- return false;
110
- }
111
- for (const [tagKey, tagValue] of slot.tags) {
112
- if (!matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag)) {
113
- return false;
114
- }
115
- }
116
- } else if (hasRequestedTags) {
117
- // Requested tags require a tagged slot: an untagged binding never matches (SPEC §6.9).
118
- return false;
119
- }
120
-
121
- return true;
122
- }
123
-
124
- function matchesRequestedTag(
125
- tagKey: string,
126
- tagValue: unknown,
127
- requestedTags: ReadonlyArray<BindingTag> | undefined,
128
- singleRequestedTag: BindingTag | undefined,
129
- ): boolean {
130
- if (
131
- singleRequestedTag !== undefined &&
132
- singleRequestedTag[0] === tagKey &&
133
- Object.is(singleRequestedTag[1], tagValue)
134
- ) {
135
- return true;
136
- }
137
- if (requestedTags === undefined || requestedTags.length === 0) {
138
- return false;
139
- }
140
- for (let index = 0; index < requestedTags.length; index += 1) {
141
- const requestedTag = requestedTags[index]!;
142
- if (requestedTag[0] === tagKey && Object.is(requestedTag[1], tagValue)) {
143
- return true;
144
- }
145
- }
146
- return false;
147
- }
148
-
149
- function matchesPredicate(binding: Binding, ctx: ConstraintContext): boolean {
150
- if (binding.predicate === undefined) {
151
- return true;
152
- }
153
- return binding.predicate(ctx);
154
- }
@@ -1,74 +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 type { ConstructorInvocation } from "#/constructor-type";
8
- import type { Container } from "#/container/container";
9
- import type { ConstructorMetadata, MetadataReader } from "#/metadata/metadata-types";
10
- import { runWithContainer } from "#/resolution/environment";
11
- import type { Constructor } from "#/types";
12
-
13
- /**
14
- * @since 0.5.0-canary.8
15
- */
16
- export class ClassIntrospector {
17
- // Unallocated until the container resolves its first class binding — a container bound entirely
18
- // to constants, factories or aliases never introspects one.
19
- #constructorMetadata: WeakMap<Constructor, ConstructorMetadata | null> | undefined;
20
- #hasPostConstruct: WeakMap<Constructor, boolean> | undefined;
21
- #needsActiveContainer: WeakMap<Constructor, boolean> | undefined;
22
- readonly #reader: MetadataReader;
23
- readonly #container: Container;
24
-
25
- constructor(reader: MetadataReader, container: Container) {
26
- this.#reader = reader;
27
- this.#container = container;
28
- }
29
-
30
- constructorMetadata(target: Constructor): ConstructorMetadata | undefined {
31
- const cached = this.#constructorMetadata?.get(target);
32
- if (cached !== undefined) {
33
- return cached === null ? undefined : cached;
34
- }
35
- const metadata = this.#reader.getConstructorMetadata(target);
36
- (this.#constructorMetadata ??= new WeakMap()).set(target, metadata ?? null);
37
- return metadata;
38
- }
39
-
40
- /**
41
- * Whether the class has a `@postConstruct` hook, or `undefined` until {@link discoverPostConstruct}.
42
- *
43
- * @remarks Callers treat unknown as "assume it does", so the first activation settles it.
44
- */
45
- knownPostConstruct(target: Constructor): boolean | undefined {
46
- return this.#hasPostConstruct?.get(target);
47
- }
48
-
49
- discoverPostConstruct(target: Constructor): void {
50
- const lifecycle = this.#reader.getLifecycleMetadata(target);
51
- (this.#hasPostConstruct ??= new WeakMap()).set(
52
- target,
53
- lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0,
54
- );
55
- }
56
-
57
- /** True when the class has accessor injection, which reads the container during construction. */
58
- needsActiveContainer(target: Constructor): boolean {
59
- let needsActiveContainer = this.#needsActiveContainer?.get(target);
60
- if (needsActiveContainer === undefined) {
61
- needsActiveContainer = (this.#reader.getAccessorMetadata?.(target)?.length ?? 0) > 0;
62
- (this.#needsActiveContainer ??= new WeakMap()).set(target, needsActiveContainer);
63
- }
64
- return needsActiveContainer;
65
- }
66
-
67
- instantiate(target: Constructor, deps: Array<unknown>): unknown {
68
- const invokable = target as ConstructorInvocation;
69
- if (!this.needsActiveContainer(target)) {
70
- return new invokable(...deps);
71
- }
72
- return runWithContainer(this.#container, () => new invokable(...deps));
73
- }
74
- }
@@ -1,121 +0,0 @@
1
- import type { Token } from "#/token";
2
- import { tokenName } from "#/token";
3
- import type { BindingTag, ConstraintContext, Constructor } from "#/types";
4
-
5
- function tokenNameOf(token: Token<unknown> | Constructor): string {
6
- return tokenName(token);
7
- }
8
-
9
- /**
10
- * @since 0.3.16-canary.0
11
- */
12
- export function whenParentIs(token: Token<unknown> | Constructor): (constraintContext: ConstraintContext) => boolean {
13
- const tokenDisplayName = tokenNameOf(token);
14
- return (constraintContext) =>
15
- constraintContext.parent !== undefined && constraintContext.parent.tokenName === tokenDisplayName;
16
- }
17
-
18
- /**
19
- * @since 0.3.16-canary.0
20
- */
21
- export function whenNoParentIs(token: Token<unknown> | Constructor): (constraintContext: ConstraintContext) => boolean {
22
- const tokenDisplayName = tokenNameOf(token);
23
- return (constraintContext) =>
24
- constraintContext.parent === undefined || constraintContext.parent.tokenName !== tokenDisplayName;
25
- }
26
-
27
- /**
28
- * @since 0.3.16-canary.0
29
- */
30
- export function whenAnyAncestorIs(
31
- token: Token<unknown> | Constructor,
32
- ): (constraintContext: ConstraintContext) => boolean {
33
- const tokenDisplayName = tokenNameOf(token);
34
- return (constraintContext) =>
35
- constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.tokenName === tokenDisplayName);
36
- }
37
-
38
- /**
39
- * @since 0.3.16-canary.0
40
- */
41
- export function whenNoAncestorIs(
42
- token: Token<unknown> | Constructor,
43
- ): (constraintContext: ConstraintContext) => boolean {
44
- const tokenDisplayName = tokenNameOf(token);
45
- return (constraintContext) =>
46
- constraintContext.ancestors.every((ancestorFrame) => ancestorFrame.tokenName !== tokenDisplayName);
47
- }
48
-
49
- /**
50
- * @since 0.3.16-canary.0
51
- */
52
- export function whenParentNamed(name: string): (constraintContext: ConstraintContext) => boolean {
53
- return (constraintContext) => constraintContext.parent !== undefined && constraintContext.parent.slot.name === name;
54
- }
55
-
56
- /**
57
- * @since 0.3.16-canary.0
58
- */
59
- export function whenAnyAncestorNamed(name: string): (constraintContext: ConstraintContext) => boolean {
60
- return (constraintContext) => constraintContext.ancestors.some((ancestorFrame) => ancestorFrame.slot.name === name);
61
- }
62
-
63
- /**
64
- * @since 0.3.16-canary.0
65
- */
66
- export function whenParentTagged(tag: string, value: unknown): (constraintContext: ConstraintContext) => boolean {
67
- return (constraintContext) =>
68
- constraintContext.parent !== undefined &&
69
- constraintContext.parent.slot.tags.some(([tagKey, tagValue]) => tagKey === tag && Object.is(tagValue, value));
70
- }
71
-
72
- /**
73
- * @since 0.3.16-canary.0
74
- */
75
- export function whenAnyAncestorTagged(tag: string, value: unknown): (constraintContext: ConstraintContext) => boolean {
76
- return (constraintContext) =>
77
- constraintContext.ancestors.some((ancestorFrame) =>
78
- ancestorFrame.slot.tags.some(([tagKey, tagValue]) => tagKey === tag && Object.is(tagValue, value)),
79
- );
80
- }
81
-
82
- /**
83
- * Matches when the direct parent slot carries **all** of the given tag pairs.
84
- * Equivalent to AND-composing multiple `whenParentTagged` calls but evaluates
85
- * in a single predicate invocation — no intermediate closure allocations.
86
- *
87
- * @since 0.3.16-canary.1
88
- */
89
- export function whenParentTaggedAll(
90
- tags: ReadonlyArray<BindingTag>,
91
- ): (constraintContext: ConstraintContext) => boolean {
92
- return (constraintContext) => {
93
- const { parent } = constraintContext;
94
- if (parent === undefined) {
95
- return false;
96
- }
97
- const { tags: parentTags } = parent.slot;
98
- return tags.every(([tagKey, tagValue]) =>
99
- parentTags.some(([otherKey, otherValue]) => otherKey === tagKey && Object.is(otherValue, tagValue)),
100
- );
101
- };
102
- }
103
-
104
- /**
105
- * Matches when at least one ancestor slot carries **all** of the given tag pairs.
106
- * Equivalent to AND-composing multiple `whenAnyAncestorTagged` calls but evaluates
107
- * in a single predicate invocation — no intermediate closure allocations.
108
- *
109
- * @since 0.3.16-canary.1
110
- */
111
- export function whenAnyAncestorTaggedAll(
112
- tags: ReadonlyArray<BindingTag>,
113
- ): (constraintContext: ConstraintContext) => boolean {
114
- return (constraintContext) =>
115
- constraintContext.ancestors.some((frame) => {
116
- const { tags: frameTags } = frame.slot;
117
- return tags.every(([tagKey, tagValue]) =>
118
- frameTags.some(([otherKey, otherValue]) => otherKey === tagKey && Object.is(otherValue, tagValue)),
119
- );
120
- });
121
- }
@@ -1,292 +0,0 @@
1
- /**
2
- * Compiles a transient class or resolved-factory binding into a nested-constructor closure.
3
- *
4
- * @see `ARCHITECTURE.md` — the escape contract every dependency the compiler cannot inline must honour.
5
- */
6
- import type { Binding } from "#/binding";
7
- import { NO_INSTANCE } from "#/binding";
8
- import type { ConstructorInvocation } from "#/constructor-type";
9
- import { AsyncResolutionError } from "#/errors";
10
- import type { ConstructorMetadata } from "#/metadata/metadata-types";
11
- import type { DependencySlot } from "#/resolution/resolve-options";
12
- import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
13
- import type { Token } from "#/token";
14
- import { tokenName } from "#/token";
15
- import type { Constructor, ResolutionFrame, ResolveOptions } from "#/types";
16
-
17
- // Past this depth a dependency escapes to the runtime path rather than inlining further —
18
- // compiled closures nest one JS frame per level, and pathological graphs are the runtime's job.
19
- const PLAN_DEPTH_LIMIT = 32;
20
-
21
- /**
22
- * Compilation asked to retry later (class lifecycle metadata not discovered yet).
23
- *
24
- * @since 0.5.0-canary.7
25
- */
26
- export const PLAN_RETRY: unique symbol = Symbol("di:plan-retry");
27
-
28
- /**
29
- * A compiled plan, `null` for "not plannable under the current cache versions",
30
- * or {@link PLAN_RETRY} when a first runtime resolve must discover metadata first.
31
- *
32
- * @since 0.5.0-canary.7
33
- */
34
- export type InstantiationPlanCompileResult = (() => unknown) | null | typeof PLAN_RETRY;
35
-
36
- /**
37
- * What compiling one *dependency* can yield.
38
- *
39
- * @remarks No `null`: a dependency escapes rather than failing, so "no plan" is only ever a
40
- * verdict on a plan's root.
41
- */
42
- type DependencyCompileResult = (() => unknown) | typeof PLAN_RETRY;
43
-
44
- /**
45
- * A dependency's terminal binding — all a compiled thunk needs.
46
- *
47
- * @since 0.5.0-canary.7
48
- */
49
- export interface InstantiationPlanDependencyEntry {
50
- readonly binding: Binding;
51
- }
52
-
53
- /**
54
- * Everything the compiler needs from its resolver, expressed as behavior so the
55
- * compiler stays independently testable and free of resolver internals.
56
- *
57
- * @since 0.5.0-canary.7
58
- */
59
- export interface InstantiationPlanHost {
60
- hasActivationHandlers(token: Token<unknown> | Constructor): boolean;
61
- /** Cached postConstruct presence — `undefined` until a runtime resolve discovers it. */
62
- knownPostConstruct(target: Constructor): boolean | undefined;
63
- needsActiveContainer(target: Constructor): boolean;
64
- getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
65
- /** Options-less lookup with alias hops folded; `null` when the fast lane can't answer. */
66
- lookupDependencyEntry(token: Token<unknown> | Constructor): InstantiationPlanDependencyEntry | null;
67
- /** The frame the interpreted path pushes for this binding, so escapes can replay it. */
68
- getResolutionFrame(binding: Binding): ResolutionFrame;
69
- /** Runtime resolve for an escaped dependency, seeded with the ancestors above it. */
70
- resolveEscaped(
71
- token: Token<unknown> | Constructor,
72
- options: ResolveOptions | undefined,
73
- arity: EscapeArity,
74
- resolutionPath: Array<string>,
75
- resolutionStack: Array<ResolutionFrame>,
76
- ): unknown;
77
- }
78
-
79
- /**
80
- * Which resolve an escaped dependency replays — mirrors how the interpreted path dispatches
81
- * a constructor param.
82
- *
83
- * @since 0.5.0-canary.8
84
- */
85
- export type EscapeArity = "all" | "optional" | "single";
86
-
87
- /**
88
- * @since 0.3.16-canary.1
89
- */
90
- export class InstantiationPlanCompiler {
91
- readonly #host: InstantiationPlanHost;
92
-
93
- constructor(host: InstantiationPlanHost) {
94
- this.#host = host;
95
- }
96
-
97
- compile(binding: Binding & { kind: "class" | "resolved" }): InstantiationPlanCompileResult {
98
- return binding.kind === "class"
99
- ? this.#compileClassPlan(binding, new Set(), 0, [])
100
- : this.#compileResolvedPlan(binding, new Set(), 0, []);
101
- }
102
-
103
- /**
104
- * Re-entry into the runtime resolver for a dependency the plan can't see through.
105
- *
106
- * The ancestors are fixed at compile time, so the seeds are built once; each call copies
107
- * them because the resolver pushes and pops on the arrays it is given.
108
- */
109
- #compileEscapeThunk(
110
- token: Token<unknown> | Constructor,
111
- ancestors: ReadonlyArray<Binding>,
112
- arity: EscapeArity = "single",
113
- options?: ResolveOptions,
114
- ): () => unknown {
115
- const host = this.#host;
116
- const frames = ancestors.map((ancestor) => host.getResolutionFrame(ancestor));
117
- const names = frames.map((frame) => frame.tokenName);
118
- return () => host.resolveEscaped(token, options, arity, [...names], [...frames]);
119
- }
120
-
121
- // A resolved binding declares its deps as explicit descriptors — same rules as
122
- // class params, with the factory call (and its sync-only check) in place of `new`.
123
- #compileResolvedPlan(
124
- binding: Binding & { kind: "resolved" },
125
- compileStack: Set<Binding["id"]>,
126
- depth: number,
127
- ancestors: ReadonlyArray<Binding>,
128
- ): InstantiationPlanCompileResult {
129
- if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding.token)) {
130
- return null;
131
- }
132
- const factory = binding.factory;
133
- const tokenDisplayName = tokenName(binding.token);
134
- const depThunks = new Array<() => unknown>(binding.deps.length);
135
- const depAncestors = [...ancestors, binding];
136
- compileStack.add(binding.id);
137
- try {
138
- for (let index = 0; index < binding.deps.length; index += 1) {
139
- const thunk = this.#compileInjectionThunk(binding.deps[index]!, compileStack, depth, depAncestors);
140
- if (thunk === PLAN_RETRY) {
141
- return thunk;
142
- }
143
- depThunks[index] = thunk;
144
- }
145
- } finally {
146
- compileStack.delete(binding.id);
147
- }
148
- return () => {
149
- const factoryResult = factory(...depThunks.map((thunk) => thunk()));
150
- if (factoryResult instanceof Promise) {
151
- throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
152
- }
153
- return factoryResult;
154
- };
155
- }
156
-
157
- /**
158
- * One dependency of a plan node — a constructor param or a `toResolved` descriptor.
159
- *
160
- * @remarks Anything but a plain required single dependency escapes to the runtime path.
161
- */
162
- #compileInjectionThunk(
163
- descriptor: DependencySlot,
164
- compileStack: Set<Binding["id"]>,
165
- depth: number,
166
- ancestors: ReadonlyArray<Binding>,
167
- ): DependencyCompileResult {
168
- const token = descriptor.token;
169
- const options = injectionSlotToResolveOptions(descriptor);
170
- if (descriptor.multi) {
171
- return this.#compileEscapeThunk(token, ancestors, "all", options);
172
- }
173
- if (descriptor.optional) {
174
- return this.#compileEscapeThunk(token, ancestors, "optional", options);
175
- }
176
- if (options !== undefined) {
177
- return this.#compileEscapeThunk(token, ancestors, "single", options);
178
- }
179
- const entry = this.#host.lookupDependencyEntry(token);
180
- if (entry === null) {
181
- return this.#compileEscapeThunk(token, ancestors);
182
- }
183
- return this.#compileDepThunk(entry, compileStack, depth, ancestors);
184
- }
185
-
186
- #compileClassPlan(
187
- binding: Binding & { kind: "class" },
188
- compileStack: Set<Binding["id"]>,
189
- depth: number,
190
- ancestors: ReadonlyArray<Binding>,
191
- ): InstantiationPlanCompileResult {
192
- if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding.token)) {
193
- return null;
194
- }
195
- const target = binding.target;
196
- const hasPostConstruct = this.#host.knownPostConstruct(target);
197
- if (hasPostConstruct === undefined) {
198
- return PLAN_RETRY;
199
- }
200
- if (hasPostConstruct || this.#host.needsActiveContainer(target)) {
201
- return null;
202
- }
203
- const invokable = target as ConstructorInvocation;
204
- const meta = this.#host.getConstructorMetadata(target);
205
- if (meta === undefined) {
206
- // Metadata-less classes with required params throw on the runtime path — keep them there.
207
- return target.length === 0 ? () => new invokable() : null;
208
- }
209
- const params = meta.params;
210
- if (params.length === 0) {
211
- return () => new invokable();
212
- }
213
- const depThunks = new Array<() => unknown>(params.length);
214
- const depAncestors = [...ancestors, binding];
215
- compileStack.add(binding.id);
216
- try {
217
- for (let index = 0; index < params.length; index += 1) {
218
- const thunk = this.#compileInjectionThunk(params[index]!, compileStack, depth, depAncestors);
219
- if (thunk === PLAN_RETRY) {
220
- return thunk;
221
- }
222
- depThunks[index] = thunk;
223
- }
224
- } finally {
225
- compileStack.delete(binding.id);
226
- }
227
- switch (depThunks.length) {
228
- case 1: {
229
- const dep0 = depThunks[0]!;
230
- return () => new invokable(dep0());
231
- }
232
- case 2: {
233
- const dep0 = depThunks[0]!;
234
- const dep1 = depThunks[1]!;
235
- return () => new invokable(dep0(), dep1());
236
- }
237
- case 3: {
238
- const dep0 = depThunks[0]!;
239
- const dep1 = depThunks[1]!;
240
- const dep2 = depThunks[2]!;
241
- return () => new invokable(dep0(), dep1(), dep2());
242
- }
243
- default:
244
- return () => new invokable(...depThunks.map((thunk) => thunk()));
245
- }
246
- }
247
-
248
- #compileDepThunk(
249
- entry: InstantiationPlanDependencyEntry,
250
- compileStack: Set<Binding["id"]>,
251
- depth: number,
252
- ancestors: ReadonlyArray<Binding>,
253
- ): DependencyCompileResult {
254
- const { binding } = entry;
255
- if (binding.kind === "constant" && binding.onActivation === undefined) {
256
- if (!this.#host.hasActivationHandlers(binding.token)) {
257
- const value = binding.value;
258
- return () => value;
259
- }
260
- }
261
- const scope = binding.scope;
262
- if (scope === "singleton") {
263
- // Cached-singleton read; the first materialization escapes so it sees the same ancestors
264
- // (and therefore the same cycle detection) the interpreted path would have built.
265
- const escape = this.#compileEscapeThunk(binding.token, ancestors);
266
- const singletonBinding = binding;
267
- return () => {
268
- const cached = singletonBinding.instance;
269
- return cached === NO_INSTANCE ? escape() : cached;
270
- };
271
- }
272
- if (
273
- scope === "transient" &&
274
- binding.kind === "class" &&
275
- depth < PLAN_DEPTH_LIMIT &&
276
- !compileStack.has(binding.id)
277
- ) {
278
- const inlined = this.#compileClassPlan(
279
- binding as Binding & { kind: "class" },
280
- compileStack,
281
- depth + 1,
282
- ancestors,
283
- );
284
- if (inlined !== null) {
285
- return inlined;
286
- }
287
- }
288
- // Anything opaque — a factory, a scoped binding, an activation hook, a class the compiler
289
- // declined — runs on the runtime path, seeded with this plan's ancestors.
290
- return this.#compileEscapeThunk(binding.token, ancestors);
291
- }
292
- }