@codefast/di 0.7.1 → 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 +40 -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 +5 -7
  7. package/dist/container/container.d.ts +1 -2
  8. package/dist/container/container.js +4 -6
  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 +16 -4
  12. package/dist/core/binding.js +30 -6
  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 -5
  22. package/dist/core/registry.js +15 -63
  23. package/dist/core/tag.d.ts +27 -2
  24. package/dist/core/tag.js +55 -13
  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 +29 -5
  39. package/dist/index.d.ts +2 -3
  40. package/dist/index.js +2 -3
  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 +18 -14
  44. package/dist/injection/resolve-options.js +70 -17
  45. package/dist/introspection/dependency-graph.d.ts +1 -2
  46. package/dist/introspection/dependency-graph.js +23 -11
  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 +2 -5
  74. package/dist/resolution/cache/binding-lookup-cache.js +2 -40
  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 +5 -14
  82. package/dist/resolution/plan/instantiation-plan.js +10 -29
  83. package/dist/resolution/resolver.d.ts +13 -7
  84. package/dist/resolution/resolver.js +107 -143
  85. package/dist/resolution/select/binding-select.d.ts +5 -7
  86. package/dist/resolution/select/binding-select.js +19 -22
  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 -332
  178. package/src/container/container.ts +0 -973
  179. package/src/core/binding-scope.ts +0 -15
  180. package/src/core/binding.ts +0 -477
  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 -371
  186. package/src/core/tag.ts +0 -158
  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 -476
  194. package/src/index.ts +0 -139
  195. package/src/injection/descriptor.ts +0 -218
  196. package/src/injection/resolve-options.ts +0 -144
  197. package/src/introspection/dependency-graph.ts +0 -282
  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 -209
  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 -617
  216. package/src/resolution/resolver.ts +0 -1675
  217. package/src/resolution/select/binding-select.ts +0 -210
  218. package/src/resolution/select/constraints.ts +0 -146
@@ -1,78 +0,0 @@
1
- import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
2
-
3
- /**
4
- * A dependency-graph node in Cytoscape's element format.
5
- *
6
- * @since 0.3.16-canary.0
7
- */
8
- export interface CytoscapeNode {
9
- readonly data: {
10
- readonly id: string;
11
- readonly label: string;
12
- readonly tokenKey: string;
13
- readonly kind: GraphNode["kind"];
14
- readonly scope: GraphNode["scope"];
15
- readonly fromParent: boolean;
16
- };
17
- }
18
-
19
- /**
20
- * A dependency-graph edge in Cytoscape's element format.
21
- *
22
- * @since 0.3.16-canary.0
23
- */
24
- export interface CytoscapeEdge {
25
- readonly data: {
26
- readonly id: string;
27
- readonly source: string;
28
- readonly target: string;
29
- readonly label?: string;
30
- readonly optional: boolean;
31
- readonly slotName?: string;
32
- };
33
- }
34
-
35
- /**
36
- * The flat node-and-edge list Cytoscape takes as its `elements` input.
37
- *
38
- * @since 0.3.16-canary.0
39
- */
40
- export type CytoscapeElements = ReadonlyArray<CytoscapeNode | CytoscapeEdge>;
41
-
42
- /**
43
- * Converts a container's dependency graph into Cytoscape's element list.
44
- *
45
- * @since 0.3.16-canary.0
46
- */
47
- export function toCytoscapeGraph(graph: ContainerGraphJson): CytoscapeElements {
48
- const elements: Array<CytoscapeNode | CytoscapeEdge> = [];
49
-
50
- for (const node of graph.nodes) {
51
- elements.push({
52
- data: {
53
- id: node.id,
54
- label: node.tokenName,
55
- tokenKey: node.tokenKey,
56
- kind: node.kind,
57
- scope: node.scope,
58
- fromParent: node.fromParent,
59
- },
60
- });
61
- }
62
-
63
- for (let idx = 0; idx < graph.edges.length; idx += 1) {
64
- const edge = graph.edges[idx]!;
65
- elements.push({
66
- data: {
67
- id: `edge-${idx}`,
68
- source: edge.from,
69
- target: edge.to,
70
- ...(edge.label !== undefined ? { label: edge.label } : {}),
71
- optional: edge.optional,
72
- ...(edge.slotName !== undefined ? { slotName: edge.slotName } : {}),
73
- },
74
- });
75
- }
76
-
77
- return elements;
78
- }
@@ -1,32 +0,0 @@
1
- import type { ContainerGraphJson } from "#/introspection/dependency-graph";
2
-
3
- // Token names are caller-supplied and land inside DOT quoted strings — ids included, since an
4
- // unbound placeholder id embeds the name it was minted from.
5
- function escapeDotString(value: string): string {
6
- return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
7
- }
8
-
9
- /**
10
- * Renders a container's dependency graph as Graphviz DOT source.
11
- *
12
- * @since 0.3.16-canary.0
13
- */
14
- export function toDotGraph(graph: ContainerGraphJson): string {
15
- const lines: Array<string> = ["digraph DI {", " rankdir=TB;"];
16
-
17
- for (const node of graph.nodes) {
18
- const label = `${escapeDotString(node.tokenName)}\\n[${node.kind}/${node.scope}]`;
19
- // Dashed for anything that is not a live binding of this container: the parent chain
20
- // and unbound-optional placeholders.
21
- const style = node.fromParent || node.kind === "unbound" ? ' style="dashed"' : "";
22
- lines.push(` "${escapeDotString(node.id)}" [label="${label}"${style}];`);
23
- }
24
-
25
- for (const edge of graph.edges) {
26
- const label = edge.label === undefined ? "" : ` [label="${escapeDotString(edge.label)}"]`;
27
- lines.push(` "${escapeDotString(edge.from)}" -> "${escapeDotString(edge.to)}"${label};`);
28
- }
29
-
30
- lines.push("}");
31
- return lines.join("\n");
32
- }
@@ -1,61 +0,0 @@
1
- import type { ContainerGraphJson } from "#/introspection/dependency-graph";
2
-
3
- // Token names are caller-supplied and Mermaid labels are HTML-bearing quoted strings, so quotes
4
- // and markup are neutralized with Mermaid's decimal entity codes before they reach a renderer.
5
- function escapeMermaidLabel(value: string): string {
6
- return value.replaceAll("&", "#38;").replaceAll('"', "#34;").replaceAll("<", "#60;").replaceAll(">", "#62;");
7
- }
8
-
9
- /**
10
- * Mermaid `flowchart TD` source for a container graph — renders anywhere Mermaid does
11
- * (GitHub markdown, docs tooling, mermaid.live) with no extra library.
12
- *
13
- * @since 0.6.0
14
- */
15
- export function toMermaidGraph(graph: ContainerGraphJson): string {
16
- // Mermaid identifiers must stay word-safe; labels carry the real token names.
17
- const idByNode = new Map<string, string>();
18
- const lines: Array<string> = ["flowchart TD"];
19
- const parentIds: Array<string> = [];
20
- const unboundIds: Array<string> = [];
21
-
22
- graph.nodes.forEach((node, index) => {
23
- const id = `n${String(index)}`;
24
-
25
- idByNode.set(node.id, id);
26
- lines.push(` ${id}["${escapeMermaidLabel(node.tokenName)}<br/>${node.kind} · ${node.scope}"]`);
27
-
28
- if (node.fromParent) {
29
- parentIds.push(id);
30
- }
31
-
32
- if (node.kind === "unbound") {
33
- unboundIds.push(id);
34
- }
35
- });
36
-
37
- for (const edge of graph.edges) {
38
- const from = idByNode.get(edge.from);
39
- const to = idByNode.get(edge.to);
40
-
41
- if (from === undefined || to === undefined) {
42
- continue;
43
- }
44
-
45
- lines.push(
46
- edge.label === undefined ? ` ${from} --> ${to}` : ` ${from} -->|"${escapeMermaidLabel(edge.label)}"| ${to}`,
47
- );
48
- }
49
-
50
- if (parentIds.length > 0) {
51
- lines.push(" classDef fromParent stroke-dasharray: 4 4;");
52
- lines.push(` class ${parentIds.join(",")} fromParent;`);
53
- }
54
-
55
- if (unboundIds.length > 0) {
56
- lines.push(" classDef unbound stroke-dasharray: 4 4,opacity:0.6;");
57
- lines.push(` class ${unboundIds.join(",")} unbound;`);
58
- }
59
-
60
- return lines.join("\n");
61
- }
@@ -1,84 +0,0 @@
1
- import type { ContainerGraphJson, GraphNode } from "#/introspection/dependency-graph";
2
-
3
- /**
4
- * A dependency-graph node in React Flow's node format.
5
- *
6
- * @since 0.3.16-canary.0
7
- */
8
- export interface ReactFlowNode {
9
- readonly id: string;
10
- readonly data: {
11
- readonly label: string;
12
- readonly tokenKey: string;
13
- readonly kind: GraphNode["kind"];
14
- readonly scope: GraphNode["scope"];
15
- readonly fromParent: boolean;
16
- };
17
- readonly position: { readonly x: number; readonly y: number };
18
- }
19
-
20
- /**
21
- * A dependency-graph edge in React Flow's edge format.
22
- *
23
- * @since 0.3.16-canary.0
24
- */
25
- export interface ReactFlowEdge {
26
- readonly id: string;
27
- readonly source: string;
28
- readonly target: string;
29
- readonly label?: string;
30
- readonly optional: boolean;
31
- readonly slotName?: string;
32
- }
33
-
34
- /**
35
- * The nodes-and-edges pair a React Flow viewer renders.
36
- *
37
- * @since 0.3.16-canary.0
38
- */
39
- export interface ReactFlowGraph {
40
- readonly nodes: ReadonlyArray<ReactFlowNode>;
41
- readonly edges: ReadonlyArray<ReactFlowEdge>;
42
- }
43
-
44
- /**
45
- * Initial grid layout: React Flow expects concrete positions; viewers re-layout anyway.
46
- *
47
- * @since 0.3.16-canary.0
48
- */
49
- const GRID_COLUMN_COUNT = 5;
50
- const GRID_CELL_WIDTH_PX = 200;
51
- const GRID_CELL_HEIGHT_PX = 100;
52
-
53
- /**
54
- * Converts a container's dependency graph into React Flow nodes and edges on an initial grid.
55
- *
56
- * @since 0.5.0-canary.7
57
- */
58
- export function toReactFlowGraph(graph: ContainerGraphJson): ReactFlowGraph {
59
- const nodes: Array<ReactFlowNode> = graph.nodes.map((node, idx) => ({
60
- id: node.id,
61
- data: {
62
- label: node.tokenName,
63
- tokenKey: node.tokenKey,
64
- kind: node.kind,
65
- scope: node.scope,
66
- fromParent: node.fromParent,
67
- },
68
- position: {
69
- x: (idx % GRID_COLUMN_COUNT) * GRID_CELL_WIDTH_PX,
70
- y: Math.floor(idx / GRID_COLUMN_COUNT) * GRID_CELL_HEIGHT_PX,
71
- },
72
- }));
73
-
74
- const edges: Array<ReactFlowEdge> = graph.edges.map((edge, idx) => ({
75
- id: `edge-${idx}`,
76
- source: edge.from,
77
- target: edge.to,
78
- ...(edge.label !== undefined ? { label: edge.label } : {}),
79
- optional: edge.optional,
80
- ...(edge.slotName !== undefined ? { slotName: edge.slotName } : {}),
81
- }));
82
-
83
- return { nodes, edges };
84
- }
@@ -1,134 +0,0 @@
1
- import type { Binding } from "#/core/binding";
2
- import { effectiveBindingScope } from "#/core/binding-scope";
3
- import type { BindingRegistry } from "#/core/registry";
4
- import type { Token } from "#/core/token";
5
- import { tokenName } from "#/core/token";
6
- import type {
7
- BindingIdentifier,
8
- BindingKind,
9
- BindingScope,
10
- BindingTag,
11
- ConstraintContext,
12
- Constructor,
13
- ResolveOptions,
14
- } from "#/core/types";
15
- import type { ScopeManager } from "#/lifecycle/scope-manager";
16
- import { selectAllBindings } from "#/resolution/select/binding-select";
17
-
18
- // ── Public types ─────────────────────────────────────────────────────────────────────────────────────────────────────
19
-
20
- /**
21
- * A read-only view of one binding: token, kind, scope, slot, and id.
22
- *
23
- * @since 0.3.16-canary.0
24
- */
25
- export interface BindingSnapshot {
26
- readonly tokenName: string;
27
- readonly kind: BindingKind;
28
- readonly scope: BindingScope;
29
- readonly slot: {
30
- readonly name?: string;
31
- readonly tags: ReadonlyArray<BindingTag>;
32
- };
33
- readonly id: BindingIdentifier;
34
- }
35
-
36
- /**
37
- * A read-only view of one container's own bindings and state.
38
- *
39
- * @since 0.3.16-canary.0
40
- */
41
- export interface ContainerSnapshot {
42
- readonly ownBindings: ReadonlyArray<BindingSnapshot>;
43
- readonly cachedSingletonCount: number;
44
- readonly hasParent: boolean;
45
- readonly isDisposed: boolean;
46
- }
47
-
48
- // ── Inspector ────────────────────────────────────────────────────────────────────────────────────────────────────────
49
-
50
- /**
51
- * The read-only introspection surface behind a container's `inspect()`.
52
- *
53
- * @since 0.3.16-canary.0
54
- */
55
- export class Inspector {
56
- readonly #registry: BindingRegistry;
57
- readonly #scope: ScopeManager;
58
- readonly #hasParent: boolean;
59
- readonly #isDisposed: () => boolean;
60
-
61
- constructor(registry: BindingRegistry, scope: ScopeManager, hasParent: boolean, isDisposed: () => boolean) {
62
- this.#registry = registry;
63
- this.#scope = scope;
64
- this.#hasParent = hasParent;
65
- this.#isDisposed = isDisposed;
66
- }
67
-
68
- inspect(): ContainerSnapshot {
69
- return {
70
- ownBindings: this.#registry.allBindings().map((binding) => this.#toSnapshot(binding)),
71
- cachedSingletonCount: this.#scope.cachedSingletons().length,
72
- hasParent: this.#hasParent,
73
- isDisposed: this.#isDisposed(),
74
- };
75
- }
76
-
77
- lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot> {
78
- const bindings = this.#registry.getAll(token);
79
- return bindings.map((binding) => this.#toSnapshot(binding));
80
- }
81
-
82
- has(token: Token<unknown> | Constructor, options?: ResolveOptions, parentHas?: () => boolean): boolean {
83
- const bindings = this.#registry.getAll(token);
84
- if (bindings.length > 0) {
85
- // An existence probe answers ambiguity with `true` — several matches still exist; only
86
- // resolution has to pick one.
87
- if (options !== undefined) {
88
- if (selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0) {
89
- return true;
90
- }
91
- } else {
92
- return true;
93
- }
94
- }
95
- return parentHas?.() ?? false;
96
- }
97
-
98
- hasOwn(token: Token<unknown> | Constructor, options?: ResolveOptions): boolean {
99
- const bindings = this.#registry.getAll(token);
100
- if (bindings.length === 0) {
101
- return false;
102
- }
103
- if (options !== undefined) {
104
- return selectAllBindings(bindings, options, this.#makeConstraintContext(options)).length > 0;
105
- }
106
- return true;
107
- }
108
-
109
- #makeConstraintContext(options: ResolveOptions): ConstraintContext {
110
- return {
111
- resolutionPath: [],
112
- resolutionStack: [],
113
- parent: undefined,
114
- ancestors: [],
115
- currentResolveOptions: options,
116
- };
117
- }
118
-
119
- #toSnapshot(binding: Binding): BindingSnapshot {
120
- // Aliased, not copied: slot tags are frozen where they are built, so a caller's write throws
121
- // instead of corrupting the registry — and the snapshot skips an allocation per binding.
122
- const slot: BindingSnapshot["slot"] =
123
- binding.slot.name !== undefined
124
- ? { name: binding.slot.name, tags: binding.slot.tags }
125
- : { tags: binding.slot.tags };
126
- return {
127
- tokenName: tokenName(binding.token),
128
- kind: binding.kind,
129
- scope: effectiveBindingScope(binding),
130
- slot,
131
- id: binding.id,
132
- };
133
- }
134
- }
@@ -1,272 +0,0 @@
1
- import type { Binding } from "#/core/binding";
2
- import { getOrInsert } from "#/core/map-upsert";
3
- import type { Token } from "#/core/token";
4
- import { tokenName } from "#/core/token";
5
- import type {
6
- ActivationHandler,
7
- Constructor,
8
- DeactivationHandler,
9
- DependencyKey,
10
- ResolutionContext,
11
- } from "#/core/types";
12
- import { AsyncActivationError, AsyncDeactivationError, InvalidMetadataError } from "#/errors/errors";
13
- import type { MetadataReader } from "#/metadata/metadata-types";
14
-
15
- /**
16
- * One container's registry of container-level activation and deactivation hooks, keyed by token.
17
- *
18
- * @since 0.3.16-canary.0
19
- */
20
- export class LifecycleManager {
21
- // Container-level activation/deactivation hooks per token — most containers register none, so
22
- // both tables stay unallocated until the first hook arrives.
23
- #activationHooks: Map<DependencyKey, Array<ActivationHandler<unknown>>> | undefined;
24
- #deactivationHooks: Map<DependencyKey, Array<DeactivationHandler<unknown>>> | undefined;
25
- #activationVersion = 0;
26
-
27
- // One-entry cache in front of the map: a resolve loop asks about the same token over and over,
28
- // and registration is the only thing that can change the answer.
29
- #cachedToken: Token<unknown> | Constructor | undefined;
30
- #cachedHooks: Array<ActivationHandler<unknown>> | undefined;
31
-
32
- registerActivation<Value>(token: Token<Value> | Constructor<Value>, handler: ActivationHandler<Value>): void {
33
- this.#activationVersion += 1;
34
- this.#cachedToken = undefined;
35
- this.#cachedHooks = undefined;
36
- this.#activationHooks ??= new Map();
37
- const list = getOrInsert(this.#activationHooks, token, []);
38
- list.push(handler as ActivationHandler<unknown>);
39
- }
40
-
41
- hasActivationHandlers<Value>(token: Token<Value> | Constructor<Value>): boolean {
42
- const list = this.activationHandlersFor(token);
43
- return list !== undefined && list.length > 0;
44
- }
45
-
46
- get activationVersion(): number {
47
- return this.#activationVersion;
48
- }
49
-
50
- /** Container-level activation handlers for a token — hot-path accessor, no copies. */
51
- activationHandlersFor<Value>(
52
- token: Token<Value> | Constructor<Value>,
53
- ): ReadonlyArray<ActivationHandler<unknown>> | undefined {
54
- const hooks = this.#activationHooks;
55
- if (hooks === undefined) {
56
- return undefined;
57
- }
58
- const key: DependencyKey = token;
59
- if (key === this.#cachedToken) {
60
- return this.#cachedHooks;
61
- }
62
- const list = hooks.get(key);
63
- this.#cachedToken = key;
64
- this.#cachedHooks = list;
65
- return list;
66
- }
67
-
68
- registerDeactivation<Value>(token: Token<Value> | Constructor<Value>, handler: DeactivationHandler<Value>): void {
69
- this.#deactivationHooks ??= new Map();
70
- const list = getOrInsert(this.#deactivationHooks, token, []);
71
- list.push(handler as DeactivationHandler<unknown>);
72
- }
73
-
74
- /** Asked only while tearing down, so it reads the map directly rather than caching like activation. */
75
- hasDeactivationHandlers<Value>(token: Token<Value> | Constructor<Value>): boolean {
76
- const list = this.#deactivationHooks?.get(token);
77
- return list !== undefined && list.length > 0;
78
- }
79
-
80
- /** Every token carrying a container-level hook, paired with the phase that registered it. */
81
- hookedTokens(): Array<[DependencyKey, "onActivation" | "onDeactivation"]> {
82
- const hooked: Array<[DependencyKey, "onActivation" | "onDeactivation"]> = [];
83
- for (const key of this.#activationHooks?.keys() ?? []) {
84
- hooked.push([key, "onActivation"]);
85
- }
86
- for (const key of this.#deactivationHooks?.keys() ?? []) {
87
- hooked.push([key, "onDeactivation"]);
88
- }
89
- return hooked;
90
- }
91
-
92
- async runActivation<Value>(
93
- resolutionContext: ResolutionContext,
94
- binding: Binding<Value>,
95
- instance: Value,
96
- metadataReader: MetadataReader,
97
- ): Promise<Value> {
98
- let activatedInstance: Value = instance;
99
-
100
- // 1. @postConstruct() — after TC39 construction (constructor + accessor addInitializer callbacks)
101
- for (const methodName of lifecycleMethods(binding, metadataReader, "postConstruct")) {
102
- const hookResult = callHook(activatedInstance, methodName);
103
- if (hookResult instanceof Promise) {
104
- await hookResult;
105
- }
106
- }
107
-
108
- // 2. per-binding onActivation
109
- if (binding.kind !== "alias" && binding.onActivation !== undefined) {
110
- const activationResult = binding.onActivation(resolutionContext, activatedInstance);
111
- activatedInstance = activationResult instanceof Promise ? await activationResult : activationResult;
112
- }
113
-
114
- // 3. container-level onActivation
115
- const containerHooks = this.#activationHooks?.get(binding.token);
116
- if (containerHooks !== undefined) {
117
- for (const hook of containerHooks) {
118
- const activationResult = hook(resolutionContext, activatedInstance);
119
- activatedInstance = (activationResult instanceof Promise ? await activationResult : activationResult) as Value;
120
- }
121
- }
122
-
123
- return activatedInstance;
124
- }
125
-
126
- runActivationSync<Value>(
127
- resolutionContext: ResolutionContext,
128
- binding: Binding<Value>,
129
- instance: Value,
130
- metadataReader: MetadataReader,
131
- ): Value {
132
- let activatedInstance: Value = instance;
133
-
134
- // 1. @postConstruct() — must be sync (instance fully constructed per TC39 order)
135
- for (const methodName of lifecycleMethods(binding, metadataReader, "postConstruct")) {
136
- if (callHook(activatedInstance, methodName) instanceof Promise) {
137
- throw new AsyncActivationError(tokenName(binding.token), "postConstruct", methodName);
138
- }
139
- }
140
-
141
- // 2. per-binding onActivation (must be sync)
142
- if (binding.kind !== "alias" && binding.onActivation !== undefined) {
143
- const activationResult = binding.onActivation(resolutionContext, activatedInstance);
144
- if (activationResult instanceof Promise) {
145
- throw new AsyncActivationError(tokenName(binding.token), "onActivation");
146
- }
147
- activatedInstance = activationResult;
148
- }
149
-
150
- // 3. container-level onActivation (must be sync)
151
- const tokenDisplayName = tokenName(binding.token);
152
- const containerHooks = this.#activationHooks?.get(binding.token);
153
- if (containerHooks !== undefined) {
154
- for (const hook of containerHooks) {
155
- const activationResult = hook(resolutionContext, activatedInstance);
156
- if (activationResult instanceof Promise) {
157
- throw new AsyncActivationError(tokenDisplayName, "onActivation");
158
- }
159
- activatedInstance = activationResult as Value;
160
- }
161
- }
162
-
163
- return activatedInstance;
164
- }
165
-
166
- async runDeactivation<Value>(
167
- binding: Binding<Value>,
168
- instance: Value,
169
- metadataReader: MetadataReader,
170
- ): Promise<void> {
171
- const tokenKey: DependencyKey = binding.token;
172
-
173
- // 1. container-level onDeactivation
174
- const containerHooks = this.#deactivationHooks?.get(tokenKey);
175
- if (containerHooks !== undefined) {
176
- for (const hook of containerHooks) {
177
- const hookResult = hook(instance);
178
- if (hookResult instanceof Promise) {
179
- await hookResult;
180
- }
181
- }
182
- }
183
-
184
- // 2. per-binding onDeactivation
185
- if (binding.kind !== "alias" && binding.onDeactivation !== undefined) {
186
- const hookResult = binding.onDeactivation(instance);
187
- if (hookResult instanceof Promise) {
188
- await hookResult;
189
- }
190
- }
191
-
192
- // 3. @preDestroy() — all methods in declaration order
193
- for (const methodName of lifecycleMethods(binding, metadataReader, "preDestroy")) {
194
- const hookResult = callHook(instance, methodName);
195
- if (hookResult instanceof Promise) {
196
- await hookResult;
197
- }
198
- }
199
- }
200
-
201
- runDeactivationSync<Value>(binding: Binding<Value>, instance: Value, metadataReader: MetadataReader): void {
202
- const tokenDisplayName = tokenName(binding.token);
203
- const tokenKey: DependencyKey = binding.token;
204
-
205
- // 1. container-level onDeactivation
206
- const containerHooks = this.#deactivationHooks?.get(tokenKey);
207
- if (containerHooks !== undefined) {
208
- for (const hook of containerHooks) {
209
- const hookResult = hook(instance);
210
- if (hookResult instanceof Promise) {
211
- throw new AsyncDeactivationError(tokenDisplayName);
212
- }
213
- }
214
- }
215
-
216
- // 2. per-binding onDeactivation
217
- if (binding.kind !== "alias" && binding.onDeactivation !== undefined) {
218
- const hookResult = binding.onDeactivation(instance);
219
- if (hookResult instanceof Promise) {
220
- throw new AsyncDeactivationError(tokenDisplayName);
221
- }
222
- }
223
-
224
- // 3. @preDestroy()
225
- for (const methodName of lifecycleMethods(binding, metadataReader, "preDestroy")) {
226
- if (callHook(instance, methodName) instanceof Promise) {
227
- throw new AsyncDeactivationError(tokenDisplayName);
228
- }
229
- }
230
- }
231
-
232
- /** Whether the deferred activation-hook table has had to be built. */
233
- get isActivationTableBuilt(): boolean {
234
- return this.#activationHooks !== undefined;
235
- }
236
- }
237
-
238
- const NO_METHODS: ReadonlyArray<string> = [];
239
-
240
- /** The `@postConstruct` / `@preDestroy` methods a binding declares — only a class can declare any. */
241
- function lifecycleMethods<Value>(
242
- binding: Binding<Value>,
243
- metadataReader: MetadataReader,
244
- phase: "postConstruct" | "preDestroy",
245
- ): ReadonlyArray<string> {
246
- if (binding.kind !== "class") {
247
- return NO_METHODS;
248
- }
249
- return metadataReader.getLifecycleMetadata(binding.target)?.[phase] ?? NO_METHODS;
250
- }
251
-
252
- /**
253
- * Invokes a lifecycle hook by name.
254
- *
255
- * @remarks A name that is not a method can only come from a {@link MetadataReader} that answered
256
- * wrongly, so it is reported rather than skipped — a hook that never runs is the failure a caller
257
- * cannot see. A non-object instance has no hooks to run and is not an error.
258
- */
259
- function callHook(instance: unknown, methodName: string): unknown {
260
- if (typeof instance !== "object" || instance === null) {
261
- return undefined;
262
- }
263
- const method: unknown = Reflect.get(instance, methodName);
264
- if (typeof method !== "function") {
265
- // The class is derived here rather than passed in, so the happy path carries no extra argument.
266
- const constructor: unknown = Reflect.get(instance, "constructor");
267
- const className = typeof constructor === "function" && constructor.name !== "" ? constructor.name : "(anonymous)";
268
- throw new InvalidMetadataError(className, `lifecycle method '${methodName}' is not a method on the instance`);
269
- }
270
-
271
- return method.call(instance);
272
- }