Skip to content

Commit b27d1eb

Browse files
committed
initial code
1 parent 489c5c0 commit b27d1eb

2 files changed

Lines changed: 80 additions & 6 deletions

File tree

src/index.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,70 @@
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+
}
370
}

tests/index.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
import { expect, test } from "vitest";
2-
import { fn } from "../src";
1+
import { describe, expect, test } from "vitest";
2+
import { parse } from "../src";
33

4-
test("fn", () => {
5-
expect(fn()).toBe("Hello, tsdown!");
4+
describe("parse", () => {
5+
test("parse", async () => {
6+
const text = "hello, csvremote!!!";
7+
let result = "";
8+
for await (const chunk of parse(text, { chunkSize: 5, isUrl: false })) {
9+
result += chunk;
10+
}
11+
expect(result).toBe(text);
12+
});
613
});

0 commit comments

Comments
 (0)