I'm trying to infer types from Readable/Writable streams in typescript. So, I did it, but maybe there is a better way for this task, and maybe you can help me with this.
Also I can't figure out, why RsReturnValueType
generic performed the wrong typification, any ideas?
Here's the simplified code:
interface Duplex<WriteType extends any = any, ReadType extends any = any> {
getWritableStream() : WritableStream<WriteType>;
getReadableStream() : ReadableStream<ReadType>;
}
class DuplexRealizationExample implements Duplex<string, string> {
getWritableStream(): WritableStream<string> {
return new WritableStream<string>();
}
getReadableStream(): ReadableStream<string> {
throw new ReadableStream<string>();
}
}
interface AbstractStreamsAdapter<T extends Duplex> {
toUnderlyingWritableStream(data : number) : getWsArgumentType<T>;
fromUnderlyingReadableStream(data : RsReturnValueType<T>["value"]) : number;
}
type RsStream<T extends Duplex> = ReturnType<T["getReadableStream"]>;
type RsReaderType<T extends ReadableStream> = ReturnType<T["getReader"]>;
// todo: is there a way to not use `unknown`?
type RsReadReturnType<T extends ReadableStreamReader<unknown>> = Awaited<ReturnType<T["read"]>>;
type RsReturnValueType<T extends Duplex> = (RsReadReturnType<RsReaderType<RsStream<T>>> & {done : false})["value"];
type WsStream<T extends Duplex> = ReturnType<T["getWritableStream"]>;
type WsReaderType<T extends WritableStream> = ReturnType<T["getWriter"]>;
type getWsArgumentType<T extends Duplex> = Parameters<WsReaderType<WsStream<T>>["write"]>[0];
class StreamAdapterExample implements AbstractStreamsAdapter<DuplexRealizationExample> {
toUnderlyingWritableStream(data: number): getWsArgumentType<DuplexRealizationExample> {
return data.toString();
}
fromUnderlyingReadableStream(data: RsReturnValueType<DuplexRealizationExample>): number {
// todo: why `data - string | ArrayBuffer`? It should be just a `string`.
return parseInt(data);
}
}
Thank you for your help^
From reading the code I presume that the intent of your chain of utility types is to extract W
and R
from a value of type Duplex<W, R>
. You're drilling down into the structure of those stream types, using indexed accesses and conditional type inference with infer
(in the form of ReturnType<T>
and Parameters<T>
), and trying to tease out the types you care about. But you're not doing it right; there are some interesting things in there like a union with ReadableStreamBYOBReader
, a type for which you "Bring Your Own Buffer", meaning it expects there to be an ArrayBuffer
, and that gets in the way. You could likely be more careful and filter that out, with more utility types like Extract<T, U>
or Exclude<T, U>
, but it's just so complicated. And if you're using infer
anyway, you might as well do that right at the top and see if it works:
type DuplexReadType<T extends Duplex> = T extends Duplex<any, infer R> ? R : never
type DuplexWriteType<T extends Duplex> = T extends Duplex<infer W, any> ? W : never;
interface AbstractStreamsAdapter<T extends Duplex> {
toUnderlyingWritableStream(data: number): DuplexWriteType<T>;
fromUnderlyingReadableStream(data: DuplexReadType<T>): number;
}
This just asks TypeScript directly what the read and write types are for a given subtype of Duplex
. Let's see how it behaves:
type DRERead = DuplexReadType<DuplexRealizationExample>;
// type DRERead = string
type DREWrite = DuplexWriteType<DuplexRealizationExample>;
// type DREWrite = string
Looks good.