🔎 Search Terms
declaration emit, unbound type parameter, d.ts, trpc, TS 7 native, any fallback
🕗 Version & Regression Information
- Regression from 6.0.3, which emits
any at the same position.
- Reproduces on
typescript@7.0.2 and on typescript@next (7.1.0-dev.20261002.1).
⏯ Playground Link
The repro needs @trpc/server@11.17.0 from npm, so there is no playground link. It is a 4-file project:
// src/contracts.js (a JS module with no types)
exports.StateSchema = { parse: (v) => v };
💻 Code
// src/router.ts
import { initTRPC } from "@trpc/server";
import { type State } from "./contracts.js";
const t = initTRPC.create();
export const getLatest = t.procedure.query((): State | null => null);
npx tsc -p tsconfig.json exits 0 on both 6.0.3 and 7.0.2.
🙁 Actual behavior
dist/router.d.ts from 7.0.2:
export declare const getLatest: import("@trpc/server").TRPCQueryProcedure<{
input: void;
output: TOutputOut extends import("@trpc/server").TRPCUnsetMarker ? $Output : TOutputOut;
meta: object;
}>;
TOutputOut is a type parameter of @trpc/server's ProcedureBuilder interface. $Output is the type parameter of its query<$Output> method. Neither name is declared or imported in the emitted file. UnsetMarker in the same expression is correctly rewritten to import("@trpc/server").TRPCUnsetMarker; only the two type parameters leak.
Inside this type-argument list, consumers end up with any anyway. I checked a 6.0.3 consumer against the 7.0.2 output. A hand-written .d.ts with the same conditional in a directly checked position reports TS2304: Cannot find name 'TOutputOut' and '$Output'.
🙂 Expected behavior
The 6.0.3 output:
export declare const getLatest: import("@trpc/server").TRPCQueryProcedure<{
input: void;
output: any;
meta: object;
}>;
Additional information about the issue
- Trigger: a resolver return annotation that references a type the compiler cannot resolve. Here it is a named import from a JS module with no declarations, and
noImplicitAny: false keeps the import error-free. If the annotation resolves to a real type, or is a literal any, 7.0.2 emits correctly.
- Not needed: NestJS, decorators, zod, path aliases, and middleware.
module: commonjs reproduces too.
- Reduced model: a tRPC-free model of the builder (
interface Builder<TOutputOut> { query<$Output>(...): Procedure<DefaultValue<TOutputOut, $Output>> }) emits any on both versions. The leak seems to need tRPC 11's full ProcedureBuilder.query signature: the conditional TCaller extends true ? … : … return plus the ProcedureResolver alias.
🔎 Search Terms
declaration emit, unbound type parameter, d.ts, trpc, TS 7 native,
anyfallback🕗 Version & Regression Information
anyat the same position.typescript@7.0.2and ontypescript@next(7.1.0-dev.20261002.1).⏯ Playground Link
The repro needs
@trpc/server@11.17.0from npm, so there is no playground link. It is a 4-file project:💻 Code
npx tsc -p tsconfig.jsonexits 0 on both 6.0.3 and 7.0.2.🙁 Actual behavior
dist/router.d.tsfrom 7.0.2:TOutputOutis a type parameter of@trpc/server'sProcedureBuilderinterface.$Outputis the type parameter of itsquery<$Output>method. Neither name is declared or imported in the emitted file.UnsetMarkerin the same expression is correctly rewritten toimport("@trpc/server").TRPCUnsetMarker; only the two type parameters leak.Inside this type-argument list, consumers end up with
anyanyway. I checked a 6.0.3 consumer against the 7.0.2 output. A hand-written.d.tswith the same conditional in a directly checked position reportsTS2304: Cannot find name 'TOutputOut'and'$Output'.🙂 Expected behavior
The 6.0.3 output:
Additional information about the issue
noImplicitAny: falsekeeps the import error-free. If the annotation resolves to a real type, or is a literalany, 7.0.2 emits correctly.module: commonjsreproduces too.interface Builder<TOutputOut> { query<$Output>(...): Procedure<DefaultValue<TOutputOut, $Output>> }) emitsanyon both versions. The leak seems to need tRPC 11's fullProcedureBuilder.querysignature: the conditionalTCaller extends true ? … : …return plus theProcedureResolveralias.