streaming 7-Zip archive extractor
1/**
2 * @module
3 *
4 * {@link Reader} factories that wrap Node.js runtime APIs.
5 */
6
7import type { FileHandle } from 'node:fs/promises';
8
9import type { Reader } from './types.ts';
10
11/**
12 * creates a reader from a Node.js' FileHandle
13 * @param handle the file handle to read from
14 * @returns a reader for the file
15 */
16export const fromFileHandle = async (handle: FileHandle): Promise<Reader> => {
17 const totalLength = (await handle.stat()).size;
18
19 return {
20 length: totalLength,
21 // deno-lint-ignore require-await
22 async read(offset: number, length?: number): Promise<ReadableStream<Uint8Array<ArrayBuffer>>> {
23 let remaining = length !== undefined ? length : totalLength - offset;
24
25 return new ReadableStream<Uint8Array<ArrayBuffer>>({
26 type: 'bytes',
27 async pull(rawController) {
28 const controller = rawController as ReadableByteStreamController;
29
30 const byob = controller.byobRequest;
31 // loose `!= null`: Bun yields `undefined` for an absent
32 // byobRequest, not the spec's `null`.
33 if (byob != null) {
34 const view = byob.view as Uint8Array<ArrayBuffer>;
35 const cap = view.byteLength <= remaining ? view.byteLength : remaining;
36 const target = cap === view.byteLength ? view : new Uint8Array(view.buffer, view.byteOffset, cap);
37 const { bytesRead } = await handle.read(target, 0, cap, offset);
38
39 if (bytesRead === 0) {
40 byob.respond(0);
41 controller.close();
42 return;
43 }
44
45 remaining -= bytesRead;
46 offset += bytesRead;
47 byob.respond(bytesRead);
48
49 if (remaining <= 0) {
50 controller.close();
51 }
52 return;
53 }
54
55 const size = Math.min(controller.desiredSize!, remaining);
56
57 const buffer = new Uint8Array(size);
58 const { bytesRead } = await handle.read(buffer, 0, size, offset);
59
60 if (bytesRead === 0) {
61 controller.close();
62 return;
63 }
64
65 if (bytesRead < size) {
66 controller.enqueue(buffer.subarray(0, bytesRead));
67 } else {
68 controller.enqueue(buffer);
69 }
70
71 remaining -= bytesRead;
72 offset += bytesRead;
73
74 if (remaining <= 0) {
75 controller.close();
76 return;
77 }
78 },
79 }, { highWaterMark: 64 * 1024 });
80 },
81 };
82};