import { Type } from "../index"; import { Provider } from "../angular/app-config"; export class Injector { private static instance: Injector; private instanceCache: Record = {}; private dependencyCache: Record = {}; private sharedInstances: Record; private constructor() { this.sharedInstances = this.getSharedInstances(); } private getSharedInstances(): Record { window.__quarc.sharedInstances ??= {}; return window.__quarc.sharedInstances; } public static get(): Injector { if (!Injector.instance) { Injector.instance = new Injector(); } return Injector.instance; } public createInstance(classType: Type): T { return this.createInstanceWithProviders(classType, []); } private findProvider(token: any, providers: Provider[]): Provider | undefined { const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name; return providers.find(p => { const providerName = typeof p.provide === 'string' ? p.provide : (p.provide as any).__quarc_original_name__ || p.provide.name; return providerName === tokenName; }); } private resolveProviderValue(provider: Provider, providers: Provider[]): any { if ('useValue' in provider) { return provider.useValue; } else if ('useFactory' in provider && provider.useFactory) { return provider.useFactory(); } else if ('useExisting' in provider && provider.useExisting) { const existingToken = provider.useExisting; const existingProvider = this.findProvider(existingToken, providers); if (existingProvider) { return this.resolveProviderValue(existingProvider, providers); } const existingKey = typeof existingToken === 'string' ? existingToken : (existingToken as any).__quarc_original_name__ || existingToken.name; return this.sharedInstances[existingKey] || this.instanceCache[existingKey]; } else if ('useClass' in provider && provider.useClass) { return this.createInstanceWithProviders(provider.useClass, providers); } return undefined; } public createInstanceWithProviders(classType: Type, providers: Provider[]): T { if (!classType) { throw new Error(`[DI] createInstanceWithProviders called with undefined classType`); } try { const dependencies = this.resolveDependenciesWithProviders(classType, providers); /** / console.log({ className: (classType as any).__quarc_original_name__ || classType.name, providers, dependencies, classType, }); /**/ const instance = new classType(...dependencies); const key = (classType as any).__quarc_original_name__ || classType.name; this.instanceCache[key] = instance; return instance; } catch (error) { const className = this.getReadableClassName(classType); const dependencyInfo = this.getDependencyInfo(classType); throw new Error(`[DI] Failed to create instance of "${className}" with providers: ${(error as Error).message}\nDependencies: ${dependencyInfo}`); } } private getReadableClassName(classType: Type): string { // Try to get original name from static metadata (saved during compilation) const staticOriginalName = (classType as any).__quarc_original_name__; if (staticOriginalName) { return staticOriginalName; } // Try to get from instance metadata const originalName = (classType as any).__quarc_original_name__; if (originalName) { return originalName; } // Try to get from constructor name const constructorName = classType?.name; if (constructorName && constructorName !== 'Unknown' && constructorName.length > 1) { return constructorName; } // Try to get from selector for components/directives const metadata = (classType as any)._quarcComponent?.[0] || (classType as any)._quarcDirective?.[0]; if (metadata?.selector) { return `${metadata.selector} (class)`; } return 'Unknown class'; } private getDependencyInfo(classType: Type): string { try { const paramTypes = this.getConstructorParameterTypes(classType); if (paramTypes.length === 0) { return 'none'; } const dependencyNames = paramTypes.map((depType, index) => { const depName = depType; const isUndefined = depType === undefined; return isUndefined ? `index ${index}: undefined` : `index ${index}: ${depName}`; }); return dependencyNames.join(', '); } catch (depError) { return `failed to resolve: ${(depError as Error).message}`; } } private resolveDependencies(classType: Type): any[] { const key = (classType as any).__quarc_original_name__ || classType.name; if (this.dependencyCache[key]) { const cachedDependencies = this.dependencyCache[key]!; return cachedDependencies.map(token => { if (typeof token === 'string') { // This should not happen in global context throw new Error(`[DI] Cannot resolve string token in global context: ${token}`); } return this.createInstance(token); }); } const tokens = this.getConstructorParameterTypes(classType); this.dependencyCache[key] = tokens; return tokens.map(token => { if (typeof token === 'string') { throw new Error(`[DI] Cannot resolve string token in global context: ${token}`); } return this.createInstance(token); }); } private resolveDependenciesWithProviders(classType: Type, providers: Provider[]): any[] { const tokens = this.getConstructorParameterTypes(classType); return tokens.map(token => { return this.resolveDependency(token, providers); }); } private resolveDependency(token: any, providers: Provider[]): any { const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name; const provider = this.findProvider(token, providers); if (provider) { return this.resolveProviderValue(provider, providers); } if (this.sharedInstances[tokenName]) { return this.sharedInstances[tokenName]; } if (this.instanceCache[tokenName]) { return this.instanceCache[tokenName]; } return this.createInstanceWithProviders(token, providers); } private getConstructorParameterTypes(classType: Type): any[] { const className = classType?.name || 'Unknown'; if (!classType) { throw new Error(`[DI] Cannot resolve dependencies: classType is undefined`); } if ((classType as any).__di_params__) { const params = (classType as any).__di_params__; for (let i = 0; i < params.length; i++) { if (params[i] === undefined) { throw new Error( `[DI] Cannot resolve dependency at index ${i} for class "${className}". ` + `The dependency type is undefined. This usually means:\n` + ` 1. Circular dependency between modules\n` + ` 2. The dependency class is not exported or imported correctly\n` + ` 3. The import is type-only but used for DI` ); } } return params; } const reflectMetadata = (Reflect as any).getMetadata; if (reflectMetadata) { return reflectMetadata('design:paramtypes', classType) || []; } return []; } public register(classType: Type, instance: T | Type): void { const key = (classType as any).__quarc_original_name__ || classType.name; this.instanceCache[key] = instance; } public registerShared(classType: Type, instance: T | Type): void { const key = (classType as any).__quarc_original_name__ || classType.name; this.sharedInstances[key] = instance; } public clear(): void { this.instanceCache = {}; this.dependencyCache = {}; } }