UNPKG

@tdi2/di-core

Version:

TypeScript Dependency Injection 2 - Core DI framework

1,367 lines (1,346 loc) 231 kB
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); } }