@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +270 -234
  3. package/dist/binding-select.d.mts +17 -6
  4. package/dist/binding-select.mjs +17 -6
  5. package/dist/binding.d.mts +167 -34
  6. package/dist/binding.mjs +111 -14
  7. package/dist/constraints.d.mts +18 -3
  8. package/dist/constraints.mjs +18 -3
  9. package/dist/container.d.mts +85 -35
  10. package/dist/container.mjs +140 -6
  11. package/dist/decorators/inject.d.mts +40 -9
  12. package/dist/decorators/inject.mjs +50 -11
  13. package/dist/decorators/injectable.d.mts +2 -1
  14. package/dist/decorators/injectable.mjs +14 -2
  15. package/dist/decorators/lifecycle-decorators.d.mts +16 -4
  16. package/dist/decorators/lifecycle-decorators.mjs +16 -4
  17. package/dist/dependency-graph.d.mts +36 -13
  18. package/dist/dependency-graph.mjs +42 -8
  19. package/dist/errors.d.mts +132 -21
  20. package/dist/errors.mjs +126 -18
  21. package/dist/graph-adapters/cytoscape.d.mts +10 -0
  22. package/dist/graph-adapters/cytoscape.mjs +40 -0
  23. package/dist/graph-adapters/dot.d.mts +9 -0
  24. package/dist/graph-adapters/dot.mjs +97 -0
  25. package/dist/graph-adapters/reactflow.d.mts +10 -0
  26. package/dist/graph-adapters/reactflow.mjs +80 -0
  27. package/dist/graph-adapters/types.d.mts +91 -0
  28. package/dist/graph-adapters/types.mjs +1 -0
  29. package/dist/index.d.mts +2 -3
  30. package/dist/index.mjs +2 -2
  31. package/dist/inspector.d.mts +42 -40
  32. package/dist/inspector.mjs +18 -169
  33. package/dist/lifecycle.d.mts +28 -6
  34. package/dist/lifecycle.mjs +29 -10
  35. package/dist/metadata/metadata-keys.d.mts +17 -6
  36. package/dist/metadata/metadata-keys.mjs +17 -6
  37. package/dist/metadata/metadata-types.d.mts +42 -18
  38. package/dist/metadata/param-registry.mjs +6 -0
  39. package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
  40. package/dist/metadata/symbol-metadata-reader.mjs +23 -4
  41. package/dist/module.d.mts +46 -2
  42. package/dist/module.mjs +19 -0
  43. package/dist/registry.d.mts +39 -8
  44. package/dist/registry.mjs +39 -8
  45. package/dist/resolver.d.mts +107 -12
  46. package/dist/resolver.mjs +134 -37
  47. package/dist/scope-validation.d.mts +3 -2
  48. package/dist/scope-validation.mjs +3 -2
  49. package/dist/scope.d.mts +38 -6
  50. package/dist/scope.mjs +42 -13
  51. package/dist/token.d.mts +9 -2
  52. package/dist/token.mjs +7 -1
  53. package/package.json +18 -2
@@ -3,15 +3,30 @@ import { ConstraintContext, Constructor } from "./binding.mjs";
3
3
 
4
4
  //#region src/constraints.d.ts
5
5
  /**
6
- * Matches when the direct parent materialization was registered for `registryKey`.
6
+ * Constraint predicate factory: matches when the direct parent on the materialization stack
7
+ * was registered under `registryKey`. Pass the result to {@link BindingBuilder.when}.
8
+ *
9
+ * @param registryKey - Token or constructor that the parent binding must be registered against.
10
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
7
11
  */
8
12
  declare function whenParentIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
9
13
  /**
10
- * Matches when any ancestor on the materialization stack was registered for `registryKey`.
14
+ * Constraint predicate factory: matches when *any* ancestor on the materialization stack
15
+ * (not just the immediate parent) was registered under `registryKey`.
16
+ * Pass the result to {@link BindingBuilder.when}.
17
+ *
18
+ * @param registryKey - Token or constructor to search for across the full construction chain.
19
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
11
20
  */
12
21
  declare function whenAnyAncestorIs(registryKey: Token<unknown> | Constructor<unknown>): (ctx: ConstraintContext) => boolean;
13
22
  /**
14
- * Matches when the immediate parent binding carries `tag` with `tagValue` (same metadata as {@link BindingBuilder.whenTagged} on the parent).
23
+ * Constraint predicate factory: matches when the immediate parent binding carries a tag
24
+ * whose key is `tag` and whose value is reference-equal to `tagValue` (`Object.is`).
25
+ * Pass the result to {@link BindingBuilder.when}.
26
+ *
27
+ * @param tag - Tag key to check on the parent binding.
28
+ * @param tagValue - Expected value; compared via `Object.is`.
29
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
15
30
  */
16
31
  declare function whenTargetTagged(tag: string, tagValue: unknown): (ctx: ConstraintContext) => boolean;
17
32
  //#endregion
@@ -1,18 +1,33 @@
1
1
  //#region src/constraints.ts
2
2
  /**
3
- * Matches when the direct parent materialization was registered for `registryKey`.
3
+ * Constraint predicate factory: matches when the direct parent on the materialization stack
4
+ * was registered under `registryKey`. Pass the result to {@link BindingBuilder.when}.
5
+ *
6
+ * @param registryKey - Token or constructor that the parent binding must be registered against.
7
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
4
8
  */
5
9
  function whenParentIs(registryKey) {
6
10
  return (ctx) => ctx.parent?.registryKey === registryKey;
7
11
  }
8
12
  /**
9
- * Matches when any ancestor on the materialization stack was registered for `registryKey`.
13
+ * Constraint predicate factory: matches when *any* ancestor on the materialization stack
14
+ * (not just the immediate parent) was registered under `registryKey`.
15
+ * Pass the result to {@link BindingBuilder.when}.
16
+ *
17
+ * @param registryKey - Token or constructor to search for across the full construction chain.
18
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
10
19
  */
11
20
  function whenAnyAncestorIs(registryKey) {
12
21
  return (ctx) => ctx.materializationStack.some((frame) => frame.registryKey === registryKey);
13
22
  }
14
23
  /**
15
- * Matches when the immediate parent binding carries `tag` with `tagValue` (same metadata as {@link BindingBuilder.whenTagged} on the parent).
24
+ * Constraint predicate factory: matches when the immediate parent binding carries a tag
25
+ * whose key is `tag` and whose value is reference-equal to `tagValue` (`Object.is`).
26
+ * Pass the result to {@link BindingBuilder.when}.
27
+ *
28
+ * @param tag - Tag key to check on the parent binding.
29
+ * @param tagValue - Expected value; compared via `Object.is`.
30
+ * @returns A predicate compatible with {@link BindingBuilder.when}.
16
31
  */
17
32
  function whenTargetTagged(tag, tagValue) {
18
33
  return (ctx) => {
@@ -1,10 +1,13 @@
1
1
  import { Token } from "./token.mjs";
2
2
  import { RegistryKey } from "./registry.mjs";
3
- import { Binding, BindingBuilder, BindingIdentifier, Constructor, ResolveHint, ResolveOptions } from "./binding.mjs";
4
- import { ContainerGraphJson, ContainerSnapshot, DotGraphOptions } from "./inspector.mjs";
3
+ import { Binding, BindingBuilder, BindingIdentifier, Constructor, ResolveHint } from "./binding.mjs";
4
+ import { ContainerGraphJson, ContainerSnapshot, GraphOptions } from "./inspector.mjs";
5
5
  import { AsyncModule, Module } from "./module.mjs";
6
6
 
7
7
  //#region src/container.d.ts
8
+ /**
9
+ * Union of sync and async modules accepted by `loadAsync` / `unloadAsync`.
10
+ */
8
11
  type ModuleLike = Module | AsyncModule;
9
12
  /**
10
13
  * Public contract for an IoC container (registry, modules, resolution, lifecycle).
@@ -14,56 +17,97 @@ type ModuleLike = Module | AsyncModule;
14
17
  * {@link Container.dispose} automatically at scope exit (TC39 Explicit Resource Management).
15
18
  */
16
19
  interface Container extends AsyncDisposable {
17
- /** Starts a fluent binding builder for the given token or constructor. */
20
+ /**
21
+ * Starts a fluent binding builder for the given token or constructor.
22
+ */
18
23
  bind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
19
- /** Removes all existing bindings for the token (with sync deactivation) then starts a fresh builder. */
24
+ /**
25
+ * Removes all existing bindings for the token (with sync deactivation) then starts a fresh builder.
26
+ */
20
27
  rebind<Value>(token: Token<Value> | Constructor<Value>): BindingBuilder<Value>;
21
- /** Removes all bindings for a token or a single binding by its {@link BindingIdentifier}; runs sync deactivation. */
28
+ /**
29
+ * Removes all bindings for a token or a single binding by its {@link BindingIdentifier}; runs sync deactivation.
30
+ */
22
31
  unbind(tokenOrId: RegistryKey | BindingIdentifier): void;
23
- /** Same as {@link unbind} but awaits async `onDeactivation` handlers before removing. */
32
+ /**
33
+ * Same as {@link unbind} but awaits async `onDeactivation` handlers before removing.
34
+ */
24
35
  unbindAsync(tokenOrId: RegistryKey | BindingIdentifier): Promise<void>;
25
- /** Returns `true` if at least one binding exists for `token`, optionally filtered by `hint`. */
36
+ /**
37
+ * Returns `true` if at least one binding exists for `token`, optionally filtered by `hint`.
38
+ */
26
39
  has(token: RegistryKey, hint?: ResolveHint): boolean;
27
- /** Resolves the token synchronously. Throws {@link AsyncResolutionError} if any binding in the chain is async. */
40
+ /**
41
+ * Resolves the token synchronously. Throws {@link AsyncResolutionError} if any binding in the chain is async.
42
+ */
28
43
  resolve<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value;
29
- /** Resolves the token, awaiting any async factory in the chain. Safe for both sync and async bindings. */
44
+ /**
45
+ * Resolves the token, awaiting any async factory in the chain. Safe for both sync and async bindings.
46
+ */
30
47
  resolveAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value>;
31
- /** Resolves all bindings registered for the token (multi-binding). Throws {@link AsyncResolutionError} if any is async. */
48
+ /**
49
+ * Resolves all bindings registered for the token (multi-binding). Throws {@link AsyncResolutionError} if any is async.
50
+ */
32
51
  resolveAll<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value[];
33
- /** Async variant of {@link resolveAll} — safe when the multi-binding set contains async factories. */
52
+ /**
53
+ * Async variant of {@link resolveAll} — safe when the multi-binding set contains async factories.
54
+ */
34
55
  resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Promise<Value[]>;
35
- /** Resolves the token or returns `undefined` if no binding is registered (never throws on missing). */
56
+ /**
57
+ * Resolves the token or returns `undefined` if no binding is registered (never throws on missing).
58
+ */
36
59
  resolveOptional<Value>(token: Token<Value> | Constructor<Value>, hint?: ResolveHint): Value | undefined;
37
- /** Registers bindings from one or more synchronous modules. Re-loading a module already present is a no-op. */
60
+ /**
61
+ * Registers bindings from one or more synchronous modules. Re-loading a module already present is a no-op.
62
+ */
38
63
  load(...modules: Module[]): void;
39
- /** Registers bindings from sync and/or async modules, awaiting each async setup in sequence. */
64
+ /**
65
+ * Registers bindings from sync and/or async modules, awaiting each async setup in sequence.
66
+ */
40
67
  loadAsync(...modules: ModuleLike[]): Promise<void>;
41
- /** Removes all bindings contributed by the given modules; runs sync deactivation on released singletons. */
68
+ /**
69
+ * Removes all bindings contributed by the given modules; runs sync deactivation on released singletons.
70
+ */
42
71
  unload(...modules: ModuleLike[]): void;
43
- /** Same as {@link unload} but awaits async `onDeactivation` handlers. */
72
+ /**
73
+ * Same as {@link unload} but awaits async `onDeactivation` handlers.
74
+ */
44
75
  unloadAsync(...modules: ModuleLike[]): Promise<void>;
45
- /** Eagerly constructs every singleton binding so the first request is never cold. */
76
+ /**
77
+ * Eagerly constructs every singleton binding so the first request is never cold.
78
+ */
46
79
  initializeAsync(): Promise<void>;
47
- /** Scans {@link getAutoRegistered} entries and binds each to its declared scope. Returns the count added. */
80
+ /**
81
+ * Scans {@link getAutoRegistered} entries and binds each to its declared scope. Returns the count added.
82
+ */
48
83
  loadAutoRegistered(): number;
49
- /** Checks for scope violations (captive dependencies). Throws {@link ScopeViolationError} on the first violation found. */
84
+ /**
85
+ * Checks for scope violations (captive dependencies). Throws {@link ScopeViolationError} on the first violation found.
86
+ */
50
87
  validate(): void;
51
- /** Returns a debug snapshot of all registered bindings and their activation state. */
88
+ /**
89
+ * Returns a debug snapshot of all registered bindings and their activation state.
90
+ */
52
91
  inspect(): ContainerSnapshot;
53
- /** Renders the dependency graph as a Graphviz DOT string (default) or a typed JSON object. */
54
- generateDependencyGraph(options?: DotGraphOptions & {
55
- format?: "dot";
56
- }): string;
57
- generateDependencyGraph(options: DotGraphOptions & {
58
- format: "json";
59
- }): ContainerGraphJson;
60
- /** Creates a child container that inherits bindings from this container without polluting its registry. */
92
+ /**
93
+ * Returns the canonical dependency graph as typed JSON (`nodes` + `edges`).
94
+ */
95
+ generateDependencyGraph(options?: GraphOptions): ContainerGraphJson;
96
+ /**
97
+ * Creates a child container that inherits bindings from this container without polluting its registry.
98
+ */
61
99
  createChild(): Container;
62
- /** @throws Always — container disposal is async; use `await using` or `await container.dispose()`. */
100
+ /**
101
+ * @throws Always — container disposal is async; use `await using` or `await container.dispose()`.
102
+ */
63
103
  [Symbol.dispose](): never;
64
- /** Returns the raw binding list for a token without triggering resolution. `undefined` means no binding. */
104
+ /**
105
+ * Returns the raw binding list for a token without triggering resolution. `undefined` means no binding.
106
+ */
65
107
  lookupBindings(token: RegistryKey): readonly Binding<unknown>[] | undefined;
66
- /** Runs all `onDeactivation` hooks on active singletons and releases all caches. */
108
+ /**
109
+ * Runs all `onDeactivation` hooks on active singletons and releases all caches.
110
+ */
67
111
  dispose(): Promise<void>;
68
112
  [Symbol.asyncDispose](): Promise<void>;
69
113
  }
@@ -71,12 +115,18 @@ interface Container extends AsyncDisposable {
71
115
  * Factory functions for {@link Container} instances (interface + namespace merge).
72
116
  */
73
117
  declare namespace Container {
74
- /** Creates an empty container with no bindings. */
118
+ /**
119
+ * Creates an empty container with no bindings.
120
+ */
75
121
  function create(): Container;
76
- /** Creates a container and immediately loads the given sync modules. */
122
+ /**
123
+ * Creates a container and immediately loads the given sync modules.
124
+ */
77
125
  function fromModules(...modules: Module[]): Container;
78
- /** Creates a container and awaits loading of sync and/or async modules. */
126
+ /**
127
+ * Creates a container and awaits loading of sync and/or async modules.
128
+ */
79
129
  function fromModulesAsync(...modules: (Module | AsyncModule)[]): Promise<Container>;
80
130
  }
81
131
  //#endregion
82
- export { type BindingIdentifier, Container, type ContainerGraphJson, type ContainerSnapshot, type ResolveOptions };
132
+ export { Container };
@@ -10,19 +10,53 @@ import { validateScopeRules } from "./scope-validation.mjs";
10
10
  import { ScopeManager } from "./scope.mjs";
11
11
  import { isDevelopmentOrTestEnvironment } from "./environment.mjs";
12
12
  //#region src/container.ts
13
+ /**
14
+ * Derives a {@link ResolveHint} from a binding's name or first tag.
15
+ * Used by {@link DefaultContainer.initializeAsync} to re-resolve named/tagged singletons
16
+ * through the standard resolution path.
17
+ */
13
18
  function resolveHintForBinding(binding) {
14
19
  if (binding.bindingName !== void 0) return { name: binding.bindingName };
15
20
  for (const [tagKey, tagValue] of binding.tags) return { tag: [tagKey, tagValue] };
16
21
  }
17
22
  /**
23
+ * Module {@link ModuleBuilder.bind}: append when the binding is disambiguated **at first
24
+ * registration** (`whenNamed` / `whenTagged` / `when` before `to*()`). Otherwise replace
25
+ * all bindings for the token (last-wins). Chaining `.whenNamed()` after `.to*()` only updates
26
+ * in place and does not enable multi-binding for subsequent module lines — use hint-before-`to*()`
27
+ * in modules (same style as `container.bind(...).whenNamed("x").to*(...)` in the package README).
28
+ */
29
+ function moduleBindingUsesMultiSlot(built) {
30
+ return built.bindingName !== void 0 || built.tags.size > 0 || built.constraint !== void 0;
31
+ }
32
+ /**
18
33
  * Default IoC container: registry + scoped caches + synchronous / asynchronous resolution.
19
34
  * @internal Implementation of {@link Container}; not part of the public package contract.
20
35
  */
21
36
  var DefaultContainer = class DefaultContainer {
37
+ /**
38
+ * Stack guard for detecting circular sync module imports during {@link ensureSyncModuleLoaded}.
39
+ */
22
40
  syncModuleStack = [];
41
+ /**
42
+ * Stack guard for detecting circular async module imports during {@link ensureAsyncModuleLoaded}.
43
+ */
23
44
  asyncModuleStack = [];
45
+ /**
46
+ * Tracks loaded modules → their binding IDs so {@link unload} / {@link unloadAsync} can
47
+ * remove exactly the bindings contributed by each module.
48
+ * Also serves as a deduplication set: a module present as a key is considered loaded.
49
+ */
24
50
  loadedModules = /* @__PURE__ */ new Map();
51
+ /**
52
+ * True after the first dev/test one-shot scope validation has run for the current registry state.
53
+ * Reset to `false` by {@link invalidateDevValidationState} on every registry mutation.
54
+ */
25
55
  devValidationRan = false;
56
+ /**
57
+ * Internal constructor for root/child instances.
58
+ * Use {@link Container.create}, {@link Container.fromModules}, or {@link createChild}.
59
+ */
26
60
  constructor(ownRegistry, ownScopeManager, parent, resolver, metadataReader) {
27
61
  this.ownRegistry = ownRegistry;
28
62
  this.ownScopeManager = ownScopeManager;
@@ -30,6 +64,10 @@ var DefaultContainer = class DefaultContainer {
30
64
  this.resolver = resolver;
31
65
  this.metadataReader = metadataReader;
32
66
  }
67
+ /**
68
+ * Creates a root container with an empty registry and fresh singleton/scoped caches.
69
+ * Wires the circular container↔resolver reference via a mutable {@link ContainerRef} holder.
70
+ */
33
71
  static create() {
34
72
  const ownRegistry = new BindingRegistry();
35
73
  const ownScopeManager = ScopeManager.createRoot();
@@ -48,7 +86,9 @@ var DefaultContainer = class DefaultContainer {
48
86
  return container;
49
87
  }
50
88
  /**
51
- * Starts a fluent binding registered on this container when {@link BindingBuilder.build} runs.
89
+ * Starts a fluent {@link BindingBuilder} for the given token or constructor.
90
+ * The binding is registered into this container's registry immediately when a
91
+ * `to*()` strategy method is called on the returned builder.
52
92
  */
53
93
  bind(token) {
54
94
  return new BindingBuilder(token, void 0, {
@@ -62,6 +102,12 @@ var DefaultContainer = class DefaultContainer {
62
102
  }
63
103
  });
64
104
  }
105
+ /**
106
+ * Fast registry presence check without instantiation.
107
+ *
108
+ * When `hint` is provided, this only verifies that at least one binding matches
109
+ * the name/tag discriminator; it does not evaluate runtime `when()` predicates.
110
+ */
65
111
  has(token, hint) {
66
112
  const list = this.lookupBindings(token);
67
113
  if (list === void 0 || list.length === 0) return false;
@@ -75,6 +121,12 @@ var DefaultContainer = class DefaultContainer {
75
121
  return true;
76
122
  });
77
123
  }
124
+ /**
125
+ * Removes bindings by token or by binding id and synchronously releases cached instances.
126
+ *
127
+ * - `binding id` path removes one binding and its cache entry.
128
+ * - `token` path removes all owned bindings for that key at once.
129
+ */
78
130
  unbind(tokenOrId) {
79
131
  this.invalidateDevValidationState();
80
132
  if (typeof tokenOrId === "string") {
@@ -86,6 +138,9 @@ var DefaultContainer = class DefaultContainer {
86
138
  if (owned !== void 0) for (const binding of owned) this.ownScopeManager.releaseBinding(binding);
87
139
  this.ownRegistry.remove(tokenOrId);
88
140
  }
141
+ /**
142
+ * Async counterpart of {@link unbind}; awaits deactivation hooks before registry removal.
143
+ */
89
144
  async unbindAsync(tokenOrId) {
90
145
  this.invalidateDevValidationState();
91
146
  if (typeof tokenOrId === "string") {
@@ -97,6 +152,11 @@ var DefaultContainer = class DefaultContainer {
97
152
  if (owned !== void 0) for (const binding of owned) await this.ownScopeManager.releaseBindingAsync(binding);
98
153
  this.ownRegistry.remove(tokenOrId);
99
154
  }
155
+ /**
156
+ * Replaces all owned bindings for `token` and returns a fresh builder.
157
+ *
158
+ * Existing cached instances for the removed bindings are synchronously released first.
159
+ */
100
160
  rebind(token) {
101
161
  this.invalidateDevValidationState();
102
162
  const owned = this.ownRegistry.get(token);
@@ -162,12 +222,18 @@ var DefaultContainer = class DefaultContainer {
162
222
  this.maybeRunDevValidationOnce();
163
223
  }
164
224
  }
165
- /** Async variant of {@link resolve}; same dev/test validation and runtime scope enforcement. */
225
+ /**
226
+ * Async variant of {@link resolve}; same dev/test validation and runtime scope enforcement.
227
+ */
166
228
  resolveAsync(key, hint) {
167
229
  return this.resolver.resolveAsyncRoot(key, hint).finally(() => {
168
230
  this.maybeRunDevValidationOnce();
169
231
  });
170
232
  }
233
+ /**
234
+ * Optional root resolution: returns `undefined` when the requested key is absent (or filtered out
235
+ * without a name/tag hint), while preserving normal errors for nested required dependencies.
236
+ */
171
237
  resolveOptional(key, hint) {
172
238
  try {
173
239
  return this.resolver.resolveOptionalRoot(key, hint);
@@ -175,6 +241,10 @@ var DefaultContainer = class DefaultContainer {
175
241
  this.maybeRunDevValidationOnce();
176
242
  }
177
243
  }
244
+ /**
245
+ * Synchronously resolves every matching binding for a key.
246
+ * Returns an empty array when no binding exists.
247
+ */
178
248
  resolveAll(key, hint) {
179
249
  try {
180
250
  return this.resolver.resolveAllRoot(key, hint);
@@ -205,9 +275,17 @@ var DefaultContainer = class DefaultContainer {
205
275
  }
206
276
  }
207
277
  }
278
+ /**
279
+ * Marks the dev/test one-shot validation as stale so the next resolve or load triggers it again.
280
+ * Called on every registry mutation (bind, unbind, rebind, load, unload).
281
+ */
208
282
  invalidateDevValidationState() {
209
283
  this.devValidationRan = false;
210
284
  }
285
+ /**
286
+ * Runs scope validation at most once per registry epoch when `NODE_ENV` is not `"production"`.
287
+ * Guards against repeated validation on successive resolves without intervening mutations.
288
+ */
211
289
  maybeRunDevValidationOnce() {
212
290
  if (!isDevelopmentOrTestEnvironment()) return;
213
291
  if (this.devValidationRan) return;
@@ -231,11 +309,16 @@ var DefaultContainer = class DefaultContainer {
231
309
  inspect() {
232
310
  return this.createInspector().getSnapshot();
233
311
  }
312
+ /**
313
+ * Delegates canonical dependency-graph generation to {@link ContainerInspector}.
314
+ */
234
315
  generateDependencyGraph(options) {
235
- const inspector = this.createInspector();
236
- if (options?.format === "json") return inspector.generateDependencyGraph(options);
237
- return inspector.generateDotGraph(options);
316
+ return this.createInspector().generateDependencyGraph(options);
238
317
  }
318
+ /**
319
+ * Registers every class collected by `@injectable({ autoRegister: true })`.
320
+ * Returns how many entries were processed.
321
+ */
239
322
  loadAutoRegistered() {
240
323
  const entries = getAutoRegistered();
241
324
  let count = 0;
@@ -257,6 +340,9 @@ var DefaultContainer = class DefaultContainer {
257
340
  [Symbol.dispose]() {
258
341
  throw new InternalError("Container disposal is async. Use `await using container = Container.create()` or call `await container.dispose()` instead of `using`.");
259
342
  }
343
+ /**
344
+ * Constructs a {@link ContainerInspector} wired to this container's full hierarchy.
345
+ */
260
346
  createInspector() {
261
347
  return new ContainerInspector({
262
348
  collectAllRegistryKeys: () => this.collectAllRegistryKeysInHierarchy(),
@@ -265,11 +351,18 @@ var DefaultContainer = class DefaultContainer {
265
351
  metadataReader: this.metadataReader
266
352
  });
267
353
  }
354
+ /**
355
+ * Collects the union of all registry keys from this container and every ancestor.
356
+ * Deduplicates by reference equality (tokens are objects).
357
+ */
268
358
  collectAllRegistryKeysInHierarchy() {
269
359
  const keys = /* @__PURE__ */ new Set();
270
360
  this.accumulateRegistryKeysFromHierarchy(keys, this);
271
361
  return [...keys];
272
362
  }
363
+ /**
364
+ * Recursive helper: walks the parent chain bottom-up, adding each level's registry keys.
365
+ */
273
366
  accumulateRegistryKeysFromHierarchy(keys, container) {
274
367
  if (container === void 0) return;
275
368
  for (const entry of container.ownRegistry.listEntries()) keys.add(entry.key);
@@ -296,22 +389,42 @@ var DefaultContainer = class DefaultContainer {
296
389
  holder.current = child;
297
390
  return child;
298
391
  }
392
+ /**
393
+ * Lookup helper with parent fallback: own bindings take precedence over parent bindings.
394
+ */
299
395
  lookupBindings(token) {
300
396
  const own = this.ownRegistry.get(token);
301
397
  if (own !== void 0 && own.length > 0) return own;
302
398
  return this.parent?.lookupBindings(token);
303
399
  }
400
+ /**
401
+ * Disposes this container's scope manager and runs async deactivation hooks.
402
+ */
304
403
  async dispose() {
305
404
  await this.ownScopeManager.disposeAsync();
306
405
  }
406
+ /**
407
+ * Async-dispose protocol hook used by `await using`.
408
+ */
307
409
  [Symbol.asyncDispose]() {
308
410
  return this.dispose();
309
411
  }
412
+ /**
413
+ * Returns a `bind` function scoped to a module. Registrations are tracked in
414
+ * {@link loadedModules} for {@link unload}.
415
+ *
416
+ * - **Last-wins** (replaces every binding for that token): `bind(token).to*(...)` with no
417
+ * `whenNamed` / `whenTagged` / `when` **before** the `to*()` call.
418
+ * - **Multi-binding** (append): call `whenNamed`, `whenTagged`, and/or `when` **before** `to*()`
419
+ * so the disambiguator exists at registration time — supports `resolveAll` and per-binding
420
+ * hints in {@link Container.initializeAsync}.
421
+ */
310
422
  bindForModule(owner) {
311
423
  return (token) => new BindingBuilder(token, owner.name, {
312
424
  register: (built) => {
313
425
  this.invalidateDevValidationState();
314
- this.ownRegistry.replaceKeyLastWins(token, built, (removed) => {
426
+ if (moduleBindingUsesMultiSlot(built)) this.ownRegistry.add(token, built);
427
+ else this.ownRegistry.replaceKeyLastWins(token, built, (removed) => {
315
428
  this.ownScopeManager.releaseBinding(removed);
316
429
  });
317
430
  this.recordBindingForModule(owner, built.id);
@@ -322,6 +435,9 @@ var DefaultContainer = class DefaultContainer {
322
435
  }
323
436
  });
324
437
  }
438
+ /**
439
+ * Appends a binding ID to the tracking list for `owner` so {@link unload} can remove it later.
440
+ */
325
441
  recordBindingForModule(owner, id) {
326
442
  const list = this.loadedModules.get(owner);
327
443
  if (list === void 0) {
@@ -330,6 +446,10 @@ var DefaultContainer = class DefaultContainer {
330
446
  }
331
447
  list.push(id);
332
448
  }
449
+ /**
450
+ * Creates the {@link ModuleBuilder} passed to a sync module's setup callback.
451
+ * The `import` method throws {@link InternalError} if an {@link AsyncModule} is passed.
452
+ */
333
453
  createSyncModuleBuilder(module) {
334
454
  return {
335
455
  import: (...deps) => {
@@ -341,6 +461,11 @@ var DefaultContainer = class DefaultContainer {
341
461
  bind: this.bindForModule(module)
342
462
  };
343
463
  }
464
+ /**
465
+ * Creates the {@link AsyncModuleBuilder} and a companion `awaitImports` thunk.
466
+ * Async sub-imports are collected into `pendingImports` and flushed after the module's
467
+ * own setup returns — this avoids interleaving setup code with dependency loading.
468
+ */
344
469
  createAsyncModuleBuilder(module) {
345
470
  const pendingImports = [];
346
471
  return {
@@ -357,6 +482,11 @@ var DefaultContainer = class DefaultContainer {
357
482
  }
358
483
  };
359
484
  }
485
+ /**
486
+ * Loads a sync module if not already loaded. Detects circular module imports via
487
+ * {@link syncModuleStack} and throws {@link CircularDependencyError} with the full cycle path.
488
+ * The module is marked as loaded *before* its setup runs so that re-entrant imports are deduped.
489
+ */
360
490
  ensureSyncModuleLoaded(module) {
361
491
  if (this.loadedModules.has(module)) return;
362
492
  if (this.syncModuleStack.includes(module)) throw new CircularDependencyError([...this.syncModuleStack.map((stackedModule) => stackedModule.name), module.name]);
@@ -369,6 +499,10 @@ var DefaultContainer = class DefaultContainer {
369
499
  this.syncModuleStack.pop();
370
500
  }
371
501
  }
502
+ /**
503
+ * Async counterpart of {@link ensureSyncModuleLoaded}: loads the module's async setup,
504
+ * then flushes any pending async sub-imports collected during setup.
505
+ */
372
506
  async ensureAsyncModuleLoaded(asyncModule) {
373
507
  if (this.loadedModules.has(asyncModule)) return;
374
508
  if (this.asyncModuleStack.includes(asyncModule)) throw new CircularDependencyError([...this.asyncModuleStack.map((stackedAsyncModule) => stackedAsyncModule.name), asyncModule.name]);
@@ -3,22 +3,53 @@ import { Constructor, ResolveHint } from "../binding.mjs";
3
3
  import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
4
4
 
5
5
  //#region src/decorators/inject.d.ts
6
- /** Options forwarded to the container when resolving an injected dependency. */
6
+ /**
7
+ * Name/tag hint forwarded to the container when resolving an injected dependency.
8
+ * Alias for {@link ResolveHint}; used as the second parameter of {@link inject} and {@link optional}.
9
+ */
7
10
  type InjectOptions = ResolveHint;
8
11
  /**
9
- * Creates an {@link InjectionDescriptor} for use in an `@injectable(deps)` array, or as an
10
- * `accessor` field decorator for post-construction property injection.
12
+ * Dual-purpose injection helper:
13
+ *
14
+ * **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
15
+ * optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
16
+ *
17
+ * ```ts
18
+ * @injectable([inject(Logger, { name: 'file' })])
19
+ * class UserService { constructor(log: Logger) {} }
20
+ * ```
21
+ *
22
+ * **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
23
+ * `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
24
+ * injection after construction.
25
+ *
26
+ * ```ts
27
+ * @inject(Logger) accessor logger!: LoggerService;
28
+ * ```
11
29
  *
12
- * As a deps-array entry: `@injectable([inject(Logger, { name: 'file' })])`
13
- * As an accessor decorator: `@inject(Logger) accessor logger!: LoggerService`
30
+ * @param token - The injection key (token or constructor) to resolve.
31
+ * @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
32
+ * `ClassAccessorDecoratorContext` automatically supplied by the runtime.
14
33
  */
15
34
  declare function inject<Value>(token: Token<Value> | Constructor<Value>, optionsOrContext?: InjectOptions | ClassAccessorDecoratorContext): InjectionDescriptor<Value>;
16
35
  /**
17
- * Same as {@link inject} but marks the dependency as optional — resolves to `undefined` instead
18
- * of throwing {@link TokenNotBoundError} when no binding exists.
36
+ * Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
37
+ * During resolution, an unbound token resolves to `undefined` instead of throwing
38
+ * {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
19
39
  */
20
40
  declare function optional<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
21
- /** Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}. */
41
+ /**
42
+ * Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
43
+ * (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
44
+ * Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
45
+ *
46
+ * Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
47
+ * callers omit options and register disambiguators on each binding instead).
48
+ */
49
+ declare function injectAll<Value>(token: Token<Value> | Constructor<Value>, options?: InjectOptions): InjectionDescriptor<Value>;
50
+ /**
51
+ * Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
52
+ */
22
53
  declare function isInjectionDescriptor(value: unknown): value is InjectionDescriptor;
23
54
  //#endregion
24
- export { InjectOptions, inject, isInjectionDescriptor, optional };
55
+ export { InjectOptions, inject, injectAll, isInjectionDescriptor, optional };