MattiasBuelens / web-streams-adapter

Adapters for converting between different implementations of WHATWG Streams
MIT License
32 stars 1 forks source link

Missing properties on *StreamLike types in TypeScript #23

Open dumbmatter opened 2 years ago

dumbmatter commented 2 years ago

First of all, much love for your work here and on the polyfill!

When using this library with TypeScript, if I do something like:

toPolyfillReadable(stream).pipeThrough

it says:

Property 'pipeThrough' does not exist on type 'ReadableStreamLike<any>'

Maybe the properties from https://github.com/microsoft/TypeScript/blob/fd6552a3c2fdfe92afd88ccf6616ec02946e7d9e/lib/lib.dom.d.ts#L11917-L11925 should be copied to https://github.com/MattiasBuelens/web-streams-adapter/blob/bff7004475a487b860f17cd0ba1ca935929470a0/src/stream-like.ts#L20-L24 ?

MattiasBuelens commented 2 years ago

The ReadableStreamLike type is intended to be the minimum required interface that a stream implementation must support in order to adapt it to a different implementation. So it should really be an input type, but it looks like it also acts as an output type. Hmm... 🤔

Thanks for reporting. I'll see if I can improve the types. 🙂

MattiasBuelens commented 2 years ago

Hmm, this might be impossible. 😕

createReadableStreamWrapper needs to take a constructor that returns a generic ReadableStream<R>, and returns a ReadableStreamWrapper which should also return a generic ReadableStream<R>. We don't want to restrict the type of R, because we want this to work:

const toPolyfillReadable = createReadableStreamWrapper(PolyfillReadableStream);
const readable1: PolyfillReadableStream<string> = toPolyfillReadable(new ReadableStream<string>());
const readable2: PolyfillReadableStream<Uint8Array> = toPolyfillReadable(new ReadableStream<Uint8Array>());

To correctly express this, we'd need some sort of "higher kinded types", which doesn't exist yet in TypeScript:

// pseudo code, not real TypeScript
export interface ReadableStreamLikeConstructor<RS extends ReadableStreamLike<?>> {
                                                                         // ~~~ not possible
  new<R = any>(
    underlyingSource?: UnderlyingSource<R>,
    strategy?: QueuingStrategy<R>
  ): RS<R>;
  // ~~~~~  not possible
}

export type ReadableStreamWrapper<RS extends ReadableStreamLike<?>> 
  = <R>(readable: ReadableStreamLike<R>) => RS<R>;

export function createReadableStreamWrapper<RS extends ReadableStreamLike<?>>(
  ctor: ReadableStreamLikeConstructor<RS>
): ReadableStreamWrapper<RS> { /* ... */ }

For the time being, you'll need to manually cast the return value to the desired type yourself:

const toPolyfillReadable = createReadableStreamWrapper(PolyfillReadableStream);
const readable1 = toPolyfillReadable(new ReadableStream<string>()) as PolyfillReadableStream<string>;
const readable2 = toPolyfillReadable(new ReadableStream<Uint8Array>()) as PolyfillReadableStream<Uint8Array>;

I wish I had a better answer. 😞

I am trying to make web-streams-polyfill itself work better together with native streams, which should ideally remove the need for web-streams-adapter entirely. If you're interested, you can follow along in MattiasBuelens/web-streams-polyfill#20.

dumbmatter commented 2 years ago

Sorry to abuse the issue tracker this way, but do you have a Paypal or BuyMeACoffee or something where I can thank you for your work? It's just really great stuff.

MattiasBuelens commented 2 years ago

Thanks for the kind words! 😊

I'm fortunate enough to have a great day job, so I'd feel bad to accept donations. But if you want, you can sponsor other projects and developers, or donate to a charity.