Skip to content

Regression in 3.5.0: ExtResult generic parameter causes select type inference failure for complex models through tRPC inferRouterOutputs #2569

@olup

Description

@olup

Description

After upgrading from 3.4.x to 3.5.0+, TypeScript can no longer resolve select result types for models with ~30+ fields when the query return type is inferred through tRPC's inferRouterOutputs. Selected fields resolve as unknown instead of their correct types.

The same queries resolve correctly when used directly (not through inferRouterOutputs). Everything worked on 3.4.2 / 3.4.6.

Versions

  • Broken: @zenstackhq/orm@3.5.0 through 3.5.3 (at least)
  • Working: @zenstackhq/orm@3.4.2, 3.4.6
  • @trpc/server@11.9.0
  • Reproduces on both typescript@5.9.3 and tsgo@7.0.0-dev

Reproduction

A tRPC procedure returning a select on a model with ~34 fields (19 scalar, 12 relations, 3 json arrays):

// Server — custom tRPC procedure
getRunResults: protectedProcedure
  .input(z.object({ runId: z.string() }))
  .query(async ({ input, ctx }) => {
    return ctx.prisma.evaluationTestResult.findMany({
      where: { runId: input.runId },
      include: {
        test: true,
        agent: { select: { id: true, name: true, slug: true, avatarUrl: true } },
      },
    });
  }),
// Direct ORM usage — works fine, even on 3.5.3
const agents = await db.agent.findMany({ select: { id: true, name: true } });
const id: string = agents[0].id;    // ✅ OK

// Through inferRouterOutputs — fields become unknown on 3.5.0+
type Outputs = inferRouterOutputs<AppRouter>;
type AgentId = Outputs["admin"]["evaluation"]["getRunResults"][0]["agent"]["id"];
const check: string = {} as AgentId;
//    ^^^^^ Error: Type 'unknown' is not assignable to type 'string'

The issue is complexity-dependent — smaller models (~13 fields) work fine through the same inference chain:

Model Field count select through inferRouterOutputs (3.5.x)
EvaluationTest ~13 ✅ resolves correctly
Agent ~34 ❌ fields resolve as unknown
App ~38 ❌ (same pattern)

What changed

The 3.5.0 release added the ExtResult plugin extension system, which threads a new generic parameter through every core result type:

3.4.6:

type ModelSelectResult<Schema, Model, Select, Omit, Options>
type ModelResult<Schema, Model, Args, Options, Optional, Array>

3.5.0:

type ModelSelectResult<Schema, Model, Select, Omit, Options, ExtResult extends ExtResultBase<Schema> = {}>
type ModelResult<Schema, Model, Args, Options, Optional, Array, ExtResult extends ExtResultBase<Schema> = {}>

This increases the type instantiation depth in three ways:

  1. ExtResultBase<Schema> constraint is evaluated at every level. It iterates all models via [M in GetModels<Schema> as Uncapitalize<M>] and all non-relation fields via NonRelationFields<Schema, M>.

  2. ExtractExtResult check added to ModelSelectResult's key filtering: Key extends keyof ExtractExtResult<ExtResult, Model & string> ? never : ... — adds a new conditional branch per selected key.

  3. SelectAwareExtResult intersection added to ModelResult: & SelectAwareExtResult<ExtResult, Model & string, Args> — adds another layer of conditional evaluation involving Pick/Omit on ExtractExtResult.

Even though ExtResult defaults to {}, TypeScript still evaluates the ExtResultBase<Schema> constraint at each level of the recursive ModelResult chain. For models with ~30+ fields, this extra depth — multiplied across the recursion — exceeds TypeScript's internal instantiation limits when additionally wrapped in tRPC's inferRouterOutputs generics.

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