@codefast/di 0.5.0 → 0.6.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 (266) hide show
  1. package/CHANGELOG.md +712 -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 +62 -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 +281 -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 +53 -0
  23. package/dist/core/constraint-requirement.d.ts.map +1 -0
  24. package/dist/core/constraint-requirement.js +62 -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 +96 -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} +9 -5
  128. package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
  129. package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +55 -0
  130. package/dist/resolution/cache/binding-lookup-cache.js.map +1 -0
  131. package/dist/resolution/cache/class-introspector.d.ts +66 -0
  132. package/dist/resolution/cache/class-introspector.d.ts.map +1 -0
  133. package/dist/resolution/cache/class-introspector.js +194 -0
  134. package/dist/resolution/cache/class-introspector.js.map +1 -0
  135. package/dist/resolution/context.d.ts +84 -0
  136. package/dist/resolution/context.d.ts.map +1 -0
  137. package/dist/resolution/{environment.js → context.js} +54 -80
  138. package/dist/resolution/context.js.map +1 -0
  139. package/dist/resolution/path/resolution-path.d.ts +94 -0
  140. package/dist/resolution/path/resolution-path.d.ts.map +1 -0
  141. package/dist/resolution/path/resolution-path.js +117 -0
  142. package/dist/resolution/path/resolution-path.js.map +1 -0
  143. package/dist/resolution/{instantiation-plan.d.ts → plan/instantiation-plan.d.ts} +37 -7
  144. package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
  145. package/dist/resolution/plan/instantiation-plan.js +419 -0
  146. package/dist/resolution/plan/instantiation-plan.js.map +1 -0
  147. package/dist/resolution/resolver.d.ts +28 -28
  148. package/dist/resolution/resolver.d.ts.map +1 -1
  149. package/dist/resolution/resolver.js +581 -239
  150. package/dist/resolution/resolver.js.map +1 -1
  151. package/dist/resolution/select/binding-select.d.ts +34 -0
  152. package/dist/resolution/select/binding-select.d.ts.map +1 -0
  153. package/dist/resolution/select/binding-select.js +175 -0
  154. package/dist/resolution/select/binding-select.js.map +1 -0
  155. package/dist/resolution/{constraints.d.ts → select/constraints.d.ts} +13 -12
  156. package/dist/resolution/select/constraints.d.ts.map +1 -0
  157. package/dist/resolution/{constraints.js → select/constraints.js} +33 -22
  158. package/dist/resolution/select/constraints.js.map +1 -0
  159. package/package.json +160 -20
  160. package/src/ambient/active-container.ts +65 -0
  161. package/src/container/binding-builders.ts +79 -44
  162. package/src/container/container.ts +357 -170
  163. package/src/{resolution → core}/binding-scope.ts +2 -2
  164. package/src/{binding.ts → core/binding.ts} +101 -39
  165. package/src/core/constraint-requirement.ts +94 -0
  166. package/src/{constructor-type.ts → core/constructor-type.ts} +1 -1
  167. package/src/{module.ts → core/module.ts} +10 -10
  168. package/src/core/registry.ts +365 -0
  169. package/src/core/tag.ts +158 -0
  170. package/src/{token.ts → core/token.ts} +2 -14
  171. package/src/{types.ts → core/types.ts} +37 -34
  172. package/src/decorators/inject.ts +39 -175
  173. package/src/decorators/injectable.ts +40 -10
  174. package/src/decorators/lifecycle-decorators.ts +22 -34
  175. package/src/{resolution → errors}/diagnostics.ts +2 -0
  176. package/src/{errors.ts → errors/errors.ts} +152 -8
  177. package/src/index.ts +43 -15
  178. package/src/injection/descriptor.ts +206 -0
  179. package/src/injection/resolve-options.ts +144 -0
  180. package/src/introspection/dependency-graph.ts +223 -63
  181. package/src/introspection/graph-adapters/cytoscape.ts +9 -3
  182. package/src/introspection/graph-adapters/dot.ts +13 -5
  183. package/src/introspection/graph-adapters/mermaid.ts +61 -0
  184. package/src/introspection/graph-adapters/reactflow.ts +11 -4
  185. package/src/introspection/inspector.ts +16 -12
  186. package/src/{resolution/lifecycle.ts → lifecycle/lifecycle-manager.ts} +68 -32
  187. package/src/lifecycle/scope-manager.ts +156 -0
  188. package/src/metadata/metadata-reader-token.ts +2 -2
  189. package/src/metadata/metadata-types.ts +9 -9
  190. package/src/metadata/symbol-metadata-reader.ts +95 -18
  191. package/src/metadata/verifying-metadata-reader.ts +41 -0
  192. package/src/resolution/{activation-need.ts → cache/activation-need.ts} +8 -8
  193. package/src/resolution/{binding-lookup-cache.ts → cache/binding-lookup-cache.ts} +63 -5
  194. package/src/resolution/cache/class-introspector.ts +251 -0
  195. package/src/resolution/{environment.ts → context.ts} +84 -145
  196. package/src/resolution/path/resolution-path.ts +167 -0
  197. package/src/resolution/plan/instantiation-plan.ts +615 -0
  198. package/src/resolution/resolver.ts +663 -313
  199. package/src/resolution/select/binding-select.ts +210 -0
  200. package/src/resolution/select/constraints.ts +130 -0
  201. package/dist/binding.d.ts.map +0 -1
  202. package/dist/binding.js.map +0 -1
  203. package/dist/constructor-type.d.ts.map +0 -1
  204. package/dist/constructor-type.js.map +0 -1
  205. package/dist/errors.d.ts.map +0 -1
  206. package/dist/errors.js.map +0 -1
  207. package/dist/module.d.ts.map +0 -1
  208. package/dist/module.js.map +0 -1
  209. package/dist/registry.d.ts.map +0 -1
  210. package/dist/registry.js.map +0 -1
  211. package/dist/resolution/activation-need.d.ts.map +0 -1
  212. package/dist/resolution/activation-need.js.map +0 -1
  213. package/dist/resolution/binding-lookup-cache.d.ts.map +0 -1
  214. package/dist/resolution/binding-lookup-cache.js.map +0 -1
  215. package/dist/resolution/binding-scope.d.ts.map +0 -1
  216. package/dist/resolution/binding-scope.js.map +0 -1
  217. package/dist/resolution/binding-select.d.ts +0 -23
  218. package/dist/resolution/binding-select.d.ts.map +0 -1
  219. package/dist/resolution/binding-select.js +0 -121
  220. package/dist/resolution/binding-select.js.map +0 -1
  221. package/dist/resolution/class-introspector.d.ts +0 -27
  222. package/dist/resolution/class-introspector.d.ts.map +0 -1
  223. package/dist/resolution/class-introspector.js +0 -60
  224. package/dist/resolution/class-introspector.js.map +0 -1
  225. package/dist/resolution/constraints.d.ts.map +0 -1
  226. package/dist/resolution/constraints.js.map +0 -1
  227. package/dist/resolution/diagnostics.d.ts.map +0 -1
  228. package/dist/resolution/diagnostics.js.map +0 -1
  229. package/dist/resolution/environment.d.ts +0 -94
  230. package/dist/resolution/environment.d.ts.map +0 -1
  231. package/dist/resolution/environment.js.map +0 -1
  232. package/dist/resolution/instantiation-plan.d.ts.map +0 -1
  233. package/dist/resolution/instantiation-plan.js +0 -183
  234. package/dist/resolution/instantiation-plan.js.map +0 -1
  235. package/dist/resolution/lifecycle.d.ts +0 -23
  236. package/dist/resolution/lifecycle.d.ts.map +0 -1
  237. package/dist/resolution/lifecycle.js.map +0 -1
  238. package/dist/resolution/resolution-path.d.ts +0 -98
  239. package/dist/resolution/resolution-path.d.ts.map +0 -1
  240. package/dist/resolution/resolution-path.js +0 -98
  241. package/dist/resolution/resolution-path.js.map +0 -1
  242. package/dist/resolution/resolve-options.d.ts.map +0 -1
  243. package/dist/resolution/resolve-options.js +0 -56
  244. package/dist/resolution/resolve-options.js.map +0 -1
  245. package/dist/resolution/scope.d.ts +0 -28
  246. package/dist/resolution/scope.d.ts.map +0 -1
  247. package/dist/resolution/scope.js +0 -88
  248. package/dist/resolution/scope.js.map +0 -1
  249. package/dist/token.d.ts.map +0 -1
  250. package/dist/token.js +0 -22
  251. package/dist/token.js.map +0 -1
  252. package/dist/types.d.ts.map +0 -1
  253. package/dist/types.js.map +0 -1
  254. package/src/registry.ts +0 -282
  255. package/src/resolution/binding-select.ts +0 -154
  256. package/src/resolution/class-introspector.ts +0 -74
  257. package/src/resolution/constraints.ts +0 -121
  258. package/src/resolution/instantiation-plan.ts +0 -292
  259. package/src/resolution/resolution-path.ts +0 -156
  260. package/src/resolution/resolve-options.ts +0 -89
  261. package/src/resolution/scope.ts +0 -105
  262. /package/dist/{resolution → core}/binding-scope.js +0 -0
  263. /package/dist/{constructor-type.js → core/constructor-type.js} +0 -0
  264. /package/dist/{types.js → core/types.js} +0 -0
  265. /package/dist/{resolution → errors}/diagnostics.js +0 -0
  266. /package/dist/resolution/{activation-need.js → cache/activation-need.js} +0 -0
@@ -1,15 +1,10 @@
1
- import type { Constructor } from "#/constructor-type";
2
- import type { Token } from "#/token";
1
+ import type { Constructor } from "#/core/constructor-type";
2
+ import type { BindingTag, TagKeyMask } from "#/core/tag";
3
+ import type { Token } from "#/core/token";
3
4
 
4
5
  // Re-export for consumers that import from `#/types`
5
- export type { Constructor } from "#/constructor-type";
6
-
7
- /**
8
- * A single [tag, value] pair used in slot constraints and resolve options.
9
- *
10
- * @since 0.3.16-canary.0
11
- */
12
- export type BindingTag = readonly [tag: string, value: unknown];
6
+ export type { Constructor } from "#/core/constructor-type";
7
+ export type { BindingTag, TagKey, TagKeyMask } from "#/core/tag";
13
8
 
14
9
  /**
15
10
  * Token or class constructor used as a binding / injection / resolve key.
@@ -18,14 +13,14 @@ export type BindingTag = readonly [tag: string, value: unknown];
18
13
  */
19
14
  export type DependencyKey = Token<unknown> | Constructor;
20
15
 
21
- // ── BindingScope ────────────────────────────────────────────────────────────
16
+ // ── BindingScope ─────────────────────────────────────────────────────────────────────────────────────────────────────
22
17
 
23
18
  /**
24
19
  * @since 0.3.16-canary.0
25
20
  */
26
21
  export type BindingScope = "singleton" | "transient" | "scoped";
27
22
 
28
- // ── BindingIdentifier ────────────────────────────────────────────────────────
23
+ // ── BindingIdentifier ────────────────────────────────────────────────────────────────────────────────────────────────
29
24
 
30
25
  declare const BINDING_ID_BRAND: unique symbol;
31
26
  /**
@@ -33,14 +28,14 @@ declare const BINDING_ID_BRAND: unique symbol;
33
28
  */
34
29
  export type BindingIdentifier = string & { readonly [BINDING_ID_BRAND]: true };
35
30
 
36
- // ── BindingKind ───────────────────────────────────────────────────────────────
31
+ // ── BindingKind ──────────────────────────────────────────────────────────────────────────────────────────────────────
37
32
 
38
33
  /**
39
34
  * @since 0.3.16-canary.0
40
35
  */
41
36
  export type BindingKind = "class" | "dynamic" | "dynamic-async" | "resolved" | "resolved-async" | "constant" | "alias";
42
37
 
43
- // ── Handlers ─────────────────────────────────────────────────────────────────
38
+ // ── Handlers ─────────────────────────────────────────────────────────────────────────────────────────────────────────
44
39
 
45
40
  /**
46
41
  * @since 0.3.16-canary.0
@@ -52,22 +47,24 @@ export type ActivationHandler<Value> = (ctx: ResolutionContext, instance: Value)
52
47
  */
53
48
  export type DeactivationHandler<Value> = (instance: Value) => void | Promise<void>;
54
49
 
55
- // ── ResolveOptions ────────────────────────────────────────────────────────────
50
+ // ── ResolveOptions ───────────────────────────────────────────────────────────────────────────────────────────────────
56
51
 
57
52
  /**
58
53
  * @since 0.3.16-canary.0
59
54
  */
60
55
  export interface ResolveOptions {
61
- name?: string;
56
+ name?: string | undefined;
62
57
  /**
63
- * Single-tag shorthand — semantics match including one pair in `tags`.
64
- * Enables fast-path lookup alongside `tags`.
58
+ * Single-tag shorthand, equivalent to listing the one pair in `tags`.
59
+ *
60
+ * @remarks Reaches the same tagged index, so choosing it costs nothing. `InjectOptions` accepts it
61
+ * too and folds it into `tags`. Only `tags` expresses a request for more than one tag.
65
62
  */
66
- tag?: BindingTag;
67
- tags?: ReadonlyArray<BindingTag>;
63
+ tag?: BindingTag | undefined;
64
+ tags?: ReadonlyArray<BindingTag> | undefined;
68
65
  }
69
66
 
70
- // ── ResolutionFrame ──────────────────────────────────────────────────────
67
+ // ── ResolutionFrame ──────────────────────────────────────────────────────────────────────────────────────────────────
71
68
 
72
69
  /**
73
70
  * @since 0.3.16-canary.0
@@ -80,10 +77,11 @@ export interface ResolutionFrame {
80
77
  readonly slot: {
81
78
  readonly name: string | undefined;
82
79
  readonly tags: ReadonlyArray<BindingTag>;
80
+ readonly keyMask: TagKeyMask;
83
81
  };
84
82
  }
85
83
 
86
- // ── ConstraintContext ─────────────────────────────────────────────────────────
84
+ // ── ConstraintContext ────────────────────────────────────────────────────────────────────────────────────────────────
87
85
 
88
86
  /**
89
87
  * @since 0.3.16-canary.0
@@ -93,31 +91,36 @@ export interface ConstraintContext {
93
91
  readonly resolutionStack: ReadonlyArray<ResolutionFrame>;
94
92
  readonly parent: ResolutionFrame | undefined;
95
93
  readonly ancestors: ReadonlyArray<ResolutionFrame>;
96
- readonly currentResolveOptions: ResolveOptions | undefined;
94
+ /** Readonly because one frozen object answers every resolve of a slot — a write would throw. */
95
+ readonly currentResolveOptions: Readonly<ResolveOptions> | undefined;
97
96
  }
98
97
 
99
- // ── ResolutionContext ─────────────────────────────────────────────────────────
98
+ /**
99
+ * The predicate `when()` selects a binding by, and the shape every `when*` helper returns.
100
+ *
101
+ * @since 0.6.0
102
+ */
103
+ export type BindingConstraint = (ctx: ConstraintContext) => boolean;
104
+
105
+ // ── ResolutionContext ────────────────────────────────────────────────────────────────────────────────────────────────
100
106
 
101
107
  /**
102
108
  * @since 0.3.16-canary.0
103
109
  */
104
110
  export interface ResolutionContext {
105
- resolve<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value;
106
- resolveAsync<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
107
- resolveOptional<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
108
- resolveOptionalAsync<const Value>(
111
+ resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value;
112
+ resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
113
+ resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
114
+ resolveOptionalAsync<Value>(
109
115
  token: Token<Value> | Constructor<Value>,
110
116
  options?: ResolveOptions,
111
117
  ): Promise<Value | undefined>;
112
- resolveAll<const Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value>;
113
- resolveAllAsync<const Value>(
114
- token: Token<Value> | Constructor<Value>,
115
- options?: ResolveOptions,
116
- ): Promise<Array<Value>>;
118
+ resolveAll<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Array<Value>;
119
+ resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Array<Value>>;
117
120
  readonly graph: ConstraintContext;
118
121
  }
119
122
 
120
- // ── TokenValue ────────────────────────────────────────────────────────────────
123
+ // ── TokenValue ───────────────────────────────────────────────────────────────────────────────────────────────────────
121
124
 
122
125
  /**
123
126
  * @since 0.3.16-canary.0
@@ -1,137 +1,33 @@
1
- import { InternalError, MissingContainerContextError } from "#/errors";
1
+ import { getActiveContainer, getAmbientResolution } from "#/ambient/active-container";
2
+ /** `@inject` — the accessor-decorator channel, resolving from the ambient container. */
3
+ import type { Token } from "#/core/token";
4
+ import type { Constructor } from "#/core/types";
5
+ import { MissingContainerContextError, StaticMemberDecoratorError } from "#/errors/errors";
6
+ import type { InjectionDescriptor, InjectOptions } from "#/injection/descriptor";
7
+ import { buildInjectionDescriptor } from "#/injection/descriptor";
8
+ import { injectionSlotToResolveOptions } from "#/injection/resolve-options";
2
9
  import { INJECT_ACCESSOR_KEY } from "#/metadata/metadata-keys";
3
- import { getActiveContainer } from "#/resolution/environment";
4
- import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
5
- import type { Token } from "#/token";
6
- import type { BindingTag, Constructor, TokenValue } from "#/types";
7
-
8
- // ── InjectionDescriptor ───────────────────────────────────────────────────────
9
-
10
- /**
11
- * @since 0.3.16-canary.0
12
- */
13
- export interface InjectOptions {
14
- name?: string;
15
- tags?: ReadonlyArray<BindingTag>;
16
- }
17
-
18
- /**
19
- * @since 0.3.16-canary.0
20
- */
21
- export interface InjectionDescriptor<Value = unknown> {
22
- readonly token: Token<Value> | Constructor<Value>;
23
- readonly optional: boolean;
24
- readonly multi: boolean;
25
- readonly name?: string;
26
- readonly tags?: ReadonlyArray<BindingTag>;
27
- }
28
-
29
- /**
30
- * @since 0.3.16-canary.0
31
- */
32
- export type InjectableDependency<Value = unknown> = Token<Value> | Constructor<Value> | InjectionDescriptor<Value>;
33
10
 
34
11
  /**
35
- * The value a factory receives for one declared dependency.
36
- *
37
- * @remarks `optional()` and `injectAll()` fold their effect into the descriptor's own type
38
- * parameter, so reading it back is enough; bare tokens fall through to {@link TokenValue}.
12
+ * The name of the class being constructed, or `undefined` when there is none to report.
39
13
  *
40
- * @since 0.5.0-canary.7
14
+ * @remarks Two ways there is none: an anonymous class expression has an empty `name`, and an
15
+ * instance whose prototype chain answers no `constructor` has nothing to read. Narrowed rather than
16
+ * asserted — the value arrives as `unknown` and the platform makes no promise about it.
41
17
  */
42
- export type ResolvedDependencyValue<Dependency> =
43
- Dependency extends InjectionDescriptor<infer Value> ? Value : TokenValue<Dependency>;
44
-
45
- /**
46
- * @since 0.3.16-canary.0
47
- */
48
- export function isInjectionDescriptor(value: unknown): value is InjectionDescriptor {
49
- if (value === null || value === undefined) {
50
- return false;
18
+ function classNameOf(instance: unknown): string | undefined {
19
+ if (typeof instance !== "object" || instance === null) {
20
+ return undefined;
51
21
  }
52
- const type = typeof value;
53
- // inject() returns a function (dual-role), so must check both object and function
54
- if (type !== "object" && type !== "function") {
55
- return false;
22
+ const constructor: unknown = Reflect.get(instance, "constructor");
23
+ if (typeof constructor !== "function" || constructor.name === "") {
24
+ return undefined;
56
25
  }
57
- return (
58
- "token" in (value as object) &&
59
- "optional" in (value as object) &&
60
- "multi" in (value as object) &&
61
- typeof (value as InjectionDescriptor).optional === "boolean" &&
62
- typeof (value as InjectionDescriptor).multi === "boolean"
63
- );
64
- }
65
26
 
66
- /**
67
- * @since 0.3.16-canary.0
68
- */
69
- export function normalizeToDescriptor(dependency: InjectableDependency): InjectionDescriptor {
70
- if (isInjectionDescriptor(dependency)) {
71
- return materializeInjectionDescriptor(dependency);
72
- }
73
- return { token: dependency as Token<unknown> | Constructor, optional: false, multi: false };
27
+ return constructor.name;
74
28
  }
75
29
 
76
- /**
77
- * Dual-role `inject()` values are functions: [[Function]].name must not be treated as a DI slot name.
78
- * Only enumerable own `name` / `tags` from `Object.defineProperties` are real injection options.
79
- */
80
- function materializeInjectionDescriptor(dependency: InjectionDescriptor): InjectionDescriptor {
81
- if (typeof dependency !== "function") {
82
- return dependency;
83
- }
84
- const dualRole = dependency as InjectionDescriptor & ((...args: Array<unknown>) => unknown);
85
- const base: Pick<InjectionDescriptor, "token" | "optional" | "multi"> = {
86
- token: dualRole.token,
87
- optional: dualRole.optional,
88
- multi: dualRole.multi,
89
- };
90
- const nameDesc = Object.getOwnPropertyDescriptor(dualRole, "name");
91
- const tagsDesc = Object.getOwnPropertyDescriptor(dualRole, "tags");
92
- const explicitName = nameDesc?.enumerable === true && typeof nameDesc.value === "string" ? nameDesc.value : undefined;
93
- const explicitTags = tagsDesc?.enumerable === true ? tagsDesc.value : undefined;
94
-
95
- if (explicitName !== undefined && explicitTags !== undefined) {
96
- return {
97
- ...base,
98
- name: explicitName,
99
- tags: explicitTags as NonNullable<InjectionDescriptor["tags"]>,
100
- };
101
- }
102
- if (explicitName !== undefined) {
103
- return { ...base, name: explicitName };
104
- }
105
- if (explicitTags !== undefined) {
106
- return { ...base, tags: explicitTags as NonNullable<InjectionDescriptor["tags"]> };
107
- }
108
- return base;
109
- }
110
-
111
- function withOptions<DescValue>(
112
- base: Pick<InjectionDescriptor<DescValue>, "token" | "optional" | "multi">,
113
- options: InjectOptions | undefined,
114
- ): InjectionDescriptor<DescValue> {
115
- if (options?.name !== undefined && options.tags !== undefined) {
116
- return { ...base, name: options.name, tags: options.tags };
117
- }
118
- if (options?.name !== undefined) {
119
- return { ...base, name: options.name };
120
- }
121
- if (options?.tags !== undefined) {
122
- return { ...base, tags: options.tags };
123
- }
124
- return base;
125
- }
126
-
127
- function buildInjectionDescriptor<const Value>(
128
- token: Token<Value> | Constructor<Value>,
129
- options?: InjectOptions,
130
- ): InjectionDescriptor<Value> {
131
- return withOptions({ token, optional: false, multi: false }, options);
132
- }
133
-
134
- // ── inject() — dual-role ──────────────────────────────────────────────────────
30
+ // ── inject() — dual-role ─────────────────────────────────────────────────────────────────────────────────────────────
135
31
 
136
32
  type ClassAccessorDecorator<This, Value> = (
137
33
  target: ClassAccessorDecoratorTarget<This, Value>,
@@ -141,23 +37,26 @@ type ClassAccessorDecorator<This, Value> = (
141
37
  /**
142
38
  * @since 0.3.16-canary.0
143
39
  */
144
- export function inject<const Value>(
40
+ export function inject<Value>(
145
41
  token: Token<Value> | Constructor<Value>,
146
42
  options?: InjectOptions,
147
43
  ): InjectionDescriptor<Value> & ClassAccessorDecorator<unknown, Value> {
148
44
  const descriptor = buildInjectionDescriptor(token, options);
45
+ // Derived from the descriptor, not from `options`: the descriptor is where the tag shorthand has
46
+ // already been folded. Built once here rather than per constructed instance.
47
+ const resolveOptions = injectionSlotToResolveOptions(descriptor);
149
48
 
150
49
  const decoratorFn = (
151
50
  _target: ClassAccessorDecoratorTarget<unknown, Value>,
152
51
  context: ClassAccessorDecoratorContext<unknown, Value>,
153
52
  ): ClassAccessorDecoratorResult<unknown, Value> => {
154
53
  if (context.static) {
155
- throw new InternalError(
156
- "@inject() on static accessors is not supported; only instance accessors participate in runWithContainer-based property injection.",
157
- );
54
+ throw new StaticMemberDecoratorError("inject", String(context.name));
158
55
  }
159
56
  const meta = context.metadata as Record<string | symbol, unknown>;
160
- if (!Array.isArray(meta[INJECT_ACCESSOR_KEY])) {
57
+ // Own bucket only: the metadata record inherits the base class's, and pushing into an inherited
58
+ // array would register this accessor on the base class instead.
59
+ if (!Object.hasOwn(meta, INJECT_ACCESSOR_KEY) || !Array.isArray(meta[INJECT_ACCESSOR_KEY])) {
161
60
  meta[INJECT_ACCESSOR_KEY] = [];
162
61
  }
163
62
  (meta[INJECT_ACCESSOR_KEY] as Array<{ key: string | symbol; descriptor: InjectionDescriptor }>).push({
@@ -166,17 +65,20 @@ export function inject<const Value>(
166
65
  });
167
66
 
168
67
  context.addInitializer(function (this: unknown) {
68
+ // Prefer the engine's path-continuing resolver: it keeps this accessor's dependencies on the
69
+ // live resolution path, so a cycle through an accessor is detected instead of recursing.
70
+ const ambient = getAmbientResolution();
71
+ if (ambient !== undefined) {
72
+ const value = descriptor.optional
73
+ ? ambient.resolveOptional(token, resolveOptions)
74
+ : ambient.resolve(token, resolveOptions);
75
+ context.access.set(this, value as Value);
76
+ return;
77
+ }
169
78
  const container = getActiveContainer();
170
79
  if (container === undefined) {
171
- throw new MissingContainerContextError(String(context.name));
80
+ throw new MissingContainerContextError(classNameOf(this), context.name);
172
81
  }
173
- const resolveOptions =
174
- options === undefined
175
- ? undefined
176
- : injectionSlotToResolveOptions({
177
- ...(options.name !== undefined ? { name: options.name } : {}),
178
- ...(options.tags !== undefined ? { tags: options.tags } : {}),
179
- });
180
82
  const value = descriptor.optional
181
83
  ? container.resolveOptional(token, resolveOptions)
182
84
  : container.resolve(token, resolveOptions);
@@ -195,41 +97,3 @@ export function inject<const Value>(
195
97
 
196
98
  return decoratorFn as InjectionDescriptor<Value> & ClassAccessorDecorator<unknown, Value>;
197
99
  }
198
-
199
- // ── optional() ────────────────────────────────────────────────────────────────
200
-
201
- /**
202
- * @since 0.3.16-canary.0
203
- */
204
- export function optional<const Value>(
205
- token: Token<Value> | Constructor<Value>,
206
- options?: InjectOptions,
207
- ): InjectionDescriptor<Value | undefined> {
208
- return withOptions(
209
- {
210
- token: token as Token<Value | undefined> | Constructor<Value | undefined>,
211
- optional: true,
212
- multi: false,
213
- },
214
- options,
215
- );
216
- }
217
-
218
- // ── injectAll() ───────────────────────────────────────────────────────────────
219
-
220
- /**
221
- * @since 0.3.16-canary.0
222
- */
223
- export function injectAll<const Value>(
224
- token: Token<Value> | Constructor<Value>,
225
- options?: InjectOptions,
226
- ): InjectionDescriptor<Array<Value>> {
227
- return withOptions(
228
- {
229
- token: token as Token<Array<Value>> | Constructor<Array<Value>>,
230
- optional: false,
231
- multi: true,
232
- },
233
- options,
234
- );
235
- }
@@ -1,10 +1,10 @@
1
- import type { InjectableDependency } from "#/decorators/inject";
2
- import { normalizeToDescriptor } from "#/decorators/inject";
1
+ import type { BindingScope, Constructor } from "#/core/types";
2
+ import type { InjectableDependency, ResolvedDependencyValue } from "#/injection/descriptor";
3
+ import { normalizeToDescriptor } from "#/injection/descriptor";
3
4
  import { INJECTABLE_KEY } from "#/metadata/metadata-keys";
4
5
  import type { ParamMetadata } from "#/metadata/metadata-types";
5
- import type { BindingScope, Constructor } from "#/types";
6
6
 
7
- // ── AutoRegisterRegistry ──────────────────────────────────────────────────────
7
+ // ── AutoRegisterRegistry ─────────────────────────────────────────────────────────────────────────────────────────────
8
8
 
9
9
  /**
10
10
  * @since 0.3.16-canary.0
@@ -29,26 +29,56 @@ export function createAutoRegisterRegistry(): AutoRegisterRegistry {
29
29
  };
30
30
  }
31
31
 
32
- // ── InjectableOptions ─────────────────────────────────────────────────────────
32
+ // ── InjectableOptions ────────────────────────────────────────────────────────────────────────────────────────────────
33
33
 
34
34
  /**
35
35
  * @since 0.3.16-canary.0
36
36
  */
37
37
  export interface InjectableOptions {
38
- autoRegister?: AutoRegisterRegistry;
39
- scope?: BindingScope;
38
+ autoRegister?: AutoRegisterRegistry | undefined;
39
+ scope?: BindingScope | undefined;
40
40
  }
41
41
 
42
- // ── @injectable() ─────────────────────────────────────────────────────────────
42
+ // ── @injectable() ────────────────────────────────────────────────────────────────────────────────────────────────────
43
43
 
44
44
  /**
45
+ * The parameters a constructor is handed, given what its `deps` declare.
46
+ *
47
+ * @remarks Reuses the same reading a `toResolved` factory gets, so `injectAll` arrives as an array
48
+ * and `optional` as possibly undefined in both places.
49
+ */
50
+ type InjectedParameters<Deps extends ReadonlyArray<InjectableDependency>> = {
51
+ -readonly [Index in keyof Deps]: ResolvedDependencyValue<Deps[Index]>;
52
+ };
53
+
54
+ /**
55
+ * Declare a class injectable, and what its constructor is to be handed.
56
+ *
45
57
  * @since 0.3.16-canary.0
46
58
  */
59
+ export function injectable(): (target: unknown, context: ClassDecoratorContext) => void;
60
+
61
+ /**
62
+ * @remarks Declaring dependencies constrains the class: the decorator only accepts one whose
63
+ * constructor takes exactly what `deps` resolve to, in that order. A class taking fewer parameters
64
+ * than `deps` declares still satisfies it, which is the one mismatch TypeScript's arity rules let
65
+ * through — the surplus dependency is resolved and discarded.
66
+ *
67
+ * @since 0.3.16-canary.0
68
+ */
69
+ export function injectable<const Deps extends ReadonlyArray<InjectableDependency>>(
70
+ deps: Deps,
71
+ options?: InjectableOptions,
72
+ ): (target: abstract new (...args: InjectedParameters<Deps>) => unknown, context: ClassDecoratorContext) => void;
73
+
74
+ /**
75
+ * @since 0.6.0
76
+ */
47
77
  export function injectable(
48
78
  deps?: ReadonlyArray<InjectableDependency>,
49
79
  options?: InjectableOptions,
50
- ): (target: unknown, context: ClassDecoratorContext) => void {
51
- return function (target: unknown, context: ClassDecoratorContext): void {
80
+ ): (target: never, context: ClassDecoratorContext) => void {
81
+ return function (target: never, context: ClassDecoratorContext): void {
52
82
  const parameterMetadataList: Array<ParamMetadata> = (deps ?? []).map((dependency, index) => {
53
83
  const descriptor = normalizeToDescriptor(dependency);
54
84
  const baseParameterMetadata: Pick<ParamMetadata, "index" | "token" | "optional" | "multi"> = {
@@ -1,51 +1,39 @@
1
- import { InternalError } from "#/errors";
1
+ import { StaticMemberDecoratorError } from "#/errors/errors";
2
2
  import { LIFECYCLE_KEY } from "#/metadata/metadata-keys";
3
3
  import type { MutableLifecycleMetadata } from "#/metadata/metadata-types";
4
4
 
5
- function appendUniqueMethod(
6
- metadata: MutableLifecycleMetadata,
7
- phase: "postConstruct" | "preDestroy",
8
- methodName: string,
9
- ): void {
10
- if (!metadata[phase].includes(methodName)) {
11
- metadata[phase].push(methodName);
12
- }
13
- }
5
+ type MethodDecorator = (target: unknown, context: ClassMethodDecoratorContext) => void;
14
6
 
15
- /**
16
- * @since 0.3.16-canary.0
17
- */
18
- export function postConstruct(): (target: unknown, context: ClassMethodDecoratorContext) => void {
7
+ /** Records the decorated method under one lifecycle phase; both decorators differ only in that phase. */
8
+ function recordLifecycleMethod(phase: "postConstruct" | "preDestroy"): MethodDecorator {
19
9
  return function (target: unknown, context: ClassMethodDecoratorContext): void {
20
10
  if (context.static) {
21
- throw new InternalError(
22
- "@postConstruct() applies to instance methods only; static methods are not invoked during instance lifecycle.",
23
- );
11
+ throw new StaticMemberDecoratorError(phase, String(context.name));
24
12
  }
25
- const methodName = String(context.name);
26
13
  const meta = context.metadata as Record<string | symbol, unknown>;
27
- if (!meta[LIFECYCLE_KEY]) {
14
+ // Own bucket only: `context.metadata` inherits the base class's record, and writing through an
15
+ // inherited bucket would register this hook on the base class instead.
16
+ if (!Object.hasOwn(meta, LIFECYCLE_KEY)) {
28
17
  meta[LIFECYCLE_KEY] = { postConstruct: [], preDestroy: [] };
29
18
  }
30
- appendUniqueMethod(meta[LIFECYCLE_KEY] as MutableLifecycleMetadata, "postConstruct", methodName);
19
+ const lifecycle = meta[LIFECYCLE_KEY] as MutableLifecycleMetadata;
20
+ const methodName = String(context.name);
21
+ if (!lifecycle[phase].includes(methodName)) {
22
+ lifecycle[phase].push(methodName);
23
+ }
31
24
  };
32
25
  }
33
26
 
34
27
  /**
35
28
  * @since 0.3.16-canary.0
36
29
  */
37
- export function preDestroy(): (target: unknown, context: ClassMethodDecoratorContext) => void {
38
- return function (target: unknown, context: ClassMethodDecoratorContext): void {
39
- if (context.static) {
40
- throw new InternalError(
41
- "@preDestroy() applies to instance methods only; static methods are not invoked during instance teardown.",
42
- );
43
- }
44
- const methodName = String(context.name);
45
- const meta = context.metadata as Record<string | symbol, unknown>;
46
- if (!meta[LIFECYCLE_KEY]) {
47
- meta[LIFECYCLE_KEY] = { postConstruct: [], preDestroy: [] };
48
- }
49
- appendUniqueMethod(meta[LIFECYCLE_KEY] as MutableLifecycleMetadata, "preDestroy", methodName);
50
- };
30
+ export function postConstruct(): MethodDecorator {
31
+ return recordLifecycleMethod("postConstruct");
32
+ }
33
+
34
+ /**
35
+ * @since 0.3.16-canary.0
36
+ */
37
+ export function preDestroy(): MethodDecorator {
38
+ return recordLifecycleMethod("preDestroy");
51
39
  }
@@ -25,6 +25,8 @@ export const RESOLUTION_DIAGNOSTICS: unique symbol = Symbol("di:resolution-diagn
25
25
  export interface ResolutionDiagnostics {
26
26
  /** Bindings with a compiled instantiation plan. */
27
27
  readonly compiledPlanCount: number;
28
+ /** Bindings with a compiled async instantiation plan. */
29
+ readonly compiledAsyncPlanCount: number;
28
30
  /** Contexts held by the depth-indexed sync pool. */
29
31
  readonly syncContextPoolSize: number;
30
32
  /** Scoped instances currently cached by this container's scope manager. */