Environment
- nitropack
2.13.4 (v2) — also reproduced on main (v3), same src/types/fetch/fetch.ts shape
- TypeScript 5.9.x, and
@typescript/native-preview (tsgo)
- Real app: a Nuxt 4 project whose generated
InternalApi has ~846 routes
Reproduction
Any project with a large generated InternalApi (hundreds of routes) that calls typed $fetch / useFetch / useAsyncData / event.$fetch with a string literal. On the real app it produces TS2589 (Type instantiation is excessively deep and possibly infinite) and, under tsgo, a flood of TS2321 (Excessive stack depth comparing types) on those call sites. A minimal repro is a Nitro app with a few hundred route handlers + one $fetch('/some/route').
Describe the bug
NitroFetchRequest is the union of all route keys and is used as the generic constraint of $Fetch (R extends NitroFetchRequest). At every typed call site the URL literal is re-scored against that union via MatchedRoutes / AvailableRouterMethod / TypedInternalResponse. With hundreds of routes this instantiates enormously — TypeScript's tsc sits just under its 5M instantiation valve, and Go-native tsgo (stricter, different stable type ordering) tips over it → TS2589/TS2321. It is not an app bug: the generated code is correct; the cost is intrinsic to scoring the union per call.
This is the same family as the TypeScript compiler issues below, where the maintainers consider the behavior intended (stable type ordering) and close the issues as NOT_PLANNED, pointing to the libraries to make their types less pathological (e.g. TypeBox fixed its own types in #929):
So Nitro is the right place to offer a mitigation.
Proposed fix (PR attached)
An opt-in, non-breaking escape hatch: a NitroFetchConfig interface that, when a project sets { flatRequest: true }, resolves NitroFetchRequest to string and stops the per-call-site route-union scoring.
declare module "nitropack" {
interface NitroFetchConfig { flatRequest: true }
}
Default behavior is unchanged (the union) — existing users keep full autocomplete + typing. When opted in, response typing is preserved (MatchedRoutes<R> still resolves the return from the URL literal); only request-key autocomplete is traded away. Measured on the real app: it removes the entire TS2589/TS2321 class on both tsc and tsgo with zero new errors.
PR: opt-in flat NitroFetchRequest (targets main).
Environment
2.13.4(v2) — also reproduced onmain(v3), samesrc/types/fetch/fetch.tsshape@typescript/native-preview(tsgo)InternalApihas ~846 routesReproduction
Any project with a large generated
InternalApi(hundreds of routes) that calls typed$fetch/useFetch/useAsyncData/event.$fetchwith a string literal. On the real app it produces TS2589 (Type instantiation is excessively deep and possibly infinite) and, under tsgo, a flood of TS2321 (Excessive stack depth comparing types) on those call sites. A minimal repro is a Nitro app with a few hundred route handlers + one$fetch('/some/route').Describe the bug
NitroFetchRequestis the union of all route keys and is used as the generic constraint of$Fetch(R extends NitroFetchRequest). At every typed call site the URL literal is re-scored against that union viaMatchedRoutes/AvailableRouterMethod/TypedInternalResponse. With hundreds of routes this instantiates enormously — TypeScript'stscsits just under its 5M instantiation valve, and Go-nativetsgo(stricter, different stable type ordering) tips over it → TS2589/TS2321. It is not an app bug: the generated code is correct; the cost is intrinsic to scoring the union per call.This is the same family as the TypeScript compiler issues below, where the maintainers consider the behavior intended (stable type ordering) and close the issues as
NOT_PLANNED, pointing to the libraries to make their types less pathological (e.g. TypeBox fixed its own types in #929):NOT_PLANNED— "it's the stable type ordering")UnionToTupletype returns "inverted" tuple in TSGO compared to TypeScript 5.8.3 and gives type error microsoft/typescript-go#787 (stable type ordering is a deliberate design decision)So Nitro is the right place to offer a mitigation.
Proposed fix (PR attached)
An opt-in, non-breaking escape hatch: a
NitroFetchConfiginterface that, when a project sets{ flatRequest: true }, resolvesNitroFetchRequesttostringand stops the per-call-site route-union scoring.Default behavior is unchanged (the union) — existing users keep full autocomplete + typing. When opted in, response typing is preserved (
MatchedRoutes<R>still resolves the return from the URL literal); only request-key autocomplete is traded away. Measured on the real app: it removes the entire TS2589/TS2321 class on bothtscandtsgowith zero new errors.PR: opt-in flat
NitroFetchRequest(targetsmain).