Skip to content

Commit caba0fb

Browse files
sayefdeenclaude
andcommitted
fix: handle tRPC v11 flattened type hierarchy in CLI extractor
In tRPC v11, record entries are namespace objects with procedures as direct properties (no nested _def.record). Updated walkRouterType to detect these namespace objects and recurse into their properties to find procedures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e401ebb commit caba0fb

2 files changed

Lines changed: 62 additions & 21 deletions

File tree

packages/core/src/extract.ts

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,37 @@ export function extractRouterOutputSchemas(options: ExtractOptions): Record<stri
181181
return result;
182182
}
183183

184-
function walkRouterType(type: Type, prefix: string, result: Record<string, JsonSchema>): void {
185-
// Get _def.record
186-
const defType = getPropertyType(type, "_def");
187-
if (!defType) return;
184+
function extractProcedureOutputType(propDefType: Type): Type | null {
185+
// v10: _def._output_out
186+
const v10Output = getPropertyType(propDefType, "_output_out");
187+
if (v10Output) return v10Output;
188+
189+
// v11: _def.$types.output
190+
const $types = getPropertyType(propDefType, "$types");
191+
if ($types) {
192+
return getPropertyType($types, "output");
193+
}
188194

189-
const recordType = getPropertyType(defType, "record");
190-
if (!recordType) return;
195+
return null;
196+
}
197+
198+
function isProcedureType(type: Type): boolean {
199+
const defType = getPropertyType(type, "_def");
200+
if (!defType) return false;
201+
// v10: has _output_out or type field
202+
// v11: has $types or procedure: true
203+
return (
204+
getPropertyType(defType, "_output_out") !== null ||
205+
getPropertyType(defType, "$types") !== null ||
206+
getPropertyType(defType, "type") !== null
207+
);
208+
}
191209

210+
function walkRecordEntries(
211+
recordType: Type,
212+
prefix: string,
213+
result: Record<string, JsonSchema>,
214+
): void {
192215
for (const prop of recordType.getProperties()) {
193216
const propName = prop.getName();
194217
const path = prefix ? `${prefix}.${propName}` : propName;
@@ -197,29 +220,47 @@ function walkRouterType(type: Type, prefix: string, result: Record<string, JsonS
197220
if (!propDecl) continue;
198221
const propType = prop.getTypeAtLocation(propDecl);
199222

200-
// Check if this is a nested router or a procedure
223+
// Check if this is a nested router (v10 style — has _def with record)
201224
const propDefType = getPropertyType(propType, "_def");
202225
if (propDefType && isRouterDef(propDefType)) {
203-
// Nested router — recurse
204226
walkRouterType(propType, path, result);
205227
continue;
206228
}
207229

208-
// This is a procedure — extract output type
209-
// v10: _def._output_out
210-
// v11: _def.$types.output
230+
// Check if this is a procedure (has _def with output type info)
211231
if (propDefType) {
212-
let outputType = getPropertyType(propDefType, "_output_out");
213-
if (!outputType) {
214-
const $types = getPropertyType(propDefType, "$types");
215-
if ($types) {
216-
outputType = getPropertyType($types, "output");
217-
}
218-
}
232+
const outputType = extractProcedureOutputType(propDefType);
219233
if (outputType) {
220-
const schema = typeToJsonSchema(outputType);
221-
result[path] = schema;
234+
result[path] = typeToJsonSchema(outputType);
235+
}
236+
continue;
237+
}
238+
239+
// v11: entry has no _def — it's a namespace object with procedures as direct properties
240+
// Check if its children are procedures and recurse
241+
const childProps = propType.getProperties();
242+
const firstChild = childProps[0];
243+
if (firstChild) {
244+
const firstDecl = firstChild.getValueDeclaration() ?? firstChild.getDeclarations()[0];
245+
if (firstDecl) {
246+
const firstChildType = firstChild.getTypeAtLocation(firstDecl);
247+
if (isProcedureType(firstChildType)) {
248+
// This is a v11 namespace — recurse into its properties
249+
walkRecordEntries(propType, path, result);
250+
continue;
251+
}
222252
}
223253
}
224254
}
225255
}
256+
257+
function walkRouterType(type: Type, prefix: string, result: Record<string, JsonSchema>): void {
258+
// Get _def.record
259+
const defType = getPropertyType(type, "_def");
260+
if (!defType) return;
261+
262+
const recordType = getPropertyType(defType, "record");
263+
if (!recordType) return;
264+
265+
walkRecordEntries(recordType, prefix, result);
266+
}

packages/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@srawad/trpc-studio",
3-
"version": "0.1.10",
3+
"version": "0.1.11",
44
"license": "MIT",
55
"description": "Swagger-like UI for tRPC — auto-generated input forms, Try It Out execution, output type visualization, and a CLI for static type extraction",
66
"repository": {

0 commit comments

Comments
 (0)