@codefast/di 0.3.13 → 0.3.14-canary.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.
- package/CHANGELOG.md +30 -0
- package/README.md +270 -234
- package/dist/binding-select.d.mts +17 -6
- package/dist/binding-select.mjs +17 -6
- package/dist/binding.d.mts +167 -34
- package/dist/binding.mjs +111 -14
- package/dist/constraints.d.mts +18 -3
- package/dist/constraints.mjs +18 -3
- package/dist/container.d.mts +85 -35
- package/dist/container.mjs +140 -6
- package/dist/decorators/inject.d.mts +40 -9
- package/dist/decorators/inject.mjs +50 -11
- package/dist/decorators/injectable.d.mts +2 -1
- package/dist/decorators/injectable.mjs +14 -2
- package/dist/decorators/lifecycle-decorators.d.mts +16 -4
- package/dist/decorators/lifecycle-decorators.mjs +16 -4
- package/dist/dependency-graph.d.mts +36 -13
- package/dist/dependency-graph.mjs +42 -8
- package/dist/errors.d.mts +132 -21
- package/dist/errors.mjs +126 -18
- package/dist/graph-adapters/cytoscape.d.mts +10 -0
- package/dist/graph-adapters/cytoscape.mjs +40 -0
- package/dist/graph-adapters/dot.d.mts +9 -0
- package/dist/graph-adapters/dot.mjs +97 -0
- package/dist/graph-adapters/reactflow.d.mts +10 -0
- package/dist/graph-adapters/reactflow.mjs +80 -0
- package/dist/graph-adapters/types.d.mts +91 -0
- package/dist/graph-adapters/types.mjs +1 -0
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +2 -2
- package/dist/inspector.d.mts +42 -40
- package/dist/inspector.mjs +18 -169
- package/dist/lifecycle.d.mts +28 -6
- package/dist/lifecycle.mjs +29 -10
- package/dist/metadata/metadata-keys.d.mts +17 -6
- package/dist/metadata/metadata-keys.mjs +17 -6
- package/dist/metadata/metadata-types.d.mts +42 -18
- package/dist/metadata/param-registry.mjs +6 -0
- package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
- package/dist/metadata/symbol-metadata-reader.mjs +23 -4
- package/dist/module.d.mts +46 -2
- package/dist/module.mjs +19 -0
- package/dist/registry.d.mts +39 -8
- package/dist/registry.mjs +39 -8
- package/dist/resolver.d.mts +107 -12
- package/dist/resolver.mjs +134 -37
- package/dist/scope-validation.d.mts +3 -2
- package/dist/scope-validation.mjs +3 -2
- package/dist/scope.d.mts +38 -6
- package/dist/scope.mjs +42 -13
- package/dist/token.d.mts +9 -2
- package/dist/token.mjs +7 -1
- package/package.json +18 -2
package/dist/inspector.d.mts
CHANGED
|
@@ -4,36 +4,51 @@ import { MetadataReader } from "./metadata/metadata-types.mjs";
|
|
|
4
4
|
import { collectStaticDependencyEdges } from "./dependency-graph.mjs";
|
|
5
5
|
|
|
6
6
|
//#region src/inspector.d.ts
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Whether a singleton/scoped binding's instance is currently held in the scope cache.
|
|
9
|
+
*/
|
|
8
10
|
type BindingActivationStatus = "cached" | "not-cached" | "transient";
|
|
9
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Per-binding row inside a {@link ContainerSnapshot}.
|
|
13
|
+
*/
|
|
10
14
|
type ContainerBindingSnapshot = {
|
|
11
|
-
readonly registryKeyLabel: string;
|
|
12
|
-
readonly bindingId: BindingIdentifier;
|
|
13
|
-
readonly kind: Binding<unknown>["kind"];
|
|
14
|
-
readonly scope: BindingScope;
|
|
15
|
-
readonly activationStatus: BindingActivationStatus;
|
|
15
|
+
/** Human-readable token/constructor label that owns this binding row. */readonly registryKeyLabel: string; /** Stable binding identifier. */
|
|
16
|
+
readonly bindingId: BindingIdentifier; /** Binding strategy kind. */
|
|
17
|
+
readonly kind: Binding<unknown>["kind"]; /** Declared binding scope. */
|
|
18
|
+
readonly scope: BindingScope; /** Cache/materialization status at snapshot time. */
|
|
19
|
+
readonly activationStatus: BindingActivationStatus;
|
|
20
|
+
/**
|
|
21
|
+
* True when {@link BindingBuilder.when} was used (runtime predicate; static graph may still show edges).
|
|
22
|
+
*/
|
|
16
23
|
readonly hasConditionalConstraint: boolean;
|
|
17
24
|
readonly moduleId?: string;
|
|
18
25
|
};
|
|
19
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Full debug snapshot returned by {@link Container.inspect}.
|
|
28
|
+
*/
|
|
20
29
|
type ContainerSnapshot = {
|
|
21
|
-
readonly bindings: readonly ContainerBindingSnapshot[];
|
|
30
|
+
/** Flat list of every visible binding row in the container hierarchy. */readonly bindings: readonly ContainerBindingSnapshot[];
|
|
22
31
|
};
|
|
23
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Canonical structured dependency graph returned by {@link Container.generateDependencyGraph}.
|
|
34
|
+
*/
|
|
24
35
|
type ContainerGraphJson = {
|
|
25
|
-
nodes: ContainerBindingSnapshot[];
|
|
36
|
+
/** Graph nodes (same shape as snapshot rows). */nodes: ContainerBindingSnapshot[]; /** Directed dependency edges between node binding ids. */
|
|
26
37
|
edges: ReturnType<typeof collectStaticDependencyEdges>[number][];
|
|
27
38
|
};
|
|
28
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Read-only view of the container internals exposed to {@link ContainerInspector}.
|
|
41
|
+
*/
|
|
29
42
|
type ContainerInspectorContext = {
|
|
30
|
-
collectAllRegistryKeys(): readonly RegistryKey[];
|
|
31
|
-
lookupBindings(key: RegistryKey): readonly Binding<unknown>[] | undefined;
|
|
32
|
-
isBindingCached(binding: Binding<unknown>): boolean;
|
|
43
|
+
/** Enumerates every registry key visible to the inspector. */collectAllRegistryKeys(): readonly RegistryKey[]; /** Returns bindings for a given key, including hierarchy lookup behavior. */
|
|
44
|
+
lookupBindings(key: RegistryKey): readonly Binding<unknown>[] | undefined; /** Reports whether a binding currently has a cached scoped/singleton instance. */
|
|
45
|
+
isBindingCached(binding: Binding<unknown>): boolean; /** Metadata reader used for static constructor/lifecycle analysis. */
|
|
33
46
|
metadataReader: MetadataReader | undefined;
|
|
34
47
|
};
|
|
35
|
-
/**
|
|
36
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Options for {@link Container.generateDependencyGraph}.
|
|
50
|
+
*/
|
|
51
|
+
type GraphOptions = {
|
|
37
52
|
/**
|
|
38
53
|
* When true, omit registry keys whose label starts with `CODEFAST_DI_` (framework-style tokens)
|
|
39
54
|
* and any edges that would only connect hidden nodes.
|
|
@@ -41,36 +56,23 @@ type DotGraphOptions = {
|
|
|
41
56
|
readonly hideInternals?: boolean;
|
|
42
57
|
};
|
|
43
58
|
/**
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the `@codefast/di/inspector` subpath export.
|
|
59
|
+
* Reads the container's registry and scope-cache state to produce debug snapshots
|
|
60
|
+
* and canonical dependency-graph output (`nodes` + `edges`).
|
|
61
|
+
*
|
|
62
|
+
* Constructed internally by the container; advanced consumers can also construct it
|
|
63
|
+
* directly via the `@codefast/di/inspector` subpath export.
|
|
50
64
|
*/
|
|
51
65
|
declare class ContainerInspector {
|
|
52
66
|
private readonly ctx;
|
|
53
67
|
constructor(ctx: ContainerInspectorContext);
|
|
54
|
-
/** Collects all registered bindings into a flat, serialisable snapshot. */
|
|
55
|
-
getSnapshot(): ContainerSnapshot;
|
|
56
68
|
/**
|
|
57
|
-
*
|
|
58
|
-
* Cycles are represented as ordinary edges (Graphviz renders them correctly).
|
|
59
|
-
* Pass `hideInternals: true` to suppress `CODEFAST_DI_`-prefixed tokens.
|
|
69
|
+
* Collects all registered bindings into a flat, serialisable snapshot.
|
|
60
70
|
*/
|
|
61
|
-
|
|
62
|
-
generateDependencyGraph(options?: DotGraphOptions & {
|
|
63
|
-
format?: "dot";
|
|
64
|
-
}): string;
|
|
65
|
-
generateDependencyGraph(options: DotGraphOptions & {
|
|
66
|
-
format: "json";
|
|
67
|
-
}): ContainerGraphJson;
|
|
68
|
-
generateDependencyGraphJsonTyped(options?: DotGraphOptions): ContainerGraphJson;
|
|
71
|
+
getSnapshot(): ContainerSnapshot;
|
|
69
72
|
/**
|
|
70
|
-
*
|
|
71
|
-
* @internal Use `generateDependencyGraph({ format: "json" })` for typed output.
|
|
73
|
+
* Builds the canonical JSON graph (`nodes` + `edges`).
|
|
72
74
|
*/
|
|
73
|
-
|
|
75
|
+
generateDependencyGraph(options?: GraphOptions): ContainerGraphJson;
|
|
74
76
|
}
|
|
75
77
|
//#endregion
|
|
76
|
-
export { BindingActivationStatus, ContainerBindingSnapshot, ContainerGraphJson, ContainerInspector, ContainerInspectorContext, ContainerSnapshot,
|
|
78
|
+
export { BindingActivationStatus, ContainerBindingSnapshot, ContainerGraphJson, ContainerInspector, ContainerInspectorContext, ContainerSnapshot, GraphOptions };
|
package/dist/inspector.mjs
CHANGED
|
@@ -1,65 +1,39 @@
|
|
|
1
1
|
import { registryKeyLabel } from "./binding-select.mjs";
|
|
2
2
|
import { collectStaticDependencyEdges } from "./dependency-graph.mjs";
|
|
3
3
|
//#region src/inspector.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Maps a binding's scope and cache state to a {@link BindingActivationStatus} label.
|
|
6
|
+
*/
|
|
5
7
|
function activationStatusFor(binding, isCached) {
|
|
6
8
|
if (binding.scope === "transient") return "transient";
|
|
7
9
|
return isCached(binding) ? "cached" : "not-cached";
|
|
8
10
|
}
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
/** Escapes a string for safe embedding inside a DOT HTML-label (`<...>`) table cell. */
|
|
14
|
-
function dotEscapeHtml(text) {
|
|
15
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
16
|
-
}
|
|
17
|
-
/** Returns the Graphviz node shape name for a given binding kind. */
|
|
18
|
-
function nodeShapeForKind(kind) {
|
|
19
|
-
switch (kind) {
|
|
20
|
-
case "constant": return "ellipse";
|
|
21
|
-
case "class": return "box";
|
|
22
|
-
case "dynamic":
|
|
23
|
-
case "async-dynamic":
|
|
24
|
-
case "resolved": return "diamond";
|
|
25
|
-
case "alias": return "octagon";
|
|
26
|
-
default: return kind;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/** Returns DOT fill-color and style attributes that visually distinguish binding scopes. */
|
|
30
|
-
function scopeVisualAttributes(scope) {
|
|
31
|
-
switch (scope) {
|
|
32
|
-
case "singleton": return "style=\"filled\", fillcolor=\"#FFD700\", penwidth=2";
|
|
33
|
-
case "scoped": return "style=\"filled\", fillcolor=\"#ADD8E6\"";
|
|
34
|
-
case "transient": return "style=\"dashed\"";
|
|
35
|
-
default: return scope;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/** Strips non-alphanumeric characters from a module name to produce a valid DOT subgraph identifier. */
|
|
39
|
-
function sanitizeClusterId(moduleName) {
|
|
40
|
-
return moduleName.replace(/[^0-9a-zA-Z_]/g, "_");
|
|
41
|
-
}
|
|
42
|
-
/** Returns `true` when the label string belongs to a framework-internal registry key. */
|
|
11
|
+
/**
|
|
12
|
+
* Returns `true` when the label string belongs to a framework-internal registry key.
|
|
13
|
+
*/
|
|
43
14
|
function registryKeyLabelIsInternal(label) {
|
|
44
15
|
return label.startsWith("CODEFAST_DI_");
|
|
45
16
|
}
|
|
46
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Returns `true` when the registry key resolves to an internal framework label.
|
|
19
|
+
*/
|
|
47
20
|
function isInternalRegistryKey(key) {
|
|
48
21
|
return registryKeyLabelIsInternal(registryKeyLabel(key));
|
|
49
22
|
}
|
|
50
23
|
/**
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* the `@codefast/di/inspector` subpath export.
|
|
24
|
+
* Reads the container's registry and scope-cache state to produce debug snapshots
|
|
25
|
+
* and canonical dependency-graph output (`nodes` + `edges`).
|
|
26
|
+
*
|
|
27
|
+
* Constructed internally by the container; advanced consumers can also construct it
|
|
28
|
+
* directly via the `@codefast/di/inspector` subpath export.
|
|
57
29
|
*/
|
|
58
30
|
var ContainerInspector = class {
|
|
59
31
|
constructor(ctx) {
|
|
60
32
|
this.ctx = ctx;
|
|
61
33
|
}
|
|
62
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Collects all registered bindings into a flat, serialisable snapshot.
|
|
36
|
+
*/
|
|
63
37
|
getSnapshot() {
|
|
64
38
|
const bindings = [];
|
|
65
39
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -87,102 +61,9 @@ var ContainerInspector = class {
|
|
|
87
61
|
return { bindings };
|
|
88
62
|
}
|
|
89
63
|
/**
|
|
90
|
-
*
|
|
91
|
-
* Cycles are represented as ordinary edges (Graphviz renders them correctly).
|
|
92
|
-
* Pass `hideInternals: true` to suppress `CODEFAST_DI_`-prefixed tokens.
|
|
64
|
+
* Builds the canonical JSON graph (`nodes` + `edges`).
|
|
93
65
|
*/
|
|
94
|
-
generateDotGraph(options) {
|
|
95
|
-
const hideInternals = options?.hideInternals === true;
|
|
96
|
-
const fullSnapshot = this.getSnapshot();
|
|
97
|
-
const visibleRows = hideInternals ? fullSnapshot.bindings.filter((row) => !registryKeyLabelIsInternal(row.registryKeyLabel)) : fullSnapshot.bindings;
|
|
98
|
-
const allowedBindingIds = new Set(visibleRows.map((row) => row.bindingId));
|
|
99
|
-
const lines = [
|
|
100
|
-
"digraph codefast_di {",
|
|
101
|
-
" rankdir=LR;",
|
|
102
|
-
" graph [fontname=\"Arial\", fontsize=12, nodesep=0.8, ranksep=1.2];",
|
|
103
|
-
" node [fontname=\"Arial\", fontsize=12, shape=box, style=\"filled,rounded\", fillcolor=\"#F5F5F5\"];",
|
|
104
|
-
" edge [fontname=\"Arial\", fontsize=10];"
|
|
105
|
-
];
|
|
106
|
-
const byModule = /* @__PURE__ */ new Map();
|
|
107
|
-
for (const row of visibleRows) {
|
|
108
|
-
const key = row.moduleId;
|
|
109
|
-
const moduleGroup = byModule.get(key);
|
|
110
|
-
if (moduleGroup === void 0) byModule.set(key, [row]);
|
|
111
|
-
else moduleGroup.push(row);
|
|
112
|
-
}
|
|
113
|
-
const clusteredEntries = [...byModule.entries()].filter((entry) => entry[0] !== void 0);
|
|
114
|
-
const unclustered = byModule.get(void 0) ?? [];
|
|
115
|
-
const nodeAttributeLine = (row, indent) => {
|
|
116
|
-
const shape = nodeShapeForKind(row.kind);
|
|
117
|
-
const scopeAttrs = scopeVisualAttributes(row.scope);
|
|
118
|
-
const kindText = dotEscapeHtml(row.kind);
|
|
119
|
-
const nameText = dotEscapeHtml(row.registryKeyLabel);
|
|
120
|
-
const scopeText = dotEscapeHtml(`scope=${row.scope}`);
|
|
121
|
-
const whenRow = row.hasConditionalConstraint ? ` <TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9" COLOR="#666666">when(...)</FONT></TD></TR>\n` : "";
|
|
122
|
-
const htmlLabel = [
|
|
123
|
-
"<",
|
|
124
|
-
" <TABLE BORDER=\"0\" CELLPADDING=\"4\" CELLSPACING=\"0\">",
|
|
125
|
-
` <TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9" COLOR="#666666">${kindText}</FONT></TD></TR>`,
|
|
126
|
-
` <TR><TD ALIGN="LEFT"><B>${nameText}</B></TD></TR>`,
|
|
127
|
-
` <TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9">${scopeText}</FONT></TD></TR>`,
|
|
128
|
-
whenRow.trimEnd(),
|
|
129
|
-
" </TABLE>",
|
|
130
|
-
" >"
|
|
131
|
-
].filter((line) => line.length > 0).join("\n");
|
|
132
|
-
return `${indent}"${row.bindingId}" [shape=${shape}, ${scopeAttrs}, label=${htmlLabel}];`;
|
|
133
|
-
};
|
|
134
|
-
for (const [moduleName, rows] of clusteredEntries) {
|
|
135
|
-
const clusterId = sanitizeClusterId(moduleName);
|
|
136
|
-
lines.push(` subgraph cluster_${clusterId} {`);
|
|
137
|
-
lines.push(` label="${dotEscapeLabel(moduleName)}";`);
|
|
138
|
-
lines.push(` style=filled;`);
|
|
139
|
-
lines.push(` fillcolor=lightgray;`);
|
|
140
|
-
for (const row of rows) lines.push(nodeAttributeLine(row, " "));
|
|
141
|
-
lines.push(` }`);
|
|
142
|
-
}
|
|
143
|
-
for (const row of unclustered) lines.push(nodeAttributeLine(row, " "));
|
|
144
|
-
const emittedNodeIds = new Set(visibleRows.map((row) => row.bindingId));
|
|
145
|
-
const edgeSeen = /* @__PURE__ */ new Set();
|
|
146
|
-
for (const registryKey of this.ctx.collectAllRegistryKeys()) {
|
|
147
|
-
if (hideInternals && isInternalRegistryKey(registryKey)) continue;
|
|
148
|
-
const list = this.ctx.lookupBindings(registryKey);
|
|
149
|
-
if (list === void 0) continue;
|
|
150
|
-
const pathStart = [registryKeyLabel(registryKey)];
|
|
151
|
-
for (const consumerBinding of list) {
|
|
152
|
-
if (hideInternals && !allowedBindingIds.has(consumerBinding.id)) continue;
|
|
153
|
-
const edges = collectStaticDependencyEdges(consumerBinding, (dependencyKey) => this.ctx.lookupBindings(dependencyKey), this.ctx.metadataReader, pathStart);
|
|
154
|
-
for (const edge of edges) {
|
|
155
|
-
if (hideInternals && (!allowedBindingIds.has(edge.fromBindingId) || !allowedBindingIds.has(edge.toBindingId))) continue;
|
|
156
|
-
const edgeKey = `${edge.fromBindingId}->${edge.toBindingId}:${edge.edgeKind}:${edge.injectHintLabel ?? ""}`;
|
|
157
|
-
if (edgeSeen.has(edgeKey)) continue;
|
|
158
|
-
edgeSeen.add(edgeKey);
|
|
159
|
-
if (!emittedNodeIds.has(edge.fromBindingId)) {
|
|
160
|
-
emittedNodeIds.add(edge.fromBindingId);
|
|
161
|
-
lines.push(` "${edge.fromBindingId}" [shape=box, style=dashed, label="(unlisted ${edge.fromBindingId})"];`);
|
|
162
|
-
}
|
|
163
|
-
if (!emittedNodeIds.has(edge.toBindingId)) {
|
|
164
|
-
emittedNodeIds.add(edge.toBindingId);
|
|
165
|
-
lines.push(` "${edge.toBindingId}" [shape=box, style=dashed, label="(unlisted ${edge.toBindingId})"];`);
|
|
166
|
-
}
|
|
167
|
-
const labelParts = [];
|
|
168
|
-
if (edge.injectHintLabel !== void 0) labelParts.push(edge.injectHintLabel);
|
|
169
|
-
labelParts.push(edge.edgeKind);
|
|
170
|
-
if (edge.toBindingConditional) labelParts.push("conditional");
|
|
171
|
-
const edgeLabel = dotEscapeLabel(labelParts.join(" | "));
|
|
172
|
-
const pathLabel = dotEscapeLabel(edge.resolutionPath.join(" -> "));
|
|
173
|
-
const edgeStyle = edge.isAliasEdge ? ", style=dashed" : "";
|
|
174
|
-
lines.push(` "${edge.fromBindingId}" -> "${edge.toBindingId}" [label="${edgeLabel}", xlabel="${pathLabel}"${edgeStyle}];`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
lines.push("}");
|
|
179
|
-
return lines.join("\n");
|
|
180
|
-
}
|
|
181
66
|
generateDependencyGraph(options) {
|
|
182
|
-
if (options?.format === "json") return this.generateDependencyGraphJsonTyped(options);
|
|
183
|
-
return this.generateDotGraph(options);
|
|
184
|
-
}
|
|
185
|
-
generateDependencyGraphJsonTyped(options) {
|
|
186
67
|
const hideInternals = options?.hideInternals === true;
|
|
187
68
|
const snapshot = this.getSnapshot();
|
|
188
69
|
const visibleNodes = hideInternals ? snapshot.bindings.filter((row) => !registryKeyLabelIsInternal(row.registryKeyLabel)) : [...snapshot.bindings];
|
|
@@ -210,38 +91,6 @@ var ContainerInspector = class {
|
|
|
210
91
|
edges
|
|
211
92
|
};
|
|
212
93
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Produces a JSON graph representation with `nodes` and `edges`.
|
|
215
|
-
* @internal Use `generateDependencyGraph({ format: "json" })` for typed output.
|
|
216
|
-
*/
|
|
217
|
-
generateDependencyGraphJson(options) {
|
|
218
|
-
const hideInternals = options?.hideInternals === true;
|
|
219
|
-
const snapshot = this.getSnapshot();
|
|
220
|
-
const visibleNodes = hideInternals ? snapshot.bindings.filter((row) => !registryKeyLabelIsInternal(row.registryKeyLabel)) : snapshot.bindings;
|
|
221
|
-
const allowedBindingIds = new Set(visibleNodes.map((row) => row.bindingId));
|
|
222
|
-
const edges = [];
|
|
223
|
-
const edgeSeen = /* @__PURE__ */ new Set();
|
|
224
|
-
for (const registryKey of this.ctx.collectAllRegistryKeys()) {
|
|
225
|
-
if (hideInternals && isInternalRegistryKey(registryKey)) continue;
|
|
226
|
-
const list = this.ctx.lookupBindings(registryKey);
|
|
227
|
-
if (list === void 0) continue;
|
|
228
|
-
const pathStart = [registryKeyLabel(registryKey)];
|
|
229
|
-
for (const consumerBinding of list) {
|
|
230
|
-
if (hideInternals && !allowedBindingIds.has(consumerBinding.id)) continue;
|
|
231
|
-
for (const edge of collectStaticDependencyEdges(consumerBinding, (dependencyKey) => this.ctx.lookupBindings(dependencyKey), this.ctx.metadataReader, pathStart)) {
|
|
232
|
-
if (hideInternals && (!allowedBindingIds.has(edge.fromBindingId) || !allowedBindingIds.has(edge.toBindingId))) continue;
|
|
233
|
-
const edgeKey = `${edge.fromBindingId}->${edge.toBindingId}:${edge.edgeKind}:${edge.injectHintLabel ?? ""}`;
|
|
234
|
-
if (edgeSeen.has(edgeKey)) continue;
|
|
235
|
-
edgeSeen.add(edgeKey);
|
|
236
|
-
edges.push(edge);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return JSON.stringify({
|
|
241
|
-
nodes: visibleNodes,
|
|
242
|
-
edges
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
94
|
};
|
|
246
95
|
//#endregion
|
|
247
96
|
export { ContainerInspector };
|
package/dist/lifecycle.d.mts
CHANGED
|
@@ -3,15 +3,30 @@ import { LifecycleMetadata } from "./metadata/metadata-types.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/lifecycle.d.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Duck-typed Promise check: returns `true` when `value` has a `then` method.
|
|
7
|
+
* Used throughout the lifecycle layer to guard against async return values on sync resolution paths.
|
|
8
|
+
*/
|
|
9
|
+
declare function isPromiseLike(value: unknown): value is Promise<unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Runs `onActivation` synchronously on a newly constructed instance.
|
|
12
|
+
* If the handler returns a Promise during synchronous resolution, throws
|
|
13
|
+
* {@link AsyncResolutionError} — callers must use `resolveAsync` for async activation handlers.
|
|
14
|
+
*
|
|
15
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** → cache.
|
|
7
16
|
*/
|
|
8
17
|
declare function runActivation(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, pathLabels: readonly string[]): unknown;
|
|
9
18
|
/**
|
|
10
|
-
* Reads lifecycle metadata directly from a constructor's
|
|
19
|
+
* Reads lifecycle metadata ({@link LifecycleMetadata}) directly from a constructor's
|
|
20
|
+
* `Symbol.metadata` object. Bypasses the {@link MetadataReader} abstraction —
|
|
21
|
+
* used by the scope manager during deactivation when the reader is not available.
|
|
11
22
|
*/
|
|
12
23
|
declare function readLifecycleMetadataFromCtor(implementationClass: Constructor<unknown>): LifecycleMetadata | undefined;
|
|
13
24
|
/**
|
|
14
|
-
* Runs the `@postConstruct()` method synchronously if present.
|
|
25
|
+
* Runs the `@postConstruct()` method synchronously if present.
|
|
26
|
+
* Throws {@link AsyncResolutionError} if the method returns a Promise — async lifecycle
|
|
27
|
+
* methods require `resolveAsync`.
|
|
28
|
+
*
|
|
29
|
+
* Lifecycle ordering: `construct` → **`@postConstruct`** → `onActivation` → cache.
|
|
15
30
|
*/
|
|
16
31
|
declare function runPostConstruct(implementationClass: Constructor<unknown>, instance: unknown, pathLabels?: string[]): void;
|
|
17
32
|
/**
|
|
@@ -19,16 +34,23 @@ declare function runPostConstruct(implementationClass: Constructor<unknown>, ins
|
|
|
19
34
|
*/
|
|
20
35
|
declare function runPostConstructAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
|
|
21
36
|
/**
|
|
22
|
-
* Runs the `@preDestroy()` method synchronously if present.
|
|
37
|
+
* Runs the `@preDestroy()` method synchronously if present.
|
|
38
|
+
* Throws if the method returns a Promise — use `disposeAsync()` / `unloadAsync()` for async teardown.
|
|
39
|
+
*
|
|
40
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`**.
|
|
23
41
|
*/
|
|
24
42
|
declare function runPreDestroy(implementationClass: Constructor<unknown>, instance: unknown): void;
|
|
25
43
|
/**
|
|
26
44
|
* Runs the `@preDestroy()` method, awaiting if it returns a Promise.
|
|
45
|
+
*
|
|
46
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`** (async variant).
|
|
27
47
|
*/
|
|
28
48
|
declare function runPreDestroyAsync(implementationClass: Constructor<unknown>, instance: unknown): Promise<void>;
|
|
29
49
|
/**
|
|
30
|
-
* Runs `onActivation`, awaiting
|
|
50
|
+
* Runs `onActivation`, awaiting if the handler returns a Promise.
|
|
51
|
+
*
|
|
52
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** (async variant) → cache.
|
|
31
53
|
*/
|
|
32
54
|
declare function runActivationAsync(binding: Binding<unknown>, instance: unknown, ctx: ResolutionContext, _pathLabels: readonly string[]): Promise<unknown>;
|
|
33
55
|
//#endregion
|
|
34
|
-
export { readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
|
56
|
+
export { isPromiseLike, readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
package/dist/lifecycle.mjs
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { AsyncResolutionError } from "./errors.mjs";
|
|
2
2
|
import { CODEFAST_DI_LIFECYCLE_METADATA, decoratorMetadataObjectSymbol } from "./metadata/metadata-keys.mjs";
|
|
3
3
|
//#region src/lifecycle.ts
|
|
4
|
+
/**
|
|
5
|
+
* Duck-typed Promise check: returns `true` when `value` has a `then` method.
|
|
6
|
+
* Used throughout the lifecycle layer to guard against async return values on sync resolution paths.
|
|
7
|
+
*/
|
|
4
8
|
function isPromiseLike(value) {
|
|
5
9
|
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
|
|
6
10
|
}
|
|
7
11
|
/**
|
|
8
|
-
* Runs `onActivation` synchronously
|
|
12
|
+
* Runs `onActivation` synchronously on a newly constructed instance.
|
|
13
|
+
* If the handler returns a Promise during synchronous resolution, throws
|
|
14
|
+
* {@link AsyncResolutionError} — callers must use `resolveAsync` for async activation handlers.
|
|
15
|
+
*
|
|
16
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** → cache.
|
|
9
17
|
*/
|
|
10
18
|
function runActivation(binding, instance, ctx, pathLabels) {
|
|
11
19
|
const handler = binding.onActivation;
|
|
@@ -16,7 +24,9 @@ function runActivation(binding, instance, ctx, pathLabels) {
|
|
|
16
24
|
return activationResult;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
|
-
* Reads lifecycle metadata directly from a constructor's
|
|
27
|
+
* Reads lifecycle metadata ({@link LifecycleMetadata}) directly from a constructor's
|
|
28
|
+
* `Symbol.metadata` object. Bypasses the {@link MetadataReader} abstraction —
|
|
29
|
+
* used by the scope manager during deactivation when the reader is not available.
|
|
20
30
|
*/
|
|
21
31
|
function readLifecycleMetadataFromCtor(implementationClass) {
|
|
22
32
|
const metadataObject = implementationClass[decoratorMetadataObjectSymbol()];
|
|
@@ -25,7 +35,11 @@ function readLifecycleMetadataFromCtor(implementationClass) {
|
|
|
25
35
|
return typeof raw === "object" && raw !== null ? raw : void 0;
|
|
26
36
|
}
|
|
27
37
|
/**
|
|
28
|
-
* Runs the `@postConstruct()` method synchronously if present.
|
|
38
|
+
* Runs the `@postConstruct()` method synchronously if present.
|
|
39
|
+
* Throws {@link AsyncResolutionError} if the method returns a Promise — async lifecycle
|
|
40
|
+
* methods require `resolveAsync`.
|
|
41
|
+
*
|
|
42
|
+
* Lifecycle ordering: `construct` → **`@postConstruct`** → `onActivation` → cache.
|
|
29
43
|
*/
|
|
30
44
|
function runPostConstruct(implementationClass, instance, pathLabels) {
|
|
31
45
|
const meta = readLifecycleMetadataFromCtor(implementationClass);
|
|
@@ -33,8 +47,7 @@ function runPostConstruct(implementationClass, instance, pathLabels) {
|
|
|
33
47
|
const methodName = meta.postConstruct;
|
|
34
48
|
const lifecycleMethod = instance[methodName];
|
|
35
49
|
if (typeof lifecycleMethod !== "function") return;
|
|
36
|
-
|
|
37
|
-
if (typeof postConstructResult === "object" && postConstructResult !== null && "then" in postConstructResult && typeof postConstructResult.then === "function") {
|
|
50
|
+
if (isPromiseLike(lifecycleMethod.call(instance))) {
|
|
38
51
|
const labels = pathLabels ?? [];
|
|
39
52
|
throw new AsyncResolutionError(labels[labels.length - 1] ?? "(unknown)", labels, `@postConstruct() "${methodName}" returned a Promise during synchronous resolution`);
|
|
40
53
|
}
|
|
@@ -50,7 +63,10 @@ async function runPostConstructAsync(implementationClass, instance) {
|
|
|
50
63
|
await lifecycleMethod.call(instance);
|
|
51
64
|
}
|
|
52
65
|
/**
|
|
53
|
-
* Runs the `@preDestroy()` method synchronously if present.
|
|
66
|
+
* Runs the `@preDestroy()` method synchronously if present.
|
|
67
|
+
* Throws if the method returns a Promise — use `disposeAsync()` / `unloadAsync()` for async teardown.
|
|
68
|
+
*
|
|
69
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`**.
|
|
54
70
|
*/
|
|
55
71
|
function runPreDestroy(implementationClass, instance) {
|
|
56
72
|
const meta = readLifecycleMetadataFromCtor(implementationClass);
|
|
@@ -58,11 +74,12 @@ function runPreDestroy(implementationClass, instance) {
|
|
|
58
74
|
const methodName = meta.preDestroy;
|
|
59
75
|
const lifecycleMethod = instance[methodName];
|
|
60
76
|
if (typeof lifecycleMethod !== "function") return;
|
|
61
|
-
|
|
62
|
-
if (typeof preDestroyResult === "object" && preDestroyResult !== null && "then" in preDestroyResult && typeof preDestroyResult.then === "function") throw new Error(`@preDestroy() "${methodName}" returned a Promise during synchronous disposal; use disposeAsync() / unloadAsync().`);
|
|
77
|
+
if (isPromiseLike(lifecycleMethod.call(instance))) throw new Error(`@preDestroy() "${methodName}" returned a Promise during synchronous disposal; use disposeAsync() / unloadAsync().`);
|
|
63
78
|
}
|
|
64
79
|
/**
|
|
65
80
|
* Runs the `@preDestroy()` method, awaiting if it returns a Promise.
|
|
81
|
+
*
|
|
82
|
+
* Lifecycle ordering: `onDeactivation` → **`@preDestroy`** (async variant).
|
|
66
83
|
*/
|
|
67
84
|
async function runPreDestroyAsync(implementationClass, instance) {
|
|
68
85
|
const meta = readLifecycleMetadataFromCtor(implementationClass);
|
|
@@ -72,7 +89,9 @@ async function runPreDestroyAsync(implementationClass, instance) {
|
|
|
72
89
|
await lifecycleMethod.call(instance);
|
|
73
90
|
}
|
|
74
91
|
/**
|
|
75
|
-
* Runs `onActivation`, awaiting
|
|
92
|
+
* Runs `onActivation`, awaiting if the handler returns a Promise.
|
|
93
|
+
*
|
|
94
|
+
* Lifecycle ordering: `construct` → `@postConstruct` → **`onActivation`** (async variant) → cache.
|
|
76
95
|
*/
|
|
77
96
|
async function runActivationAsync(binding, instance, ctx, _pathLabels) {
|
|
78
97
|
const handler = binding.onActivation;
|
|
@@ -80,4 +99,4 @@ async function runActivationAsync(binding, instance, ctx, _pathLabels) {
|
|
|
80
99
|
return await handler(ctx, instance);
|
|
81
100
|
}
|
|
82
101
|
//#endregion
|
|
83
|
-
export { readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
|
102
|
+
export { isPromiseLike, readLifecycleMetadataFromCtor, runActivation, runActivationAsync, runPostConstruct, runPostConstructAsync, runPreDestroy, runPreDestroyAsync };
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
//#region src/metadata/metadata-keys.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Well-known key for
|
|
3
|
+
* Well-known property key for constructor injection metadata on `Symbol.metadata`.
|
|
4
|
+
* Written by `@injectable()`, read by `SymbolMetadataReader.getConstructorMetadata()`.
|
|
5
|
+
* The `:v1` suffix is a schema version — bump only when the `ConstructorMetadata` shape changes.
|
|
4
6
|
*/
|
|
5
7
|
declare const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Well-known property key for accessor-field injection metadata on `Symbol.metadata`.
|
|
10
|
+
* Written by `@inject()` when used as an accessor decorator; read during post-construction
|
|
11
|
+
* property injection by the resolver.
|
|
12
|
+
*/
|
|
7
13
|
declare const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
|
|
8
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`.
|
|
16
|
+
*/
|
|
9
17
|
declare const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
|
|
10
18
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `Symbol.metadata`
|
|
19
|
+
* Returns the runtime symbol used to access TC39 decorator metadata on a class.
|
|
20
|
+
*
|
|
21
|
+
* Prefers the native `Symbol.metadata` when available (Stage 3 decorators); falls back to
|
|
22
|
+
* `Symbol.for("Symbol.metadata")` for Node/runtime versions that polyfill or partially
|
|
23
|
+
* implement the proposal. The fallback key is the de facto convention used by TypeScript
|
|
24
|
+
* and polyfill libraries.
|
|
14
25
|
*/
|
|
15
26
|
declare function decoratorMetadataObjectSymbol(): symbol;
|
|
16
27
|
//#endregion
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
//#region src/metadata/metadata-keys.ts
|
|
2
2
|
/**
|
|
3
|
-
* Well-known key for
|
|
3
|
+
* Well-known property key for constructor injection metadata on `Symbol.metadata`.
|
|
4
|
+
* Written by `@injectable()`, read by `SymbolMetadataReader.getConstructorMetadata()`.
|
|
5
|
+
* The `:v1` suffix is a schema version — bump only when the `ConstructorMetadata` shape changes.
|
|
4
6
|
*/
|
|
5
7
|
const CODEFAST_DI_CONSTRUCTOR_METADATA = "codefast/di:constructor-metadata:v1";
|
|
6
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* Well-known property key for accessor-field injection metadata on `Symbol.metadata`.
|
|
10
|
+
* Written by `@inject()` when used as an accessor decorator; read during post-construction
|
|
11
|
+
* property injection by the resolver.
|
|
12
|
+
*/
|
|
7
13
|
const CODEFAST_DI_ACCESSOR_INJECTIONS = "codefast/di:accessor-injections:v1";
|
|
8
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Well-known key for lifecycle method names written by `@postConstruct()` / `@preDestroy()`.
|
|
16
|
+
*/
|
|
9
17
|
const CODEFAST_DI_LIFECYCLE_METADATA = "codefast/di:lifecycle-metadata:v1";
|
|
10
18
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `Symbol.metadata`
|
|
19
|
+
* Returns the runtime symbol used to access TC39 decorator metadata on a class.
|
|
20
|
+
*
|
|
21
|
+
* Prefers the native `Symbol.metadata` when available (Stage 3 decorators); falls back to
|
|
22
|
+
* `Symbol.for("Symbol.metadata")` for Node/runtime versions that polyfill or partially
|
|
23
|
+
* implement the proposal. The fallback key is the de facto convention used by TypeScript
|
|
24
|
+
* and polyfill libraries.
|
|
14
25
|
*/
|
|
15
26
|
function decoratorMetadataObjectSymbol() {
|
|
16
27
|
return typeof Symbol.metadata === "symbol" ? Symbol.metadata : Symbol.for("Symbol.metadata");
|