ampp
ampp8mo ago

Type instantiation is excessively deep and possibly infinite.

The last day or two ive had to disable typescript checking or tsignore as i have been getting this within the convex folder for the first time. Needing typecheck=disable to get around it. I had complained about it appearing in the app directory before as a annoyance. I recently added a lot of actions and some scheduled actions or mutations. I managed to get the same errors in the fullstack template by moving folders into the similar structure. I was seeing this was not suggested.
convex
├── projects
│ └── activities.ts
└── projects.ts
convex
├── projects
│ └── activities.ts
└── projects.ts
https://discord.com/channels/1019350475847499849/1187401985301950574 But that's old and i followed the design of the convex ents example. Im doing things 2 deep.
convex
├── projects
│ └──- tasks
│ └ tasks.ts
└── projects.ts
convex
├── projects
│ └──- tasks
│ └ tasks.ts
└── projects.ts
When I was playing around with the fullstack-convex template in its own environment it was triggered when i moved tasks.ts and /tasks/ 1 level deeper Its possible the saas-starter template does not have enough api or internal calls to triggers this. It seems to require there to be more then 1 on a page or multiple files with more than one call. I also just noticed that --typecheck=disable is set on saas-starter so maybe that would avoid ever getting the error combined with the lack of api or internal calls. Tonight i really don't really like typecheck=disable as i made schema changes to indexs and somehow got no warnings of issues till my business partner ran the same code without that flag and several functions were broken. This fairly reproducible i'm curious if i should just change my file structure or try to spend time creating another fork example of it in a broken state if we want to go down the "this needs to get fixed" path. I'm also curious if anyone else is naming their stuff like this in a big project. Thanks
26 Replies
erquhart
erquhart8mo ago
I would avoid using the same name for a directory and file within the same directory. Which Convex ents example did you see this in?
RJ
RJ8mo ago
I’ve always been able to resolve this error by explicitly annotating the return type of my Convex function handlers. I’m not aware of this being related to directory structure (although it may well be), and in any case I often have directories and files with the same name, as you’re describing here.
ampp
amppOP8mo ago
GitHub
saas-starter/convex at main · xixixao/saas-starter
Convex, Clerk, Next.js, Convex Ents. Contribute to xixixao/saas-starter development by creating an account on GitHub.
erquhart
erquhart8mo ago
RJ's solution makes sense, explicitly typing function return values does take care of circular inference issues (which is what this is)
ampp
amppOP8mo ago
are you spending effort annotating both functions like export function querySphereMember() and export const function = query/mutation(): (used fo api and internal)
erquhart
erquhart8mo ago
cc/ @Michal Srb in case he knows something about the directory structure implications
ampp
amppOP8mo ago
Yeah we are at like 150 functions and im guessing id have to make types for some of them. unless i lazy annotate with any?
erquhart
erquhart8mo ago
You'll regret annotating as any lol, but yeah that's an option I would take an alternate file/directory naming scheme over having to explicitly type function return values, personally. Also, you linked to a conversation about the file/directory naming issue that was old, but I think it's still valid.
RJ
RJ8mo ago
I've only ever needed to annotate the handlers, specifically. I.e.
export const getArea = query({
args: { width: v.number(), height: v.number() },
handler: (_ctx, { width, height}): Promise<number> => Promise.resolve(width * height)
})
export const getArea = query({
args: { width: v.number(), height: v.number() },
handler: (_ctx, { width, height}): Promise<number> => Promise.resolve(width * height)
})
My usual experience looks like this: - Happily code - Run into this type inference failure (Type instantiation is excessively deep and possibly infinite.) - Furrow brow - Annotate the Convex function handler(s) that I just wrote/modified - Return to happily coding I don't think I've annotated all of them, I just do it when I encounter this issue. Personally, I don't mind this at all because I think explicit type annotations are great. And ensuring that type inference is working properly for these functions is a big part of what makes using Convex queries/mutations/actions so convenient (the type hints/autocompletion). See https://discord.com/channels/1019350475847499849/1237101416519041158 also
Michal Srb
Michal Srb8mo ago
There are potentially different issues at play here. Usually, the "Type instantiation is excessively deep and possibly infinite." error is addressed as documented here: https://docs.convex.dev/functions/actions#dealing-with-circular-type-inference But I know that @ampp ran into a different problem, reported in another thread, and we haven't been able to look into that one yet. That was a different type error: https://discord.com/channels/1019350475847499849/1230233206893314111/1230233206893314111
Actions | Convex Developer Hub
Actions can call third party services to do things such as processing a payment
ampp
amppOP8mo ago
Yeah, im going to try to check through everything to hopefully find when it stops happening, but getting it to happen in the fullstack-convex template without editing function design was making me think its primarily triggered once you get into using sub-sub folders regardless.
Michal Srb
Michal Srb8mo ago
It is possible that there is a different cause at play, but I haven't found a consistent repro for it. If you do please share it. (what does sometime trigger the error is that when you have an issue in your code that makes it impossible for TypeScript to infer the types correctly - moving a file without fixing the code that usess its exports could create such a situation)
RJ
RJ8mo ago
Oh interesting, the advice in the docs seems good as well! Annotating the function handlers still feels better to me personally (annotating once at the source rather than multiple times at each call site), but I guess the point is that you just need to annotate some link in the inference cycle to break it
ampp
amppOP8mo ago
We're refactoring because it just seems like a better overall design and clearer design at scale to not have convex files named the same as folders within /convex/. I do notice how we have two Type instantiation is excessively deep and possibly infinite issues, one that causes convex bundler issues, and one that doesn't. As soon as i renamed and moved the convex files, my hooks.ts file(where i have the majority of api calls) started showing errors as i had some stuff named improperly so the Type instantiation error on that page was causing other errors to not show. dispite being all usable functions. Yep, what I have observed since playing with this more is there are two errors that show up in different spots. I figured id provide details. Yes, moving the files in convex with the same names as folders got rid of the "excessively deep error" and earlier i had no functions that had the "implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.". However i was writing more ctx.runQuery actions in actions so i obviously did that:
const user: Ent<"users"> = await ctx.runQuery(api.users.getUser, { userId: userId });
"implicitly has type 'any' " errors always showed up in in the use of user
const user: Ent<"users"> = await ctx.runQuery(api.users.getUser, { userId: userId });
"implicitly has type 'any' " errors always showed up in in the use of user
and "excessively deep" always highlighted at: the api or internal word and only the first one within a function: ctx.runMutation(api.users.createUser, { }) I was thinking of the runMutation and runQuery as effectively the same as useMutation and useQuery but from what i can tell the run variant never knows the return type of what is getting ran so it should always be annotated. Maybe that will help others. I certainly didn't touch those till i needed to with actions.
ampp
amppOP8mo ago
ctx.runMutation(internal.folder1.folder2.files.save,
No description
Michal Srb
Michal Srb8mo ago
runMutation can infer the return type of the mutation, the problem is then using that value as the return type of the action. This creates a cycle:
No description
erquhart
erquhart8mo ago
Note that this applies if even the smallest part of a ctx.run* return value is included in the action return value, even indirectly, which is often the case and makes this harder to hunt down.
ampp
amppOP8mo ago
I get that side of things, but i was getting the excessively deep issue on functions like await ctx.scheduler.runAfter(0, internal.notifications.createNotification, without any return value. and without requesting the value from the calling function. Its hard to annotate something you dont want the output of.
magicseth
magicseth6mo ago
Is there any chance that there will be a workaround for this limitation?
jamwt
jamwt6mo ago
which particular one? it seems several things were discussed in this thread @magicseth ?
magicseth
magicseth6mo ago
We are returning the value from runAction from an action which breaks the type system Our current strategy is to pull the handler from the inner action out to its own function and pass it as a handler to one function and then call it directly from the other action. Feels slightly un ergonomic
ampp
amppOP6mo ago
I haven't had the same degree of issue once i renamed any convex files differently than the folders. But typescript randomly has a seizure often but that seems unrelated. it also seemed like in my hooks file that i was breaking rules by not using the "use" prefix, that also helped with similar issues on front end.
Michal Srb
Michal Srb6mo ago
@magicseth sounds like a combination of issues: 1. The annotation to break the inference cycle is required. That's just the nature of how TypeScript does typechecking (and how we provide a single api object that has all the return types). I think we'd have to use codegen and split the api and internal to actually be separate, and disallow calling functions via api, to avoid this issue completely. 2. You need to expose some logic publicly (or to the scheduler) and call it directly. This might look like some ceremony, but it's not too bad in my experience:
export const foo = action({
args: {....}
handler: async (ctx, args) => {
return await helper(ctx, args);
}
});

async function helper (ctx: ActionCtx, args: SomeType) {
....
}
export const foo = action({
args: {....}
handler: async (ctx, args) => {
return await helper(ctx, args);
}
});

async function helper (ctx: ActionCtx, args: SomeType) {
....
}
You can remove some duplication in the typing of arguments via Infer, but not doing it gives you the flexibility to evolve this code easily over time (add a check only to the public functions, add more capability only to the helper, etc.)
magicseth
magicseth6mo ago
Thanks. Number two is where we landed, but we pass helper directly as the handler instead of awaiting it. Then instead of using runAction from a different action, we just call handler directly. Are there any major reasons to use runAction? We miss out on the validators?
Michal Srb
Michal Srb6mo ago
The only good reason to use ctx.runAction is when you're switching between runtimes: https://docs.convex.dev/functions/runtimes
Runtimes | Convex Developer Hub
Convex functions can run in two runtimes:
magicseth
magicseth6mo ago
Got it. Thanks!