@codefast/di 0.3.13-canary.4 → 0.3.14-canary.0

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 (45) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +262 -235
  3. package/dist/binding-select.d.mts +17 -6
  4. package/dist/binding-select.mjs +17 -6
  5. package/dist/binding.d.mts +148 -23
  6. package/dist/binding.mjs +103 -14
  7. package/dist/constraints.d.mts +18 -3
  8. package/dist/constraints.mjs +18 -3
  9. package/dist/container.d.mts +81 -26
  10. package/dist/container.mjs +91 -3
  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 +31 -8
  18. package/dist/dependency-graph.mjs +42 -8
  19. package/dist/errors.d.mts +124 -13
  20. package/dist/errors.mjs +126 -18
  21. package/dist/index.d.mts +2 -2
  22. package/dist/index.mjs +2 -2
  23. package/dist/inspector.d.mts +38 -14
  24. package/dist/inspector.mjs +36 -15
  25. package/dist/lifecycle.d.mts +28 -6
  26. package/dist/lifecycle.mjs +29 -10
  27. package/dist/metadata/metadata-keys.d.mts +17 -6
  28. package/dist/metadata/metadata-keys.mjs +17 -6
  29. package/dist/metadata/metadata-types.d.mts +29 -5
  30. package/dist/metadata/param-registry.mjs +6 -0
  31. package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
  32. package/dist/metadata/symbol-metadata-reader.mjs +23 -4
  33. package/dist/module.d.mts +34 -2
  34. package/dist/module.mjs +19 -0
  35. package/dist/registry.d.mts +39 -8
  36. package/dist/registry.mjs +39 -8
  37. package/dist/resolver.d.mts +107 -12
  38. package/dist/resolver.mjs +134 -37
  39. package/dist/scope-validation.d.mts +3 -2
  40. package/dist/scope-validation.mjs +3 -2
  41. package/dist/scope.d.mts +34 -6
  42. package/dist/scope.mjs +38 -13
  43. package/dist/token.d.mts +9 -2
  44. package/dist/token.mjs +7 -1
  45. package/package.json +2 -2
@@ -1,8 +1,14 @@
1
1
  import { CODEFAST_DI_LIFECYCLE_METADATA } from "../metadata/metadata-keys.mjs";
2
2
  //#region src/decorators/lifecycle-decorators.ts
3
3
  /**
4
- * Stage 3 method decorator: marks a method to be called after the class is instantiated by the container.
5
- * Order: construct → `@postConstruct()` → `.onActivation()` → cache.
4
+ * Stage 3 method decorator: marks a method to be called after the class is instantiated
5
+ * by the container and before the `onActivation` hook runs.
6
+ *
7
+ * Lifecycle order: `new Class(…)` → **`@postConstruct()`** → `onActivation()` → scope cache.
8
+ *
9
+ * Only one method per class may carry this decorator; a second application throws.
10
+ * If the decorated method returns a `Promise` during synchronous resolution,
11
+ * {@link AsyncResolutionError} is thrown — use `Container.resolveAsync()` instead.
6
12
  */
7
13
  function postConstruct() {
8
14
  return (_target, context) => {
@@ -16,8 +22,14 @@ function postConstruct() {
16
22
  };
17
23
  }
18
24
  /**
19
- * Stage 3 method decorator: marks a method to be called before the instance is destroyed by the container.
20
- * Order: `.onDeactivation()` → `@preDestroy()`.
25
+ * Stage 3 method decorator: marks a method to be called when the container disposes or
26
+ * unloads the owning binding.
27
+ *
28
+ * Lifecycle order: `onDeactivation()` → **`@preDestroy()`**.
29
+ *
30
+ * Only one method per class may carry this decorator; a second application throws.
31
+ * If the decorated method returns a `Promise` during synchronous disposal,
32
+ * an error is thrown — use `Container.disposeAsync()` instead.
21
33
  */
22
34
  function preDestroy() {
23
35
  return (_target, context) => {
@@ -3,27 +3,50 @@ import { Binding, BindingIdentifier, ResolveHint } from "./binding.mjs";
3
3
  import { MetadataReader } from "./metadata/metadata-types.mjs";
4
4
 
5
5
  //#region src/dependency-graph.d.ts
6
- /** A directed edge in the static dependency graph produced by {@link collectStaticDependencyEdges}. */
6
+ /**
7
+ * A directed edge in the static dependency graph produced by {@link collectStaticDependencyEdges}.
8
+ */
7
9
  type StaticDependencyEdge = {
8
10
  readonly fromBindingId: BindingIdentifier;
9
11
  readonly toBindingId: BindingIdentifier;
10
12
  readonly resolutionPath: readonly string[];
11
- readonly edgeKind: "sync" | "async"; /** True when the resolved target binding carries a {@link BindingBuilder.when} predicate (runtime may skip this edge). */
12
- readonly toBindingConditional: boolean; /** Constructor inject hint for this edge (named / tagged), when known statically. */
13
- readonly injectHintLabel?: string; /** True when the consumer binding is an alias (rebind to another token). */
13
+ readonly edgeKind: "sync" | "async";
14
+ /**
15
+ * True when the resolved target binding carries a {@link BindingBuilder.when} predicate (runtime may skip this edge).
16
+ */
17
+ readonly toBindingConditional: boolean;
18
+ /**
19
+ * Constructor inject hint for this edge (named / tagged), when known statically.
20
+ */
21
+ readonly injectHintLabel?: string;
22
+ /**
23
+ * True when the consumer binding is an alias (rebind to another token).
24
+ */
14
25
  readonly isAliasEdge: boolean;
15
26
  };
16
- /** A single resolved dependency entry produced by {@link listResolvedDependencies}. */
27
+ /**
28
+ * A single resolved dependency entry produced by {@link listResolvedDependencies}.
29
+ */
17
30
  type ResolvedDependency = {
18
31
  readonly binding: Binding<unknown>;
19
32
  readonly path: readonly string[];
20
33
  readonly injectHintLabel?: string;
21
34
  };
22
- /** Converts a {@link ResolveHint} to a human-readable edge label for graph output (`name: x` / `tag: k=v`). */
35
+ /**
36
+ * Converts a {@link ResolveHint} to a human-readable edge label for graph output (`name: x` / `tag: k=v`).
37
+ */
23
38
  declare function injectHintLabelFromResolveHint(hint: ResolveHint | undefined): string | undefined;
24
39
  /**
25
- * Lists direct static dependencies (constructor metadata, `toResolved` tokens, alias targets).
26
- * Factories (`toDynamic` / `toAsyncDynamic`) have no enumerable dependency keys.
40
+ * Lists the direct static dependencies of `consumer` by inspecting binding metadata.
41
+ *
42
+ * - `constant` / `dynamic` / `async-dynamic` — no enumerable deps (empty array).
43
+ * - `alias` — single dependency on the alias target (chased through alias chains).
44
+ * - `resolved` — one dependency per entry in `dependencyTokens`; a missing binding
45
+ * always throws {@link InternalError} (there is no optional concept for `resolved`).
46
+ * - `class` — one dependency per `@injectable()` constructor parameter
47
+ * (requires a {@link MetadataReader}). Parameters marked `optional` whose token
48
+ * has no binding are silently skipped; non-optional missing tokens throw
49
+ * {@link InternalError}.
27
50
  */
28
51
  declare function listResolvedDependencies(consumer: Binding<unknown>, lookup: (key: RegistryKey) => readonly Binding<unknown>[] | undefined, reader: MetadataReader | undefined, pathPrefix: readonly string[]): readonly ResolvedDependency[];
29
52
  /**
@@ -1,7 +1,9 @@
1
1
  import { InternalError } from "./errors.mjs";
2
- import { registryKeyLabel, selectBindingForRegistry } from "./binding-select.mjs";
2
+ import { filterMatchingBindings, registryKeyLabel, selectBindingForRegistry } from "./binding-select.mjs";
3
3
  //#region src/dependency-graph.ts
4
- /** Converts a tag value to a printable string for graph edge labels. */
4
+ /**
5
+ * Converts a tag value to a printable string for graph edge labels.
6
+ */
5
7
  function formatTagValueForGraph(value) {
6
8
  if (typeof value === "string") return value;
7
9
  try {
@@ -10,7 +12,9 @@ function formatTagValueForGraph(value) {
10
12
  return String(value);
11
13
  }
12
14
  }
13
- /** Converts a {@link ResolveHint} to a human-readable edge label for graph output (`name: x` / `tag: k=v`). */
15
+ /**
16
+ * Converts a {@link ResolveHint} to a human-readable edge label for graph output (`name: x` / `tag: k=v`).
17
+ */
14
18
  function injectHintLabelFromResolveHint(hint) {
15
19
  if (hint === void 0) return;
16
20
  if (hint.name !== void 0) return `name: ${hint.name}`;
@@ -19,7 +23,9 @@ function injectHintLabelFromResolveHint(hint) {
19
23
  return `tag: ${tagKey}=${formatTagValueForGraph(tagValue)}`;
20
24
  }
21
25
  }
22
- /** Returns `"async"` when either the consumer or dependency binding is an `async-dynamic` factory. */
26
+ /**
27
+ * Returns `"async"` when either the consumer or dependency binding is an `async-dynamic` factory.
28
+ */
23
29
  function edgeKindFor(consumer, dependency) {
24
30
  if (consumer.kind === "async-dynamic" || dependency.kind === "async-dynamic") return "async";
25
31
  return "sync";
@@ -37,7 +43,13 @@ function resolveDefaultBinding(lookup, depKey, pathPrefix) {
37
43
  }
38
44
  /**
39
45
  * Follows alias bindings until a non-alias binding is reached.
40
- * Returns the last reachable binding; stops if an alias target is unregistered.
46
+ * Returns the last reachable binding; stops early if an alias target is unregistered.
47
+ *
48
+ * **Warning:** This is a static walk with no cycle detection (`visiting` set). If the
49
+ * registry contains a cyclic alias chain (A → B → A), this function will loop
50
+ * indefinitely. The runtime resolver prevents such cycles via its own `visiting`
51
+ * guard, but callers invoking `expandAliasChain` on a manually-constructed or
52
+ * corrupted registry must ensure alias chains are acyclic.
41
53
  */
42
54
  function expandAliasChain(lookup, start, pathPrefix) {
43
55
  let current = start;
@@ -52,8 +64,16 @@ function expandAliasChain(lookup, start, pathPrefix) {
52
64
  return current;
53
65
  }
54
66
  /**
55
- * Lists direct static dependencies (constructor metadata, `toResolved` tokens, alias targets).
56
- * Factories (`toDynamic` / `toAsyncDynamic`) have no enumerable dependency keys.
67
+ * Lists the direct static dependencies of `consumer` by inspecting binding metadata.
68
+ *
69
+ * - `constant` / `dynamic` / `async-dynamic` — no enumerable deps (empty array).
70
+ * - `alias` — single dependency on the alias target (chased through alias chains).
71
+ * - `resolved` — one dependency per entry in `dependencyTokens`; a missing binding
72
+ * always throws {@link InternalError} (there is no optional concept for `resolved`).
73
+ * - `class` — one dependency per `@injectable()` constructor parameter
74
+ * (requires a {@link MetadataReader}). Parameters marked `optional` whose token
75
+ * has no binding are silently skipped; non-optional missing tokens throw
76
+ * {@link InternalError}.
57
77
  */
58
78
  function listResolvedDependencies(consumer, lookup, reader, pathPrefix) {
59
79
  switch (consumer.kind) {
@@ -90,6 +110,20 @@ function listResolvedDependencies(consumer, lookup, reader, pathPrefix) {
90
110
  const tok = param.token;
91
111
  const label = registryKeyLabel(tok);
92
112
  const nextPath = [...pathPrefix, label];
113
+ const paramHint = param.name !== void 0 ? { name: param.name } : param.tag !== void 0 ? { tag: param.tag } : void 0;
114
+ if (param.all === true) {
115
+ const bindings = lookup(tok);
116
+ if (bindings === void 0 || bindings.length === 0) return [];
117
+ const candidates = filterMatchingBindings(bindings, paramHint, void 0);
118
+ if (candidates.length === 0) return [];
119
+ return candidates.map((binding) => {
120
+ return {
121
+ binding: expandAliasChain(lookup, binding, nextPath),
122
+ path: nextPath,
123
+ injectHintLabel: injectHintLabelFromResolveHint(paramHint)
124
+ };
125
+ });
126
+ }
93
127
  const binding = resolveDefaultBinding(lookup, tok, pathPrefix);
94
128
  if (binding === void 0) {
95
129
  if (param.optional) return [];
@@ -98,7 +132,7 @@ function listResolvedDependencies(consumer, lookup, reader, pathPrefix) {
98
132
  return [{
99
133
  binding: expandAliasChain(lookup, binding, nextPath),
100
134
  path: nextPath,
101
- injectHintLabel: injectHintLabelFromResolveHint(param.name !== void 0 ? { name: param.name } : param.tag !== void 0 ? { tag: param.tag } : void 0)
135
+ injectHintLabel: injectHintLabelFromResolveHint(paramHint)
102
136
  }];
103
137
  });
104
138
  }
package/dist/errors.d.mts CHANGED
@@ -2,74 +2,177 @@ import { Binding, BindingIdentifier, BindingScope, ResolveHint } from "./binding
2
2
 
3
3
  //#region src/errors.d.ts
4
4
  /**
5
- * Base error for all `@codefast/di` failures. Subclasses expose a stable, machine-readable `code`.
5
+ * Formats a resolution path array into a human-readable `"A -> B -> C"` string.
6
+ */
7
+ declare function formatResolutionPath(resolutionPath: readonly string[]): string;
8
+ /**
9
+ * Base error for all `@codefast/di` failures.
10
+ *
11
+ * Every concrete subclass exposes a stable, machine-readable {@link DiError.code} property
12
+ * (e.g. `"TOKEN_NOT_BOUND"`) so consumers can `switch` on error type without relying
13
+ * on `instanceof` across package versions.
6
14
  */
7
15
  declare abstract class DiError extends Error {
16
+ /**
17
+ * Machine-readable error code, constant per subclass (e.g. `"TOKEN_NOT_BOUND"`).
18
+ */
8
19
  abstract readonly code: string;
9
20
  constructor(message: string, options?: ErrorOptions);
10
21
  }
11
22
  /**
12
23
  * Raised for internal programming errors — invalid library usage or unexpected state that
13
- * indicates a bug in the caller (e.g. accessing an uninitialized container, misconfigured binding).
24
+ * indicates a bug in the caller or the library itself.
25
+ *
26
+ * Examples: double `to*()` call on a {@link BindingBuilder}, ambiguous binding resolution
27
+ * with multiple candidates, or scope mutation on a constant binding.
28
+ *
29
+ * Code: `"INTERNAL_ERROR"`
14
30
  */
15
31
  declare class InternalError extends DiError {
16
32
  readonly code = "INTERNAL_ERROR";
17
33
  }
18
34
  /**
19
- * Raised when a name/tag filter matches no binding although other bindings exist for the token.
35
+ * Raised when bindings exist for the token but none match the provided name/tag hint.
36
+ * Distinguishes from {@link TokenNotBoundError} (no bindings at all).
37
+ *
38
+ * Thrown when a `{ name }` or `{ tag }` hint is specified but no registered binding satisfies it
39
+ * (e.g. `Container.resolve`, `Container.resolveAsync`, `Container.resolveOptional`,
40
+ * `Container.resolveAll`, `Container.resolveAllAsync`, and binding selection helpers such as
41
+ * {@link selectBindingForRegistry}).
42
+ *
43
+ * Code: `"NO_MATCHING_BINDING"`
20
44
  */
21
45
  declare class NoMatchingBindingError extends DiError {
22
46
  readonly code = "NO_MATCHING_BINDING";
47
+ /**
48
+ * The `Token.name` or `Constructor.name` that was resolved.
49
+ */
23
50
  readonly tokenName: string;
51
+ /**
52
+ * The name/tag hint that failed to match any binding.
53
+ */
24
54
  readonly hint: ResolveHint;
55
+ /**
56
+ * Label path from the resolution root to the failing token.
57
+ */
25
58
  readonly resolutionPath: readonly string[];
26
59
  constructor(tokenName: string, hint: ResolveHint, resolutionPath: readonly string[], options?: ErrorOptions);
27
60
  }
28
61
  /**
29
- * Raised when resolving a value for a token that has no binding.
62
+ * Raised when no binding exists for the requested token or constructor.
63
+ *
64
+ * Thrown by `Container.resolve`, `Container.resolveAsync`, and during transitive
65
+ * dependency resolution when a required token has never been registered.
66
+ *
67
+ * Note on optional resolution:
68
+ * - Root-level `Container.resolveOptional` returns `undefined` when **that key** has no
69
+ * registry entries (it never throws this error for the root key in that case). Missing
70
+ * **transitive** dependencies still throw during instantiation.
71
+ * - `ResolutionContext.resolveOptional` (used inside factories) catches this error
72
+ * internally to return `undefined` for missing dependencies.
73
+ *
74
+ * Code: `"TOKEN_NOT_BOUND"`
30
75
  */
31
76
  declare class TokenNotBoundError extends DiError {
32
77
  readonly code = "TOKEN_NOT_BOUND";
78
+ /**
79
+ * The `Token.name` or `Constructor.name` that could not be found.
80
+ */
33
81
  readonly tokenName: string;
82
+ /**
83
+ * Label path from the resolution root to the missing token.
84
+ */
34
85
  readonly resolutionPath: readonly string[];
35
86
  constructor(tokenName: string, resolutionPath: readonly string[], options?: ErrorOptions);
36
87
  }
37
88
  /**
38
- * Raised when the dependency graph contains a cycle during resolution.
89
+ * Raised when a token is encountered a second time on the same resolution call stack,
90
+ * indicating a cyclic dependency (A → B → … → A).
91
+ *
92
+ * Also raised during module loading when `import()` forms a cycle between modules.
93
+ *
94
+ * Code: `"CIRCULAR_DEPENDENCY"`
39
95
  */
40
96
  declare class CircularDependencyError extends DiError {
41
97
  readonly code = "CIRCULAR_DEPENDENCY";
98
+ /**
99
+ * Full label path including the repeated token at the end.
100
+ */
42
101
  readonly resolutionPath: readonly string[];
102
+ /**
103
+ * Mutable copy of {@link resolutionPath} for consumer convenience.
104
+ */
43
105
  readonly cycle: string[];
44
106
  constructor(resolutionPath: readonly string[], options?: ErrorOptions);
45
107
  }
46
108
  /**
47
- * Raised when a class binding requires `@injectable()` / `Symbol.metadata` constructor metadata but none is present.
109
+ * Raised when the container's {@link MetadataReader} is **configured** and reports no
110
+ * constructor metadata for a `class` binding whose implementation has `arity > 0`.
111
+ *
112
+ * If `metadataReader` is `undefined`, the resolver calls `new ImplementationClass()` without
113
+ * this check — this error is **not** thrown in that configuration.
114
+ *
115
+ * Fix: add `@injectable([...deps])` on the class (so `getConstructorMetadata` returns params),
116
+ * or omit constructor parameters if you intentionally run without a reader.
117
+ *
118
+ * Code: `"MISSING_METADATA"`
48
119
  */
49
120
  declare class MissingMetadataError extends DiError {
50
121
  readonly code = "MISSING_METADATA";
122
+ /**
123
+ * Name of the class that is missing `@injectable()` metadata.
124
+ */
51
125
  readonly className: string;
126
+ /**
127
+ * Label path from the resolution root to the class binding.
128
+ */
52
129
  readonly resolutionPath: readonly string[];
53
130
  constructor(className: string, resolutionPath: readonly string[], options?: ErrorOptions);
54
131
  }
55
- /** Raised when `load()` is used with an async module. */
132
+ /**
133
+ * Raised when the synchronous `Container.load()` is called with an {@link AsyncModule}.
134
+ * Use `Container.loadAsync()` or `Container.fromModulesAsync()` instead.
135
+ *
136
+ * Code: `"ASYNC_MODULE_LOAD"`
137
+ */
56
138
  declare class AsyncModuleLoadError extends DiError {
57
139
  readonly code = "ASYNC_MODULE_LOAD";
140
+ /**
141
+ * Name of the async module that was passed to the sync loader.
142
+ */
58
143
  readonly moduleName: string;
59
144
  constructor(moduleName: string, options?: ErrorOptions);
60
145
  }
61
146
  /**
62
- * Raised when `resolve()` is called on a binding chain that contains an async factory or
63
- * an async `onActivation` handler. Use `resolveAsync()` / `resolveAllAsync()` instead.
147
+ * Raised when synchronous `Container.resolve()` / `Container.resolveAll()` encounters an
148
+ * async operation: an `async-dynamic` factory, a `toDynamic` factory that returns a Promise,
149
+ * an `onActivation` handler that returns a Promise, or a `@postConstruct` method that
150
+ * returns a Promise.
151
+ *
152
+ * Fix: switch to `Container.resolveAsync()` / `Container.resolveAllAsync()`.
153
+ *
154
+ * Code: `"ASYNC_RESOLUTION"`
64
155
  */
65
156
  declare class AsyncResolutionError extends DiError {
66
157
  readonly code = "ASYNC_RESOLUTION";
158
+ /**
159
+ * Token or class name that triggered the async path.
160
+ */
67
161
  readonly tokenName: string;
162
+ /**
163
+ * Label path from the resolution root to the async binding.
164
+ */
68
165
  readonly resolutionPath: readonly string[];
166
+ /**
167
+ * Human-readable description of why async resolution was required.
168
+ */
69
169
  readonly reason: string;
70
170
  constructor(tokenName: string, resolutionPath: readonly string[], reason: string, options?: ErrorOptions);
71
171
  }
72
- /** Structured payload attached to {@link ScopeViolationError}. */
172
+ /**
173
+ * Structured payload passed to the {@link ScopeViolationError} constructor.
174
+ * Carries identities and scopes of both the long-lived consumer and the shorter-lived dependency.
175
+ */
73
176
  type ScopeViolationDetails = {
74
177
  readonly consumerBindingId: BindingIdentifier;
75
178
  readonly consumerKind: Binding<unknown>["kind"];
@@ -82,8 +185,16 @@ type ScopeViolationDetails = {
82
185
  readonly resolutionPath: readonly string[];
83
186
  };
84
187
  /**
85
- * Raised when a long-lived binding would capture a shorter-lived (scoped or transient) dependency
86
- * (captive dependency). Constant value dependencies are excluded.
188
+ * Raised for a **captive dependency**: a singleton consumer resolves (or would resolve) a
189
+ * non-constant binding whose lifetime is `scoped` or `transient`. Constant bindings are exempt.
190
+ *
191
+ * - **Runtime:** each resolution step checks the parent on the materialization stack, so
192
+ * violations are detected along the actual construction chain.
193
+ * - **`Container.validate()`:** {@link validateScopeRules} walks **direct** static edges from
194
+ * {@link listResolvedDependencies} only — it does not recursively expand the whole graph,
195
+ * so it may miss violations that appear only deeper in the dependency tree.
196
+ *
197
+ * Code: `"SCOPE_VIOLATION"`
87
198
  */
88
199
  declare class ScopeViolationError extends DiError {
89
200
  readonly code = "SCOPE_VIOLATION";
@@ -97,4 +208,4 @@ declare class ScopeViolationError extends DiError {
97
208
  constructor(details: ScopeViolationDetails, options?: ErrorOptions);
98
209
  }
99
210
  //#endregion
100
- export { AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, DiError, InternalError, MissingMetadataError, NoMatchingBindingError, ScopeViolationDetails, ScopeViolationError, TokenNotBoundError };
211
+ export { AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, DiError, InternalError, MissingMetadataError, NoMatchingBindingError, ScopeViolationDetails, ScopeViolationError, TokenNotBoundError, formatResolutionPath };
package/dist/errors.mjs CHANGED
@@ -1,9 +1,18 @@
1
1
  //#region src/errors.ts
2
- /** Formats a resolution path array into a human-readable `"A -> B -> C"` string. */
2
+ /**
3
+ * Formats a resolution path array into a human-readable `"A -> B -> C"` string.
4
+ */
3
5
  function formatResolutionPath(resolutionPath) {
4
6
  return resolutionPath.length > 0 ? resolutionPath.join(" -> ") : "(empty)";
5
7
  }
6
- /** Serializes a {@link ResolveHint} to a debug string; never throws even for exotic values. */
8
+ const SCOPE_LABELS = {
9
+ singleton: "Singleton",
10
+ scoped: "Scoped",
11
+ transient: "Transient"
12
+ };
13
+ /**
14
+ * Serializes a {@link ResolveHint} to a debug string; never throws even for exotic values.
15
+ */
7
16
  function safeSerializeHint(hint) {
8
17
  if (hint === void 0) return "(none)";
9
18
  try {
@@ -19,7 +28,11 @@ function safeSerializeHint(hint) {
19
28
  }
20
29
  }
21
30
  /**
22
- * Base error for all `@codefast/di` failures. Subclasses expose a stable, machine-readable `code`.
31
+ * Base error for all `@codefast/di` failures.
32
+ *
33
+ * Every concrete subclass exposes a stable, machine-readable {@link DiError.code} property
34
+ * (e.g. `"TOKEN_NOT_BOUND"`) so consumers can `switch` on error type without relying
35
+ * on `instanceof` across package versions.
23
36
  */
24
37
  var DiError = class extends Error {
25
38
  constructor(message, options) {
@@ -29,21 +42,43 @@ var DiError = class extends Error {
29
42
  };
30
43
  /**
31
44
  * Raised for internal programming errors — invalid library usage or unexpected state that
32
- * indicates a bug in the caller (e.g. accessing an uninitialized container, misconfigured binding).
45
+ * indicates a bug in the caller or the library itself.
46
+ *
47
+ * Examples: double `to*()` call on a {@link BindingBuilder}, ambiguous binding resolution
48
+ * with multiple candidates, or scope mutation on a constant binding.
49
+ *
50
+ * Code: `"INTERNAL_ERROR"`
33
51
  */
34
52
  var InternalError = class extends DiError {
35
53
  code = "INTERNAL_ERROR";
36
54
  };
37
55
  /**
38
- * Raised when a name/tag filter matches no binding although other bindings exist for the token.
56
+ * Raised when bindings exist for the token but none match the provided name/tag hint.
57
+ * Distinguishes from {@link TokenNotBoundError} (no bindings at all).
58
+ *
59
+ * Thrown when a `{ name }` or `{ tag }` hint is specified but no registered binding satisfies it
60
+ * (e.g. `Container.resolve`, `Container.resolveAsync`, `Container.resolveOptional`,
61
+ * `Container.resolveAll`, `Container.resolveAllAsync`, and binding selection helpers such as
62
+ * {@link selectBindingForRegistry}).
63
+ *
64
+ * Code: `"NO_MATCHING_BINDING"`
39
65
  */
40
66
  var NoMatchingBindingError = class extends DiError {
41
67
  code = "NO_MATCHING_BINDING";
68
+ /**
69
+ * The `Token.name` or `Constructor.name` that was resolved.
70
+ */
42
71
  tokenName;
72
+ /**
73
+ * The name/tag hint that failed to match any binding.
74
+ */
43
75
  hint;
76
+ /**
77
+ * Label path from the resolution root to the failing token.
78
+ */
44
79
  resolutionPath;
45
80
  constructor(tokenName, hint, resolutionPath, options) {
46
- const pathText = resolutionPath.length > 0 ? resolutionPath.join(" -> ") : "(empty)";
81
+ const pathText = formatResolutionPath(resolutionPath);
47
82
  const hintText = safeSerializeHint(hint);
48
83
  super(`No binding matched resolve options ${hintText} for token "${tokenName}" (resolution path: ${pathText})`, options);
49
84
  this.tokenName = tokenName;
@@ -52,11 +87,29 @@ var NoMatchingBindingError = class extends DiError {
52
87
  }
53
88
  };
54
89
  /**
55
- * Raised when resolving a value for a token that has no binding.
90
+ * Raised when no binding exists for the requested token or constructor.
91
+ *
92
+ * Thrown by `Container.resolve`, `Container.resolveAsync`, and during transitive
93
+ * dependency resolution when a required token has never been registered.
94
+ *
95
+ * Note on optional resolution:
96
+ * - Root-level `Container.resolveOptional` returns `undefined` when **that key** has no
97
+ * registry entries (it never throws this error for the root key in that case). Missing
98
+ * **transitive** dependencies still throw during instantiation.
99
+ * - `ResolutionContext.resolveOptional` (used inside factories) catches this error
100
+ * internally to return `undefined` for missing dependencies.
101
+ *
102
+ * Code: `"TOKEN_NOT_BOUND"`
56
103
  */
57
104
  var TokenNotBoundError = class extends DiError {
58
105
  code = "TOKEN_NOT_BOUND";
106
+ /**
107
+ * The `Token.name` or `Constructor.name` that could not be found.
108
+ */
59
109
  tokenName;
110
+ /**
111
+ * Label path from the resolution root to the missing token.
112
+ */
60
113
  resolutionPath;
61
114
  constructor(tokenName, resolutionPath, options) {
62
115
  const pathText = formatResolutionPath(resolutionPath);
@@ -66,11 +119,22 @@ var TokenNotBoundError = class extends DiError {
66
119
  }
67
120
  };
68
121
  /**
69
- * Raised when the dependency graph contains a cycle during resolution.
122
+ * Raised when a token is encountered a second time on the same resolution call stack,
123
+ * indicating a cyclic dependency (A → B → … → A).
124
+ *
125
+ * Also raised during module loading when `import()` forms a cycle between modules.
126
+ *
127
+ * Code: `"CIRCULAR_DEPENDENCY"`
70
128
  */
71
129
  var CircularDependencyError = class extends DiError {
72
130
  code = "CIRCULAR_DEPENDENCY";
131
+ /**
132
+ * Full label path including the repeated token at the end.
133
+ */
73
134
  resolutionPath;
135
+ /**
136
+ * Mutable copy of {@link resolutionPath} for consumer convenience.
137
+ */
74
138
  cycle;
75
139
  constructor(resolutionPath, options) {
76
140
  const pathText = formatResolutionPath(resolutionPath);
@@ -80,11 +144,26 @@ var CircularDependencyError = class extends DiError {
80
144
  }
81
145
  };
82
146
  /**
83
- * Raised when a class binding requires `@injectable()` / `Symbol.metadata` constructor metadata but none is present.
147
+ * Raised when the container's {@link MetadataReader} is **configured** and reports no
148
+ * constructor metadata for a `class` binding whose implementation has `arity > 0`.
149
+ *
150
+ * If `metadataReader` is `undefined`, the resolver calls `new ImplementationClass()` without
151
+ * this check — this error is **not** thrown in that configuration.
152
+ *
153
+ * Fix: add `@injectable([...deps])` on the class (so `getConstructorMetadata` returns params),
154
+ * or omit constructor parameters if you intentionally run without a reader.
155
+ *
156
+ * Code: `"MISSING_METADATA"`
84
157
  */
85
158
  var MissingMetadataError = class extends DiError {
86
159
  code = "MISSING_METADATA";
160
+ /**
161
+ * Name of the class that is missing `@injectable()` metadata.
162
+ */
87
163
  className;
164
+ /**
165
+ * Label path from the resolution root to the class binding.
166
+ */
88
167
  resolutionPath;
89
168
  constructor(className, resolutionPath, options) {
90
169
  const pathText = formatResolutionPath(resolutionPath);
@@ -93,9 +172,17 @@ var MissingMetadataError = class extends DiError {
93
172
  this.resolutionPath = resolutionPath;
94
173
  }
95
174
  };
96
- /** Raised when `load()` is used with an async module. */
175
+ /**
176
+ * Raised when the synchronous `Container.load()` is called with an {@link AsyncModule}.
177
+ * Use `Container.loadAsync()` or `Container.fromModulesAsync()` instead.
178
+ *
179
+ * Code: `"ASYNC_MODULE_LOAD"`
180
+ */
97
181
  var AsyncModuleLoadError = class extends DiError {
98
182
  code = "ASYNC_MODULE_LOAD";
183
+ /**
184
+ * Name of the async module that was passed to the sync loader.
185
+ */
99
186
  moduleName;
100
187
  constructor(moduleName, options) {
101
188
  super(`Cannot load async module "${moduleName}" synchronously; use loadAsync() or Container.fromModulesAsync().`, options);
@@ -103,13 +190,28 @@ var AsyncModuleLoadError = class extends DiError {
103
190
  }
104
191
  };
105
192
  /**
106
- * Raised when `resolve()` is called on a binding chain that contains an async factory or
107
- * an async `onActivation` handler. Use `resolveAsync()` / `resolveAllAsync()` instead.
193
+ * Raised when synchronous `Container.resolve()` / `Container.resolveAll()` encounters an
194
+ * async operation: an `async-dynamic` factory, a `toDynamic` factory that returns a Promise,
195
+ * an `onActivation` handler that returns a Promise, or a `@postConstruct` method that
196
+ * returns a Promise.
197
+ *
198
+ * Fix: switch to `Container.resolveAsync()` / `Container.resolveAllAsync()`.
199
+ *
200
+ * Code: `"ASYNC_RESOLUTION"`
108
201
  */
109
202
  var AsyncResolutionError = class extends DiError {
110
203
  code = "ASYNC_RESOLUTION";
204
+ /**
205
+ * Token or class name that triggered the async path.
206
+ */
111
207
  tokenName;
208
+ /**
209
+ * Label path from the resolution root to the async binding.
210
+ */
112
211
  resolutionPath;
212
+ /**
213
+ * Human-readable description of why async resolution was required.
214
+ */
113
215
  reason;
114
216
  constructor(tokenName, resolutionPath, reason, options) {
115
217
  const pathText = formatResolutionPath(resolutionPath);
@@ -120,8 +222,16 @@ var AsyncResolutionError = class extends DiError {
120
222
  }
121
223
  };
122
224
  /**
123
- * Raised when a long-lived binding would capture a shorter-lived (scoped or transient) dependency
124
- * (captive dependency). Constant value dependencies are excluded.
225
+ * Raised for a **captive dependency**: a singleton consumer resolves (or would resolve) a
226
+ * non-constant binding whose lifetime is `scoped` or `transient`. Constant bindings are exempt.
227
+ *
228
+ * - **Runtime:** each resolution step checks the parent on the materialization stack, so
229
+ * violations are detected along the actual construction chain.
230
+ * - **`Container.validate()`:** {@link validateScopeRules} walks **direct** static edges from
231
+ * {@link listResolvedDependencies} only — it does not recursively expand the whole graph,
232
+ * so it may miss violations that appear only deeper in the dependency tree.
233
+ *
234
+ * Code: `"SCOPE_VIOLATION"`
125
235
  */
126
236
  var ScopeViolationError = class extends DiError {
127
237
  code = "SCOPE_VIOLATION";
@@ -136,9 +246,7 @@ var ScopeViolationError = class extends DiError {
136
246
  const pathText = formatResolutionPath(details.resolutionPath);
137
247
  const consumerLabel = details.consumerLabel ?? String(details.consumerBindingId);
138
248
  const dependencyLabel = details.dependencyLabel ?? String(details.dependencyBindingId);
139
- const consumerScopeLabel = details.consumerScope.charAt(0).toUpperCase() + details.consumerScope.slice(1);
140
- const dependencyScopeLabel = details.dependencyScope.charAt(0).toUpperCase() + details.dependencyScope.slice(1);
141
- super(`Scope Violation: ${consumerScopeLabel} "${consumerLabel}" cannot depend on ${dependencyScopeLabel} "${dependencyLabel}" (resolution path: ${pathText})`, options);
249
+ super(`Scope Violation: ${SCOPE_LABELS[details.consumerScope]} "${consumerLabel}" cannot depend on ${SCOPE_LABELS[details.dependencyScope]} "${dependencyLabel}" (resolution path: ${pathText})`, options);
142
250
  this.consumerBindingId = details.consumerBindingId;
143
251
  this.consumerKind = details.consumerKind;
144
252
  this.consumerScope = details.consumerScope;
@@ -149,4 +257,4 @@ var ScopeViolationError = class extends DiError {
149
257
  }
150
258
  };
151
259
  //#endregion
152
- export { AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, DiError, InternalError, MissingMetadataError, NoMatchingBindingError, ScopeViolationError, TokenNotBoundError };
260
+ export { AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, DiError, InternalError, MissingMetadataError, NoMatchingBindingError, ScopeViolationError, TokenNotBoundError, formatResolutionPath };
package/dist/index.d.mts CHANGED
@@ -3,8 +3,8 @@ import { ActivationHandler, BindingBuilder, BindingIdentifier, BindingScope, Con
3
3
  import { ContainerGraphJson, ContainerSnapshot } from "./inspector.mjs";
4
4
  import { AsyncModule, AsyncModuleBuilder, Module, ModuleBuilder } from "./module.mjs";
5
5
  import { Container } from "./container.mjs";
6
- import { InjectOptions, inject, isInjectionDescriptor, optional } from "./decorators/inject.mjs";
6
+ import { InjectOptions, inject, injectAll, isInjectionDescriptor, optional } from "./decorators/inject.mjs";
7
7
  import { InjectableDependency, getAutoRegistered, injectable } from "./decorators/injectable.mjs";
8
8
  import { postConstruct, preDestroy } from "./decorators/lifecycle-decorators.mjs";
9
9
  import { AsyncModuleLoadError, AsyncResolutionError, CircularDependencyError, DiError, InternalError, MissingMetadataError, NoMatchingBindingError, ScopeViolationDetails, ScopeViolationError, TokenNotBoundError } from "./errors.mjs";
10
- export { type ActivationHandler, AsyncModule, type AsyncModuleBuilder, AsyncModuleLoadError, AsyncResolutionError, type BindingBuilder, type BindingIdentifier, type BindingScope, CircularDependencyError, type ConstraintContext, type Constructor, Container, type ContainerGraphJson, type ContainerSnapshot, type DeactivationHandler, DiError, type InjectOptions, type InjectableDependency, InternalError, MissingMetadataError, Module, type ModuleBuilder, NoMatchingBindingError, type ResolveOptions, type ScopeViolationDetails, ScopeViolationError, type Token, TokenNotBoundError, type TokenValue, getAutoRegistered, inject, injectable, isInjectionDescriptor, optional, postConstruct, preDestroy, token };
10
+ export { type ActivationHandler, AsyncModule, type AsyncModuleBuilder, AsyncModuleLoadError, AsyncResolutionError, type BindingBuilder, type BindingIdentifier, type BindingScope, CircularDependencyError, type ConstraintContext, type Constructor, Container, type ContainerGraphJson, type ContainerSnapshot, type DeactivationHandler, DiError, type InjectOptions, type InjectableDependency, InternalError, MissingMetadataError, Module, type ModuleBuilder, NoMatchingBindingError, type ResolveOptions, type ScopeViolationDetails, ScopeViolationError, type Token, TokenNotBoundError, type TokenValue, getAutoRegistered, inject, injectAll, injectable, isInjectionDescriptor, optional, postConstruct, preDestroy, token };