sveltejs / language-tools

The Svelte Language Server, and official extensions which use it
MIT License
1.17k stars 187 forks source link

`svelte-check` inconsistency with eslint #2367

Open CNSeniorious000 opened 3 weeks ago

CNSeniorious000 commented 3 weeks ago

Describe the bug

I am trying to create a streaming response in an +server.ts endpoint, but svelte-check tells me an AsyncIteratable<Uint8Array> is not a BodyInit, I don't know why.

My eslint didn't error, but svelte-check did, although they share the same tsconfig.json file.

I didn't reproduce the difference between eslint and svelte-check on StackBlitz, but the type error still exists. If this is not related to svelte-check, I will close this sorry. But I just want to ask for help. What's the best practise to stream a response in SvelteKit?

Reproduction

https://stackblitz.com/edit/sveltejs-kit-template-default-kfwjms?file=src%2Froutes%2F%2Bserver.ts

async function* iterStr() {
    for (const i of '123') {
        yield i;
    }
}

async function* iterUint8Array() {
    const encoder = new TextEncoder();

    for await (const i of iterStr()) {
        console.log(i, encoder.encode(i));
        yield encoder.encode(i);
    }
}

export const GET = async () => {
    // return new Response('123');
    return new Response(iterUint8Array());

    // svelte-check: Argument of type 'AsyncIterable<Uint8Array>' is not assignable to parameter of type 'BodyInit | null | undefined'
    // but `BodyInit` in fact is a union of `AsyncIterable<Uint8Array>` and others
};

Expected behaviour

svelte-check should pass

System Info

Which package is the issue about?

svelte-check

Additional Information, eg. Screenshots

No response

jasonlyu123 commented 2 weeks ago

AsyncIteratable<T> is only part of the BodyInit union in undici's implementation instead of the dom standard. It might not be available if you're not using adapter-node. And the type error is because the type is from lib.dom.d.ts. In the latest version of @types/node the BodyInit type from lib.dom.d.ts is also prioritized. So even when both type is loaded the dom one takes priority.

If you're sure the platform you're deploying supports this, you can type assert the Response object to be from undici-types or type assert the async-iterable to unknown and then to BodyInit. Else you can try manually converting it to ReableStream following the example from mdn.

function iteratorToStream(iterator) {
  return new ReadableStream({
    async pull(controller) {
      const { value, done } = await iterator.next();

      if (done) {
        controller.close();
      } else {
        controller.enqueue(value);
      }
    },
  });
}
CNSeniorious000 commented 2 weeks ago

Thank you very much for your answer! I tried the method you mentioned about manually constructing a ReadableStream, as well as using as unknown as BodyInit. And later I found that using // @ts-expect-error seems to be slightly more elegant. About its compatibility in non-node environments, I discovered that using ReadableStream.from works fine when deploying on Vercel, Netlify, and Cloudflare.

If you don't mind, I have another question. (I'm a novice in TypeScript and haven't studied it systematically, so I sincerely apologize if I ask a silly question) When I hover over ReadableStream, TypeScript prompts me with ""ReadableStream" only refers to a type, but is being used as a value here. ts(2693)". Why is that? How can I let it know that it's a class and not just a type? My entire project is a standard Svelte project generated using create svelte@latest about a month or two ago. The tsconfig.json should be extending the tsconfig from .svelte-kit, which includes both DOM and DOM.Iterable. So why is this happening? If this isn't a common issue, I can create a separate StackBlitz. (I encounter this problem in many of my projects, so I'm wondering if it's a widespread issue.)

jasonlyu123 commented 2 weeks ago

TypeScript prompts me with '"ReadableStream" only refers to a type, but is being used as a value here. ts(2693)'

Can't tell for sure without a reproduction but I guess you might have accidentally had import type ReadableStream from somewhere and shadowed the global type.