@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.
- package/CHANGELOG.md +30 -0
- package/README.md +270 -234
- package/dist/binding-select.d.mts +17 -6
- package/dist/binding-select.mjs +17 -6
- package/dist/binding.d.mts +167 -34
- package/dist/binding.mjs +111 -14
- package/dist/constraints.d.mts +18 -3
- package/dist/constraints.mjs +18 -3
- package/dist/container.d.mts +85 -35
- package/dist/container.mjs +140 -6
- package/dist/decorators/inject.d.mts +40 -9
- package/dist/decorators/inject.mjs +50 -11
- package/dist/decorators/injectable.d.mts +2 -1
- package/dist/decorators/injectable.mjs +14 -2
- package/dist/decorators/lifecycle-decorators.d.mts +16 -4
- package/dist/decorators/lifecycle-decorators.mjs +16 -4
- package/dist/dependency-graph.d.mts +36 -13
- package/dist/dependency-graph.mjs +42 -8
- package/dist/errors.d.mts +132 -21
- package/dist/errors.mjs +126 -18
- package/dist/graph-adapters/cytoscape.d.mts +10 -0
- package/dist/graph-adapters/cytoscape.mjs +40 -0
- package/dist/graph-adapters/dot.d.mts +9 -0
- package/dist/graph-adapters/dot.mjs +97 -0
- package/dist/graph-adapters/reactflow.d.mts +10 -0
- package/dist/graph-adapters/reactflow.mjs +80 -0
- package/dist/graph-adapters/types.d.mts +91 -0
- package/dist/graph-adapters/types.mjs +1 -0
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +2 -2
- package/dist/inspector.d.mts +42 -40
- package/dist/inspector.mjs +18 -169
- package/dist/lifecycle.d.mts +28 -6
- package/dist/lifecycle.mjs +29 -10
- package/dist/metadata/metadata-keys.d.mts +17 -6
- package/dist/metadata/metadata-keys.mjs +17 -6
- package/dist/metadata/metadata-types.d.mts +42 -18
- package/dist/metadata/param-registry.mjs +6 -0
- package/dist/metadata/symbol-metadata-reader.d.mts +20 -3
- package/dist/metadata/symbol-metadata-reader.mjs +23 -4
- package/dist/module.d.mts +46 -2
- package/dist/module.mjs +19 -0
- package/dist/registry.d.mts +39 -8
- package/dist/registry.mjs +39 -8
- package/dist/resolver.d.mts +107 -12
- package/dist/resolver.mjs +134 -37
- package/dist/scope-validation.d.mts +3 -2
- package/dist/scope-validation.mjs +3 -2
- package/dist/scope.d.mts +38 -6
- package/dist/scope.mjs +42 -13
- package/dist/token.d.mts +9 -2
- package/dist/token.mjs +7 -1
- package/package.json +18 -2
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { InternalError } from "../errors.mjs";
|
|
2
2
|
import { CODEFAST_DI_ACCESSOR_INJECTIONS } from "../metadata/metadata-keys.mjs";
|
|
3
3
|
//#region src/decorators/inject.ts
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Validates and normalises the `tag` option from {@link InjectOptions}; throws {@link InternalError} on bad input.
|
|
6
|
+
*/
|
|
5
7
|
function normalizeTag(tag) {
|
|
6
8
|
if (tag === void 0) return;
|
|
7
9
|
if (!Array.isArray(tag) || tag.length !== 2) throw new InternalError(`@inject tag must be a tuple [tagKey, value] with length 2; received ${String(tag)}`);
|
|
@@ -9,7 +11,9 @@ function normalizeTag(tag) {
|
|
|
9
11
|
if (typeof tagName !== "string") throw new InternalError(`@inject tag key must be a string; received ${typeof tagName}`);
|
|
10
12
|
return [tagName, value];
|
|
11
13
|
}
|
|
12
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Builds an {@link InjectionDescriptor} from a token, optional flag, and raw inject options.
|
|
16
|
+
*/
|
|
13
17
|
function toDescriptor(token, optional, options) {
|
|
14
18
|
const normalizedTag = normalizeTag(options?.tag);
|
|
15
19
|
if (options?.name !== void 0) return {
|
|
@@ -27,16 +31,34 @@ function toDescriptor(token, optional, options) {
|
|
|
27
31
|
optional
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Type guard — returns `true` when `value` is a TC39 `ClassAccessorDecoratorContext` (accessor field).
|
|
36
|
+
*/
|
|
31
37
|
function isAccessorDecoratorContext(value) {
|
|
32
38
|
return typeof value === "object" && value !== null && "kind" in value && value.kind === "accessor";
|
|
33
39
|
}
|
|
34
40
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
41
|
+
* Dual-purpose injection helper:
|
|
42
|
+
*
|
|
43
|
+
* **1. As a deps-array entry** — returns an {@link InjectionDescriptor} carrying the token,
|
|
44
|
+
* optional flag (`false`), and any name/tag hint. Used inside `@injectable([...deps])`.
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* @injectable([inject(Logger, { name: 'file' })])
|
|
48
|
+
* class UserService { constructor(log: Logger) {} }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* **2. As a Stage 3 accessor decorator** — writes accessor-injection metadata into
|
|
52
|
+
* `Symbol.metadata` and returns a no-op sentinel. The container performs the actual
|
|
53
|
+
* injection after construction.
|
|
37
54
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* @inject(Logger) accessor logger!: LoggerService;
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param token - The injection key (token or constructor) to resolve.
|
|
60
|
+
* @param optionsOrContext - Either an {@link InjectOptions} hint or the TC39
|
|
61
|
+
* `ClassAccessorDecoratorContext` automatically supplied by the runtime.
|
|
40
62
|
*/
|
|
41
63
|
function inject(token, optionsOrContext) {
|
|
42
64
|
if (isAccessorDecoratorContext(optionsOrContext)) {
|
|
@@ -54,16 +76,33 @@ function inject(token, optionsOrContext) {
|
|
|
54
76
|
return toDescriptor(token, false, optionsOrContext);
|
|
55
77
|
}
|
|
56
78
|
/**
|
|
57
|
-
* Same as {@link inject} but marks the dependency as optional
|
|
58
|
-
*
|
|
79
|
+
* Same as {@link inject} but marks the dependency as optional (`InjectionDescriptor.optional = true`).
|
|
80
|
+
* During resolution, an unbound token resolves to `undefined` instead of throwing
|
|
81
|
+
* {@link TokenNotBoundError}. Only usable as a deps-array entry (not as an accessor decorator).
|
|
59
82
|
*/
|
|
60
83
|
function optional(token, options) {
|
|
61
84
|
return toDescriptor(token, true, options);
|
|
62
85
|
}
|
|
63
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Deps-array helper for `@injectable()`: injects **all** bindings registered for `token`
|
|
88
|
+
* (same semantics as {@link Container.resolveAll} / {@link ResolutionContext.resolveAll}).
|
|
89
|
+
* Use for multi-binding — constructor parameter type should be `T[]` (or a readonly array).
|
|
90
|
+
*
|
|
91
|
+
* Optional {@link InjectOptions.name} / `tag` narrow which bindings are collected (unusual; most
|
|
92
|
+
* callers omit options and register disambiguators on each binding instead).
|
|
93
|
+
*/
|
|
94
|
+
function injectAll(token, options) {
|
|
95
|
+
return {
|
|
96
|
+
...toDescriptor(token, false, options),
|
|
97
|
+
all: true
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Type-guard — returns `true` when `value` is an {@link InjectionDescriptor}.
|
|
102
|
+
*/
|
|
64
103
|
function isInjectionDescriptor(value) {
|
|
65
104
|
if (typeof value !== "object" || value === null) return false;
|
|
66
105
|
return "token" in value && "optional" in value;
|
|
67
106
|
}
|
|
68
107
|
//#endregion
|
|
69
|
-
export { inject, isInjectionDescriptor, optional };
|
|
108
|
+
export { inject, injectAll, isInjectionDescriptor, optional };
|
|
@@ -6,7 +6,8 @@ import { InjectionDescriptor } from "../metadata/metadata-types.mjs";
|
|
|
6
6
|
/**
|
|
7
7
|
* A single entry in the `deps` array passed to `@injectable()`.
|
|
8
8
|
* Can be a plain token/constructor (resolved with no hint) or an {@link InjectionDescriptor}
|
|
9
|
-
* produced by
|
|
9
|
+
* produced by `inject` / `optional` / `injectAll` when name, tag, optional, or resolve-all
|
|
10
|
+
* semantics are needed.
|
|
10
11
|
*/
|
|
11
12
|
type InjectableDependency = Token<unknown> | Constructor<unknown> | InjectionDescriptor<unknown>;
|
|
12
13
|
/**
|
|
@@ -2,6 +2,11 @@ import { InternalError } from "../errors.mjs";
|
|
|
2
2
|
import { CODEFAST_DI_CONSTRUCTOR_METADATA } from "../metadata/metadata-keys.mjs";
|
|
3
3
|
import { isInjectionDescriptor } from "./inject.mjs";
|
|
4
4
|
//#region src/decorators/injectable.ts
|
|
5
|
+
/**
|
|
6
|
+
* Global mutable registry of classes decorated with `@injectable({ autoRegister: true })`.
|
|
7
|
+
* Populated at class-definition time (via `context.addInitializer`), drained by
|
|
8
|
+
* {@link Container.loadAutoRegistered}. Entries accumulate for the lifetime of the process.
|
|
9
|
+
*/
|
|
5
10
|
const AUTO_REGISTER_REGISTRY = [];
|
|
6
11
|
/**
|
|
7
12
|
* Returns all classes decorated with `@injectable({ autoRegister: true })`.
|
|
@@ -10,14 +15,21 @@ const AUTO_REGISTER_REGISTRY = [];
|
|
|
10
15
|
function getAutoRegistered() {
|
|
11
16
|
return AUTO_REGISTER_REGISTRY;
|
|
12
17
|
}
|
|
13
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Normalises a single `@injectable` deps-array entry into the uniform {@link ParamMetadata}
|
|
20
|
+
* shape used by the resolver's constructor-instantiation path.
|
|
21
|
+
*
|
|
22
|
+
* - {@link InjectionDescriptor} entries carry `optional`, `name`, and `tag` fields.
|
|
23
|
+
* - Plain token / constructor entries are wrapped with `optional: false` and no hint.
|
|
24
|
+
*/
|
|
14
25
|
function toParamMetadata(dependency, index) {
|
|
15
26
|
if (isInjectionDescriptor(dependency)) return {
|
|
16
27
|
index,
|
|
17
28
|
token: dependency.token,
|
|
18
29
|
optional: dependency.optional,
|
|
19
30
|
name: dependency.name,
|
|
20
|
-
tag: dependency.tag
|
|
31
|
+
tag: dependency.tag,
|
|
32
|
+
all: dependency.all === true ? true : void 0
|
|
21
33
|
};
|
|
22
34
|
return {
|
|
23
35
|
index,
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
//#region src/decorators/lifecycle-decorators.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Stage 3 method decorator: marks a method to be called after the class is instantiated
|
|
4
|
-
*
|
|
3
|
+
* Stage 3 method decorator: marks a method to be called after the class is instantiated
|
|
4
|
+
* by the container and before the `onActivation` hook runs.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle order: `new Class(…)` → **`@postConstruct()`** → `onActivation()` → scope cache.
|
|
7
|
+
*
|
|
8
|
+
* Only one method per class may carry this decorator; a second application throws.
|
|
9
|
+
* If the decorated method returns a `Promise` during synchronous resolution,
|
|
10
|
+
* {@link AsyncResolutionError} is thrown — use `Container.resolveAsync()` instead.
|
|
5
11
|
*/
|
|
6
12
|
declare function postConstruct(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
|
|
7
13
|
/**
|
|
8
|
-
* Stage 3 method decorator: marks a method to be called
|
|
9
|
-
*
|
|
14
|
+
* Stage 3 method decorator: marks a method to be called when the container disposes or
|
|
15
|
+
* unloads the owning binding.
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle order: `onDeactivation()` → **`@preDestroy()`**.
|
|
18
|
+
*
|
|
19
|
+
* Only one method per class may carry this decorator; a second application throws.
|
|
20
|
+
* If the decorated method returns a `Promise` during synchronous disposal,
|
|
21
|
+
* an error is thrown — use `Container.disposeAsync()` instead.
|
|
10
22
|
*/
|
|
11
23
|
declare function preDestroy(): (target: () => unknown, context: ClassMethodDecoratorContext) => void;
|
|
12
24
|
//#endregion
|
|
@@ -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
|
|
5
|
-
*
|
|
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
|
|
20
|
-
*
|
|
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
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* A directed edge in the static dependency graph produced by {@link collectStaticDependencyEdges}.
|
|
8
|
+
*/
|
|
7
9
|
type StaticDependencyEdge = {
|
|
8
|
-
readonly fromBindingId: BindingIdentifier;
|
|
9
|
-
readonly toBindingId: BindingIdentifier;
|
|
10
|
-
readonly resolutionPath: readonly string[];
|
|
11
|
-
readonly edgeKind: "sync" | "async";
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/** Binding id of the consumer node (edge source). */readonly fromBindingId: BindingIdentifier; /** Binding id of the dependency node (edge target). */
|
|
11
|
+
readonly toBindingId: BindingIdentifier; /** Resolution labels leading to this edge. */
|
|
12
|
+
readonly resolutionPath: readonly string[]; /** Edge execution kind inferred from binding strategies. */
|
|
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
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* A single resolved dependency entry produced by {@link listResolvedDependencies}.
|
|
29
|
+
*/
|
|
17
30
|
type ResolvedDependency = {
|
|
18
|
-
readonly binding: Binding<unknown>;
|
|
19
|
-
readonly path: readonly string[];
|
|
31
|
+
/** Effective dependency binding selected for this edge. */readonly binding: Binding<unknown>; /** Resolution labels from consumer to dependency. */
|
|
32
|
+
readonly path: readonly string[]; /** Optional name/tag label shown in graph outputs. */
|
|
20
33
|
readonly injectHintLabel?: string;
|
|
21
34
|
};
|
|
22
|
-
/**
|
|
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
|
|
26
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
56
|
-
*
|
|
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(
|
|
135
|
+
injectHintLabel: injectHintLabelFromResolveHint(paramHint)
|
|
102
136
|
}];
|
|
103
137
|
});
|
|
104
138
|
}
|
package/dist/errors.d.mts
CHANGED
|
@@ -2,88 +2,199 @@ import { Binding, BindingIdentifier, BindingScope, ResolveHint } from "./binding
|
|
|
2
2
|
|
|
3
3
|
//#region src/errors.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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()`
|
|
63
|
-
* an async
|
|
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
|
-
/**
|
|
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
|
-
readonly consumerBindingId: BindingIdentifier;
|
|
75
|
-
readonly consumerKind: Binding<unknown>["kind"];
|
|
76
|
-
readonly consumerScope: BindingScope;
|
|
77
|
-
readonly consumerLabel?: string;
|
|
78
|
-
readonly dependencyBindingId: BindingIdentifier;
|
|
79
|
-
readonly dependencyKind: Binding<unknown>["kind"];
|
|
80
|
-
readonly dependencyScope: BindingScope;
|
|
81
|
-
readonly dependencyLabel?: string;
|
|
177
|
+
/** Binding id of the long-lived consumer (typically singleton). */readonly consumerBindingId: BindingIdentifier; /** Binding strategy kind of the consumer. */
|
|
178
|
+
readonly consumerKind: Binding<unknown>["kind"]; /** Scope of the consumer binding. */
|
|
179
|
+
readonly consumerScope: BindingScope; /** Optional display label for consumer in error messages. */
|
|
180
|
+
readonly consumerLabel?: string; /** Binding id of the shorter-lived dependency. */
|
|
181
|
+
readonly dependencyBindingId: BindingIdentifier; /** Binding strategy kind of the dependency. */
|
|
182
|
+
readonly dependencyKind: Binding<unknown>["kind"]; /** Scope of the dependency binding. */
|
|
183
|
+
readonly dependencyScope: BindingScope; /** Optional display label for dependency in error messages. */
|
|
184
|
+
readonly dependencyLabel?: string; /** Resolution path captured at the violation point. */
|
|
82
185
|
readonly resolutionPath: readonly string[];
|
|
83
186
|
};
|
|
84
187
|
/**
|
|
85
|
-
* Raised
|
|
86
|
-
*
|
|
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 };
|