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:
-
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>.
-
ExtractExtResult check added to ModelSelectResult's key filtering: Key extends keyof ExtractExtResult<ExtResult, Model & string> ? never : ... — adds a new conditional branch per selected key.
-
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.
Description
After upgrading from
3.4.xto3.5.0+, TypeScript can no longer resolveselectresult types for models with ~30+ fields when the query return type is inferred through tRPC'sinferRouterOutputs. Selected fields resolve asunknowninstead of their correct types.The same queries resolve correctly when used directly (not through
inferRouterOutputs). Everything worked on3.4.2/3.4.6.Versions
@zenstackhq/orm@3.5.0through3.5.3(at least)@zenstackhq/orm@3.4.2,3.4.6@trpc/server@11.9.0typescript@5.9.3andtsgo@7.0.0-devReproduction
A tRPC procedure returning a
selecton a model with ~34 fields (19 scalar, 12 relations, 3 json arrays):The issue is complexity-dependent — smaller models (~13 fields) work fine through the same inference chain:
selectthroughinferRouterOutputs(3.5.x)EvaluationTestAgentunknownAppWhat changed
The 3.5.0 release added the
ExtResultplugin extension system, which threads a new generic parameter through every core result type:3.4.6:
3.5.0:
This increases the type instantiation depth in three ways:
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 viaNonRelationFields<Schema, M>.ExtractExtResultcheck added toModelSelectResult's key filtering:Key extends keyof ExtractExtResult<ExtResult, Model & string> ? never : ...— adds a new conditional branch per selected key.SelectAwareExtResultintersection added toModelResult:& SelectAwareExtResult<ExtResult, Model & string, Args>— adds another layer of conditional evaluation involvingPick/OmitonExtractExtResult.Even though
ExtResultdefaults to{}, TypeScript still evaluates theExtResultBase<Schema>constraint at each level of the recursiveModelResultchain. For models with ~30+ fields, this extra depth — multiplied across the recursion — exceeds TypeScript's internal instantiation limits when additionally wrapped in tRPC'sinferRouterOutputsgenerics.