@codefast/di 0.3.16-canary.2 → 0.4.0-canary.4
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 +16 -0
- package/README.md +5 -17
- package/dist/binding.d.mts +24 -24
- package/dist/constraints.d.mts +3 -3
- package/dist/container.d.mts +3 -3
- package/dist/container.mjs +89 -157
- package/dist/decorators/inject.d.mts +3 -2
- package/dist/decorators/inject.mjs +14 -41
- package/dist/decorators/injectable.mjs +4 -12
- package/dist/decorators/lifecycle-decorators.mjs +13 -74
- package/dist/dependency-graph.d.mts +1 -1
- 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 +6 -6
- package/dist/index.mjs +1 -1
- 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/module.mjs +1 -1
- 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 +6 -1
- package/dist/resolver.mjs +15 -21
- package/dist/types.d.mts +10 -4
- package/package.json +40 -14
- package/src/binding-scope.ts +26 -0
- package/src/binding-select.ts +158 -0
- package/src/binding.ts +277 -0
- package/src/constraints.ts +121 -0
- package/src/constructor-type.ts +19 -0
- package/src/container.ts +1135 -0
- package/src/decorators/inject.ts +222 -0
- package/src/decorators/injectable.ts +85 -0
- package/src/decorators/lifecycle-decorators.ts +51 -0
- package/src/dependency-graph.ts +116 -0
- package/src/environment.ts +207 -0
- package/src/errors.ts +260 -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 +125 -0
- package/src/lifecycle.ts +217 -0
- package/src/metadata/metadata-keys.ts +25 -0
- package/src/metadata/metadata-reader-token.ts +8 -0
- package/src/metadata/metadata-types.ts +51 -0
- package/src/metadata/symbol-metadata-reader.ts +45 -0
- package/src/module.ts +93 -0
- package/src/registry.ts +232 -0
- package/src/resolve-options.ts +42 -0
- package/src/resolver.ts +1609 -0
- package/src/scope.ts +77 -0
- package/src/token.ts +40 -0
- package/src/types.ts +123 -0
- package/dist/graph-adapters/types.d.mts +0 -2
- package/dist/graph-adapters/types.mjs +0 -1
package/src/errors.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { BindingIdentifier, BindingScope, ResolveOptions } from "#/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @since 0.3.16-canary.0
|
|
5
|
+
*/
|
|
6
|
+
export abstract class DiError extends Error {
|
|
7
|
+
abstract readonly code: string;
|
|
8
|
+
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = this.constructor.name;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @since 0.3.16-canary.0
|
|
17
|
+
*/
|
|
18
|
+
export class InternalError extends DiError {
|
|
19
|
+
readonly code = "INTERNAL_ERROR";
|
|
20
|
+
|
|
21
|
+
constructor(message: string) {
|
|
22
|
+
super(message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @since 0.3.16-canary.0
|
|
28
|
+
*/
|
|
29
|
+
export class TokenNotBoundError extends DiError {
|
|
30
|
+
readonly code = "TOKEN_NOT_BOUND";
|
|
31
|
+
readonly tokenName: string;
|
|
32
|
+
|
|
33
|
+
constructor(tokenName: string) {
|
|
34
|
+
super(`No binding found for token '${tokenName}'. Did you forget container.bind(${tokenName})?`);
|
|
35
|
+
this.tokenName = tokenName;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @since 0.3.16-canary.0
|
|
41
|
+
*/
|
|
42
|
+
export class NoMatchingBindingError extends DiError {
|
|
43
|
+
readonly code = "NO_MATCHING_BINDING";
|
|
44
|
+
readonly tokenName: string;
|
|
45
|
+
readonly hint: ResolveOptions;
|
|
46
|
+
readonly availableSlots: Array<string>;
|
|
47
|
+
|
|
48
|
+
constructor(tokenName: string, hint: ResolveOptions, availableSlots: Array<string>) {
|
|
49
|
+
const hintStr = JSON.stringify(hint);
|
|
50
|
+
const slotsStr = availableSlots.join(", ");
|
|
51
|
+
super(`No binding for '${tokenName}' matching ${hintStr}. Available slots: [${slotsStr}].`);
|
|
52
|
+
this.tokenName = tokenName;
|
|
53
|
+
this.hint = hint;
|
|
54
|
+
this.availableSlots = availableSlots;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @since 0.3.16-canary.0
|
|
60
|
+
*/
|
|
61
|
+
export class AmbiguousBindingError extends DiError {
|
|
62
|
+
readonly code = "AMBIGUOUS_BINDING";
|
|
63
|
+
readonly tokenName: string;
|
|
64
|
+
readonly candidateIds: ReadonlyArray<BindingIdentifier>;
|
|
65
|
+
|
|
66
|
+
constructor(tokenName: string, candidateIds: ReadonlyArray<BindingIdentifier>) {
|
|
67
|
+
super(
|
|
68
|
+
`Multiple bindings for '${tokenName}' matched without a clear winner. Candidates: [${candidateIds.join(", ")}]. Ensure when() predicates are mutually exclusive.`,
|
|
69
|
+
);
|
|
70
|
+
this.tokenName = tokenName;
|
|
71
|
+
this.candidateIds = candidateIds;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @since 0.3.16-canary.0
|
|
77
|
+
*/
|
|
78
|
+
export class CircularDependencyError extends DiError {
|
|
79
|
+
readonly code = "CIRCULAR_DEPENDENCY";
|
|
80
|
+
readonly cycle: Array<string>;
|
|
81
|
+
|
|
82
|
+
constructor(cycle: Array<string>) {
|
|
83
|
+
super(`Circular dependency detected: ${cycle.join(" → ")}`);
|
|
84
|
+
this.cycle = cycle;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @since 0.3.16-canary.0
|
|
90
|
+
*/
|
|
91
|
+
export class AsyncResolutionError extends DiError {
|
|
92
|
+
readonly code = "ASYNC_RESOLUTION";
|
|
93
|
+
readonly tokenName: string;
|
|
94
|
+
readonly asyncSourceToken: string;
|
|
95
|
+
|
|
96
|
+
constructor(tokenName: string, asyncSourceToken: string) {
|
|
97
|
+
super(
|
|
98
|
+
`Token '${tokenName}' requires async resolution because '${asyncSourceToken}' in its dependency chain has an async factory. Use container.resolveAsync(${tokenName}).`,
|
|
99
|
+
);
|
|
100
|
+
this.tokenName = tokenName;
|
|
101
|
+
this.asyncSourceToken = asyncSourceToken;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @since 0.3.16-canary.0
|
|
107
|
+
*/
|
|
108
|
+
export class AsyncDeactivationError extends DiError {
|
|
109
|
+
readonly code = "ASYNC_DEACTIVATION";
|
|
110
|
+
readonly tokenName: string;
|
|
111
|
+
|
|
112
|
+
constructor(tokenName: string) {
|
|
113
|
+
super(`Token '${tokenName}' has an async onDeactivation handler. Use unbindAsync() instead.`);
|
|
114
|
+
this.tokenName = tokenName;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @since 0.3.16-canary.0
|
|
120
|
+
*/
|
|
121
|
+
export interface ScopeViolationDetails {
|
|
122
|
+
readonly consumerToken: string;
|
|
123
|
+
readonly consumerScope: BindingScope;
|
|
124
|
+
readonly dependencyToken: string;
|
|
125
|
+
readonly dependencyScope: BindingScope;
|
|
126
|
+
readonly path: Array<string>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @since 0.3.16-canary.0
|
|
131
|
+
*/
|
|
132
|
+
export class ScopeViolationError extends DiError {
|
|
133
|
+
readonly code = "SCOPE_VIOLATION";
|
|
134
|
+
readonly details: ScopeViolationDetails;
|
|
135
|
+
|
|
136
|
+
constructor(details: ScopeViolationDetails) {
|
|
137
|
+
super(
|
|
138
|
+
`Scope violation: '${details.consumerToken}' (${details.consumerScope}) depends on '${details.dependencyToken}' (${details.dependencyScope}). Path: ${details.path.join(" → ")}`,
|
|
139
|
+
);
|
|
140
|
+
this.details = details;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @since 0.3.16-canary.0
|
|
146
|
+
*/
|
|
147
|
+
export class MissingMetadataError extends DiError {
|
|
148
|
+
readonly code = "MISSING_METADATA";
|
|
149
|
+
readonly targetName: string;
|
|
150
|
+
|
|
151
|
+
constructor(targetName: string) {
|
|
152
|
+
super(
|
|
153
|
+
`Class '${targetName}' is missing @injectable() decorator. Add @injectable([...deps]) or use toDynamic()/toResolved() instead.`,
|
|
154
|
+
);
|
|
155
|
+
this.targetName = targetName;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @since 0.3.16-canary.0
|
|
161
|
+
*/
|
|
162
|
+
export class AsyncModuleLoadError extends DiError {
|
|
163
|
+
readonly code = "ASYNC_MODULE_LOAD";
|
|
164
|
+
readonly moduleName: string;
|
|
165
|
+
|
|
166
|
+
constructor(moduleName: string) {
|
|
167
|
+
super(`Module '${moduleName}' is async. Use container.loadAsync() instead.`);
|
|
168
|
+
this.moduleName = moduleName;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @since 0.3.16-canary.0
|
|
174
|
+
*/
|
|
175
|
+
export class SyncDisposalNotSupportedError extends DiError {
|
|
176
|
+
readonly code = "SYNC_DISPOSAL_NOT_SUPPORTED";
|
|
177
|
+
|
|
178
|
+
constructor() {
|
|
179
|
+
super(
|
|
180
|
+
"Container cannot be disposed synchronously because onDeactivation handlers may be async. Use `await using` or call container.dispose() explicitly.",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @since 0.3.16-canary.0
|
|
187
|
+
*/
|
|
188
|
+
export class MissingScopeContextError extends DiError {
|
|
189
|
+
readonly code = "MISSING_SCOPE_CONTEXT";
|
|
190
|
+
readonly tokenName: string;
|
|
191
|
+
|
|
192
|
+
constructor(tokenName: string) {
|
|
193
|
+
super(
|
|
194
|
+
`Token '${tokenName}' is scoped but was resolved from a container without a child scope context. Use container.createChild() to create a scoped context.`,
|
|
195
|
+
);
|
|
196
|
+
this.tokenName = tokenName;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @since 0.3.16-canary.0
|
|
202
|
+
*/
|
|
203
|
+
export class MissingContainerContextError extends DiError {
|
|
204
|
+
readonly code = "MISSING_CONTAINER_CONTEXT";
|
|
205
|
+
readonly targetName: string;
|
|
206
|
+
|
|
207
|
+
constructor(targetName: string) {
|
|
208
|
+
super(
|
|
209
|
+
`Class '${targetName}' has @inject accessor fields but was instantiated outside a container context. Resolve it via container.resolve(${targetName}) instead.`,
|
|
210
|
+
);
|
|
211
|
+
this.targetName = targetName;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @since 0.3.16-canary.0
|
|
217
|
+
*/
|
|
218
|
+
export class RebindUnboundTokenError extends DiError {
|
|
219
|
+
readonly code = "REBIND_UNBOUND_TOKEN";
|
|
220
|
+
readonly tokenName: string;
|
|
221
|
+
|
|
222
|
+
constructor(tokenName: string) {
|
|
223
|
+
super(
|
|
224
|
+
`Cannot rebind token '${tokenName}' because it has no own binding in this container. Use container.bind(${tokenName}) to create a new binding instead.`,
|
|
225
|
+
);
|
|
226
|
+
this.tokenName = tokenName;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @since 0.3.16-canary.0
|
|
232
|
+
*/
|
|
233
|
+
export class DisposedContainerError extends DiError {
|
|
234
|
+
readonly code = "DISPOSED_CONTAINER";
|
|
235
|
+
|
|
236
|
+
constructor() {
|
|
237
|
+
super("Cannot perform operations on a disposed container.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @since 0.3.16-canary.0
|
|
243
|
+
*/
|
|
244
|
+
export class AsyncActivationError extends DiError {
|
|
245
|
+
readonly code = "ASYNC_ACTIVATION";
|
|
246
|
+
readonly tokenName: string;
|
|
247
|
+
readonly hookKind: "postConstruct" | "onActivation";
|
|
248
|
+
readonly methodName: string | undefined;
|
|
249
|
+
|
|
250
|
+
constructor(tokenName: string, hookKind: "postConstruct" | "onActivation", methodName?: string) {
|
|
251
|
+
const detail =
|
|
252
|
+
hookKind === "postConstruct"
|
|
253
|
+
? `@postConstruct method '${methodName ?? ""}' returned a Promise`
|
|
254
|
+
: `onActivation for '${tokenName}' returned a Promise`;
|
|
255
|
+
super(`${detail}. Use resolveAsync() instead.`);
|
|
256
|
+
this.tokenName = tokenName;
|
|
257
|
+
this.hookKind = hookKind;
|
|
258
|
+
this.methodName = methodName;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ContainerGraphJson } from "#/dependency-graph";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @since 0.3.16-canary.0
|
|
5
|
+
*/
|
|
6
|
+
export interface CytoscapeNode {
|
|
7
|
+
readonly data: {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly kind: string;
|
|
11
|
+
readonly scope: string;
|
|
12
|
+
readonly fromParent: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @since 0.3.16-canary.0
|
|
18
|
+
*/
|
|
19
|
+
export interface CytoscapeEdge {
|
|
20
|
+
readonly data: {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly source: string;
|
|
23
|
+
readonly target: string;
|
|
24
|
+
readonly label?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @since 0.3.16-canary.0
|
|
30
|
+
*/
|
|
31
|
+
export type CytoscapeElements = ReadonlyArray<CytoscapeNode | CytoscapeEdge>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @since 0.3.16-canary.0
|
|
35
|
+
*/
|
|
36
|
+
export function toCytoscapeGraph(graph: ContainerGraphJson): CytoscapeElements {
|
|
37
|
+
const elements: Array<CytoscapeNode | CytoscapeEdge> = [];
|
|
38
|
+
|
|
39
|
+
for (const node of graph.nodes) {
|
|
40
|
+
elements.push({
|
|
41
|
+
data: {
|
|
42
|
+
id: node.id,
|
|
43
|
+
label: node.tokenName,
|
|
44
|
+
kind: node.kind,
|
|
45
|
+
scope: node.scope,
|
|
46
|
+
fromParent: node.fromParent,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (let idx = 0; idx < graph.edges.length; idx += 1) {
|
|
52
|
+
const edge = graph.edges[idx]!;
|
|
53
|
+
elements.push({
|
|
54
|
+
data: {
|
|
55
|
+
id: `edge-${idx}`,
|
|
56
|
+
source: edge.from,
|
|
57
|
+
target: edge.to,
|
|
58
|
+
...(edge.label !== undefined ? { label: edge.label } : {}),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return elements;
|
|
64
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ContainerGraphJson } from "#/dependency-graph";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @since 0.3.16-canary.0
|
|
5
|
+
*/
|
|
6
|
+
export function toDotGraph(graph: ContainerGraphJson): string {
|
|
7
|
+
const lines: Array<string> = ["digraph DI {", " rankdir=TB;"];
|
|
8
|
+
|
|
9
|
+
for (const node of graph.nodes) {
|
|
10
|
+
const label = `${node.tokenName}\\n[${node.kind}/${node.scope}]`;
|
|
11
|
+
const style = node.fromParent ? ' style="dashed"' : "";
|
|
12
|
+
lines.push(` "${node.id}" [label="${label}"${style}];`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const edge of graph.edges) {
|
|
16
|
+
const label = edge.label !== undefined ? ` [label="${edge.label}"]` : "";
|
|
17
|
+
lines.push(` "${edge.from}" -> "${edge.to}"${label};`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
lines.push("}");
|
|
21
|
+
return lines.join("\n");
|
|
22
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ContainerGraphJson } from "#/dependency-graph";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @since 0.3.16-canary.0
|
|
5
|
+
*/
|
|
6
|
+
export interface ReactFlowNode {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly data: {
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly kind: string;
|
|
11
|
+
readonly scope: string;
|
|
12
|
+
readonly fromParent: boolean;
|
|
13
|
+
};
|
|
14
|
+
readonly position: { readonly x: number; readonly y: number };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @since 0.3.16-canary.0
|
|
19
|
+
*/
|
|
20
|
+
export interface ReactFlowEdge {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly source: string;
|
|
23
|
+
readonly target: string;
|
|
24
|
+
readonly label?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @since 0.3.16-canary.0
|
|
29
|
+
*/
|
|
30
|
+
export interface ReactFlowGraph {
|
|
31
|
+
readonly nodes: ReadonlyArray<ReactFlowNode>;
|
|
32
|
+
readonly edges: ReadonlyArray<ReactFlowEdge>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @since 0.3.16-canary.0
|
|
37
|
+
*/
|
|
38
|
+
export function toReactFlowGraph(graph: ContainerGraphJson): ReactFlowGraph {
|
|
39
|
+
const nodes: Array<ReactFlowNode> = graph.nodes.map((node, idx) => ({
|
|
40
|
+
id: node.id,
|
|
41
|
+
data: {
|
|
42
|
+
label: node.tokenName,
|
|
43
|
+
kind: node.kind,
|
|
44
|
+
scope: node.scope,
|
|
45
|
+
fromParent: node.fromParent,
|
|
46
|
+
},
|
|
47
|
+
position: { x: (idx % 5) * 200, y: Math.floor(idx / 5) * 100 },
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
const edges: Array<ReactFlowEdge> = graph.edges.map((edge, idx) => ({
|
|
51
|
+
id: `edge-${idx}`,
|
|
52
|
+
source: edge.from,
|
|
53
|
+
target: edge.to,
|
|
54
|
+
...(edge.label !== undefined ? { label: edge.label } : {}),
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
return { nodes, edges };
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Foundation types
|
|
2
|
+
export type {
|
|
3
|
+
ActivationHandler,
|
|
4
|
+
BindingIdentifier,
|
|
5
|
+
BindingKind,
|
|
6
|
+
BindingScope,
|
|
7
|
+
BindingTag,
|
|
8
|
+
ConstraintContext,
|
|
9
|
+
Constructor,
|
|
10
|
+
DependencyKey,
|
|
11
|
+
DeactivationHandler,
|
|
12
|
+
ResolutionFrame,
|
|
13
|
+
ResolveOptions,
|
|
14
|
+
ResolutionContext,
|
|
15
|
+
TokenValue,
|
|
16
|
+
} from "#/types";
|
|
17
|
+
|
|
18
|
+
// Token
|
|
19
|
+
export { token, tokenName, isToken } from "#/token";
|
|
20
|
+
export type { Token } from "#/token";
|
|
21
|
+
|
|
22
|
+
// Binding builders — types only
|
|
23
|
+
export type {
|
|
24
|
+
AliasBindingBuilder,
|
|
25
|
+
BindToBuilder,
|
|
26
|
+
BindingBuilder,
|
|
27
|
+
ConstantBindingBuilder,
|
|
28
|
+
ScopedBindingBuilder,
|
|
29
|
+
SingletonBindingBuilder,
|
|
30
|
+
SingletonLifecycleBuilder,
|
|
31
|
+
SlotConstrainedBuilder,
|
|
32
|
+
TransientBindingBuilder,
|
|
33
|
+
} from "#/binding";
|
|
34
|
+
|
|
35
|
+
// Container
|
|
36
|
+
export { Container } from "#/container";
|
|
37
|
+
export type { Container as ContainerInterface, ContainerStatic } from "#/container";
|
|
38
|
+
|
|
39
|
+
export { effectiveBindingScope } from "#/binding-scope";
|
|
40
|
+
export { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#/resolve-options";
|
|
41
|
+
|
|
42
|
+
// Introspection types
|
|
43
|
+
export type { BindingSnapshot, ContainerSnapshot } from "#/inspector";
|
|
44
|
+
|
|
45
|
+
// Graph types
|
|
46
|
+
export type { ContainerGraphJson, GraphEdge, GraphNode, GraphOptions } from "#/dependency-graph";
|
|
47
|
+
|
|
48
|
+
// Module
|
|
49
|
+
export { AsyncModule, isSyncModule, Module, SyncModule } from "#/module";
|
|
50
|
+
export type { AsyncModuleBuilder, ModuleBuilder } from "#/module";
|
|
51
|
+
|
|
52
|
+
// Decorators
|
|
53
|
+
export { inject, injectAll, isInjectionDescriptor, optional } from "#/decorators/inject";
|
|
54
|
+
export type { InjectionDescriptor, InjectOptions } from "#/decorators/inject";
|
|
55
|
+
export { injectable } from "#/decorators/injectable";
|
|
56
|
+
export type { InjectableDependency, InjectableOptions } from "#/decorators/injectable";
|
|
57
|
+
export { postConstruct, preDestroy } from "#/decorators/lifecycle-decorators";
|
|
58
|
+
|
|
59
|
+
// Auto-register
|
|
60
|
+
export { createAutoRegisterRegistry } from "#/decorators/injectable";
|
|
61
|
+
export type { AutoRegisterRegistry } from "#/decorators/injectable";
|
|
62
|
+
|
|
63
|
+
// MetadataReader
|
|
64
|
+
export { MetadataReaderToken } from "#/metadata/metadata-reader-token";
|
|
65
|
+
export type { MetadataReader, MutableLifecycleMetadata } from "#/metadata/metadata-types";
|
|
66
|
+
|
|
67
|
+
// Constraints — contextual injection predicates for .when()
|
|
68
|
+
export {
|
|
69
|
+
whenAnyAncestorIs,
|
|
70
|
+
whenAnyAncestorNamed,
|
|
71
|
+
whenAnyAncestorTagged,
|
|
72
|
+
whenAnyAncestorTaggedAll,
|
|
73
|
+
whenNoAncestorIs,
|
|
74
|
+
whenNoParentIs,
|
|
75
|
+
whenParentIs,
|
|
76
|
+
whenParentNamed,
|
|
77
|
+
whenParentTagged,
|
|
78
|
+
whenParentTaggedAll,
|
|
79
|
+
} from "#/constraints";
|
|
80
|
+
|
|
81
|
+
// Errors
|
|
82
|
+
export {
|
|
83
|
+
AmbiguousBindingError,
|
|
84
|
+
AsyncActivationError,
|
|
85
|
+
AsyncDeactivationError,
|
|
86
|
+
AsyncModuleLoadError,
|
|
87
|
+
AsyncResolutionError,
|
|
88
|
+
CircularDependencyError,
|
|
89
|
+
DiError,
|
|
90
|
+
DisposedContainerError,
|
|
91
|
+
InternalError,
|
|
92
|
+
MissingContainerContextError,
|
|
93
|
+
MissingMetadataError,
|
|
94
|
+
MissingScopeContextError,
|
|
95
|
+
NoMatchingBindingError,
|
|
96
|
+
RebindUnboundTokenError,
|
|
97
|
+
ScopeViolationError,
|
|
98
|
+
SyncDisposalNotSupportedError,
|
|
99
|
+
TokenNotBoundError,
|
|
100
|
+
} from "#/errors";
|
|
101
|
+
export type { ScopeViolationDetails } from "#/errors";
|
package/src/inspector.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Binding } from "#/binding";
|
|
2
|
+
import { effectiveBindingScope } from "#/binding-scope";
|
|
3
|
+
import { selectBinding } from "#/binding-select";
|
|
4
|
+
import type { BindingRegistry } from "#/registry";
|
|
5
|
+
import type { ScopeManager } from "#/scope";
|
|
6
|
+
import type { Token } from "#/token";
|
|
7
|
+
import { tokenName } from "#/token";
|
|
8
|
+
import type {
|
|
9
|
+
BindingIdentifier,
|
|
10
|
+
BindingKind,
|
|
11
|
+
BindingScope,
|
|
12
|
+
BindingTag,
|
|
13
|
+
ConstraintContext,
|
|
14
|
+
Constructor,
|
|
15
|
+
ResolveOptions,
|
|
16
|
+
} from "#/types";
|
|
17
|
+
|
|
18
|
+
// ── Public types ──────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @since 0.3.16-canary.0
|
|
22
|
+
*/
|
|
23
|
+
export interface BindingSnapshot {
|
|
24
|
+
readonly tokenName: string;
|
|
25
|
+
readonly kind: BindingKind;
|
|
26
|
+
readonly scope: BindingScope;
|
|
27
|
+
readonly slot: {
|
|
28
|
+
readonly name?: string;
|
|
29
|
+
readonly tags: ReadonlyArray<BindingTag>;
|
|
30
|
+
};
|
|
31
|
+
readonly id: BindingIdentifier;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @since 0.3.16-canary.0
|
|
36
|
+
*/
|
|
37
|
+
export interface ContainerSnapshot {
|
|
38
|
+
readonly ownBindings: ReadonlyArray<BindingSnapshot>;
|
|
39
|
+
readonly cachedSingletonCount: number;
|
|
40
|
+
readonly hasParent: boolean;
|
|
41
|
+
readonly isDisposed: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Inspector ─────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @since 0.3.16-canary.0
|
|
48
|
+
*/
|
|
49
|
+
export class Inspector {
|
|
50
|
+
constructor(
|
|
51
|
+
private readonly _registry: BindingRegistry,
|
|
52
|
+
private readonly _scope: ScopeManager,
|
|
53
|
+
private readonly _hasParent: boolean,
|
|
54
|
+
private readonly _isDisposed: () => boolean,
|
|
55
|
+
) {}
|
|
56
|
+
|
|
57
|
+
inspect(): ContainerSnapshot {
|
|
58
|
+
const snapshots = this.allBindingSnapshots();
|
|
59
|
+
return {
|
|
60
|
+
ownBindings: snapshots,
|
|
61
|
+
cachedSingletonCount: this._scope.getAllSingletons().size,
|
|
62
|
+
hasParent: this._hasParent,
|
|
63
|
+
isDisposed: this._isDisposed(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
lookupBindings<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<BindingSnapshot> {
|
|
68
|
+
const bindings = this._registry.getAll(token);
|
|
69
|
+
return bindings.map((binding) => this._toSnapshot(binding));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
has(token: Token<unknown> | Constructor, hint?: ResolveOptions, parentHas?: () => boolean): boolean {
|
|
73
|
+
const bindings = this._registry.getAll(token);
|
|
74
|
+
if (bindings.length > 0) {
|
|
75
|
+
if (hint !== undefined) {
|
|
76
|
+
if (selectBinding(bindings, hint, this._makeHintContext(hint), tokenName(token)) !== undefined) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return parentHas?.() ?? false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
hasOwn(token: Token<unknown> | Constructor, hint?: ResolveOptions): boolean {
|
|
87
|
+
const bindings = this._registry.getAll(token);
|
|
88
|
+
if (bindings.length === 0) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (hint !== undefined) {
|
|
92
|
+
return selectBinding(bindings, hint, this._makeHintContext(hint), tokenName(token)) !== undefined;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private _makeHintContext(hint: ResolveOptions): ConstraintContext {
|
|
98
|
+
return {
|
|
99
|
+
resolutionPath: [],
|
|
100
|
+
resolutionStack: [],
|
|
101
|
+
parent: undefined,
|
|
102
|
+
ancestors: [],
|
|
103
|
+
currentResolveHint: hint,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private allBindingSnapshots(): ReadonlyArray<BindingSnapshot> {
|
|
108
|
+
return this._registry.allBindings().map((binding) => this._toSnapshot(binding));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private _toSnapshot(binding: Binding): BindingSnapshot {
|
|
112
|
+
const scope = effectiveBindingScope(binding);
|
|
113
|
+
const slot: BindingSnapshot["slot"] =
|
|
114
|
+
binding.slot.name !== undefined
|
|
115
|
+
? { name: binding.slot.name, tags: binding.slot.tags }
|
|
116
|
+
: { tags: binding.slot.tags };
|
|
117
|
+
return {
|
|
118
|
+
tokenName: tokenName(binding.token),
|
|
119
|
+
kind: binding.kind,
|
|
120
|
+
scope,
|
|
121
|
+
slot,
|
|
122
|
+
id: binding.id,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|