diff --git a/packages/nestjs-trpc/lib/generators/procedure.generator.ts b/packages/nestjs-trpc/lib/generators/procedure.generator.ts index d99d0e1..5a82cda 100644 --- a/packages/nestjs-trpc/lib/generators/procedure.generator.ts +++ b/packages/nestjs-trpc/lib/generators/procedure.generator.ts @@ -40,7 +40,7 @@ export class ProcedureGenerator { public flattenZodSchema( node: Node, - sourceFile: SourceFile, + sourceFile: SourceFile | null, project: Project, schema: string, ): string { @@ -51,7 +51,7 @@ export class ProcedureGenerator { if (Node.isIdentifier(node)) { const identifierName = node.getText(); const identifierDeclaration = - sourceFile.getVariableDeclaration(identifierName); + sourceFile?.getVariableDeclaration(identifierName); if (identifierDeclaration != null) { const identifierInitializer = identifierDeclaration.getInitializer(); @@ -70,15 +70,27 @@ export class ProcedureGenerator { const importedIdentifier = importsMap.get(identifierName); if (importedIdentifier != null) { - const { initializer } = importedIdentifier; - const identifierSchema = this.flattenZodSchema( - initializer, - importedIdentifier.sourceFile, - project, - initializer.getText(), - ); + const { initializer, sourceFile: importedSourceFile } = importedIdentifier; + + // Handle external imports (workspace packages, node_modules) + if (initializer == null || importedSourceFile == null) { + // Can't flatten external imports, add to imports and keep as-is + this.staticGenerator.addSchemaImports( + this.appRouterSourceFile, + [identifierName], + importsMap, + ); + } else { + // Flatten local imports + const identifierSchema = this.flattenZodSchema( + initializer, + importedSourceFile, + project, + initializer.getText(), + ); - schema = schema.replace(identifierName, identifierSchema); + schema = schema.replace(identifierName, identifierSchema); + } } } } else if (Node.isObjectLiteralExpression(node)) { diff --git a/packages/nestjs-trpc/lib/generators/static.generator.ts b/packages/nestjs-trpc/lib/generators/static.generator.ts index 0e031b6..d51e1ff 100644 --- a/packages/nestjs-trpc/lib/generators/static.generator.ts +++ b/packages/nestjs-trpc/lib/generators/static.generator.ts @@ -40,30 +40,69 @@ export class StaticGenerator { schemaImportNames: Array, importsMap: Map, ): void { - const importDeclarations: ImportDeclarationStructure[] = []; + // Group imports by their module specifier + const importsByModule = new Map>(); for (const schemaImportName of schemaImportNames) { - for (const [importMapKey, importMapMetadata] of importsMap.entries()) { - if (schemaImportName == null || importMapKey !== schemaImportName) { - continue; + const importMapMetadata = importsMap.get(schemaImportName); + + if (importMapMetadata == null) { + continue; + } + + // Handle external/workspace imports (e.g., @repo/trpc/schemas) + if (importMapMetadata.moduleSpecifier != null) { + if (!importsByModule.has(importMapMetadata.moduleSpecifier)) { + importsByModule.set(importMapMetadata.moduleSpecifier, new Set()); } + importsByModule.get(importMapMetadata.moduleSpecifier)!.add(schemaImportName); + continue; + } + // Handle local imports with relative paths + if (importMapMetadata.sourceFile != null) { const relativePath = path.relative( path.dirname(sourceFile.getFilePath()), importMapMetadata.sourceFile.getFilePath().replace(/\.ts$/, ''), ); - importDeclarations.push({ + const moduleSpecifier = relativePath.startsWith('.') + ? relativePath + : `./${relativePath}`; + + if (!importsByModule.has(moduleSpecifier)) { + importsByModule.set(moduleSpecifier, new Set()); + } + importsByModule.get(moduleSpecifier)!.add(schemaImportName); + } + } + + // Merge with existing imports or create new ones + for (const [moduleSpecifier, namedImportsSet] of importsByModule) { + const existingImport = sourceFile + .getImportDeclarations() + .find((imp) => imp.getModuleSpecifierValue() === moduleSpecifier); + + if (existingImport) { + // Add to existing import declaration + const existingNamedImports = existingImport + .getNamedImports() + .map((ni) => ni.getName()); + + for (const namedImport of namedImportsSet) { + if (!existingNamedImports.includes(namedImport)) { + existingImport.addNamedImport(namedImport); + } + } + } else { + // Create new import declaration + sourceFile.addImportDeclaration({ kind: StructureKind.ImportDeclaration, - moduleSpecifier: relativePath.startsWith('.') - ? relativePath - : `./${relativePath}`, - namedImports: [schemaImportName], + moduleSpecifier, + namedImports: Array.from(namedImportsSet), }); } } - - sourceFile.addImportDeclarations(importDeclarations); } public findCtxOutProperty(type: Type): string | undefined { diff --git a/packages/nestjs-trpc/lib/generators/trpc.generator.ts b/packages/nestjs-trpc/lib/generators/trpc.generator.ts index f12a33d..a31b325 100644 --- a/packages/nestjs-trpc/lib/generators/trpc.generator.ts +++ b/packages/nestjs-trpc/lib/generators/trpc.generator.ts @@ -147,16 +147,19 @@ export class TRPCGenerator implements OnModuleInit { throw new Error('Could not find context import declaration.'); } - const contextType = await this.contextHandler.getContextInterface( - contextImport.sourceFile, - context, - ); + // Skip context type generation if it's an external import + if (contextImport.sourceFile != null) { + const contextType = await this.contextHandler.getContextInterface( + contextImport.sourceFile, + context, + ); - helperTypesSourceFile.addTypeAlias({ - isExported: true, - name: 'Context', - type: contextType ?? '{}', - }); + helperTypesSourceFile.addTypeAlias({ + isExported: true, + name: 'Context', + type: contextType ?? '{}', + }); + } } for (const middleware of middlewares) { diff --git a/packages/nestjs-trpc/lib/interfaces/generator.interface.ts b/packages/nestjs-trpc/lib/interfaces/generator.interface.ts index 1919d5e..da91714 100644 --- a/packages/nestjs-trpc/lib/interfaces/generator.interface.ts +++ b/packages/nestjs-trpc/lib/interfaces/generator.interface.ts @@ -34,6 +34,8 @@ export interface SourceFileImportsMap { | InterfaceDeclaration | EnumDeclaration | VariableDeclaration - | FunctionDeclaration; - sourceFile: SourceFile; + | FunctionDeclaration + | null; + sourceFile: SourceFile | null; + moduleSpecifier?: string; } diff --git a/packages/nestjs-trpc/lib/scanners/imports.scanner.ts b/packages/nestjs-trpc/lib/scanners/imports.scanner.ts index aa7ef01..af3802a 100644 --- a/packages/nestjs-trpc/lib/scanners/imports.scanner.ts +++ b/packages/nestjs-trpc/lib/scanners/imports.scanner.ts @@ -5,10 +5,15 @@ import { SourceFileImportsMap } from '../interfaces/generator.interface'; @Injectable() export class ImportsScanner { public buildSourceFileImportsMap( - sourceFile: SourceFile, + sourceFile: SourceFile | null, project: Project, ): Map { const sourceFileImportsMap = new Map(); + + if (sourceFile == null) { + return sourceFileImportsMap; + } + const importDeclarations = sourceFile.getImportDeclarations(); for (const importDeclaration of importDeclarations) { @@ -19,6 +24,12 @@ export class ImportsScanner { importDeclaration.getModuleSpecifierSourceFile(); if (importedSourceFile == null) { + // Preserve external/workspace imports that can't be resolved + sourceFileImportsMap.set(name, { + initializer: null, + sourceFile: null, + moduleSpecifier: importDeclaration.getModuleSpecifierValue(), + }); continue; }