di-bag 0.5.0 → 0.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/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@ export type { ReboundProviders, ReboundSelection } from './types';
18
18
  export type { CanonicalLifetime, ChildReplacementAdmission, CheckedChildContainerLifetimes, CheckedLifetimes, LifetimeObligation, Reach } from './lifetime-types';
19
19
  export type { DiBagPolicy } from './types';
20
20
  export type { CheckDependencyCompatibility, CheckDependencyCompleteness, RegistrationEntries, OverrideFactoryContext, RegistrationsFromEntries, OverrideRegistrations, Overrides, ServicesOf, SelectedRegistrations, Selection } from './types';
21
- export type { Presence, AcquisitionMetadataPresence, AcquisitionSnapshot, RegistrationSnapshot, BindingSnapshot, GraphSnapshot } from './inspection';
21
+ export type { Presence, AcquisitionMetadataPresence, AcquisitionSnapshot, RegistrationSnapshot, BindingSnapshot, ModuleInstallationSnapshot, GraphSnapshot } from './inspection';
22
22
  export type { PositionalFactoryArguments, PositionalFactoryFunction } from './composition';
23
23
  export type { OptionalDependency, LazyDependency, DependencyReference } from './dependency-references';
24
24
  export type { PluginOutputValidator, PluginProvider } from './plugins';
@@ -53,6 +53,8 @@ export interface RegistrationSnapshot<M = Readonly<{}>, A extends readonly unkno
53
53
  * @see https://dany-fedorov.github.io/di-bag/guides/tutorial.html#attach-metadata-and-inspect-without-resolving
54
54
  */
55
55
  export interface BindingSnapshot<M = Readonly<{}>, A extends readonly unknown[] = readonly []> extends RegistrationSnapshot<M, A> {
56
+ /** Innermost module installation that introduced this binding; absent for host declarations and host replacements. */
57
+ readonly moduleInstallationId: symbol | undefined;
56
58
  /** Public names or token symbols that select this binding, in service key order; empty for a private module binding. */
57
59
  readonly serviceKeys: readonly (string | symbol)[];
58
60
  readonly lifetime: Lifetime;
@@ -65,6 +67,15 @@ export interface BindingSnapshot<M = Readonly<{}>, A extends readonly unknown[]
65
67
  readonly dependencyKind: 'required' | 'optional' | 'lazy';
66
68
  }[];
67
69
  }
70
+ /** One occurrence of a sealed module in a container graph. */
71
+ export interface ModuleInstallationSnapshot {
72
+ /** Stable identity for this occurrence in the graph. */
73
+ readonly installationId: symbol;
74
+ /** Exact label supplied when the module was sealed, if any. */
75
+ readonly moduleLabel: string | undefined;
76
+ /** Enclosing installation, if this module was installed inside another module. */
77
+ readonly parentInstallationId: symbol | undefined;
78
+ }
68
79
  /**
69
80
  * A frozen description of every binding a container can resolve, plus the edges observed so far.
70
81
  * Named dependencies read from a factory's object parameter are not knowable until the factory
@@ -73,6 +84,8 @@ export interface BindingSnapshot<M = Readonly<{}>, A extends readonly unknown[]
73
84
  */
74
85
  export interface GraphSnapshot {
75
86
  readonly containerId: symbol;
87
+ /** Module occurrences in installation order, with each parent preceding its contiguous descendants. */
88
+ readonly moduleInstallations: readonly ModuleInstallationSnapshot[];
76
89
  /** Public bindings in service key order, then contributions in group order, then remaining private bindings. */
77
90
  readonly bindings: readonly BindingSnapshot<object, readonly unknown[]>[];
78
91
  readonly contributions: readonly {
package/dist/module.d.ts CHANGED
@@ -39,6 +39,8 @@ declare class Module<ExportedServices extends object, RequiredServices extends o
39
39
  /** @internal */
40
40
  readonly [moduleInvariant]: (value: [ExportedServices, RequiredServices, Constraints, PublicProviders]) => [ExportedServices, RequiredServices, Constraints, PublicProviders];
41
41
  constructor(description: ModuleDescription);
42
+ /** The exact label supplied when this module was sealed, if any. Reading it never acquires a service. */
43
+ get moduleLabel(): string | undefined;
42
44
  /**
43
45
  * Return a module view with one string-named export renamed through an options object.
44
46
  * @param options - The current export and its noncolliding new name.
package/dist/module.js CHANGED
@@ -27,6 +27,10 @@ class Module {
27
27
  });
28
28
  Object.freeze(this);
29
29
  }
30
+ /** The exact label supplied when this module was sealed, if any. Reading it never acquires a service. */
31
+ get moduleLabel() {
32
+ return descriptions.get(this).label;
33
+ }
30
34
  /**
31
35
  * Return a module view with one string-named export renamed through an options object.
32
36
  * @param options - The current export and its noncolliding new name.
@@ -171,6 +175,19 @@ function moduleGraph(value, index) {
171
175
  throw (0, errors_1.libraryError)('DI_BAG_INVALID_MODULE', `withInstalledModules requires genuine modules: element ${index} is not one`, { operation: 'withInstalledModules', index });
172
176
  }
173
177
  const { graph, exports, label, requirementRenames } = description;
178
+ const installationId = Symbol('module installation');
179
+ const remapped = new Map();
180
+ for (const nested of graph.moduleInstallations ?? [])
181
+ remapped.set(nested.installationId, Symbol('module installation'));
182
+ const moduleInstallations = [
183
+ { installationId, moduleLabel: label, parentInstallationId: undefined },
184
+ ...(graph.moduleInstallations ?? []).map(nested => ({
185
+ installationId: remapped.get(nested.installationId),
186
+ moduleLabel: nested.moduleLabel,
187
+ parentInstallationId: nested.parentInstallationId === undefined
188
+ ? installationId : remapped.get(nested.parentInstallationId),
189
+ })),
190
+ ];
174
191
  const exported = new Set();
175
192
  for (const localKey of exports.values())
176
193
  exported.add(graph.publicSlots.get(localKey));
@@ -219,6 +236,8 @@ function moduleGraph(value, index) {
219
236
  bindings.set(fresh, {
220
237
  id: fresh, label: labelOf(id, binding), registration: binding.registration,
221
238
  localNames: localNamesFor(binding),
239
+ moduleInstallationId: binding.moduleInstallationId === undefined
240
+ ? installationId : remapped.get(binding.moduleInstallationId),
222
241
  });
223
242
  }
224
243
  const publicSlots = new Map();
@@ -232,5 +251,6 @@ function moduleGraph(value, index) {
232
251
  publicSlots,
233
252
  contributions,
234
253
  tokenKinds: new Map(graph.tokenKinds ?? []),
254
+ moduleInstallations,
235
255
  };
236
256
  }
package/dist/runtime.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ScopeAcquisitions } from './acquisition';
2
2
  import { normalize } from './registration';
3
3
  import type { ProviderOrFactory, Registrations } from './registration';
4
- import type { GraphSnapshot, RegistrationSnapshot } from './inspection';
4
+ import type { GraphSnapshot, ModuleInstallationSnapshot, RegistrationSnapshot } from './inspection';
5
5
  import type { RuntimeContext } from './acquisition-mode';
6
6
  import { type TokenKind } from './tokens';
7
7
  import type { LifetimeKind } from './lifetime';
@@ -19,12 +19,14 @@ export interface BindingDescription {
19
19
  readonly label: string;
20
20
  readonly registration: ProviderOrFactory;
21
21
  readonly localNames: ReadonlyMap<BindingKey, BindingRef>;
22
+ readonly moduleInstallationId?: symbol;
22
23
  }
23
24
  export interface GraphDescription {
24
25
  readonly bindings: ReadonlyMap<BindingId, BindingDescription>;
25
26
  readonly publicSlots: ReadonlyMap<BindingKey, BindingId>;
26
27
  readonly contributions?: ReadonlyMap<symbol, readonly BindingId[]>;
27
28
  readonly tokenKinds?: ReadonlyMap<symbol, TokenKind>;
29
+ readonly moduleInstallations?: readonly ModuleInstallationSnapshot[];
28
30
  }
29
31
  type Normalized = Readonly<ReturnType<typeof normalize>>;
30
32
  /** Immutable descriptions and path-copied lookup storage. Retained maps never escape. */
@@ -60,6 +62,8 @@ export declare class BindingGraph {
60
62
  registration(id: BindingId): Normalized;
61
63
  private requireRegistration;
62
64
  label(id: BindingId): string;
65
+ moduleInstallationId(id: BindingId): symbol | undefined;
66
+ moduleInstallations(): readonly ModuleInstallationSnapshot[];
63
67
  withPublicRegistrations(registrations: Registrations, operation?: string): BindingGraph;
64
68
  /** Replace ordered slots and prune only unreferenced public replacement history. */
65
69
  withPublicBindings(entries: readonly (readonly [BindingKey, ProviderOrFactory])[], operation?: string): BindingGraph;
package/dist/runtime.js CHANGED
@@ -12,6 +12,7 @@ const tokens_1 = require("./tokens");
12
12
  const lifetime_1 = require("./lifetime");
13
13
  const emptyContributions = Object.freeze([]);
14
14
  const emptyNames = new Map();
15
+ const emptyModuleInstallations = Object.freeze([]);
15
16
  /** Immutable descriptions and path-copied lookup storage. Retained maps never escape. */
16
17
  class BindingGraph {
17
18
  #bindings = new persistent_map_1.PersistentMap();
@@ -32,12 +33,16 @@ class BindingGraph {
32
33
  // First public registration order by key, for module snapshots. Keys are
33
34
  // never unregistered, so this retains nothing a graph would otherwise drop.
34
35
  #publicOrder;
36
+ #moduleInstallations;
35
37
  #bindingCache = new Map();
36
38
  #registrationCache = new Map();
37
39
  #publicCache = new Map();
38
40
  #contributionCache = new Map();
39
41
  #explicitlyClassified = false;
40
42
  constructor(description = { bindings: new Map(), publicSlots: new Map() }) {
43
+ if (description.moduleInstallations?.length) {
44
+ this.#moduleInstallations = { values: Object.freeze(description.moduleInstallations.map(record => Object.freeze({ ...record }))) };
45
+ }
41
46
  const declaredKinds = description.tokenKinds ?? new Map();
42
47
  const lexicalSnapshots = new Map();
43
48
  for (const [id, binding] of description.bindings) {
@@ -59,7 +64,10 @@ class BindingGraph {
59
64
  this.#lexicalUsers = this.#lexicalUsers.set(lexical.id, (this.#lexicalUsers.get(lexical.id) ?? 0) + 1);
60
65
  const entry = {
61
66
  lexical,
62
- description: Object.freeze({ id: binding.id, label: binding.label, registration: binding.registration, localNames: lexical.names }),
67
+ description: Object.freeze({
68
+ id: binding.id, label: binding.label, registration: binding.registration, localNames: lexical.names,
69
+ ...(binding.moduleInstallationId === undefined ? {} : { moduleInstallationId: binding.moduleInstallationId }),
70
+ }),
63
71
  normalized: Object.freeze((0, registration_1.normalize)(binding.registration)),
64
72
  };
65
73
  this.retainBindingTokenKinds(entry, 'withInstalledModules');
@@ -98,6 +106,7 @@ class BindingGraph {
98
106
  graph.#tokenKindOwners = this.#tokenKindOwners;
99
107
  graph.#directTokenKinds = this.#directTokenKinds;
100
108
  graph.#publicOrder = this.#publicOrder;
109
+ graph.#moduleInstallations = this.#moduleInstallations;
101
110
  return graph;
102
111
  }
103
112
  entry(id) {
@@ -253,6 +262,12 @@ class BindingGraph {
253
262
  return registration;
254
263
  }
255
264
  label(id) { return (this.#bindingCache.get(id) ?? this.entry(id)?.description)?.label ?? String(id); }
265
+ moduleInstallationId(id) {
266
+ return (this.#bindingCache.get(id) ?? this.entry(id)?.description)?.moduleInstallationId;
267
+ }
268
+ moduleInstallations() {
269
+ return this.#moduleInstallations ? (0, persistent_sequence_1.materialize)(this.#moduleInstallations) : emptyModuleInstallations;
270
+ }
256
271
  withPublicRegistrations(registrations, operation = 'register') {
257
272
  return this.withPublicBindings(Object.keys(registrations).map(key => [key, registrations[key]]), operation);
258
273
  }
@@ -360,7 +375,7 @@ class BindingGraph {
360
375
  for (const [key, kind] of this.#tokenKinds) {
361
376
  tokenKinds.set(key, kind);
362
377
  }
363
- return { bindings, publicSlots, contributions, tokenKinds };
378
+ return { bindings, publicSlots, contributions, tokenKinds, moduleInstallations: this.moduleInstallations() };
364
379
  }
365
380
  /** Every retained binding in `describe()` order, with the public keys that select it. */
366
381
  bindingSummaries() {
@@ -396,6 +411,9 @@ class BindingGraph {
396
411
  this.assertTokenKind(key, kind, operation);
397
412
  }
398
413
  const graph = this.copy();
414
+ if (description.moduleInstallations?.length) {
415
+ graph.#moduleInstallations = (0, persistent_sequence_1.append)(graph.#moduleInstallations, installation.#moduleInstallations);
416
+ }
399
417
  const pending = [];
400
418
  // Publish all incoming protection before releasing overwritten descriptions.
401
419
  for (const [id, count] of installation.#lexicalUsers)
@@ -485,6 +503,7 @@ class BagRuntime {
485
503
  const description = this.graph.registration(id);
486
504
  return Object.freeze({
487
505
  ...this.inspectBinding(id),
506
+ moduleInstallationId: this.graph.moduleInstallationId(id),
488
507
  serviceKeys: keys,
489
508
  lifetime: (0, lifetime_1.publicLifetime)(description.lifetime.kind),
490
509
  factoryReturnKind: description.factoryReturnKind,
@@ -494,6 +513,7 @@ class BagRuntime {
494
513
  });
495
514
  return Object.freeze({
496
515
  containerId: this.acquisitions.ownerId,
516
+ moduleInstallations: this.graph.moduleInstallations(),
497
517
  bindings: Object.freeze(bindings),
498
518
  contributions: this.graph.contributionGroups(),
499
519
  observedEdges: this.acquisitions.observedEdges(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "di-bag",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "DI container for agent-built codebases: the compiler checks the wiring, modules stay private, resources get cleaned up.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",