Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions packages/nestjs-trpc/lib/generators/procedure.generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class ProcedureGenerator {

public flattenZodSchema(
node: Node,
sourceFile: SourceFile,
sourceFile: SourceFile | null,
project: Project,
schema: string,
): string {
Expand All @@ -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();
Expand All @@ -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)) {
Expand Down
61 changes: 50 additions & 11 deletions packages/nestjs-trpc/lib/generators/static.generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,69 @@ export class StaticGenerator {
schemaImportNames: Array<string>,
importsMap: Map<string, SourceFileImportsMap>,
): void {
const importDeclarations: ImportDeclarationStructure[] = [];
// Group imports by their module specifier
const importsByModule = new Map<string, Set<string>>();

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 {
Expand Down
21 changes: 12 additions & 9 deletions packages/nestjs-trpc/lib/generators/trpc.generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 4 additions & 2 deletions packages/nestjs-trpc/lib/interfaces/generator.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface SourceFileImportsMap {
| InterfaceDeclaration
| EnumDeclaration
| VariableDeclaration
| FunctionDeclaration;
sourceFile: SourceFile;
| FunctionDeclaration
| null;
sourceFile: SourceFile | null;
moduleSpecifier?: string;
}
13 changes: 12 additions & 1 deletion packages/nestjs-trpc/lib/scanners/imports.scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { SourceFileImportsMap } from '../interfaces/generator.interface';
@Injectable()
export class ImportsScanner {
public buildSourceFileImportsMap(
sourceFile: SourceFile,
sourceFile: SourceFile | null,
project: Project,
): Map<string, SourceFileImportsMap> {
const sourceFileImportsMap = new Map<string, SourceFileImportsMap>();

if (sourceFile == null) {
return sourceFileImportsMap;
}

const importDeclarations = sourceFile.getImportDeclarations();

for (const importDeclaration of importDeclarations) {
Expand All @@ -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;
}

Expand Down