@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.
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAEH,sBAAsB;AACtB,yCAAwC;AAA/B,sGAAA,SAAS,OAAA;AAClB,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,iCAAgC;AAAvB,8FAAA,KAAK,OAAA"}
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"}
@@ -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.instances = new Map();
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 Map();
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
- * Generates a consistent string key for caching, handling both strings and symbols.
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
- _toCacheKey(key, context) {
46
- const keyStr = key.toString();
47
- if (context) {
48
- return `${keyStr}::${context.toString()}`;
49
- }
50
- return keyStr;
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(cacheKey)) {
232
- return this.bindingCache.get(cacheKey);
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
- // Cache the result only if no conditions (conditions are dynamic)
245
- if (match && !match.condition) {
246
- this.bindingCache.set(cacheKey, match);
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.findBinding(key, context);
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 binding = this.findBinding(key, this.currentContext);
285
- if (!binding) {
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
- return this.resolveSingleton(binding, realKey);
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 we use a string-based key for the instance map.
326
- const fullCacheKey = this._toCacheKey(cacheKey, binding.context);
327
- if (this.instances.has(fullCacheKey)) {
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(fullCacheKey, instance);
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 fullCacheKey = `lazy::${this._toCacheKey(cacheKey)}`;
355
- if (this.instances.has(fullCacheKey)) {
356
- return this.instances.get(fullCacheKey);
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.instances.set(fullCacheKey, lazy);
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 = [];