@khapu2906/treasure-chest 1.4.1 → 1.5.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/README.md +57 -1
- package/dist/cjs/Container.js +240 -31
- package/dist/cjs/Container.js.map +1 -1
- package/dist/cjs/Graph.js +232 -0
- package/dist/cjs/Graph.js.map +1 -0
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/Container.js +240 -31
- package/dist/esm/Container.js.map +1 -1
- package/dist/esm/Graph.js +228 -0
- package/dist/esm/Graph.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/types/Container.d.ts +55 -4
- package/dist/types/Container.d.ts.map +1 -1
- package/dist/types/Graph.d.ts +183 -0
- package/dist/types/Graph.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dependency graph for inspecting and filtering container bindings
|
|
4
|
+
*
|
|
5
|
+
* @module Graph
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.Graph = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* Read-only, filterable view over a container's bindings and the
|
|
11
|
+
* dependency edges discovered while resolving them.
|
|
12
|
+
*
|
|
13
|
+
* Edges are recorded as `resolve()` runs, so they only reflect
|
|
14
|
+
* dependencies that have actually been resolved at least once - this is
|
|
15
|
+
* a runtime graph, not a static analysis of factory source code.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const graph = container.graph();
|
|
20
|
+
*
|
|
21
|
+
* const singletons = graph.getNodes({ lifecycle: 'singleton' });
|
|
22
|
+
* const unresolved = graph.getNodes({ resolved: false });
|
|
23
|
+
* const deps = graph.dependenciesOf('userService');
|
|
24
|
+
*
|
|
25
|
+
* console.log(graph.toDot()); // paste into a Graphviz viewer
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
class Graph {
|
|
29
|
+
/**
|
|
30
|
+
* @param snapshot Nodes and edges, keyed by label
|
|
31
|
+
* @param keyLabels Optional map from real service keys to their labels.
|
|
32
|
+
* Container#graph() passes this so lookups by a Symbol find that exact
|
|
33
|
+
* Symbol's node, even if another Symbol has the same description.
|
|
34
|
+
*/
|
|
35
|
+
constructor(snapshot, keyLabels) {
|
|
36
|
+
this.nodes = snapshot.nodes;
|
|
37
|
+
this.edges = snapshot.edges;
|
|
38
|
+
this.keyLabels = keyLabels;
|
|
39
|
+
this.byPlainKey = new Map();
|
|
40
|
+
for (const node of this.nodes) {
|
|
41
|
+
if (!this.byPlainKey.has(node.key)) {
|
|
42
|
+
this.byPlainKey.set(node.key, node);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Turn a service key (or an already-known label) into its node label
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
toLabel(key) {
|
|
51
|
+
var _a, _b;
|
|
52
|
+
return (_b = (_a = this.keyLabels) === null || _a === void 0 ? void 0 : _a.get(key)) !== null && _b !== void 0 ? _b : key.toString();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get all nodes, optionally filtered
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* graph.getNodes({ lifecycle: ['singleton', 'scoped'], resolved: true });
|
|
60
|
+
* graph.getNodes({ key: /Repository$/ });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
getNodes(filter) {
|
|
64
|
+
let result = this.nodes;
|
|
65
|
+
if (!filter) {
|
|
66
|
+
return [...result];
|
|
67
|
+
}
|
|
68
|
+
if (filter.lifecycle) {
|
|
69
|
+
const lifecycles = Array.isArray(filter.lifecycle)
|
|
70
|
+
? filter.lifecycle
|
|
71
|
+
: [filter.lifecycle];
|
|
72
|
+
result = result.filter((n) => lifecycles.includes(n.lifecycle));
|
|
73
|
+
}
|
|
74
|
+
if (filter.resolved !== undefined) {
|
|
75
|
+
result = result.filter((n) => n.resolved === filter.resolved);
|
|
76
|
+
}
|
|
77
|
+
if (filter.lazy !== undefined) {
|
|
78
|
+
result = result.filter((n) => n.lazy === filter.lazy);
|
|
79
|
+
}
|
|
80
|
+
if (filter.inherited !== undefined) {
|
|
81
|
+
result = result.filter((n) => n.inherited === filter.inherited);
|
|
82
|
+
}
|
|
83
|
+
if (filter.key !== undefined) {
|
|
84
|
+
const matcher = filter.key;
|
|
85
|
+
result = result.filter((n) => matcher instanceof RegExp
|
|
86
|
+
? matcher.test(n.key)
|
|
87
|
+
: n.key.includes(matcher));
|
|
88
|
+
}
|
|
89
|
+
return [...result];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get a single (non-contextual) node by key
|
|
93
|
+
*/
|
|
94
|
+
getNode(key) {
|
|
95
|
+
return this.byPlainKey.get(this.toLabel(key));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get all edges, optionally filtered by source and/or target key
|
|
99
|
+
*/
|
|
100
|
+
getEdges(filter) {
|
|
101
|
+
if (!filter) {
|
|
102
|
+
return [...this.edges];
|
|
103
|
+
}
|
|
104
|
+
const from = filter.from !== undefined ? this.toLabel(filter.from) : undefined;
|
|
105
|
+
const to = filter.to !== undefined ? this.toLabel(filter.to) : undefined;
|
|
106
|
+
return this.edges.filter((e) => {
|
|
107
|
+
if (from !== undefined && e.from !== from)
|
|
108
|
+
return false;
|
|
109
|
+
if (to !== undefined && e.to !== to)
|
|
110
|
+
return false;
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Direct dependencies of a key - the services it needs
|
|
116
|
+
*/
|
|
117
|
+
dependenciesOf(key) {
|
|
118
|
+
return this.dependenciesOfLabel(this.toLabel(key));
|
|
119
|
+
}
|
|
120
|
+
/** @private */
|
|
121
|
+
dependenciesOfLabel(keyStr) {
|
|
122
|
+
return this.edges
|
|
123
|
+
.filter((e) => e.from === keyStr)
|
|
124
|
+
.map((e) => this.byPlainKey.get(e.to))
|
|
125
|
+
.filter((n) => n !== undefined);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Direct dependents of a key - the services that need it
|
|
129
|
+
*/
|
|
130
|
+
dependentsOf(key) {
|
|
131
|
+
const keyStr = this.toLabel(key);
|
|
132
|
+
return this.edges
|
|
133
|
+
.filter((e) => e.to === keyStr)
|
|
134
|
+
.map((e) => this.byPlainKey.get(e.from))
|
|
135
|
+
.filter((n) => n !== undefined);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Nodes that nothing else depends on (candidate entry points)
|
|
139
|
+
*/
|
|
140
|
+
roots() {
|
|
141
|
+
const dependedOn = new Set(this.edges.map((e) => e.to));
|
|
142
|
+
return this.nodes.filter((n) => !dependedOn.has(n.key));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Nodes with no recorded dependencies (leaves of the graph)
|
|
146
|
+
*/
|
|
147
|
+
leaves() {
|
|
148
|
+
const withDeps = new Set(this.edges.map((e) => e.from));
|
|
149
|
+
return this.nodes.filter((n) => !withDeps.has(n.key));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Build a nested tree starting at `key`, recursively expanding
|
|
153
|
+
* dependencies into `children`. Handy for rendering a dependency tree in
|
|
154
|
+
* a UI (e.g. a collapsible tree view).
|
|
155
|
+
*
|
|
156
|
+
* A node that reappears along the same branch (A depends on B, B depends
|
|
157
|
+
* on A, recorded from two separate resolve() calls) is marked
|
|
158
|
+
* `cyclic: true` instead of recursing forever - `children` stays empty
|
|
159
|
+
* there. Nodes that appear in multiple, unrelated branches (e.g. two
|
|
160
|
+
* services sharing the same `db` dependency) are duplicated as separate
|
|
161
|
+
* tree nodes, which is expected for a tree view.
|
|
162
|
+
*
|
|
163
|
+
* @param key The root key to build the tree from
|
|
164
|
+
* @returns The tree, or undefined if `key` isn't a known node
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const tree = container.graph().toTree('service');
|
|
169
|
+
* // { key: 'service', children: [{ key: 'repo', children: [{ key: 'db', children: [] }] }] }
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
toTree(key) {
|
|
173
|
+
return this.toTreeByLabel(this.toLabel(key), new Set());
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Recursion works on labels directly (never back through toLabel), so a
|
|
177
|
+
* label can't be re-mapped to a different key mid-walk.
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
toTreeByLabel(keyStr, _visited) {
|
|
181
|
+
const node = this.byPlainKey.get(keyStr);
|
|
182
|
+
if (!node)
|
|
183
|
+
return undefined;
|
|
184
|
+
if (_visited.has(keyStr)) {
|
|
185
|
+
return { ...node, children: [], cyclic: true };
|
|
186
|
+
}
|
|
187
|
+
const visited = new Set(_visited);
|
|
188
|
+
visited.add(keyStr);
|
|
189
|
+
const children = this.dependenciesOfLabel(keyStr)
|
|
190
|
+
.map((dep) => this.toTreeByLabel(dep.key, visited))
|
|
191
|
+
.filter((n) => n !== undefined);
|
|
192
|
+
return { ...node, children };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Build a tree for every root (nodes nothing else depends on), covering
|
|
196
|
+
* the whole graph at once - ready to hand to a UI as-is.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* const forest = container.graph().forest();
|
|
201
|
+
* // [{ key: 'service', children: [...] }, { key: 'unusedSingleton', children: [] }]
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
forest() {
|
|
205
|
+
return this.roots()
|
|
206
|
+
.map((root) => this.toTreeByLabel(root.key, new Set()))
|
|
207
|
+
.filter((n) => n !== undefined);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Plain JSON-serializable snapshot of the graph
|
|
211
|
+
*/
|
|
212
|
+
toJSON() {
|
|
213
|
+
return { nodes: [...this.nodes], edges: [...this.edges] };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Export as Graphviz DOT source, for visualizing the graph
|
|
217
|
+
*/
|
|
218
|
+
toDot() {
|
|
219
|
+
const lines = ['digraph Container {'];
|
|
220
|
+
for (const node of this.nodes) {
|
|
221
|
+
const label = `${node.key}\\n(${node.lifecycle}${node.lazy ? ', lazy' : ''})`;
|
|
222
|
+
lines.push(` "${node.key}" [label="${label}"];`);
|
|
223
|
+
}
|
|
224
|
+
for (const edge of this.edges) {
|
|
225
|
+
lines.push(` "${edge.from}" -> "${edge.to}";`);
|
|
226
|
+
}
|
|
227
|
+
lines.push('}');
|
|
228
|
+
return lines.join('\n');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.Graph = Graph;
|
|
232
|
+
//# sourceMappingURL=Graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Graph.js","sourceRoot":"","sources":["../../src/Graph.ts"],"names":[],"mappings":"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"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* - High-performance Map-based storage (v1.2.0)
|
|
19
19
|
* - Auto-dispose for IDisposable instances (v1.2.0)
|
|
20
20
|
* - Modular architecture (v1.2.0)
|
|
21
|
+
* - Dependency graph inspection with filtering, tree, and DOT export (v1.5.0)
|
|
21
22
|
*
|
|
22
23
|
* @example
|
|
23
24
|
* ```typescript
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
* ```
|
|
30
31
|
*/
|
|
31
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.Scope = exports.Lazy = exports.Container = void 0;
|
|
33
|
+
exports.Graph = exports.Scope = exports.Lazy = exports.Container = void 0;
|
|
33
34
|
// Export main classes
|
|
34
35
|
var Container_1 = require("./Container");
|
|
35
36
|
Object.defineProperty(exports, "Container", { enumerable: true, get: function () { return Container_1.Container; } });
|
|
@@ -37,4 +38,6 @@ var Lazy_1 = require("./Lazy");
|
|
|
37
38
|
Object.defineProperty(exports, "Lazy", { enumerable: true, get: function () { return Lazy_1.Lazy; } });
|
|
38
39
|
var Scope_1 = require("./Scope");
|
|
39
40
|
Object.defineProperty(exports, "Scope", { enumerable: true, get: function () { return Scope_1.Scope; } });
|
|
41
|
+
var Graph_1 = require("./Graph");
|
|
42
|
+
Object.defineProperty(exports, "Graph", { enumerable: true, get: function () { return Graph_1.Graph; } });
|
|
40
43
|
//# sourceMappingURL=index.js.map
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;;AAEH,sBAAsB;AACtB,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAClB,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,iCAAgC;AAAvB,8FAAA,KAAK,OAAA;AACd,iCAAgC;AAAvB,8FAAA,KAAK,OAAA"}
|
package/dist/esm/Container.js
CHANGED
|
@@ -5,6 +5,38 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Lazy } from './Lazy';
|
|
7
7
|
import { Scope } from './Scope';
|
|
8
|
+
import { Graph } from './Graph';
|
|
9
|
+
/**
|
|
10
|
+
* Two-level map keyed by (service key, optional context key).
|
|
11
|
+
*
|
|
12
|
+
* Keys are compared by identity, never by their string form, so two
|
|
13
|
+
* distinct Symbols with the same description (`Symbol('x')` twice) or a
|
|
14
|
+
* string key that looks like a Symbol (`'Symbol(x)'`) never collide.
|
|
15
|
+
*/
|
|
16
|
+
class KeyContextMap {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.map = new Map();
|
|
19
|
+
}
|
|
20
|
+
get(key, context) {
|
|
21
|
+
var _a;
|
|
22
|
+
return (_a = this.map.get(key)) === null || _a === void 0 ? void 0 : _a.get(context);
|
|
23
|
+
}
|
|
24
|
+
has(key, context) {
|
|
25
|
+
var _a, _b;
|
|
26
|
+
return (_b = (_a = this.map.get(key)) === null || _a === void 0 ? void 0 : _a.has(context)) !== null && _b !== void 0 ? _b : false;
|
|
27
|
+
}
|
|
28
|
+
set(key, context, value) {
|
|
29
|
+
let inner = this.map.get(key);
|
|
30
|
+
if (!inner) {
|
|
31
|
+
inner = new Map();
|
|
32
|
+
this.map.set(key, inner);
|
|
33
|
+
}
|
|
34
|
+
inner.set(context, value);
|
|
35
|
+
}
|
|
36
|
+
clear() {
|
|
37
|
+
this.map.clear();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
8
40
|
/**
|
|
9
41
|
* Container class for dependency injection
|
|
10
42
|
* Manages service bindings, instances, and resolution logic
|
|
@@ -24,30 +56,35 @@ export class Container {
|
|
|
24
56
|
constructor(parent) {
|
|
25
57
|
/** Map-based storage for bindings - O(1) lookup */
|
|
26
58
|
this.bindings = new Map();
|
|
27
|
-
/** Cache for singleton instances */
|
|
59
|
+
/** Cache for singleton instances, keyed by (service key, binding context) */
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
this.instances = new KeyContextMap();
|
|
62
|
+
/** Cache for lazy singleton wrappers */
|
|
28
63
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
-
this.
|
|
64
|
+
this.lazyInstances = new Map();
|
|
30
65
|
/** Mapping of aliases to original keys */
|
|
31
66
|
this.aliases = new Map();
|
|
32
67
|
/** Cache for resolved bindings - performance optimization */
|
|
33
|
-
this.bindingCache = new
|
|
68
|
+
this.bindingCache = new KeyContextMap();
|
|
34
69
|
/** Composed containers for composition pattern */
|
|
35
70
|
this.composedContainers = [];
|
|
36
71
|
/** Resolution stack for circular dependency detection */
|
|
37
72
|
this.resolutionStack = [];
|
|
73
|
+
/** Dependency edges observed while resolving: consumerKey -> Set of dependency keys */
|
|
74
|
+
this.dependencyEdges = new Map();
|
|
38
75
|
this.parent = parent;
|
|
39
76
|
}
|
|
40
77
|
/**
|
|
41
|
-
*
|
|
42
|
-
* This is a simple implementation; it assumes no string keys will collide with `Symbol().toString()`.
|
|
78
|
+
* Record that `from` depends on `to`, for later inspection via graph()
|
|
43
79
|
* @private
|
|
44
80
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
81
|
+
_recordEdge(from, to) {
|
|
82
|
+
var _a;
|
|
83
|
+
if (from === to)
|
|
84
|
+
return;
|
|
85
|
+
const deps = (_a = this.dependencyEdges.get(from)) !== null && _a !== void 0 ? _a : new Set();
|
|
86
|
+
deps.add(to);
|
|
87
|
+
this.dependencyEdges.set(from, deps);
|
|
51
88
|
}
|
|
52
89
|
/**
|
|
53
90
|
* Register a transient binding (new instance on each resolve)
|
|
@@ -225,11 +262,25 @@ export class Container {
|
|
|
225
262
|
* @returns The matching binding or undefined
|
|
226
263
|
*/
|
|
227
264
|
findBinding(key, context) {
|
|
265
|
+
var _a;
|
|
266
|
+
return (_a = this.findBindingEntry(key, context)) === null || _a === void 0 ? void 0 : _a.binding;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Find the appropriate binding for a given key and context, along with
|
|
270
|
+
* the container that actually declared it (the "owner"). Searches in
|
|
271
|
+
* current container and parent containers. Uses cached result when
|
|
272
|
+
* possible for performance.
|
|
273
|
+
*
|
|
274
|
+
* @private
|
|
275
|
+
* @param key The service key to find
|
|
276
|
+
* @param context Optional context for contextual binding
|
|
277
|
+
* @returns The matching binding paired with its owning container, or undefined
|
|
278
|
+
*/
|
|
279
|
+
findBindingEntry(key, context) {
|
|
228
280
|
const realKey = this.aliases.get(key) || key;
|
|
229
|
-
const cacheKey = this._toCacheKey(realKey, context);
|
|
230
281
|
// Check cache first
|
|
231
|
-
if (this.bindingCache.has(
|
|
232
|
-
return this.bindingCache.get(
|
|
282
|
+
if (this.bindingCache.has(realKey, context)) {
|
|
283
|
+
return this.bindingCache.get(realKey, context);
|
|
233
284
|
}
|
|
234
285
|
// O(1) Map lookup instead of O(n) array filter
|
|
235
286
|
const bindings = this.bindings.get(realKey);
|
|
@@ -241,15 +292,18 @@ export class Container {
|
|
|
241
292
|
const match = bindings.find((b) => b.context === context && (!b.condition || b.condition(this))) ||
|
|
242
293
|
bindings.find((b) => !b.context && (!b.condition || b.condition(this))) ||
|
|
243
294
|
bindings[0];
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
295
|
+
if (match) {
|
|
296
|
+
const entry = { binding: match, owner: this };
|
|
297
|
+
// Cache the result only if no conditions (conditions are dynamic)
|
|
298
|
+
if (!match.condition) {
|
|
299
|
+
this.bindingCache.set(realKey, context, entry);
|
|
300
|
+
}
|
|
301
|
+
return entry;
|
|
247
302
|
}
|
|
248
|
-
return match;
|
|
249
303
|
}
|
|
250
304
|
// Try parent container
|
|
251
305
|
if (this.parent) {
|
|
252
|
-
return this.parent.
|
|
306
|
+
return this.parent.findBindingEntry(key, context);
|
|
253
307
|
}
|
|
254
308
|
return undefined;
|
|
255
309
|
}
|
|
@@ -279,12 +333,18 @@ export class Container {
|
|
|
279
333
|
.join(' -> ');
|
|
280
334
|
throw new Error(`Circular dependency detected: ${chain}`);
|
|
281
335
|
}
|
|
336
|
+
// Record dependency edge: whatever is currently resolving depends on this key
|
|
337
|
+
if (this.resolutionStack.length > 0) {
|
|
338
|
+
const consumer = this.resolutionStack[this.resolutionStack.length - 1];
|
|
339
|
+
this._recordEdge(consumer, realKey);
|
|
340
|
+
}
|
|
282
341
|
const prevContext = this.currentContext;
|
|
283
342
|
this.currentContext = context || key;
|
|
284
|
-
const
|
|
285
|
-
if (!
|
|
343
|
+
const entry = this.findBindingEntry(key, this.currentContext);
|
|
344
|
+
if (!entry) {
|
|
286
345
|
throw new Error(`No binding found for key: ${realKey.toString()}`);
|
|
287
346
|
}
|
|
347
|
+
const { binding, owner } = entry;
|
|
288
348
|
// Add to resolution stack
|
|
289
349
|
this.resolutionStack.push(realKey);
|
|
290
350
|
try {
|
|
@@ -296,7 +356,9 @@ export class Container {
|
|
|
296
356
|
// Handle different lifecycles
|
|
297
357
|
switch (binding.lifecycle) {
|
|
298
358
|
case 'singleton':
|
|
299
|
-
|
|
359
|
+
// Cached on the owning container so the instance is truly
|
|
360
|
+
// shared across every child that inherits the binding.
|
|
361
|
+
return owner.resolveSingleton(binding, realKey);
|
|
300
362
|
case 'scoped':
|
|
301
363
|
return this.resolveScoped(binding, realKey);
|
|
302
364
|
case 'transient':
|
|
@@ -322,13 +384,12 @@ export class Container {
|
|
|
322
384
|
* @private
|
|
323
385
|
*/
|
|
324
386
|
resolveSingleton(binding, cacheKey) {
|
|
325
|
-
// Singletons can be contextual, so
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return this.instances.get(fullCacheKey);
|
|
387
|
+
// Singletons can be contextual, so instances are keyed by (key, context).
|
|
388
|
+
if (this.instances.has(cacheKey, binding.context)) {
|
|
389
|
+
return this.instances.get(cacheKey, binding.context);
|
|
329
390
|
}
|
|
330
391
|
const instance = binding.factory(this);
|
|
331
|
-
this.instances.set(
|
|
392
|
+
this.instances.set(cacheKey, binding.context, instance);
|
|
332
393
|
return instance;
|
|
333
394
|
}
|
|
334
395
|
/**
|
|
@@ -351,13 +412,13 @@ export class Container {
|
|
|
351
412
|
* @private
|
|
352
413
|
*/
|
|
353
414
|
resolveLazy(binding, cacheKey) {
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
356
|
-
return
|
|
415
|
+
const cached = this.lazyInstances.get(cacheKey);
|
|
416
|
+
if (cached) {
|
|
417
|
+
return cached;
|
|
357
418
|
}
|
|
358
419
|
const lazy = new Lazy(() => binding.factory(this));
|
|
359
420
|
if (binding.lifecycle === 'singleton') {
|
|
360
|
-
this.
|
|
421
|
+
this.lazyInstances.set(cacheKey, lazy);
|
|
361
422
|
}
|
|
362
423
|
return lazy;
|
|
363
424
|
}
|
|
@@ -442,6 +503,152 @@ export class Container {
|
|
|
442
503
|
const parentKeys = this.parent ? this.parent.keys() : [];
|
|
443
504
|
return [...new Set([...ownKeys, ...parentKeys])];
|
|
444
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Collect this container's bindings (own, inherited from a parent, and
|
|
508
|
+
* inherited from composed containers), plus the dependency edges observed
|
|
509
|
+
* so far. Keys are kept as real ServiceKeys here; graph() turns them into
|
|
510
|
+
* unique display labels afterwards.
|
|
511
|
+
*
|
|
512
|
+
* @private
|
|
513
|
+
* @param seen Keys already captured by a more specific (closer) container
|
|
514
|
+
*/
|
|
515
|
+
inspect(seen = new Set()) {
|
|
516
|
+
var _a;
|
|
517
|
+
const nodes = [];
|
|
518
|
+
for (const [key, bindingList] of this.bindings) {
|
|
519
|
+
if (seen.has(key))
|
|
520
|
+
continue;
|
|
521
|
+
seen.add(key);
|
|
522
|
+
for (const binding of bindingList) {
|
|
523
|
+
nodes.push({
|
|
524
|
+
key,
|
|
525
|
+
lifecycle: binding.lifecycle,
|
|
526
|
+
lazy: !!binding.lazy,
|
|
527
|
+
context: binding.context,
|
|
528
|
+
inherited: false,
|
|
529
|
+
resolved: this.instances.has(key, binding.context) ||
|
|
530
|
+
this.lazyInstances.has(key),
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
for (const [aliasKey, originalKey] of this.aliases) {
|
|
535
|
+
if (seen.has(aliasKey))
|
|
536
|
+
continue;
|
|
537
|
+
seen.add(aliasKey);
|
|
538
|
+
const target = this.findBinding(aliasKey);
|
|
539
|
+
nodes.push({
|
|
540
|
+
key: aliasKey,
|
|
541
|
+
lifecycle: (_a = target === null || target === void 0 ? void 0 : target.lifecycle) !== null && _a !== void 0 ? _a : 'transient',
|
|
542
|
+
lazy: !!(target === null || target === void 0 ? void 0 : target.lazy),
|
|
543
|
+
aliasOf: originalKey,
|
|
544
|
+
inherited: false,
|
|
545
|
+
resolved: false,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
const edges = [];
|
|
549
|
+
for (const [from, tos] of this.dependencyEdges) {
|
|
550
|
+
for (const to of tos) {
|
|
551
|
+
edges.push({ from, to });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const ancestors = [
|
|
555
|
+
...(this.parent ? [this.parent] : []),
|
|
556
|
+
...this.composedContainers,
|
|
557
|
+
];
|
|
558
|
+
for (const ancestor of ancestors) {
|
|
559
|
+
const snapshot = ancestor.inspect(seen);
|
|
560
|
+
for (const node of snapshot.nodes) {
|
|
561
|
+
nodes.push({ ...node, inherited: true });
|
|
562
|
+
}
|
|
563
|
+
edges.push(...snapshot.edges);
|
|
564
|
+
}
|
|
565
|
+
return { nodes, edges };
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Assign every distinct service key a unique, human-readable label.
|
|
569
|
+
*
|
|
570
|
+
* String keys always keep their own value as label. Other keys use
|
|
571
|
+
* `toString()`; when that is already taken by a different key (e.g. two
|
|
572
|
+
* `Symbol('db')`s, or `Symbol('db')` next to the string `'Symbol(db)'`),
|
|
573
|
+
* a `#2`, `#3`, ... suffix is appended so graph nodes never merge.
|
|
574
|
+
*
|
|
575
|
+
* @private
|
|
576
|
+
*/
|
|
577
|
+
static labelKeys(keys) {
|
|
578
|
+
const all = [...new Set(keys)];
|
|
579
|
+
const labels = new Map();
|
|
580
|
+
const used = new Set();
|
|
581
|
+
for (const key of all) {
|
|
582
|
+
if (typeof key === 'string') {
|
|
583
|
+
labels.set(key, key);
|
|
584
|
+
used.add(key);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
for (const key of all) {
|
|
588
|
+
if (typeof key === 'string')
|
|
589
|
+
continue;
|
|
590
|
+
const base = key.toString();
|
|
591
|
+
let label = base;
|
|
592
|
+
for (let n = 2; used.has(label); n++) {
|
|
593
|
+
label = `${base}#${n}`;
|
|
594
|
+
}
|
|
595
|
+
labels.set(key, label);
|
|
596
|
+
used.add(label);
|
|
597
|
+
}
|
|
598
|
+
return labels;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Build a filterable dependency graph of this container's bindings and
|
|
602
|
+
* the dependency edges observed while resolving them.
|
|
603
|
+
*
|
|
604
|
+
* @returns A Graph snapshot you can filter/inspect
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* ```typescript
|
|
608
|
+
* const graph = container.graph();
|
|
609
|
+
* const singletons = graph.getNodes({ lifecycle: 'singleton' });
|
|
610
|
+
* const deps = graph.dependenciesOf('userService');
|
|
611
|
+
* ```
|
|
612
|
+
*/
|
|
613
|
+
graph() {
|
|
614
|
+
const raw = this.inspect();
|
|
615
|
+
const keys = [];
|
|
616
|
+
for (const node of raw.nodes) {
|
|
617
|
+
keys.push(node.key);
|
|
618
|
+
if (node.context !== undefined)
|
|
619
|
+
keys.push(node.context);
|
|
620
|
+
if (node.aliasOf !== undefined)
|
|
621
|
+
keys.push(node.aliasOf);
|
|
622
|
+
}
|
|
623
|
+
for (const edge of raw.edges) {
|
|
624
|
+
keys.push(edge.from, edge.to);
|
|
625
|
+
}
|
|
626
|
+
const labels = Container.labelKeys(keys);
|
|
627
|
+
const label = (key) => labels.get(key);
|
|
628
|
+
const nodes = raw.nodes.map((node) => {
|
|
629
|
+
const out = {
|
|
630
|
+
key: label(node.key),
|
|
631
|
+
lifecycle: node.lifecycle,
|
|
632
|
+
lazy: node.lazy,
|
|
633
|
+
inherited: node.inherited,
|
|
634
|
+
resolved: node.resolved,
|
|
635
|
+
};
|
|
636
|
+
// Preserve the node shape graph() has always produced: binding nodes
|
|
637
|
+
// carry `context` (possibly undefined), alias nodes carry `aliasOf`.
|
|
638
|
+
if ('context' in node) {
|
|
639
|
+
out.context =
|
|
640
|
+
node.context !== undefined ? label(node.context) : undefined;
|
|
641
|
+
}
|
|
642
|
+
if (node.aliasOf !== undefined)
|
|
643
|
+
out.aliasOf = label(node.aliasOf);
|
|
644
|
+
return out;
|
|
645
|
+
});
|
|
646
|
+
const edges = raw.edges.map((edge) => ({
|
|
647
|
+
from: label(edge.from),
|
|
648
|
+
to: label(edge.to),
|
|
649
|
+
}));
|
|
650
|
+
return new Graph({ nodes, edges }, labels);
|
|
651
|
+
}
|
|
445
652
|
/**
|
|
446
653
|
* Reset the container to its initial state
|
|
447
654
|
* Clears all bindings, instances, aliases, and context
|
|
@@ -457,8 +664,10 @@ export class Container {
|
|
|
457
664
|
reset() {
|
|
458
665
|
this.bindings.clear();
|
|
459
666
|
this.instances.clear();
|
|
667
|
+
this.lazyInstances.clear();
|
|
460
668
|
this.aliases.clear();
|
|
461
669
|
this.bindingCache.clear();
|
|
670
|
+
this.dependencyEdges.clear();
|
|
462
671
|
this.currentContext = undefined;
|
|
463
672
|
this.currentScope = undefined;
|
|
464
673
|
this.resolutionStack = [];
|