@codefast/di 0.3.16-canary.2 → 0.3.16-canary.3
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 +8 -0
- package/dist/binding.d.mts +24 -24
- package/dist/constraints.d.mts +3 -3
- package/dist/container.mjs +82 -150
- package/dist/decorators/inject.d.mts +3 -2
- package/dist/decorators/inject.mjs +13 -40
- package/dist/decorators/injectable.mjs +4 -12
- package/dist/decorators/lifecycle-decorators.mjs +11 -72
- package/dist/dependency-graph.mjs +6 -4
- package/dist/graph-adapters/cytoscape.d.mts +12 -12
- package/dist/graph-adapters/cytoscape.mjs +7 -7
- package/dist/graph-adapters/reactflow.d.mts +15 -15
- package/dist/graph-adapters/reactflow.mjs +6 -9
- package/dist/index.d.mts +3 -3
- package/dist/inspector.d.mts +3 -2
- package/dist/inspector.mjs +7 -10
- package/dist/lifecycle.mjs +2 -12
- package/dist/metadata/metadata-keys.d.mts +8 -33
- package/dist/metadata/metadata-keys.mjs +8 -21
- package/dist/metadata/metadata-types.d.mts +5 -0
- package/dist/metadata/symbol-metadata-reader.d.mts +1 -0
- package/dist/metadata/symbol-metadata-reader.mjs +11 -33
- package/dist/registry.mjs +3 -22
- package/dist/resolve-options.d.mts +2 -2
- package/dist/resolve-options.mjs +10 -8
- package/dist/resolver.d.mts +5 -0
- package/dist/resolver.mjs +15 -21
- package/dist/types.d.mts +10 -4
- package/package.json +39 -13
- package/src/binding-scope.ts +26 -0
- package/src/binding-select.ts +167 -0
- package/src/binding.ts +281 -0
- package/src/constraints.ts +149 -0
- package/src/constructor-type.ts +19 -0
- package/src/container.ts +1213 -0
- package/src/decorators/inject.ts +233 -0
- package/src/decorators/injectable.ts +85 -0
- package/src/decorators/lifecycle-decorators.ts +55 -0
- package/src/dependency-graph.ts +116 -0
- package/src/environment.ts +232 -0
- package/src/errors.ts +262 -0
- package/src/graph-adapters/cytoscape.ts +64 -0
- package/src/graph-adapters/dot.ts +22 -0
- package/src/graph-adapters/reactflow.ts +58 -0
- package/src/index.ts +101 -0
- package/src/inspector.ts +133 -0
- package/src/lifecycle.ts +238 -0
- package/src/metadata/metadata-keys.ts +25 -0
- package/src/metadata/metadata-reader-token.ts +8 -0
- package/src/metadata/metadata-types.ts +53 -0
- package/src/metadata/symbol-metadata-reader.ts +57 -0
- package/src/module.ts +93 -0
- package/src/registry.ts +241 -0
- package/src/resolve-options.ts +42 -0
- package/src/resolver.ts +1837 -0
- package/src/scope.ts +77 -0
- package/src/token.ts +40 -0
- package/src/types.ts +145 -0
- package/dist/graph-adapters/types.d.mts +0 -2
- package/dist/graph-adapters/types.mjs +0 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { BindingTag, Constructor } from "#/types";
|
|
2
|
+
import type { Token } from "#/token";
|
|
3
|
+
import { INJECT_ACCESSOR_KEY } from "#/metadata/metadata-keys";
|
|
4
|
+
import { InternalError, MissingContainerContextError } from "#/errors";
|
|
5
|
+
import { getActiveContainer } from "#/environment";
|
|
6
|
+
import { injectionSlotToResolveOptions } from "#/resolve-options";
|
|
7
|
+
|
|
8
|
+
// ── InjectionDescriptor ───────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @since 0.3.16-canary.0
|
|
12
|
+
*/
|
|
13
|
+
export interface InjectOptions {
|
|
14
|
+
name?: string;
|
|
15
|
+
tags?: ReadonlyArray<BindingTag>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @since 0.3.16-canary.0
|
|
20
|
+
*/
|
|
21
|
+
export interface InjectionDescriptor<Value = unknown> {
|
|
22
|
+
readonly token: Token<Value> | Constructor<Value>;
|
|
23
|
+
readonly optional: boolean;
|
|
24
|
+
readonly multi: boolean;
|
|
25
|
+
readonly name?: string;
|
|
26
|
+
readonly tags?: ReadonlyArray<BindingTag>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @since 0.3.16-canary.0
|
|
31
|
+
*/
|
|
32
|
+
export type InjectableDependency<Value = unknown> =
|
|
33
|
+
| Token<Value>
|
|
34
|
+
| Constructor<Value>
|
|
35
|
+
| InjectionDescriptor<Value>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @since 0.3.16-canary.0
|
|
39
|
+
*/
|
|
40
|
+
export function isInjectionDescriptor(value: unknown): value is InjectionDescriptor {
|
|
41
|
+
if (value === null || value === undefined) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const type = typeof value;
|
|
45
|
+
// inject() returns a function (dual-role), so must check both object and function
|
|
46
|
+
if (type !== "object" && type !== "function") {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return (
|
|
50
|
+
"token" in (value as object) &&
|
|
51
|
+
"optional" in (value as object) &&
|
|
52
|
+
"multi" in (value as object) &&
|
|
53
|
+
typeof (value as InjectionDescriptor).optional === "boolean" &&
|
|
54
|
+
typeof (value as InjectionDescriptor).multi === "boolean"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @since 0.3.16-canary.0
|
|
60
|
+
*/
|
|
61
|
+
export function normalizeToDescriptor(dependency: InjectableDependency): InjectionDescriptor {
|
|
62
|
+
if (isInjectionDescriptor(dependency)) {
|
|
63
|
+
return materializeInjectionDescriptor(dependency);
|
|
64
|
+
}
|
|
65
|
+
return { token: dependency as Token<unknown> | Constructor, optional: false, multi: false };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Dual-role `inject()` values are functions: [[Function]].name must not be treated as a DI slot name.
|
|
70
|
+
* Only enumerable own `name` / `tags` from `Object.defineProperties` are real injection options.
|
|
71
|
+
*/
|
|
72
|
+
function materializeInjectionDescriptor(dependency: InjectionDescriptor): InjectionDescriptor {
|
|
73
|
+
if (typeof dependency !== "function") {
|
|
74
|
+
return dependency;
|
|
75
|
+
}
|
|
76
|
+
const dualRole = dependency as InjectionDescriptor & ((...args: Array<unknown>) => unknown);
|
|
77
|
+
const base: Pick<InjectionDescriptor<unknown>, "token" | "optional" | "multi"> = {
|
|
78
|
+
token: dualRole.token,
|
|
79
|
+
optional: dualRole.optional,
|
|
80
|
+
multi: dualRole.multi,
|
|
81
|
+
};
|
|
82
|
+
const nameDesc = Object.getOwnPropertyDescriptor(dualRole, "name");
|
|
83
|
+
const tagsDesc = Object.getOwnPropertyDescriptor(dualRole, "tags");
|
|
84
|
+
const explicitName =
|
|
85
|
+
nameDesc?.enumerable === true && typeof nameDesc.value === "string"
|
|
86
|
+
? nameDesc.value
|
|
87
|
+
: undefined;
|
|
88
|
+
const explicitTags = tagsDesc?.enumerable === true ? tagsDesc.value : undefined;
|
|
89
|
+
|
|
90
|
+
if (explicitName !== undefined && explicitTags !== undefined) {
|
|
91
|
+
return {
|
|
92
|
+
...base,
|
|
93
|
+
name: explicitName,
|
|
94
|
+
tags: explicitTags as NonNullable<InjectionDescriptor["tags"]>,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (explicitName !== undefined) {
|
|
98
|
+
return { ...base, name: explicitName };
|
|
99
|
+
}
|
|
100
|
+
if (explicitTags !== undefined) {
|
|
101
|
+
return { ...base, tags: explicitTags as NonNullable<InjectionDescriptor["tags"]> };
|
|
102
|
+
}
|
|
103
|
+
return base;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function withOptions<DescValue>(
|
|
107
|
+
base: Pick<InjectionDescriptor<DescValue>, "token" | "optional" | "multi">,
|
|
108
|
+
options: InjectOptions | undefined,
|
|
109
|
+
): InjectionDescriptor<DescValue> {
|
|
110
|
+
if (options?.name !== undefined && options.tags !== undefined) {
|
|
111
|
+
return { ...base, name: options.name, tags: options.tags };
|
|
112
|
+
}
|
|
113
|
+
if (options?.name !== undefined) {
|
|
114
|
+
return { ...base, name: options.name };
|
|
115
|
+
}
|
|
116
|
+
if (options?.tags !== undefined) {
|
|
117
|
+
return { ...base, tags: options.tags };
|
|
118
|
+
}
|
|
119
|
+
return base;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildInjectionDescriptor<const Value>(
|
|
123
|
+
token: Token<Value> | Constructor<Value>,
|
|
124
|
+
options?: InjectOptions,
|
|
125
|
+
): InjectionDescriptor<Value> {
|
|
126
|
+
return withOptions({ token, optional: false, multi: false }, options);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── inject() — dual-role ──────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
type ClassAccessorDecorator<This, Value> = (
|
|
132
|
+
target: ClassAccessorDecoratorTarget<This, Value>,
|
|
133
|
+
context: ClassAccessorDecoratorContext<This, Value>,
|
|
134
|
+
) => ClassAccessorDecoratorResult<This, Value> | void;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @since 0.3.16-canary.0
|
|
138
|
+
*/
|
|
139
|
+
export function inject<const Value>(
|
|
140
|
+
token: Token<Value> | Constructor<Value>,
|
|
141
|
+
options?: InjectOptions,
|
|
142
|
+
): InjectionDescriptor<Value> & ClassAccessorDecorator<unknown, Value> {
|
|
143
|
+
const descriptor = buildInjectionDescriptor(token, options);
|
|
144
|
+
|
|
145
|
+
const decoratorFn = (
|
|
146
|
+
_target: ClassAccessorDecoratorTarget<unknown, Value>,
|
|
147
|
+
context: ClassAccessorDecoratorContext<unknown, Value>,
|
|
148
|
+
): ClassAccessorDecoratorResult<unknown, Value> => {
|
|
149
|
+
if (context.static === true) {
|
|
150
|
+
throw new InternalError(
|
|
151
|
+
"@inject() on static accessors is not supported; only instance accessors participate in runWithContainer-based property injection.",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const meta = context.metadata as Record<string | symbol, unknown>;
|
|
155
|
+
if (!Array.isArray(meta[INJECT_ACCESSOR_KEY])) {
|
|
156
|
+
meta[INJECT_ACCESSOR_KEY] = [];
|
|
157
|
+
}
|
|
158
|
+
(
|
|
159
|
+
meta[INJECT_ACCESSOR_KEY] as Array<{ key: string | symbol; descriptor: InjectionDescriptor }>
|
|
160
|
+
).push({
|
|
161
|
+
key: context.name,
|
|
162
|
+
descriptor,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
context.addInitializer(function (this: unknown) {
|
|
166
|
+
const container = getActiveContainer();
|
|
167
|
+
if (container === undefined) {
|
|
168
|
+
throw new MissingContainerContextError(String(context.name));
|
|
169
|
+
}
|
|
170
|
+
const hint =
|
|
171
|
+
options === undefined
|
|
172
|
+
? undefined
|
|
173
|
+
: injectionSlotToResolveOptions({
|
|
174
|
+
...(options.name !== undefined ? { name: options.name } : {}),
|
|
175
|
+
...(options.tags !== undefined ? { tags: options.tags } : {}),
|
|
176
|
+
});
|
|
177
|
+
const value = descriptor.optional
|
|
178
|
+
? container.resolveOptional(token, hint)
|
|
179
|
+
: container.resolve(token, hint);
|
|
180
|
+
context.access.set(this, value as Value);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return {};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Use defineProperties to handle read-only `name` property of functions
|
|
187
|
+
const props: PropertyDescriptorMap = {};
|
|
188
|
+
for (const key of Object.keys(descriptor) as Array<keyof typeof descriptor>) {
|
|
189
|
+
props[key] = { value: descriptor[key], writable: true, enumerable: true, configurable: true };
|
|
190
|
+
}
|
|
191
|
+
Object.defineProperties(decoratorFn, props);
|
|
192
|
+
|
|
193
|
+
return decoratorFn as unknown as InjectionDescriptor<Value> &
|
|
194
|
+
ClassAccessorDecorator<unknown, Value>;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── optional() ────────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @since 0.3.16-canary.0
|
|
201
|
+
*/
|
|
202
|
+
export function optional<const Value>(
|
|
203
|
+
token: Token<Value> | Constructor<Value>,
|
|
204
|
+
options?: InjectOptions,
|
|
205
|
+
): InjectionDescriptor<Value | undefined> {
|
|
206
|
+
return withOptions(
|
|
207
|
+
{
|
|
208
|
+
token: token as Token<Value | undefined> | Constructor<Value | undefined>,
|
|
209
|
+
optional: true,
|
|
210
|
+
multi: false,
|
|
211
|
+
},
|
|
212
|
+
options,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── injectAll() ───────────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @since 0.3.16-canary.0
|
|
220
|
+
*/
|
|
221
|
+
export function injectAll<const Value>(
|
|
222
|
+
token: Token<Value> | Constructor<Value>,
|
|
223
|
+
options?: InjectOptions,
|
|
224
|
+
): InjectionDescriptor<Array<Value>> {
|
|
225
|
+
return withOptions(
|
|
226
|
+
{
|
|
227
|
+
token: token as Token<Array<Value>> | Constructor<Array<Value>>,
|
|
228
|
+
optional: false,
|
|
229
|
+
multi: true,
|
|
230
|
+
},
|
|
231
|
+
options,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { BindingScope, Constructor } from "#/types";
|
|
2
|
+
import type { ParamMetadata } from "#/metadata/metadata-types";
|
|
3
|
+
import type { InjectableDependency } from "#/decorators/inject";
|
|
4
|
+
import { normalizeToDescriptor } from "#/decorators/inject";
|
|
5
|
+
import { INJECTABLE_KEY } from "#/metadata/metadata-keys";
|
|
6
|
+
|
|
7
|
+
// ── AutoRegisterRegistry ──────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @since 0.3.16-canary.0
|
|
11
|
+
*/
|
|
12
|
+
export interface AutoRegisterRegistry {
|
|
13
|
+
register(target: Constructor, scope: BindingScope): void;
|
|
14
|
+
entries(): ReadonlyArray<{ target: Constructor; scope: BindingScope }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @since 0.3.16-canary.0
|
|
19
|
+
*/
|
|
20
|
+
export function createAutoRegisterRegistry(): AutoRegisterRegistry {
|
|
21
|
+
const _registeredEntries: Array<{ target: Constructor; scope: BindingScope }> = [];
|
|
22
|
+
return {
|
|
23
|
+
register(target: Constructor, scope: BindingScope): void {
|
|
24
|
+
_registeredEntries.push({ target, scope });
|
|
25
|
+
},
|
|
26
|
+
entries(): ReadonlyArray<{ target: Constructor; scope: BindingScope }> {
|
|
27
|
+
return _registeredEntries;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── InjectableOptions ─────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @since 0.3.16-canary.0
|
|
36
|
+
*/
|
|
37
|
+
export interface InjectableOptions {
|
|
38
|
+
autoRegister?: AutoRegisterRegistry;
|
|
39
|
+
scope?: BindingScope;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── @injectable() ─────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @since 0.3.16-canary.0
|
|
46
|
+
*/
|
|
47
|
+
export function injectable(
|
|
48
|
+
deps?: ReadonlyArray<InjectableDependency>,
|
|
49
|
+
options?: InjectableOptions,
|
|
50
|
+
): (target: unknown, context: ClassDecoratorContext) => void {
|
|
51
|
+
return function (target: unknown, context: ClassDecoratorContext): void {
|
|
52
|
+
const parameterMetadataList: Array<ParamMetadata> = (deps ?? []).map((dependency, index) => {
|
|
53
|
+
const descriptor = normalizeToDescriptor(dependency);
|
|
54
|
+
const baseParameterMetadata: Pick<ParamMetadata, "index" | "token" | "optional" | "multi"> = {
|
|
55
|
+
index,
|
|
56
|
+
token: descriptor.token,
|
|
57
|
+
optional: descriptor.optional,
|
|
58
|
+
multi: descriptor.multi,
|
|
59
|
+
};
|
|
60
|
+
if (descriptor.name !== undefined && descriptor.tags !== undefined) {
|
|
61
|
+
return { ...baseParameterMetadata, name: descriptor.name, tags: descriptor.tags };
|
|
62
|
+
}
|
|
63
|
+
if (descriptor.name !== undefined) {
|
|
64
|
+
return { ...baseParameterMetadata, name: descriptor.name };
|
|
65
|
+
}
|
|
66
|
+
if (descriptor.tags !== undefined) {
|
|
67
|
+
return { ...baseParameterMetadata, tags: descriptor.tags };
|
|
68
|
+
}
|
|
69
|
+
return baseParameterMetadata;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Field decorators run before the class decorator — accessor @inject entries are
|
|
73
|
+
// already on context.metadata by the time this runs.
|
|
74
|
+
(context.metadata as Record<string | symbol, unknown>)[INJECTABLE_KEY] = {
|
|
75
|
+
params: parameterMetadataList,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (options?.autoRegister !== undefined) {
|
|
79
|
+
const scope: BindingScope = options.scope ?? "transient";
|
|
80
|
+
options.autoRegister.register(target as Constructor, scope);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type { InjectableDependency };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { MutableLifecycleMetadata } from "#/metadata/metadata-types";
|
|
2
|
+
import { InternalError } from "#/errors";
|
|
3
|
+
import { LIFECYCLE_KEY } from "#/metadata/metadata-keys";
|
|
4
|
+
|
|
5
|
+
function appendUniqueMethod(
|
|
6
|
+
metadata: MutableLifecycleMetadata,
|
|
7
|
+
phase: "postConstruct" | "preDestroy",
|
|
8
|
+
methodName: string,
|
|
9
|
+
): void {
|
|
10
|
+
if (!metadata[phase].includes(methodName)) {
|
|
11
|
+
metadata[phase].push(methodName);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @since 0.3.16-canary.0
|
|
17
|
+
*/
|
|
18
|
+
export function postConstruct(): (target: unknown, context: ClassMethodDecoratorContext) => void {
|
|
19
|
+
return function (target: unknown, context: ClassMethodDecoratorContext): void {
|
|
20
|
+
if (context.static === true) {
|
|
21
|
+
throw new InternalError(
|
|
22
|
+
"@postConstruct() applies to instance methods only; static methods are not invoked during instance lifecycle.",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const methodName = String(context.name);
|
|
26
|
+
const meta = context.metadata as Record<string | symbol, unknown>;
|
|
27
|
+
if (!meta[LIFECYCLE_KEY]) {
|
|
28
|
+
meta[LIFECYCLE_KEY] = { postConstruct: [], preDestroy: [] };
|
|
29
|
+
}
|
|
30
|
+
appendUniqueMethod(
|
|
31
|
+
meta[LIFECYCLE_KEY] as MutableLifecycleMetadata,
|
|
32
|
+
"postConstruct",
|
|
33
|
+
methodName,
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @since 0.3.16-canary.0
|
|
40
|
+
*/
|
|
41
|
+
export function preDestroy(): (target: unknown, context: ClassMethodDecoratorContext) => void {
|
|
42
|
+
return function (target: unknown, context: ClassMethodDecoratorContext): void {
|
|
43
|
+
if (context.static === true) {
|
|
44
|
+
throw new InternalError(
|
|
45
|
+
"@preDestroy() applies to instance methods only; static methods are not invoked during instance teardown.",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const methodName = String(context.name);
|
|
49
|
+
const meta = context.metadata as Record<string | symbol, unknown>;
|
|
50
|
+
if (!meta[LIFECYCLE_KEY]) {
|
|
51
|
+
meta[LIFECYCLE_KEY] = { postConstruct: [], preDestroy: [] };
|
|
52
|
+
}
|
|
53
|
+
appendUniqueMethod(meta[LIFECYCLE_KEY] as MutableLifecycleMetadata, "preDestroy", methodName);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { BindingRegistry } from "#/registry";
|
|
2
|
+
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
3
|
+
import type { BindingScope, Constructor } from "#/types";
|
|
4
|
+
import { tokenName } from "#/token";
|
|
5
|
+
import { effectiveBindingScope } from "#/binding-scope";
|
|
6
|
+
|
|
7
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @since 0.3.16-canary.0
|
|
11
|
+
*/
|
|
12
|
+
export interface GraphNode {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly tokenName: string;
|
|
15
|
+
readonly kind: string;
|
|
16
|
+
readonly scope: BindingScope;
|
|
17
|
+
readonly fromParent: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @since 0.3.16-canary.0
|
|
22
|
+
*/
|
|
23
|
+
export interface GraphEdge {
|
|
24
|
+
readonly from: string;
|
|
25
|
+
readonly to: string;
|
|
26
|
+
readonly label?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @since 0.3.16-canary.0
|
|
31
|
+
*/
|
|
32
|
+
export interface ContainerGraphJson {
|
|
33
|
+
readonly nodes: ReadonlyArray<GraphNode>;
|
|
34
|
+
readonly edges: ReadonlyArray<GraphEdge>;
|
|
35
|
+
readonly includesParent: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @since 0.3.16-canary.0
|
|
40
|
+
*/
|
|
41
|
+
export interface GraphOptions {
|
|
42
|
+
readonly includeParent?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Builder ───────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @since 0.3.16-canary.0
|
|
49
|
+
*/
|
|
50
|
+
export function buildDependencyGraph(
|
|
51
|
+
registry: BindingRegistry,
|
|
52
|
+
metadataReader: MetadataReader,
|
|
53
|
+
options: GraphOptions | undefined,
|
|
54
|
+
parentRegistry?: BindingRegistry,
|
|
55
|
+
): ContainerGraphJson {
|
|
56
|
+
const nodes: Array<GraphNode> = [];
|
|
57
|
+
const edges: Array<GraphEdge> = [];
|
|
58
|
+
const includesParent = options?.includeParent === true;
|
|
59
|
+
|
|
60
|
+
const addBindings = (sourceRegistry: BindingRegistry, fromParent: boolean): void => {
|
|
61
|
+
for (const binding of sourceRegistry.allBindings()) {
|
|
62
|
+
const scope = effectiveBindingScope(binding);
|
|
63
|
+
nodes.push({
|
|
64
|
+
id: binding.id,
|
|
65
|
+
tokenName: tokenName(binding.token),
|
|
66
|
+
kind: binding.kind,
|
|
67
|
+
scope,
|
|
68
|
+
fromParent,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Build edges
|
|
72
|
+
if (binding.kind === "class") {
|
|
73
|
+
const meta = metadataReader.getConstructorMetadata(binding.target as Constructor);
|
|
74
|
+
if (meta !== undefined) {
|
|
75
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
76
|
+
const param = meta.params[index]!;
|
|
77
|
+
const dependencyBinding = sourceRegistry.getAll(param.token as Constructor)[0];
|
|
78
|
+
if (dependencyBinding !== undefined) {
|
|
79
|
+
edges.push({
|
|
80
|
+
from: binding.id,
|
|
81
|
+
to: dependencyBinding.id,
|
|
82
|
+
label: `[${index}]`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else if (binding.kind === "resolved" || binding.kind === "resolved-async") {
|
|
88
|
+
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
89
|
+
const dependency = binding.deps[index]!;
|
|
90
|
+
const dependencyBindings = sourceRegistry.getAll(dependency.token as Constructor);
|
|
91
|
+
if (dependencyBindings.length > 0 && dependencyBindings[0] !== undefined) {
|
|
92
|
+
const label =
|
|
93
|
+
dependency.name !== undefined
|
|
94
|
+
? `name:${dependency.name}`
|
|
95
|
+
: dependency.tags !== undefined && dependency.tags.length > 0
|
|
96
|
+
? `tag:${dependency.tags[0]?.[0]}=${String(dependency.tags[0]?.[1])}`
|
|
97
|
+
: `[${index}]`;
|
|
98
|
+
edges.push({ from: binding.id, to: dependencyBindings[0].id, label });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} else if (binding.kind === "alias") {
|
|
102
|
+
const targetBindings = sourceRegistry.getAll(binding.target as Constructor);
|
|
103
|
+
if (targetBindings.length > 0 && targetBindings[0] !== undefined) {
|
|
104
|
+
edges.push({ from: binding.id, to: targetBindings[0].id, label: "alias" });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
addBindings(registry, false);
|
|
111
|
+
if (includesParent && parentRegistry !== undefined) {
|
|
112
|
+
addBindings(parentRegistry, true);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { nodes, edges, includesParent };
|
|
116
|
+
}
|