From e48f6f778218e617b63bfe104d542ab152039663 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 26 Oct 2025 09:59:33 -0400 Subject: [PATCH 1/2] Adds support for monorepo imports --- .../lib/generators/procedure.generator.ts | 32 +++++++++---- .../lib/generators/static.generator.ts | 46 ++++++++++++++----- .../lib/generators/trpc.generator.ts | 21 +++++---- .../lib/interfaces/generator.interface.ts | 6 ++- .../lib/scanners/imports.scanner.ts | 13 +++++- 5 files changed, 84 insertions(+), 34 deletions(-) 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..c37c915 100644 --- a/packages/nestjs-trpc/lib/generators/static.generator.ts +++ b/packages/nestjs-trpc/lib/generators/static.generator.ts @@ -40,29 +40,51 @@ 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) { + const existing = importsByModule.get(importMapMetadata.moduleSpecifier) || []; + existing.push(schemaImportName); + importsByModule.set(importMapMetadata.moduleSpecifier, existing); + 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({ - kind: StructureKind.ImportDeclaration, - moduleSpecifier: relativePath.startsWith('.') - ? relativePath - : `./${relativePath}`, - namedImports: [schemaImportName], - }); + const moduleSpecifier = relativePath.startsWith('.') + ? relativePath + : `./${relativePath}`; + + const existing = importsByModule.get(moduleSpecifier) || []; + existing.push(schemaImportName); + importsByModule.set(moduleSpecifier, existing); } } + // Generate import declarations grouped by module + const importDeclarations: ImportDeclarationStructure[] = []; + for (const [moduleSpecifier, namedImports] of importsByModule) { + importDeclarations.push({ + kind: StructureKind.ImportDeclaration, + moduleSpecifier, + namedImports, + }); + } + sourceFile.addImportDeclarations(importDeclarations); } 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; } From 8fec688aa34f45066de952b456ec92becbe62dab Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 26 Oct 2025 10:29:12 -0400 Subject: [PATCH 2/2] De-duplicate imports --- .../lib/generators/static.generator.ts | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/packages/nestjs-trpc/lib/generators/static.generator.ts b/packages/nestjs-trpc/lib/generators/static.generator.ts index c37c915..d51e1ff 100644 --- a/packages/nestjs-trpc/lib/generators/static.generator.ts +++ b/packages/nestjs-trpc/lib/generators/static.generator.ts @@ -41,7 +41,7 @@ export class StaticGenerator { importsMap: Map, ): void { // Group imports by their module specifier - const importsByModule = new Map(); + const importsByModule = new Map>(); for (const schemaImportName of schemaImportNames) { const importMapMetadata = importsMap.get(schemaImportName); @@ -52,9 +52,10 @@ export class StaticGenerator { // Handle external/workspace imports (e.g., @repo/trpc/schemas) if (importMapMetadata.moduleSpecifier != null) { - const existing = importsByModule.get(importMapMetadata.moduleSpecifier) || []; - existing.push(schemaImportName); - importsByModule.set(importMapMetadata.moduleSpecifier, existing); + if (!importsByModule.has(importMapMetadata.moduleSpecifier)) { + importsByModule.set(importMapMetadata.moduleSpecifier, new Set()); + } + importsByModule.get(importMapMetadata.moduleSpecifier)!.add(schemaImportName); continue; } @@ -69,23 +70,39 @@ export class StaticGenerator { ? relativePath : `./${relativePath}`; - const existing = importsByModule.get(moduleSpecifier) || []; - existing.push(schemaImportName); - importsByModule.set(moduleSpecifier, existing); + if (!importsByModule.has(moduleSpecifier)) { + importsByModule.set(moduleSpecifier, new Set()); + } + importsByModule.get(moduleSpecifier)!.add(schemaImportName); } } - // Generate import declarations grouped by module - const importDeclarations: ImportDeclarationStructure[] = []; - for (const [moduleSpecifier, namedImports] of importsByModule) { - importDeclarations.push({ - kind: StructureKind.ImportDeclaration, - moduleSpecifier, - namedImports, - }); - } + // 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()); - sourceFile.addImportDeclarations(importDeclarations); + for (const namedImport of namedImportsSet) { + if (!existingNamedImports.includes(namedImport)) { + existingImport.addNamedImport(namedImport); + } + } + } else { + // Create new import declaration + sourceFile.addImportDeclaration({ + kind: StructureKind.ImportDeclaration, + moduleSpecifier, + namedImports: Array.from(namedImportsSet), + }); + } + } } public findCtxOutProperty(type: Type): string | undefined {