|
1 | | -export function fn() { |
2 | | - return "Hello, tsdown!"; |
| 1 | +const defaultChunkSize = 1024 * 1024; // 1MB |
| 2 | + |
| 3 | +export async function* parse( |
| 4 | + url: string, |
| 5 | + options: { |
| 6 | + chunkSize?: number; |
| 7 | + isUrl?: boolean; |
| 8 | + } |
| 9 | +) { |
| 10 | + const chunkSize = options.chunkSize ?? defaultChunkSize; |
| 11 | + const isUrl = options.isUrl ?? true; |
| 12 | + // See https://github.com/nodejs/node/issues/60382 |
| 13 | + let objectURLWorkaround = false; |
| 14 | + if (!isUrl) { |
| 15 | + // transform to a URL object |
| 16 | + // See https://github.com/nodejs/node/issues/60382 |
| 17 | + objectURLWorkaround = true; |
| 18 | + url = URL.createObjectURL(new Blob([url + " "], { type: "text/plain" })); |
| 19 | + } |
| 20 | + |
| 21 | + const decoder = new TextDecoder("utf-8"); |
| 22 | + let rangeStart = 0; |
| 23 | + let fileSize = Infinity; |
| 24 | + while (rangeStart < fileSize) { |
| 25 | + const rangeEnd = rangeStart + chunkSize - 1 + (objectURLWorkaround ? 1 : 0); |
| 26 | + console.log(`Fetching bytes ${rangeStart}-${rangeEnd}`); |
| 27 | + const response = await fetch(url, { |
| 28 | + headers: { |
| 29 | + Range: `bytes=${rangeStart}-${rangeEnd}`, |
| 30 | + }, |
| 31 | + }); |
| 32 | + if (response.status === 416) { |
| 33 | + // Requested Range Not Satisfiable |
| 34 | + throw new Error( |
| 35 | + `Requested range not satisfiable: ${rangeStart}-${rangeEnd}` |
| 36 | + ); |
| 37 | + } |
| 38 | + if (response.status === 200) { |
| 39 | + // Server ignored Range header |
| 40 | + throw new Error(`Server did not support range requests.`); |
| 41 | + } |
| 42 | + if (response.status !== 206) { |
| 43 | + throw new Error( |
| 44 | + `Failed to fetch chunk: ${response.status} ${response.statusText}` |
| 45 | + ); |
| 46 | + } |
| 47 | + // Check the content-range header |
| 48 | + const contentRange = response.headers.get("content-range"); |
| 49 | + const contentLength = response.headers.get("content-length"); |
| 50 | + if (!contentRange || !contentLength) { |
| 51 | + throw new Error(`Missing content-range or content-length header.`); |
| 52 | + } |
| 53 | + const last = contentRange.split("/")[1]; |
| 54 | + if (last === undefined) { |
| 55 | + throw new Error(`Invalid content-range header: ${contentRange}`); |
| 56 | + } |
| 57 | + fileSize = parseInt(last); |
| 58 | + |
| 59 | + // Decode exactly chunkSize bytes or less if it's the last chunk |
| 60 | + const bytes = await response.bytes(); |
| 61 | + console.log(`Received ${bytes.length} bytes`); |
| 62 | + console.log(`headers: ${JSON.stringify([...response.headers])}`); |
| 63 | + const bytesToDecode = Math.min(chunkSize, fileSize - rangeStart); |
| 64 | + const chunk = decoder.decode(bytes.subarray(0, bytesToDecode)); |
| 65 | + console.log(`Decoded ${chunk.length} characters: ${chunk}`); |
| 66 | + yield chunk; |
| 67 | + |
| 68 | + rangeStart += chunkSize; |
| 69 | + } |
3 | 70 | } |
0 commit comments