cloudflare / cloudflare/workerd
"Type instantiation is excessively deep and possibly infinite", unidentified DO types, attempting to get a clean type from DO RPC Call
- Dominant language
- C++
- Stars
- 8.7k
- Forks
- 739
- Avg merge
- 2d 20h
- Merged PRs (30d)
- 174
Description
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:
```typescript
const stub = ctx.DURABLE_OBJECT.get(ctx.DURABLE_OBJECT.idFromName("some-name"));
const [response, fooInfo] = await tryResponse(stub.method("foo"));
if (response) {
return response;
}
// fooInfo is guaranteed and typed
```
Note that the DO in the above example would be something like:
```typescript
type FooInfo = { something: string }
class MyDOClass extends DurableObject {
async method(something: string): Response | FooInfo {
const r = await fetch("...");
if (!r.ok) {
return r;
}
return { something };
}
}
```
This has a number of issues. In order:
1. `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.
```typescript
export const getStub = (
ns: DurableObjectNamespace,
id: DurableObjectId
): DurableObjectStub => {
return ns.get(id) as DurableObjectStub;
};
```
2. tryResponse is typed similarly to [this playground](https://www.typescriptlang.org/play/?jsx=0#code/FAFwngDgpgBASlAzhA9gO0VA8gJwGoCGANgK5QAKRBAlmgDwAqAfDALwwDaCy6mANDBJoAJlABmtKMIC6MAD6choiWikCAogA8AxqVGMB3VBihNpAbmBRNqHCBjho8JMcy5CpKHTgt2cGNYgUCKIMOQ4KAC21JgAMtQA1l60YlA4MACqLAD8wDBhEdFxiV5GvNj4xGSUNPRZLABczjwm7lUUVLTeTJbA2rz2MeFRMVDxSWwwjEwAFABuTQzyBSPFSdMAlE1zMDErRWMl02xMeQ6QUChiMPMwBKHDB+NezBsAdCAAFsFsrOwARGIhNoQNR0P9eo5YAwcGAyiYAJJoCAkEDHPwucrLJYKR6jZ6MZbwzA9UAXGAwuGYkwMC7dSb+QLBYShSnEqBIlFolJpTI5GBaXQkfQZQzUkkwJqqOZpXrWWz2foYewgWHsyaEpkheCGSZs8VQWnQRhqg2c1HTJgCPB600tTDmtHMWZnBYwPDALbNVwVDxkOh4XwsADeZ2o1xmQ0K+JK8w2Gxgofy+RwUBAJBwaBgcw+3zQMxmOATrBYMXZhYT2U4OAESnEkhkksUInrqmEAhw0jO+QT9295TangDpPyAF8AkRMInuzBU+nMzcywa4zAqxw5rWWyopLImhw69v29npL3QuzB-7A2dR8BR3KbCg7DAlYhBoh1expjMQIsiQavW+-YmCcZyDMqBBoNolzXOyvRkk4ADKUSGuS7DBmIKAoE0r44LQADmd59AMMDCCgCAAI5kK+kz3GAkE3F6eKYN4BrLEhkQodAQbTimaYZlmACyBBfG8OAQaRkQzAmLAAAxvAArKuMCqAA7kBmAzICNBEFI-wJk06GYU0-wAJrqAhACE-yETeAD0tnPsRpEUVRIAIXR2iTFJTTqgo7GcbAJbTvZyZzvxMBCSJYkiFEUkwLJClKap6lQJpYjabp+mJhhWEwKZ5lWYRIV2Q5L72BAkzOVAlFIG5HlSb0ZUwBV7BVTVr4NURyrVgaAhzO0sjsAQKk0Cqdo+jMEAbL0On2IgyFQk0-lGlAljhjcqb2lACZJjAIVoCgXz4beE5Trt80cVCkz9Z4lijkAA), and has a return type of (roughly) `Awaited<[Response, undefined] | [undefined, T]>` where `T` is `Exclude, Response>`.
This should in theory allow me to pass a stub method result to it - and indeed, if I pass something typed as `ReturnType` 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:
```typescript
type Fn = (...a: any) => any;
export type StubCall<
DOClass extends DurableObject,
K extends keyof DOClass
> = DOClass[K] extends Fn ? ReturnType : never;
```
However, this results in the following code, which is bailing out of a lot of the type system:
```typescript
const repoStub = getStub(DURABLE_OBJECT, DURABLE_OBJECT.idFromName("some-name"));
const [response, fooInfo] = await tryResponse(stub.method("foo") as StubCall);
if (response) {
return response;
}
// 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?
Contributor guide
Assessment
This issue has not been assessed yet.