@tdi2/di-core
Version:
TypeScript Dependency Injection 2 - Core DI framework
1,367 lines (1,346 loc) • 231 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// tools/config-manager.ts
import * as crypto from "crypto";
import * as fs from "fs";
import * as path from "path";
import { createRequire } from "module";
var _ConfigManager = class _ConfigManager {
constructor(options) {
__publicField(this, "configHash");
__publicField(this, "configDir");
__publicField(this, "bridgeDir");
__publicField(this, "options");
__publicField(this, "packageName");
this.options = {
nodeEnv: process.env.NODE_ENV || "development",
...options
};
this.packageName = this.getPackageName();
this.configHash = this.generateConfigHash();
this.configDir = path.resolve(`node_modules/.tdi2/configs/${this.configHash}`);
this.bridgeDir = path.resolve(options.srcDir, ".tdi2");
this.ensureDirectories();
}
getPackageName() {
try {
const require2 = createRequire(import.meta.url);
const packageJson = require2(path.resolve("package.json"));
return packageJson.name || "unknown";
} catch {
return "unknown";
}
}
generateConfigHash() {
const normalizedSrcDir = path.resolve(this.options.srcDir).replace(/\\/g, "/");
const hashInput = {
srcDir: normalizedSrcDir,
enableFunctionalDI: this.options.enableFunctionalDI,
packageName: this.packageName,
// Remove nodeEnv from hash unless it's explicitly different
// This prevents dev vs build from having different configs
environment: this.options.nodeEnv === "production" ? "production" : "development",
// Only include customSuffix if provided
...this.options.customSuffix && {
customSuffix: this.options.customSuffix
}
};
const sortedKeys = Object.keys(hashInput).sort();
const sortedHashInput = sortedKeys.reduce((acc, key) => {
acc[key] = hashInput[key];
return acc;
}, {});
const hashString = JSON.stringify(sortedHashInput);
const hash = crypto.createHash("sha256").update(hashString).digest("hex").substring(0, 8);
const configName = `${this.packageName}-${hash}`;
if (this.options.verbose) {
console.log(`\u{1F511} Config hash inputs:`, sortedHashInput);
console.log(`\u{1F3D7}\uFE0F Generated config: ${configName}`);
}
return configName;
}
// FIXED: Add method to check for existing configurations
findExistingConfig() {
const tdi2Dir = path.resolve("node_modules/.tdi2/configs");
if (!fs.existsSync(tdi2Dir)) {
return null;
}
try {
const configs = fs.readdirSync(tdi2Dir).filter((name) => name.startsWith(this.packageName)).map((name) => ({
name,
path: path.join(tdi2Dir, name),
stats: fs.statSync(path.join(tdi2Dir, name))
})).filter((item) => item.stats.isDirectory()).sort((a, b) => b.stats.mtime.getTime() - a.stats.mtime.getTime());
for (const config of configs) {
const diConfigFile = path.join(config.path, "di-config.ts");
if (fs.existsSync(diConfigFile)) {
if (this.options.verbose) {
console.log(`\u267B\uFE0F Found existing config: ${config.name}`);
}
return config.name;
}
}
} catch (error) {
if (this.options.verbose) {
console.warn("\u26A0\uFE0F Failed to scan existing configs:", error);
}
}
return null;
}
// FIXED: Use existing config if available and valid
ensureDirectories() {
const existingConfig = this.findExistingConfig();
if (existingConfig && existingConfig !== this.configHash) {
if (this.options.verbose) {
console.log(`\u{1F504} Using existing config: ${existingConfig}`);
}
this.configHash = existingConfig;
this.configDir = path.resolve(`node_modules/.tdi2/configs/${this.configHash}`);
}
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, {
recursive: true
});
}
if (!fs.existsSync(this.bridgeDir)) {
fs.mkdirSync(this.bridgeDir, {
recursive: true
});
}
const transformedDir = path.join(this.configDir, "transformed");
if (!fs.existsSync(transformedDir)) {
fs.mkdirSync(transformedDir, {
recursive: true
});
}
this.writeConfigMetadata();
}
writeConfigMetadata() {
const metadata = {
configHash: this.configHash,
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
options: this.options,
packageName: this.packageName,
paths: {
configDir: this.configDir,
bridgeDir: this.bridgeDir
},
// FIXED: Add version tracking
version: "2.0.0",
hashInputs: {
srcDir: path.resolve(this.options.srcDir).replace(/\\/g, "/"),
enableFunctionalDI: this.options.enableFunctionalDI,
packageName: this.packageName,
environment: this.options.nodeEnv === "production" ? "production" : "development"
}
};
fs.writeFileSync(path.join(this.configDir, ".config-meta.json"), JSON.stringify(metadata, null, 2));
}
generateBridgeFiles() {
if (this.options.verbose) {
console.log(`\u{1F309} Generating bridge files in ${this.bridgeDir}`);
}
this.generateDIConfigBridge();
this.generateRegistryBridge();
this.generateBridgeGitignore();
if (this.options.verbose) {
console.log(`\u2705 Bridge files generated for config: ${this.configHash}`);
}
}
generateDIConfigBridge() {
const relativePath = path.relative(this.bridgeDir, path.join(this.configDir, "di-config.ts")).replace(/\\/g, "/");
const bridgeContent = `// Auto-generated bridge file - do not edit
// Config: ${this.configHash}
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
export * from '${relativePath}';
`;
fs.writeFileSync(path.join(this.bridgeDir, "di-config.ts"), bridgeContent);
}
generateRegistryBridge() {
const relativePath = path.relative(this.bridgeDir, path.join(this.configDir, "AutoGeneratedRegistry.ts")).replace(/\\/g, "/");
const bridgeContent = `// Auto-generated bridge file - do not edit
// Config: ${this.configHash}
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
export * from '${relativePath}';
`;
fs.writeFileSync(path.join(this.bridgeDir, "registry.ts"), bridgeContent);
}
generateBridgeGitignore() {
const gitignoreContent = `# Auto-generated TDI2 bridge files
*
!.gitignore
!README.md
`;
fs.writeFileSync(path.join(this.bridgeDir, ".gitignore"), gitignoreContent);
const readmeContent = `# TDI2 Bridge Files
This directory contains auto-generated bridge files that connect your source code to the actual DI configuration files.
**Do not edit these files manually** - they are regenerated automatically.
## Current Configuration
- Config Hash: ${this.configHash}
- Config Directory: ${this.configDir}
- Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
## Files
- \`di-config.ts\` - Exports DI configuration
- \`registry.ts\` - Exports service registry
## Debugging
If you see issues with mismatched configurations:
1. Check \`npm run di:info\` for debug URLs
2. Compare config hashes between CLI and dev server
3. Use \`npm run di:clean\` to reset all configs
4. Run \`npm run di:enhanced\` followed by \`npm run dev\`
`;
fs.writeFileSync(path.join(this.bridgeDir, "README.md"), readmeContent);
}
// FIXED: Add method to check if config is valid
isConfigValid() {
const diConfigFile = path.join(this.configDir, "di-config.ts");
const registryFile = path.join(this.configDir, "AutoGeneratedRegistry.ts");
return fs.existsSync(diConfigFile) && fs.existsSync(registryFile);
}
// FIXED: Add method to force regeneration
forceRegenerate() {
if (fs.existsSync(this.configDir)) {
fs.rmSync(this.configDir, {
recursive: true,
force: true
});
}
this.ensureDirectories();
}
// Getters for other classes to use
getConfigDir() {
return this.configDir;
}
getBridgeDir() {
return this.bridgeDir;
}
getConfigHash() {
return this.configHash;
}
getTransformedDir() {
return path.join(this.configDir, "transformed");
}
// FIXED: Enhanced cleanup with better logic
static cleanOldConfigs(keepCount = 3) {
const tdi2Dir = path.resolve("node_modules/.tdi2/configs");
if (!fs.existsSync(tdi2Dir)) {
return;
}
try {
const configs = fs.readdirSync(tdi2Dir).map((name) => ({
name,
path: path.join(tdi2Dir, name),
stats: fs.statSync(path.join(tdi2Dir, name))
})).filter((item) => item.stats.isDirectory()).sort((a, b) => b.stats.mtime.getTime() - a.stats.mtime.getTime());
const toRemove = configs.slice(keepCount);
for (const config of toRemove) {
try {
fs.rmSync(config.path, {
recursive: true,
force: true
});
console.log(`\u{1F5D1}\uFE0F Cleaned up old config: ${config.name}`);
} catch (error) {
console.warn(`\u26A0\uFE0F Failed to remove config ${config.name}:`, error);
}
}
if (toRemove.length === 0 && configs.length > 0) {
console.log(`\u{1F4CB} Found ${configs.length} configs, all within keep limit`);
}
} catch (error) {
console.warn("\u26A0\uFE0F Failed to clean old configs:", error);
}
}
// FIXED: Add method to list all configs
static listConfigs() {
const tdi2Dir = path.resolve("node_modules/.tdi2/configs");
if (!fs.existsSync(tdi2Dir)) {
console.log("\u{1F4CB} No configuration directory found");
return;
}
try {
const configs = fs.readdirSync(tdi2Dir).map((name) => {
const configPath = path.join(tdi2Dir, name);
const metaFile = path.join(configPath, ".config-meta.json");
let metadata = null;
if (fs.existsSync(metaFile)) {
try {
metadata = JSON.parse(fs.readFileSync(metaFile, "utf8"));
} catch (error) {
}
}
return {
name,
path: configPath,
metadata,
stats: fs.statSync(configPath),
isValid: fs.existsSync(path.join(configPath, "di-config.ts"))
};
}).filter((item) => item.stats.isDirectory()).sort((a, b) => b.stats.mtime.getTime() - a.stats.mtime.getTime());
console.log(`\u{1F4CB} Found ${configs.length} configurations:`);
for (const config of configs) {
const age = Math.round((Date.now() - config.stats.mtime.getTime()) / (1e3 * 60));
const status = config.isValid ? "\u2705" : "\u274C";
console.log(` ${status} ${config.name} (${age}m ago)`);
if (config.metadata) {
console.log(` Generated: ${config.metadata.generatedAt}`);
console.log(` Options: functional=${config.metadata.options?.enableFunctionalDI}`);
}
}
} catch (error) {
console.warn("\u26A0\uFE0F Failed to list configs:", error);
}
}
};
__name(_ConfigManager, "ConfigManager");
var ConfigManager = _ConfigManager;
// tools/dependency-tree-builder.ts
import * as path4 from "path";
import * as fs3 from "fs";
// tools/shared/SharedDependencyExtractor.ts
import { Node as Node2 } from "ts-morph";
// tools/shared/RecursiveInjectExtractor.ts
import { Node } from "ts-morph";
import * as path2 from "path";
var _RecursiveInjectExtractor = class _RecursiveInjectExtractor {
constructor(options = {}) {
__publicField(this, "options");
this.options = options;
}
/**
* Extract all Inject markers from a type node recursively
*/
extractInjectMarkersRecursive(typeNode, sourceFile, propertyPath = []) {
const markers = [];
if (this.options.verbose) {
console.log(`\u{1F50D} Extracting from ${typeNode.getKindName()} at path: [${propertyPath.join(", ")}]`);
}
if (Node.isTypeLiteral(typeNode)) {
const literalMarkers = this.extractFromTypeLiteral(typeNode, sourceFile, propertyPath);
markers.push(...literalMarkers);
}
if (Node.isTypeReference(typeNode)) {
const typeName = typeNode.getTypeName().getText();
const typeDeclaration = this.findTypeDeclaration(typeName, sourceFile);
if (typeDeclaration) {
const refMarkers = this.extractFromTypeDeclaration(typeDeclaration, sourceFile, propertyPath);
markers.push(...refMarkers);
}
}
if (Node.isUnionTypeNode && Node.isUnionTypeNode(typeNode)) {
const unionTypes = typeNode.getTypeNodes();
for (const unionType of unionTypes) {
const unionMarkers = this.extractInjectMarkersRecursive(unionType, sourceFile, propertyPath);
markers.push(...unionMarkers);
}
}
if (Node.isArrayTypeNode && Node.isArrayTypeNode(typeNode)) {
const elementType = typeNode.getElementTypeNode();
const arrayMarkers = this.extractInjectMarkersRecursive(elementType, sourceFile, propertyPath);
markers.push(...arrayMarkers);
}
if (Node.isIntersectionTypeNode && Node.isIntersectionTypeNode(typeNode)) {
const intersectionTypes = typeNode.getTypeNodes();
for (const intersectionType of intersectionTypes) {
const intersectionMarkers = this.extractInjectMarkersRecursive(intersectionType, sourceFile, propertyPath);
markers.push(...intersectionMarkers);
}
}
return markers;
}
/**
* Extract markers from type literal
*/
extractFromTypeLiteral(typeNode, sourceFile, propertyPath) {
if (!Node.isTypeLiteral(typeNode)) {
return [];
}
const markers = [];
const members = typeNode.getMembers();
if (this.options.verbose) {
console.log(`\u{1F4DD} Processing type literal with ${members.length} members at path: [${propertyPath.join(", ")}]`);
}
for (const member of members) {
if (Node.isPropertySignature(member)) {
const propName = member.getName();
const currentPath = [
...propertyPath,
propName
];
const memberTypeNode = member.getTypeNode();
if (memberTypeNode) {
const directMarker = this.extractDirectInjectMarker(member, currentPath, sourceFile);
if (directMarker) {
markers.push(directMarker);
if (this.options.verbose) {
console.log(`\u2705 Found direct inject marker: ${directMarker.propertyPath.join(".")} -> ${directMarker.interfaceType}`);
}
} else {
const nestedMarkers = this.extractInjectMarkersRecursive(memberTypeNode, sourceFile, currentPath);
markers.push(...nestedMarkers);
}
}
}
}
return markers;
}
/**
* FIXED: Extract direct Inject<T> or InjectOptional<T> marker from a property with correct service key extraction
*/
extractDirectInjectMarker(property, propertyPath, sourceFile) {
const propertyTypeNode = property.getTypeNode();
if (!propertyTypeNode) return null;
const typeText = propertyTypeNode.getText();
const injectMatch = typeText.match(/^Inject<(.+)>$/);
const optionalMatch = typeText.match(/^InjectOptional<(.+)>$/);
if (injectMatch || optionalMatch) {
const interfaceType = injectMatch ? injectMatch[1] : optionalMatch[1];
const isOptional = !!optionalMatch;
let serviceKey;
if (propertyPath.length === 1) {
serviceKey = propertyPath[0];
} else if (propertyPath.length > 1) {
serviceKey = propertyPath[propertyPath.length - 1];
} else {
serviceKey = property.getName();
}
if (this.options.verbose) {
console.log(`\u{1F517} Found ${isOptional ? "optional" : "required"} inject marker:`);
console.log(` propertyPath: [${propertyPath.join(", ")}]`);
console.log(` serviceKey: "${serviceKey}"`);
console.log(` interfaceType: "${interfaceType}"`);
}
return {
serviceKey,
interfaceType,
isOptional,
propertyPath,
sourceLocation: `${sourceFile.getBaseName()}:${property.getStartLineNumber()}`
};
}
return null;
}
/**
* Extract markers from interface or type alias declaration
*/
extractFromTypeDeclaration(typeDeclaration, sourceFile, propertyPath) {
const markers = [];
if (this.options.verbose) {
const declName = Node.isInterfaceDeclaration(typeDeclaration) ? typeDeclaration.getName() : typeDeclaration.getName();
console.log(`\u{1F4CB} Processing ${typeDeclaration.getKindName()}: ${declName} at path: [${propertyPath.join(", ")}]`);
}
if (Node.isInterfaceDeclaration(typeDeclaration)) {
const properties = typeDeclaration.getProperties();
for (const property of properties) {
const propName = property.getName();
const currentPath = [
...propertyPath,
propName
];
const propertyTypeNode = property.getTypeNode();
if (propertyTypeNode) {
const directMarker = this.extractDirectInjectMarker(property, currentPath, sourceFile);
if (directMarker) {
markers.push(directMarker);
} else {
const nestedMarkers = this.extractInjectMarkersRecursive(propertyTypeNode, sourceFile, currentPath);
markers.push(...nestedMarkers);
}
}
}
}
if (Node.isTypeAliasDeclaration(typeDeclaration)) {
const aliasTypeNode = typeDeclaration.getTypeNode();
if (aliasTypeNode) {
const aliasMarkers = this.extractInjectMarkersRecursive(aliasTypeNode, sourceFile, propertyPath);
markers.push(...aliasMarkers);
}
}
return markers;
}
/**
* Find type declaration (interface or type alias) in current file or imports
*/
findTypeDeclaration(typeName, sourceFile) {
const localInterface = sourceFile.getInterface(typeName);
if (localInterface) {
if (this.options.verbose) {
console.log(`\u2705 Found interface ${typeName} in current file`);
}
return localInterface;
}
const localTypeAlias = sourceFile.getTypeAlias(typeName);
if (localTypeAlias) {
if (this.options.verbose) {
console.log(`\u2705 Found type alias ${typeName} in current file`);
}
return localTypeAlias;
}
for (const importDecl of sourceFile.getImportDeclarations()) {
const namedImports = importDecl.getNamedImports();
const isTypeImported = namedImports.some((namedImport) => namedImport.getName() === typeName);
if (isTypeImported) {
const moduleSpecifier = importDecl.getModuleSpecifierValue();
if (this.options.verbose) {
console.log(`\u{1F50D} Looking for ${typeName} in imported module: ${moduleSpecifier}`);
}
const importedFile = this.resolveImportedFile(moduleSpecifier, sourceFile);
if (importedFile) {
const importedInterface = importedFile.getInterface(typeName);
if (importedInterface) {
if (this.options.verbose) {
console.log(`\u2705 Found interface ${typeName} in imported file`);
}
return importedInterface;
}
const importedTypeAlias = importedFile.getTypeAlias(typeName);
if (importedTypeAlias) {
if (this.options.verbose) {
console.log(`\u2705 Found type alias ${typeName} in imported file`);
}
return importedTypeAlias;
}
}
}
}
if (this.options.verbose) {
console.log(`\u274C Could not find declaration for type: ${typeName}`);
}
return null;
}
/**
* Resolve imported file path
*/
resolveImportedFile(moduleSpecifier, sourceFile) {
try {
const currentDir = path2.dirname(sourceFile.getFilePath());
let resolvedPath;
if (moduleSpecifier.startsWith(".")) {
resolvedPath = path2.resolve(currentDir, moduleSpecifier);
} else {
const srcDir = this.options.srcDir || "./src";
resolvedPath = path2.resolve(srcDir, moduleSpecifier);
}
const extensions = [
".ts",
".tsx",
"/index.ts",
"/index.tsx"
];
for (const ext of extensions) {
const fullPath = resolvedPath + ext;
const project = sourceFile.getProject();
const importedFile = project.getSourceFile(fullPath);
if (importedFile) {
if (this.options.verbose) {
console.log(`\u2705 Resolved import: ${moduleSpecifier} -> ${fullPath}`);
}
return importedFile;
}
}
if (this.options.verbose) {
console.log(`\u274C Could not resolve import: ${moduleSpecifier}`);
}
} catch (error) {
if (this.options.verbose) {
console.warn(`\u26A0\uFE0F Failed to resolve import: ${moduleSpecifier}`, error);
}
}
return null;
}
/**
* Check if a type node has inject markers (detection only, no extraction)
*/
hasInjectMarkersRecursive(typeNode, sourceFile) {
const typeText = typeNode.getText();
if (typeText.includes("Inject<") || typeText.includes("InjectOptional<")) {
return true;
}
if (Node.isTypeLiteral(typeNode)) {
return this.hasInjectMarkersInTypeLiteral(typeNode, sourceFile);
}
if (Node.isTypeReference(typeNode)) {
const typeName = typeNode.getTypeName().getText();
const typeDeclaration = this.findTypeDeclaration(typeName, sourceFile);
if (typeDeclaration) {
return this.hasInjectMarkersInTypeDeclaration(typeDeclaration, sourceFile);
}
}
if (Node.isUnionTypeNode && Node.isUnionTypeNode(typeNode)) {
const unionTypes = typeNode.getTypeNodes();
return unionTypes.some((unionType) => this.hasInjectMarkersRecursive(unionType, sourceFile));
}
if (Node.isArrayTypeNode && Node.isArrayTypeNode(typeNode)) {
const elementType = typeNode.getElementTypeNode();
return this.hasInjectMarkersRecursive(elementType, sourceFile);
}
return false;
}
/**
* Check if type literal has inject markers (detection only)
*/
hasInjectMarkersInTypeLiteral(typeNode, sourceFile) {
if (!Node.isTypeLiteral(typeNode)) {
return false;
}
const members = typeNode.getMembers();
for (const member of members) {
if (Node.isPropertySignature(member)) {
const memberTypeNode = member.getTypeNode();
if (memberTypeNode && this.hasInjectMarkersRecursive(memberTypeNode, sourceFile)) {
return true;
}
}
}
return false;
}
/**
* Check if type declaration has inject markers (detection only)
*/
hasInjectMarkersInTypeDeclaration(typeDeclaration, sourceFile) {
if (Node.isInterfaceDeclaration(typeDeclaration)) {
const properties = typeDeclaration.getProperties();
for (const property of properties) {
const propertyTypeNode = property.getTypeNode();
if (propertyTypeNode && this.hasInjectMarkersRecursive(propertyTypeNode, sourceFile)) {
return true;
}
}
}
if (Node.isTypeAliasDeclaration(typeDeclaration)) {
const aliasTypeNode = typeDeclaration.getTypeNode();
if (aliasTypeNode && this.hasInjectMarkersRecursive(aliasTypeNode, sourceFile)) {
return true;
}
}
return false;
}
/**
* Extract all inject markers from interface declaration (public method)
*/
extractFromInterfaceDeclaration(interfaceDecl, sourceFile, initialPath = []) {
if (this.options.verbose) {
console.log(`\u{1F50D} Recursively extracting inject markers from interface ${interfaceDecl.getName()}`);
}
return this.extractFromTypeDeclaration(interfaceDecl, sourceFile, initialPath);
}
/**
* Extract all inject markers from type alias declaration (public method)
*/
extractFromTypeAliasDeclaration(typeAlias, sourceFile, initialPath = []) {
if (this.options.verbose) {
console.log(`\u{1F50D} Recursively extracting inject markers from type alias ${typeAlias.getName()}`);
}
return this.extractFromTypeDeclaration(typeAlias, sourceFile, initialPath);
}
/**
* Extract all inject markers from type node (public method)
*/
extractFromTypeNode(typeNode, sourceFile, initialPath = []) {
if (this.options.verbose) {
console.log(`\u{1F50D} Recursively extracting inject markers from type node: ${typeNode.getKindName()}`);
}
return this.extractInjectMarkersRecursive(typeNode, sourceFile, initialPath);
}
};
__name(_RecursiveInjectExtractor, "RecursiveInjectExtractor");
var RecursiveInjectExtractor = _RecursiveInjectExtractor;
// tools/shared/SharedDependencyExtractor.ts
var _SharedDependencyExtractor = class _SharedDependencyExtractor {
constructor(typeResolver, options = {}) {
__publicField(this, "typeResolver");
__publicField(this, "options");
__publicField(this, "recursiveExtractor");
this.typeResolver = typeResolver;
this.options = options;
this.recursiveExtractor = new RecursiveInjectExtractor({
verbose: this.options.verbose,
srcDir: this.options.srcDir
});
}
/**
* Extract dependencies from class constructor with @Inject decorators
*/
extractFromClassConstructor(classDecl, sourceFile) {
const dependencies = [];
const constructors = classDecl.getConstructors();
if (constructors.length === 0) {
return dependencies;
}
const constructor = constructors[0];
const parameters = constructor.getParameters();
for (let i = 0; i < parameters.length; i++) {
const param = parameters[i];
const dependency = this.extractFromConstructorParameter(param, i, sourceFile);
if (dependency) {
dependencies.push(dependency);
}
}
if (this.options.verbose && dependencies.length > 0) {
console.log(`\u{1F50D} Found ${dependencies.length} constructor dependencies in ${classDecl.getName()}`);
}
return dependencies;
}
/**
* Extract dependencies from function parameter with Inject<T> markers
*/
extractFromFunctionParameter(func, sourceFile) {
const parameters = func.getParameters();
if (parameters.length === 0) return [];
const firstParam = parameters[0];
return this.extractFromTypeMarkers(firstParam, sourceFile, "function-parameter");
}
/**
* Extract dependencies from arrow function parameter with Inject<T> markers
*/
extractFromArrowFunction(arrowFunc, sourceFile) {
const parameters = arrowFunc.getParameters();
if (parameters.length === 0) return [];
const firstParam = parameters[0];
return this.extractFromTypeMarkers(firstParam, sourceFile, "arrow-function-parameter");
}
/**
* Extract from constructor parameter with @Inject decorator
*/
extractFromConstructorParameter(param, parameterIndex, sourceFile) {
const hasInjectDecorator = this.hasInjectDecorator(param);
if (!hasInjectDecorator) {
return null;
}
const paramName = param.getName();
const paramType = param.getTypeNode()?.getText();
if (!paramType) {
if (this.options.verbose) {
console.warn(`\u26A0\uFE0F Parameter ${paramName} missing type annotation`);
}
return null;
}
const isOptional = param.hasQuestionToken();
const qualifier = this.extractQualifier(param);
const resolutionRequest = {
interfaceType: paramType,
context: "class-constructor",
isOptional,
sourceLocation: `${sourceFile.getBaseName()}:${param.getStartLineNumber()}`,
sourceFile: sourceFile.getFilePath()
};
const resolution = this.typeResolver.resolveType(resolutionRequest);
return {
serviceKey: paramName,
interfaceType: paramType,
sanitizedKey: resolution.sanitizedKey,
isOptional,
resolvedImplementation: resolution.implementation,
extractionSource: "decorator",
sourceLocation: resolutionRequest.sourceLocation,
metadata: {
parameterIndex,
hasQualifier: !!qualifier,
qualifier
}
};
}
/**
* Extract dependencies from Inject<T> marker types in function parameters using recursive extraction
*/
extractFromTypeMarkers(param, sourceFile, context) {
const dependencies = [];
const typeNode = param.getTypeNode();
if (!typeNode) {
return dependencies;
}
if (this.options.verbose) {
console.log(`\u{1F50D} Analyzing parameter type: ${typeNode.getKindName()}`);
}
const injectMarkers = this.recursiveExtractor.extractFromTypeNode(typeNode, sourceFile);
if (this.options.verbose && injectMarkers.length > 0) {
console.log(`\u{1F3AF} Found ${injectMarkers.length} inject markers in parameter type`);
injectMarkers.forEach((marker) => {
console.log(` - ${marker.propertyPath.join(".")} -> ${marker.interfaceType} (${marker.isOptional ? "optional" : "required"})`);
});
}
for (const marker of injectMarkers) {
const dependency = this.convertInjectMarkerToDependency(marker, sourceFile, context);
if (dependency) {
dependencies.push(dependency);
}
}
return dependencies;
}
/**
* Convert an inject marker to an extracted dependency with proper property path handling
*/
convertInjectMarkerToDependency(marker, sourceFile, context) {
const resolutionRequest = {
interfaceType: marker.interfaceType,
context,
isOptional: marker.isOptional,
sourceLocation: marker.sourceLocation,
sourceFile: sourceFile.getFilePath()
};
const resolution = this.typeResolver.resolveType(resolutionRequest);
if (this.options.verbose) {
console.log(`\u{1F517} Converting marker: ${marker.propertyPath.join(".")} -> ${marker.interfaceType} (${marker.isOptional ? "optional" : "required"})`);
}
return {
serviceKey: marker.serviceKey,
interfaceType: marker.interfaceType,
sanitizedKey: resolution.sanitizedKey,
isOptional: marker.isOptional,
resolvedImplementation: resolution.implementation,
extractionSource: "marker-type",
sourceLocation: marker.sourceLocation,
propertyPath: marker.propertyPath,
metadata: {
propertyName: marker.serviceKey
}
};
}
/**
* Extract from type reference (external interface) using recursive extraction
*/
extractFromTypeReference(typeNode, sourceFile, context) {
if (!Node2.isTypeReference(typeNode)) return [];
const typeName = typeNode.getTypeName().getText();
if (this.options.verbose) {
console.log(`\u{1F50D} Resolving type reference: ${typeName}`);
}
const injectMarkers = this.recursiveExtractor.extractFromTypeNode(typeNode, sourceFile);
if (this.options.verbose) {
console.log(`\u{1F3AF} Found ${injectMarkers.length} inject markers in type reference ${typeName}`);
}
const dependencies = [];
for (const marker of injectMarkers) {
const dependency = this.convertInjectMarkerToDependency(marker, sourceFile, context);
if (dependency) {
dependencies.push(dependency);
}
}
return dependencies;
}
/**
* Extract from interface declaration using recursive extraction
*/
extractFromInterfaceDeclaration(interfaceDecl, sourceFile, context) {
if (this.options.verbose) {
console.log(`\u2705 Recursively extracting dependencies from interface ${interfaceDecl.getName()}`);
}
const injectMarkers = this.recursiveExtractor.extractFromInterfaceDeclaration(interfaceDecl, sourceFile);
if (this.options.verbose) {
console.log(`\u{1F3AF} Found ${injectMarkers.length} inject markers in interface ${interfaceDecl.getName()}`);
}
const dependencies = [];
for (const marker of injectMarkers) {
const dependency = this.convertInjectMarkerToDependency(marker, sourceFile, context);
if (dependency) {
dependencies.push(dependency);
}
}
return dependencies;
}
/**
* Extract from type alias declaration using recursive extraction
*/
extractFromTypeAliasDeclaration(typeAlias, sourceFile, context) {
if (this.options.verbose) {
console.log(`\u2705 Recursively extracting dependencies from type alias ${typeAlias.getName()}`);
}
const injectMarkers = this.recursiveExtractor.extractFromTypeAliasDeclaration(typeAlias, sourceFile);
if (this.options.verbose) {
console.log(`\u{1F3AF} Found ${injectMarkers.length} inject markers in type alias ${typeAlias.getName()}`);
}
const dependencies = [];
for (const marker of injectMarkers) {
const dependency = this.convertInjectMarkerToDependency(marker, sourceFile, context);
if (dependency) {
dependencies.push(dependency);
}
}
return dependencies;
}
/**
* Check if parameter has @Inject decorator
*/
hasInjectDecorator(param) {
return param.getDecorators().some((decorator) => {
try {
const expression = decorator.getExpression();
if (Node2.isCallExpression(expression)) {
const expressionText = expression.getExpression().getText();
return this.isInjectDecoratorName(expressionText);
} else if (Node2.isIdentifier(expression)) {
const expressionText = expression.getText();
return this.isInjectDecoratorName(expressionText);
}
} catch (error) {
}
return false;
});
}
/**
* Check if decorator name indicates injection
*/
isInjectDecoratorName(decoratorName) {
const injectDecorators = [
"Inject",
"AutoWireInject",
"Autowired",
"Dependency",
"Resource",
"Value"
];
return injectDecorators.some((name) => decoratorName === name || decoratorName.includes(name));
}
/**
* Extract qualifier from parameter decorators
*/
extractQualifier(param) {
const decorators = param.getDecorators();
for (const decorator of decorators) {
try {
const expression = decorator.getExpression();
if (Node2.isCallExpression(expression)) {
const decoratorName = expression.getExpression().getText();
if (decoratorName === "Qualifier") {
const args = expression.getArguments();
if (args.length > 0) {
const qualifierArg = args[0];
if (Node2.isStringLiteral(qualifierArg)) {
return qualifierArg.getLiteralValue();
}
}
}
}
} catch (error) {
}
}
return void 0;
}
/**
* Validate extracted dependencies
*/
validateDependencies(dependencies) {
const errors = [];
const warnings = [];
for (const dep of dependencies) {
if (!dep.resolvedImplementation && !dep.isOptional) {
errors.push(`Required dependency '${dep.serviceKey}' (${dep.interfaceType}) has no implementation`);
}
if (!dep.resolvedImplementation && dep.isOptional) {
warnings.push(`Optional dependency '${dep.serviceKey}' (${dep.interfaceType}) has no implementation`);
}
const duplicates = dependencies.filter((d) => d.serviceKey === dep.serviceKey);
if (duplicates.length > 1) {
errors.push(`Duplicate service key '${dep.serviceKey}' found`);
}
}
return {
isValid: errors.length === 0,
errors,
warnings
};
}
/**
* Group dependencies by resolution strategy
*/
groupByResolutionStrategy(dependencies) {
const groups = {
interface: [],
inheritance: [],
state: [],
class: [],
notFound: []
};
for (const dep of dependencies) {
if (!dep.resolvedImplementation) {
groups.notFound.push(dep);
} else if (dep.resolvedImplementation.isStateBased) {
groups.state.push(dep);
} else if (dep.resolvedImplementation.isInheritanceBased) {
groups.inheritance.push(dep);
} else if (dep.resolvedImplementation.isClassBased) {
groups.class.push(dep);
} else {
groups.interface.push(dep);
}
}
return groups;
}
/**
* Get dependency statistics
*/
getDependencyStats(dependencies) {
const stats = {
total: dependencies.length,
resolved: 0,
optional: 0,
missing: 0,
bySource: {}
};
for (const dep of dependencies) {
if (dep.resolvedImplementation) stats.resolved++;
if (dep.isOptional) stats.optional++;
if (!dep.resolvedImplementation) stats.missing++;
stats.bySource[dep.extractionSource] = (stats.bySource[dep.extractionSource] || 0) + 1;
}
return stats;
}
};
__name(_SharedDependencyExtractor, "SharedDependencyExtractor");
var SharedDependencyExtractor = _SharedDependencyExtractor;
// tools/shared/SharedServiceRegistry.ts
import * as path3 from "path";
import * as fs2 from "fs";
var _SharedServiceRegistry = class _SharedServiceRegistry {
constructor(configManager, options = {}) {
__publicField(this, "configManager");
__publicField(this, "options");
__publicField(this, "services", /* @__PURE__ */ new Map());
__publicField(this, "interfaceMapping", /* @__PURE__ */ new Map());
__publicField(this, "classMapping", /* @__PURE__ */ new Map());
__publicField(this, "dependencyGraph", /* @__PURE__ */ new Map());
this.configManager = configManager;
this.options = options;
}
/**
* Register a service implementation
*/
registerService(implementation, dependencies) {
const registration = this.createServiceRegistration(implementation, dependencies);
this.services.set(registration.token, registration);
this.updateInterfaceMapping(registration);
this.classMapping.set(registration.implementationClass, registration.token);
this.updateDependencyGraph(registration);
if (this.options.verbose) {
console.log(`\u{1F4DD} Registered: ${registration.token} -> ${registration.implementationClass} (${registration.registrationType})`);
}
}
/**
* Register multiple services efficiently
*/
registerServices(implementations, dependencyMap) {
for (const implementation of implementations) {
const dependencies = dependencyMap.get(implementation.implementationClass) || [];
this.registerService(implementation, dependencies);
}
}
/**
* Get service registration by token
*/
getService(token) {
return this.services.get(token);
}
/**
* Get all services implementing an interface
*/
getServicesByInterface(interfaceName) {
const implementationClasses = this.interfaceMapping.get(interfaceName) || [];
return implementationClasses.map((className) => this.getServiceByClass(className)).filter((service) => !!service);
}
/**
* Get service by implementation class name
*/
getServiceByClass(className) {
const token = this.classMapping.get(className);
return token ? this.services.get(token) : void 0;
}
/**
* Get all registered services
*/
getAllServices() {
return Array.from(this.services.values());
}
/**
* Get dependency graph for a service
*/
getDependencies(serviceToken) {
return this.dependencyGraph.get(serviceToken) || [];
}
/**
* Generate DI configuration file
*/
async generateDIConfiguration() {
const configContent = this.generateConfigContent();
const configFilePath = path3.join(this.configManager.getConfigDir(), "di-config.ts");
await fs2.promises.writeFile(configFilePath, configContent, "utf8");
if (this.options.verbose) {
console.log(`\u{1F4DD} Generated DI configuration: ${configFilePath}`);
}
}
/**
* Generate service registry file
*/
async generateServiceRegistry() {
const registryContent = this.generateRegistryContent();
const registryFilePath = path3.join(this.configManager.getConfigDir(), "service-registry.ts");
await fs2.promises.writeFile(registryFilePath, registryContent, "utf8");
if (this.options.verbose) {
console.log(`\u{1F4DD} Generated service registry: ${registryFilePath}`);
}
}
/**
* Validate registry configuration
*/
validateRegistry() {
const errors = [];
const warnings = [];
const circularDeps = this.findCircularDependencies();
if (circularDeps.length > 0) {
errors.push(...circularDeps.map((cycle) => `Circular dependency: ${cycle.join(" -> ")}`));
}
for (const [serviceToken, deps] of this.dependencyGraph) {
for (const depToken of deps) {
if (!this.services.has(depToken)) {
errors.push(`Service ${serviceToken} depends on missing service ${depToken}`);
}
}
}
const ambiguous = this.findAmbiguousRegistrations();
if (ambiguous.length > 0) {
warnings.push(...ambiguous.map((iface) => `Multiple implementations for interface: ${iface}`));
}
const stats = this.generateStats();
return {
isValid: errors.length === 0,
errors,
warnings,
stats
};
}
/**
* Create service registration from implementation and dependencies
*/
createServiceRegistration(implementation, dependencies) {
const dependencyTokens = dependencies.map((dep) => dep.sanitizedKey).filter((token) => token);
return {
token: implementation.sanitizedKey,
interfaceName: implementation.interfaceName,
implementationClass: implementation.implementationClass,
scope: "singleton",
dependencies: dependencyTokens,
factory: this.generateFactoryName(implementation.implementationClass),
filePath: implementation.filePath,
registrationType: this.determineRegistrationType(implementation),
metadata: {
isGeneric: implementation.isGeneric,
typeParameters: implementation.typeParameters,
sanitizedKey: implementation.sanitizedKey,
baseClass: implementation.baseClass,
baseClassGeneric: implementation.baseClassGeneric,
stateType: implementation.stateType,
serviceInterface: implementation.serviceInterface,
isAutoResolved: true
}
};
}
/**
* Update interface mapping
*/
updateInterfaceMapping(registration) {
const { interfaceName, implementationClass } = registration;
if (!this.interfaceMapping.has(interfaceName)) {
this.interfaceMapping.set(interfaceName, []);
}
const implementations = this.interfaceMapping.get(interfaceName);
if (!implementations.includes(implementationClass)) {
implementations.push(implementationClass);
}
}
/**
* Update dependency graph
*/
updateDependencyGraph(registration) {
this.dependencyGraph.set(registration.token, registration.dependencies);
}
/**
* Determine registration type from implementation
*/
determineRegistrationType(implementation) {
if (implementation.isStateBased) return "state";
if (implementation.isInheritanceBased) return "inheritance";
if (implementation.isClassBased) return "class";
return "interface";
}
/**
* Generate factory function name
*/
generateFactoryName(implementationClass) {
return `create${implementationClass}`;
}
/**
* Generate DI configuration content
*/
generateConfigContent() {
const imports = [];
const factories = [];
const diMapEntries = [];
const processedClasses = /* @__PURE__ */ new Set();
for (const [token, registration] of this.services) {
const { implementationClass } = registration;
if (!processedClasses.has(implementationClass)) {
processedClasses.add(implementationClass);
const configDir = this.configManager.getConfigDir();
const servicePath = path3.resolve(registration.filePath);
const relativePath = path3.relative(configDir, servicePath).replace(/\.(ts|tsx)$/, "").replace(/\\/g, "/");
const importPath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
imports.push(`import { ${implementationClass} } from '${importPath}';`);
const factoryCode = this.generateFactoryFunction(registration);
factories.push(factoryCode);
}
diMapEntries.push(` '${token}': {
factory: ${registration.factory},
scope: '${registration.scope}' as const,
dependencies: [${registration.dependencies.map((dep) => `'${dep}'`).join(", ")}],
interfaceName: '${registration.interfaceName}',
implementationClass: '${implementationClass}',
isAutoResolved: ${registration.metadata.isAutoResolved},
registrationType: '${registration.registrationType}',
isClassBased: ${registration.registrationType === "class"},
isInheritanceBased: ${registration.registrationType === "inheritance"},
isStateBased: ${registration.registrationType === "state"},
baseClass: ${registration.metadata.baseClass ? `'${registration.metadata.baseClass}'` : "null"},
baseClassGeneric: ${registration.metadata.baseClassGeneric ? `'${registration.metadata.baseClassGeneric}'` : "null"},
stateType: ${registration.metadata.stateType ? `'${registration.metadata.stateType}'` : "null"},
serviceInterface: ${registration.metadata.serviceInterface ? `'${registration.metadata.serviceInterface}'` : "null"}
}`);
}
return `// Auto-generated DI configuration
// Config: ${this.configManager.getConfigHash()}
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
${imports.join("\n")}
// Factory functions
${factories.join("\n\n")}
// DI Configuration Map
export const DI_CONFIG = {
${diMapEntries.join(",\n")}
};
// Service mappings
export const SERVICE_TOKENS = {
${Array.from(this.classMapping.entries()).map(([className, token]) => ` '${className}': '${token}'`).join(",\n")}
};
export const INTERFACE_IMPLEMENTATIONS = {
${Array.from(this.interfaceMapping.entries()).map(([interfaceName, implementations]) => ` '${interfaceName}': [${implementations.map((impl) => `'${impl}'`).join(", ")}]`).join(",\n")}
};`;
}
/**
* Generate service registry content
*/
generateRegistryContent() {
const servicesList = Array.from(this.services.values());
return `// Auto-generated service registry
// Config: ${this.configManager.getConfigHash()}
// Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
export const REGISTRY_STATS = {
totalServices: ${servicesList.length},
byType: {
interface: ${servicesList.filter((s) => s.registrationType === "interface").length},
inheritance: ${servicesList.filter((s) => s.registrationType === "inheritance").length},
state: ${servicesList.filter((s) => s.registrationType === "state").length},
class: ${servicesList.filter((s) => s.registrationType === "class").length}
},
withDependencies: ${servicesList.filter((s) => s.dependencies.length > 0).length}
};
export const ALL_SERVICES = ${JSON.stringify(servicesList, null, 2)};`;
}
/**
* Generate factory function for service
*/
generateFactoryFunction(registration) {
const { implementationClass, dependencies } = registration;
if (dependencies.length === 0) {
return `function ${registration.factory}(container: any) {
return () => new ${implementationClass}();
}`;
}
const dependencyResolves = dependencies.map((depToken, index) => ` const dep${index} = container.resolve('${depToken}');`).join("\n");
const constructorArgs = dependencies.map((_, index) => `dep${index}`).join(", ");
return `function ${registration.factory}(container: any) {
return () => {
${dependencyResolves}
return new ${implementationClass}(${constructorArgs});
};
}`;
}
/**
* Find circular dependencies
*/
findCircularDependencies() {
const cycles = [];
const visited = /* @__PURE__ */ new Set();
const recursionStack = /* @__PURE__ */ new Set();
const detectCycle = /* @__PURE__ */ __name((token, path8) => {
if (recursionStack.has(token)) {
const cycleStart = path8.indexOf(token);
if (cycleStart >= 0) {
cycles.push(path8.slice(cycleStart).concat([
token
]));
}
return true;
}
if (visited.has(token)) return false;
visited.add(token);
recursionStack.add(token);
const dependencies = this.dependencyGraph.get(token) || [];
for (const depToken of dependencies) {
if (detectCycle(depToken, [
...path8,
token
])) {
recursionStack.delete(token);
return true;
}
}
recursionStack.delete(token);
return false;
}, "detectCycle");
for (const token of this.services.keys()) {
if (!visited.has(token)) {
detectCycle(token, []);
}
}
return cycles;
}
/**
* Find interfaces with multiple implementations
*/
findAmbiguousRegistrations() {
const ambiguous = [];
for (const [interfaceName, implementations] of this.interfaceMapping) {
if (implementations.length > 1) {
ambiguous.push(interfaceName);
}
}
return ambiguous;
}
/**
* Generate registry statistics
*/
generateStats() {
const services = Array.from(this.services.values());
const byType = {};
const byScope = {};
for (const service of services) {
byType[service.registrationType] = (byType[service.registrationType] || 0) + 1;
byScope[service.scope] = (byScope[service.scope] || 0) + 1;
}
return {
totalServices: services.length,
byType,
byScope,
withDependencies: services.filter((s) => s.dependencies.length > 0).length,
orphanedServices: this.findOrphanedServices().length
};
}
/**
* Find services that are never used as dependencies
*/
findOrphanedServices() {
const usedTokens = /* @__PURE__ */ new Set();
for (const dependencies of this.dependencyGraph.values()) {
for (const token of dependencies) {
usedTokens.add(token);
}
}