Skip to content

TS 7 declaration emit writes unbound type parameters (TOutputOut, $Output) into .d.ts where 6.0 emits any #64614

Description

🔎 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:

// package.json
{ "dependencies": { "@trpc/server": "11.17.0", "typescript": "7.0.2" } }
// tsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "node20",
    "moduleResolution": "nodenext",
    "strict": false,
    "strictNullChecks": true,
    "noImplicitAny": false,
    "declaration": true,
    "skipLibCheck": true,
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src"]
}
// 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions