mirror of
https://github.com/nestjs/nest.git
synced 2026-02-21 15:08:37 +00:00
241 lines
6.9 KiB
TypeScript
241 lines
6.9 KiB
TypeScript
import { UnknownDependenciesException } from '../errors/exceptions/unknown-dependencies.exception';
|
|
import { NestContainer } from '../injector/container';
|
|
import { InstanceWrapper } from '../injector/instance-wrapper';
|
|
import { Module } from '../injector/module';
|
|
import { DeterministicUuidRegistry } from './deterministic-uuid-registry';
|
|
import { EnhancerMetadataCacheEntry } from './interfaces/enhancer-metadata-cache-entry.interface';
|
|
import { Entrypoint } from './interfaces/entrypoint.interface';
|
|
import { OrphanedEnhancerDefinition } from './interfaces/extras.interface';
|
|
import { ClassNode, Node } from './interfaces/node.interface';
|
|
import { PartialGraphHost } from './partial-graph.host';
|
|
import { SerializedGraph } from './serialized-graph';
|
|
|
|
export class GraphInspector {
|
|
private readonly graph: SerializedGraph;
|
|
private readonly enhancersMetadataCache =
|
|
new Array<EnhancerMetadataCacheEntry>();
|
|
|
|
constructor(private readonly container: NestContainer) {
|
|
this.graph = container.serializedGraph;
|
|
}
|
|
|
|
public inspectModules(
|
|
modules: Map<string, Module> = this.container.getModules(),
|
|
) {
|
|
for (const moduleRef of modules.values()) {
|
|
this.insertModuleNode(moduleRef);
|
|
this.insertClassNodes(moduleRef);
|
|
this.insertModuleToModuleEdges(moduleRef);
|
|
}
|
|
|
|
this.enhancersMetadataCache.forEach(entry =>
|
|
this.insertEnhancerEdge(entry),
|
|
);
|
|
|
|
DeterministicUuidRegistry.clear();
|
|
}
|
|
|
|
public registerPartial(error: unknown) {
|
|
this.graph.status = 'partial';
|
|
|
|
if (error instanceof UnknownDependenciesException) {
|
|
this.graph.metadata = {
|
|
cause: {
|
|
type: 'unknown-dependencies',
|
|
context: error.context,
|
|
moduleId: error.moduleRef?.id,
|
|
nodeId: error.metadata?.id,
|
|
},
|
|
};
|
|
} else {
|
|
this.graph.metadata = {
|
|
cause: {
|
|
type: 'unknown',
|
|
error,
|
|
},
|
|
};
|
|
}
|
|
PartialGraphHost.register(this.graph);
|
|
}
|
|
|
|
public inspectInstanceWrapper<T = any>(
|
|
source: InstanceWrapper<T>,
|
|
moduleRef: Module,
|
|
) {
|
|
const ctorMetadata = source.getCtorMetadata();
|
|
ctorMetadata?.forEach((target, index) =>
|
|
this.insertClassToClassEdge(
|
|
source,
|
|
target,
|
|
moduleRef,
|
|
index,
|
|
'constructor',
|
|
),
|
|
);
|
|
|
|
const propertiesMetadata = source.getPropertiesMetadata();
|
|
propertiesMetadata?.forEach(({ key, wrapper: target }) =>
|
|
this.insertClassToClassEdge(source, target, moduleRef, key, 'property'),
|
|
);
|
|
}
|
|
|
|
public insertEnhancerMetadataCache(entry: EnhancerMetadataCacheEntry) {
|
|
this.enhancersMetadataCache.push(entry);
|
|
}
|
|
|
|
public insertOrphanedEnhancer(entry: OrphanedEnhancerDefinition) {
|
|
this.graph.insertOrphanedEnhancer({
|
|
...entry,
|
|
ref: entry.ref?.constructor?.name ?? 'Object',
|
|
});
|
|
}
|
|
|
|
public insertAttachedEnhancer(wrapper: InstanceWrapper) {
|
|
const existingNode = this.graph.getNodeById(wrapper.id)!;
|
|
existingNode.metadata.global = true;
|
|
|
|
this.graph.insertAttachedEnhancer(existingNode.id);
|
|
}
|
|
|
|
public insertEntrypointDefinition<T>(
|
|
definition: Entrypoint<T>,
|
|
parentId: string,
|
|
) {
|
|
definition = {
|
|
...definition,
|
|
id: `${definition.classNodeId}_${definition.methodName}`,
|
|
};
|
|
this.graph.insertEntrypoint(definition, parentId);
|
|
}
|
|
|
|
public insertClassNode(
|
|
moduleRef: Module,
|
|
wrapper: InstanceWrapper,
|
|
type: Exclude<Node['metadata']['type'], 'module'>,
|
|
) {
|
|
this.graph.insertNode({
|
|
id: wrapper.id,
|
|
label: wrapper.name,
|
|
parent: moduleRef.id,
|
|
metadata: {
|
|
type,
|
|
internal: wrapper.metatype === moduleRef.metatype,
|
|
sourceModuleName: moduleRef.name,
|
|
durable: wrapper.isDependencyTreeDurable(),
|
|
static: wrapper.isDependencyTreeStatic(),
|
|
scope: wrapper.scope!,
|
|
transient: wrapper.isTransient,
|
|
exported: moduleRef.exports.has(wrapper.token),
|
|
token: wrapper.token,
|
|
subtype: wrapper.subtype,
|
|
initTime: wrapper.initTime!,
|
|
},
|
|
});
|
|
}
|
|
|
|
private insertModuleNode(moduleRef: Module) {
|
|
const dynamicMetadata = this.container.getDynamicMetadataByToken(
|
|
moduleRef.token,
|
|
);
|
|
const node: Node = {
|
|
id: moduleRef.id,
|
|
label: moduleRef.name,
|
|
metadata: {
|
|
type: 'module',
|
|
global: moduleRef.isGlobal,
|
|
dynamic: !!dynamicMetadata,
|
|
internal: moduleRef.name === 'InternalCoreModule',
|
|
},
|
|
};
|
|
this.graph.insertNode(node);
|
|
}
|
|
|
|
private insertModuleToModuleEdges(moduleRef: Module) {
|
|
for (const targetModuleRef of moduleRef.imports) {
|
|
this.graph.insertEdge({
|
|
source: moduleRef.id,
|
|
target: targetModuleRef.id,
|
|
metadata: {
|
|
type: 'module-to-module',
|
|
sourceModuleName: moduleRef.name,
|
|
targetModuleName: targetModuleRef.name,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
private insertEnhancerEdge(entry: EnhancerMetadataCacheEntry) {
|
|
const moduleRef = this.container.getModuleByKey(entry.moduleToken);
|
|
const sourceInstanceWrapper =
|
|
moduleRef.controllers.get(entry.classRef) ??
|
|
moduleRef.providers.get(entry.classRef)!;
|
|
const existingSourceNode = this.graph.getNodeById(
|
|
sourceInstanceWrapper.id,
|
|
) as ClassNode;
|
|
const enhancers = existingSourceNode.metadata.enhancers ?? [];
|
|
|
|
if (entry.enhancerInstanceWrapper) {
|
|
this.insertClassToClassEdge(
|
|
sourceInstanceWrapper,
|
|
entry.enhancerInstanceWrapper,
|
|
moduleRef,
|
|
undefined,
|
|
'decorator',
|
|
);
|
|
|
|
enhancers.push({
|
|
id: entry.enhancerInstanceWrapper.id,
|
|
methodKey: entry.methodKey,
|
|
subtype: entry.subtype,
|
|
});
|
|
} else {
|
|
const name =
|
|
entry.enhancerRef!.constructor?.name ??
|
|
(entry.enhancerRef as Function).name;
|
|
|
|
enhancers.push({
|
|
name,
|
|
methodKey: entry.methodKey,
|
|
subtype: entry.subtype,
|
|
});
|
|
}
|
|
existingSourceNode.metadata.enhancers = enhancers;
|
|
}
|
|
|
|
private insertClassToClassEdge<T>(
|
|
source: InstanceWrapper<T>,
|
|
target: InstanceWrapper,
|
|
moduleRef: Module,
|
|
keyOrIndex: number | string | symbol | undefined,
|
|
injectionType: 'constructor' | 'property' | 'decorator',
|
|
) {
|
|
this.graph.insertEdge({
|
|
source: source.id,
|
|
target: target.id,
|
|
metadata: {
|
|
type: 'class-to-class',
|
|
sourceModuleName: moduleRef.name,
|
|
sourceClassName: source.name,
|
|
targetClassName: target.name,
|
|
sourceClassToken: source.token,
|
|
targetClassToken: target.token,
|
|
targetModuleName: target.host?.name!,
|
|
keyOrIndex,
|
|
injectionType,
|
|
},
|
|
});
|
|
}
|
|
|
|
private insertClassNodes(moduleRef: Module) {
|
|
moduleRef.providers.forEach(value =>
|
|
this.insertClassNode(moduleRef, value, 'provider'),
|
|
);
|
|
moduleRef.injectables.forEach(value =>
|
|
this.insertClassNode(moduleRef, value, 'injectable'),
|
|
);
|
|
moduleRef.controllers.forEach(value =>
|
|
this.insertClassNode(moduleRef, value, 'controller'),
|
|
);
|
|
}
|
|
}
|