You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
"Type instantiation is excessively deep and possibly infinite", unidentified DO types, attempting to get a clean type from DO RPC Call #3063
In a worker, I commonly use a pattern of calling durable objects from an API worker - when the DOs respond, they either contain a response type (e.g. a serializable object) or a Response type. I use a utility method to split these types similar to a safe assignment operator. Ideally, my code looks like this:
conststub=ctx.DURABLE_OBJECT.get(ctx.DURABLE_OBJECT.idFromName("some-name"));const[response,fooInfo]=awaittryResponse(stub.method("foo"));if(response){returnresponse;}// fooInfo is guaranteed and typed
Note that the DO in the above example would be something like:
ctx.STUB does not appear to return typed stubs when I use wrangler types in a project with non-local DOs, even if I provide a path to the DO definition directly via script_name. This feels like a pretty obvious shortcoming, or is the expectation that you'd only use RPC via a single worker that exposes its own DOs? I've worked around this by using a shim for the type, not the end of the world.
tryResponse is typed similarly to this playground, and has a return type of (roughly) Awaited<[Response, undefined] | [undefined, T]> where T is Exclude<ReturnType<StubMethod>, Response>.
This should in theory allow me to pass a stub method result to it - and indeed, if I pass something typed as ReturnType<MyDOClass["method"]> to it, the types are correct. However, when I call with tryResponse(stub.method("foo")), I get a Type instantiation is excessively deep and possibly infinite error. I can workaround this as well by using this shim:
However, this results in the following code, which is bailing out of a lot of the type system:
constrepoStub=getStub<MyDOClass>(DURABLE_OBJECT,DURABLE_OBJECT.idFromName("some-name"));const[response,fooInfo]=awaittryResponse(stub.method("foo")asStubCall<MyDOClass,"method">);if(response){returnresponse;}// fooInfo is guaranteed and typed
There is some help here, but it's quite repetitive and ideally the first block of code has enough info to generate the correct types without TS failing due to complexity.
I'll try consolidating to a single project to fix issue #1 (is that really the recommended approach?), but I suspect that I'll still have issue #2 regardless, due to the complex type inference. What are my options here?
Thanks for opening this @jesseditson, and for the detailed information. I've added it to our backlog to take a deeper look at 2, to see if we can simplify the RPC TS types to reduce the complexity. In terms of 1, while we can definitely improve the situation (we're thinking of adding a location property to DO bindings pointing to the path on the filesystem so that among other things we can properly generate types), I think a relatively simple stopgap for now would be adding something along these lines to your worker and using that as your Env type:
Thank you! I'll keep an eye on this issue. I'm not blocked, but it would be very slick for the types to "just work".
WRT the env, that makes sense. I'm assuming this would go in an ambient d.ts file and that the Env here is one from worker-configuration.d.ts - I'm using npx wrangler types to generate these.
All together that would make my setup:
worker-configuration.d.ts is generated via npx wrangler types --env-interface WranglerEnv
env.d.ts extends and augments via interface WorkerEnv extends Omit<WranglerEnv, "DURABLE_OBJECT">
This works but will get a little confusing since various Envs will be ambiently declared. Admittedly I already am adding some complexity here since I augment all env in my stack with observability tools, so the type in my case will be ExtendedEnv<WorkerEnv>. Not something to address as much as just some case study info.
I think I'll still end up just merging the projects since that's what I do in test and it seems to work well. This would then let wrangler fill in the env via DURABLE_OBJECT: DurableObjectNamespace<import("./index").DoClass> which it knows how to do with local DOs.
@jesseditson did you ever end up improving this? I have a lot of promises showing up like never. And they're not even complex types, sometimes it's just a simple object.
Same issue with service bindings in a monorepo. Rpc.Provider<T> collapses methods to any for any non-trivial return type — the Rpc.Serializable<T> recursion hits TypeScript's depth limit.
Simple returns like Promise<number> work. Complex ones like Promise<{ status: number; data: T | null; error: boolean }> silently become any.
We work around it by replacing Service<T> → InstanceType<T> in the generated worker-configuration.d.ts via sed post-processing.
Activity
penalosa commentedon Nov 18, 2024
Thanks for opening this @jesseditson, and for the detailed information. I've added it to our backlog to take a deeper look at 2, to see if we can simplify the RPC TS types to reduce the complexity. In terms of 1, while we can definitely improve the situation (we're thinking of adding a
locationproperty to DO bindings pointing to the path on the filesystem so that among other things we can properly generate types), I think a relatively simple stopgap for now would be adding something along these lines to your worker and using that as yourEnvtype:jesseditson commentedon Nov 18, 2024
Thank you! I'll keep an eye on this issue. I'm not blocked, but it would be very slick for the types to "just work".
WRT the env, that makes sense. I'm assuming this would go in an ambient d.ts file and that the
Envhere is one fromworker-configuration.d.ts- I'm usingnpx wrangler typesto generate these.All together that would make my setup:
worker-configuration.d.tsis generated vianpx wrangler types --env-interface WranglerEnvenv.d.tsextends and augments viainterface WorkerEnv extends Omit<WranglerEnv, "DURABLE_OBJECT">This works but will get a little confusing since various
Envs will be ambiently declared. Admittedly I already am adding some complexity here since I augment allenvin my stack with observability tools, so the type in my case will beExtendedEnv<WorkerEnv>. Not something to address as much as just some case study info.I think I'll still end up just merging the projects since that's what I do in test and it seems to work well. This would then let wrangler fill in the env via
DURABLE_OBJECT: DurableObjectNamespace<import("./index").DoClass>which it knows how to do with local DOs.multiplehats commentedon Jan 16, 2025
@jesseditson did you ever end up improving this? I have a lot of promises showing up like
never. And they're not even complex types, sometimes it's just a simple object.watford-ep commentedon Aug 12, 2025
I'm blocked using
wrangler typesand anything beyond the most simple of definitions.filipfalcon commentedon Apr 15, 2026
Same issue with service bindings in a monorepo.
Rpc.Provider<T>collapses methods toanyfor any non-trivial return type — theRpc.Serializable<T>recursion hits TypeScript's depth limit.Simple returns like
Promise<number>work. Complex ones likePromise<{ status: number; data: T | null; error: boolean }>silently becomeany.We work around it by replacing
Service<T>→InstanceType<T>in the generatedworker-configuration.d.tsvia sed post-processing.