Skip to content

Commit 3f1a4b4

Browse files
committed
fix: import proto response types in v2 codegen when no Orval counterpart exists
The code generator only added request types to proto imports, so when a new API had no legacy Orval V1 types (e.g. ProjectStorage in #9203), the response type was referenced but never imported — failing TypeScript. Also cleaned up the import generation section for readability: colocated variables with their usage, improved comments, consistent naming (protoImports/orvalImports), and resequenced the collection loop next to the emission code it feeds.
1 parent 3621a45 commit 3f1a4b4

2 files changed

Lines changed: 54 additions & 37 deletions

File tree

web-common/src/runtime-client/v2/codegen/generator.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,28 @@ describe("generateServiceFile", () => {
398398
expect(code).not.toContain("createMutation");
399399
expect(code).not.toContain("CreateMutationOptions");
400400
});
401+
402+
it("imports proto response types when no Orval counterpart exists", () => {
403+
// With no Orval types, response types must be imported from _pb.ts
404+
// so PartialMessage<FakeResponse> resolves in TypeScript
405+
expect(output).toContain("FakeResponse,");
406+
expect(output).toContain("FakePaginatedResponse,");
407+
expect(output).toContain("PartialMessage");
408+
});
409+
410+
it("does not import proto response types when Orval counterpart exists", () => {
411+
const orvalTypes = new Set([
412+
"V1FakeRequestWithInstanceId",
413+
"V1FakeRequestWithoutInstanceId",
414+
"V1FakeResponse",
415+
"V1FakePaginatedRequest",
416+
"V1FakePaginatedResponse",
417+
]);
418+
const { code } = generateServiceFile(mockService, orvalTypes);
419+
// Response types should come from Orval, not proto
420+
expect(code).toContain("V1FakeResponse");
421+
expect(code).not.toContain("PartialMessage");
422+
});
401423
});
402424
});
403425

web-common/src/runtime-client/v2/codegen/generator.ts

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -376,44 +376,20 @@ function generateServiceFile(
376376
(m) => m.hasPageToken && m.hasNextPageToken,
377377
);
378378

379-
// Collect proto types (value imports; needed for fromJson/toJson calls)
380-
const protoTypes = new Set<string>();
381-
// Collect Orval types (type-only imports; used in public API signatures)
382-
const orvalTypeImports = new Set<string>();
383-
// Track whether PartialMessage is needed (for request types without V1 counterparts)
384-
let needsPartialMessage = false;
385-
const needsInfiniteQuery = infiniteQueryMethods.length > 0;
386-
387-
for (const m of methods) {
388-
protoTypes.add(m.inputType);
389-
390-
if (hasOrvalType(availableOrvalTypes, m.inputType)) {
391-
orvalTypeImports.add(orvalTypeName(m.inputType));
392-
} else {
393-
needsPartialMessage = true;
394-
}
395-
396-
if (hasOrvalType(availableOrvalTypes, m.outputType)) {
397-
orvalTypeImports.add(orvalTypeName(m.outputType));
398-
}
399-
}
400-
401379
const lines: string[] = [];
402380

403381
// Header
404382
lines.push(`// Generated by codegen/run.ts — DO NOT EDIT`);
405383
lines.push(``);
406384

407-
// Imports are sorted to match VS Code's "Organize Imports" order:
408-
// package imports first (alphabetical), then relative imports (alphabetical).
409-
// Specifiers within each import: values first, then types, each alphabetical.
410-
411-
const hasQueries = queryMethods.length > 0;
412-
const hasMutations = mutationMethods.length > 0;
413-
414-
// --- Package imports (sorted by module path) ---
385+
// --- Package imports ---
415386

416387
// @bufbuild/protobuf
388+
const needsPartialMessage = methods.some(
389+
(m) =>
390+
!hasOrvalType(availableOrvalTypes, m.inputType) ||
391+
!hasOrvalType(availableOrvalTypes, m.outputType),
392+
);
417393
const bufSpecs: string[] = ["JsonValue"];
418394
if (needsPartialMessage) bufSpecs.push("PartialMessage");
419395
bufSpecs.sort();
@@ -424,7 +400,10 @@ function generateServiceFile(
424400
// @connectrpc/connect
425401
lines.push(`import type { ConnectError } from "@connectrpc/connect";`);
426402

427-
// @tanstack/svelte-query (mixed import: values first, then inline types)
403+
// @tanstack/svelte-query
404+
const hasQueries = queryMethods.length > 0;
405+
const hasMutations = mutationMethods.length > 0;
406+
const needsInfiniteQuery = infiniteQueryMethods.length > 0;
428407
const tanstackValues: string[] = [];
429408
const tanstackTypes: string[] = [];
430409
if (hasQueries) {
@@ -461,19 +440,35 @@ function generateServiceFile(
461440
);
462441
}
463442

464-
// --- Relative imports (sorted by module path) ---
443+
// --- Relative imports ---
444+
445+
// Use legacy Orval types (from index.schemas.ts) for methods that still
446+
// have them; use proto types (from _pb.ts) for new and migrated methods.
447+
const protoImports = new Set<string>();
448+
const orvalImports = new Set<string>();
449+
450+
for (const m of methods) {
451+
protoImports.add(m.inputType); // always needed for serialization
452+
if (hasOrvalType(availableOrvalTypes, m.inputType)) {
453+
orvalImports.add(orvalTypeName(m.inputType));
454+
}
455+
456+
if (hasOrvalType(availableOrvalTypes, m.outputType)) {
457+
orvalImports.add(orvalTypeName(m.outputType));
458+
} else {
459+
protoImports.add(m.outputType);
460+
}
461+
}
465462

466-
// Proto type imports (value; needed for fromJson calls)
467-
const sortedProto = [...protoTypes].sort();
463+
const sortedProto = [...protoImports].sort();
468464
lines.push(
469465
`import {`,
470466
...sortedProto.map((t) => ` ${t},`),
471467
`} from "${protoImportPath}";`,
472468
);
473469

474-
// Orval type imports (type-only; for public API signatures)
475-
if (orvalTypeImports.size > 0) {
476-
const sortedOrval = [...orvalTypeImports].sort();
470+
if (orvalImports.size > 0) {
471+
const sortedOrval = [...orvalImports].sort();
477472
lines.push(
478473
`import type {`,
479474
...sortedOrval.map((t) => ` ${t},`),

0 commit comments

Comments
 (0)